如何使用腾讯 EdgeOne 边缘函数动态重写 M3U8 文件?
目前,主要的主流制造商已经推出了各自的边缘无服务器服务,例如CloudFlare Workers、Vercel Edge Runtime等。腾讯EdgeOne边缘函数提供了一种部署在边缘节点上的无服务器代码执行环境。您只需编写业务功能代码并设置触发规则,即可在靠近用户的边缘节点上弹性和安全地运行代码。
1. 什么是M3U8文件?
M3U8是一种用于存储多媒体播放列表的文件格式。它基于M3U格式,"8"表示它使用UTF-8字符编码。M3U8文件主要用于HTTP直播流(HLS),这是Apple开发的一种流行自适应流协议,用于通过互联网传输视频和音频内容。
M3U8文件包含一个媒体文件URL的列表,可以是音频、视频或两者的混合。它还可以包括元数据和其他信息,例如每个媒体片段的持续时间和片段的顺序。在HLS的上下文中,M3U8文件用于创建自适应流播放列表,允许视频播放器根据观看者的网络状况和设备能力在不同质量级别或比特率之间切换。 在本文中,我们提供了关于M3U8相关内容的详细介绍。
2. 如何使用边缘函数重写M3U8文件?
在实际使用中,开发人员可能需要重写和处理M3U8文件。以下是一些常见场景:
- 定制播放列表:开发人员可能需要根据用户的网络状况、设备性能或地理位置选择不同的媒体流。
- 内容安全和访问控制:为了保护版权内容,开发人员可能需要处理M3U8文件以实现加密、添加访问令牌和其他功能。
- 广告插入:在流媒体内容中插入广告是一种常见的商业模式。开发人员可以修改M3U8文件,在视频播放的特定点插入广告片段。
重写和处理M3U8文件可以帮助开发人员实现更丰富、更灵活的流媒体应用场景。
得益于EdgeOne边缘函数的可编程性,开发人员可以在边缘节点处理M3U8媒体文件,动态修改和注入内容。
现在,让我们来看三个场景,以了解如何使用边缘函数动态重写M3U8文件。
2.1 添加URL认证
重写M3U8文件内容,并为每个TS文件请求添加URL认证参数,主要是为了保护视频内容的安全。以下是一些可能的场景:
- 付费内容:例如,一些视频网站可能提供付费电影、电视节目或直播服务。这些网站可以使用URL认证来确保只有付费用户可以访问内容;
- 会员内容:一些网站可能为会员提供独家内容。这些网站可以使用URL认证来限制这些内容仅供会员访问;
我们可以在边缘函数中实现动态URL认证参数的添加,以达到保护内容的目的。
假设原始M3U8文件如下:
#EXTM3U
#EXT-X-VERSION:3
#EXT-X-TARGETDURATION:10
#EXT-X-MEDIA-SEQUENCE:0
#EXTINF:10.000000,
8k60_-bulgaria_8k_hdr_60p_fuhd692899210.ts
#EXTINF:10.000000,
8k60_-bulgaria_8k_hdr_60p_fuhd692899211.ts
#EXTINF:10.000000,
8k60_-bulgaria_8k_hdr_60p_fuhd692899212.ts
...
#EXTINF:9.360422,
8k60_-bulgaria_8k_hdr_60p_fuhd6928992133.ts
#EXT-X-ENDLIST
客户端获取M3U8文件的HTTP请求携带Type A参数auth_key,形式为:http://localhost:8080/xxx.m3u8?auth_key=1698752518-0-00001-ff87dbc0598...
该请求触发边缘函数,该函数从M3U8 HTTP请求的auth_key参数中检索关键信息,如时间戳、随机数和用户ID。根据这些参数,它计算每个TS请求的Type A认证参数auth_key,并修改M3U8文件中每个TS请求的URL:
...
async function rewriteLine({ line, querySign }) {
if (/^\s*$/.test(line)) {
return line;
}
if (line.charAt(0) === "#") {
if (line.startsWith("#EXT-X-MAP")) {
const key = await createSign(querySign, line);
line = line.replace(/URI="([^"]+)"/, (matched, p1) => {
return p1 ? matched.replace(p1, `${p1}?key=${key}`) : matched;
});
}
return line;
}
const key = await createSign(querySign, line);
return `${line}?${AUTH_KEY_NAME}=${key}`;
}
async function createSign(querySign, line) {
const { basePath, ts, rand, uid } = querySign;
const pathname = `${basePath}/${line}`;
const md5hash = await md5([pathname, ts, rand, uid, SECRET_KEY].join("-"));
const key = [ts, rand, uid, md5hash].join("-");
return key;
}
...
经过上述处理,原始M3U8文件中的TS请求已被重写:
客户端播放器解析M3U8文件,并发起携带Type A认证参数auth_key的TS请求,形式为:http://localhost:8080/xxx.ts?auth_key=1698752518-0-00001-88cbab261a...
获取TS资源的HTTP请求仍会触发边缘函数的执行,此时将验证auth_key:
...
async function checkTypeA(urlInfo) {
const sign = urlInfo.searchParams.get(AUTH_KEY_NAME) || "";
const elements = sign.split("-");
if (elements.length !== 4) {
return {
flag: false,
message: "签名格式无效",
};
}
const [ts, rand, uid, md5hash] = elements;
if (!ts || !rand || !uid || !md5hash) {
return {
flag: false,
message: "签名格式无效",
};
}
if (!isNumber(ts)) {
return {
flag: false,
message: "签名已过期",
};
}
const now = Math.floor(Date.now() / 1000);
if (now > Number(ts)) {
return {
flag: false,
message: "签名已过期",
};
}
const hash = await md5(
[urlInfo.pathname, ts, rand, uid, SECRET_KEY].join("-")
);
if (hash !== md5hash) {
return {
flag: false,
message: "验证签名失败",
};
}
return {
flag: true,
message: "成功",
};
}
...
如果TS URL携带的auth_key验证成功,资源将正常返回,客户端可以正常播放视频,如图所示:
如果TS URL携带的auth_key无效或未携带,边缘函数将返回403,视频将无法正常播放:
2.2 插入广告
通过添加TS文件在M3U8文件中插入广告是一种交付在线视频广告的常见方法。这种方法允许无缝地将广告插入视频流。主要应用场景包括:
- 在线视频平台:例如,YouTube、Netflix等,可以在视频的开头、中间或结尾插入广告;
- 直播平台:例如,Twitch、斗鱼等,可以在直播中的特定时刻插入广告;
这种方法相比于其他广告插入方法具有以下优点:
- 灵活性:广告可以根据需要随时插入,例如在视频的开头、中间或结尾;
- 兼容性:这种方法不依赖于特定播放器,只要设备支持HLS,就可以播放广告;
- 个性化:可以根据用户行为、兴趣或地理位置等提供定制广告;
我们可以利用这个思路在边缘节点动态修改M3U8文件中的TS文件列表。
假设原始M3U8文件如下:
#EXTM3U
#EXT-X-VERSION:3
#EXT-X-TARGETDURATION:10
#EXT-X-MEDIA-SEQUENCE:0
#EXTINF:10.000000,
8k60_-bulgaria_8k_hdr_60p_fuhd692899210.ts
#EXTINF:10.000000,
8k60_-bulgaria_8k_hdr_60p_fuhd692899211.ts
#EXTINF:10.000000,
8k60_-bulgaria_8k_hdr_60p_fuhd692899212.ts
...
#EXTINF:9.360422,
8k60_-bulgaria_8k_hdr_60p_fuhd6928992133.ts
#EXT-X-ENDLIST
使用边缘函数,注入预先准备好的广告TS片段:
const AD_TS = `#EXTINF:10.000000,
http://m3u8-1251557890.cos.ap-guangzhou.myqcloud.com/apple_watch_apple_watch_hermes184006680.ts
#EXTINF:10.000000,
http://m3u8-1251557890.cos.ap-guangzhou.myqcloud.com/apple_watch_apple_watch_hermes184006681.ts
#EXTINF:10.000000,
http://m3u8-1251557890.cos.ap-guangzhou.myqcloud.com/apple_watch_apple_watch_hermes184006682.ts
#EXT-X-DISCONTINUITY`;
...
async function handleM3U8(request) {
...
const response = await fetchOrigin(request);
if (response.status !== 200) {
return response;
}
const originalM3U8 = await response.text();
const modifiedM3U8 = originalM3U8.replace(
"#EXT-X-MEDIA-SEQUENCE:0",
`#EXT-X-MEDIA-SEQUENCE:0\n${AD_TS}`
);
return new Response(modifiedM3U8, response);
}
...
经过上述处理,广告TS片段已插入到原始M3U8文件中:
当客户端播放器获取到重写后的M3U8文件时,将首先播放广告:
广告播放完后,无缝切换并播放视频内容:
广告内容在一定时期内相对固定,因此无需重复重写M3U8文件。您可以合理缓存以利用Cache。
2.3 地理定位投放
根据地理位置投放广告可以更有效地实现广告目标,提高广告效果,并提供个性化体验。基于场景2,我们可以进一步修改代码,使用边缘函数请求API的request.eo.geo参数获取客户端的地理位置,注入不同的广告内容,实现精细化广告投放。
const AD_TS_1 = `...AD_TS_1...`;
const AD_TS_2 = `...AD_TS_2...`;
const ADS = {
CN: AD_TS_1,
US: AD_TS_2,
};
...
async function handleM3U8(request) {
...
const response = await fetchOrigin(request);
if (response.status !== 200) {
return response;
}
const originalM3U8 = await response.text();
const alpha2code = request.eo.geo.countryCodeAlpha2;
const AD_TS = ADS?.[alpha2code];
let modifiedM3U8 = originalM3U8;
if (AD_TS) {
modifiedM3U8 = originalM3U8.replace(
"#EXT-X-MEDIA-SEQUENCE:0",
`#EXT-X-MEDIA-SEQUENCE:0\n${AD_TS}`
);
}
return new Response(modifiedM3U8, response);
}
...
类似于基于 request.eo.geo定制广告TS片段,开发人员可以定制视频中其他部分的TS。由于文章长度原因,这里不再演示。
3. 结论
使用腾讯EdgeOne边缘函数在边缘节点重写M3U8文件,方便灵活地进行流媒体处理,同时确保CDN加速。它可以根据用户的实际情况动态调整媒体流,从而优化业务流程并降低维护成本。我们现在已推出免费试用,点击这里或联系我们了解更多信息。