Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

When I use a custom loader, hls to load key or load frag, the content.type is always undefined #6666

Open
5 tasks done
fresleo opened this issue Sep 1, 2024 · 0 comments
Open
5 tasks done
Labels
Bug Needs Triage If there is a suspected stream issue, apply this label to triage if it is something we should fix.

Comments

@fresleo
Copy link

fresleo commented Sep 1, 2024

What version of Hls.js are you using?

1.5.15

What browser (including version) are you using?

Edge

What OS (including version) are you using?

win11

Test stream

No response

Configuration

if (Hls.isSupported() && videoElementRef.value) {
            //请求m3u8
            const { $customLoader } = useNuxtApp();
            const { path, videoID } = await getPath(id);
            hls.value = new Hls(
                {
                    loader: $customLoader as any, // 使用自定义加载器
                    autoStartLoad: false,
                    lowLatencyMode: false, // 禁用低延迟模式
                    maxBufferLength: 30, // 缓冲区中最多保留30秒的视频数据
                    maxMaxBufferLength: 60, // 缓冲区中的最大保留时长
                    backBufferLength: 30,
                    maxBufferSize: 60 * 1000 * 1000, // 缓冲区最大数据量,单位为字节
                    maxBufferHole: 0.5, // 在缓冲的片段之间允许的最大时间间隙,单位为秒
                    startFragPrefetch: true, // 启用片段预加载
                    keyLoadPolicy: 'none',
                    // xhrSetup: function (xhr, url) {
                    //     xhr.withCredentials = true; // 允许发送凭据(如 Cookie)
                    // },
                    debug: true // 启用调试日志
                } as any,
            );
            await hls.value?.loadSource(`${useNuxtApp().$apiBase}/video/${path}/${videoID}.m3u8`);
            hls.value.attachMedia(videoElementRef.value);
            hls.value?.on(Hls.Events.MANIFEST_PARSED, () => {
                console.log('Manifest parsed, video can start playing.');
                hls.value?.startLoad(); // 手动开始加载 TS 片段
            });
            hls.value?.on(Hls.Events.ERROR, (event, data) => {
                console.error('HLS.js error:', event, 'data:', data);
                if (data.fatal) {
                    switch (data.type) {
                        case Hls.ErrorTypes.NETWORK_ERROR:
                            console.error('Fatal network error encountered, trying to recover...');
                            hls.value?.startLoad();
                            break;
                        case Hls.ErrorTypes.MEDIA_ERROR:
                            console.error('Fatal media error encountered, trying to recover...');
                            hls.value?.recoverMediaError();
                            break;
                        default:
                            console.error('Unrecoverable error');
                            hls.value?.destroy();
                            break;
                    }
                }
            });
            console.log('This browser support HLS.');
        }else if (videoElementRef.value.canPlayType('application/vnd.apple.mpegurl')) {
            
            const { path, videoID } = await getPath(id);
            videoElementRef.value.src = `${useNuxtApp().$apiBase}/video/${path}/${videoID}.m3u8`;
            videoElementRef.value.addEventListener('loadedmetadata', () => {
                videoElementRef.value.play();
            });
            console.log('This browser supports native HLS.');
        } else {
            console.error('HLS is not supported by this browser.');
        }

Additional player setup steps

No response

Checklist

Steps to reproduce

  1. hls.value = new Hls(
    {
    loader: $customLoader as any,
    autoStartLoad: false,
    lowLatencyMode: false,
    maxBufferLength: 30,
    maxMaxBufferLength: 60,
    backBufferLength: 30,
    maxBufferSize: 60 * 1000 * 1000,
    maxBufferHole: 0.5,
    startFragPrefetch: true,

                 // xhrSetup: function (xhr, url) {
                 //     xhr.withCredentials = true; 
                 // },
                 debug: true 
             } as any,
         );
    

Expected behaviour

1.Each context has its own type
2.I can't find a way to load a custom key
3.The fragment stream is loaded with the key, and the fragment is loaded when the key is not returned
4.My m3u8 file :
#EXTM3U
#EXT-X-VERSION:3
#EXT-X-TARGETDURATION:7
#EXT-X-MEDIA-SEQUENCE:0
#EXT-X-PLAYLIST-TYPE:VOD
#EXT-X-KEY:METHOD=AES-128,URI="keys/encryption.key",IV=0xe2640f2cbb490f4fa29103ba2cd4afff
#EXTINF:7.200000,
1080P/000.ts
#EXTINF:4.800000,
1080P/001.ts
#EXTINF:4.800000,
1080P/002.ts
#EXTINF:4.800000,
1080P/003.ts
#EXTINF:3.600000,
1080P/004.ts....

