本帖最后由 MrXinYuan 于 2023-2-13 17:23 编辑
前言
之前一个朋友给我写了一个工具:m3u8在线下载工具,解释一下能够通过网页,下载m3u8视频到本地为Mp4,我就很震惊,随后了解到他使用了ffmpeg的wasm,最后又发现了一些列问题:wasm下载的话,会带来严重的跨域问题,可是经过研究油猴上某位大佬的这个工具我发现他的就不存在跨域问题,随后发现他的实现是使用ajax请求拿到每一个m3u8的ts片段,然后合并成为一个mp4,说干就干。
彩蛋
提前放个图,只看文字是在枯燥,也不知道各位看官有没有兴趣。
逛gay
要做一个程序,第一步当然是逛一下GitHub,找一找有没有可以用的轮子,当然让我找到了:https://github.com/Momo707577045/m3u8-downloader
这位大佬研究的很透彻,所以我还写什么呢?看大佬的就好了。似乎没有问题,不过我还是记下来留作是这次做这个小工具的纪念吧。
通过看大佬的代码,基本上了解了大概流程:
- 使用ajax获取m3u8文件信息
- 解析信息,判断是否需要解密,如果需要就那要密钥
- 继续解析,获取到所有ts片段地址
- 下载所有的ts片段并转换成mp4,同样是用Ajax,转换使用muxjs(如果需要解密的话先解密)
- 最后把下载好的片段合并然后创建一个a标签下载
代码
以下代码仅为片段,完整版在这里:
https://github.com/ZN-GG/ZNGG-Nuxt3/blob/main/pages/tool/detail/M3U8V2Pro.vue
使用ajax获取m3u8信息
const { origin } = new URL(url.value);
let data = await ajax(url.value);
if (data.indexOf('#EXT-X-KEY') > -1) {
aesConf.value.status = true
aesConf.value.method = (data.match(/(.*METHOD=([^,\s]+))/) || ['', '', ''])[2]
aesConf.value.uri = (data.match(/(.*URI="([^"]+))"/) || ['', '', ''])[2]
aesConf.value.iv = (data.match(/(.*IV=([^,\s]+))/) || ['', '', ''])[2]
aesConf.value.iv = aesConf.value.iv ? aesConf.value.stringToBuffer(aesConf.value.iv) : ''
aesConf.value.uri = applyURL(aesConf.value.uri, url.value)
await getAES();
}
// 获取AES配置
async function getAES() {
// alert('视频被 AES 加密,点击确认,进行视频解码')
let res = await ajax(aesConf.value.uri, "arraybuffer")
aesConf.value.key = res
aesConf.value.decryptor = new AESDecryptor()
// aesConf.value.decryptor.constructor()
aesConf.value.decryptor.expandKey(aesConf.value.key);
}
function ajax<T = string>(url: string, type: XMLHttpRequestResponseType = "") {
return new Promise<T>((resolve, reject) => {
const xhr = new XMLHttpRequest();
if (type) {
xhr.responseType = type;
}
xhr.onreadystatechange = () => {
if (xhr.readyState === 4) {
const status = xhr.status;
if (status >= 200 && status < 300) {
resolve(xhr.response);
} else {
reject(new Error("请求失败"));
}
}
};
xhr.open("GET", url, true);
xhr.send(null);
});
}
防止错误文件缓存
有时候一些片段会下载错误,但是如果重新发起请求的话,chrome会拿起错误的缓存给我们,所以需要在下载时防止缓存,我们仅需要在ts后加一个不断变化的参数即可,时间戳就是一个不错的选择:
let r = '?r=' + new Date().getTime() + Math.random() + Math.random();
let data = await ajax<Buffer>(item.src + r, "arraybuffer");
剑走偏锋
标题起的好,其实就是个小垃圾找不到解决办法才这样的,在合成mp4的时候用的时mux.js这个js库,但是总是出现以ig问题:合成的mp4时间线有问题,2分钟的视频它写成俩小时,整个我肯定不能忍,但是无论如何都是这样,可是别人的又没有这个问题,于是我只能狗起来,明明用的时nuxt3,却只能像传统的html那样,外挂一个mux.min.js,去解决问题:
onMounted(() => {
let script = document.createElement('script');
script.type = 'text/javascript';
script.src = '/js/mux-mp4.min.js';
document.getElementsByTagName('body')[0].appendChild(script)
})
兄弟们,轻喷。
代码里有很多东西都是看前辈所写,另有一些自以为是的优化,故而成了现在这个样子。
那么下一个工具做什么呢?
|