Skip to content

Conversation

@hjanuschka
Copy link

@hjanuschka hjanuschka commented Nov 3, 2025

WIP, might delete later! 🙈

trying to make HLS a thing!

test_hls.html

<!DOCTYPE html>
<html>
<head>
    <title>hls.js MSE Test</title>
    <style>
        body {
            font-family: monospace;
            padding: 20px;
            background: #1e1e1e;
            color: #d4d4d4;
        }
        h1 {
            color: #4ec9b0;
        }
        video {
            width: 640px;
            height: 360px;
            background: #000;
            margin: 20px 0;
        }
        #log {
            background: #252526;
            border: 1px solid #3e3e42;
            padding: 15px;
            max-height: 400px;
            overflow-y: auto;
            margin-top: 20px;
        }
        .log-entry {
            margin: 5px 0;
            padding: 5px;
            border-left: 3px solid #007acc;
            padding-left: 10px;
        }
        .log-error {
            border-left-color: #f48771;
            color: #f48771;
        }
        .log-success {
            border-left-color: #4ec9b0;
            color: #4ec9b0;
        }
        .log-info {
            border-left-color: #dcdcaa;
            color: #dcdcaa;
        }
        button {
            background: #0e639c;
            color: white;
            border: none;
            padding: 10px 20px;
            cursor: pointer;
            margin-right: 10px;
        }
        button:hover {
            background: #1177bb;
        }
    </style>
