视频点播解决方案
流媒体的概念:
将视频文件分成许多小的视频文件片段,当用户请求视频资源的时候,将视频文件片段包通过网络传输发送过去,从而实现一边传输数据包一边播放视频
流式传输:
1.顺序流式传输
即顺序下载音、视频文件,可以实现边下载边播放,不过,用户只能观看已下载的视频内容,无法快进到未
下载的视频部分,顺序流式传输可以使用Http服务器来实现,比如Nginx、Apache等。
2.实时流式传输
实时流式传输可以解决顺序流式传输无法快进的问题,它与Http流式传输不同,它必须使用流媒体服务器并
且使用流媒体协议来传输视频,它比Http流式传输复杂。常见的实时流式传输协议有RTSP、RTMP、RSVP等。
直播使用rtmp协议:使用rtmp协议需要架设媒体服务器,造价高
HLS协议视频播放的流程:
将视频拆分成若干ts格式的小文件,通过m3u8格式的索引文件对这些ts小文件建立索引。一般
10秒一个ts文件,播放器连接m3u8文件播放,当快进时通过m3u8即可找到对应的索引文件,并去下载对应的ts文
件,从而实现快进、快退以近实时 的方式播放视频。
浏览器播放器:
H5播放器(Video.js),flash播放器
断点续传解决方案:
1、上传前先把文件分成块
2、一块一块的上传,上传中断后重新上传,已上传的分块则不用再上传
3、各分块上传完成最后合并文件
使用百度提供的WebUpload实现前端断点续传,网址 http://fexteam.gz01.bdysite.com/webuploader/
视频点播解决方案整体流程:
1.教学管理系统使用WebUpload前端,采用断点续传的方式上传文件
2.服务器端先得到文件片段,再合并为一个完整的文件
3.服务器端再使用FFmpeg将这个文件编码设置参数并拆分为新的.ts结尾的文件片段,并在.m3u8中保存视频的索引,并将.ts文件和.m3u8文件存储到文件服务器,并部署到nginx服务器下
4.用户点播视频,首先会将.m3u8文件下载下来,再根据.m3u8文件中的索引排序下载相应的.ts文件,从而达到快进快退的效果
5.浏览器播放视频,默认使用h5播放器,如果不支持h5就换位flash播放器,如果还不支持就提示用户更换浏览器
流媒体的原理代码:
@Test
public void testThunk(){
File targetFile = new File("h:/test.avi");
if(!targetFile.isFile()){
throw new RuntimeException();
}
File thunkDir = new File("h:/thunkDir/");
if(!thunkDir.exists()){
thunkDir.mkdirs();
}
int fileSize = 1 * 1024 *1024;
byte[] buff = new byte[1024];
int count = (int)Math.ceil( targetFile.length() * 1.0 / fileSize );
RandomAccessFile random_read = new RandomAccessFile(targetFile,"r");
int len;
for(int i = 1; i <= count ; i++ ){
File thunkFile = new File(thunkDir,i + "");
thunkFile.createNewFile();
RandomAccessFile random_write = new RandomAccessFile(thunkFile,"rw");
while((len = random_read.read(buff)) != -1){
random_write.write(buff,0,len);
if(thunkFile.length() >= fileSize){
break;
}
}
random_write.close();
}
random_read.close();
}
@Test
public void testMerge(){
File thunkDir = new File("h:/thunkDir/");
if(!thunkDir.isDirectory()){
throw new RuntimeException();
}
File mergeFile = new File("h:/mergeFile.avi");
if(mergeFile.exists()){
mergeFile.delete();
}
mergeFile.createNewFile();
RandomAccessFile random_write = new RandomAccessFile(mergeFile,"rw");
byte[] buff = new byte[1024];
int len;
File[] files = thunkDir.listFiles();
List<File> fileList = Arrays.asList(files);
Collections.sort(fileList,new Comparator<File>() {
@Override
public int compare(File o1, File o2) {
return Integer.parseInt(o1.getName()) - Integer.parseInt(o2.getName());
}}
);
for(File file : fileList){
RandomAccessFile random_read = new RandomAccessFile(file,"r");
while((len = random_read.read(buff))!=-1){
random_write.write(buff,0,len);
}
random_read.close();
}
random_write.close();
}
=WebUpload============
before-send-file
before-send-file
文件开始上传前前端请求服务端准备上传工作
public ResponseResult register(String fileMd5,
String fileName,
Long fileSize,
String mimetype,
String fileExt);
1.根据fileMd5 + fileExt 找到文件目录下是否已经存在了这个文件
2.根据fileMd5从MongoDB中查询是否已经存在文件信息
3.如果两者都为true,则认为文件已经存在,抛出异常
4.创建存储数据片段的目录
before-send
before-send
上传分块前前端请求服务端校验分块是否存在。
public CheckChunkResult checkChunk(String fileMd5,
Integer chunk,
Integer chunkSize);
1.根据fileMd5 + chunk从数据片段目录从判断文件是否已经存在
2.如果存在,判断数据是否完整,不完整就删除
public ResponseResult uploadChunk(MultipartFile file,
Integer chunk,
String fileMd5);
1.先判断上传的文件片段是否为空,空就抛异常
2.以chunk为文件名,在数据片段目录下创建文件
3.通过file.getInputStream()得到输入流,然后写入到新建的文件中
after-send-file
after-send-file
在所有分块上传完成后触发,可以请求服务端合并分块文件
public ResponseResult mergeChunk(String fileMd5,
String fileName,
Long fileSize,
String mimetype,
String fileExt);
1.根据fileMd5得到数据片段文件目录下的所有的thunk文件
2.对chunk文件进行排序,输出到合并文件中
3.根据DigestUtils.md5DigestAsHex(new FileInputStream(file))得到上传文件的MD5编码,跟fileMd5比较,true表示合并文件跟原始文件一致