Java 推送视频到html页面展示

springboot 推送视频到前端页面展示

一. http推送到前端显示

  1. java读取视频发送
    这种找视频文件需要在项目根目录下创建videos目录,将视频放置后视频根目录下
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.InputStreamResource;
import org.springframework.core.io.Resource;
import org.springframework.http.*;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;

import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

@RestController
@RequestMapping("/api/video")
public class VideoController {

    // 视频文件存储路径,可以根据需要修改
    private static final String VIDEO_PATH = "videos/";

    /**
     * 获取视频文件
     *
     * @param filename 视频文件名
     * @return 视频资源
     */
    @GetMapping("/{filename}")
    public ResponseEntity<Resource> getVideo(@PathVariable String filename) {
        try {
            Path filePath = Paths.get(VIDEO_PATH, filename);
            Resource resource = new FileSystemResource(filePath);

            if (!resource.exists()) {
                return ResponseEntity.notFound().build();
            }

            // 获取文件的媒体类型
            String contentType = Files.probeContentType(filePath);
            if (contentType == null) {
                contentType = "application/octet-stream";
            }

            return ResponseEntity.ok()
                    .contentType(MediaType.parseMediaType(contentType))
                    .body(resource);
        } catch (IOException e) {
            return ResponseEntity.internalServerError().build();
        }
    }

    /**
     * 支持范围请求的视频流服务(适用于大文件)
     *
     * @param filename 视频文件名
     * @param range    Range请求头
     * @return 视频流
     */
    @GetMapping("/stream/{filename}")
    public ResponseEntity<StreamingResponseBody> streamVideo(
            @PathVariable String filename,
            @RequestHeader(value = "Range", required = false) String range) {

        try {
            Path filePath = Paths.get(VIDEO_PATH, filename);

            if (!Files.exists(filePath)) {
                return ResponseEntity.notFound().build();
            }

            long fileSize = Files.size(filePath);

            // 处理范围请求
            if (range == null) {
                // 没有范围请求,返回整个文件
                InputStreamResource resource = new InputStreamResource(new FileInputStream(filePath.toFile()));

                return ResponseEntity.status(HttpStatus.OK)
                        .contentType(MediaType.valueOf("video/mp4"))
                        .contentLength(fileSize)
                        .body(outputStream -> {
                            try (InputStream inputStream = new FileInputStream(filePath.toFile())) {
                                inputStream.transferTo(outputStream);
                            }
                        });
            } else {
                // 解析Range请求头
                long rangeStart = 0;
                long rangeEnd = fileSize - 1;

                if (range.startsWith("bytes=")) {
                    String[] ranges = range.substring(6).split("-");
                    rangeStart = Long.parseLong(ranges[0]);
                    if (ranges.length > 1 && !ranges[1].isEmpty()) {
                        rangeEnd = Long.parseLong(ranges[1]);
                    }
                }

                long dataLength = rangeEnd - rangeStart + 1;

                // 创建响应
                final long finalRangeStart = rangeStart;
                final long finalRangeEnd = rangeEnd;

                return ResponseEntity.status(HttpStatus.PARTIAL_CONTENT)
                        .contentType(MediaType.valueOf("video/mp4"))
                        .header("Accept-Ranges", "bytes")
                        .header("Content-Range", "bytes " + finalRangeStart + "-" + finalRangeEnd + "/" + fileSize)
                        .contentLength(dataLength)
                        .body(outputStream -> {
                            try (RandomAccessFile file = new RandomAccessFile(filePath.toFile(), "r")) {
                                file.seek(finalRangeStart);
                                byte[] buffer = new byte[1024];
                                long remaining = dataLength;

                                while (remaining > 0) {
                                    int read = file.read(buffer, 0, (int) Math.min(buffer.length, remaining));
                                    if (read == -1) break;
                                    outputStream.write(buffer, 0, read);
                                    remaining -= read;
                                }
                            }
                        });
            }
        } catch (Exception e) {
            return ResponseEntity.internalServerError().build();
        }
    }
}
  1. html展示页面
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body><!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>HTTP视频播放器</title>
  <style>
    body {
      font-family: Arial, sans-serif;
      max-width: 800px;
      margin: 0 auto;
      padding: 20px;
    }
    .video-container {
      margin: 20px 0;
    }
    video {
      width: 100%;
      height: auto;
      background-color: #000;
    }
    .controls {
      margin: 20px 0;
    }
    input[type="text"] {
      width: 70%;
      padding: 10px;
      margin-right: 10px;
    }
    button {
      padding: 10px 20px;
      background-color: #007bff;
      color: white;
      border: none;
      cursor: pointer;
    }
    button:hover {
      background-color: #0056b3;
    }
  </style>
