javascriptreactjsvideostreambytestream

streaming a video into a web player using javacsript (react component)


I am using Javascript, however the possibly important caveat is in a WebView. I have a stream of data which is an mp4 that I can retrieve like so

            window.runtime.EventsOn('playback-continue', (chunk) => {
                console.log("chunk ", chunk)
                queue.push(new Uint8Array(chunk));
                appendToBuffer(sourceBuffer);
            });

I have no issues retrieving the bytes this way (if i print the chunk i can see the data albeit its printed in base64).

However I can't seem to be able to use this to stream a video into a player.

My component looks like


const VideoPlayer = () => {
    const videoRef = useRef(null);
    const mimeCodec = 'video/mp4; codecs="avc1.4D401F"';
    let queue = [];
    const mediaSource = new MediaSource();
    useEffect(() => {
        if (!('MediaSource' in window) || !MediaSource.isTypeSupported(mimeCodec)) {
            console.error('Unsupported MIME type or codec: ', mimeCodec);
            return;
        }


        const video = videoRef.current;
        video.srcObject = mediaSource;

        mediaSource.addEventListener('sourceopen', () => {
            const sourceBuffer = mediaSource.addSourceBuffer(mimeCodec);

            window.runtime.EventsOn('playback-continue', (chunk) => {
                console.log("chunk ", chunk)
                queue.push(new Uint8Array(chunk));
                appendToBuffer(sourceBuffer);
            });

            window.runtime.EventsOn('playback-end', () => {
                mediaSource.endOfStream();
            });

            function appendToBuffer(sourceBuffer) {
                if (queue.length > 0 && !sourceBuffer.updating && mediaSource.readyState === 'open') {
                    sourceBuffer.appendBuffer(queue.shift());
                }
            }

            sourceBuffer.addEventListener('updateend', () => appendToBuffer(sourceBuffer));
            sourceBuffer.addEventListener('error', (e) => console.error('SourceBuffer error:', e));
        });

        return () => {
            window.runtime.EventsOff('playback-continue');
            window.runtime.EventsOff('playback-end');
        };
    }, []);

    const startStreaming = () => {
        const videoPath = '/Users/username/Desktop/video.mp4'; // Change to your actual video file path
        OpenVideoFile(videoPath);
    };
    const stopStreaming = () => {
        mediaSource.endOfStream();
    };
    return (
        <div>
            <button onClick={startStreaming}>Start Streaming</button>
            <button onClick={stopStreaming}>Stop Streaming</button>
            <video ref={videoRef} controls />
        </div>
    );
};

I have tried to find blogs/tutorials but I think the issue is how I am passing the stream to the player, not the player code itself. I just don't see anything except a continual spinner on the player

P.S OpenVideoFile calls a backend function that begins emitting the stream


Solution

  • " I have a stream of data which is an mp4 "

    (1) The MP4 must be in fragmented MP4 format.
    A "regular" MP4 cannot work in mediaSource (because that API expects chunked data, with each chunk having its own audio/video header. A regular MP4 doesn't have such multiple headers).

    If you need to convert (mux) the MP4 into being fragmented, then try by using the free FFmpeg tool. There might be a JS muxer out there.

    (2) Make sure codecs="avc1.4D401F" is the correct setting for your MP4 video file.

    You can check by opening the file in a hex editor and look for "avcC" text on the right-hand side, then the codec info is next 4 bytes (usually starting with an 01 if your MP4 has H264 video codec inside it):

    example 4 bytes:
    01 64 XX YY means set as codecs="avc1.64XXYY" (write XX/YY values as shown in hex editor).

    Also you can do it automatically by JS code.
    See if my function helps you with that, by trying Step 2's example code.

    (3) Convert from Base64 into actual binary data (as it would have been in the original file). I guess Base64 is needed for sending purposes? I have added a modified version of the linked Answer for quick testing:

    window.runtime.EventsOn('playback-continue', 
    (chunk) =>  {
                    console.log("chunk ", chunk);
                    
                    let temp_bytes = convert_B64_toBytes(chunk);
                    queue.push(temp_bytes);
                    appendToBuffer(sourceBuffer);
                }
    );
                        
                        
    

    Where the supporting function is:

    function convert_B64_toBytes( input_str_base64 ) 
    {
        //# eg: AtoB decode gives you original byte 0xFF as text "FF"
        let str_binary = atob(input_str_base64);
        let bytes = new Uint8Array(str_binary.length);
        
        //# fill the bytes array (eg: convert "FF" back to a byte (integer)
        for (var i = 0; i < str_binary.length; i++) 
        { bytes[i] = str_binary.charCodeAt(i); }
        return bytes;
    }