summaryrefslogtreecommitdiffstats
path: root/mobile/android/chrome/content/CastingApps.js
diff options
context:
space:
mode:
Diffstat (limited to 'mobile/android/chrome/content/CastingApps.js')
-rw-r--r--mobile/android/chrome/content/CastingApps.js850
1 files changed, 0 insertions, 850 deletions
diff --git a/mobile/android/chrome/content/CastingApps.js b/mobile/android/chrome/content/CastingApps.js
deleted file mode 100644
index 76773c4d8..000000000
--- a/mobile/android/chrome/content/CastingApps.js
+++ /dev/null
@@ -1,850 +0,0 @@
-// -*- 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);
- },
- types: ["video/mp4"],
- extensions: ["mp4"]
-};
-
-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 fxOSTVDevice = {
- id: "app://fling-player.gaiamobile.org",
- target: "app://fling-player.gaiamobile.org/index.html",
- factory: function(aService) {
- Cu.import("resource://gre/modules/PresentationApp.jsm");
- let request = new window.PresentationRequest(this.target);
- return new PresentationApp(aService, request);
- },
- init: function() {
- Services.obs.addObserver(this, "presentation-device-change", false);
- SimpleServiceDiscovery.addExternalDiscovery(this);
- },
- observe: function(subject, topic, data) {
- let device = subject.QueryInterface(Ci.nsIPresentationDevice);
- let service = this.toService(device);
- switch (data) {
- case "add":
- SimpleServiceDiscovery.addService(service);
- break;
- case "update":
- SimpleServiceDiscovery.updateService(service);
- break;
- case "remove":
- if(SimpleServiceDiscovery.findServiceForID(device.id)) {
- SimpleServiceDiscovery.removeService(device.id);
- }
- break;
- }
- },
- toService: function(device) {
- return {
- location: device.id,
- target: fxOSTVDevice.target,
- friendlyName: device.name,
- uuid: device.id,
- manufacturer: "Firefox OS TV",
- modelName: "Firefox OS TV",
- };
- },
- startDiscovery: function() {
- window.navigator.mozPresentationDeviceInfo.forceDiscovery();
-
- // need to update the lastPing time for known device.
- window.navigator.mozPresentationDeviceInfo.getAll()
- .then(function(devices) {
- for (let device of devices) {
- let service = fxOSTVDevice.toService(device);
- SimpleServiceDiscovery.addService(service);
- }
- });
- },
- stopDiscovery: function() {
- // do nothing
- },
- types: ["video/mp4", "video/webm"],
- extensions: ["mp4", "webm"],
-};
-
-var CastingApps = {
- _castMenuId: -1,
- mirrorStartMenuId: -1,
- mirrorStopMenuId: -1,
- _blocked: null,
- _bound: null,
- _interval: 120 * 1000, // 120 seconds
-
- init: function ca_init() {
- if (!this.isCastingEnabled()) {
- return;
- }
-
- // Register targets
- SimpleServiceDiscovery.registerDevice(rokuDevice);
-
- // MediaPlayerDevice will notify us any time the native device list changes.
- mediaPlayerDevice.init();
- SimpleServiceDiscovery.registerDevice(mediaPlayerDevice);
-
- // Presentation Device will notify us any time the available device list changes.
- if (window.PresentationRequest) {
- fxOSTVDevice.init();
- SimpleServiceDiscovery.registerDevice(fxOSTVDevice);
- }
-
- // Search for devices continuously
- SimpleServiceDiscovery.search(this._interval);
-
- 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);
- Services.obs.addObserver(this, "application-background", false);
- Services.obs.addObserver(this, "application-foreground", 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);
- BrowserApp.deck.addEventListener("MozAutoplayMediaBlocked", this, true);
- // Note that the XBL binding is untrusted
- BrowserApp.deck.addEventListener("MozNoControlsVideoBindingAttached", this, true, 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;
- case "application-background":
- // Turn off polling while in the background
- this._interval = SimpleServiceDiscovery.search(0);
- SimpleServiceDiscovery.stopSearch();
- break;
- case "application-foreground":
- // Turn polling on when app comes back to foreground
- SimpleServiceDiscovery.search(this._interval);
- 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 <video>, but if ended we send nothing to shutdown the pageaction
- this._updatePageActionForVideo(aEvent.type === "playing" ? video : null);
- }
- break;
- }
- case "MozAutoplayMediaBlocked": {
- if (this._bound && this._bound.has(aEvent.target)) {
- aEvent.target.dispatchEvent(new CustomEvent("MozNoControlsBlockedVideo"));
- } else {
- if (!this._blocked) {
- this._blocked = new WeakMap;
- }
- this._blocked.set(aEvent.target, true);
- }
- break;
- }
- case "MozNoControlsVideoBindingAttached": {
- if (!this._bound) {
- this._bound = new WeakMap;
- }
- this._bound.set(aEvent.target, true);
- if (this._blocked && this._blocked.has(aEvent.target)) {
- this._blocked.delete(aEvent.target);
- aEvent.target.dispatchEvent(new CustomEvent("MozNoControlsBlockedVideo"));
- }
- break;
- }
- }
- },
-
- _sendEventToVideo: function _sendEventToVideo(aElement, aData) {
- let event = aElement.ownerDocument.createEvent("CustomEvent");
- event.initCustomEvent("media-videoCasting", false, true, JSON.stringify(aData));
- aElement.dispatchEvent(event);
- },
-
- handleVideoBindingAttached: function handleVideoBindingAttached(aTab, aEvent) {
- // Let's figure out if we have everything needed to cast a video. The binding
- // defaults to |false| so we only need to send an event if |true|.
- let video = aEvent.target;
- if (!(video instanceof HTMLVideoElement)) {
- return;
- }
-
- if (SimpleServiceDiscovery.services.length == 0) {
- return;
- }
-
- this.getVideo(video, 0, 0, (aBundle) => {
- // Let the binding know casting is allowed
- if (aBundle) {
- this._sendEventToVideo(aBundle.element, { allow: true });
- }
- });
- },
-
- handleVideoBindingCast: function handleVideoBindingCast(aTab, aEvent) {
- // The binding wants to start a casting session
- let video = aEvent.target;
- if (!(video instanceof HTMLVideoElement)) {
- return;
- }
-
- // Close an existing session first. closeExternal has checks for an exsting
- // session and handles remote and video binding shutdown.
- this.closeExternal();
-
- // Start the new session
- UITelemetry.addEvent("cast.1", "button", null);
- this.openExternal(video, 0, 0);
- },
-
- makeURI: function makeURI(aURL, aOriginCharset, aBaseURI) {
- return Services.io.newURI(aURL, aOriginCharset, aBaseURI);
- },
-
- allowableExtension: function(aURI, aExtensions) {
- return (aURI instanceof Ci.nsIURL) && aExtensions.indexOf(aURI.fileExtension) != -1;
- },
-
- allowableMimeType: function(aType, aTypes) {
- return aTypes.indexOf(aType) != -1;
- },
-
- // This method will look at the aElement (or try to find a video at aX, aY) that has
- // a castable source. If found, aCallback will be called with a JSON meta bundle. If
- // no castable source was found, aCallback is called with null.
- getVideo: function(aElement, aX, aY, aCallback) {
- let extensions = SimpleServiceDiscovery.getSupportedExtensions();
- let types = SimpleServiceDiscovery.getSupportedMimeTypes();
-
- // Fast path: Is the given element a video element?
- if (aElement instanceof HTMLVideoElement) {
- // If we found a video element, no need to look further, even if no
- // castable video source is found.
- this._getVideo(aElement, types, extensions, aCallback);
- return;
- }
-
- // Maybe this is an overlay, with the video element under it.
- // Use the (x, y) location to guess at a <video> element.
-
- // The context menu system will keep walking up the DOM giving us a chance
- // to find an element we match. When it hits <html> things can go BOOM.
- try {
- let elements = aElement.ownerDocument.querySelectorAll("video");
- for (let element of elements) {
- // Look for a video element contained in the overlay bounds
- let rect = element.getBoundingClientRect();
- if (aY >= rect.top && aX >= rect.left && aY <= rect.bottom && aX <= rect.right) {
- // Once we find a <video> under the overlay, we check it and exit.
- this._getVideo(element, types, extensions, aCallback);
- return;
- }
- }
- } catch(e) {}
- },
-
- _getContentTypeForURI: function(aURI, aElement, aCallback) {
- let channel;
- try {
- let secFlags = Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_INHERITS;
- if (aElement.crossOrigin) {
- secFlags = Ci.nsILoadInfo.SEC_REQUIRE_CORS_DATA_INHERITS;
- if (aElement.crossOrigin === "use-credentials") {
- secFlags |= Ci.nsILoadInfo.SEC_COOKIES_INCLUDE;
- }
- }
- channel = NetUtil.newChannel({
- uri: aURI,
- loadingNode: aElement,
- securityFlags: secFlags,
- contentPolicyType: Ci.nsIContentPolicy.TYPE_INTERNAL_VIDEO
- });
- } catch(e) {
- aCallback(null);
- return;
- }
-
- let listener = {
- onStartRequest: function(request, context) {
- switch (channel.responseStatus) {
- case 301:
- case 302:
- case 303:
- request.cancel(0);
- let location = channel.getResponseHeader("Location");
- CastingApps._getContentTypeForURI(CastingApps.makeURI(location), aElement, aCallback);
- break;
- default:
- aCallback(channel.contentType);
- request.cancel(0);
- break;
- }
- },
- onStopRequest: function(request, context, statusCode) {},
- onDataAvailable: function(request, context, stream, offset, count) {}
- };
-
- if (channel) {
- channel.asyncOpen2(listener);
- } else {
- aCallback(null);
- }
- },
-
- // Because this method uses a callback, make sure we return ASAP if we know
- // we have a castable video source.
- _getVideo: function(aElement, aTypes, aExtensions, aCallback) {
- // Keep a list of URIs we need for an async mimetype check
- let asyncURIs = [];
-
- // Grab the poster attribute from the <video>
- let posterURL = aElement.poster;
-
- // First, look to see if the <video> has a src attribute
- let sourceURL = aElement.src;
-
- // If empty, try the currentSrc
- if (!sourceURL) {
- sourceURL = aElement.currentSrc;
- }
-
- if (sourceURL) {
- // Use the file extension to guess the mime type
- let sourceURI = this.makeURI(sourceURL, null, this.makeURI(aElement.baseURI));
- if (this.allowableExtension(sourceURI, aExtensions)) {
- aCallback({ element: aElement, source: sourceURI.spec, poster: posterURL, sourceURI: sourceURI});
- return;
- }
-
- if (aElement.type) {
- // Fast sync check
- if (this.allowableMimeType(aElement.type, aTypes)) {
- aCallback({ element: aElement, source: sourceURI.spec, poster: posterURL, sourceURI: sourceURI, type: aElement.type });
- return;
- }
- }
-
- // Delay the async check until we sync scan all possible URIs
- asyncURIs.push(sourceURI);
- }
-
- // Next, look to see if there is a <source> child element that meets
- // our needs
- let sourceNodes = aElement.getElementsByTagName("source");
- for (let sourceNode of sourceNodes) {
- let sourceURI = this.makeURI(sourceNode.src, null, this.makeURI(sourceNode.baseURI));
-
- // Using the type attribute is our ideal way to guess the mime type. Otherwise,
- // fallback to using the file extension to guess the mime type
- if (this.allowableExtension(sourceURI, aExtensions)) {
- aCallback({ element: aElement, source: sourceURI.spec, poster: posterURL, sourceURI: sourceURI, type: sourceNode.type });
- return;
- }
-
- if (sourceNode.type) {
- // Fast sync check
- if (this.allowableMimeType(sourceNode.type, aTypes)) {
- aCallback({ element: aElement, source: sourceURI.spec, poster: posterURL, sourceURI: sourceURI, type: sourceNode.type });
- return;
- }
- }
-
- // Delay the async check until we sync scan all possible URIs
- asyncURIs.push(sourceURI);
- }
-
- // Helper method that walks the array of possible URIs, fetching the mimetype as we go.
- // As soon as we find a good sourceURL, avoid firing the callback any further
- var _getContentTypeForURIs = (aURIs) => {
- // Do an async fetch to figure out the mimetype of the source video
- let sourceURI = aURIs.pop();
- this._getContentTypeForURI(sourceURI, aElement, (aType) => {
- if (this.allowableMimeType(aType, aTypes)) {
- // We found a supported mimetype.
- aCallback({ element: aElement, source: sourceURI.spec, poster: posterURL, sourceURI: sourceURI, type: aType });
- } else {
- // This URI was not a supported mimetype, so let's try the next, if we have more.
- if (aURIs.length > 0) {
- _getContentTypeForURIs(aURIs);
- } else {
- // We were not able to find a supported mimetype.
- aCallback(null);
- }
- }
- });
- }
-
- // If we didn't find a good URI directly, let's look using async methods.
- if (asyncURIs.length > 0) {
- _getContentTypeForURIs(asyncURIs);
- }
- },
-
- // This code depends on handleVideoBindingAttached setting mozAllowCasting
- // so we can quickly figure out if the video is castable
- isVideoCastable: function(aElement, aX, aY) {
- // Use the flag set when the <video> binding was created as the check
- if (aElement instanceof HTMLVideoElement) {
- return aElement.mozAllowCasting;
- }
-
- // This is called by the context menu system and the system will keep
- // walking up the DOM giving us a chance to find an element we match.
- // When it hits <html> things can go BOOM.
- try {
- // Maybe this is an overlay, with the video element under it
- // Use the (x, y) location to guess at a <video> element
- let elements = aElement.ownerDocument.querySelectorAll("video");
- for (let element of elements) {
- // Look for a video element contained in the overlay bounds
- let rect = element.getBoundingClientRect();
- if (aY >= rect.top && aX >= rect.left && aY <= rect.bottom && aX <= rect.right) {
- // Use the flag set when the <video> binding was created as the check
- return element.mozAllowCasting;
- }
- }
- } catch(e) {}
-
- return false;
- },
-
- filterCast: {
- matches: function(aElement, aX, aY) {
- // This behavior matches the pageaction: As long as a video is castable,
- // we can cast it, even if it's already being cast to a device.
- if (SimpleServiceDiscovery.services.length == 0)
- return false;
- return CastingApps.isVideoCastable(aElement, aX, aY);
- }
- },
-
- pageAction: {
- click: function() {
- // Since this is a pageaction, we use the selected browser
- let browser = BrowserApp.selectedBrowser;
- if (!browser) {
- return;
- }
-
- // Look for a castable <video> that is playing, and start casting it
- let videos = browser.contentDocument.querySelectorAll("video");
- for (let video of videos) {
- if (!video.paused && video.mozAllowCasting) {
- UITelemetry.addEvent("cast.1", "pageaction", null);
- CastingApps.openExternal(video, 0, 0);
- return;
- }
- }
- }
- },
-
- _findCastableVideo: function _findCastableVideo(aBrowser) {
- if (!aBrowser) {
- return null;
- }
-
- // Scan for a <video> being actively cast. Also look for a castable <video>
- // on the page.
- let castableVideo = null;
- let videos = aBrowser.contentDocument.querySelectorAll("video");
- for (let video of videos) {
- if (video.mozIsCasting) {
- // This <video> is cast-active. Break out of loop.
- return video;
- }
-
- if (!video.paused && video.mozAllowCasting) {
- // This <video> is cast-ready. Keep looking so cast-active could be found.
- castableVideo = video;
- }
- }
-
- // Could be null
- return castableVideo;
- },
-
- _updatePageActionForTab: function _updatePageActionForTab(aTab, aEvent) {
- // We only care about events on the selected tab
- if (aTab != BrowserApp.selectedTab) {
- return;
- }
-
- // Update the page action, scanning for a castable <video>
- this._updatePageAction();
- },
-
- _updatePageActionForVideo: function _updatePageActionForVideo(aVideo) {
- this._updatePageAction(aVideo);
- },
-
- _updatePageAction: function _updatePageAction(aVideo) {
- // Remove any exising pageaction first, in case state changes or we don't have
- // a castable video
- if (this.pageAction.id) {
- PageActions.remove(this.pageAction.id);
- delete this.pageAction.id;
- }
-
- if (!aVideo) {
- aVideo = this._findCastableVideo(BrowserApp.selectedBrowser);
- if (!aVideo) {
- return;
- }
- }
-
- // We only show pageactions if the <video> is from the selected tab
- if (BrowserApp.selectedTab != BrowserApp.getTabForWindow(aVideo.ownerDocument.defaultView.top)) {
- return;
- }
-
- // We check for two state here:
- // 1. The video is actively being cast
- // 2. The video is allowed to be cast and is currently playing
- // Both states have the same action: Show the cast page action
- if (aVideo.mozIsCasting) {
- this.pageAction.id = PageActions.add({
- title: Strings.browser.GetStringFromName("contextmenu.sendToDevice"),
- icon: "drawable://casting_active",
- clickCallback: this.pageAction.click,
- important: true
- });
- } else if (aVideo.mozAllowCasting) {
- this.pageAction.id = PageActions.add({
- title: Strings.browser.GetStringFromName("contextmenu.sendToDevice"),
- icon: "drawable://casting",
- clickCallback: this.pageAction.click,
- important: true
- });
- }
- },
-
- prompt: function(aCallback, aFilterFunc) {
- let items = [];
- let filteredServices = [];
- SimpleServiceDiscovery.services.forEach(function(aService) {
- let item = {
- label: aService.friendlyName,
- selected: false
- };
- if (!aFilterFunc || aFilterFunc(aService)) {
- filteredServices.push(aService);
- items.push(item);
- }
- });
-
- if (items.length == 0) {
- return;
- }
-
- let prompt = new Prompt({
- title: Strings.browser.GetStringFromName("casting.sendToDevice")
- }).setSingleChoiceItems(items).show(function(data) {
- let selected = data.button;
- let service = selected == -1 ? null : filteredServices[selected];
- if (aCallback)
- aCallback(service);
- });
- },
-
- handleContextMenu: function(aElement, aX, aY) {
- UITelemetry.addEvent("action.1", "contextmenu", null, "web_cast");
- UITelemetry.addEvent("cast.1", "contextmenu", null);
- this.openExternal(aElement, aX, aY);
- },
-
- openExternal: function(aElement, aX, aY) {
- // Start a second screen media service
- this.getVideo(aElement, aX, aY, this._openExternal.bind(this));
- },
-
- _openExternal: function(aVideo) {
- if (!aVideo) {
- return;
- }
-
- function filterFunc(aService) {
- return this.allowableExtension(aVideo.sourceURI, aService.extensions) || this.allowableMimeType(aVideo.type, aService.types);
- }
-
- this.prompt(function(aService) {
- if (!aService)
- return;
-
- // Make sure we have a player app for the given service
- let app = SimpleServiceDiscovery.findAppForService(aService);
- if (!app)
- return;
-
- if (aVideo.element) {
- aVideo.title = aVideo.element.ownerDocument.defaultView.top.document.title;
-
- // If the video is currently playing on the device, pause it
- if (!aVideo.element.paused) {
- aVideo.element.pause();
- }
- }
-
- app.stop(function() {
- app.start(function(aStarted) {
- if (!aStarted) {
- dump("CastingApps: Unable to start app");
- return;
- }
-
- app.remoteMedia(function(aRemoteMedia) {
- if (!aRemoteMedia) {
- dump("CastingApps: Failed to create remotemedia");
- return;
- }
-
- this.session = {
- service: aService,
- app: app,
- remoteMedia: aRemoteMedia,
- data: {
- title: aVideo.title,
- source: aVideo.source,
- poster: aVideo.poster
- },
- videoRef: Cu.getWeakReference(aVideo.element)
- };
- }.bind(this), this);
- }.bind(this));
- }.bind(this));
- }.bind(this), filterFunc.bind(this));
- },
-
- closeExternal: function() {
- if (!this.session) {
- return;
- }
-
- this.session.remoteMedia.shutdown();
- this._shutdown();
- },
-
- _shutdown: function() {
- if (!this.session) {
- return;
- }
-
- this.session.app.stop();
- let video = this.session.videoRef.get();
- if (video) {
- this._sendEventToVideo(video, { active: false });
- this._updatePageAction();
- }
-
- delete this.session;
- },
-
- // RemoteMedia callback API methods
- onRemoteMediaStart: function(aRemoteMedia) {
- if (!this.session) {
- return;
- }
-
- aRemoteMedia.load(this.session.data);
- Messaging.sendRequest({ type: "Casting:Started", device: this.session.service.friendlyName });
-
- let video = this.session.videoRef.get();
- if (video) {
- this._sendEventToVideo(video, { active: true });
- this._updatePageAction(video);
- }
- },
-
- onRemoteMediaStop: function(aRemoteMedia) {
- Messaging.sendRequest({ type: "Casting:Stopped" });
- this._shutdown();
- },
-
- onRemoteMediaStatus: function(aRemoteMedia) {
- if (!this.session) {
- return;
- }
-
- let status = aRemoteMedia.status;
- switch (status) {
- case "started":
- Messaging.sendRequest({ type: "Casting:Playing" });
- break;
- case "paused":
- Messaging.sendRequest({ type: "Casting:Paused" });
- break;
- case "completed":
- this.closeExternal();
- break;
- }
- }
-};