springboot 推送视频到前端页面展示
一. http推送到前端显示
- 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();}}
}
- 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推送视频后前端展示
- 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连接建立时调用*/@Overridepublic 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\":\"连接成功\"}"));}/*** 处理收到的文本消息*/@Overrideprotected 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());}}/*** 处理二进制消息(如果需要)*/@Overrideprotected void handleBinaryMessage(WebSocketSession session, BinaryMessage message) throws Exception {// 这里可以处理客户端发送的二进制数据super.handleBinaryMessage(session, message);}/*** 当WebSocket连接关闭时调用*/@Overridepublic 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 + " 的视频传输");}/*** 发生错误时调用*/@Overridepublic 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();}
}
- 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;}@Overridepublic void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {registry.addHandler(httpAuthHandler, "ws").setAllowedOrigins("*");registry.addHandler(videoHandler, "video-stream").setAllowedOrigins("*");}}
- 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' });// 创建对象URLconst 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>