Java 推送视频到html页面展示
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连接建立时调用
*/
@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();
}
}
- 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("*");
}
}
- 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>
浙公网安备 33010602011771号