SpringBoot结合Vue 播放 m3u8 格式视频
1、将MP4转M3U8格式
使用ffmpeg 命令
基于命令
ffmpeg -i 1.mp4 -c:v copy -c:a copy -strict -2 -f hls -hls_time 5 -hls_list_size 0 -hls_segment_filename %04d.ts index.m3u8
具体java 工具类如下:
import lombok.extern.slf4j.Slf4j;import java.io.*;/***** @author xuancg* @date 2025/2/25*/
@Slf4j
public class FfmpegUtil {public static void main(String[] args) {File mp4 = new File("C:\\Users\\BHL\\Desktop\\2025-09-20-10-00-48-0.mp4");File dir = new File("C:\\Users\\BHL\\Desktop\\1");System.out.println(convertM3u8(mp4.getAbsolutePath(), dir.getAbsolutePath()));}public static boolean doExeCommd(String cmd) {return new FfmpegUtil().exec(cmd);}/*** 转换为m3u8* ffmpeg -i 1.mp4 -c:v copy -c:a copy -strict -2 -f hls -hls_time 5 -hls_list_size 0 -hls_segment_filename %04d.ts index.m3u8* @param file 原始视频文件的绝对路径* @param dir 转换后的m3u8文件存放的目录*/public static boolean convertM3u8(String file, String dir) {String cmd = String.format("ffmpeg -i \"%s\" -c:v copy -c:a copy -strict -2 -f hls -hls_time 5 -hls_list_size 0 " +" -hls_segment_filename \"%s%s \"%s/index.m3u8\" ", file, dir, "/%04d.ts\"", dir);return doExeCommd(cmd);}private boolean exec(String cmd) {log.info("待执行的命令:" + cmd);try {Runtime rt = Runtime.getRuntime();Process proc = rt.exec(cmd);InputStream stderr = proc.getErrorStream();InputStreamReader isr = new InputStreamReader(stderr);BufferedReader br = new BufferedReader(isr);String line = null;while ((line = br.readLine()) != null){log.debug("执行结果" + line);}return true;} catch (IOException e) {e.printStackTrace();}return false;}
}
2、下载m3u8文件
通用文件下载接口
注意:
针对m3u8格式需要定义特殊的contentType
response.setHeader("Content-Type", "application/vnd.apple.mpegurl");
具体代码如下:
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import javax.annotation.Resource;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;/*** 通用请求处理** @author xuancg*/
@RestController
@RequestMapping("/profile")
@Slf4j
public class ProfileController {@Resourceprivate MinioFileService minioFileService;@GetMapping("/**")public void profile(HttpServletRequest request, HttpServletResponse response) {String path = request.getServletPath();if(path.startsWith("/profile")){path = path.substring(8);}// TODO minio下载,依据实际自定义File tempFile = minioFileService.download(path);if(null != tempFile && tempFile.isFile()){preview(tempFile, response);}}public void preview(File file, HttpServletResponse response) {try(InputStream ins = new FileInputStream(file);ServletOutputStream outStream = response.getOutputStream()) {String fileName = file.getName();// 替换文件名中的非数字、字母、点、下划线fileName = fileName.replaceAll("[^0-9a-zA-Z._-]", "");// 设置响应头response.setHeader("Content-Disposition", "attachment;filename=" + fileName);response.setContentLength(ins.available());// 如果是m3u8 格式 设置if(fileName.endsWith(".m3u8")){response.setHeader("Content-Type", "application/vnd.apple.mpegurl");} else{response.setContentType(FileUtils.getContentType(file.getName()));}// 写入文件内容byte[] buffer = new byte[4096];int bytesRead = -1;while ((bytesRead = ins.read(buffer)) != -1) {outStream.write(buffer, 0, bytesRead);}outStream.flush();} catch (Exception e) {log.error("下载失败", e);}}}
3、创建m3u8播放器
3.1 引入依赖
npm install video.js videojs-contrib-hls --save
3.2 定义播放器
在 component/M3u8Player文件夹下
创建 index.vue 文件
<template><div class="m3u8-player-container"><div class="player-wrapper"><video ref="videoPlayer" class="video-js vjs-big-play-centered" controls:width="width":height="height"><p class="vjs-no-js">请启用JavaScript以观看视频</p></video></div><!-- <div class="control-panel" v-if="showControlPanel"><div class="form-group"><label for="m3u8Url">M3U8地址:</label><input type="text" id="m3u8Url" v-model="videoUrl" placeholder="输入M3U8视频地址"class="url-input"></div><button @click="loadVideo" class="load-button":disabled="!videoUrl">加载视频</button></div> --><div class="status-message" v-if="statusMessage">{{ statusMessage }}</div></div></template><script>import videojs from 'video.js';import 'video.js/dist/video-js.css';import 'videojs-contrib-hls';export default {name: 'M3u8Player',props: {// 视频地址videoUrl: {type: String,default: ''},// 播放器宽度width: {type: String,default: '100%'},// 播放器高度height: {type: String,default: 'auto'},// 是否显示控制面板showControlPanel: {type: Boolean,default: true}},data() {return {player: null,statusMessage: '',baseUrl: process.env.VUE_APP_BASE_API,};},mounted() {// 初始化播放器this.initPlayer();// 如果有初始视频地址,自动加载if (this.videoUrl) {this.loadVideo();}},beforeUnmount() {// 销毁播放器实例if (this.player) {this.player.dispose();this.player = null;}},methods: {// 初始化播放器initPlayer() {const videoElement = this.$refs.videoPlayer;// 创建播放器实例this.player = videojs(videoElement, {autoplay: false,controls: true,responsive: true,fluid: true,sources: []});// 监听错误事件this.player.on('error', (error) => {console.error('视频播放错误:', error);this.statusMessage = '视频播放出错,请检查地址是否正确';});// 监听加载事件this.player.on('loadedmetadata', () => {this.statusMessage = '';});},// 加载视频loadVideo() {if (!this.player) {this.initPlayer();}if (!this.videoUrl) {this.statusMessage = '请输入有效的M3U8地址';return;}// 显示加载状态this.statusMessage = '正在加载视频...';// 设置视频源this.player.src({src: this.baseUrl + this.videoUrl,type: 'application/x-mpegURL' // M3U8格式});// 尝试加载视频this.player.load();}},watch: {// 监听视频地址变化,自动加载新视频videoUrl(newVal) {if (newVal && this.player) {this.loadVideo();}}}};</script><style scoped>.m3u8-player-container {max-width: 1200px;margin: 0 auto;padding: 20px;}.player-wrapper {border-radius: 8px;overflow: hidden;box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);background-color: #000;}.control-panel {margin-top: 20px;display: flex;gap: 10px;align-items: center;}.form-group {flex: 1;}.url-input {width: 100%;padding: 8px 12px;border: 1px solid #ddd;border-radius: 4px;font-size: 14px;}.load-button {padding: 8px 16px;background-color: #42b983;color: white;border: none;border-radius: 4px;cursor: pointer;transition: background-color 0.3s;}.load-button:disabled {background-color: #cccccc;cursor: not-allowed;}.load-button:not(:disabled):hover {background-color: #359e75;}.status-message {margin-top: 10px;padding: 8px;border-radius: 4px;color: #666;font-size: 14px;}</style>
3.3 加载组件
修改main.js 文件
import M3u8Player from '@/components/M3u8Player'Vue.component('M3u8Player', M3u8Player)
4、加载播放
<m3u8-player
:videoUrl="url"
width="100%"
:showControlPanel="true"
/>
5、补充Nginx 代理配置
可以使用本地磁盘 方式 alias /etc/nginx/hls;
或者使用远程资源下载 proxy_pass http://192.168.1.1:8080/profile/hls;
其中 hls 为自定义路径
location /profile/hls {proxy_pass http://192.168.1.1:8080/profile/hls;# alias /etc/nginx/hls/; # 注意:使用 alias,不是 rootproxy_set_header Host $host;proxy_set_header X-Real-IP $remote_addr;proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;add_header Cache-Control no-cache;add_header Access-Control-Allow-Origin *;add_header Access-Control-Allow-Methods GET;# 关键:设置正确的 MIME 类型types {application/vnd.apple.mpegurl m3u8;video/MP2T ts;}default_type application/vnd.apple.mpegurl;}