Recovering from MEDIA_ERR_DECODE errors in dash.js

One of the key features of a production grade video player is the ability to recover from critical errors. Talking about web-based video players there are a lot of potential errors that can cause the playback to fail. From our experience, one of the most common group of errors are the ones related to media decoding resulting in a MEDIA_ERR_DECODE thrown by the HTML5 video element.

According to the MDN Web documentation and the HTML specification a MEDIA_ERR_DECODE occurs when the attempt to decode the media-data, previously been determined to be usable, results in an error,.

With version 4.1.0 of dash.js we are introducing a recover mechanism for such MEDIA_ERR_DECODE scenarios. By that, we account for potentially broken media-segments that can not be decoded..

Catching decode errors

In order to handle errors we first register for all errors on the HTML5 video element:

videoModel.addEventListener('error', _onPlaybackError)

Once a MEDIA_ERR_DECODE error occurs, we extract the error code and call the decode error handler _handleMediaErrorDecode().

function _onPlaybackError(e) {
        switch (e.error.code) {
            case 3:
                msg = 'MEDIA_ERR_DECODE';
                _handleMediaErrorDecode();
                break;
}

A MEDIA_ERR_DECODE error is thrown right after the invalid media segment has been appended to the respective SourceBuffer. Consequently, the error is dispatched by the video element before we reach the problematic position in the buffer. For that reason, we first save the current playback position. Next we reset some internal objects as well as the MediaSource(MSE). As part of the the MSE reset we attach new SourceBuffer objects as well:

function _handleMediaErrorDecode() {
    logger.warn('A MEDIA_ERR_DECODE occured: Resetting the MediaSource');

    // Save the current playback position
    const time = playbackController.getTime();
    
    // Deactivate the current stream.
    activeStream.deactivate(false);

    // Reset MSE
    _openMediaSource(time, false);
}

Blacklisting the problematic segment

In addition to resetting the MediaSource and the SourceBuffer objects we also need to blacklist the problematic segment. Otherwise the player will load the broken segment again and we end up with the same error. For that reason we register error handlers on the SourceBuffer objects. Once an error occurs during a buffer.append operation we get a notification and can forward the error to the class responsible for blacklisting the broken segments:

buffer.addEventListener('error', _errHandler, false);
function _errHandler(e) {
    const error = e.target || {};
    _triggerEvent(Events.SOURCE_BUFFER_ERROR, { error, lastRequestAppended })
}

Note that we are blacklisting the segment url including the request range. The reason for that is that streams using a SegmentBase addressing scheme rely on the player issuing range requests on a single .mp4 file. If we simply blacklist the segment url the whole .mp4 would be blacklisted.

eventBus.on(Events.SOURCE_BUFFER_ERROR, _onSourceBufferError, instance);

function _onSourceBufferError(e) {
    let blacklistUrl = e.lastRequestAppended.url;

    if (e.lastRequestAppended.range) {
        blacklistUrl = blacklistUrl.concat('_', e.lastRequestAppended.range);
    }
    logger.warn(`Blacklisting segment with url ${blacklistUrl}`);
    segmentBlacklistController.add(blacklistUrl);
}

Conclusion

Recovering from MEDIA_ERR_DECODE errors is one of many exciting new features in dash.js. It is already available in the nightly build of dash.js and will be part of the upcoming release (version 4.1.0).

If you have any question regarding our DASH activities or dash.js in particular, feel free to check out our website and contact us.

Leave a Reply

Your email address will not be published. Required fields are marked *