tencent cloud

文档反馈

m3u8 改写与鉴权

最后更新时间:2023-04-13 11:14:30
    该示例对 m3u8 改写,添加 Type A 鉴权,实现访问 .m3u8 与 .ts 片段资源的权限控制。开发者可根据需要修改代码,支持其他鉴权方式。

    示例代码

    // Type A鉴权私钥,请自行设定并防止泄漏
    const PK = '0123456789';
    // 加密校验 key 的有效时间(秒)
    const TTL = 60;
    const KEY_NAME = 'key';
    const UID = 0;
    const SUFFIX_LIST = ['.m3u8', '.ts'];
    
    addEventListener('fetch', (event) => {
    event.respondWith(handleEvent(event));
    });
    
    async function handleEvent(event) {
    try {
    const { request } = event;
    const urlInfo = new URL(request.url);
    const suffix = getSuffix(urlInfo.pathname);
    // 检查文件后缀为:.m3u8,.ts
    if (!SUFFIX_LIST.includes(suffix)) {
    return fetch(request);
    }
    // Type A 鉴权
    const checkResult = await checkTypeA(urlInfo);
    if (!checkResult.flag) {
    return new Response(checkResult.message, {
    status: 403,
    headers: {
    'X-Auth-Err': checkResult.message
    },
    });
    }
    // 改写 .m3u8 并响应
    if (suffix === '.m3u8') {
    return fetchM3u8({
    request,
    querySign: {
    basePath: urlInfo.pathname.substring(0, urlInfo.pathname.lastIndexOf('/')),
    ...checkResult.querySign,
    }
    });
    }
    // 响应 .ts 资源
    if (suffix === '.ts') {
    return fetchTs(request);
    }
    } catch (error) {
    return new Response(error.stack, { status: 544 });
    }
    return fetch(request);
    }
    
    async function checkTypeA(urlInfo) {
    const sign = urlInfo.searchParams.get(KEY_NAME) || '';
    const elements = sign.split('-');
    
    if (elements.length !== 4) {
    return {
    flag: false,
    message: 'Invalid Sign Format',
    };
    }
    
    const [ts, rand, uid, md5hash] = elements;
    if (ts === undefined || rand === undefined || uid === undefined || md5hash === undefined) {
    return {
    flag: false,
    message: 'Invalid Sign Format',
    };
    }
    
    if (!isNumber(ts)) {
    return {
    flag: false,
    message: 'Sign Expired',
    };
    }
    
    if (Date.now() > (Number(ts) + TTL) * 1000) {
    return {
    flag: false,
    message: 'Sign Expired',
    };
    }
    
    const hash = await md5([urlInfo.pathname, ts, rand, uid, PK].join('-'));
    if (hash !== md5hash) {
    return {
    flag: false,
    message: 'Verify Sign Failed',
    };
    }
    return {
    flag: true,
    message: 'success',
    querySign: {
    rand,
    uid,
    md5hash,
    ts,
    },
    };
    }
    
    async function fetchM3u8({ request, querySign }) {
    request.headers.delete('Accept-Encoding');
    let response = null;
    try {
    response = await fetch(request);
    if (response.status !== 200) {
    return response;
    }
    } catch (error) {
    return new Response('', {
    status: 504,
    headers: { 'X-Fetch-Err': 'Invalid Origin' }
    });
    }
    
    const content = await response.text();
    const lines = content.split('\\n');
    
    const contentArr = await Promise.all(
    lines.map(line => rewriteLine({ line, querySign }))
    );
    return new Response(contentArr.join('\\n'), response);
    }
    
    async function fetchTs(request) {
    let response = null;
    try {
    response = await fetch(request);
    if (response.status !== 200) {
    return response;
    }
    } catch (error) {
    return new Response('', {
    status: 504,
    headers: { 'X-Fetch-Err': 'Invalid Origin' }
    });
    }
    return response;
    }
    
    async function rewriteLine({ line, querySign }) {
    // 跳过空行
    if (/^\\s*$/.test(line)) {
    return line;
    }
    if (line.charAt(0) === '#') {
    // 处理 #EXT-X-MAP
    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}?${KEY_NAME}=${key}`;
    }
    
    async function createSign(querySign, line) {
    const { ts, rand, uid = 0 } = querySign;
    const pathname = `${querySign.basePath}/${line}`;
    
    const md5hash = await md5([pathname, ts, rand, uid, PK].join('-'));
    const key = [ts, rand, uid, md5hash].join('-');
    
    return key;
    }
    
    function getSuffix(pathname) {
    const suffix = pathname.match(/\\.m3u8|\\.ts$/);
    return suffix ? suffix[0] : null;
    }
    
    function isNumber(num) {
    return Number.isInteger(Number(num));
    }
    
    function bufferToHex(arr) {
    return Array.prototype.map
    .call(arr, (x) => (x >= 16 ? x.toString(16) : '0' + x.toString(16)))
    .join('');
    }
    
    async function md5(text) {
    const buffer = await crypto.subtle.digest('MD5', TextEncoder().encode(text));
    return bufferToHex(new Uint8Array(buffer));
    }
    

    示例预览

    在浏览器地址栏中输入匹配到边缘函数触发规则的 URL(如:http://www.example.com/index.m3u8?key=1678873033-123456-0-32f4xxxxcabcxxxx1602xxxx6756d8f4),即可预览到示例效果。
    
    
    

    相关参考

    联系我们

    联系我们,为您的业务提供专属服务。

    技术支持

    如果你想寻求进一步的帮助,通过工单与我们进行联络。我们提供7x24的工单服务。

    7x24 电话支持