</head>
<body>
  <h1>HTTP视频播放器</h1>

  <div class="controls">
    <input type="text" id="videoUrl" placeholder="请输入视频URL,例如:http://example.com/video.mp4">
    <button onclick="loadVideo()">加载视频</button>
  </div>

  <div class="video-container">
    <video id="videoPlayer" controls>
      您的浏览器不支持视频播放。
    </video>
  </div>

  <div>
    <h3>使用说明:</h3>
    <ul>
      <li>输入支持HTTP协议的视频文件URL</li>
      <li>支持格式:MP4, WebM, Ogg</li>
      <li>点击"加载视频"按钮开始播放</li>
    </ul>
  </div>

  <script>
    function loadVideo() {
      const videoUrl = document.getElementById('videoUrl').value;
      const videoPlayer = document.getElementById('videoPlayer');

      if (!videoUrl) {
        alert('请输入视频URL');
        return;
      }

      videoPlayer.src = videoUrl;
      videoPlayer.load();
      videoPlayer.play().catch(error => {
        console.error('播放失败:', error);
        alert('视频播放失败,请检查URL是否正确');
      });
    }

    // 示例URL(需要替换为实际可用的HTTP视频地址)
    // document.getElementById('videoUrl').value = 'http://example.com/sample.mp4';
  </script>
</body>
</html>

</body>
</html>

二. webScoket推送视频后前端展示

  1. java WebSocket视频推送
import org.springframework.stereotype.Component;
import org.springframework.web.socket.BinaryMessage;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.AbstractWebSocketHandler;

import java.io.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

@Component
public class VideoHandler extends AbstractWebSocketHandler {

    // 存储所有活跃的WebSocket会话
    private final ConcurrentHashMap<String, WebSocketSession> sessions = new ConcurrentHashMap<>();
    private final ConcurrentHashMap<String, Boolean> streamingStatus = new ConcurrentHashMap<>();

    // 线程池用于处理视频传输
    private final ExecutorService executorService = Executors.newCachedThreadPool();

    // 视频文件路径
    private static final String VIDEO_FILE_PATH = "videos/1.mp4";

    // 视频缓冲区大小
    private static final int BUFFER_SIZE = 1024 * 1024;

    /**
     * 当WebSocket连接建立时调用
     */
    @Override
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
        sessions.put(session.getId(), session);
        streamingStatus.put(session.getId(), false);
        System.out.println("WebSocket连接已建立: " + session.getId());

