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 {
@Resource
private 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,不是 root
proxy_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;
}