前言
最近接到一个需求,用户需要在浏览器上播放上传的视频,然后想到以前学习的学成在线项目中的媒资管理模块有这部分需求,立马开动,
后台服务搭建
作为一个开发者,风骚的走位是必须滴。。。所以就萌发了使用一把gradle的想法。
后端模块使用的是springboot + gradle + jetty
使用gradle遇到的问题
-
gradle 中怎么使用springboot
在maven 中我们引入Springboot 需要在pom.xml中设置依赖即可<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.5.RELEASE</version> </parent>
但是在gradle 中我们需要在build.gradle 文件中设置如下
plugins { id 'java' // 指定springboot的版本 id 'org.springframework.boot' version '2.0.5.RELEASE' // 使用springboot的依赖管理 id 'io.spring.dependency-management' version '1.0.7.RELEASE' } // 依赖 dependencies { /** * implementation和api是取代之前的compile的,其中api和compile是一样的效果, * implementation有所不同,通过implementation依赖的库只能自己库本身访问, * 举个例子,A依赖B,B依赖C,如果B依赖C是使用的implementation依赖, * 那么在A中是访问不到C中的方法的,如果需要访问,请使用api依赖 */ implementation( 'org.springframework.boot:spring-boot-dependencies:2.0.5.RELEASE' ) }
其实仔细来看 gradle 和maven 还是有很多共通之处的,毕竟gradle是吸取了maven和ant而设计出来的
-
gradle 中怎么使用jetty 替代tomcat
configurations { // 排除掉tomcat compile.exclude module: "spring-boot-starter-tomcat" providedRuntime // remove default logger all*.exclude group: 'org.springframework.boot', module: 'spring-boot-starter-logging' } implementation( // ... 省略其他的 /** * 使用jetty替代tomcat * Jetty和Tomcat性能方面差异不大 * Jetty可以同时处理大量连接而且可以长时间保持连接,适合于web聊天应用等等。 * Jetty的架构简单,因此作为服务器,Jetty可以按需加载组件,减少不需要的组件,减少了服务器内存开销,从而提高服务器性能。 * Jetty默认采用NIO结束在处理I/O请求上更占优势,在处理静态资源时,性能较高 * 因为本功能主要是支持视频,图片的读取 所以采用jetty代替tomcat */ 'org.springframework.boot:spring-boot-starter-jetty', )
使用gradle之后的总结
- gradle 实在太灵活了,灵活到idea 没有代码提示。
- 构建太费劲了,最终还是屈服于gradleWrapper了。
- 每次debug 起项目的时候gradle会启动2个控制台 这一点很不能够理解
- gradle的编译速度和打包速度很快
后台实现思路
需求
1. 由于浏览器能够播放的视频格式是有限的,而用户上传的视频格式不确定的,所以我们需要对视频进行处理。
2. 针对于每一个视频,我们需要截图视频中的第一秒画面作为视频封面。
3. 针对于大视频,用户不能感受到明显的卡顿,应该边缓冲边播放。
流程图:
后台代码
service.upload
public MediaFileVo upload(MultipartFile file, String uid) throws Exception {
// 1.保存上传记录
MediaFilePo mediaFilePo = saveFileInfo(file);
// 2.上传文件
saveFileInServer(file, mediaFilePo);
// 3.将文件转换格式 转换格式的过程另外起一个线程去做 使用socket往前台推送进度
MediaFileVo vo = new MediaFileVo();
BeanUtils.copyProperties(mediaFilePo, vo);
executor.execute(() -> {
CurrentConvertVideoSign.put(uid);
try {
MediaFileVo curVo = transform(mediaFilePo);
// 4. 保存图片地址
mediaFilePo.setPicPath(curVo.getPicPath());
mediaFileRepository.save(mediaFilePo);
ConvertProcessVo processVo = new ConvertProcessVo(ConvertProcessVo.END);
processVo.setDesc("转码完成!");
WebSocketServer.sendInfo(CurrentConvertVideoSign.get(), processVo);
} catch (Exception e) {
ConvertProcessVo processVo = new ConvertProcessVo(ConvertProcessVo.EXCEPTION);
processVo.setDesc("转码过程中发生异常!");
WebSocketServer.sendInfo(CurrentConvertVideoSign.get(), processVo);
} finally {
CurrentConvertVideoSign.clear();
}
});
return vo;
}
/**
* 此处实现文件的转换逻辑 在这里设定转换类型
*
* @param po
* @return
*/
public MediaFileVo transform(MediaFilePo po) throws Exception {
MediaFileVo vo = new MediaFileVo();
if (po != null) {
BeanUtils.copyProperties(po, vo);
}
if (needTransform(vo)) {
// 使用文件转换器进行转换
return mediaFileTransverterManager.transform(vo);
}
return vo;
}
MediaFileTransverterManager
/**
* 转换管理器的本身也是文件转换器的一个实现
*/
@Component
public class MediaFileTransverterManager implements MediaFileTransverter {
private Map<String, MediaFileTransverter> transverterMap = new HashMap<>();
/**
* 添加一个转换器
*
* @param sourceTypes 源文件类型
* @param destType 目标类型
* @param transverter 转换器对象
*/
public void addTransverter(String[] sourceTypes, String destType, MediaFileTransverter transverter) {
for (String sourceType : sourceTypes) {
String customKey = sourceType +SPLIT+ destType;
if (transverterMap.containsKey(customKey)) {
throw new RuntimeException(String.format("%s is exist!old is %s ", customKey, transverterMap.get(customKey).toString()));
}
transverterMap.put(customKey, transverter);
}
}
/**
* 获取转换器
* @param mediaFileVo
* @return
*/
public MediaFileTransverter getTransverter(MediaFileVo mediaFileVo){
String convertType = mediaFileVo.getConvertType();
if(StringUtils.isBlank(convertType)){
// 如果转换前后的类型相同,或者没有指定需要转换的类型 那么视为不提供转换器
return null;
}
return transverterMap.get(mediaFileVo.getFileType() +SPLIT+ convertType);
}
@Override
public MediaFileVo transform(MediaFileVo mediaFileVo) throws Exception{
MediaFileTransverter transverter = getTransverter(mediaFileVo);
if(transverter != null){
return transverter.transform(mediaFileVo);
}
return mediaFileVo;
}
}
MediaFileTransverter
public interface MediaFileTransverter extends MediaTypes, MediaConstants {
/**
* 将原本的媒资文件转换成为一个新的格式
* 例如 avi 转换成为 mp4
* @param mediaFileVo 当前的文件对象
* @return
*/
MediaFileVo transform(MediaFileVo mediaFileVo) throws Exception;
}
调用ffmpeg进行视频处理
在后台代码中比较麻烦的就是如何通过ffmpeg 对视频文件进行处理,之前在网上找了很多资料,他们的avi转成mp4之后视频画面都会出现绿色的图案,最终还是选择使用学成在线中的转换命令成功解决这个问题。
-
视频转换成为mp4格式
ffmpeg.exe -i lucene.avi -c:v libx264 -s 1280x720 -pix_fmt yuv420p -b:a 63k -b:v 753k -r 18 .\lucene.mp4 -
截取首页图片
ffmpeg -ss 00:00:01 -i test1.flv -f image2 -y test1.jpg -
mp4格式转换成为m3u8格式
ffmpeg -i lucene.mp4 -hls_time 10 -hls_list_size 0 -hls_segment_filename ./hls/lucene_%05d.ts ./hls/lucene.m3u8
补充
- ffmpeg 支持的格式是有限的,对于其他格式很多方案是先通过mencoder对视频进行处理,然后在使用ffmpeg处理
前台项目搭建
为了贯彻要做就做全套的理念,后台搞了一点骚操作 前台必须也要跟上,所以前台框架选择了react + react-element
使用react 遇到的问题
- react-element 的资料很难找,最开始选框架很重要。
- 由于之前使用的项目都是vue ,使用习惯了v-if v-show 等指令,突然转换成为使用render 构建多少有些不适应。
- 打包发布的时候是空白屏
a> 使用HashRouter 替换BrowserRouter,这一点类似与vue 中的routerd hash模式和history模式
index.js
b> rouer/index.js 中需要设置严格模式,否则会不能通过路由进入具体的组件// 加入路由 import {routes} from './router/index' import {HashRouter} from 'react-router-dom' import {renderRoutes} from 'react-router-config'; ReactDOM.render( (<HashRouter> {renderRoutes(routes)} </HashRouter>), document.getElementById('root') );
import app from './../App' import VideoPlayer from './../component/video/player/VideoPlayer' import VideoCardPage from './../component/video/VideoCardPage' const routes = [ { path: '/', component: app, exact: true }, { path: '/video/player/:id', component: VideoPlayer, exact: true }, { path: '/video/cardPage', component: VideoCardPage, exact: true } ] export {routes}
效果图
文件上传中
开始截取封面
进行转码
总结
做技术 还是要折腾 ,虽然折腾的过程中很累,有句话说的好,付出不一定有回报,但是不付出一定没有,经历了这个项目,让我对视频流的处理有了一个简单的认识,了解到了gradle,react 这些以前没有用过的技术,奥里给!