        // 发送连接成功消息
        session.sendMessage(new TextMessage("{\"type\":\"connected\", \"message\":\"连接成功\"}"));
    }

    /**
     * 处理收到的文本消息
     */
    @Override
    protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
        String payload = message.getPayload();
        System.out.println("收到消息: " + payload);

        // 解析JSON消息
        if (payload.contains("\"action\":\"start\"")) {
            // 开始发送视频
            startVideoStreaming(session);
        } else if (payload.contains("\"action\":\"stop\"")) {
            // 停止发送视频
            stopVideoStreaming(session.getId());
        }
    }

    /**
     * 处理二进制消息(如果需要)
     */
    @Override
    protected void handleBinaryMessage(WebSocketSession session, BinaryMessage message) throws Exception {
        // 这里可以处理客户端发送的二进制数据
        super.handleBinaryMessage(session, message);
    }

    /**
     * 当WebSocket连接关闭时调用
     */
    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
        sessions.remove(session.getId());
        streamingStatus.remove(session.getId());
        System.out.println("WebSocket连接已关闭: " + session.getId() + ", 状态: " + status);
    }

    /**
     * 启动视频流传输
     */
    private void startVideoStreaming(WebSocketSession session) {
        String sessionId = session.getId();
        streamingStatus.put(sessionId, true);

        // 在单独的线程中处理视频传输
        executorService.submit(() -> {
            try {
                File videoFile = new File(VIDEO_FILE_PATH);

                if (!videoFile.exists()) {
                    session.sendMessage(new TextMessage("{\"type\":\"error\", \"message\":\"视频文件不存在\"}"));
                    return;
                }

                // 发送开始传输消息
                session.sendMessage(new TextMessage("{\"type\":\"info\", \"message\":\"开始传输视频\"}"));

                // 打开文件输入流
                try (FileInputStream fis = new FileInputStream(videoFile);
                     BufferedInputStream bis = new BufferedInputStream(fis)) {

                    byte[] buffer = new byte[BUFFER_SIZE];
                    int bytesRead;

                    // 持续读取并发送视频数据
                    while ((bytesRead = bis.read(buffer)) != -1 &&
                           sessions.containsKey(sessionId) &&
                           streamingStatus.getOrDefault(sessionId, false)) {

                        // 创建要发送的字节数据
                        byte[] dataToSend = new byte[bytesRead];
                        System.arraycopy(buffer, 0, dataToSend, 0, bytesRead);

                        // 发送二进制消息
                        session.sendMessage(new BinaryMessage(dataToSend));

                        // 添加小延迟以控制传输速度(可根据需要调整)
                        try {
                            Thread.sleep(5); // 减少延迟以提高传输速度
                        } catch (InterruptedException e) {
                            Thread.currentThread().interrupt();
                            break;
                        }
                    }
                }

                // 发送传输完成消息
                if (sessions.containsKey(sessionId)) {
                    session.sendMessage(new TextMessage("{\"type\":\"info\", \"message\":\"视频传输完成\"}"));
                }

            } catch (Exception e) {
                System.err.println("视频传输错误: " + e.getMessage());
                e.printStackTrace();

                try {
                    if (sessions.containsKey(sessionId)) {
                        session.sendMessage(new TextMessage("{\"type\":\"error\", \"message\":\"视频传输错误: " + e.getMessage() + "\"}"));
                    }
                } catch (Exception ex) {
                    System.err.println("发送错误消息失败: " + ex.getMessage());
                }
            } finally {
                streamingStatus.put(sessionId, false);
            }
        });
    }

    /**
     * 停止视频流传输
     */
    private void stopVideoStreaming(String sessionId) {
        streamingStatus.put(sessionId, false);
        System.out.println("已停止会话 " + sessionId + " 的视频传输");
    }

    /**
     * 发生错误时调用
     */
    @Override
    public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
        System.err.println("WebSocket传输错误 (会话ID: " + session.getId() + "): " + exception.getMessage());
        sessions.remove(session.getId());
        streamingStatus.remove(session.getId());
    }

    /**
     * 销毁Bean时关闭线程池
     */
    public void destroy() {
        executorService.shutdown();
    }
}
  1. socket接口配置

import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;

@Configuration
@EnableWebSocket
public class WebsocketConfig implements WebSocketConfigurer {

    private final VideoHandler videoHandler;

