diff --git a/browser/base/content/browser-context.inc b/browser/base/content/browser-context.inc index e8a58892b3b1..44fb0cf3ef0f 100644 --- a/browser/base/content/browser-context.inc +++ b/browser/base/content/browser-context.inc @@ -240,6 +240,11 @@ label="&emailVideoCmd.label;" accesskey="&emailVideoCmd.accesskey;" oncommand="gContextMenu.sendMedia();"/> + + + 0 && + CastingApps.getServicesForVideo(this.target).length > 0; + this.setItemAttr("context-castvideo", "disabled", !shouldShowCast); }, initViewItems: function CM_initViewItems() { @@ -1316,6 +1326,25 @@ nsContextMenu.prototype = { MailIntegration.sendMessage(this.mediaURL, ""); }, + castVideo: function() { + CastingApps.openExternal(this.target, window); + }, + + populateCastVideoMenu: function(popup) { + let videoEl = this.target; + popup.innerHTML = null; + let doc = popup.ownerDocument; + let services = CastingApps.getServicesForVideo(videoEl); + services.forEach(service => { + let item = doc.createElement("menuitem"); + item.setAttribute("label", service.friendlyName); + item.addEventListener("command", event => { + CastingApps.sendVideoToService(videoEl, service); + }); + popup.appendChild(item); + }); + }, + playPlugin: function() { gPluginHandler.contextMenuCommand(this.browser, this.target, "play"); }, diff --git a/browser/base/content/test/general/test_contextmenu.html b/browser/base/content/test/general/test_contextmenu.html index 20c86b4534a7..856494036637 100644 --- a/browser/base/content/test/general/test_contextmenu.html +++ b/browser/base/content/test/general/test_contextmenu.html @@ -174,7 +174,8 @@ function runTest(testNum) { "---", null, "context-savevideo", true, "context-video-saveimage", true, - "context-sendvideo", true + "context-sendvideo", true, + "context-castvideo", false ].concat(inspectItems)); closeContextMenu(); openContextMenuFor(audio_in_video); // Invoke context menu for next test. diff --git a/browser/components/nsBrowserGlue.js b/browser/components/nsBrowserGlue.js index abd2504a910e..5acb094f716e 100644 --- a/browser/components/nsBrowserGlue.js +++ b/browser/components/nsBrowserGlue.js @@ -99,6 +99,9 @@ XPCOMUtils.defineLazyModuleGetter(this, "AsyncShutdown", XPCOMUtils.defineLazyModuleGetter(this, "LoginManagerParent", "resource://gre/modules/LoginManagerParent.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "SimpleServiceDiscovery", + "resource://gre/modules/SimpleServiceDiscovery.jsm"); + #ifdef NIGHTLY_BUILD XPCOMUtils.defineLazyModuleGetter(this, "SignInToWebsiteUX", "resource:///modules/SignInToWebsite.jsm"); @@ -747,8 +750,30 @@ BrowserGlue.prototype = { FormValidationHandler.uninit(); }, + _initServiceDiscovery: function () { + var rokuDevice = { + id: "roku:ecp", + target: "roku:ecp", + factory: function(aService) { + Cu.import("resource://gre/modules/RokuApp.jsm"); + return new RokuApp(aService); + }, + mirror: false, + types: ["video/mp4"], + extensions: ["mp4"] + }; + + // Register targets + SimpleServiceDiscovery.registerDevice(rokuDevice); + + // Search for devices continuously every 120 seconds + SimpleServiceDiscovery.search(120 * 1000); + }, + // All initial windows have opened. _onWindowsRestored: function BG__onWindowsRestored() { + this._initServiceDiscovery(); + // Show update notification, if needed. if (Services.prefs.prefHasUserValue("app.update.postupdate")) this._showUpdateNotification(); diff --git a/browser/locales/en-US/chrome/browser/browser.dtd b/browser/locales/en-US/chrome/browser/browser.dtd index 29f515d814c0..e84a5512a5c9 100644 --- a/browser/locales/en-US/chrome/browser/browser.dtd +++ b/browser/locales/en-US/chrome/browser/browser.dtd @@ -484,6 +484,8 @@ These should match what Safari and other Apple applications use on OS X Lion. -- + + diff --git a/browser/modules/CastingApps.jsm b/browser/modules/CastingApps.jsm new file mode 100644 index 000000000000..df974f04f606 --- /dev/null +++ b/browser/modules/CastingApps.jsm @@ -0,0 +1,160 @@ +// -*- 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"; +this.EXPORTED_SYMBOLS = ["CastingApps"]; + +const { classes: Cc, interfaces: Ci, utils: Cu } = Components; + +Cu.import("resource://gre/modules/Services.jsm"); +Cu.import("resource://gre/modules/SimpleServiceDiscovery.jsm"); + + +var CastingApps = { + _sendEventToVideo: function (element, data) { + let event = element.ownerDocument.createEvent("CustomEvent"); + event.initCustomEvent("media-videoCasting", false, true, JSON.stringify(data)); + element.dispatchEvent(event); + }, + + makeURI: function (url, charset, baseURI) { + return Services.io.newURI(url, charset, baseURI); + }, + + getVideo: function (element) { + if (!element) { + return null; + } + + let extensions = SimpleServiceDiscovery.getSupportedExtensions(); + let types = SimpleServiceDiscovery.getSupportedMimeTypes(); + + // Grab the poster attribute from the