Skip to content

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

@fresleo

Description

@fresleo

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

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    Status

    Top priorities

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions