betajs/betajs-media

View on GitHub
src/webrtc/media_recorder.js

Summary

Maintainability
A
3 hrs
Test Coverage
Scoped.define("module:WebRTC.MediaRecorder", [
    "base:Class",
    "base:Events.EventsMixin",
    "base:Functions",
    "base:Promise",
    "browser:Info",
    "module:WebRTC.Support",
    "base:Async"
], function(Class, EventsMixin, Functions, Promise, Info, Support, Async, scoped) {
    return Class.extend({
        scoped: scoped
    }, [EventsMixin, function(inherited) {
        return {

            constructor: function(stream, options) {
                options = options || {};
                inherited.constructor.call(this);
                this._stream = stream;
                this._started = false;
                var MediaRecorder = Support.globals().MediaRecorder;
                /*
                 * This is supposed to work according to the docs, but it is not:
                 *
                 * https://developer.mozilla.org/en-US/docs/Web/API/MediaRecorder/MediaRecorder#Example
                 */
                var mediaRecorderOptions = {
                    mimeType: ""
                };
                //mediaRecorderOptions.mimeType = "video/mp4";
                try {
                    if (options.audioonly) {
                        if (MediaRecorder.isTypeSupported('audio/mp3')) {
                            mediaRecorderOptions = {
                                mimeType: 'audio/mp3'
                            };
                        } else if (MediaRecorder.isTypeSupported('audio/ogg;codecs=opus')) {
                            mediaRecorderOptions = {
                                mimeType: 'audio/ogg;codecs=opus'
                            };
                        }
                    } else {
                        if (typeof MediaRecorder.isTypeSupported === "undefined") {
                            mediaRecorderOptions = {
                                mimeType: 'video/webm'
                            };
                        } else if (MediaRecorder.isTypeSupported('video/webm;codecs=vp9') && !options.cpuFriendly) {
                            mediaRecorderOptions = {
                                mimeType: 'video/webm;codecs=vp9'
                            };
                        } else if (MediaRecorder.isTypeSupported('video/webm;codecs=vp8')) {
                            // https://bugzilla.mozilla.org/show_bug.cgi?id=1594466
                            // firefox71 + fixed
                            mediaRecorderOptions = {
                                mimeType: 'video/webm;codecs=vp8' + (Info.isFirefox() && Info.firefoxVersion() >= 71 ? ",opus" : "")
                            };
                        } else if (MediaRecorder.isTypeSupported('video/webm')) {
                            mediaRecorderOptions = {
                                mimeType: 'video/webm'
                            };
                        } else if (MediaRecorder.isTypeSupported('video/webm;codecs=vp9')) {
                            // In case if therefore no option will proceed with cpuFriendly as false
                            mediaRecorderOptions = {
                                mimeType: 'video/webm;codecs=vp9'
                            };
                        } else if (MediaRecorder.isTypeSupported('video/mp4')) {
                            // Safari should support webm format after macOS Big Sur 11.3
                            mediaRecorderOptions = {
                                mimeType: 'video/mp4'
                            };
                        }
                    }
                } catch (e) {
                    mediaRecorderOptions = {};
                }
                if (options.videoBitrate)
                    mediaRecorderOptions.videoBitsPerSecond = options.videoBitrate * 1000;
                if (options.audioBitrate)
                    mediaRecorderOptions.audioBitsPerSecond = options.audioBitrate * 1000;
                this.__audioonly = options.audioonly;
                this.__mediaRecorderOptions = mediaRecorderOptions;
                this._mediaRecorder = new MediaRecorder(stream, mediaRecorderOptions);
                this._mediaRecorder.ondataavailable = Functions.as_method(this._dataAvailable, this);
                this._mediaRecorder.onstop = Functions.as_method(this._dataStop, this);
                this._mediaRecorder.onpause = Functions.as_method(this._hasPaused, this);
                this._mediaRecorder.onresume = Functions.as_method(this._hasResumed, this);
                this._mediaRecorder.onstart = Functions.as_method(this._recorderStarted, this);
                this._mediaRecorder.onerror = Functions.as_method(this.onErrorMethod, this);
            },

            _recorderStarted: function(ev) {
                this._started = true;
                this.trigger("started");
                this._startRecPromise.asyncSuccess();
            },

            onErrorMethod: function(err) {
                this._startRecPromise.asyncError(err);
                this.trigger("error", err);
            },


            destroy: function() {
                this.stop();
                inherited.destroy.call(this);
            },

            start: function() {
                if (this._started)
                    return Promise.value(true);
                this._startRecPromise = Promise.create();
                this._chunks = [];
                // Safari Release 73 implemented non-timeslice mode encoding for MediaRecorder
                // https://developer.apple.com/safari/technology-preview/release-notes/
                this._mediaRecorder.start(10);
                if (Info.isSafari() && !("onstart" in MediaRecorder.prototype)) {
                    this._started = true;
                    this.trigger("started");
                    Async.eventually(function() {
                        if (this._mediaRecorder.state === 'recording') {
                            this._startRecPromise.asyncSuccess();
                        } else {
                            this._startRecPromise.asyncError('Could not start recording');
                        }
                    }, this, 1000);
                }
                return this._startRecPromise;
            },

            pause: function() {
                if (this._paused || !this._started)
                    return;
                this._paused = true;
                this._mediaRecorder.pause();
                this.trigger("pause");
            },

            _hasPaused: function() {
                this.trigger("paused");
            },

            resume: function() {
                if (!this._paused || !this._started)
                    return;
                this._paused = false;
                this._mediaRecorder.resume();
                this.trigger("resume");
            },

            _hasResumed: function() {
                this.trigger("resumed");
            },

            stop: function() {
                if (!this._started)
                    return;
                try {
                    this._mediaRecorder.stop();
                    this._started = false;
                    this.trigger("stopped");
                } catch (err) {
                    // "inactive", raise a DOM InvalidState error and terminate these steps.
                    if ((err.message && err.message.indexOf('inactive') !== -1) && this._mediaRecorder.state !== 'inactive') {
                        this._mediaRecorder.state = 'inactive';
                        this.stop();
                    } else {
                        // console.log("Error when try to stop the recorder. Details: ", e);
                        this.trigger("error", err.message);
                    }
                }
            },

            _dataAvailable: function(e) {
                this.trigger("dataavailable", e);
                if (e.data && e.data.size > 0)
                    this._chunks.push(e.data);
            },

            _dataStop: function(e) {
                this._data = new Blob(this._chunks, {
                    type: (this.__mediaRecorderOptions.mimeType.split(";"))[0] || (this.__audioonly ? "audio/ogg" : "video/webm")
                });
                this._chunks = [];
                if (Info.isFirefox()) {
                    var self = this;
                    var fileReader = new FileReader();
                    fileReader.onload = function() {
                        self._data = new Blob([this.result], {
                            type: self._data.type
                        });
                        self.trigger("data", self._data);
                    };
                    fileReader.readAsArrayBuffer(this._data);
                } else
                    this.trigger("data", this._data);
            }

        };
    }], {

        supported: function() {
            if (!Support.globals().MediaRecorder)
                return false;
            if (document.location.href.indexOf("https://") !== 0 && document.location.hostname !== "localhost") {
                if (Info.isOpera() || Info.isChrome())
                    return false;
            }
            if (Info.isOpera() && Info.operaVersion() < 44)
                return false;
            if (Info.isChrome() && Info.chromeVersion() < 57)
                return false;
            return true;
        }

    });
});