当前位置: 首页 > article >正文

使用spring boot vue 上传mp4转码为dash并播放

1.前端实现

<template><div class="video-upload"><el-uploadclass="upload-demo"action="/api/upload":before-upload="beforeUpload":on-success="handleSuccess":on-error="handleError":show-file-list="false":data="uploadData":headers="headers"><i class="el-icon-upload"></i><div class="el-upload__text">将视频文件拖到此处,或<em>点击上传</em></div></el-upload><div class="video-preview"><video :src="videoUrl" id="video" ref="videoPlayer"  controls class="w-full"></video></div><div v-if="progress > 0" class="progress-container">转码进度:<el-progress :percentage="progress" :status="progressStatus"></el-progress><p>{{ progressMessage }}</p></div></div>
</template><script>
import axios from 'axios';
import * as dashjs from 'dashjs';
import '../../node_modules/dashjs/dist/modern/esm/dash.mss.min.js';export default {name: 'HelloWorld',data() {return {timerId: null,id: null,uploadData: {title: '',description: ''},headers: {},videoUrl: '',progress: 0,progressStatus: '',progressMessage: '',playerOptions: {autoplay: false,controls: true,sources: []}};},methods: {beforeUpload(file) {const isVideo = /\.(mp4|avi|mov|mkv|flv|wmv)$/i.test(file.name);if (!isVideo) {this.$message.error('只能上传视频文件!');return false;}// 初始化上传状态this.progress = 0;this.progressStatus = '';this.progressMessage = '准备上传...';return true;},async handleSuccess(response, file) {console.log("file",file);if (response.success) {this.progress = 100;this.progressStatus = 'success';this.progressMessage = '上传成功! 转码处理中...';// 开始轮询转码状态await this.pollTranscodingStatus(response.data.taskId);} else {this.handleError(response.message);}},handleError(err) {this.progressStatus = 'exception';this.progressMessage = `上传失败: ${err.message || err}`;console.error('上传错误:', err);},async pollTranscodingStatus(taskId) {try {const res = await axios.get(`/api/transcode/status/${taskId}`);if (res.data.data.status === 'COMPLETED') {this.progressMessage = '转码完成!';          this.id = res.data.data.fileName;this.playVideo(res.data.data.fileName)} else if (res.data.data.status === 'FAILED') {this.progressStatus = 'exception';this.progressMessage = `转码失败: ${res.data.data.message}`;} else {this.progressMessage = `转码进度: ${res.data.data.progress || 0}%`;this.timerId = setTimeout(() => this.pollTranscodingStatus(taskId), 1000);}} catch (err) {this.timerId = setTimeout(() => this.pollTranscodingStatus(taskId), 1000);console.error('获取转码状态失败:', err);}},async playVideo(fileName){const videoId = fileName.substring(0,fileName.lastIndexOf('.'));this.videoUrl = "http://localhost:3000/dashVideo/dash/"+videoId+"/manifest.mpd"const player = dashjs.MediaPlayer().create();player.initialize(document.querySelector('#video'), this.videoUrl, true);}}
};
</script><style scoped>
.video-upload {padding: 20px;
}
.upload-demo {margin-bottom: 20px;
}
.video-preview {margin-top: 20px;
}
.progress-container {margin-top: 20px;
}
</style>

2前端依赖

  "dependencies": {"core-js": "^3.8.3","axios": "^0.18.0","element-ui": "^2.15.14","dashjs": "^5.0.1","vue": "^2.5.2"},

3后端实现

3.1接收文件

    @PostMapping("/upload")public ResponseEntity<?> uploadVideo(@RequestParam("file") MultipartFile file) {try {// 生成唯一文件名String originalFilename = file.getOriginalFilename(); //客户端上传时的完整文件名String extension = originalFilename.substring(originalFilename.lastIndexOf('.'));String filename = UUID.randomUUID().toString() + extension;// 上传原始文件storageService.upload(file, filename);// 创建转码任务String taskId = UUID.randomUUID().toString();TranscodeTask task = new TranscodeTask();task.setOriginalFile(filename);task.setStatus("UPLOADED");transcodeTasks.put(taskId, task);// 异步开始转码transcodeService.transcodeToDash(filename, filename.substring(0, filename.lastIndexOf('.'))).thenAccept(result -> {task.setStatus(result.isSuccess() ? "COMPLETED" : "FAILED");task.setPlayUrl(result.getPlaylistUrl());task.setMessage(result.getMessage());});Map<String,String> taskIdMap = new HashMap<>();taskIdMap.put("taskId", taskId);return ResponseEntity.ok().body(new ApiResponse(true, "上传成功dfgdf", taskIdMap));} catch (Exception e) {return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(new ApiResponse(false, "上传失败: " + e.getMessage(), null));}}

3.2文件转码

@Service
public class VideoTranscodeService {@Value("${video.transcode.ffmpeg-path}")private String ffmpegPath;@Value("${video.transcode.hls-time}")private int hlsTime;@Value("${video.storage.local.path}")private String uploadLocation;@Autowiredprivate StorageService storageService;private Map<String, Double> transcodeprogress = new ConcurrentHashMap<>();// 将本地视频转码为DASH分片(多码率)@Async("asyncTranscodeExecutor")public CompletableFuture<TranscodeResult> transcodeToDash(String filename, String outputBasePath) throws Exception {String outputDir = "../dash/"+outputBasePath + "_dash";Path outputPath = Paths.get(outputDir);Files.createDirectories(outputPath);FFmpegFrameGrabber grabber = new FFmpegFrameGrabber(uploadLocation+"/"+filename);grabber.start();int totalFrames = grabber.getLengthInFrames();System.out.println("totalFrames:"+totalFrames);String outputInitPattern = "init_$RepresentationID$.m4s";String playsegmentPath = "segment_$RepresentationID$_$Number$.m4s";String playmanifestPath = outputDir + "/manifest.mpd";List<String> commands = new ArrayList<>();commands.add(ffmpegPath);commands.add("-i");commands.add(uploadLocation+"/"+filename);commands.add("-map");commands.add("0:v");commands.add("-map");commands.add("0:a");commands.add("-c:v");commands.add("libx264");commands.add("-crf");commands.add("22");commands.add("-profile:v");commands.add("high");commands.add("-level");commands.add("4.2");commands.add("-keyint_min");commands.add("60");commands.add("-g");commands.add("60");commands.add("-sc_threshold");commands.add("0");commands.add("-b:v:0");commands.add("1000k");commands.add("-s:v:0");commands.add("1280x720");commands.add("-b:v:1");commands.add("5000k");commands.add("-s:v:1");commands.add("1920x1080");commands.add("-c:a");commands.add("aac");commands.add("-b:a");commands.add("128k");commands.add("-f");commands.add("dash");commands.add("-seg_duration");commands.add("4");commands.add("-init_seg_name");commands.add(outputInitPattern);commands.add("-media_seg_name");commands.add(playsegmentPath);commands.add(playmanifestPath);ProcessBuilder builder = new ProcessBuilder(commands);builder.redirectErrorStream(true);Process process = builder.start();// 读取输出流try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {String line;while ((line = reader.readLine()) != null) {
//                System.out.println(line); // 可以记录日志或解析进度if (line.contains("frame=")) {// 提取当前帧数int currentFrame = extractFrame(line);System.out.println("currentFrame:"+currentFrame);double progress1 = ((double) currentFrame/totalFrames) * 100;System.out.println("adasdasdasd:"+progress1);transcodeprogress.put(filename, progress1);}}}process.waitFor(); // 等待转码完成int exitCode = process.waitFor();if (exitCode != 0) {throw new RuntimeException("FFmpeg转码失败,退出码: " + exitCode);}return CompletableFuture.completedFuture(new TranscodeResult(true, "转码成功"));}//转码进度计算public double getProgress(String filename) {Double progress = transcodeprogress.get(filename);System.out.println("progress:"+progress);return progress;}private int extractFrame(String logLine) {// 正则匹配 frame= 后的数字(兼容空格和不同分隔符)Pattern pattern = Pattern.compile("frame=\\s*(\\d+)"); // 匹配示例:frame= 123 或 frame=456Matcher matcher = pattern.matcher(logLine);if (matcher.find()) {try {return Integer.parseInt(matcher.group(1)); // 提取捕获组中的数字} catch (NumberFormatException e) {throw new IllegalStateException("帧数解析失败:" + logLine);}}return 0; // 未匹配时返回默认值或抛异常}@Data@AllArgsConstructorpublic static class TranscodeResult {private boolean success;private String message;}
}

3.3转码进度查询

@GetMapping("/transcode/status/{taskId}")public ResponseEntity<?> getTranscodeStatus(@PathVariable String taskId) {TranscodeTask task = transcodeTasks.get(taskId);if (task == null) {return ResponseEntity.notFound().build();}double progres = transcodeService.getProgress(task.getOriginalFile());Map<String, Object> data = new HashMap<>();data.put("status", task.getStatus());data.put("fileName", task.getOriginalFile());data.put("message", task.getMessage());data.put("progress", progres);return ResponseEntity.ok().body(new ApiResponse(true, "查询成功", data));}

3.4视频播放

@RestController
@RequestMapping("/dashVideo")
public class DashController {@Value("${video.storage.local.path}")private String storagePath;@GetMapping("/dash/{videoId}/manifest.mpd")public ResponseEntity<Resource> getDashManifest(@PathVariable String videoId) {String pathStr = "../dash/" + videoId+"_dash/manifest.mpd";Path mpdPath = Paths.get(pathStr);Resource resource = new FileSystemResource(mpdPath);return ResponseEntity.ok().header("Content-Type", "application/dash+xml").body(resource);}@GetMapping("/dash/{videoId}/{segment}")public ResponseEntity<Resource> getSegment(@PathVariable String videoId,@PathVariable String segment) {Path segmentPath = Paths.get("../dash/"+videoId+"_dash/"+segment);Resource resource = new FileSystemResource(segmentPath);return ResponseEntity.ok().header("Content-Type", "video/mp4").body(resource);}}

3.5上传完成后未转码文件位置

在这里插入图片描述
3.6转码后文件位置
在这里插入图片描述
播放的是转码分片后的文件

相关文章:

使用spring boot vue 上传mp4转码为dash并播放

1.前端实现 <template><div class"video-upload"><el-uploadclass"upload-demo"action"/api/upload":before-upload"beforeUpload":on-success"handleSuccess":on-error"handleError":show-file-…...

深入理解指针 (1)

1.内存和地址 1.1内存 1.1.1内存的使用和管理 &#xff08;1&#xff09;内存划分为一个个的内存单元&#xff0c;每个内存单元的大小是1个字节&#xff0c;一个内存单元可以存放8个bit。 &#xff08;2&#xff09;每个内存单元有一个编号&#xff0c;内存单元的编号在计…...

Leetcode98、230:二叉搜索树——递归学习

什么是二叉搜索树&#xff1a;右子树节点 > 根节点 > 左子树节点&#xff0c; 二叉搜索树中的搜索&#xff0c;返回给定值val所在的树节点 终止条件为传进来的节点为空、或者节点的值 val值&#xff0c;返回这个节点&#xff1b; 单程递归逻辑&#xff1a;定义一个resu…...

4/25 研0学习日志

Python学习 python 4个常用的数据容器 list dict tuple set list 列表中数据类型可以不一样 构造方式 mylist["xxx","xxxx"] 获取数据方式 mylist[1] mylist[:4] mylist[-1:] 添加数据 mylist.append() mylist.extern(["aaa","aaaa&…...

15. LangChain多模态应用开发:融合文本、图像与语音

引言&#xff1a;当AI学会"看听说想" 2025年某智慧医院的多模态问诊系统&#xff0c;通过同时分析患者CT影像、语音描述和电子病历&#xff0c;将误诊率降低42%。本文将基于LangChain多模态框架与Deepseek-R1&#xff0c;手把手构建能理解复合信息的智能系统。 一、…...

2022李宏毅老师机器学习课程笔记

机器学习笔记目录 1.绪论&#xff08;内容概述&#xff09;2.机器学习和深度学习的基本概念transformer 1.绪论&#xff08;内容概述&#xff09; 机器学习&#xff1a;让机器找一个函数&#xff0c;通过函数输出想要的结果。应用举例&#xff1a;语音识别&#xff0c;图像识别…...

笔试强训:Day2

一、字符串中找出连续最长的数字串(双指针) 字符串中找出连续最长的数字串_牛客题霸_牛客网 #include <iostream> #include <string> #include <cctype> using namespace std;int main() {//双指针string str;cin>>str;int nstr.size();int begin-1,l…...

linux合并命令(一行执行多个命令)的几种方式总结

背景&#xff1a; 最近安装配置机器&#xff0c;需要手打很多命令。又不能使用docker&#xff0c;所以就使用iTerm2连接多台服务器&#xff0c;然后move session到一个窗口中&#xff0c;shift command i使用XSHELL类似的撰写功能&#xff0c;就可以一次在多台服务器命令窗口…...

基于归纳共形预测的大型视觉-语言模型中预测集的**数据驱动校准**

摘要 本研究通过分离共形预测&#xff08;SCP&#xff09;框架&#xff0c;解决了大型视觉语言模型&#xff08;LVLMs&#xff09;在视觉问答&#xff08;VQA&#xff09;任务中幻觉缓解的关键挑战。虽然LVLMs在多模态推理方面表现出色&#xff0c;但它们的输出常常表现出具有…...

【器件专题1——IGBT第2讲】IGBT 基本工作原理:从结构到特性,一文解析 “电力电子心脏” 的核心机制

IGBT&#xff08;绝缘栅双极型晶体管&#xff0c;Insulated Gate Bipolar Transistor&#xff09;作为现代电力电子领域的核心器件&#xff0c;其工作原理融合了 MOSFET 的高效控制优势与 BJT 的大功率处理能力。本文从物理结构、导通 / 关断机制、核心特性等维度&#xff0c;深…...

【避坑指南】Spring拦截器中instanceof HandlerMethod失效的问题排查

问题背景 最近在使用Spring MVC开发项目时&#xff0c;我遇到了一个诡异的问题&#xff1a;在自定义拦截器的preHandle方法中&#xff0c;明明请求的是Controller层的方法&#xff0c;但handler instanceof HandlerMethod判断却总是返回false&#xff0c;导致拦截逻辑无法正常…...

青少年编程与数学 02-018 C++数据结构与算法 06课题、树

青少年编程与数学 02-018 C数据结构与算法 06课题、树 一、树(Tree)1. 树的定义2. 树的基本术语3. 常见的树类型4. 树的主要操作5. 树的应用 二、二叉树(Binary Tree)1. 二叉树的定义2. 二叉树的基本术语3. 二叉树的常见类型4. 二叉树的主要操作5. 二叉树的实现代码说明输出示例…...

docker学习笔记5-docker中启动Mysql的最佳实践

一、查找目录文件位置 1、mysql的配置文件路径 /etc/mysql/conf.d 2、mysql的数据目录 /var/lib/mysql 3、环境变量 4、端口 mysql的默认端口3306。 二、启动命令 1、启动命令说明 docker run -d -p 3306:3306 -v /app/myconf:/etc/mysql/conf.d # 挂载配置目录 -v…...

从零开始搭建Django博客③--前端界面实现

本文主要在Ubuntu环境上搭建&#xff0c;为便于研究理解&#xff0c;采用SSH连接在虚拟机里的ubuntu-24.04.2-desktop系统搭建&#xff0c;当涉及一些文件操作部分便于通过桌面化进行理解&#xff0c;通过Nginx代理绑定域名&#xff0c;对外发布。 此为从零开始搭建Django博客…...

系统与网络安全------弹性交换网络(3)

资料整理于网络资料、书本资料、AI&#xff0c;仅供个人学习参考。 STP协议 环路的危害 单点故障 PC之间的互通链路仅仅存在1个 任何一条链路出现问题&#xff0c;PC之间都会无法通信 解决办法 提高网络可靠性 增加冗余/备份链路 增加备份链路后交换网络上产生二层环路 …...

Cursor 配置 MCP Tool

文章目录 1、MCP Tool 的集合2、一个 demo :Sequential Thinking2.1、搜索一个 MCP Tool 获取 command 命令2.2、在 Cursor 配置2.3、配置状态检查与修正(解决网络问题)检查解决办法 2.4、使用 1、MCP Tool 的集合 https://smithery.ai/ 2、一个 demo :Sequential Thinking …...

SQL进阶知识:四、索引优化

今天介绍下关于索引优化的详细介绍&#xff0c;并结合MySQL数据库提供实际例子。 索引优化是数据库性能优化的关键环节之一&#xff0c;尤其是在处理大量数据时。索引可以加快查询速度&#xff0c;减少数据扫描范围&#xff0c;但不当的索引设计也可能导致性能问题。以下是关于…...

【Leetcode 每日一题】2799. 统计完全子数组的数目

问题背景 给你一个由 正 整数组成的数组 n u m s nums nums。 如果数组中的某个子数组满足下述条件&#xff0c;则称之为 完全子数组 &#xff1a; 子数组中 不同 元素的数目等于整个数组不同元素的数目。 返回数组中 完全子数组 的数目。 子数组 是数组中的一个连续非空序…...

OpenCV中的SIFT特征提取

文章目录 引言一、SIFT算法概述二、OpenCV中的SIFT实现2.1 基本使用2.1.1 导入库2.1.2 图片预处理2.1.3 创建SIFT检测器2.1.4 检测关键点并计算描述符2.1.5 检测关键点并计算描述符并对关键点可视化2.1.6 印关键点和描述符的形状信息 2.2 参数调优 三、SIFT的优缺点分析3.1 优点…...

【金仓数据库征文】-《深入探索金仓数据库:从基础到实战》

目录 前言 什么是金仓数据库&#xff1f; 金仓数据库的特点 金仓数据库的核心特点 金仓数据库与其他数据库的对比 金仓数据库的安装 常见的语句 总结 前言 为助力开发者、运维人员及技术爱好者快速掌握这一工具&#xff0c;本文将系统性地介绍金仓数据库的核心知识。内…...

RocketMQ 主题与队列的协同作用解析(既然队列存储在不同的集群中,那要主题有什么用呢?)---管理命令、配置安装

学习之前呢需要会使用linux的基础命令 一.RocketMQ 主题与队列的协同作用解析 在 RocketMQ 中&#xff0c;‌主题&#xff08;Topic&#xff09;‌与‌队列&#xff08;Queue&#xff09;‌的协同设计实现了消息系统的逻辑抽象与物理存储分离。虽然队列实际存储在不同集群的 B…...

从岗位依附到能力生态:AI革命下“什么叫就业”的重构与价值

在人工智能(AI)技术深刻重塑社会生产关系的当下,“就业”这一概念正经历着从“职业绑定”到“能力变现”的范式转移。本文将从传统就业观的解构、AI赋能艺术教育的价值逻辑、以及未来就业形态的进化方向三个维度,探讨技术驱动下就业的本质变革,并揭示AI技术如何通过教育创…...

leetcode_二叉树 230. 二叉搜索树中第 K 小的元素

230. 二叉搜索树中第 K 小的元素 给定一个二叉搜索树的根节点 root &#xff0c;和一个整数 k &#xff0c;请你设计一个算法查找其中第 k 小的元素&#xff08;从 1 开始计数&#xff09;。 示例 1&#xff1a; 输入&#xff1a;root [3,1,4,null,2], k 1输出&#xff1a;1…...

海外版高端Apple科技汽车共享投资理财系统

这一款PHP海外版高端Apple、科技汽车、共享投资理财系统phplaravel框架。...

架构-软件架构设计

一、软件架构基础概念 1. 软件架构的定义 通俗理解&#xff1a;软件架构是软件系统的“骨架”&#xff0c;定义了系统的结构、行为和属性&#xff0c;就像盖房子的设计图纸&#xff0c;规划了房间布局、承重结构和功能分区。核心作用&#xff1a; 沟通桥梁&#xff1a;让技术…...

企业为何要禁止“片断引用开源软件代码”?一文看透!

开篇故事&#xff1a;一段“开源代码”引发的百亿级灾难 某电商平台为快速上线新功能&#xff0c;从GitHub复制了一段“高性能加密算法”代码到支付系统中。 半年后&#xff0c;黑客通过该代码中的隐藏后门&#xff0c;盗取百万用户信用卡信息。 事后调查&#xff1a;这段代…...

yolo常用操作(长话短说)热力图,特征图,结构图,训练,测试,预测

训练 from ultralytics import YOLOmodel YOLO(ryolo11n.yaml) # 改为模型文件名model.load(yolo11n.pt) # 权重文件名&#xff0c;官网下载results model.train(datarfish.yaml, # 数据yaml文件epochs300,batch8,device0,workers0,workspace4) yaml文件不会搞的&#xff0…...

【C++指南】告别C字符串陷阱:如何实现封装string?

&#x1f31f; 各位看官好&#xff0c;我是egoist2023&#xff01; &#x1f30d; 种一棵树最好是十年前&#xff0c;其次是现在&#xff01; &#x1f4ac; 注意&#xff1a;本章节只详讲string中常用接口及实现&#xff0c;有其他需求查阅文档介绍。 &#x1f680; 今天通过了…...

国内ip地址怎么改?详细教程

在中国&#xff0c;更改IP地址需要遵守规则&#xff0c;并确保所有操作合规。在特定情况下&#xff0c;可能需要修改IP地址以满足不同需求或解决特定问题。以下是一些常见且合法的IP地址变更方法及注意事项&#xff1a; 一、理解IP地址 IP地址是设备在网络中的唯一标识&#x…...

模式设计简介

设计模式简介 设计模式是软件开发中经过验证的最佳实践解决方案,它是针对特定问题的通用解决方案,能够帮助开发者提升代码的可维护性、可扩展性和复用性。设计模式并非具体的代码实现,而是一种解决问题的思路和方法论,它源于大量的实践经验总结,旨在解决软件开发过程中反…...