现如今网络上的视频大多数都是m3u8格式的,使用m3u8格式有以下好处
-
方便切换码率,例如从高清转到蓝光
-
节约流量,m3u8实际切割成一段段的TS后缀视频,传统请求是把整个文件流返回去,网络不好或者文件过大时,都会造成响应缓慢,m3u8则是返回一个个的ts文件,当前ts缓存完才会自动请求下一个ts,ts切割的很小,所以,几乎是秒响应
3 防盗,m3u8可以对ts文件加密,其他就不知道
下面介绍如何下载m3u8
本次介绍一个下载框架,aria官方文档 1.1 开始 · Aria 使用指南 (laoyuyu.me)
需要在app.build引入aria
implementation 'me.laoyuyu.aria:core:3.8.16'
annotationProcessor 'me.laoyuyu.aria:compiler:3.8.16'
implementation 'me.laoyuyu.aria:m3u8:3.8.16'
引入完成后,可以使用以下代码下载文件,,如果是mp4或者图片,可以使用以下代码下载,下载代码:
Aria.download(this).register();//注册aria
long taskId = Aria.download(SingleTaskActivity.this)
.load(url) // 下载地址
.setFilePath(filePath) // 设置文件保存路径
.create();
如果是m3u8,需要重写IBandWidthUrlConverter和IVodTsUrlConverter,先说说m3u8,我们打开一个m3u8,发现他是一个索引文件,记录下一个m3u8的码率,下一个m3u8则记录对应的ts文件,如下
但是有这样3三种情况,
-
记录的码率文件是全路径路径,如:https://xxx.com/xxx/index.m3u8
-
记录的码率是相对路径,如 xxx/index.m3u8(上图就是这种情况)
-
记录的码率只要文件名,如 index.m3u8
那么aria就不知根据那种规则下载,所以我们要重写IBandWidthUrlConverter,同理Ts文件也一样,也要重写IVodTsUrlConverter
重写IBandWidthUrlConverter:
public class M3U8_Converter implements IBandWidthUrlConverter {
@Override
public String convert(String m3u8Url, String bandWidthUrl) {
System.out.println("m3u8Url:"+m3u8Url);
System.out.println("bandWidthUrl:"+bandWidthUrl);
//m3u8Url:第一个m3u8,连接到下一个m3u8
//bandWidthUrl:第二个m3u8,包含不同码率的ts视频
/*第二个m3u8有些文件带有全路径,如:http://xxxx.xxx.com/xxx
*有些没有,如:/20210927/3oCoCiM4/hls/index.m3u8
*因此需要区分,区分规则如下:
* 如果包含有http或者https,一定是全路径,直接访问
* 如果没有则是用域名+第二个文件路径,如:(域名)http://xxxx.xxx.com/20210927/3oCoCiM4/hls/index.m3u8(路径)
*/
if(bandWidthUrl.contains("http://")||bandWidthUrl.contains("https://")){
return bandWidthUrl;
}else{
//获取域名
URL url = new URL(m3u8Url);
String host=url.getProtocol()+"://"+url.getHost()+":"+url.getDefaultPort();
return host+"/"+bandWidthUrl;
}
}
重写IVodTsUrlConverter
public List<String> convert(String m3u8Url, List<String> tsUrls) {
System.out.println("m3u8Url:"+m3u8Url);
System.out.println("tsUrls:"+tsUrls);
//m3u8Url:m3u8文件,包含ts文件
//tsUrls:所有的ts文件
/*某些ts文件带有全路径,如:http://xxxx.xxx.com/xxx.ts
*有些没有只有相对路径,如:/20210927/3oCoCiM4/hls/xx.ts
* 有些只有文件名,如:xxx.ts
*因此需要区分,区分规则如下:
* 如果包含有http或者https,一定是全路径,直接访问
* 如果只有相对则是用域名+ts文件路径,如:(域名)http://xxxx.xxx.com/20210927/3oCoCiM4/hls/0.ts(路径)
* 如果只有文件名,则是1 截取0到xxx.m3u8(不包含)的路径 2加上ts文件名,如:
* http://xxxx.xxx.com/20210927/3oCoCiM4/index.m3u8
* 去掉路径中xxx.m3u8的路径
* http://xxxx.xxx.com/20210927/3oCoCiM4/
* 加上ts文件名
*/
List<String> newTslist=new ArrayList<>();
String pattern="[0-9a-zA-Z]+[.]ts";
Pattern r = Pattern.compile(pattern);
for (int i = 0; i < tsUrls.size(); i++) {
String tspath=tsUrls.get(i);
Matcher m = r.matcher(tspath);
//全路径
if(tspath.contains("http://")||tspath.contains("https://")){
newTslist.add(tspath);
}
//只有文件名
else
if(m.find()){
int e=m3u8Url.lastIndexOf("/")+1;
newTslist.add(m3u8Url.substring(0,e)+tspath);
}
//相对路径
else{
String host=Host.gethost(m3u8Url);
newTslist.add(host+"/"+tspath);
}
}
return newTslist;
}
重写完这两个类,我们启动下载时,就会根据我们重写的规则进行访问
启动下载
M3U8VodOption option = new M3U8VodOption(); // 创建m3u8点播文件配
option.setUseDefConvert(false);//必须设置false,否则不会使用你重写那两个类
option.setBandWidthUrlConverter(new M3U8_Converter());
option.setVodTsUrlConvert(new TS_Converter());
option.generateIndexFile();
long taskId = Aria.download(Activity_Home.context)
.load("m3u8的下载路径")
.setFilePath("保存路径", true) // 设置点播文件保存路径,true表示,如果当前目录有文件,则覆盖相同的文件
.m3u8VodOption(option) // 调整下载模式为m3u8点播
.create();
接下来是几个注解
//任务开始时
@Download.onTaskStart void taskStart(DownloadTask task) {}
//任务运行时
@Download.onTaskRunning void running(DownloadTask task) {}
//任务停止时
@Download.onTaskStop void taskStop(DownloadTask task) {}
//下载失败时
@Download.onTaskFail void taskFail(DownloadTask task) {}
//下载完成时
@Download.onTaskComplete void taskComplete(DownloadTask task) {}
//任务删除时
@Download.onTaskCancel void taskCancel(DownloadTask task) {}
其中DownloadTask有以下方法
DownloadTask.getPercent(); //下载进度
DownloadTask.getSpeed();//下载速度,原始数据,需要手动转为mb/s,kb/s
DownloadTask.getConvertFileSize();//文件大小,已处理单位,自动为xxmb或者xxgb
DownloadTask.getConvertCurrentProgress();//已下载的文件大小,单位已处理
具体用法可以参考官方文档
这样m3u8就下载好了,我们可以在@Download.onTaskRunning void running(DownloadTask task) {}中监听下载进度等,具体可以根据实际业务开发
接下来我们讲讲m3u8的播放,网络m3u8可以直接赋值给videoview,也可以正常播放,我们理所当然的认为下载到本地的m3u8也可以赋值给videoview,然后正确的播放,但是,事实上,我们把本地路径赋值给videoview,不能正确播放,会出现该视频无法播放,那么怎么解决呢,m3u8格式只支持网络播放,那么我们把本地路径换为本地网络路径就可以解决,如http://127.0.0.1:80/index.m3u8,需要本地搭建webservice,搭建步骤如下:
引入nanohttpd
implementation 'org.nanohttpd:nanohttpd:2.3.1'
然后继承实现NanoHTTPD
public class MyServer extends NanoHTTPD {
public static String M3U8="application/x-mpegURL";
public static String Video="video/mp4";
public static String text="video/mp4";
public static String TS="video/mp2t";
public MyServer(int port) {
super(port);
}
public MyServer(int port,Context context) {
super(port);
}
public MyServer(String hostname, int port) {
super(hostname, port);
}
@Override
public Response serve(IHTTPSession session) {
try {
//文件下载得保存路径
String path = "/storage/emulated/0/Android/data/com.example.iflq/files/Download/";
//本地地址转为网络地址
if (session.getUri().contains(path)) {
File f = new File(session.getUri());
FileInputStream inputStream = new FileInputStream(f);
return newFixedLengthResponse(Response.Status.OK, M3U8, inputStream,f.length());
}
//默认页面
String msg = "<html><body><h1>Hello server</h1></body></html>";
return newFixedLengthResponse(msg);
}catch (Exception e){
e.printStackTrace();
String msg = "<html><body><h1>服务器出错</h1></body></html>";
return newFixedLengthResponse(msg);
}
}
}
启动webservice
这时候我们就可以把m3u8赋值给videoview了,videoView.setVideoPath(“http://127.0.0.1:8080/index.m3u8”);
注意文件路径(必须存在,否则一样不能播放),注意文件的返回格式(application/x-mpegURL)
效果:
From:最菜的黑客