免费视频 *** 观看(如何实现在线视频播放?)
如何在电脑上播放DVD,如何播放在线视频?
视频点播要求如下
1.学生可以在windows浏览器上在线观看视频。
2.播放器具有快进、快退、暂停等基本功能。
视频点播
1. 视频点播需求分析
流媒体详细参考https://baike.baidu.com/item/流媒体/98740?fr=阿拉丁一般理解流媒体就是将视频文件分割成许多小片段,并将这些小片段作为数据包通过 发送,从而实现在传输视频数据包的观看视频。
流式传输在 上传输音视频信息有两种方式下载和流式传输。
下载音视频文件全部下载到本机,然后开始播放。其特点是视频文件下载后才能播放,播放等待时间长,无法播放部分未下载的视频。
流式传输指客户端通过链接视频服务器实时传输音视频信息,从而实现 边下载边玩。。
流式传输包括以下两种方式
1)顺序流式传输
即音视频文件可以顺序下载,可以边下载边播放。但用户只能观看已下载的视频内容,不能快进到未下载的视频部分。使用Http服务器,如Nginx、Apache等,可以实现顺序流传输。
2)实时流传输
实时流可以解决顺序流不能快进的问题。它不同于Http流。它必须使用流媒体服务器和流媒体协议来传输视频,比Http流更复杂。常见的实时流协议包括RTSP、RTMP、RSVP等。
流媒体系统概要结构通过流媒体系统概要结构学习流媒体系统的基本业务流程。
1.编码器将原始视频文件转换成适合 传输的流格式,编码后的视频直接传输到媒体服务器。
原始视频文件通常是预先录制好的,比如摄像机、照相机等录制设备采集的音视频文件,这些文件的大小都比较大。如果要在 上传输,就需要对它们进行压缩,也就是用编码器进行编码。
2.媒体服务获取编码后的视频文件,并向外界提供流媒体数据传输接口。接口协议包括HTTP、RTSP、RTMP等。
3.播放器通过流媒体协议与媒体服务器通信,获取视频数据并播放视频。
1.1 需求描述
本项目包括点播和直播两种方式。让 让我们研究按需方案,如下所示1.播放器通过http协议从http服务器下载视频文件并播放。
问题必须等到视频下载完成后才能播放。不支持快进到某个时间点。
2.播放器通过rtmp协议连接到媒体服务器,以实时流模式播放视频。
使用rtmp协议需要架设媒体服务器,费用较高,所以直播经常采用这种方案。
3.播放器使用HLS协议连接http服务器(Nginx,Apache等。)以接近实时的流模式播放视频。
HLS协议规定基于Http协议,视频打包格式为ts,视频编码格式为H264,音频编码格式为MP3、AAC或AC-3。
什么是HLS?HLS的工作模式是将视频分成若干个ts格式的小文件,这些小ts文件由m3u8格式的索引文件进行索引。一般每10秒就有一个ts文件,播放器连接m3u8文件播放。快进时,可以通过m3u8找到相应的索引文件,下载相应的ts文件,实现快进快退,以接近实时的方式播放视频。
IOS、Android设备和各大浏览器都支持HLS协议。
详细参考https://baike.baidu.com/item/HLS/8328931? fr=阿拉丁
采用HLS方案可以边下载边播放,不需要使用rtmp等流媒体协议和搭建专门的媒体服务器,节约了成本。
本项目点播方案确定为方案3。
1.2 视频点播解决方案
我们录完视频后,用视频编码软件对视频进行编码。在这个项目中,FFmpeg被用来编码视频。下载ffmpeg-2018 02 27-fa 0 c 9d 6-win 64-static . zip,解压。本教程将ffmpeg解压缩到C: \ Java _ soft \ xczx \ ffmpeg
FFmpeg已被许多开源项目采用,如 视频、暴风影音、VLC等。
下载FFMPEG https://www.ffmpeg.org/download.html#build-windows
在path环境变量中配置c : \ Java _ soft \ xczx \ ffmpeg-2018 02 27-fa 0 c 9d 6-win 64-static \ bin目录。
简单测试
转换一个。avi文件转换成mp4、mp3、gif等。
例如,我们将lucene.avi文件转换成
mp4,运行如下命令ffmpeg -i lucene.avi lucene.mp4
转成mp3ffmpeg -i lucene.avi lucene.mp3
转成gifffmpeg -i lucene.avi lucene.gif
官方文档(英文)http://ffmpeg.org/ffmpeg.html
2.1 生成m3u8/ts文件
使用ffmpeg生成 m3u8的步骤如下
之一步先将avi视频转成mp4
ffmpeg.exe -i lucene.avi -c:v libx264 -s 1280x720 -pix_fmt yuv420p -b:a 63k -b:v 753k -r 18 .\lucene.mp4
下面把各参数意思大概讲讲,大概了解意思即可,不再此展开流媒体专业知识的讲解。
-c:v 视频编码为x264 ,x264编码是H264的一种开源编码格式。
-s 设置分辨率
-pix_fmt yuv420p设置像素采样方式,主流的采样方式有三种,YUV4:4:4,YUV4:2:2,YUV4:2:0,它的作用是根据采样方式来从码流中还原每个像素点的YUV(亮度信息与色彩信息)值。
-b 设置码率,-b:a和-b:v分别表示音频的码率和视频的码率,-b表示音频加视频的总码率。码率对一个视频质量有很大的作用。
-r帧率,表示每秒更新图像画面的次数,通常大于24肉眼就没有连贯与停顿的感觉了。
第二步将mp4生成m3u8
ffmpeg -i lucene.mp4 nbsp -hls_time 10 -hls_list_size 0 nbsp-hls_segment_filename ./hls/lucene_%05d.ts ./hls/lucene.m3u8
-hls_time 设置每片的长度,单位为秒
-hls_list_size n: 保存的分片的数量,设置为0表示保存所有分片
-hls_segment_filename 段文件的名称,%05d表示5位数字
生成的效果是将lucene.mp4视频文件每10秒生成一个ts文件,生成一个m3u8文件,m3u8文件是ts的索引文件。
使用VLC打开m3u8文件,测试播放效果,VLC 是一款自由、开源的跨平台多媒体播放器及框架,可播放大多数多媒体文件,以及 DVD、音频 CD、VCD 及各类流媒体协议。(http://www.videolan.org/)
3. 视频(媒资)处理
开发环境
1. 创建媒资数据库
导入shcool.sql
2. 创建媒资服务工程
基于springboot创建工程
3. 上传文件
3.1 断点续传解决方案
通常视频文件都比较大,所以对于媒资系统上传文件的需求要满足大文件的上传要求。http协议本身对上传文件大小没有限制,客户的 环境质量、电脑硬件环境等参差不齐,如果一个大文件快上传完了网断了,电断了没有上传完成,需要客户重新上传,这是致命的,所以对于大文件上传的要求最基本的是断点续传。
什么是断点续传
引用百度百科断点续传指的是在下载或上传时,将下载或上传任务(一个文件或一个压缩包)人为的划分为几个部分,每一个部分采用一个线程进行上传或下载,如果碰到 故障,可以从已经上传或下载的部分开始继续上传下载未完成的部分,而没有必要从头开始上传下载,断点续传可以提高节省操作时间,提高用户体验性。
上传流程如下
1、上传前先把文件分成块
2、一块一块的上传,上传中断后重新上传,已上传的分块则不用再上传
3、各分块上传完成合并文件
文件下载则同理。
3.2 文件分块与合并
为了更好的理解文件分块上传的原理,下边用java代码测试文件的分块与合并。
3.3 文件分块
文件分块的流程如下
1、获取源文件长度
2、根据设定的分块文件的大小计算出块数
3、从源文件读数据依次向每一个块文件写数据
//测试文件分块 nbsp nbsp@Test nbsp nbsppublic void testChunk() throws IOException { nbsp nbsp nbsp nbspFile sourceFile = new File(#34F:/develop/ffmpeg/lucene.mp4#34)// nbsp nbsp nbsp File sourceFile = new File(#34d:/logo.png#34) nbsp nbsp nbsp nbspString chunkPath = #34F:/develop/ffmpeg/chunk/#34 nbsp nbsp nbsp nbspFile chunkFolder = new File(chunkPath) nbsp nbsp nbsp nbspif(!chunkFolder.exists()){ nbsp nbsp nbsp nbsp nbsp nbspchunkFolder.mkdirs() nbsp nbsp nbsp } nbsp nbsp nbsp nbsp//分块大小 nbsp nbsp nbsp nbsplong chunkSize = 102410241 nbsp nbsp nbsp nbsp//分块数量 nbsp nbsp nbsp nbsplong chunkNum = (long) Math.ceil(sourceFile.length() 1.0 / chunkSize ) nbsp nbsp nbsp nbspif(chunkNumlt=0){ nbsp nbsp nbsp nbsp nbsp nbspchunkNum = 1 nbsp nbsp nbsp } nbsp nbsp nbsp nbsp//缓冲区大小 nbsp nbsp nbsp nbspbyte[] b = new byte[1024] nbsp nbsp nbsp nbsp//使用RandomAccessFile访问文件 nbsp nbsp nbsp nbspRandomAccessFile raf_read = new RandomAccessFile(sourceFile, #34r#34) nbsp nbsp nbsp nbsp//分块 nbsp nbsp nbsp nbspfor(int i=0iltchunkNumi++){ nbsp nbsp nbsp nbsp nbsp nbsp//创建分块文件 nbsp nbsp nbsp nbsp nbsp nbspFile file = new File(chunkPath+i) nbsp nbsp nbsp nbsp nbsp nbspboolean newFile = file.createNewFile() nbsp nbsp nbsp nbsp nbsp nbspif(newFile){ nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp//向分块文件中写数据 nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbspRandomAccessFile raf_write = new RandomAccessFile(file, #34rw#34) nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbspint len = -1 nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbspwhile((len = raf_read.read(b))!=-1){ nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbspraf_write.write(b,0,len) nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbspif(file.length()gtchunkSize){ nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbspbreak nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp } nbsp nbsp nbsp nbsp nbsp nbsp nbsp } nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbspraf_write.close() nbsp nbsp nbsp nbsp nbsp } nbsp nbsp nbsp } nbsp nbsp nbsp nbspraf_read.close()? nbsp }
3.4 文件合并
文件合并流程
1、找到要合并的文件并按文件合并的先后进行排序
2、创建合并文件
3、依次从合并的文件中读取数据向合并文件写入数据
//测试文件合并 @Testpublic void testMerge() throws IOException { nbsp nbsp//块文件目录 nbsp nbspFile chunkFolder = new File(#34F:/develop/ffmpeg/chunk/#34) nbsp nbsp//合并文件 nbsp nbspFile mergeFile = new File(#34F:/develop/ffmpeg/lucene1.mp4#34) nbsp nbspif(mergeFile.exists()){ nbsp nbsp nbsp nbspmergeFile.delete() nbsp } nbsp nbsp//创建新的合并文件 nbsp nbspmergeFile.createNewFile() nbsp nbsp//用于写文件 nbsp nbspRandomAccessFile raf_write = new RandomAccessFile(mergeFile, #34rw#34) nbsp nbsp//指针指向文件顶端 nbsp nbspraf_write.seek(0) nbsp nbsp//缓冲区 nbsp nbspbyte[] b = new byte[1024] nbsp nbsp//分块列表 nbsp nbspFile[] fileArray = chunkFolder.listFiles() nbsp nbsp// 转成 ,便于排序 nbsp nbspListltFilegt fileList = new ArrayListltFilegt(Arrays.asList(fileArray)) nbsp nbsp// 从小到大排序 nbsp nbspCollections.sort(fileList, new ComparatorltFilegt() { nbsp nbsp nbsp nbsp@Override nbsp nbsp nbsp nbsppublic int compare(File o1, File o2) { nbsp nbsp nbsp nbsp nbsp nbspif (Integer.parseInt(o1.getName()) lt Integer.parseInt(o2.getName())) { nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbspreturn -1 nbsp nbsp nbsp nbsp nbsp } nbsp nbsp nbsp nbsp nbsp nbspreturn 1 nbsp nbsp nbsp } nbsp }) nbsp nbsp//合并文件 nbsp nbspfor(File chunkFile:fileList){ nbsp nbsp nbsp nbspRandomAccessFile raf_read = new RandomAccessFile(chunkFile,#34rw#34) nbsp nbsp nbsp nbspint len = -1 nbsp nbsp nbsp nbspwhile((len=raf_read.read(b))!=-1){ nbsp nbsp nbsp nbsp nbsp nbspraf_write.write(b,0,len)? nbsp nbsp nbsp } nbsp nbsp nbsp nbspraf_read.close() nbsp } nbsp nbspraf_write.close()?}
4. 前端页面
WebUploader介绍
如何在web页面实现断点续传?
常见的方案有
1、通过Flash上传,比如SWFupload、Uploadify。
2、安装浏览器插件,变相的pc客户端,用的比较少。
3、Html5
随着html5的流行,本项目采用Html5完成文件分块上传。
本项目使用WebUploader完成大文件上传功能的开发,WebUploader官网地址http://fexteam.gz01.bdysite.com/webuploader/
使用WebUploader上传流程如下
钩子
在webuploader中提供很多钩子 ,下边列出一些重要的
本项目使用如下钩子
1)before-send-file
在开始对文件分块儿之前调用,可以做一些上传文件前的准备工作,比如检查文件目录是否创建完成等。
2)before-send
在上传文件分块之前调用此 ,可以请求服务端检查分块是否存在,如果已存在则此分块儿不再上传。
3)after-send-file
在所有分块上传完成后触发,可以请求服务端合并分块文件。
注册钩子 源代码
WebUploader.Uploader.register({ nbsp nbsp#34before-send-file#34:#34beforeSendFile#34, nbsp nbsp#34before-send#34:#34beforeSend#34, nbsp nbsp#34after-send-file#34:#34afterSendFile#34 }
构建WebUploader
使用webUploader前需要创建webUploader对象。
指定上传分块的地址/api/media/upload/uploadchunk
// 创建uploader对象,配置参数this.uploader = WebUploader.create( { nbsp nbspswf:#34/static/plugins/webuploader/dist/Uploader.swf#34,//上传文件的flash文件,浏览器不支持h5时启动flash nbsp nbspserver:#34/api/media/upload/uploadchunk#34,//上传分块的服务端地址,注意跨域问题 nbsp nbspfileVal:#34file#34,//文件上传域的name nbsp nbsppick:#34#picker#34,//指定选择文件的按钮容器 nbsp nbspauto:false,//手动触发上传 nbsp nbspdisableGlobalDnd:true,//禁掉整个页面的拖拽功能 nbsp nbspchunked:true,// 是否分块上传 nbsp nbspchunkSize:110241024, // 分块大小(默认5M) nbsp nbspthreads:3, // 开启多个线程(默认3个) nbsp nbspprepareNextFile:true// 允许在文件传输时提前把下一个文件准备好 })
before-send-file
文件开始上传前前端请求服务端准备上传工作。
type:#34POST#34,url:#34/api/media/upload/register#34,data:{ nbsp// 文件唯一表示 nbspfileMd5:this.fileMd5, nbspfileName: file.name, nbspfileSize:file.size, nbspmimetype:file.type, nbspfileExt:file.ext}
before-send
上传分块前前端请求服务端校验分块是否存在。
type:#34POST#34,url:#34/api/media/upload/checkchunk#34,data:{ nbsp// 文件唯一表示 nbspfileMd5:this.fileMd5, nbsp// 当前分块下标 nbspchunk:block.chunk, nbsp// 当前分块大小 nbspchunkSize:block.end-block.start}
after-send-file
在所有分块上传完成后触发,可以请求服务端合并分块文件。
type:#34POST#34,url:#34/api/media/upload/mergechunks#34,data:{ fileMd5:this.fileMd5, fileName: file.name, fileSize:file.size, mimetype:file.type, fileExt:file.ext}
页面效果
学员使用
直接解压资料xc-ui-pc-teach到webstorm工作目录通过nginx访问,在nginx中配置 (解决跨域)server { listen nbsp nbsp nbsp 82 nbsp nbsp server_name localhost nbsp nbsp #视频中心 nbsp nbsp location / { proxy_pass http://127.0.0.1:12000 proxy_set_header Host $http_host nbsp add_header Access-Control-Allow-Origin nbsp nbsp nbsp nbsp add_header Access-Control-Allow-Credentials true nbsp nbsp nbsp nbsp nbsp add_header Access-Control-Allow-Methods GET } ? #媒资管理后台跨域 nbsp nbsp location ^~ /api/media/ { nbsp nbsp nbsp proxy_pass http://127.0.0.1:9000/media/ ? proxy_set_header Host $http_host nbsp add_header Access-Control-Allow-Origin nbsp nbsp nbsp nbsp add_header Access-Control-Allow-Credentials true nbsp nbsp nbsp nbsp nbsp add_header Access-Control-Allow-Methods #34GET,POST,OPTIONS#34 } nbsp nbsp }
可以访问 http://127.0.0.1:12000/#/media/upload 或者 http://localhost:82/#/media/upload/ 查看页面效果。
5. 媒资服务端编写
服务端需要实现如下功能
1、上传前检查上传环境
检查文件是否上传,已上传则直接返回。
检查文件上传路径是否存在,不存在则创建。
2、分块检查
检查分块文件是否上传,已上传则返回true。
未上传则检查上传路径是否存在,不存在则创建。
3、分块上传
将分块文件上传到指定的路径。
4、合并分块
将所有分块文件合并为一个文件。
在数据库记录文件信息。
由于上传过程复杂,开发时按业务流程分别实现。配置
application.yml配置上传文件的路径
xc-service-manage-media: upload-location: C:/school/video/ nbsp# 媒资保存路径 ffmpeg-path: C:/Java_Soft/xczx/ffmpeg-20180227-fa0c9d6-win64-static/bin/ffmpeg.exe # ffmpeg路径定义Dao
使用mybatis-plus
@Mapper@Componentpublic interface FileMsgMapper extends BaseMapperltFileMsggt {}定义controller
@RestController@RequestMapping(#34/media/upload#34)public class MediaUploadController {?? nbsp nbsp@Autowired nbsp nbspMediaUploadService mediaUploadService? nbsp nbsp/ nbsp nbsp 文件上传前的注册 nbsp nbsp /? nbsp nbsp@PostMapping(#34/register#34) nbsp nbsppublic ResponseResult register(String fileMd5, String fileName, Long fileSize, String mimetype, String fileExt) {? nbsp nbsp nbsp nbspreturn mediaUploadService.register(fileMd5, fileName, fileSize, mimetype, fileExt)? nbsp }? nbsp nbsp/ nbsp nbsp 检测分块 nbsp nbsp nbsp nbsp @param fileMd5 nbsp nbsp @param chunk nbsp nbsp @param chunkSize nbsp nbsp @return nbsp nbsp /? nbsp nbsp@PostMapping(#34/checkchunk#34) nbsp nbsppublic ResponseResult checkchunk(String fileMd5, Integer chunk, Integer chunkSize) { nbsp nbsp nbsp nbspreturn mediaUploadService.checkchunk(fileMd5, chunk, chunkSize) nbsp }? nbsp nbsp/ nbsp nbsp 上传分块 nbsp nbsp nbsp nbsp @param file nbsp nbsp @param fileMd5 nbsp nbsp @param chunk nbsp nbsp @return nbsp nbsp / nbsp nbsp@PostMapping(#34/uploadchunk#34) nbsp nbsppublic ResponseResult uploadchunk(MultipartFile file, String fileMd5, Integer chunk) { nbsp nbsp nbsp nbspreturn mediaUploadService.uploadchunk(file, fileMd5, chunk) nbsp }? nbsp nbsp/ nbsp nbsp 合并分块 nbsp nbsp nbsp nbsp @param fileMd5 nbsp nbsp @param fileName nbsp nbsp @param fileSize nbsp nbsp @param mimetype nbsp nbsp @param fileExt nbsp nbsp @return nbsp nbsp / nbsp nbsp@PostMapping(#34/mergechunks#34) nbsp nbsppublic ResponseResult mergechunks(String fileMd5, String fileName, Long fileSize, String mimetype, String fileExt) { nbsp nbsp nbsp nbspreturn mediaUploadService.mergechunks(fileMd5, fileName, fileSize, mimetype, fileExt) nbsp }}?定义service (略)由于代码过多,请参见源码。
现在视频已经通过断点续传的方式,上传到了我们的媒资服务器, 接下来就要使用FFmpeg将视频转换成 流媒体。
6.视频处理技术方案
如何通过程序进行视频处理?
ffmpeg是一个可行的视频处理程序,可以通过Java调用ffmpeg.exe完成视频处理。
在java中可以使用Runtime类和Process Builder类两种方式来执行外部程序,工作中至少掌握一种。
本项目使用Process Builder的方式来调用ffmpeg完成视频处理。
关于Process Builder的测试如下
@Test nbsp nbsppublic void testProcessBuilder(){ nbsp nbsp nbsp nbspProcessBuilder processBuilder = new ProcessBuilder()// nbsp nbsp nbsp processBuilder.command(#34ping#34,#34127.0.0.1#34) nbsp nbsp nbsp processBuilder.command(#34ipconfig#34) nbsp nbsp nbsp nbsp//将标准输入流和错误输入流合并,通过标准输入流读取信息 nbsp nbsp nbsp nbspprocessBuilder.redirectErrorStream(true) nbsp nbsp nbsp nbsptry { nbsp nbsp nbsp nbsp nbsp nbsp//启动进程 nbsp nbsp nbsp nbsp nbsp nbspProcess start = processBuilder.start() nbsp nbsp nbsp nbsp nbsp nbsp//获取输入流 nbsp nbsp nbsp nbsp nbsp nbspInputStream inputStream = start.getInputStream() nbsp nbsp nbsp nbsp nbsp nbsp//转成字符输入流 nbsp nbsp nbsp nbsp nbsp nbspInputStreamReader inputStreamReader = new InputStreamReader(inputStream,#34gbk#34) nbsp nbsp nbsp nbsp nbsp nbspint len = -1 nbsp nbsp nbsp nbsp nbsp nbspchar[] c = new char[1024] nbsp nbsp nbsp nbsp nbsp nbspStringBuffer outputString = new StringBuffer() nbsp nbsp nbsp nbsp nbsp nbsp//读取进程输入流中的内容 nbsp nbsp nbsp nbsp nbsp nbspwhile ((len= inputStreamReader.read(c))!=-1) { nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbspString s = new String(c,0,len) nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbspoutputString.append(s) nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbspSystem.out.print(s) nbsp nbsp nbsp nbsp nbsp } nbsp nbsp nbsp nbsp nbsp nbspinputStream.close() nbsp nbsp nbsp } catch (IOException e) { nbsp nbsp nbsp nbsp nbsp nbspe.printStackTrace() nbsp nbsp nbsp } nbsp } nbsp nbsp@Test nbsp nbsppublic void testFFmpeg(){ nbsp nbsp nbsp nbspProcessBuilder processBuilder = new ProcessBuilder() nbsp nbsp nbsp nbsp//定义命令内容 nbsp nbsp nbsp nbspListltStringgt command = new ArrayListltgt() nbsp nbsp nbsp nbspcommand.add(#34D:\Program Files\ffmpeg-20180227-fa0c9d6-win64-static\bin\ffmpeg.exe#34) nbsp nbsp nbsp nbspcommand.add(#34-i#34) nbsp nbsp nbsp nbspcommand.add(#34E:\ffmpeg_test\1.avi#34) nbsp nbsp nbsp nbspcommand.add(#34-y#34)//覆盖输出文件 nbsp nbsp nbsp nbspcommand.add(#34-c:v#34) nbsp nbsp nbsp nbspcommand.add(#34libx264#34) nbsp nbsp nbsp nbspcommand.add(#34-s#34) nbsp nbsp nbsp nbspcommand.add(#341280x720#34) nbsp nbsp nbsp nbspcommand.add(#34-pix_fmt#34) nbsp nbsp nbsp nbspcommand.add(#34yuv420p#34) nbsp nbsp nbsp nbspcommand.add(#34-b:a#34) nbsp nbsp nbsp nbspcommand.add(#3463k#34) nbsp nbsp nbsp nbspcommand.add(#34-b:v#34) nbsp nbsp nbsp nbspcommand.add(#34753k#34) nbsp nbsp nbsp nbspcommand.add(#34-r#34) nbsp nbsp nbsp nbspcommand.add(#3418#34) nbsp nbsp nbsp nbspcommand.add(#34E:\ffmpeg_test\1.mp4#34) nbsp nbsp nbsp nbspprocessBuilder.command(command) nbsp nbsp nbsp nbsp//将标准输入流和错误输入流合并,通过标准输入流读取信息 nbsp nbsp nbsp nbspprocessBuilder.redirectErrorStream(true) nbsp nbsp nbsp nbsptry { nbsp nbsp nbsp nbsp nbsp nbsp//启动进程 nbsp nbsp nbsp nbsp nbsp nbspProcess start = processBuilder.start() nbsp nbsp nbsp nbsp nbsp nbsp//获取输入流 nbsp nbsp nbsp nbsp nbsp nbspInputStream inputStream = start.getInputStream() nbsp nbsp nbsp nbsp nbsp nbsp//转成字符输入流 nbsp nbsp nbsp nbsp nbsp nbspInputStreamReader inputStreamReader = new InputStreamReader(inputStream,#34gbk#34) nbsp nbsp nbsp nbsp nbsp nbspint len = -1 nbsp nbsp nbsp nbsp nbsp nbspchar[] c = new char[1024] nbsp nbsp nbsp nbsp nbsp nbspStringBuffer outputString = new StringBuffer() nbsp nbsp nbsp nbsp nbsp nbsp//读取进程输入流中的内容 nbsp nbsp nbsp nbsp nbsp nbspwhile ((len= inputStreamReader.read(c))!=-1) { nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbspString s = new String(c,0,len) nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbspoutputString.append(s) nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbspSystem.out.print(s) nbsp nbsp nbsp nbsp nbsp } nbsp nbsp nbsp nbsp nbsp nbspinputStream.close() nbsp nbsp nbsp } catch (IOException e) { nbsp nbsp nbsp nbsp nbsp nbspe.printStackTrace() nbsp nbsp nbsp } nbsp }
我们已经准备好了工具类
Mp4VideoUtil.java完成avi转mp4
HlsVideoUtil.java完成mp4转hls
可以直接使用。
6.1 处理流程
当视频合并成功后
把视频处理成m3u8流媒体把视频信息写到数据库具体代码 在合并完成 中调用
@Value(#34${xc-service-manage-media.ffmpeg-path}#34) nbsp nbspString ffmpeg_path?? nbsp nbspprivate void ChangeHLS(File mergeFile, String fileMd5) {? nbsp nbsp nbsp nbsp// mp4 文件保存目录 nbsp nbsp nbsp nbspString fileFolderPath = getFileFolderPath(fileMd5) nbsp nbsp nbsp nbsp//生成的mp4的文件名称 nbsp nbsp nbsp nbspString mp4_name = fileMd5 + #34.mp4#34 nbsp nbsp nbsp nbsp//生成的mp4所在的路径 nbsp nbsp nbsp nbspString mp4folder_path = fileFolderPath?? nbsp nbsp nbsp nbsp//创建工具类对象 nbsp nbsp nbsp nbspMp4VideoUtil mp4VideoUtil = new Mp4VideoUtil(ffmpeg_path, mergeFile.getAbsolutePath(), mp4_name, mp4folder_path)? nbsp nbsp nbsp nbsp//进行处理 nbsp nbsp nbsp nbspString result = mp4VideoUtil.generateMp4()?? nbsp nbsp nbsp nbsp//4、将mp4生成m3u8和ts文件 nbsp nbsp nbsp nbsp//String ffmpeg_path, String video_path, String m3u8_name,String m3u8folder_path nbsp nbsp nbsp nbsp//mp4视频文件路径 nbsp nbsp nbsp nbspString mp4_video_path = mp4folder_path + mp4_name? nbsp nbsp nbsp nbsp//m3u8_name文件名称 nbsp nbsp nbsp nbspString m3u8_name = fileMd5 + #34.m3u8#34 nbsp nbsp nbsp nbsp//m3u8文件所在目录 nbsp nbsp nbsp nbspString m3u8folder_path = fileFolderPath + #34hls/#34? nbsp nbsp nbsp nbspHlsVideoUtil hlsVideoUtil = new HlsVideoUtil(ffmpeg_path, mp4_video_path, m3u8_name, m3u8folder_path)? nbsp nbsp nbsp nbsp//生成m3u8和ts文件 nbsp nbsp nbsp nbspString tsResult = hlsVideoUtil.generateM3u8()? nbsp nbsp nbsp nbsp//保存fileUrl(此url就是视频播放的相对路径) nbsp nbsp nbsp nbspString filePath = fileFolderPath + #34hls/#34 + m3u8_name?? nbsp nbsp nbsp nbsp// 将fileUrl 保存到数据库 nbsp nbsp nbsp nbspFileMsg fileMsg = new FileMsg() nbsp nbsp nbsp nbspfileMsg.setFileId(fileMd5) nbsp nbsp nbsp nbspfileMsg.setFileName(mergeFile.getName()) nbsp nbsp nbsp nbspfileMsg.setFilePath(filePath) nbsp nbsp nbsp nbspfileMsg.setFileUrl(filePath.split(#34C:/school/video#34)[1])? nbsp nbsp nbsp nbspfileMsgMapper.insert(fileMsg)?? nbsp nbsp nbsp nbsp//优化: 文件处理成功后, 可以删除mp4文件?? nbsp }
说明
mp4转成m3u8如何判断转换成功?
之一、根据视频时长来判断,同mp4转换成功的判断 。
第二、还要判断m3u8文件内容是否完整。
4 播放器
4.1 技术选型
视频编码后要使用播放器对其进行解码、播放视频内容。在web应用中常用的播放器有flash播放器、H5播放器或浏览器插件播放器,其中以flash和H5播放器最常见。
flash播放器缺点是需要在客户机安装Adobe Flash Player播放器,优点是flash播放器已经很成熟了,并且浏览器对flash支持也很好。
H5播放器基于h5自带video标签进行构建,优点是大部分浏览器支持H5,不用再安装第三方的flash播放器,并且随着前端技术的发展,h5技术会越来越成熟。
本项目采用H5播放器,使用Video.js开源播放器。
Video.js是一款基于HTML5世界的 视频播放器。它支持HTML5和Flash视频,它支持在台式机和移动设备上播放视频。这个项目于2010年中开始,目前已在40万网站使用。
官方地址http://videojs.com/
4.2 下载video.js
Video.js https://github.com/videojs/video.js
videojs-contrib-hls https://github.com/videojs/videojs-contrib-hls#installation
(videojs-contrib-hls是播放hls的一个插件)
使用文档http://docs.videojs.com/tutorial-videojs_.html
本教程使用 video.js 6.7.3 版本,videojs-contrib-hls 5.14.1版本
将资料中提供的plugins赋值到xc-ui-pc-video项目目录下
4.3 搭建媒体服务器
正常使用video.js播放视频是通过一个网页,用户通过浏览器打开网页去播放视频,网页和视频都从web服务器请求。
4.3.1 Nginx媒体服务器
根据上边的流程,我们在媒体服务器上安装Nginx,并配置如下
server { nbsp nbsp nbsp listen nbsp nbsp nbsp 81 nbsp nbsp nbsp server_name localhost # 流媒体 静态资源 nbsp nbsp nbsp location ^~ /school/video/ { nbsp nbsp nbsp alias nbsp C:/school/video/ } # 视频静态资源 nbsp nbsp nbsp location / { nbsp nbsp nbsp nbsp nbsp alias nbsp C:/CODE/JAVA/school/xc-ui-pc-video/ nbsp nbsp nbsp nbsp nbsp index index.html index.htm nbsp nbsp nbsp } nbsp }
4.4 测试video.js
把资料中的video.html 复制到 C:/CODE/JAVA/school/xc-ui-pc-video/ 如图上面已经使用nginx C:/CODE/JAVA/school/xc-ui-pc-video/ 目录。 所以可以通过http请求直接访问video.html页面
把src路径修改为自己的媒资访问路径测试用户打开浏览器输入 http://localhost:81/video.html 。即可打开视频播放页面,并播放。
视频怎么实现 视频自动播放教程