betajs/betajs-media

View on GitHub
src/webrtc/peer_recorder.js

Summary

Maintainability
D
2 days
Test Coverage
Scoped.define("module:WebRTC.PeerRecorder", [
    "base:Class",
    "base:Events.EventsMixin",
    "base:Functions",
    "base:Objs",
    "base:Promise",
    "base:Async",
    "browser:Info",
    "module:WebRTC.Support"
], function(Class, EventsMixin, Functions, Objs, Promise, Async, Info, Support, scoped) {
    return Class.extend({
        scoped: scoped
    }, [EventsMixin, function(inherited) {
        return {

            constructor: function(stream, options) {
                inherited.constructor.call(this);
                this._stream = stream;
                if (!options.videoBitrate && options.recorderWidth && options.recorderHeight)
                    options.videoBitrate = Math.round(options.recorderWidth * options.recorderHeight / 250);
                this._videoBitrate = options.videoBitrate || 1024;
                this._audioBitrate = options.audioBitrate || 256;
                this._videoFrameRate = options.framerate; // || "29.97";
                this._audioonly = options.audioonly;
                this._started = false;
            },

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

            start: function(options) {
                if (this._started)
                    return Promise.value(true);
                this._wssUrl = options.wssUrl;
                this._streamInfo = options.streamInfo;
                this._userData = options.userData || {};
                this._delay = options.delay || 0;
                this._started = true;
                this._wsConnection = new(Support.globals()).WebSocket(this._wssUrl);
                this._wsConnection.binaryType = 'arraybuffer';
                this._wsConnection.onopen = Functions.as_method(this._wsOnOpen, this);
                this._wsConnection.onmessage = Functions.as_method(this._wsOnMessage, this);
                this._wsConnection.onclose = Functions.as_method(this._wsOnClose, this);
                this._wsConnection.onerror = this._errorCallback("WS_CONNECTION");
                var promise = Promise.create();
                var ctx = {};
                var self = this;
                this.on("started", function() {
                    self.off(null, null, ctx);
                    promise.asyncSuccess(true);
                }, ctx).on("error", function(error) {
                    self.off(null, null, ctx);
                    promise.asyncError(error);
                }, ctx);
                return promise;
            },

            stop: function() {
                if (!this._started)
                    return;
                this._started = false;
                if (this._peerConnection)
                    this._peerConnection.close();
                this._peerConnection = null;
                if (this._wsConnection)
                    this._wsConnection.close();
                this._wsConnection = null;
                this.trigger("stopped");
            },

            _wsOnOpen: function() {
                this._peerConnection = new(Support.globals()).RTCPeerConnection({
                    'iceServers': []
                });
                if (this._stream.getTracks && this._peerConnection.addTrack) {
                    Objs.iter(this._stream.getTracks(), function(localTrack) {
                        this._peerConnection.addTrack(localTrack, this._stream);
                    }, this);
                } else {
                    this._peerConnection.addStream(this._stream);
                }
                var offer = this._peerConnection.createOffer();
                offer.then(Functions.as_method(this._offerGotDescription, this));
                offer['catch'](this._errorCallback("PEER_CREATE_OFFER"));
            },

            _wsOnMessage: function(evt) {
                var data = JSON.parse(evt.data);
                var status = parseInt(data.status, 10);
                var command = data.command;
                if (status !== 200) {
                    this._error("MESSAGE_ERROR", {
                        status: status,
                        description: data.statusDescription
                    });
                } else {
                    if (data.sdp !== undefined) {
                        var remoteDescription = this._peerConnection.setRemoteDescription(new(Support.globals()).RTCSessionDescription(data.sdp));
                        remoteDescription.then(function() {
                            // peerConnection.createAnswer(gotDescription, errorHandler);
                        });
                        remoteDescription['catch'](this._errorCallback("PEER_REMOTE_DESCRIPTION"));
                    }
                    if (data.iceCandidates) {
                        Objs.iter(data.iceCandidates, function(iceCandidate) {
                            this._peerConnection.addIceCandidate(new(Support.globals()).RTCIceCandidate(iceCandidate));
                        }, this);
                    }
                    Async.eventually(function() {
                        this.trigger("started");
                    }, this, this._delay);
                }
                if (this._wsConnection)
                    this._wsConnection.close();
                this._wsConnection = null;
            },

            _offerGotDescription: function(description) {
                var enhanceData = {};
                if (this._audioBitrate)
                    enhanceData.audioBitrate = Number(this._audioBitrate);
                if (this._videoBitrate && !this._audioonly)
                    enhanceData.videoBitrate = Number(this._videoBitrate);
                if (this._videoFrameRate && !this._audioonly)
                    enhanceData.videoFrameRate = Number(this._videoFrameRate);
                description.sdp = this._enhanceSDP(description.sdp, enhanceData);
                return this._peerConnection.setLocalDescription(description).then(Functions.as_method(function() {
                    this._wsConnection.send(JSON.stringify({
                        direction: "publish",
                        command: "sendOffer",
                        streamInfo: this._streamInfo,
                        sdp: description,
                        userData: this._userData
                    }));
                }, this))['catch'](this._errorCallback("Peer Local Description"));
            },

            _enhanceSDP: function(sdpStr, enhanceData) {
                var sdpLines = sdpStr.split(/\r\n/);
                var sdpSection = 'header';
                var hitMID = false;
                var sdpStrRet = '';
                Objs.iter(sdpLines, function(sdpLine) {
                    if (sdpLine.length <= 0)
                        return;
                    sdpStrRet += sdpLine + '\r\n';
                    if (sdpLine.indexOf("m=audio") === 0) {
                        sdpSection = 'audio';
                        hitMID = false;
                    } else if (sdpLine.indexOf("m=video") === 0) {
                        sdpSection = 'video';
                        hitMID = false;
                    } else if (sdpLine.indexOf("a=rtpmap") === 0) {
                        sdpSection = 'bandwidth';
                        hitMID = false;
                    }
                    // Skip i and c lines
                    if (sdpLine.indexOf("i=") === 0 || sdpLine.indexOf("c=") === 0)
                        return;
                    if (hitMID)
                        return;
                    if ('audio'.localeCompare(sdpSection) === 0) {
                        if (enhanceData.audioBitrate !== undefined) {
                            sdpStrRet += 'b=AS:' + enhanceData.audioBitrate + '\r\n';
                            sdpStrRet += 'b=TIAS:' + (enhanceData.audioBitrate * 1024) + '\r\n';
                            // sdpStrRet += Info.isChrome()
                            //     ? 'b=CT:' + enhanceData.videoBitrate + '\r\n'
                            //     : 'b=TIAS:' + (enhanceData.audioBitrate * 1024) + '\r\n';
                        }
                    } else if ('video'.localeCompare(sdpSection) === 0) {
                        if (enhanceData.videoBitrate !== undefined) {
                            sdpStrRet += 'b=AS:' + enhanceData.videoBitrate + '\r\n';
                            // if (Info.isChrome()) {
                            // The Conference Total is indicated by giving the modifier
                            // can co-exist with any other sessions, defined in RFC 2327
                            sdpStrRet += 'b=CT:' + enhanceData.videoBitrate + '\r\n';
                            if (enhanceData.videoFrameRate !== undefined) {
                                sdpStrRet += 'a=framerate:' + enhanceData.videoFrameRate + '\r\n';
                            }
                            // } else {
                            //     // Transport Independent Application Specific Maximum (TIAS)
                            //     // Therefore, it gives a good indication of the maximum codec bit-
                            //     // rate required to be supported by the decoder.
                            //     sdpStrRet += 'b=TIAS:' + (enhanceData.videoBitrate * 1024) + '\r\n';
                            //     if (enhanceData.videoFrameRate !== undefined) {
                            //         sdpStrRet += 'a=maxprate:' + enhanceData.videoFrameRate + '\r\n';
                            //     }
                            // }
                        }
                    } else if ('bandwidth'.localeCompare(sdpSection) === 0 && Info.isChrome()) {
                        var rtpmapID;
                        rtpmapID = this._getRTPMapID(sdpLine);
                        if (rtpmapID !== null) {
                            var match = rtpmapID[2].toLowerCase();
                            if (('vp9'.localeCompare(match) === 0) || ('vp8'.localeCompare(match) === 0) || ('h264'.localeCompare(match) === 0) ||
                                ('red'.localeCompare(match) === 0) || ('ulpfec'.localeCompare(match) === 0) || ('rtx'.localeCompare(match) === 0)) {
                                if (enhanceData.videoBitrate !== undefined) {
                                    sdpStrRet += 'a=fmtp:' + rtpmapID[1] + ' x-google-min-bitrate=' + (enhanceData.videoBitrate) + ';x-google-max-bitrate=' + (enhanceData.videoBitrate) + '\r\n';
                                }
                            }

                            if (('opus'.localeCompare(match) === 0) || ('isac'.localeCompare(match) === 0) || ('g722'.localeCompare(match) === 0) || ('pcmu'.localeCompare(match) === 0) ||
                                ('pcma'.localeCompare(match) === 0) || ('cn'.localeCompare(match) === 0)) {
                                if (enhanceData.audioBitrate !== undefined) {
                                    sdpStrRet += 'a=fmtp:' + rtpmapID[1] + ' x-google-min-bitrate=' + (enhanceData.audioBitrate) + ';x-google-max-bitrate=' + (enhanceData.audioBitrate) + '\r\n';
                                }
                            }
                        }
                    }
                    hitMID = true;
                }, this);
                return sdpStrRet;
            },

            _getRTPMapID: function(line) {
                var findid = new RegExp('a=rtpmap:(\\d+) (\\w+)/(\\d+)');
                var found = line.match(findid);
                return (found && found.length >= 3) ? found : null;
            },

            _wsOnClose: function() {},

            _error: function(errorName, errorData) {
                this.trigger("error", errorName + " " + errorData.toString(), errorData);
                this.stop();
            },

            _errorCallback: function(errorName) {
                return Functions.as_method(function(errorData) {
                    this._error(errorName, errorData);
                }, this);
            }

        };
    }], {

        supported: function() {
            if (Info.isEdge())
                return false;
            if (Info.isSafari() && Info.safariVersion() < 11)
                return false;
            if (document.location.href.indexOf("https://") !== 0 && document.location.hostname !== "localhost") {
                if (Info.isChrome() && Info.chromeVersion() >= 47)
                    return false;
                if (Info.isOpera() && Info.operaVersion() >= 34)
                    return false;
            }
            return (Support.globals()).RTCPeerConnection &&
                (Support.globals()).RTCIceCandidate &&
                (Support.globals()).RTCSessionDescription &&
                (Support.globals()).WebSocket;
        }

    });
});