</head>
<body>
    <h1>hls.js + MSE Test in Ladybird</h1>

    <div>
        <button onclick="testBasicMSE()">Test Basic MSE</button>
        <button onclick="clearLog()">Clear Log</button>
    </div>

    <div style="margin: 15px 0;">
        <label for="streamSelect" style="color: #4ec9b0;">Select Stream:</label>
        <select id="streamSelect" style="padding: 8px; background: #252526; color: #d4d4d4; border: 1px solid #3e3e42; margin: 0 10px;">
            <option value="https://test-streams.mux.dev/x36xhzz/x36xhzz.m3u8">Mux Test Stream (VOD)</option>
            <option value="https://kronetv.mdn.ors.at/live/eds/kronetv_hd/hls3_nodrm/kronetv_hd-nodrm.m3u8">Krone TV Live Stream</option>
            <option value="https://mediaviod-bitmovin.krone.at/nogeo/videos/2025/11/03/c89c4665843c824-dfb_pokal_achtelfinale/stream.m3u8">Krone VOD (DFB Pokal)</option>
        </select>
        <button onclick="loadSelectedStream()">Load Stream</button>
    </div>

    <video id="video" controls></video>

    <div id="log"></div>

    <!-- Load hls.js from CDN -->
    <script src="https://cdn.jsdelivr.net/npm/hls.js@latest"></script>

    <script>
        const video = document.getElementById('video');
        const logDiv = document.getElementById('log');
        let hls = null;

        function log(message, type = 'info') {
            const entry = document.createElement('div');
            entry.className = `log-entry log-${type}`;
            entry.textContent = `[${new Date().toLocaleTimeString()}] ${message}`;
            logDiv.appendChild(entry);
            logDiv.scrollTop = logDiv.scrollHeight;
            console.log(message);
        }

        function clearLog() {
            logDiv.innerHTML = '';
        }

        function testBasicMSE() {
            log('=== Testing Basic MSE Support ===', 'info');

            // Test 1: MediaSource exists
            if (typeof MediaSource !== 'undefined') {
                log('✅ MediaSource is defined', 'success');
            } else {
                log('❌ MediaSource is NOT defined', 'error');
                return;
            }

            // Test 2: Create MediaSource
            try {
                const ms = new MediaSource();
                log('✅ MediaSource instance created', 'success');
                log(`   readyState: ${ms.readyState}`, 'info');
            } catch (e) {
                log(`❌ Failed to create MediaSource: ${e.message}`, 'error');
                return;
            }

            // Test 3: Check codec support
            const codecs = [
                'video/mp4; codecs="avc1.42E01E,mp4a.40.2"',
                'video/webm; codecs="vp9,opus"',
                'video/webm; codecs="vp8,vorbis"',
                'audio/mp4; codecs="mp4a.40.2"',
                'audio/webm; codecs="opus"'
            ];

            log('Testing codec support:', 'info');
            codecs.forEach(codec => {
                const supported = MediaSource.isTypeSupported(codec);
                log(`  ${supported ? '✅' : '❌'} ${codec}`, supported ? 'success' : 'error');
            });

            // Test 4: Check hls.js support
            if (typeof Hls !== 'undefined') {
                log('✅ hls.js loaded successfully', 'success');
                log(`   Version: ${Hls.version || 'unknown'}`, 'info');

                if (Hls.isSupported()) {
                    log('✅ hls.js reports MSE is supported!', 'success');
                } else {
                    log('❌ hls.js says MSE is NOT supported', 'error');
                    if (video.canPlayType('application/vnd.apple.mpegurl')) {
                        log('   Native HLS support detected', 'info');
                    }
                }
            } else {
                log('❌ hls.js not loaded', 'error');
            }
        }

        function loadSelectedStream() {
            const streamSelect = document.getElementById('streamSelect');
            const streamUrl = streamSelect.value;
            const streamName = streamSelect.options[streamSelect.selectedIndex].text;
            loadHLS(streamUrl, streamName);
        }

        function loadHLS(streamUrl, streamName = 'Unknown') {
            log(`=== Loading HLS Stream: ${streamName} ===`, 'info');

            // Check if hls.js is available
            if (typeof Hls === 'undefined') {
                log('❌ hls.js library not loaded!', 'error');
                return;
            }

            log(`Stream URL: ${streamUrl}`, 'info');

            // Destroy previous instance if exists
            if (hls) {
                log('Destroying previous hls.js instance', 'info');
                hls.destroy();
            }

            if (Hls.isSupported()) {
                log('MSE is supported, creating hls.js instance', 'success');

                hls = new Hls({
                    debug: true,
                    enableWorker: false  // Disable worker for easier debugging
                });

                // Attach extensive event listeners for debugging
                hls.on(Hls.Events.MEDIA_ATTACHING, () => {
                    log('📎 MEDIA_ATTACHING', 'info');
                });

                hls.on(Hls.Events.MEDIA_ATTACHED, () => {
                    log('✅ MEDIA_ATTACHED', 'success');
                });

                hls.on(Hls.Events.MANIFEST_LOADING, () => {
                    log('📥 MANIFEST_LOADING', 'info');
                });

                hls.on(Hls.Events.MANIFEST_LOADED, (event, data) => {
                    log(`✅ MANIFEST_LOADED: ${data.levels.length} quality levels`, 'success');
                });

                hls.on(Hls.Events.MANIFEST_PARSED, (event, data) => {
                    log(`✅ MANIFEST_PARSED: ${data.levels.length} levels`, 'success');
                    data.levels.forEach((level, i) => {
                        log(`   Level ${i}: ${level.width}x${level.height} @ ${level.bitrate}bps`, 'info');
                    });
                });

                hls.on(Hls.Events.LEVEL_LOADED, (event, data) => {
                    log(`✅ LEVEL_LOADED: level ${data.level}`, 'success');
                });

                hls.on(Hls.Events.FRAG_LOADING, (event, data) => {
                    log(`📥 FRAG_LOADING: ${data.frag.sn}`, 'info');
                });

                hls.on(Hls.Events.FRAG_LOADED, (event, data) => {
                    log(`✅ FRAG_LOADED: ${data.frag.sn}`, 'success');
                });

                hls.on(Hls.Events.FRAG_PARSING_INIT_SEGMENT, () => {
                    log('🔧 FRAG_PARSING_INIT_SEGMENT', 'info');
                });

                hls.on(Hls.Events.FRAG_PARSING_DATA, () => {
                    log('🔧 FRAG_PARSING_DATA', 'info');
                });

                hls.on(Hls.Events.FRAG_PARSED, () => {
                    log('✅ FRAG_PARSED', 'success');
                });

                hls.on(Hls.Events.BUFFER_CREATED, (event, data) => {
                    log('✅ BUFFER_CREATED', 'success');
                    log(`   Tracks: ${JSON.stringify(Object.keys(data.tracks))}`, 'info');
                });

                hls.on(Hls.Events.BUFFER_APPENDING, (event, data) => {
                    log(`📝 BUFFER_APPENDING: ${data.type}`, 'info');
                });

                hls.on(Hls.Events.BUFFER_APPENDED, (event, data) => {
                    log(`✅ BUFFER_APPENDED: ${data.type}`, 'success');
                });

                hls.on(Hls.Events.ERROR, (event, data) => {
                    log(`❌ ERROR: ${data.type} - ${data.details}`, 'error');
                    if (data.fatal) {
                        log(`   Fatal error! ${data.error?.message || ''}`, 'error');
                        switch(data.type) {
                            case Hls.ErrorTypes.NETWORK_ERROR:
                                log('   Attempting to recover from network error...', 'info');
                                hls.startLoad();
                                break;
                            case Hls.ErrorTypes.MEDIA_ERROR:
                                log('   Attempting to recover from media error...', 'info');
                                hls.recoverMediaError();
                                break;
                            default:
                                log('   Cannot recover, destroying hls.js instance', 'error');
                                hls.destroy();
                                break;
                        }
                    }
                });

                // Load the stream
                try {
                    log('Loading manifest...', 'info');
                    hls.loadSource(streamUrl);

                    log('Attaching to video element...', 'info');
                    hls.attachMedia(video);

                    hls.on(Hls.Events.MANIFEST_PARSED, () => {
                        log('Starting playback...', 'info');
                        video.play().then(() => {
                            log('✅ Video playback started!', 'success');
                        }).catch(err => {
                            log(`❌ Playback error: ${err.message}`, 'error');
                        });
                    });
                } catch (e) {
                    log(`❌ Exception: ${e.message}`, 'error');
                    log(`   Stack: ${e.stack}`, 'error');
                }

            } else if (video.canPlayType('application/vnd.apple.mpegurl')) {
                log('Native HLS support detected, using native playback', 'info');
                video.src = streamUrl;
                video.addEventListener('loadedmetadata', () => {
                    log('✅ Metadata loaded', 'success');
                    video.play();
                });
            } else {
                log('❌ No HLS support available!', 'error');
            }
        }

        // Monitor video events
        video.addEventListener('loadstart', () => log('VIDEO: loadstart', 'info'));
        video.addEventListener('loadedmetadata', () => log('VIDEO: loadedmetadata', 'success'));
        video.addEventListener('loadeddata', () => log('VIDEO: loadeddata', 'success'));
        video.addEventListener('canplay', () => log('VIDEO: canplay', 'success'));
        video.addEventListener('playing', () => log('VIDEO: playing', 'success'));
        video.addEventListener('error', (e) => {
            log(`VIDEO ERROR: ${video.error?.message || 'unknown'}`, 'error');
        });

        // Run basic test on page load
        window.addEventListener('load', () => {
            log('Page loaded, ready to test!', 'info');
            setTimeout(() => testBasicMSE(), 500);
        });
    </script>
</body>
</html>

@ladybird-bot
Copy link
Collaborator

Hello!

One or more of the commit messages in this PR do not match the Ladybird code submission policy, please check the lint_commits CI job for more details on which commits were flagged and why.
Please do not close this PR and open another, instead modify your commit message(s) with git commit --amend and force push those changes to update this PR.

@gmta gmta marked this pull request as draft November 3, 2025 10:56
@github-actions github-actions bot added conflicts Pull request has merge conflicts that need resolution labels Nov 6, 2025
@github-actions
Copy link

github-actions bot commented Nov 6, 2025

Your pull request has conflicts that need to be resolved before it can be reviewed and merged. Make sure to rebase your branch on top of the latest master.

1 similar comment
@github-actions
Copy link

github-actions bot commented Nov 6, 2025

Your pull request has conflicts that need to be resolved before it can be reviewed and merged. Make sure to rebase your branch on top of the latest master.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

conflicts Pull request has merge conflicts that need resolution

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants