Commit a20ea8db authored by yoshiki@chromium.org's avatar yoshiki@chromium.org

Video Player: Add a cast menu

This patch adds the menu and the list of casts. The menu just shows the menu, casting feature is not implemented.

All changes are effective only when the command line flag is specified. And the resource (cast icons) may change later.

Major changes:
- Detect Cast extension and load it
- Discover casts and show them on the menu

BUG=305511
TEST=manually testted

Review URL: https://codereview.chromium.org/381073003

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@282609 0039d316-1c4b-4281-b951-d872f2087c98
parent 7abe3e80
/* Copyright 2014 The Chromium Authors. All rights reserved.
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file. */
.cast-menu {
border: 1px solid #7f7f7f;
border-radius: 1px;
min-width: 120px;
padding: 0;
position: absolute;
z-index: 1000;
}
.cast-menu.hidden {
display: none;
}
.cast-menu > :not(hr) {
background-color: #fff;
font-size: 12px;
padding: 5px 10px; /* top&bottom, left&right */
text-overflow: ellipsis;
}
.cast-menu > :not(hr):hover {
background-color: #eee;
}
...@@ -69,6 +69,16 @@ ...@@ -69,6 +69,16 @@
width: 32px; width: 32px;
} }
#video-player > .header > button.cast-button {
background-image: -webkit-image-set(
url(../images/100/cast_off.png) 1x,
url(../images/200/cast_off.png) 2x);
}
#video-player > .header > button.cast-button.hidden {
display: none;
}
#video-player > .header > button.minimize-button { #video-player > .header > button.minimize-button {
background-image: -webkit-image-set( background-image: -webkit-image-set(
url(chrome://resources/images/apps/topbar_button_minimize.png) 1x, url(chrome://resources/images/apps/topbar_button_minimize.png) 1x,
......
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
'use strict';
/**
* Discover the ID of installed cast extesnion.
* @constructor
*/
function CastExtensionDiscoverer() {
}
/**
* Tentatice IDs to try.
* @type {Array.<string>}
* @const
*/
CastExtensionDiscoverer.CAST_EXTENSION_IDS = [
'boadgeojelhgndaghljhdicfkmllpafd', // release
'dliochdbjfkdbacpmhlcpmleaejidimm', // beta
'hfaagokkkhdbgiakmmlclaapfelnkoah',
'fmfcbgogabcbclcofgocippekhfcmgfj',
'enhhojjnijigcajfphajepfemndkmdlo'
];
/**
* @param {function(string)} callback Callback called with the extension ID. The
* ID may be null if extension is not found.
*/
CastExtensionDiscoverer.findInstalledExtension = function(callback) {
CastExtensionDiscoverer.findInstalledExtensionHelper_(0, callback);
};
/**
* @param {number} index Current index which is tried to check.
* @param {function(string)} callback Callback function which will be called
* the extension is found.
*/
CastExtensionDiscoverer.findInstalledExtensionHelper_ = function(index,
callback) {
if (index === CastExtensionDiscoverer.CAST_EXTENSION_IDS.length) {
// no extension found.
callback(null);
return;
}
CastExtensionDiscoverer.isExtensionInstalled_(
CastExtensionDiscoverer.CAST_EXTENSION_IDS[index],
function(installed) {
if (installed) {
callback(CastExtensionDiscoverer.CAST_EXTENSION_IDS[index]);
} else {
CastExtensionDiscoverer.findInstalledExtensionHelper_(index + 1,
callback);
}
});
};
/**
* The result will be notified on |callback|. True if installed, false not.
* @param {string} extensionId Id to be checked.
* @param {function(boolean)} callback Callback to notify the result.
*/
CastExtensionDiscoverer.isExtensionInstalled_ =
function(extensionId, callback) {
var responseCallback =
/** @param {*} response */
function(response) {
if (chrome.runtime.lastError || response === false) {
// An error occurred while sending the message.
callback(false);
} else {
// Cast extension found.
callback(true);
}
}.wrap(this);
chrome.runtime.sendMessage(extensionId, {}, responseCallback);
};
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
'use strict';
/**
* The instance of cast api.
* @type {cast.ExtensionApi}
*/
var castApi = null;
/**
* @type {string}
* @const
*/
var CAST_COMMAND_LINE_FLAG = 'enable-video-player-chromecast-support';
chrome.commandLinePrivate.hasSwitch(CAST_COMMAND_LINE_FLAG, function(result) {
if (!result)
return;
CastExtensionDiscoverer.findInstalledExtension(onCastExtensionFound);
});
function onCastExtensionFound(extensionId) {
if (!extensionId) {
console.info('Cast extention is not found.');
return;
}
var api = document.createElement('script');
api.src = 'chrome-extension://' + extensionId + '/api_script.js';
api.onload = function() {
initializeCast(extensionId);
};
api.onerror = function() {
console.error('api_script.js load failed.');
};
document.body.appendChild(api);
};
function initializeCast(extensionId) {
loadCastExtensionApi();
castApi = new cast.ExtensionApi(extensionId);
castApi.addReceiverListener('ChromeCast', onReceiverUpdate);
}
function onReceiverUpdate(receivers) {
player.setCastList(receivers);
}
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/**
* Defines the cast.ExtensionApi class. This class inherits cast.Api and must
* be decraled after the parent is loaded. So we introduce this method to
* decrale it with delay.
*/
function loadCastExtensionApi() {
if (!cast) {
console.error('"cast" namespace is not defined.');
return;
}
/**
* @constructor
* @param {string} castExtensionId Extension ID of cast extension.
* @extends {cast.Api}
*/
cast.ExtensionApi = function(castExtensionId) {
this.castExtensionId_ = castExtensionId;
cast.Client.clientId_ = chrome.runtime.id;
cast.Api.call(this);
};
cast.ExtensionApi.prototype.__proto__ = cast.Api.prototype;
/**
* @override
*/
cast.ExtensionApi.prototype.init = function() {
chrome.runtime.onMessageExternal.addListener(
this.onMessageExternal_.bind(this));
this.sendRequest(cast.AppRequestType.REGISTER_CLIENT, {});
cast.isAvailable = true;
};
/**
* @override
*/
cast.ExtensionApi.prototype.onDisconnect = function() {};
/**
* @override
*/
cast.ExtensionApi.prototype.sendRequest = function(requestType, request) {
if (!this.castExtensionId_)
return null;
var appRequest = new cast.Message(cast.Client.getClientId(),
cast.NAME, cast.Client.getNextSeq(), requestType, request);
// Use response callback for message ACK to detect extension unload.
var responseCallback = function() {
if (chrome.runtime.lastError) {
// Unregister the cast extension.
this.castExtensionId_ = '';
cast.isAvailable = false;
this.onDisconnect();
}
}.bind(this);
chrome.runtime.sendMessage(this.castExtensionId_,
appRequest,
responseCallback);
return appRequest;
};
/**
* @param {string} message
* @param {function(boolean)} sender
* @param {function(boolean)} sendResponse
* @private
*/
cast.ExtensionApi.prototype.onMessageExternal_ = function(message,
sender, sendResponse) {
if (!this.castExtensionId_ || sender.id != this.castExtensionId_)
return;
// No content.
if (!message)
return;
this.processCastMessage(/** @type {cast.Message} */(message));
};
}
...@@ -165,6 +165,18 @@ VideoPlayer.prototype.prepare = function(videos) { ...@@ -165,6 +165,18 @@ VideoPlayer.prototype.prepare = function(videos) {
}.wrap(null)); }.wrap(null));
closeButton.addEventListener('mousedown', preventDefault); closeButton.addEventListener('mousedown', preventDefault);
var castButton = document.querySelector('.cast-button');
cr.ui.decorate(castButton, cr.ui.MenuButton);
castButton.addEventListener(
'click',
function(event) {
event.stopPropagation();
}.wrap(null));
castButton.addEventListener('mousedown', preventDefault);
var menu = document.querySelector('#cast-menu');
cr.ui.decorate(menu, cr.ui.Menu);
this.controls_ = new FullWindowVideoControls( this.controls_ = new FullWindowVideoControls(
document.querySelector('#video-player'), document.querySelector('#video-player'),
document.querySelector('#video-container'), document.querySelector('#video-container'),
...@@ -345,6 +357,28 @@ VideoPlayer.prototype.reloadCurrentVideo_ = function(opt_callback) { ...@@ -345,6 +357,28 @@ VideoPlayer.prototype.reloadCurrentVideo_ = function(opt_callback) {
this.loadVideo_(currentVideo.fileUrl, currentVideo.entry.name, opt_callback); this.loadVideo_(currentVideo.fileUrl, currentVideo.entry.name, opt_callback);
}; };
/**
* Set the list of casts.
* @param {Array.<Object>} casts List of casts.
*/
VideoPlayer.prototype.setCastList = function(casts) {
var button = document.querySelector('.cast-button');
var menu = document.querySelector('#cast-menu');
menu.innerHTML = '';
if (casts.length === 0) {
button.classList.add('hidden');
return;
}
for (var i = 0; i < casts.length; i++) {
var item = new cr.ui.MenuItem();
item.textContent = casts[i].name;
menu.appendChild(item);
}
button.classList.remove('hidden');
};
/** /**
* Initialize the list of videos. * Initialize the list of videos.
* @param {function(Array.<Object>)} callback Called with the video list when * @param {function(Array.<Object>)} callback Called with the video list when
......
...@@ -13,6 +13,17 @@ ...@@ -13,6 +13,17 @@
//<include src="../../../webui/resources/js/cr.js"/> //<include src="../../../webui/resources/js/cr.js"/>
//<include src="../../../webui/resources/js/load_time_data.js"/> //<include src="../../../webui/resources/js/load_time_data.js"/>
//<include src="../../../webui/resources/js/event_tracker.js"/>
//<include src="../../../webui/resources/js/cr/ui.js"/>
//<include src="../../../webui/resources/js/cr/event_target.js"/>
//<include src="../../../webui/resources/js/cr/ui/position_util.js"/>
//<include src="../../../webui/resources/js/cr/ui/menu_item.js"/>
//<include src="../../../webui/resources/js/cr/ui/menu.js"/>
//<include src="../../../webui/resources/js/cr/ui/menu_button.js"/>
//<include src="../../../webui/resources/js/cr/ui/context_menu_handler.js"/>
(function() { (function() {
'use strict'; 'use strict';
...@@ -20,6 +31,10 @@ ...@@ -20,6 +31,10 @@
//<include src="../../file_manager/foreground/js/media/media_controls.js"/> //<include src="../../file_manager/foreground/js/media/media_controls.js"/>
//<include src="../../file_manager/foreground/js/media/mouse_inactivity_watcher.js"/> //<include src="../../file_manager/foreground/js/media/mouse_inactivity_watcher.js"/>
//<include src="cast/cast_extension_discoverer.js"/>
//<include src="cast/load_cast_extension_api.js"/>
//<include src="cast/caster.js"/>
//<include src="video_player.js"/> //<include src="video_player.js"/>
window.unload = unload; window.unload = unload;
......
...@@ -12,7 +12,9 @@ ...@@ -12,7 +12,9 @@
"32": "images/200/icon.png" "32": "images/200/icon.png"
}, },
"permissions": [ "permissions": [
"commandLinePrivate",
"fileSystem", "fileSystem",
"fileBrowserHandler",
"fileBrowserPrivate", "fileBrowserPrivate",
"fullscreen", "fullscreen",
"mediaPlayerPrivate", "mediaPlayerPrivate",
...@@ -48,9 +50,24 @@ ...@@ -48,9 +50,24 @@
"scripts": [ "scripts": [
"js/error_util.js", "js/error_util.js",
"js/test_util.js", "js/test_util.js",
"js/background.js" "js/background.js",
"chrome://resources/js/cr.js",
"chrome://resources/js/cr/event_target.js",
"chrome://resources/js/cr/ui/array_data_model.js",
"chrome://resources/js/load_time_data.js",
"chrome-extension://hhaomjibdihmijegdhdafkllkbggdgoj/common/js/util.js",
"chrome-extension://hhaomjibdihmijegdhdafkllkbggdgoj/common/js/async_util.js",
"chrome-extension://hhaomjibdihmijegdhdafkllkbggdgoj/common/js/volume_manager_common.js",
"chrome-extension://hhaomjibdihmijegdhdafkllkbggdgoj/background/js/volume_manager.js",
"chrome-extension://hhaomjibdihmijegdhdafkllkbggdgoj/foreground/js/file_type.js"
] ]
}, },
"content_security_policy": "default-src 'none'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' chrome://resources chrome://theme data:; media-src 'self'; object-src 'self'" // The following ids are cast extension's ids .
// - boadgeojelhgndaghljhdicfkmllpafd (release)
// - dliochdbjfkdbacpmhlcpmleaejidimm (beta)
// - hfaagokkkhdbgiakmmlclaapfelnkoah
// - fmfcbgogabcbclcofgocippekhfcmgfj
// - enhhojjnijigcajfphajepfemndkmdlo
"content_security_policy": "default-src 'none'; script-src 'self' chrome://resources chrome-extension://boadgeojelhgndaghljhdicfkmllpafd chrome-extension://dliochdbjfkdbacpmhlcpmleaejidimm chrome-extension://hhaomjibdihmijegdhdafkllkbggdgoj chrome-extension://fmfcbgogabcbclcofgocippekhfcmgfj chrome-extension://enhhojjnijigcajfphajepfemndkmdlo; style-src 'self' chrome://resources 'unsafe-inline'; img-src 'self' chrome://theme chrome://resources data:; media-src 'self'; object-src 'self'"
} }
} }
...@@ -3,24 +3,31 @@ ...@@ -3,24 +3,31 @@
-- Use of this source code is governed by a BSD-style license that can be -- Use of this source code is governed by a BSD-style license that can be
-- found in the LICENSE file. -- found in the LICENSE file.
--> -->
<html> <html>
<head> <head>
<title>#xFEFF;</title> <title>#xFEFF;</title>
<link rel="icon" type="image/png" href="images/200/icon.png"> <link rel="icon" type="image/png" href="images/200/icon.png">
<link rel="stylesheet" type="text/css" <link rel="stylesheet" type="text/css"
href="../file_manager/foreground/css/media_controls.css"> href="../file_manager/foreground/css/media_controls.css">
<link rel="stylesheet" href="chrome://resources/css/menu.css"></link>
<link rel="stylesheet" type="text/css" href="css/video_player.css"> <link rel="stylesheet" type="text/css" href="css/video_player.css">
<link rel="stylesheet" type="text/css" href="css/header.css"> <link rel="stylesheet" type="text/css" href="css/header.css">
<link rel="stylesheet" type="text/css" href="css/arrow_box.css"> <link rel="stylesheet" type="text/css" href="css/arrow_box.css">
<link rel="stylesheet" type="text/css" href="css/cast_menu.css">
<script src="js/video_player_scripts.js"></script> <script src="js/video_player_scripts.js"></script>
</head> </head>
<body> <body>
<div id="video-player" tools> <div id="video-player" tools>
<menu id='cast-menu' class='cast-menu tool'></menu>
<div id="video-container"> <div id="video-container">
</div> </div>
<div id="header-container" class="header tool"> <div id="header-container" class="header tool">
<div id="title">&nbsp;</div> <div id="title">&nbsp;</div>
<button class="cast-button menubutton hidden tool"
menu="#cast-menu"></button>
<button class="minimize-button tool" tabindex="-1"></button> <button class="minimize-button tool" tabindex="-1"></button>
<button class="maximize-button tool" tabindex="-1"></button> <button class="maximize-button tool" tabindex="-1"></button>
<button class="close-button tool" tabindex="-1"></button> <button class="close-button tool" tabindex="-1"></button>
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment