Modifying MP4 segments Bitmovin Player Client-Side

Background

You might notice your Stream plays on 80% of devices but fails on 20% of them. i.e typical 80-20 rule. Particularly on Live Streams.

In Bitmovin we’ve seen older devices making the bulk of this 20% devices.

Fixing the Stream

After investigating, if the issue’s with the in-stream mp4 boxes you’re likely to update your encoder/packager. This is COSTLY as regression testing is needed and RISKS breaking a device in your 80%.

At Bitmovin, you’ve ANOTHER alternative. CLIENT-SIDE FIXING of the mp4 box in the client!

A Real-Life Usecase

Problem

  1. Customer’s stream does not have PSSH boxes due to legacy reason
  2. Tizen 2016 Tv MUST have PSSH box in init segment for DRM decoding

Solution

Bitmovin + codem-isoboxer in THREE steps :slight_smile:

  1. Include the codem-isoboxer js script in your HTML file.
    <script type="text/javascript"
        src="https://cdn.jsdelivr.net/npm/codem-isoboxer@0.3.7/dist/iso_boxer.min.js"></script>
  1. Update Bitmovin Player network configuration to preprocess media segments before passing it to decoder.
        var conf = {
            key: 'YOUR-PLAYER-KEY',
            playback: {
                muted: true,
                autoplay: true,
            },
            .....
            .....
            .....
            network: {
                preprocessHttpResponse: function (type, response) {
                    if (type.includes('media')) {
                        response.body = prepareSegment(response.body);
                    }
                    return Promise.resolve(response);
                },
            }
        };
  1. Code to modify MP4 box in the segment. In this case add the PSSH box in the init segment
        function prepareSegment(segmentData) {
            var isoFile = ISOBoxer.parseBuffer(segmentData);

           /****** An init segment MUST contain `ftyp` and `moov` box *****/
            var ftyp = isoFile.fetch('ftyp');
            if (!ftyp) {
                return segmentData;
            }

           var moov = isoFile.fetch('moov');
           if(!moov) {
               return segmentData;
           }
          /************************************************************/

            appendPsshBox(moov);
            console.log('---------edited moov box---------');
            console.log(moov);

            return isoFile.write();
        }

        function appendPsshBox(parent) {
            var psshWidevine = "--YOUR--BASE64--WIDEVINE--PSSH--VALUE--FROM--MANIFEST--";
            var psshPlayready = "--YOUR--BASE64--PLAYREADY--PSSH--VALUE--FROM--MANIFEST--"

            /******************* Widevine ***************************/
            let wvPsshBox = ISOBoxer.createFullBox('pssh');
            // Widevine PSSH
            wvPsshBox['SystemID'] = [0xed, 0xef, 0x8b, 0xa9, 0x79, 0xd6, 0x4a, 0xce, 0xa3, 0xc8, 0x27, 0xdc, 0xd5, 0x1d, 0x21, 0xed];

            // PSSH in binary form
            wvPsshBox['Data'] = new Uint8Array(atob(psshWidevine).split("").map(function (c) {
                return c.charCodeAt(0);
            }));
            wvPsshBox['DataSize'] = wvPsshBox['Data'].length;

            ISOBoxer.Utils.appendBox(parent, wvPsshBox, 0);

            /******************* Playready ***************************/
            let prPsshBox = ISOBoxer.createFullBox('pssh');
            // Widevine PSSH
            prPsshBox['SystemID'] = [0x9a, 0x04, 0xf0, 0x79, 0x98, 0x40, 0x42, 0x86, 0xab, 0x92, 0xe6, 0x5b, 0xe0, 0x88, 0x5f, 0x95];

            // PSSH in binary form
            prPsshBox['Data'] = new Uint8Array(atob(psshPlayready).split("").map(function (c) {
                return c.charCodeAt(0);
            }));
            prPsshBox['DataSize'] = prPsshBox['Data'].length;

            ISOBoxer.Utils.appendBox(parent, prPsshBox, 0);
        }