在线学习


概括理解:流媒体就是将视频文件分成许多小块儿,将这些小块儿作为数据包通过网络发送出去,实现一边传输视
频 数据 包一边观看视频。

HLS是什么?

HLS的工作方式是:将视频拆分成若干ts格式的小文件,通过m3u8格式的索引文件对这些ts小文件建立索引。一般10秒一个ts文件,播放器连接m3u8文件播放,当快进时通过m3u8即可找到对应的索引文件,并去下载对应的ts文件,从而实现快进、快退以近实时 的方式播放视频。
IOS、Android设备、及各大浏览器都支持HLS协议。

视频编码

视频编码格式
详情参考 :https://baike.baidu.com/item/%E8%A7%86%E9%A2%91%E7%BC%96%E7%A0%81/839038
首先我们要分清文件格式和编码格式:
文件格式:是指.mp4、.avi、.rmvb等 这些不同扩展名的视频文件的文件格式 ,视频文件的内容主要包括视频和音
频,其文件格式是按照一 定的编码格式去编码,并且按照该文件所规定的封装格式将视频、音频、字幕等信息封装在一起,播放器会根据它们的封装格式去提取出编码,然后由播放器解码,最终播放音视频。
音视频编码格式:通过音视频的压缩技术,将视频格式转换成另一种视频格式,通过视频编码实现流媒体的传输。
比如:一个.avi的视频文件原来的编码是a,通过编码后编码格式变为b,音频原来为c,通过编码后变为d。
音视频编码格式各类繁多,主要有几下几类:
MPEG系列 (由ISO[国际标准组织机构]下属的MPEG[运动图象专家组]开发 )视频编码方面主要是Mpeg1(vcd用
的就是它)、Mpeg2(DVD使用)、Mpeg4(的DVDRIP使用的都是它的变种,如:divx,xvid等)、Mpeg4
AVC(正热门);音频编码方面主要是MPEG Audio Layer 1/2、MPEG Audio Layer 3(大名鼎鼎的mp3)、
MPEG-2 AAC 、MPEG-4 AAC等等。注意:DVD音频没有采用Mpeg的。
H.26X系列 (由ITU[国际电传视讯联盟]主导,侧重网络传输,注意:只是视频编码) 包括H.261、H.262、
H.263、H.263+、H.263++、H.264(就是MPEG4 AVC-合作的结晶)
目前最常用的编码标准是视频H.264,音频AAC。
提问:
H.264是编码格式还是文件格式? 编码格式
mp4是编码格式还是文件格式? 文件格式

FFmpeg 的基本使用


下载:FFmpeg

https://www.ffmpeg.org/download.html#build-windows


下载 :ffmpeg-20180227-fa0c9d6-win64-static.zip,并解压,本教程将ffmpeg解压到了
F:\devenv\edusoft\ffmpeg-20180227-fa0c9d6-win64-static\ffmpeg-20180227-fa0c9d6-win64-static下。
将F:\devenv\edusoft\ffmpeg-20180227-fa0c9d6-win64-static\ffmpeg-20180227-fa0c9d6-win64-static\bin目录配置在path环境变量中:
检测是否安装成功:

简单的测试:
将一个.avi文件转成mp4、mp3、gif等。
比如我们将lucene.avi文件转成mp4,运行如下命令:
ffmpeg -i lucene.avi lucene.mp4
转成mp3:ffmpeg -i lucene.avi lucene.mp3
转成gif:ffmpeg -i lucene.avi lucene.gif
官方文档(英文):

http://ffmpeg.org/ffmpeg.html

生成m3u8/ts文件

使用ffmpeg生成 m3u8的步骤如下:
第一步:先将avi视频转成mp4
生成m3u8/ts文件,去视频所在目录中,cmd运行。

ffmpeg.exe -i lucene.avi -c:v libx264 -s 1280x720 -pix_fmt yuv420p -b:a 63k -b:v 753k -r 18 .\lucene.mp4


第二步:将mp4生成m3u8,注意,需要先创建hls文件夹

ffmpeg -i lucene.mp4 -hls_time 10 -hls_list_size 0 -hls_segment_filename ./hls/lucene_%05d.ts ./hls/lucene.m3u8

画面清晰度的码率


H5播放器


官方地址:

http://videojs.com/

下载video.js
Video.js :

https://github.com/videojs/video.js

具体用gitup的下载步骤截图:



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目录中。

