/* -*- Mode: js; js-indent-level: 2; indent-tabs-mode: nil; tab-width: 2 -*- */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this file, * You can obtain one at http://mozilla.org/MPL/2.0/. */ "use strict"; const { classes: Cc, interfaces: Ci, utils: Cu } = Components; Cu.import("resource://gre/modules/Services.jsm"); Cu.import("resource://gre/modules/Messaging.jsm"); const CONFIG = { iceServers: [{ "urls": ["stun:stun.services.mozilla.com"] }] }; var log = Cu.import("resource://gre/modules/AndroidLog.jsm", {}).AndroidLog.d.bind(null, "TabMirror"); var failure = function(x) { log("ERROR: " + JSON.stringify(x)); }; var TabMirror = function(deviceId, window) { this.deviceId = deviceId; // Save RTCSessionDescription and RTCIceCandidate for later when the window object is not available. this.RTCSessionDescription = window.RTCSessionDescription; this.RTCIceCandidate = window.RTCIceCandidate; Services.obs.addObserver((aSubject, aTopic, aData) => this._processMessage(aData), "MediaPlayer:Response", false); this._sendMessage({ start: true }); this._window = window; this._pc = new window.RTCPeerConnection(CONFIG, {}); if (!this._pc) { throw "Failure creating Webrtc object"; } }; TabMirror.prototype = { _window: null, _screenSize: { width: 1280, height: 720 }, _pc: null, _start: function() { this._pc.onicecandidate = this._onIceCandidate.bind(this); let windowId = this._window.BrowserApp.selectedBrowser.contentWindow.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils).outerWindowID; let constraints = { video: { mediaSource: "browser", browserWindow: windowId, scrollWithPage: true, advanced: [ { width: { min: 0, max: this._screenSize.width }, height: { min: 0, max: this._screenSize.height } }, { aspectRatio: this._screenSize.width / this._screenSize.height } ] } }; this._window.navigator.mozGetUserMedia(constraints, this._onGumSuccess.bind(this), this._onGumFailure.bind(this)); }, _processMessage: function(data) { if (!data) { return; } let msg = JSON.parse(data); if (!msg) { return; } if (msg.sdp && msg.type === "answer") { this._processAnswer(msg); } else if (msg.type == "size") { if (msg.height) { this._screenSize.height = msg.height; } if (msg.width) { this._screenSize.width = msg.width; } this._start(); } else if (msg.candidate) { this._processIceCandidate(msg); } else { log("dropping unrecognized message: " + JSON.stringify(msg)); } }, // Signaling methods _processAnswer: function(msg) { this._pc.setRemoteDescription(new this.RTCSessionDescription(msg), this._setRemoteAnswerSuccess.bind(this), failure); }, _processIceCandidate: function(msg) { // WebRTC generates a warning if the success and fail callbacks are not passed in. this._pc.addIceCandidate(new this.RTCIceCandidate(msg), () => log("Ice Candiated added successfuly"), () => log("Failed to add Ice Candidate")); }, _setRemoteAnswerSuccess: function() { }, _setLocalSuccessOffer: function(sdp) { this._sendMessage(sdp); }, _createOfferSuccess: function(sdp) { this._pc.setLocalDescription(sdp, () => this._setLocalSuccessOffer(sdp), failure); }, _onIceCandidate: function (msg) { log("NEW Ice Candidate: " + JSON.stringify(msg.candidate)); this._sendMessage(msg.candidate); }, _ready: function() { this._pc.createOffer(this._createOfferSuccess.bind(this), failure); }, _onGumSuccess: function(stream){ this._pc.addStream(stream); this._ready(); }, _onGumFailure: function() { log("Could not get video stream"); this._pc.close(); }, _sendMessage: function(msg) { if (this.deviceId) { let obj = { type: "MediaPlayer:Message", id: this.deviceId, data: JSON.stringify(msg) }; Messaging.sendRequest(obj); } }, stop: function() { if (this.deviceId) { let obj = { type: "MediaPlayer:End", id: this.deviceId }; Services.androidBridge.handleGeckoMessage(obj); } }, }; this.EXPORTED_SYMBOLS = ["TabMirror"];