    public WebsocketConfig(VideoHandler videoHandler) {
        this.videoHandler = videoHandler;
    }

    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        registry.addHandler(httpAuthHandler, "ws").setAllowedOrigins("*");
        registry.addHandler(videoHandler, "video-stream").setAllowedOrigins("*");
    }

}


  1. webSocket页面展示
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>WebSocket视频接收器</title>
  <style>
    body {
      font-family: Arial, sans-serif;
      max-width: 1000px;
      margin: 0 auto;
      padding: 20px;
      background-color: #f5f5f5;
    }

    .container {
      background-color: white;
      border-radius: 8px;
      padding: 20px;
      box-shadow: 0 2px 10px rgba(0,0,0,0.1);
    }

    h1 {
      text-align: center;
      color: #333;
    }

    .video-container {
      margin: 20px 0;
      text-align: center;
    }

    #videoPlayer {
      width: 100%;
      max-width: 800px;
      height: auto;
      background-color: #000;
      border-radius: 4px;
    }

    .controls {
      display: flex;
      justify-content: center;
      gap: 10px;
      margin: 20px 0;
      flex-wrap: wrap;
    }

    button {
      padding: 12px 24px;
      background-color: #007bff;
      color: white;
      border: none;
      border-radius: 4px;
      cursor: pointer;
      font-size: 16px;
    }

    button:hover {
      background-color: #0056b3;
    }

    button:disabled {
      background-color: #cccccc;
      cursor: not-allowed;
    }

    .status {
      text-align: center;
      padding: 10px;
      margin: 10px 0;
      border-radius: 4px;
    }

    .status.connected {
      background-color: #d4edda;
      color: #155724;
      border: 1px solid #c3e6cb;
    }

    .status.disconnected {
      background-color: #f8d7da;
      color: #721c24;
      border: 1px solid #f5c6cb;
    }

    .info {
      background-color: #e2e3e5;
      color: #383d41;
      padding: 15px;
      border-radius: 4px;
      margin-top: 20px;
    }

    .progress-container {
      width: 100%;
      max-width: 800px;
      margin: 20px auto;
    }

    progress {
      width: 100%;
      height: 20px;
    }
  </style>
