// -*- Mode: js; tab-width: 2; indent-tabs-mode: nil; js2-basic-offset: 2; js2-skip-preprocessor-directives: t; -*- /* 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"; XPCOMUtils.defineLazyModuleGetter(this, "PageActions", "resource://gre/modules/PageActions.jsm"); // Define service devices. We should consider moving these to their respective // JSM files, but we left them here to allow for better lazy JSM loading. var rokuDevice = { id: "roku:ecp", target: "roku:ecp", factory: function(aService) { Cu.import("resource://gre/modules/RokuApp.jsm"); return new RokuApp(aService); }, mirror: Services.prefs.getBoolPref("browser.mirroring.enabled.roku"), types: ["video/mp4"], extensions: ["mp4"] }; var matchstickDevice = { id: "matchstick:dial", target: "urn:dial-multiscreen-org:service:dial:1", filters: { manufacturer: "openflint" }, factory: function(aService) { Cu.import("resource://gre/modules/MatchstickApp.jsm"); return new MatchstickApp(aService); }, types: ["video/mp4", "video/webm"], extensions: ["mp4", "webm"] }; var mediaPlayerDevice = { id: "media:router", target: "media:router", factory: function(aService) { Cu.import("resource://gre/modules/MediaPlayerApp.jsm"); return new MediaPlayerApp(aService); }, types: ["video/mp4", "video/webm", "application/x-mpegurl"], extensions: ["mp4", "webm", "m3u", "m3u8"], init: function() { Services.obs.addObserver(this, "MediaPlayer:Added", false); Services.obs.addObserver(this, "MediaPlayer:Changed", false); Services.obs.addObserver(this, "MediaPlayer:Removed", false); }, observe: function(subject, topic, data) { if (topic === "MediaPlayer:Added") { let service = this.toService(JSON.parse(data)); SimpleServiceDiscovery.addService(service); } else if (topic === "MediaPlayer:Changed") { let service = this.toService(JSON.parse(data)); SimpleServiceDiscovery.updateService(service); } else if (topic === "MediaPlayer:Removed") { SimpleServiceDiscovery.removeService(data); } }, toService: function(display) { // Convert the native data into something matching what is created in _processService() return { location: display.location, target: "media:router", friendlyName: display.friendlyName, uuid: display.uuid, manufacturer: display.manufacturer, modelName: display.modelName, mirror: display.mirror }; } }; var CastingApps = { _castMenuId: -1, mirrorStartMenuId: -1, mirrorStopMenuId: -1, init: function ca_init() { if (!this.isCastingEnabled()) { return; } // Register targets SimpleServiceDiscovery.registerDevice(rokuDevice); SimpleServiceDiscovery.registerDevice(matchstickDevice); // MediaPlayerDevice will notify us any time the native device list changes. mediaPlayerDevice.init(); SimpleServiceDiscovery.registerDevice(mediaPlayerDevice); // Search for devices continuously every 120 seconds SimpleServiceDiscovery.search(120 * 1000); this._castMenuId = NativeWindow.contextmenus.add( Strings.browser.GetStringFromName("contextmenu.sendToDevice"), this.filterCast, this.handleContextMenu.bind(this) ); Services.obs.addObserver(this, "Casting:Play", false); Services.obs.addObserver(this, "Casting:Pause", false); Services.obs.addObserver(this, "Casting:Stop", false); Services.obs.addObserver(this, "Casting:Mirror", false); Services.obs.addObserver(this, "ssdp-service-found", false); Services.obs.addObserver(this, "ssdp-service-lost", false); BrowserApp.deck.addEventListener("TabSelect", this, true); BrowserApp.deck.addEventListener("pageshow", this, true); BrowserApp.deck.addEventListener("playing", this, true); BrowserApp.deck.addEventListener("ended", this, true); }, _mirrorStarted: function(stopMirrorCallback) { this.stopMirrorCallback = stopMirrorCallback; NativeWindow.menu.update(this.mirrorStartMenuId, { visible: false }); NativeWindow.menu.update(this.mirrorStopMenuId, { visible: true }); }, serviceAdded: function(aService) { if (this.isMirroringEnabled() && aService.mirror && this.mirrorStartMenuId == -1) { this.mirrorStartMenuId = NativeWindow.menu.add({ name: Strings.browser.GetStringFromName("casting.mirrorTab"), callback: function() { let callbackFunc = function(aService) { let app = SimpleServiceDiscovery.findAppForService(aService); if (app) { app.mirror(function() {}, window, BrowserApp.selectedTab.getViewport(), this._mirrorStarted.bind(this), window.BrowserApp.selectedBrowser.contentWindow); } }.bind(this); this.prompt(callbackFunc, aService => aService.mirror); }.bind(this), parent: NativeWindow.menu.toolsMenuID }); this.mirrorStopMenuId = NativeWindow.menu.add({ name: Strings.browser.GetStringFromName("casting.mirrorTabStop"), callback: function() { if (this.tabMirror) { this.tabMirror.stop(); this.tabMirror = null; } else if (this.stopMirrorCallback) { this.stopMirrorCallback(); this.stopMirrorCallback = null; } NativeWindow.menu.update(this.mirrorStartMenuId, { visible: true }); NativeWindow.menu.update(this.mirrorStopMenuId, { visible: false }); }.bind(this), }); } if (this.mirrorStartMenuId != -1) { NativeWindow.menu.update(this.mirrorStopMenuId, { visible: false }); } }, serviceLost: function(aService) { if (aService.mirror && this.mirrorStartMenuId != -1) { let haveMirror = false; SimpleServiceDiscovery.services.forEach(function(service) { if (service.mirror) { haveMirror = true; } }); if (!haveMirror) { NativeWindow.menu.remove(this.mirrorStartMenuId); this.mirrorStartMenuId = -1; } } }, isCastingEnabled: function isCastingEnabled() { return Services.prefs.getBoolPref("browser.casting.enabled"); }, isMirroringEnabled: function isMirroringEnabled() { return Services.prefs.getBoolPref("browser.mirroring.enabled"); }, observe: function (aSubject, aTopic, aData) { switch (aTopic) { case "Casting:Play": if (this.session && this.session.remoteMedia.status == "paused") { this.session.remoteMedia.play(); } break; case "Casting:Pause": if (this.session && this.session.remoteMedia.status == "started") { this.session.remoteMedia.pause(); } break; case "Casting:Stop": if (this.session) { this.closeExternal(); } break; case "Casting:Mirror": { Cu.import("resource://gre/modules/TabMirror.jsm"); this.tabMirror = new TabMirror(aData, window); NativeWindow.menu.update(this.mirrorStartMenuId, { visible: false }); NativeWindow.menu.update(this.mirrorStopMenuId, { visible: true }); } break; case "ssdp-service-found": { this.serviceAdded(SimpleServiceDiscovery.findServiceForID(aData)); break; } case "ssdp-service-lost": { this.serviceLost(SimpleServiceDiscovery.findServiceForID(aData)); break; } } }, handleEvent: function(aEvent) { switch (aEvent.type) { case "TabSelect": { let tab = BrowserApp.getTabForBrowser(aEvent.target); this._updatePageActionForTab(tab, aEvent); break; } case "pageshow": { let tab = BrowserApp.getTabForWindow(aEvent.originalTarget.defaultView); this._updatePageActionForTab(tab, aEvent); break; } case "playing": case "ended": { let video = aEvent.target; if (video instanceof HTMLVideoElement) { // If playing, send the