What actually happened?

In order to be more specific, I give part of the code, because the content.type has not been correct, so I forced to mediaSegment, part of the comment is Chinese, please ignore:
```
this.xhr.onload = async () => {

        if (this.xhr.status >= 200 && this.xhr.status < 300) {
            stats.tload = performance.now();
            stats.loaded = this.xhr.response.byteLength || this.xhr.responseText.length;
            stats.loading.end = performance.now();
            if (context.type === 'manifest') {
                stats.parsing.start = performance.now();
                const manifestData = this.xhr.responseText;
                stats.parsing.end = performance.now();
                console.log('mainfestData', manifestData);
                // console.log('context', context);
                console.log('callbacks', callbacks);
                console.log('stats', stats);

                const response = {
                    url: this.xhr.responseURL,
                    data: manifestData
                };
                callbacks.onSuccess(response, stats, context, null);
            } else if (context.type === 'mediaSegment') {
                const pathSegments = url.split('/').filter(Boolean); // 去除空字符串部分
                const secondLastSegment = pathSegments[pathSegments.length - 2];

                if (secondLastSegment === 'keys') {

                    console.log('the latest path:', secondLastSegment);
                    console.log('this.xhr.response', this.xhr.response);
                    const response = {
                        url: this.xhr.responseURL,
                        data: this.xhr.response
                    };

                    CustomLoader.soulkey = this.xhr.response;
                    console.log(' CustomLoader.soulkey:', CustomLoader.soulkey); // 确认密钥加载成功
                    callbacks.onSuccess(response, stats, context, null);

                } else 
                 {
                    // 处理 TS 片段
                    const segmentData = new Uint8Array(this.xhr.response);
                    let finalData;
                    // console.log('this.config.soulkey2', this.config.soulkey);

                    if (!CustomLoader.soulkey || !CustomLoader.ivHex) {
                        console.warn('No key available, skipping decryption.');
                        finalData = segmentData;
                    } else {
                        const mm = performance.now();
                        finalData = await this.decrypt(segmentData, CustomLoader.soulkey, CustomLoader.ivHex);
                        const duration = performance.now() - mm;
                        // console.log('duration',duration);
                    }
                    // console.log('segmentData', finalData)
                    const response = {
                        url: this.xhr.responseURL,
                        data: finalData
                    };
                    callbacks.onSuccess(response, stats, context, null);
                }

            } else if (context.type === 'level') {
                stats.parsing.start = performance.now();
                const levelData = this.xhr.responseText;
                stats.parsing.end = performance.now();

                console.log('levelData', levelData);
                // 检查 manifestData 中是否包含 #EXT-X-KEY
                const keyUriMatch = levelData.match(/#EXT-X-KEY:METHOD=AES-128,URI="(.*?)"(?:,IV=(0x[0-9a-fA-F]+))?/);
                const baseUrl = url.substring(0, url.lastIndexOf('/'));
                if (keyUriMatch) {
                    let keyUri = keyUriMatch[1];
                    keyUri = `${baseUrl}/${keyUri}`;
                    const key = await this.loadKey(keyUri);
                    const ivHex = keyUriMatch[2] ? keyUriMatch[2].slice(2) : null;
                    CustomLoader.ivHex = ivHex;
                    CustomLoader.soulkey=key;
                    console.log(' CustomLoader.ivHex:', CustomLoader.ivHex); // 确认密钥加载成功
                }
                const response = {
                    url: this.xhr.responseURL,
                    data: levelData
                };
                // console.log('Before onSuccess:', JSON.stringify(stats));
                callbacks.onSuccess(response, stats, context, null);
                // console.log('After onSuccess:', JSON.stringify(stats));
            }
        } else {
            callbacks.onError({ code: this.xhr.status, text: this.xhr.statusText }, context);
        }
    };

### Console output

```shell
url http://localhost:3001/api/video/2024/08/2024-08-22/0bffc702-ab68-4fe9-ab28-8d777c87bdfb/1080P/000.ts
vconsole.min.js:10 [log] > [stream-controller]: FRAG_LOADING->ERROR
vconsole.min.js:10 [log] > [audio-stream-controller]: STOPPED->ERROR
vconsole.min.js:10 [log] > stopLoad

Chrome media internals output

No response

@fresleo fresleo added Bug Needs Triage If there is a suspected stream issue, apply this label to triage if it is something we should fix. labels Sep 1, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Bug Needs Triage If there is a suspected stream issue, apply this label to triage if it is something we should fix.
Projects
None yet
Development

No branches or pull requests

1 participant