</head>
<body>
  <div class="container">
    <h1>WebSocket视频接收器</h1>

    <div id="status" class="status disconnected">
      WebSocket连接状态: 未连接
    </div>

    <div class="controls">
      <button id="connectBtn">连接WebSocket</button>
      <button id="disconnectBtn" disabled>断开连接</button>
      <button id="startVideoBtn" disabled>开始接收视频</button>
      <button id="stopVideoBtn" disabled>停止接收视频</button>
    </div>

    <div class="progress-container">
      <progress id="progressBar" value="0" max="100" style="display: none;"></progress>
      <div id="progressText"></div>
    </div>

    <div class="video-container">
      <video id="videoPlayer" controls autoplay muted>
        您的浏览器不支持视频播放。
      </video>
    </div>

    <div class="info">
      <h3>使用说明:</h3>
      <ul>
        <li>点击"连接WebSocket"连接到后端服务</li>
        <li>连接成功后,点击"开始接收视频"开始接收视频流</li>
        <li>视频数据通过WebSocket以二进制格式传输</li>
        <li>默认连接地址: ws://localhost:8080/video-stream</li>
      </ul>
    </div>
  </div>

  <script>
    class WebSocketVideoClient {
      constructor() {
        this.ws = null;
        this.videoPlayer = document.getElementById('videoPlayer');
        this.statusElement = document.getElementById('status');
        this.connectBtn = document.getElementById('connectBtn');
        this.disconnectBtn = document.getElementById('disconnectBtn');
        this.startVideoBtn = document.getElementById('startVideoBtn');
        this.stopVideoBtn = document.getElementById('stopVideoBtn');
        this.progressBar = document.getElementById('progressBar');
        this.progressText = document.getElementById('progressText');

        // 用于存储接收到的视频数据
        this.videoChunks = [];
        this.totalBytesReceived = 0;
        this.isReceiving = false;

        this.initEventListeners();
      }

      initEventListeners() {
        this.connectBtn.addEventListener('click', () => this.connect());
        this.disconnectBtn.addEventListener('click', () => this.disconnect());
        this.startVideoBtn.addEventListener('click', () => this.startVideo());
        this.stopVideoBtn.addEventListener('click', () => this.stopVideo());
      }

      connect() {
        try {
          // 连接到WebSocket服务器
          this.ws = new WebSocket('ws://localhost:9000/video-stream');

          this.ws.binaryType = 'arraybuffer';

          this.ws.onopen = () => {
            this.updateStatus('已连接到WebSocket服务器', 'connected');
            this.connectBtn.disabled = true;
            this.disconnectBtn.disabled = false;
            this.startVideoBtn.disabled = false;
          };

          this.ws.onmessage = (event) => {
            if (event.data instanceof ArrayBuffer) {
              this.handleVideoData(event.data);
            } else {
              this.handleTextMessage(event.data);
            }
          };

          this.ws.onclose = () => {
            this.updateStatus('WebSocket连接已断开', 'disconnected');
            this.connectBtn.disabled = false;
            this.disconnectBtn.disabled = true;
            this.startVideoBtn.disabled = true;
            this.stopVideoBtn.disabled = true;
          };

          this.ws.onerror = (error) => {
            this.updateStatus('WebSocket连接错误: ' + error, 'disconnected');
          };
        } catch (error) {
          this.updateStatus('连接失败: ' + error, 'disconnected');
        }
      }

      disconnect() {
        if (this.ws) {
          this.ws.close();
        }
      }

      startVideo() {
        if (this.ws && this.ws.readyState === WebSocket.OPEN) {
          this.ws.send(JSON.stringify({action: 'start'}));
          this.startVideoBtn.disabled = true;
          this.stopVideoBtn.disabled = false;
          this.updateStatus('开始接收视频流...', 'connected');

          // 重置视频数据
          this.videoChunks = [];
          this.totalBytesReceived = 0;
          this.isReceiving = true;
          this.progressBar.style.display = 'block';
          this.progressBar.value = 0;
          this.progressText.textContent = '开始接收视频数据...';
        }
      }

      stopVideo() {
        if (this.ws && this.ws.readyState === WebSocket.OPEN) {
          this.ws.send(JSON.stringify({action: 'stop'}));
          this.startVideoBtn.disabled = false;
          this.stopVideoBtn.disabled = true;
          this.updateStatus('已停止接收视频流', 'connected');
          this.isReceiving = false;
        }
      }

      handleVideoData(data) {
        if (!this.isReceiving) return;

        // 将接收到的数据块添加到数组中
        this.videoChunks.push(data);
        this.totalBytesReceived += data.byteLength;

        // 更新进度显示
        this.progressText.textContent = `已接收: ${Math.round(this.totalBytesReceived / 1024)} KB`;

        // 当接收完成时创建视频URL并播放
        // 注意:实际应用中应该由服务端发送完成信号
        // 这里我们简单地在接收到一定数据后尝试播放
      }

      handleTextMessage(data) {
        try {
          const message = JSON.parse(data);
          if (message.type === 'info' && message.message === '视频传输完成') {
            this.createAndPlayVideo();
            this.progressBar.value = 100;
            this.progressText.textContent = `视频接收完成,共 ${Math.round(this.totalBytesReceived / 1024)} KB`;
          } else if (message.type === 'info') {
            this.updateStatus(message.message, 'connected');
          } else if (message.type === 'error') {
            this.updateStatus('错误: ' + message.message, 'disconnected');
          } else if (message.type === 'connected') {
            this.updateStatus(message.message, 'connected');
          }
        } catch (e) {
          console.log('收到文本消息:', data);
        }
      }

      createAndPlayVideo() {
        if (this.videoChunks.length === 0) return;

        // 创建Blob对象
        const blob = new Blob(this.videoChunks, { type: 'video/mp4' });

        // 创建对象URL
        const videoUrl = URL.createObjectURL(blob);

        // 设置视频源
        this.videoPlayer.src = videoUrl;

        // 视频加载完成后的处理
        this.videoPlayer.onloadedmetadata = () => {
          console.log('视频元数据加载完成');
        };

        this.videoPlayer.oncanplay = () => {
          console.log('视频可以播放');
          // 可以选择自动播放
          // this.videoPlayer.play().catch(e => console.log('自动播放失败:', e));
        };

        this.updateStatus('视频接收完成,可以播放', 'connected');
      }

      updateStatus(message, type) {
        this.statusElement.textContent = message;
        this.statusElement.className = 'status ' + type;
      }
    }

    // 初始化客户端
    document.addEventListener('DOMContentLoaded', () => {
      const client = new WebSocketVideoClient();

      // 页面卸载时关闭连接
      window.addEventListener('beforeunload', () => {
        if (client.ws) {
          client.ws.close();
        }
      });
    });
  </script>
</body>
</html>

posted @ 2025-08-08 20:15  wds09  阅读(84)  评论(0)    收藏  举报