搭建学习中心前端

ngix媒体服务器的配置新增:(未成功,有路径访问301的跨域问题)

 #学成网媒体服务代理map $http_origin $origin_list{default http://www.xuecheng.com;"~http://www.xuecheng.com" http://www.xuecheng.com;"~http://ucenter.xuecheng.com" http://ucenter.xuecheng.com;}#学成网媒体服务代理server {listen       80;    server_name video.xuecheng.com;    location /video {      proxy_pass http://video_server_pool; #$origin_list,这个列表里的路径可以访问add_header Access-Control-Allow-Origin $origin_list;        #设置可以跨域访问,*代表可以公共厕所,都能访问 #add_header Access-Control-Allow-Origin *;          add_header Access-Control-Allow-Credentials true;add_header Access-Control-Allow-Methods GET;               }     }#学成网媒体服务server {listen       90;    server_name  localhost;    #视频目录    location /video/ {    alias   D:/a1/xuechengzaixianUI/develop/video/;           }   }#媒体服务upstream video_server_pool{server 127.0.0.1:90 weight=10;    }   #前端ucenterupstream ucenter_server_pool{#server 127.0.0.1:7081 weight=10;server 127.0.0.1:13000 weight=10;}

媒资管理服务端


6.2 开发环境

6.2.1 创建媒资mongo数据库

创建媒资服务工程
媒资管理的相关功能单独在媒资服务中开发,下边创建媒资服务工程(xc-service-manage-media)。
媒资服务的配置与cms类似,导入 “资料”–》xc-service-manage-media工程,工程结构如下:

上传文件和合并文件的测试代码案例(原理很重要)

6.3 上传文件
6.3.1 断点续传解决方案
通常视频文件都比较大,所以对于媒资系统上传文件的需求要满足大文件的上传要求。http协议本身对上传文件大
小没有限制,但是客户的网络环境质量、电脑硬件环境等参差不齐,如果一个大文件快上传完了网断了,电断了没
有上传完成,需要客户重新上传,这是致命的,所以对于大文件上传的要求最基本的是断点续传。
什么是断点续传:
引用百度百科:断点续传指的是在下载或上传时,将下载或上传任务(一个文件或一个压缩包)人为的划分为几个
部分,每一个部分采用一个线程进行上传或下载,如果碰到网络故障,可以从已经上传或下载的部分开始继续上传
下载未完成的部分,而没有必要从头开始上传下载,断点续传可以提高节省操作时间,提高用户体验性。
如下图:

上传和下载的案例代码(很重要):

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;import java.awt.*;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.lang.reflect.Array;
import java.util.*;
import java.util.List;@SpringBootTest
@RunWith(SpringRunner.class)
public class uploadTest {//大文件上传断点续传的分块处理@Testpublic void testChunk() throws IOException {//先指向要上传的大文件,源文件File sourceFile = new File("D:\\a1\\xuechengzaixianUI\\develop\\video\\lucene.avi");//把要上传的文件,存放到哪个目录String chunkPath = "D:\\a1\\xuechengzaixianUI\\develop\\video\\chunk\\";//判断有没有这个存放上传文件的目录文件夹,没有就创建一个File chunkDirectory = new File(chunkPath);if (!chunkDirectory.exists()){//如果没有这个目录,就创建一个目录chunkDirectory.mkdir();}//确定文件要分为多少块,先设定每块大小多大long chunkSize = 1*1024*1024;//通过除法公式:文件的字节长度*小数/每块字节大小,可能会有小数,需要向上取整// 得到该文件要分为多少块long chunkNum = (long) Math.ceil(sourceFile.length()*1.0/chunkSize);//如果得到的分块小于1,证明不合法,就默认至少有一块。if (chunkNum<=0){chunkNum = 1;}//准备一个缓冲区byte[] bytes = new byte[1024];//准备一个随机读的对象,抛io异常,把文件放进去RandomAccessFile raf_read = new RandomAccessFile(sourceFile, "r");//每次读一块的大小。装完所有的块就是读完了//D:\a1\xuechengzaixianUI\develop\video\chunk\1....xfor (int i = 0; i <chunkNum ; i++) {//每读一块,就生成一个装字节的文件File file = new File(chunkPath + i);//创建这个文件boolean newFile = file.createNewFile();//文件一旦创建成功,就把读到的数据写进去if (newFile){//输处流通过准备往块文件中写数据RandomAccessFile raf_write = new RandomAccessFile(file, "rw");//准备一个len字节长度属性int len = -1;//每次读,缓冲区大小内容,如果没有返回-1就是没有读完while ((len=raf_read.read(bytes))!=-1){//读到缓冲区大小文件,就写入缓冲区大小文件,从0字节写到len字节结束raf_write.write(bytes,0,len);//如果写入的数据字节,大于定义的块文件大小,就结束写入。if (file.length()>chunkSize){break;}}//写完一个块文件,就关闭输出流raf_write.close();}}//写完所有块文件,就关闭读流raf_read.close();}@Test//把上传分块的文件合并public void testMerge() throws IOException {//指向这个分块的文件夹File chunkFolder = new File("D:\\a1\\xuechengzaixianUI\\develop\\video\\chunk");//准备好,把合并的文件存放到哪个文件指向File mergeFile = new File("D:\\a1\\xuechengzaixianUI\\develop\\video\\hebing\\lucene.avi");//如果有这个文件,就先删除重名文件,再重新创建这个文件if (mergeFile.exists()){mergeFile.delete();}//没有重名文件,创建这个文件mergeFile.createNewFile();//取,分块目录的集合File[] files = chunkFolder.listFiles();//把数组转为集合,方便排序ArrayList<File> fileList = new ArrayList<>(Arrays.asList(files));//对块文件的list集合,进行按名字升序0-10Collections.sort(fileList, new Comparator<File>() {@Overridepublic int compare(File o1, File o2) {//一种排序规则,不用理解,死记就行。if (Integer.parseInt(o1.getName())>Integer.parseInt(o2.getName())){//名字大于,返回1升序return 1;}//名字小于,返回-1降序return -1;}});//开始准备合并文件,先准备一个输出流,到汇总文件RandomAccessFile raf_rw = new RandomAccessFile(mergeFile, "rw");//准备一个缓冲区byte[] bytes = new byte[1024];//开始合并,把排过序的文件集合迭代//测试读取写入多少次的自增变量int index = 1;for (File file : fileList) {//每次遍历,创建一个输入(读)流,指向当前文件RandomAccessFile raf_r = new RandomAccessFile(file, "r");//开始写数据int len = -1;while ((len=raf_r.read(bytes))!=-1){//读到len长度的数据,就写一次数据raf_rw.write(bytes,0,len);System.err.println("测试合并操作,经过了:"+index+"次");index++;}//写完单个文件,就关闭读流raf_r.close();}//写完全部文件,就关闭写流raf_rw.close();}}

WebUploader百度上传插件

如何在web页面实现断点续传?
常见的方案有:
1、通过Flash上传,比如SWFupload、Uploadify。
2 、安装浏览器插件,变相的pc客户端,用的比较少。
3、Html5随着html5的流行,本项目采用Html5完成文件分块上传。
本项目使用WebUploader完成大文件上传功能的开发,WebUploader官网地址:

http://fexteam.gz01.bdysite.com/webuploader/



实现这些钩子的接口方法:

本项目使用如下钩子方法:
1)before-send-file
在开始对文件分块儿之前调用,可以做一些上传文件前的准备工作,比如检查文件目录是否创建完成等。
2 )before-send
在上传文件分块之前调用此方法,可以请求服务端检查分块是否存在,如果已存在则此分块儿不再上传。
3)after-send-file
在所有分块上传完成后触发,可以请求服务端合并分块文件。
注册钩子方法源代码:

WebUploader.Uploader.register({"before-send-file":"beforeSendFile","before-send":"beforeSend","after-send-file":"afterSendFile"}

6.3.3.2 构建WebUploader(没搞懂,视频没讲)
使用webUploader前需要创建webUploader对象。
指定上传分块的地址:/api/media/upload/uploadchunk

// 创建uploader对象,配置参数
this.uploader = WebUploader.create({swf:"/static/plugins/webuploader/dist/Uploader.swf",//上传文件的flash文件,浏览器不支持h5时启动
flashserver:"/api/media/upload/uploadchunk",//上传分块的服务端地址,注意跨域问题fileVal:"file",//文件上传域的namepick:"#picker",//指定选择文件的按钮容器auto:false,//手动触发上传disableGlobalDnd:true,//禁掉整个页面的拖拽功能chunked:true,// 是否分块上传chunkSize:1*1024*1024, // 分块大小(默认5M)threads:3, // 开启多个线程(默认3个)prepareNextFile:true// 允许在文件传输时提前把下一个文件准备好}
)

6.3.3.3 before-send-file
文件开始上传前前端请求服务端准备上传工作。
参考源代码如下:

type:"POST",
url:"/api/media/upload/register",
data:{// 文件唯一表示fileMd5:this.fileMd5,fileName: file.name,fileSize:file.size,mimetype:file.type,fileExt:file.ext
}

6.3.3.4 before-send
上传分块前前端请求服务端校验分块是否存在。
参考源代码如下:

type:"POST",
url:"/api/media/upload/checkchunk",
data:{// 文件唯一表示fileMd5:this.fileMd5,// 当前分块下标chunk:block.chunk,// 当前分块大小chunkSize:block.end-block.start
}

6.3.3.5 after-send-file
在所有分块上传完成后触发,可以请求服务端合并分块文件
参考代码如下:

type:"POST",
url:"/api/media/upload/mergechunks",
data:{fileMd5:this.fileMd5,fileName: file.name,fileSize:file.size,mimetype:file.type,fileExt:file.ext
}

6.3.3.6 页面效果

媒资服务端代码:

6.3.4 Api接口
定义文件上传的Api接口,此接收是前端WebUploader调用服务端的接口。
编写此接口需要参数前端WebUploader应用代码

import com.xuecheng.framework.domain.media.response.CheckChunkResult;
import com.xuecheng.framework.model.response.ResponseResult;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.multipart.MultipartFile;/*** 媒资管理接口,涉及文件断点续传* 实现的前端上传组件的各种钩子接口方法*/
@Api(value = "媒资管理接口",description = "媒资管理接口,提供文件上传、处理等接口")
public interface MediaUploadControllerApi {//文件上传前的准备工作,校验文件是否存在/**** @param fileMd5 用前端用MD5生成的文件id* @param fileName  文件名称* @param fileSize  文件大小* @param mimetype  文件属性* @param fileExt   文件扩展名* @return   返回成功代码*/@ApiOperation("文件上传注册")public ResponseResult register(String fileMd5,String fileName,Long fileSize,String mimetype,String fileExt);/**** @param fileMd5 文件唯一id* @param chunk   分块的下标* @param chunkSize  分块的大小* @return  返回是否存在*/@ApiOperation("校验分块文件是否存在")public CheckChunkResult checkchunk(String fileMd5,Integer chunk,Integer chunkSize);/*** @param file  校验完成后,分块文件的内容* @param fileMd5   文件id* @param chunk     分块下标* @return  成功代码*/@ApiOperation("上传分块")public ResponseResult uploadchunk(MultipartFile file,String fileMd5,Integer chunk);/**** @param fileMd5 用前端用MD5生成的文件id* @param fileName  文件名称* @param fileSize  文件大小* @param mimetype  文件属性* @param fileExt   文件扩展名* @return   返回成功代码*/@ApiOperation("合并分块")public ResponseResult mergechunks(String fileMd5,String fileName,Long fileSize,String mimetype,String fileExt);}

6.3.5.1 业务流程梳理
服务端需要实现如下功能:
1、上传前检查上传环境
检查文件是否上传,已上传则直接返回。
检查文件上传路径是否存在,不存在则创建。
2、分块检查
检查分块文件是否上传,已上传则返回true。
未上传则检查上传路径是否存在,不存在则创建。
3、分块上传
将分块文件上传到指定的路径。
4、合并分块
将所有分块文件合并为一个文件。
在数据库记录文件信息。

yml,配置文件:

server:port: 31400
spring:application:name: xc-service-manage-mediadata:mongodb:uri:  mongodb://root:123@localhost:27017database: xc_media
xc-service-manage-media:upload-location: D:/a1/xuechengzaixianUI/develop/video
eureka:client:registerWithEureka: true #服务注册开关fetchRegistry: true #服务发现开关serviceUrl: #Eureka客户端与Eureka服务端进行交互的地址,多个中间用逗号分隔defaultZone: ${EUREKA_SERVER:http://localhost:50101/eureka/}instance:prefer-ip-address:  true  #将自己的ip地址注册到Eureka服务中ip-address: ${IP_ADDRESS:127.0.0.1}instance-id: ${spring.application.name}:${server.port} #指定实例id
ribbon:MaxAutoRetries: 2 #最大重试次数,当Eureka中可以找到服务,但是服务连不上时将会重试,如果eureka中找不到服务则直接走断路器MaxAutoRetriesNextServer: 3 #切换实例的重试次数OkToRetryOnAllOperations: false  #对所有操作请求都进行重试,如果是get则可以,如果是post,put等操作没有实现幂等的情况下是很危险的,所以设置为falseConnectTimeout: 5000  #请求连接的超时时间ReadTimeout: 6000 #请求处理的超时时间

Controller
为什么这个controller中,获取的值,不用加@requestParam 来获取值都能跑通呢。是因为名称对应,且类型都是String吗?

import com.xuecheng.api.media.MediaUploadControllerApi;
import com.xuecheng.framework.domain.media.response.CheckChunkResult;
import com.xuecheng.framework.model.response.ResponseResult;
import com.xuecheng.manage_media.service.MediaUploadService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;/*** @author 上传注册的controller* @version 1.0**/
@RestController
@RequestMapping("/media/upload")
public class MediaUploadController implements MediaUploadControllerApi {@AutowiredMediaUploadService mediaUploadService;//文件上传前的准备工作,校验文件是否存在/**** @param fileMd5 用前端用MD5生成的文件id* @param fileName  文件名称* @param fileSize  文件大小* @param mimetype  文件属性* @param fileExt   文件扩展名* @return   返回成功代码*/@Override@PostMapping("/register")public ResponseResult register(String fileMd5, String fileName, Long fileSize, String mimetype, String fileExt) {return mediaUploadService.register(fileMd5,fileName,fileSize,mimetype,fileExt);}/**** @param fileMd5 文件唯一id* @param chunk   分块的下标* @param chunkSize  分块的大小* @return  返回是否存在*/@Override@PostMapping("/checkchunk")public CheckChunkResult checkchunk(String fileMd5, Integer chunk, Integer chunkSize) {return mediaUploadService.checkchunk(fileMd5,chunk,chunkSize);}@Override@PostMapping("/uploadchunk")public ResponseResult uploadchunk(MultipartFile file, String fileMd5, Integer chunk) {return mediaUploadService.uploadchunk(file,fileMd5,chunk);}@Override@PostMapping("/mergechunks")public ResponseResult mergechunks(String fileMd5, String fileName, Long fileSize, String mimetype, String fileExt) {return mediaUploadService.mergechunks(fileMd5,fileName,fileSize, mimetype,fileExt);}}


Service

import com.xuecheng.framework.domain.media.MediaFile;
import com.xuecheng.framework.domain.media.response.CheckChunkResult;
import com.xuecheng.framework.domain.media.response.MediaCode;
import com.xuecheng.framework.exception.ExceptionCast;
import com.xuecheng.framework.model.response.CommonCode;
import com.xuecheng.framework.model.response.ResponseResult;
import com.xuecheng.manage_media.dao.MediaFileRepository;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.io.IOUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;import java.io.*;
import java.security.MessageDigest;
import java.util.*;/*** @author* @version 1.0**/
@Service
public class MediaUploadService {//操作媒资数据库的Mongo,dao@Autowiredprivate MediaFileRepository mediaFileRepository;//去存放视频目录的根路径:D:/a1/xuechengzaixianUI/develop/video@Value("${xc-service-manage-media.upload-location}")private String upload_location;//得到文件所属目录路径private String getFileFolderPath(String fileMd5){//先通过进来的id,得到该文件的目录String mkdirPath = upload_location+"\\"+fileMd5.substring(0,1)//subString(1,2),从1下标开始截取,2下标结束。+"\\"+fileMd5.substring(1,2)+"\\"+fileMd5;return mkdirPath;}//根据文件id和文件扩展名,得到文件的绝对路径private String getFilePath(String fileMd5,String fileExt){return upload_location + fileMd5.substring(0,1)+ "/" + fileMd5.substring(1,2) + "/" + fileMd5+ "/" + fileMd5 + "." +fileExt;}//得到块文件所属目录路径private String getChunkFileFolderPath(String fileMd5){return  upload_location + fileMd5.substring(0,1)+ "/" + fileMd5.substring(1,2)+ "/" + fileMd5 + "/chunk/";}/*** 文件上传前的注册,检查文件是否存在* 根据文件md5得到文件路径* 规则:* 一级目录:md5的第一个字符* 二级目录:md5的第二个字符* 三级目录:md5* 文件名:md5+文件扩展名* @param fileMd5 文件md5值* @param fileExt 文件扩展名* @return 文件路径*/public ResponseResult register(String fileMd5, String fileName, Long fileSize, String mimetype, String fileExt) {//1  检查文件在磁盘上是否存在//文件所属目录的路径String fileFolderPath = this.getFileFolderPath(fileMd5);//得到文件绝对路径String filePath = this.getFilePath(fileMd5, fileExt);//判断,这个文件是否存在File file = new File(filePath);boolean exists = file.exists();//从mongo中取这个文件Optional<MediaFile> optionalFile = mediaFileRepository.findById(fileMd5);if (exists && optionalFile.isPresent()){//如果都本地和mongo中都有这个文件,证明上传过了,无需再次上传ExceptionCast.cast(MediaCode.UPLOAD_FILE_REGISTER_EXIST);}//如果有数据库中没有。就判断,该文件的本地目录是否存在,不存在,就先创建该目File fileFolder = new File(fileFolderPath);if (!fileFolder.exists()){//如果没有这个目录就创建这个多级目录,mkdirs,加s是多级,不加是单个目录fileFolder.mkdirs();}//准备完毕,返回成功代码return new ResponseResult(CommonCode.SUCCESS);}/***分块检查* @param fileMd5 文件唯一id* @param chunk   分块的下标* @param chunkSize  分块的大小* @return  返回是否存在*/public CheckChunkResult checkchunk(String fileMd5, Integer chunk, Integer chunkSize) {//检查分块文件是否存在//得到分块文件的所在目录String chunkFileFolderPath = this.getChunkFileFolderPath(fileMd5);//通过存放分块文件的目录+文件下标。就指向这个分块文件File file = new File(chunkFileFolderPath + chunk);//判断有没有这个分块if (file.exists()){//如果有这个分块返回成功代码return new CheckChunkResult(CommonCode.SUCCESS,true);}else {//如果没有这个分块返回falsereturn new CheckChunkResult(CommonCode.SUCCESS,false);}}/*** @param file  校验完成后,分块文件的内容* @param fileMd5   文件id* @param chunk     分块下标* @return  成功代码*/public ResponseResult uploadchunk(MultipartFile file, String fileMd5, Integer chunk) {//先得到该文件的分块目录String chunkFileFolderPath = this.getChunkFileFolderPath(fileMd5);//先判断有没有这个目录,没有就创建File folderPath = new File(chunkFileFolderPath);if (!folderPath.exists()){//没有,创建这个多级目录folderPath.mkdirs();}//建立目录+下标的,分块文件指向File chunkFile = new File(chunkFileFolderPath + chunk);//准备读流,上传的参数,自带路径。InputStream inputStream = null;//和File带路径的写流FileOutputStream outputStream=null;try {//获取读流文件,并赋值inputStream = file.getInputStream();//创建指向目录+文件id的写流,并赋值outputStream = new FileOutputStream(chunkFile);//把读到的流复制到写流中,完成上传块文件IOUtils.copy(inputStream,outputStream);} catch (IOException e) {e.printStackTrace();}finally {//最后关闭读写流try {inputStream.close();outputStream.close();} catch (IOException e) {e.printStackTrace();}}//返回成功代码return new ResponseResult(CommonCode.SUCCESS);}/***合并文件* @param fileMd5 用前端用MD5生成的文件id* @param fileName  文件名称* @param fileSize  文件大小* @param mimetype  文件属性* @param fileExt   文件扩展名* @return   返回成功代码*/public ResponseResult mergechunks(String fileMd5, String fileName, Long fileSize, String mimetype, String fileExt) {//1、合并所有分块//得到分块文件的属目录String chunkFileFolderPath = this.getChunkFileFolderPath(fileMd5);File chunkFile = new File(chunkFileFolderPath);//取分块文件列表,并转为list方便排序File[] files = chunkFile.listFiles();List<File> fileList = Arrays.asList(files);//准备一个合并后放文件的文件夹文件String filePath = this.getFilePath(fileMd5, fileExt);File mergeFile = new File(filePath);//执行合并,得到合併文件的指向File mergeFile1 = this.mergeFile(fileList, mergeFile);if (mergeFile1 == null) {//合併文件失敗ExceptionCast.cast(MediaCode.MERGE_FILE_FAIL);}//執行文件校驗,File指向id和進來的id,是否一致boolean checkFileMd5 = this.checkFileMd5(mergeFile, fileMd5);if (!checkFileMd5){//校驗不成功,拋異常ExceptionCast.cast(MediaCode.MERGE_FILE_CHECKFAIL);}//校驗成功,就把上傳的信息,給添加進mongoMediaFile mediaFile = new MediaFile();mediaFile.setFileId(fileMd5);mediaFile.setFileOriginalName(fileName);//文件名稱+擴展名mediaFile.setFileName(fileMd5 + "." +fileExt);//文件路径保存相对路径,也就是不要配置的前綴。String filePath1 = fileMd5.substring(0,1) + "/" + fileMd5.substring(1,2) + "/" + fileMd5 + "/" + fileMd5 + "." +fileExt;mediaFile.setFilePath(filePath1);mediaFile.setFileSize(fileSize);mediaFile.setUploadTime(new Date());mediaFile.setMimeType(mimetype);mediaFile.setFileType(fileExt);//状态为上传成功mediaFile.setFileStatus("301002");//執行保存mediaFileRepository.save(mediaFile);//返回成功代碼return new ResponseResult(CommonCode.SUCCESS);}//校验文件private boolean checkFileMd5(File mergeFile,String md5){try {//創建文件的輸入流FileInputStream fileInputStream = new FileInputStream(mergeFile);//用阿帕奇的工具類得到MD5值String md5Hex = DigestUtils.md5Hex(fileInputStream);//對比md5值,不區分大小寫if (md5Hex.equalsIgnoreCase(md5)){//如果一致,就返回truereturn true;}//不一致,就falsereturn false;} catch (IOException e) {e.printStackTrace();return false;}}//合并文件的方法,参数1:分块集合,参数2:存放合并文件的目录private File mergeFile(List<File> fileList,File mergeFile){//把整段代碼,都try異常try {//先对目录中的文件进行判断,有这个文件就删除覆盖if (mergeFile.exists()){//如果有这个文件,就删除mergeFile.delete();}else {//如果有就创建这个文件try {mergeFile.createNewFile();} catch (IOException e) {e.printStackTrace();}}//对分块文件List集合进行按名称排序Collections.sort(fileList, new Comparator<File>() {@Overridepublic int compare(File o1, File o2) {if (Integer.parseInt(o1.getName())>Integer.parseInt(o2.getName())){return 1;}return -1;}});//排过序后,执行合并,遍历分块文件//先准备一个写流RandomAccessFile raf_rw = new RandomAccessFile(mergeFile,"rw");for (File file : fileList) {//每次循環準備一個讀流RandomAccessFile raf_r = new RandomAccessFile(file,"r");//準備一個緩存區byte[] bytes = new byte[1024];int len = -1;//讀流每次讀緩衝區大小的數據,只要有數據,就一直讀while ((len = (raf_r.read(bytes)))!=-1){//讀一次,也就跟著寫一次raf_rw.write(bytes,0,len);}//寫完一個塊文件,就關閉一次讀流raf_r.close();}//遍歷結束了,就關閉寫流raf_rw.close();//執行完,沒異常,就是合併成功,返回合併的文件File指向return mergeFile;} //整段try結束catch (IOException e) {e.printStackTrace();//有異常就返回nullreturn null;}}
}

学成在线day13,HLS视频文件处理,FFmpeg,画面清晰度的码率, H5播放器video.js,媒资管理,断点上传文件和合并文件,WebUploader百度上传插件,相关推荐

  1. html 在线播放器,HTML5 Web播放器-Video.js

    原标题:HTML5 Web播放器-Video.js Video.js是一款优秀的HTML5 Web视频播放器.它同时支持 HTM5 和 Flash 视频,支持在桌面和移动设备上的视频播放,截至目前,大 ...

  2. 网页全终端视频直播/点播H5播放器EasyPlayer.js正式发布,支持H.265网页播放

    在之前的多篇博客<历时半年,终于研发完成了Web网页对H.265(HEVC)直播和点播的支持,WebAssembly(wasm)>.<EasyPlayer.js开发Web H5网页播 ...

  3. 集RTMP, HLS, FLV, WebSocket 于一身的网页直播/点播播放器EasyPlayer.js引用videojs无法自动播放问题解决

    EasyPlayer几乎涵盖了开发者所需的全部平台,尤其是在web端的EasyPlayer.js时遇到的播放器接入的问题,比如:不懂前端的js代码.没有公网服务器放置这些js脚本和网页.微信对接入IP ...

  4. 微服务[学成在线] day13:使用FFmpeg进行格式转换以及m3u8文件生成、文件分块上传接口实现

  5. 学成在线--媒资管理模块

    完整版请移步至我的个人博客查看:https://cyborg2077.github.io/ 学成在线–项目环境搭建 学成在线–内容管理模块 学成在线–媒资管理模块 学成在线–课程发布模块 学成在线–认 ...

  6. 微服务实战项目-学成在线-课程发布模块

    学成在线-课程发布模块 1 模块需求分析 1.1 模块介绍 课程信息编辑完毕即可发布课程,发布课程相当于一个确认操作,课程发布后学习者在网站可以搜索到课程,然后查看课程的详细信息,进一步选课.支付.在 ...

  7. 学成在线--课程发布模块

    完整版请移步至我的个人博客查看:https://cyborg2077.github.io/ 学成在线–项目环境搭建 学成在线–内容管理模块 学成在线–媒资管理模块 学成在线–课程发布模块 学成在线–认 ...

  8. 学成在线-第13天-讲义-在线学习 HLS

    1在线学习需求分析 1.1需求描述 学成在线作为在线教育网站,提供多种学习形式,包括:录播.直播.图文.社群等,学生登录进入学习中心即可 在线学习,本章节将开发录播课程的在线学习功能,需求如下: 1. ...

  9. 学成在线笔记+踩坑(5)——【媒资模块】上传视频,断点续传

    导航: [黑马Java笔记+踩坑汇总]JavaSE+JavaWeb+SSM+SpringBoot+瑞吉外卖+SpringCloud+黑马旅游+谷粒商城+学成在线+牛客面试题 目录 5 上传视频 5.1 ...

  10. Java的新项目学成在线笔记-day13(八)

    6 媒资管理 前边章节完成在线视频播放,如何实现点击课程计划播放视频呢,课程视频如何管理呢? 本节开始将对课程视频进行管理. 6.1需求分析 媒资管理系统是每个在线教育平台所必须具备的,百度百科对它的 ...

最新文章

  1. php键顺序初始化,Java类中各成员初始化的顺序
  2. 从0梳理1场NLP赛事!
  3. 计算机专业英语第3章,计算机专业英语教案第3章
  4. 机房系统(四)——【分页控件SSTab】
  5. redhat9.0配置apache 出现乱码
  6. Struts2中的全局结果集
  7. Python之 if-elif-else
  8. macbook pro M1Pro安装java开发环境,jdk和eclipse安装包快速下载方式
  9. DLL注入explorer.exe进程
  10. su室外渲染参数设置_【QA答疑】VRay3.4 for SketchUp2017 渲染参数设置
  11. Excel快捷键总结
  12. Excel ActiveX组合框项目选择宏
  13. 【KITTI】KITTI数据集简介(四) — 标定校准数据calib
  14. 前端组件化开发实践总结
  15. ACdream 1069 无耻的出题人 无聊写着玩的题
  16. 2021WSB-day3-1 - Arun Ross 老师讲解Privacy Preserving Biometrics
  17. A cost-effective recommender system for taxi drivers
  18. 【Python3】抓取Github吉祥物Octocat昵图并下载到本地
  19. 新海诚画集[秒速5センチメートル:樱花抄·春]
  20. DIY制作示波器的超详细教程:(一)我不是为了做一个示波器

热门文章

  1. 解决UE4官方文档C++API查询慢问题
  2. 单片机步进电机正反转C语言程序,单片机控制步进电机正反转
  3. CRM客户关系管理系统HR人事OA系统APP源码
  4. CAD转GIS工具软件下载
  5. 惠普136nw打印机清零_HP打印机清零方法(冷复位恢复出厂设置)
  6. 计算机维修与维护怎么学,做电脑维修需要学习哪些知识呢?
  7. VLAN Mapping实现同一网段不同VLAN的主机通信
  8. Egg教程_Egg.js视频教程免费分享
  9. 介绍两个office软件的插件,很好用——SaveAsPDFandXPS.exe和OfficeTab
  10. 计算机操作系统安装实验报告,操作系统实验报告1.doc