Added new demo page.

This is a complete replacement for the old demo page, made to be more
modern-looking and easier to maintain. It contains new features such as
remembering the URIs you provide for custom assets, and searching through
the default assets by feature.
This demo page is not quite ready for release yet, but it's getting close.

Change-Id: Iad01d1fc02c3cd238d73b9b9e02dbb4301cb6f2a
This commit is contained in:
Theodore Abshire
2019-04-23 15:12:44 -07:00
parent c0366cbf14
commit efc2ed3df1
66 changed files with 5172 additions and 4279 deletions
+1
View File
@@ -5,3 +5,4 @@ package-lock.json
dist/
docs/api/
coverage/
.DS_Store
+14 -8
View File
@@ -30,6 +30,17 @@ import shakaBuildHelpers
import os
import re
def compile_less(path_name, main_file_name, parsed_args):
match = re.compile(r'.*\.less$')
base = shakaBuildHelpers.get_source_base()
main_less_src = os.path.join(base, path_name, main_file_name + '.less')
all_less_srcs = shakaBuildHelpers.get_all_files(
os.path.join(base, path_name), match)
output = os.path.join(base, 'dist', main_file_name + '.css')
less = compiler.Less(main_less_src, all_less_srcs, output)
return less.compile(parsed_args.force)
def main(args):
parser = argparse.ArgumentParser(
description='User facing build script for building the Shaka'
@@ -98,14 +109,9 @@ def main(args):
if docs.main(docs_args) != 0:
return 1
match = re.compile(r'.*\.less$')
main_less_src = os.path.join(base, 'ui', 'controls.less')
all_less_srcs = shakaBuildHelpers.get_all_files(
os.path.join(base, 'ui'), match)
output = os.path.join(base, 'dist', 'controls.css')
less = compiler.Less(main_less_src, all_less_srcs, output)
if not less.compile(parsed_args.force):
if not compile_less('ui', 'controls', parsed_args):
return 1;
if not compile_less('demo', 'demo', parsed_args):
return 1
build_args_with_ui = ['--name', 'ui', '+@complete']
+1
View File
@@ -118,6 +118,7 @@ def check_tests(args):
files = set(get('lib') + get('externs') + get('test') + get('ui') +
get('third_party', 'closure') +
get('third_party', 'language-mapping-list'))
files.add(os.path.join(base, 'demo', 'common', 'asset.js'))
files.add(os.path.join(base, 'demo', 'common', 'assets.js'))
localizations = compiler.GenerateLocalizations(None)
+3 -2
View File
@@ -52,6 +52,7 @@ requirement {
# Until we can get this rule updated for ES6 static methods
# (https://github.com/google/closure-compiler/issues/2880):
whitelist_regexp: 'test/test/util/canned_idb.js'
whitelist_regexp: 'test/assets/assets_integration.js'
}
@@ -214,13 +215,13 @@ requirement: {
type: BANNED_NAME
value: 'window.eval'
error_message: 'Using "eval" is not allowed'
whitelist_regexp: 'demo/common/demo_utils.js'
whitelist_regexp: 'demo/demo_utils.js'
}
requirement: {
type: BANNED_NAME
value: 'eval'
error_message: 'Using "eval" is not allowed'
whitelist_regexp: 'demo/common/demo_utils.js'
whitelist_regexp: 'demo/demo_utils.js'
}
+1
View File
@@ -31,6 +31,7 @@ deps_args = [
'--root_with_prefix=third_party/language-mapping-list ' +
'../../../third_party/language-mapping-list',
'--root_with_prefix=dist ../../../dist',
'--root_with_prefix=demo ../../../demo',
]
+1 -1
View File
@@ -22,7 +22,7 @@
"sizes": "512x512",
"type": "image/png"
}],
"start_url": "./#compiled",
"start_url": "./#build=compiled",
"display": "standalone",
"background_color": "#FFFFFF",
"theme_color": "#2F3BA2"
+253
View File
@@ -0,0 +1,253 @@
/**
* @license
* Copyright 2016 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Creates and contains an MDL card that presents info about the given asset.
* @final
*/
class AssetCard {
/**
* @param {!Element} parentDiv
* @param {!ShakaDemoAssetInfo} asset
* @param {boolean} isFeatured True if this card should use the "featured"
* style, which use the asset's short name and have descriptions.
*/
constructor(parentDiv, asset, isFeatured) {
/** @private {!Element} */
this.card_ = document.createElement('div');
/** @private {!ShakaDemoAssetInfo} */
this.asset_ = asset;
/** @private {!Element} */
this.actions_ = document.createElement('div');
/** @private {!Element} */
this.featureIconsContainer_ = document.createElement('div');
/** @private {!Element} */
this.progressBar_ = document.createElement('progress');
// Lay out the card.
this.card_.classList.add('mdl-card-wide');
this.card_.classList.add('mdl-card');
this.card_.classList.add('mdl-shadow--2dp');
this.card_.classList.add('asset-card');
const titleDiv = document.createElement('div');
titleDiv.classList.add('mdl-card__title');
this.card_.appendChild(titleDiv);
const titleText = document.createElement('h2');
titleText.classList.add('mdl-card__title-text');
titleText.textContent = asset.shortName || asset.name;
titleDiv.appendChild(titleText);
if (asset.iconUri) {
const img = document.createElement('IMG');
img.src = asset.iconUri;
this.card_.appendChild(img);
}
if (asset.description && isFeatured) {
const supportingText = document.createElement('div');
supportingText.classList.add('mdl-card__supporting-text');
supportingText.textContent = asset.description;
this.card_.appendChild(supportingText);
}
this.card_.appendChild(this.featureIconsContainer_);
this.addFeatureIcons_(asset);
this.actions_.classList.add('mdl-card__actions');
this.actions_.classList.add('mdl-card--border');
this.card_.appendChild(this.actions_);
const progressContainer = document.createElement('div');
this.progressBar_.classList.add('hidden');
this.progressBar_.setAttribute('max', 1);
this.progressBar_.setAttribute('value', asset.storedProgress);
progressContainer.appendChild(this.progressBar_);
this.card_.appendChild(progressContainer);
parentDiv.appendChild(this.card_);
}
/**
* @param {string} icon
* @param {string} title
* @private
*/
addFeatureIcon_(icon, title) {
const iconDiv = document.createElement('div');
iconDiv.classList.add('feature-icon');
iconDiv.setAttribute('icon', icon);
this.featureIconsContainer_.appendChild(iconDiv);
ShakaDemoTooltips.make(this.featureIconsContainer_, iconDiv, title);
}
/**
* @param {!ShakaDemoAssetInfo} asset
* @private
*/
addFeatureIcons_(asset) {
const Feature = shakaAssets.Feature;
const KeySystem = shakaAssets.KeySystem;
const icons = new Map()
.set(Feature.SUBTITLES, 'subtitles')
.set(Feature.CAPTIONS, 'closed_caption')
.set(Feature.LIVE, 'live')
.set(Feature.TRICK_MODE, 'trick_mode')
.set(Feature.HIGH_DEFINITION, 'high_definition')
.set(Feature.ULTRA_HIGH_DEFINITION, 'ultra_high_definition')
.set(Feature.SURROUND, 'surround_sound')
.set(Feature.MULTIPLE_LANGUAGES, 'multiple_languages')
.set(Feature.AUDIO_ONLY, 'audio_only');
for (const feature of asset.features) {
const icon = icons.get(feature);
if (icon) {
this.addFeatureIcon_(icon, feature);
}
}
for (let drm of asset.drm) {
switch (drm) {
case KeySystem.WIDEVINE:
this.addFeatureIcon_('widevine', 'Widevine DRM');
break;
case KeySystem.CLEAR_KEY:
this.addFeatureIcon_('clear_key', 'Clear Key DRM');
break;
case KeySystem.PLAYREADY:
this.addFeatureIcon_('playready', 'PlayReady DRM');
break;
}
}
}
/**
* Modify an asset to make it clear that it is unsupported.
* @param {?string} unsupportedReason
*/
markAsUnsupported(unsupportedReason) {
this.card_.classList.add('asset-card-unsupported');
this.makeUnsupportedButton_('Not Available', unsupportedReason);
}
/**
* Make a button that represents the lack of a working button.
* @param {string} buttonName
* @param {?string} unsupportedReason
* @private
*/
makeUnsupportedButton_(buttonName, unsupportedReason) {
const button = this.addButton(buttonName, () => {});
button.setAttribute('disabled', '');
// Place the tooltip into the parent container of the card, so that the
// tooltip won't be clipped by other asset cards.
// Also, tooltips don't work on disabled buttons (on some platforms), so
// the button itself has to be "uprooted" and placed in a synthetic div
// specifically to attach the tooltip to.
const tooltipContainer = this.card_.parentElement;
if (tooltipContainer && unsupportedReason) {
const attachPoint = document.createElement('div');
if (button.parentElement) {
button.parentElement.removeChild(button);
}
attachPoint.classList.add('tooltip-attach-point');
attachPoint.appendChild(button);
this.actions_.appendChild(attachPoint);
ShakaDemoTooltips.make(tooltipContainer, attachPoint, unsupportedReason);
}
}
/**
* Select this card if the card's asset matches |asset|.
* Used to simplify the implementation of "shaka-main-selected-asset-changed"
* handlers.
* @param {!ShakaDemoAssetInfo} asset
*/
selectByAsset(asset) {
this.card_.classList.remove('selected');
if (this.asset_ == asset) {
this.card_.classList.add('selected');
}
}
/**
* Adds a button to the bottom of the card that controls storage behavior.
* This is a separate function because it involves a significant amount of
* custom behavior, such as the download bar.
*/
addStoreButton() {
const unsupportedReason = shakaDemoMain.getAssetUnsupportedReason(
this.asset_, /* needOffline= */ true);
if (unsupportedReason || !this.asset_.storeCallback) {
// This can't be stored.
this.makeUnsupportedButton_('Download', unsupportedReason);
return;
}
if (this.asset_.isStored()) {
this.addButton('Delete', async () => {
await this.asset_.unstoreCallback();
});
} else {
this.addButton('Download', async () => {
await this.asset_.storeCallback();
});
}
}
/** Updates the progress bar on the card. */
updateProgress() {
if (this.asset_.storedProgress < 1) {
this.progressBar_.classList.remove('hidden');
for (const button of this.actions_.childNodes) {
button.disabled = true;
}
} else {
this.progressBar_.classList.add('hidden');
for (const button of this.actions_.childNodes) {
button.disabled = false;
}
}
this.progressBar_.setAttribute('value', this.asset_.storedProgress);
}
/**
* Adds a button to the bottom of the card that will call |onClick| when
* clicked. For example, a play or delete button.
* @param {string} name
* @param {function()} onclick
* @return {!Element}
*/
addButton(name, onclick) {
const button = document.createElement('button');
button.classList.add('mdl-button');
button.classList.add('mdl-button--colored');
button.classList.add('mdl-js-button');
button.classList.add('mdl-js-ripple-effect');
button.textContent = name;
button.addEventListener('click', () => {
if (!button.hasAttribute('disabled')) {
onclick();
}
});
this.actions_.appendChild(button);
return button;
}
}
-416
View File
@@ -1,416 +0,0 @@
/**
* @license
* Copyright 2016 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @fileoverview Shaka Player demo, main section.
*
* @suppress {visibility} to work around compiler errors until we can
* refactor the demo into classes that talk via public method. TODO
*/
/** @suppress {duplicate} */
var shakaDemo = shakaDemo || {}; // eslint-disable-line no-var
/** @private {!Array.<HTMLOptGroupElement>} */
shakaDemo.onlineOptGroups_ = [];
/**
* @return {!Promise}
* @private
*/
shakaDemo.setupAssets_ = function() {
// Populate the asset list.
let assetList = document.getElementById('assetList');
/** @type {!Object.<string, !HTMLOptGroupElement>} */
let groups = {};
let first = null;
shakaAssets.testAssets.forEach(function(asset) {
if (asset.disabled) return;
let group = groups[asset.source];
if (!group) {
group = /** @type {!HTMLOptGroupElement} */(
document.createElement('optgroup'));
group.label = asset.source;
group.disabled = !navigator.onLine;
groups[asset.source] = group;
assetList.appendChild(group);
shakaDemo.onlineOptGroups_.push(group);
}
let option = document.createElement('option');
option.textContent = asset.name;
option.asset = asset; // A custom attribute to map back to the asset.
group.appendChild(option);
if (asset.drm.length && !asset.drm.some(
function(keySystem) { return shakaDemo.support_.drm[keySystem]; })) {
option.disabled = true;
}
let mimeTypes = [];
if (asset.features.includes(shakaAssets.Feature.WEBM)) {
mimeTypes.push('video/webm');
}
if (asset.features.includes(shakaAssets.Feature.MP4)) {
mimeTypes.push('video/mp4');
}
if (asset.features.includes(shakaAssets.Feature.MP2TS)) {
mimeTypes.push('video/mp2t');
}
if (!mimeTypes.some(
function(type) { return shakaDemo.support_.media[type]; })) {
option.disabled = true;
}
if (asset.features.includes(shakaAssets.Feature.DASH) &&
!shakaDemo.support_.manifest['mpd']) {
option.disabled = true;
}
if (asset.features.includes(shakaAssets.Feature.HLS) &&
!shakaDemo.support_.manifest['m3u8']) {
option.disabled = true;
}
if (!option.disabled && !group.disabled) {
first = first || option;
if (asset.focus) first = option;
}
});
if (first) {
first.selected = true;
}
// This needs to be started before we add the custom asset option.
let asyncOfflineSetup = shakaDemo.setupOfflineAssets_();
// Add an extra option for custom assets.
let option = document.createElement('option');
option.textContent = '(custom asset)';
assetList.appendChild(option);
assetList.addEventListener('change', function() {
// Show/hide the custom asset fields based on the selection.
let asset = assetList.options[assetList.selectedIndex].asset;
let customAsset = document.getElementById('customAsset');
customAsset.style.display = asset ? 'none' : 'block';
// Update the hash to reflect this change.
shakaDemo.hashShouldChange_();
});
document.getElementById('loadButton').addEventListener(
'click', shakaDemo.load);
document.getElementById('unloadButton').addEventListener(
'click', shakaDemo.unload);
const assetInputs = [
document.getElementById('licenseServerInput'),
document.getElementById('manifestInput'),
document.getElementById('certificateInput'),
];
for (const input of assetInputs) {
input.addEventListener('input', shakaDemo.onAssetInput_);
input.addEventListener('keydown', shakaDemo.onAssetKeyDown_);
}
return asyncOfflineSetup;
};
/**
* @param {!Event} event
* @private
*/
shakaDemo.onAssetKeyDown_ = function(event) {
if (event.key == 'Enter') {
shakaDemo.load();
}
};
/**
* @param {!Event} event
* @private
*/
shakaDemo.onAssetInput_ = function(event) {
// Mirror the users input as they type.
shakaDemo.hashShouldChange_();
};
/**
* @param {string} uri
* @return {!Promise.<ArrayBuffer>}
* @private
*/
shakaDemo.requestCertificate_ = function(uri) {
let netEngine = shakaDemo.player_.getNetworkingEngine();
const requestType = shaka.net.NetworkingEngine.RequestType.APP;
let request = /** @type {shaka.extern.Request} */ ({uris: [uri]});
return netEngine.request(requestType, request).promise
.then((response) => response.data);
};
/**
* @param {ArrayBuffer} certificate
* @private
*/
shakaDemo.configureCertificate_ = function(certificate) {
let player = shakaDemo.player_;
let config = player.getConfiguration();
let certConfig = {};
for (let keySystem in config.drm.advanced) {
certConfig[keySystem] = {
serverCertificate: new Uint8Array(certificate),
};
}
player.configure({
drm: {
advanced: certConfig,
},
});
};
/**
* Prepares the Player to load the given assets by setting the configuration
* values. This does not load the asset.
*
* @param {?shakaAssets.AssetInfo} asset
* @return {shakaAssets.AssetInfo}
* @private
*/
shakaDemo.preparePlayer_ = function(asset) {
shakaDemo.closeError();
let player = shakaDemo.player_;
let videoRobustness =
document.getElementById('drmSettingsVideoRobustness').value;
let audioRobustness =
document.getElementById('drmSettingsAudioRobustness').value;
let commonDrmSystems = [
'com.widevine.alpha',
'com.microsoft.playready',
'com.apple.fps.1_0',
'com.adobe.primetime',
'org.w3.clearkey',
];
let config = /** @type {shaka.extern.PlayerConfiguration} */(
{abr: {}, streaming: {}, manifest: {dash: {}}, offline: {}});
config.drm = /** @type {shaka.extern.DrmConfiguration} */({
advanced: {}});
commonDrmSystems.forEach(function(system) {
config.drm.advanced[system] =
/** @type {shaka.extern.AdvancedDrmConfiguration} */({});
});
config.manifest.dash.clockSyncUri =
'https://shaka-player-demo.appspot.com/time.txt';
if (!asset) {
// Use the custom fields.
let licenseServerUri = document.getElementById('licenseServerInput').value;
let licenseServers = {};
if (licenseServerUri) {
commonDrmSystems.forEach(function(system) {
licenseServers[system] = licenseServerUri;
});
}
asset = /** @type {shakaAssets.AssetInfo} */ ({
manifestUri: document.getElementById('manifestInput').value,
// Use the custom license server for all key systems.
// This simplifies configuration for the user.
// They will simply fill in a Widevine license server on Chrome, etc.
licenseServers: licenseServers,
// Use a custom certificate for all key systems as well
certificateUri: document.getElementById('certificateInput').value,
});
}
// Any storage operation should update our progress label.
config.offline.progressCallback = function(data, percent) {
let progress = document.getElementById('progress');
progress.textContent = (percent * 100).toFixed(2);
};
player.resetConfiguration();
// Add configuration from this asset.
ShakaDemoUtils.setupAssetMetadata(asset, player);
shakaDemo.castProxy_.setAppData({'asset': asset});
// Add drm configuration from the UI.
if (videoRobustness) {
commonDrmSystems.forEach(function(system) {
config.drm.advanced[system].videoRobustness = videoRobustness;
});
}
if (audioRobustness) {
commonDrmSystems.forEach(function(system) {
config.drm.advanced[system].audioRobustness = audioRobustness;
});
}
// Add other configuration from the UI.
config.preferredAudioLanguage =
document.getElementById('preferredAudioLanguage').value;
config.preferredTextLanguage =
document.getElementById('preferredTextLanguage').value;
const preferredAudioChannelCount =
Number(document.getElementById('preferredAudioChannelCount').value);
if (!isNaN(preferredAudioChannelCount)) {
config.preferredAudioChannelCount = preferredAudioChannelCount;
}
let availabilityWindowOverrideRaw =
document.getElementById('availabilityWindowOverride').value;
let availabilityWindowOverride = Number(availabilityWindowOverrideRaw);
if (!isNaN(availabilityWindowOverride) &&
availabilityWindowOverrideRaw.length) {
// Don't configure if the field contains an empty string; this is because
// Number('') evaluates to 0, which is a valid (if fairly useless) override
// value, while we would rather it mean "don't override".
config.manifest.availabilityWindowOverride = availabilityWindowOverride;
}
config.abr.enabled =
document.getElementById('enableAdaptation').checked;
let smallGapLimit = document.getElementById('smallGapLimit').value;
if (!isNaN(Number(smallGapLimit)) && smallGapLimit.length > 0) {
config.streaming.smallGapLimit = Number(smallGapLimit);
}
config.streaming.jumpLargeGaps =
document.getElementById('jumpLargeGaps').checked;
// When we use native controls, we must always stream text.
// See comments in onNativeChange_ for details.
config.streaming.alwaysStreamText =
document.getElementById('showNative').checked;
const videoContainer = shakaDemo.controls_.getVideoContainer();
config.textDisplayFactory = function() {
return new shaka.ui.TextDisplayer(shakaDemo.video_, videoContainer);
};
player.configure(config);
// TODO: Document demo app debugging features.
if (window.debugConfig) {
player.configure(window.debugConfig);
}
return asset;
};
/** Compute which assets should be disabled. */
shakaDemo.computeDisabledAssets = function() {
// TODO: Use a remote support probe, recompute asset disabled when casting?
shakaDemo.onlineOptGroups_.forEach(function(group) {
group.disabled = !navigator.onLine;
});
};
/** Load the selected asset. */
shakaDemo.load = function() {
let assetList = document.getElementById('assetList');
let option = assetList.options[assetList.selectedIndex];
let player = shakaDemo.player_;
let asset = shakaDemo.preparePlayer_(option.asset);
// Revert to default poster while we load.
shakaDemo.localVideo_.poster = shakaDemo.mainPoster_;
let configureCertificate = Promise.resolve();
if (asset.certificateUri) {
configureCertificate = shakaDemo.requestCertificate_(asset.certificateUri)
.then(shakaDemo.configureCertificate_);
}
configureCertificate.then(function() {
// Load the manifest.
return player.load(asset.manifestUri, shakaDemo.startTime_);
}).then(function() {
// Update the control state in case autoplay is disabled.
shakaDemo.controls_.loadComplete();
if (shakaDemo.video_.controls) {
shakaDemo.controls_.setEnabledNativeControls(true);
} else {
shakaDemo.controls_.setEnabledShakaControls(true);
}
shakaDemo.hashShouldChange_();
// Set a different poster for audio-only assets.
if (player.isAudioOnly()) {
shakaDemo.localVideo_.poster = shakaDemo.audioOnlyPoster_;
}
// Disallow the casting of offline content.
let isOffline = asset.manifestUri.startsWith('offline:');
shakaDemo.controls_.allowCast(!isOffline);
(asset.extraText || []).forEach(function(extraText) {
player.addTextTrack(extraText.uri, extraText.language, extraText.kind,
extraText.mime, extraText.codecs);
});
// Check if browser supports Media Session first.
if ('mediaSession' in navigator) {
// Set media session title.
navigator.mediaSession.metadata = new MediaMetadata({title: asset.name});
}
}, function(reason) {
let error = /** @type {!shaka.util.Error} */(reason);
if (error.code == shaka.util.Error.Code.LOAD_INTERRUPTED) {
// Don't use shaka.log, which is not present in compiled builds.
console.debug('load() interrupted');
} else {
shakaDemo.onError_(error);
}
});
// While the manifest is being loaded in parallel, go ahead and ask the video
// to play. This can help with autoplay on Android, since Android requires
// user interaction to play a video and this function is called from a click
// event. This seems to work only because Shaka Player has already created a
// MediaSource object and set video.src.
shakaDemo.video_.play();
};
/** Unload any current asset. */
shakaDemo.unload = function() {
shakaDemo.player_.unload();
if (!shakaDemo.castProxy_.isCasting()) {
shakaDemo.controls_.setEnabledShakaControls(false);
}
};
+1 -1
View File
@@ -47,8 +47,8 @@ UNCOMPILED_JS = [
// This file contains goog.require calls for all exported library classes.
'../../shaka-player.uncompiled.js',
// These are the individual parts of the receiver app.
'../common/asset.js',
'../common/assets.js',
'../common/demo_utils.js',
'receiver_app.js',
];
</script>
+4 -2
View File
@@ -115,8 +115,10 @@ ShakaReceiver.prototype.appDataCallback_ = function(appData) {
// appData is null if we start the app without any media loaded.
if (!appData) return;
let asset = /** @type {shakaAssets.AssetInfo} */(appData['asset']);
ShakaDemoUtils.setupAssetMetadata(asset, this.player_);
const asset = ShakaDemoAssetInfo.fromJSON(appData['asset']);
asset.applyFilters(this.player_.getNetworkingEngine());
const config = asset.getConfiguration();
this.player_.configure(config);
};
+67
View File
@@ -0,0 +1,67 @@
/**
* @license
* Copyright 2016 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* A custom UI button, to allow users to close the video element.
* This cannot actually extend shaka.ui.Element, as that class does not exist
* at load-time when in uncompiled mode.
* @implements {shaka.extern.IUIElement}
*/
class CloseButton {
/**
* @param {!HTMLElement} parent
* @param {!shaka.ui.Controls} controls
*/
constructor(parent, controls) {
/** @protected {!HTMLElement} */
this.parent = parent;
this.button_ = document.createElement('button');
this.button_.classList.add('material-icons');
this.button_.classList.add('close-button');
this.button_.textContent = 'close'; // Close icon.
this.parent.appendChild(this.button_);
this.button_.addEventListener('click', () => {
shakaDemoMain.unload();
});
// TODO: Make sure that the screenreader description of this control is
// localized!
}
/** @override */
destroy() {
return Promise.resolve();
}
}
/**
* @implements {shaka.extern.IUIElement.Factory}
* @final
*/
CloseButton.Factory = class {
/** @override */
create(rootElement, controls) {
return new CloseButton(rootElement, controls);
}
};
// This button is registered inside setup in ShakaDemoMain, rather than
// statically here, since shaka.ui.Controls does not exist in this stage of the
// load process.
+383
View File
@@ -0,0 +1,383 @@
/**
* @license
* Copyright 2016 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
goog.provide('ShakaDemoAssetInfo');
goog.require('goog.asserts');
/**
* An object that contains information about an asset.
*/
const ShakaDemoAssetInfo = class {
/**
* @param {string} name
* @param {string} iconUri
* @param {string} manifestUri
* @param {shakaAssets.Source} source
*/
constructor(name, iconUri, manifestUri, source) {
// Required members.
/** @type {string} */
this.name = name;
/** @type {string} */
this.shortName = '';
/** @type {string} */
this.iconUri = iconUri;
/** @type {string} */
this.manifestUri = manifestUri;
/** @type {string} */
this.source = source;
// Optional members.
/** @type {boolean} */
this.focus = false;
/** @type {boolean} */
this.disabled = false;
/** @type {!Array.<!shakaAssets.ExtraText>} */
this.extraText = [];
/** @type {?string} */
this.certificateUri = null;
/** @type {string} */
this.description = '';
/** @type {boolean} */
this.isFeatured = false;
/** @type {!Array.<!shakaAssets.KeySystem>} */
this.drm = [shakaAssets.KeySystem.CLEAR];
/** @type {!Array.<!shakaAssets.Feature>} */
this.features = [];
/** @type {!Map.<string, string>} */
this.licenseServers = new Map();
/** @type {!Map.<string, string>} */
this.licenseRequestHeaders = new Map();
/** @type {?shaka.extern.RequestFilter} */
this.requestFilter = null;
/** @type {?shaka.extern.ResponseFilter} */
this.responseFilter = null;
/** @type {?shaka.extern.DashContentProtectionCallback} */
this.drmCallback = null; // TODO: Setter method?
/** @type {!Map.<string, string>} */
this.clearKeys = new Map(); // TODO: Setter method?
/** @type {?Object} */
this.extraConfig = null;
// Offline storage values.
/** @type {?function()} */
this.storeCallback;
/** @type {?function()} */
this.unstoreCallback;
/** @type {?shaka.extern.StoredContent} */
this.storedContent;
/** @type {number} */
this.storedProgress = 1;
}
/**
* @param {string} description
* @return {!ShakaDemoAssetInfo}
*/
addDescription(description) {
this.description = description;
return this;
}
/**
* @param {string} certificateUri
* @return {!ShakaDemoAssetInfo}
*/
addCertificateUri(certificateUri) {
this.certificateUri = certificateUri;
return this;
}
/**
* A sort comparator for comparing two strings, ignoring case.
* @param {string} a
* @param {string} b
* @return {number}
* @private
*/
static caseLessAlphaComparator_(a, b) {
if (a.toLowerCase() < b.toLowerCase()) {
return -1;
}
if (a.toLowerCase() > b.toLowerCase()) {
return 1;
}
return 0;
}
/**
* @param {shakaAssets.Feature} feature
* @return {!ShakaDemoAssetInfo}
*/
addFeature(feature) {
goog.asserts.assert(feature != shakaAssets.Feature.STORED,
'Assets should not be given the synthetic "STORED" ' +
'property!');
this.features.push(feature);
// Sort the features list, so that features are in a predictable order.
this.features.sort(ShakaDemoAssetInfo.caseLessAlphaComparator_);
return this;
}
/**
* @param {shakaAssets.KeySystem} keySystem
* @return {!ShakaDemoAssetInfo}
*/
addKeySystem(keySystem) {
if (this.drm.length == 1 && this.drm[0] == shakaAssets.KeySystem.CLEAR) {
// Once an asset has an actual key system, it's no longer a CLEAR asset.
this.drm = [];
}
this.drm.push(keySystem);
// Sort the drm list, so that key systems are in a predictable order.
this.drm.sort(ShakaDemoAssetInfo.caseLessAlphaComparator_);
return this;
}
/**
* @param {!Object} extraConfig
* @return {!ShakaDemoAssetInfo}
*/
setExtraConfig(extraConfig) {
this.extraConfig = extraConfig;
return this;
}
/**
* @param {!shaka.extern.RequestFilter} requestFilter
* @return {!ShakaDemoAssetInfo}
*/
setRequestFilter(requestFilter) {
this.requestFilter = requestFilter;
return this;
}
/**
* @param {!shaka.extern.ResponseFilter} responseFilter
* @return {!ShakaDemoAssetInfo}
*/
setResponseFilter(responseFilter) {
this.responseFilter = responseFilter;
return this;
}
/**
* @param {string} keySystem
* @param {string} licenseServer
* @return {!ShakaDemoAssetInfo}
*/
addLicenseServer(keySystem, licenseServer) {
this.licenseServers.set(keySystem, licenseServer);
return this;
}
/**
* @param {string} keySystem
* @param {string} licenseRequestHeader
* @return {!ShakaDemoAssetInfo}
*/
addLicenseRequestHeader(keySystem, licenseRequestHeader) {
this.licenseRequestHeaders.set(keySystem, licenseRequestHeader);
return this;
}
/**
* @param {shakaAssets.ExtraText} extraText
* @return {!ShakaDemoAssetInfo}
*/
addExtraText(extraText) {
// TODO: At no point do we actually use the extraText... why does it exist?
this.extraText.push(extraText);
return this;
}
/**
* If this is called, the asset will be focused on by the integration tests.
* @return {!ShakaDemoAssetInfo}
*/
markAsFocused() {
this.focus = true;
return this;
}
/**
* If this is called, the asset will appear on the main page of the demo.
* Also, this allows you to provide a shorter name to be used in the feature
* card.
* @param {string=} shortName
* @return {!ShakaDemoAssetInfo}
*/
markAsFeatured(shortName) {
this.isFeatured = true;
this.shortName = shortName || this.shortName;
return this;
}
/**
* If this is called, the asset is disabled in tests and in the demo app.
* @return {!ShakaDemoAssetInfo}
*/
markAsDisabled() {
this.disabled = true;
return this;
}
/**
* @return {!Object}
* @override
*
* Suppress checkTypes warnings, so that we can access properties of this
* object as though it were a struct.
* @suppress {checkTypes}
*/
toJSON() {
// Construct a generic object with the values of this object, but with the
// proper formatting.
const raw = {};
for (let key in this) {
const value = this[key];
if (value instanceof Map) {
// The built-in JSON functions cannot convert Maps; this converts Maps
// to objects.
const replacement = {};
replacement['__type__'] = 'map';
for (let entry of value.entries()) {
replacement[entry[0]] = entry[1];
}
raw[key] = replacement;
} else {
raw[key] = value;
}
}
return raw;
}
/**
* Applies appropriate request or response filters to the player.
* @param {shaka.net.NetworkingEngine} networkingEngine
*/
applyFilters(networkingEngine) {
networkingEngine.clearAllRequestFilters();
networkingEngine.clearAllResponseFilters();
if (this.licenseRequestHeaders.size) {
const filter = (requestType, request) => {
return this.addLicenseRequestHeaders_(this.licenseRequestHeaders,
requestType,
request);
};
networkingEngine.registerRequestFilter(filter);
}
if (this.requestFilter) {
networkingEngine.registerRequestFilter(this.requestFilter);
}
if (this.responseFilter) {
networkingEngine.registerResponseFilter(this.responseFilter);
}
}
/**
* Gets the configuration object for the asset.
* @return {!shaka.extern.PlayerConfiguration}
*/
getConfiguration() {
const config = /** @type {shaka.extern.PlayerConfiguration} */(
{drm: {}, manifest: {dash: {}}});
if (this.licenseServers.size) {
config.drm.servers = {};
this.licenseServers.forEach((value, key) => {
config.drm.servers[key] = value;
});
}
if (this.drmCallback) {
config.manifest.dash.customScheme = this.drmCallback;
}
if (this.clearKeys.size) {
config.drm.clearKeys = {};
this.clearKeys.forEach((value, key) => {
config.drm.clearKeys[key] = value;
});
}
if (this.extraConfig) {
for (let key in this.extraConfig) {
config[key] = this.extraConfig[key];
}
}
return config;
}
/**
* @param {!Map.<string, string>} headers
* @param {shaka.net.NetworkingEngine.RequestType} requestType
* @param {shaka.extern.Request} request
* @private
*/
addLicenseRequestHeaders_(headers, requestType, request) {
if (requestType != shaka.net.NetworkingEngine.RequestType.LICENSE) return;
// Add these to the existing headers. Do not clobber them!
// For PlayReady, there will already be headers in the request.
headers.forEach((value, key) => {
request.headers[key] = value;
});
}
/** @return {boolean} */
isStored() {
return this.storedContent != null;
}
};
/** @return {!ShakaDemoAssetInfo} */
ShakaDemoAssetInfo.makeBlankAsset = function() {
return new ShakaDemoAssetInfo(
/* name= */ '',
/* iconUri= */ '',
/* manifestUri= */ '',
/* source= */ shakaAssets.Source.CUSTOM);
};
/**
* @param {!Object} raw
* @return {!ShakaDemoAssetInfo}
*/
ShakaDemoAssetInfo.fromJSON = function(raw) {
// This handles the special case for Maps in toJSON.
const parsed = {};
for (let key in raw) {
const value = raw[key];
if (value && typeof value == 'object' && value['__type__'] == 'map') {
const replacement = new Map();
for (let key in value) {
if (key != '__type__') {
replacement.set(key, value[key]);
}
}
parsed[key] = replacement;
} else {
parsed[key] = value;
}
}
const asset = ShakaDemoAssetInfo.makeBlankAsset();
Object.assign(asset, parsed);
return asset;
};
+767 -1379
View File
File diff suppressed because it is too large Load Diff
+584
View File
@@ -0,0 +1,584 @@
/**
* @license
* Copyright 2016 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/** @type {?ShakaDemoConfig} */
let shakaDemoConfig;
/**
* Shaka Player demo, configuration page layout.
*/
class ShakaDemoConfig {
/**
* Register the page configuration.
*/
static init() {
const container = shakaDemoMain.getHamburgerMenu();
shakaDemoConfig = new ShakaDemoConfig(container);
}
/** @param {!Element} container */
constructor(container) {
/** @private {!Element} */
this.container_ = container;
/**
* A list of all sections.
* @private {!Array.<!ShakaDemoInputContainer>}
*/
this.sections_ = [];
/**
* The input object for the control currently being constructed.
* @private {?ShakaDemoInput}
*/
this.latestInput_ = null;
/** @private {!Set.<string>} */
this.addedFields_ = new Set();
this.reload_();
// Listen to external config changes (i.e. from hash changes).
document.addEventListener('shaka-main-config-change', () => {
// Respond to them by remaking. This is to avoid triggering any config
// changes based on the config changes.
this.reloadAndSaveState_();
});
}
/** @private */
reload_() {
shaka.ui.Utils.removeAllChildren(this.container_);
this.sections_ = [];
this.addMetaSection_();
this.addLanguageSection_();
this.addAbrSection_();
this.addDrmSection_();
this.addStreamingSection_();
this.addManifestSection_();
this.addRetrictionsSection_('', '');
this.checkSectionCompleteness_();
this.checkSectionValidity_();
}
/**
* Remake the contents of the div. Unlike |reload_|, this will also remember
* which sections were open.
* @private
*/
reloadAndSaveState_() {
const wasOpenArray = this.sections_.map((section) => section.getIsOpen());
this.reload_();
for (let i = 0; i < wasOpenArray.length; i++) {
const wasOpen = wasOpenArray[i];
const section = this.sections_[i];
if (wasOpen) {
section.open();
}
}
// Update the componentHandler, to account for any new MDL elements added.
componentHandler.upgradeDom();
}
/** @private */
addDrmSection_() {
const docLink = this.resolveExternLink_('.DrmConfiguration');
this.addSection_('DRM', docLink)
.addBoolInput_('Delay License Request Until Played',
'drm.delayLicenseRequestUntilPlayed');
const advanced = shakaDemoMain.getConfiguration().drm.advanced;
const robustnessSuggestions = [
'SW_SECURE_CRYPTO',
'SW_SECURE_DECODE',
'HW_SECURE_CRYPTO',
'HW_SECURE_DECODE',
'HW_SECURE_ALL',
];
const commonDrmSystems = [
'com.widevine.alpha',
'com.microsoft.playready',
'com.apple.fps.1_0',
'com.adobe.primetime',
'org.w3.clearkey',
];
const addRobustnessField = (name, valueName) => {
// All robustness fields of a given type are set at once.
this.addDatalistInput_(name, robustnessSuggestions, (input) => {
// Add in any common drmSystem not currently in advanced.
for (let drmSystem of commonDrmSystems) {
if (!(drmSystem in advanced)) {
advanced[commonDrmSystems] = {};
}
}
// Set the robustness.
for (let drmSystem in advanced) {
advanced[drmSystem][valueName] = input.value;
}
shakaDemoMain.configure('drm.advanced', advanced);
shakaDemoMain.remakeHash();
});
const keySystem = Object.keys(advanced)[0];
if (keySystem) {
const currentRobustness = advanced[keySystem][valueName];
this.latestInput_.input().value = currentRobustness;
}
};
addRobustnessField('Video Robustness', 'videoRobustness');
addRobustnessField('Audio Robustness', 'audioRobustness');
this.addRetrySection_('drm', 'DRM');
}
/** @private */
addManifestSection_() {
const docLink = this.resolveExternLink_('.ManifestConfiguration');
this.addSection_('Manifest', docLink)
.addBoolInput_('Ignore DASH DRM Info', 'manifest.dash.ignoreDrmInfo')
.addBoolInput_('Xlink Should Fail Gracefully',
'manifest.dash.xlinkFailGracefully')
.addNumberInput_('Availability Window Override',
'manifest.availabilityWindowOverride',
/* canBeDecimal = */ true,
/* canBeZero = */ false,
/* canBeUnset = */ true)
.addTextInput_('Clock Sync URI', 'manifest.dash.clockSyncUri')
.addBoolInput_('Ignore DRM Info', 'manifest.dash.ignoreDrmInfo')
.addNumberInput_('Default Presentation Delay',
'manifest.dash.defaultPresentationDelay')
.addBoolInput_('Ignore Min Buffer Time',
'manifest.dash.ignoreMinBufferTime');
this.addRetrySection_('manifest', 'Manifest');
}
/** @private */
addAbrSection_() {
const docLink = this.resolveExternLink_('.AbrConfiguration');
this.addSection_('Adaptation', docLink)
.addBoolInput_('Enabled', 'abr.enabled')
.addNumberInput_('Default Bandwidth Estimate',
'abr.defaultBandwidthEstimate')
.addNumberInput_('Bandwidth Downgrade Target',
'abr.bandwidthDowngradeTarget',
/* canBeDecimal = */ true)
.addNumberInput_('Bandwidth Upgrade Target',
'abr.bandwidthUpgradeTarget',
/* canBeDecimal = */ true)
.addNumberInput_('Switch Interval',
'abr.switchInterval',
/* canBeDecimal = */ true);
this.addRetrictionsSection_('abr', 'Adaptation');
}
/**
* @param {string} category
* @param {string} categoryName
* @private
*/
addRetrictionsSection_(category, categoryName) {
const prefix = (category ? category + '.' : '') + 'restrictions.';
const sectionName = (categoryName ? categoryName + ' ' : '') +
'Restrictions';
const docLink = this.resolveExternLink_('.Restrictions');
this.addSection_(sectionName, docLink)
.addNumberInput_('Min Width', prefix + 'minWidth')
.addNumberInput_('Max Width', prefix + 'maxWidth')
.addNumberInput_('Min Height', prefix + 'minHeight')
.addNumberInput_('Max Height', prefix + 'maxHeight')
.addNumberInput_('Min Pixels', prefix + 'minPixels')
.addNumberInput_('Max Pixels', prefix + 'maxPixels')
.addNumberInput_('Min Bandwidth', prefix + 'minBandwidth')
.addNumberInput_('Max Bandwidth', prefix + 'maxBandwidth');
}
/**
* @param {string} category
* @param {string} categoryName
* @private
*/
addRetrySection_(category, categoryName) {
const prefix = category + '.retryParameters.';
const docLink = this.resolveExternLink_('.RetryParameters');
this.addSection_(categoryName + ' Retry Parameters', docLink)
.addNumberInput_('Max Attempts', prefix + 'maxAttempts')
.addNumberInput_('Base Delay',
prefix + 'baseDelay',
/* canBeDecimal = */ true)
.addNumberInput_('Backoff Factor',
prefix + 'backoffFactor',
/* canBeDecimal = */ true)
.addNumberInput_('Fuzz Factor',
prefix + 'fuzzFactor',
/* canBeDecimal = */ true)
.addNumberInput_('Timeout',
prefix + 'timeout',
/* canBeDecimal = */ true);
}
/** @private */
addStreamingSection_() {
const docLink = this.resolveExternLink_('.StreamingConfiguration');
this.addSection_('Streaming', docLink)
.addNumberInput_('Maximum Small Gap Size', 'streaming.smallGapLimit',
/* canBeDecimal = */ true)
.addNumberInput_('Buffering Goal', 'streaming.bufferingGoal',
/* canBeDecimal = */ true)
.addNumberInput_('Duration Backoff', 'streaming.durationBackoff',
/* canBeDecimal = */ true)
.addNumberInput_('Rebuffering Goal', 'streaming.rebufferingGoal',
/* canBeDecimal = */ true)
.addNumberInput_('Buffer Behind', 'streaming.bufferBehind',
/* canBeDecimal = */ true);
if (!shakaDemoMain.getNativeControlsEnabled()) {
this.addBoolInput_('Always Stream Text', 'streaming.alwaysStreamText');
} else {
// Add a fake custom fixed "input" that warns the users not to change it.
const noop = (input) => {};
const tooltipMessage = 'Text must always be streamed while native ' +
'controls are enabled, for captions to work.';
this.addCustomBoolInput_('Always Stream Text', noop, tooltipMessage);
this.latestInput_.input().disabled = true;
this.latestInput_.input().checked = true;
}
this.addBoolInput_('Jump Large Gaps', 'streaming.jumpLargeGaps')
.addBoolInput_('Force Transmux TS', 'streaming.forceTransmuxTS')
.addBoolInput_('Start At Segment Boundary',
'streaming.startAtSegmentBoundary')
.addBoolInput_('Ignore Text Stream Failures',
'streaming.ignoreTextStreamFailures');
this.addRetrySection_('streaming', 'Streaming');
}
/** @private */
addLanguageSection_() {
const docLink = this.resolveExternLink_('.PlayerConfiguration');
this.addSection_('Language', docLink)
.addTextInput_('Preferred Audio Language', 'preferredAudioLanguage')
.addTextInput_('Preferred Text Language', 'preferredTextLanguage');
const onChange = (input) => {
shakaDemoMain.setUILocale(input.value);
};
this.addCustomTextInput_('Preferred UI Locale', onChange);
this.latestInput_.input().value = shakaDemoMain.getUILocale();
this.addNumberInput_('Preferred Audio Channel Count',
'preferredAudioChannelCount');
}
/** @private */
addMetaSection_() {
this.addSection_(/* name = */ '', /* docLink = */ null);
this.addCustomBoolInput_('Shaka Controls', (input) => {
shakaDemoMain.setNativeControlsEnabled(!input.checked);
if (input.checked) {
// Forcibly set |streaming.alwaysStreamText| to true.
shakaDemoMain.configure('streaming.alwaysStreamText', true);
shakaDemoMain.remakeHash();
}
// Enabling/disabling Shaka Controls will change how other controls in
// the config work, so reload the page.
this.reloadAndSaveState_();
});
// TODO: Re-add the tooltipMessage of 'Takes effect next load.' once we
// are ready to add ALL of the tooltip messages.
if (!shakaDemoMain.getNativeControlsEnabled()) {
this.latestInput_.input().checked = true;
}
// shaka.log is not set if logging isn't enabled.
// I.E. if using the release version of shaka.
if (!shaka['log']) return;
// Access shaka.log using bracket syntax because shaka.log is not exported.
// Exporting the logging methods proved to be a bad solution, both in terms
// of difficulty and in terms of what changes it would require of the
// architectural design of Shaka Player, so this non-type-safe solution is
// the best remaining way to get the Closure compiler to compile this
// method.
const Level = shaka['log']['Level'];
const setLevel = shaka['log']['setLevel'];
const logLevels = {
'info': 'Info', 'debug': 'Debug', 'v': 'Verbose', 'vv': 'Very Verbose'};
const onChange = (input) => {
switch (input.value) {
case 'info':
setLevel(Level['INFO']);
break;
case 'debug':
setLevel(Level['DEBUG']);
break;
case 'vv':
setLevel(Level['V2']);
break;
case 'v':
setLevel(Level['V1']);
break;
}
shakaDemoMain.remakeHash();
};
this.addSelectInput_('Log Level', logLevels, onChange);
const input = this.latestInput_.input();
switch (shaka['log']['currentLevel']) {
case Level['DEBUG']: input.value = 'debug'; break;
case Level['V2']: input.value = 'vv'; break;
case Level['V1']: input.value = 'v'; break;
default: input.value = 'info'; break;
}
}
/**
* @param {string} suffix
* @return {string}
* @private
*/
resolveExternLink_(suffix) {
return '../docs/api/shakaExtern.html#' + suffix;
}
/**
* @param {string} name
* @param {?string} docLink
* @return {!ShakaDemoConfig}
* @private
*/
addSection_(name, docLink) {
const style = name ?
ShakaDemoInputContainer.Style.ACCORDION :
ShakaDemoInputContainer.Style.VERTICAL;
this.sections_.push(new ShakaDemoInputContainer(
this.container_, name, style, docLink));
return this;
}
/**
* @param {string} name
* @param {string} valueName
* @param {string=} tooltipMessage
* @return {!ShakaDemoConfig}
* @private
*/
addBoolInput_(name, valueName, tooltipMessage) {
const onChange = (input) => {
shakaDemoMain.configure(valueName, input.checked);
shakaDemoMain.remakeHash();
};
this.addCustomBoolInput_(name, onChange, tooltipMessage);
if (shakaDemoMain.getCurrentConfigValue(valueName)) {
this.latestInput_.input().checked = true;
}
this.addedFields_.add(valueName);
return this;
}
/**
* @param {string} name
* @param {function(!Element)} onChange
* @param {string=} tooltipMessage
* @return {!ShakaDemoConfig}
* @private
*/
addCustomBoolInput_(name, onChange, tooltipMessage) {
this.createRow_(name, tooltipMessage);
this.latestInput_ = new ShakaDemoBoolInput(
this.getLatestSection_(), name, onChange);
return this;
}
/**
* @param {string} name
* @param {string} valueName
* @param {string=} tooltipMessage
* @return {!ShakaDemoConfig}
* @private
*/
addTextInput_(name, valueName, tooltipMessage) {
const onChange = (input) => {
shakaDemoMain.configure(valueName, input.value);
shakaDemoMain.remakeHash();
};
this.addCustomTextInput_(name, onChange, tooltipMessage);
this.latestInput_.input().value =
shakaDemoMain.getCurrentConfigValue(valueName);
this.addedFields_.add(valueName);
return this;
}
/**
* @param {string} name
* @param {function(!Element)} onChange
* @param {string=} tooltipMessage
* @return {!ShakaDemoConfig}
* @private
*/
addCustomTextInput_(name, onChange, tooltipMessage) {
this.createRow_(name, tooltipMessage);
this.latestInput_ = new ShakaDemoTextInput(
this.getLatestSection_(), name, onChange);
return this;
}
/**
* @param {string} name
* @param {string} valueName
* @param {boolean=} canBeDecimal
* @param {boolean=} canBeZero
* @param {boolean=} canBeUnset
* @param {string=} tooltipMessage
* @return {!ShakaDemoConfig}
* @private
*/
addNumberInput_(name, valueName, canBeDecimal = false, canBeZero = true,
canBeUnset = false, tooltipMessage) {
const onChange = (input) => {
shakaDemoMain.resetConfiguration(valueName);
shakaDemoMain.remakeHash();
if (input.value == 'Infinity') {
shakaDemoMain.configure(valueName, Infinity);
shakaDemoMain.remakeHash();
return;
}
if (input.value == '' && canBeUnset) {
return;
}
const valueAsNumber = Number(input.value);
if (valueAsNumber == 0 && !canBeZero) {
return;
}
if (!isNaN(valueAsNumber)) {
if (Math.floor(valueAsNumber) != valueAsNumber && !canBeDecimal) {
return;
}
shakaDemoMain.configure(valueName, valueAsNumber);
shakaDemoMain.remakeHash();
}
};
this.createRow_(name, tooltipMessage);
this.latestInput_ = new ShakaDemoNumberInput(
this.getLatestSection_(), name, onChange, canBeDecimal, canBeZero,
canBeUnset);
this.latestInput_.input().value =
shakaDemoMain.getCurrentConfigValue(valueName);
if (isNaN(Number(this.latestInput_.input().value)) && canBeUnset) {
this.latestInput_.input().value = '';
}
this.addedFields_.add(valueName);
return this;
}
/**
* @param {string} name
* @param {!Array.<string>} values
* @param {function(!Element)} onChange
* @param {string=} tooltipMessage
* @return {!ShakaDemoConfig}
* @private
*/
addDatalistInput_(name, values, onChange, tooltipMessage) {
this.createRow_(name, tooltipMessage);
this.latestInput_ = new ShakaDemoDatalistInput(
this.getLatestSection_(), name, onChange, values);
return this;
}
/**
* @param {string} name
* @param {!Object.<string, string>} values
* @param {function(!Element)} onChange
* @param {string=} tooltipMessage
* @return {!ShakaDemoConfig}
* @private
*/
addSelectInput_(name, values, onChange, tooltipMessage) {
this.createRow_(name, tooltipMessage);
this.latestInput_ = new ShakaDemoSelectInput(
this.getLatestSection_(), name, onChange, values);
return this;
}
/**
* @param {string} name
* @param {string=} tooltipMessage
* @private
*/
createRow_(name, tooltipMessage) {
this.getLatestSection_().addRow(name, tooltipMessage || null);
}
/**
* Checks for config values that do not have corresponding fields.
* @private
*/
checkSectionCompleteness_() {
const configPrimitives = new Set(['number', 'string', 'boolean']);
/**
* Recursively checks all of the sections of the config object.
* @param {!Object} section
* @param {string} accumulatedName
*/
const check = (section, accumulatedName) => {
for (const key in section) {
const name = (accumulatedName) ? (accumulatedName + '.' + key) : (key);
const value = section[key];
if (configPrimitives.has(typeof value)) {
if (!this.addedFields_.has(name)) {
console.warn('WARNING: Does not have config field for ' + name);
}
} else {
// It's a sub-section.
check(value, name);
}
}
};
check(shakaDemoMain.getConfiguration(), '');
}
/**
* Checks for config fields that point to invalid/obsolete config values.
* @private
*/
checkSectionValidity_() {
for (const field of this.addedFields_) {
const value = shakaDemoMain.getCurrentConfigValue(field);
if (value == undefined) {
console.warn('WARNING: Invalid config field ' + field);
}
}
}
/**
* Gets the latest section. Results in a failed assert if there is no latest
* section.
* @return {!ShakaDemoInputContainer}
* @private
*/
getLatestSection_() {
goog.asserts.assert(this.sections_.length > 0,
'Must have at least one section.');
return this.sections_[this.sections_.length - 1];
}
}
document.addEventListener('shaka-main-loaded', ShakaDemoConfig.init);
-222
View File
@@ -1,222 +0,0 @@
/**
* @license
* Copyright 2016 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @fileoverview Shaka Player demo, main section.
*
* @suppress {visibility} to work around compiler errors until we can
* refactor the demo into classes that talk via public method. TODO
*/
/** @suppress {duplicate} */
var shakaDemo = shakaDemo || {}; // eslint-disable-line no-var
/** @private */
shakaDemo.setupConfiguration_ = function() {
document.getElementById('smallGapLimit').addEventListener(
'input', shakaDemo.onGapInput_);
document.getElementById('jumpLargeGaps').addEventListener(
'change', shakaDemo.onJumpLargeGapsChange_);
document.getElementById('preferredAudioLanguage').addEventListener(
'input', shakaDemo.onConfigInput_);
document.getElementById('preferredTextLanguage').addEventListener(
'input', shakaDemo.onConfigInput_);
document.getElementById('preferredUILanguage').addEventListener(
'input', shakaDemo.onConfigInput_);
document.getElementById('preferredAudioChannelCount').addEventListener(
'input', shakaDemo.onConfigInput_);
document.getElementById('showNative').addEventListener(
'change', shakaDemo.onNativeChange_);
document.getElementById('enableAdaptation').addEventListener(
'change', shakaDemo.onAdaptationChange_);
document.getElementById('logLevelList').addEventListener(
'change', shakaDemo.onLogLevelChange_);
document.getElementById('enableLoadOnRefresh').addEventListener(
'change', shakaDemo.onLoadOnRefreshChange_);
document.getElementById('drmSettingsVideoRobustness').addEventListener(
'input', shakaDemo.onDrmSettingsChange_);
document.getElementById('drmSettingsAudioRobustness').addEventListener(
'input', shakaDemo.onDrmSettingsChange_);
document.getElementById('availabilityWindowOverride').addEventListener(
'input', shakaDemo.onAvailabilityWindowOverrideChange_);
let robustnessSuggestions = document.getElementById('robustnessSuggestions');
if (shakaDemo.support_.drm['com.widevine.alpha']) {
let widevineSuggestions = ['SW_SECURE_CRYPTO', 'SW_SECURE_DECODE',
'HW_SECURE_CRYPTO', 'HW_SECURE_DECODE', 'HW_SECURE_ALL'];
// Add Widevine robustness suggestions if it is supported.
widevineSuggestions.forEach(function(suggestion) {
let option = document.createElement('option');
option.value = suggestion;
option.textContent = 'Widevine';
robustnessSuggestions.appendChild(option);
});
}
};
/** @private */
shakaDemo.onLoadOnRefreshChange_ = function() {
// Change the hash, to mirror this.
shakaDemo.hashShouldChange_();
};
/**
* @param {!Event} event
* @private
*/
shakaDemo.onDrmSettingsChange_ = function(event) {
// Change the hash, to mirror this.
shakaDemo.hashShouldChange_();
};
/**
* @param {!Event} event
* @private
*/
shakaDemo.onAvailabilityWindowOverrideChange_ = function(event) {
// Change the hash, to mirror this.
shakaDemo.hashShouldChange_();
};
/**
* @param {!Event} event
* @private
*/
shakaDemo.onLogLevelChange_ = function(event) {
// shaka.log is not set if logging isn't enabled.
// I.E. if using the compiled version of shaka.
if (shaka.log) {
let logLevel = event.target[event.target.selectedIndex];
switch (logLevel.value) {
case 'info':
shaka.log.setLevel(shaka.log.Level.INFO);
break;
case 'debug':
shaka.log.setLevel(shaka.log.Level.DEBUG);
break;
case 'vv':
shaka.log.setLevel(shaka.log.Level.V2);
break;
case 'v':
shaka.log.setLevel(shaka.log.Level.V1);
break;
}
// Change the hash, to mirror this.
shakaDemo.hashShouldChange_();
}
};
/**
* @param {!Event} event
* @private
*/
shakaDemo.onJumpLargeGapsChange_ = function(event) {
shakaDemo.player_.configure(({
streaming: {jumpLargeGaps: event.target.checked},
}));
// Change the hash, to mirror this.
shakaDemo.hashShouldChange_();
};
/**
* @param {!Event} event
* @private
*/
shakaDemo.onGapInput_ = function(event) {
let smallGapLimit = Number(event.target.value);
let useDefault = isNaN(smallGapLimit) || event.target.value.length == 0;
shakaDemo.player_.configure(({
streaming: {
smallGapLimit: useDefault ? undefined : smallGapLimit,
},
}));
// Change the hash, to mirror this.
shakaDemo.hashShouldChange_();
};
/**
* @param {!Event} event
* @private
*/
shakaDemo.onConfigInput_ = function(event) {
let preferredAudioChannelCount =
Number(document.getElementById('preferredAudioChannelCount').value) || 2;
shakaDemo.player_.configure(/** @type {shaka.extern.PlayerConfiguration} */({
preferredAudioLanguage:
document.getElementById('preferredAudioLanguage').value,
preferredTextLanguage:
document.getElementById('preferredTextLanguage').value,
preferredAudioChannelCount: preferredAudioChannelCount,
}));
// TODO: As an optimization, defer this change until the user stops typing.
// Currently, this is triggered on each keystroke. Combine this with
// lazy-loading of localization data, and we get a fetch on every keypress.
const uiLang = document.getElementById('preferredUILanguage').value;
shakaDemo.controls_.getLocalization().changeLocale([uiLang]);
// TODO(#1591): Support multiple language preferences
// Change the hash, to mirror this.
shakaDemo.hashShouldChange_();
};
/**
* @param {!Event} event
* @private
*/
shakaDemo.onAdaptationChange_ = function(event) {
// Update adaptation config.
shakaDemo.player_.configure({
abr: {enabled: event.target.checked},
});
// Change the hash, to mirror this.
shakaDemo.hashShouldChange_();
};
/**
* @param {!Event} event
* @private
*/
shakaDemo.onNativeChange_ = function(event) {
if (event.target.checked) {
shakaDemo.controls_.setEnabledNativeControls(true);
} else {
shakaDemo.controls_.setEnabledShakaControls(true);
}
// Update text streaming config. When we use native controls, we must always
// stream text. This is because the native controls can't send an event when
// the text display state changes, so we can't use the display state to choose
// when to stream text.
shakaDemo.player_.configure({
streaming: {alwaysStreamText: event.target.checked},
});
// Change the hash, to mirror this.
shakaDemo.hashShouldChange_();
};
+396
View File
@@ -0,0 +1,396 @@
/**
* @license
* Copyright 2016 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/** @type {?ShakaDemoCustom} */
let shakaDemoCustom;
/**
* Shaka Player demo, custom asset page layout.
*/
class ShakaDemoCustom {
/**
* Register the page configuration.
*/
static init() {
const container = shakaDemoMain.addNavButton('custom');
shakaDemoCustom = new ShakaDemoCustom(container);
}
/** @param {!Element} container */
constructor(container) {
this.dialog_ = document.createElement('dialog');
this.dialog_.classList.add('mdl-dialog');
container.appendChild(this.dialog_);
if (!this.dialog_.showModal) {
dialogPolyfill.registerDialog(this.dialog_);
}
this.assets_ = this.loadAssetInfos_();
/** @private {!Array.<!AssetCard>} */
this.assetCards_ = [];
this.savedList_ = document.createElement('div');
container.appendChild(this.savedList_);
this.remakeSavedList_();
// Add the "new" button, which shows the dialog.
const addButtonContainer = document.createElement('div');
addButtonContainer.classList.add('add-button-container');
container.appendChild(addButtonContainer);
// Style it as an MDL Floating Action Button (FAB).
const addButton = this.makeButton_('add', /* isFAB = */ true, () => {
this.showAssetDialog_(ShakaDemoAssetInfo.makeBlankAsset());
});
addButtonContainer.appendChild(addButton);
document.addEventListener('shaka-main-selected-asset-changed', () => {
this.updateSelected_();
});
}
/**
* @param {!ShakaDemoAssetInfo} assetInProgress
* @private
*/
showAssetDialog_(assetInProgress) {
// Remove buttons for any previous assets.
shaka.ui.Utils.removeAllChildren(this.dialog_);
const inputDiv = document.createElement('div');
this.dialog_.appendChild(inputDiv);
const iconDiv = document.createElement('div');
this.dialog_.appendChild(iconDiv);
// An array of inputs which have validity checks which we care about.
const inputsToCheck = [];
const commonDrmSystems = new Set([
'com.widevine.alpha',
'com.microsoft.playready',
'com.adobe.primetime',
'org.w3.clearkey',
]);
// The license server and drm system fields need to know each others
// contents, and react to each others changes, to work.
// To simplify things, this method picks out the process of setting license
// server URLs; it can be called within both fields.
let licenseServerUrlInput;
let customDrmSystemInput;
const setLicenseServerURLs = () => {
const licenseServerURL = licenseServerUrlInput.value;
const customDRMSystem = customDrmSystemInput.value;
if (licenseServerURL) {
// Make a license server entry for every common DRM plugin.
assetInProgress.licenseServers.clear();
for (const drmSystem of commonDrmSystems.values()) {
assetInProgress.licenseServers.set(drmSystem, licenseServerURL);
}
if (customDRMSystem) {
// Make a custom entry too.
assetInProgress.licenseServers.set(customDRMSystem, licenseServerURL);
}
} else {
assetInProgress.licenseServers.clear();
}
};
const containerStyle = ShakaDemoInputContainer.Style.VERTICAL;
const container = new ShakaDemoInputContainer(
inputDiv, /* headerText = */ null, containerStyle,
/* docLink = */ null);
/**
* A utility to simplify the creation of fields on the dialog.
* @param {string} name
* @param {function(!Element, !Element)} setup
* @param {function(!Element)} onChange
*/
const makeField = (name, setup, onChange) => {
container.addRow(null, null);
const input = new ShakaDemoTextInput(container, name, onChange);
input.extra().textContent = name;
setup(input.input(), input.container());
};
// Make the manifest URL field.
const manifestSetup = (input, container) => {
input.value = assetInProgress.manifestUri;
inputsToCheck.push(input);
// Make an error that shows up if you did not provide an URL.
const error = document.createElement('span');
error.classList.add('mdl-textfield__error');
error.textContent = 'Must have a manifest URL.';
container.appendChild(error);
// Add a regex that will detect empty strings.
input.required = true;
input.pattern = '^(?!([\r\n\t\f\v ]+)$).*$';
};
const manifestOnChange = (input) => {
assetInProgress.manifestUri = input.value;
};
makeField('Manifest URL', manifestSetup, manifestOnChange);
// Make the license server URL field.
const licenseSetup = (input, container) => {
licenseServerUrlInput = input;
const drmSystems = assetInProgress.licenseServers.keys();
// Custom assets have only a single license server URL, no matter how
// many key systems they have. Thus, it's safe to say that the license
// server URL associated with the first key system is the asset's
// over-all license server URL.
const drmSystem = drmSystems.next();
if (drmSystem && drmSystem.value) {
input.value = assetInProgress.licenseServers.get(drmSystem.value);
}
};
const licenseOnChange = (input) => {
setLicenseServerURLs();
};
makeField('Custom License Server URL', licenseSetup, licenseOnChange);
// Make the license certificate URL field.
const certSetup = (input, container) => {
if (assetInProgress.certificateUri) {
input.value = assetInProgress.certificateUri;
}
};
const certOnChange = (input) => {
assetInProgress.certificateUri = input.value;
};
makeField('Custom License Certificate URL', certSetup, certOnChange);
// Make the drm system field.
const drmSetup = (input, container) => {
customDrmSystemInput = input;
const drmSystems = assetInProgress.licenseServers.keys();
for (const drmSystem of drmSystems) {
if (!commonDrmSystems.has(drmSystem)) {
input.value = drmSystem;
break;
}
}
};
const drmOnChange = (input) => {
setLicenseServerURLs();
};
makeField('Custom DRM System', drmSetup, drmOnChange);
// Make the name field.
const nameSetup = (input, container) => {
input.value = assetInProgress.name;
inputsToCheck.push(input);
// Make an error that shows up if you have an empty/duplicate name.
const error = document.createElement('span');
error.classList.add('mdl-textfield__error');
error.textContent = 'Must be a unique alphanumeric name.';
container.appendChild(error);
// Make a regex that will detect duplicates.
input.required = true;
input.pattern = '^(?!( *';
for (const asset of this.assets_) {
if (asset == assetInProgress) {
// If editing an existing asset, it's okay if the name doesn't change.
continue;
}
input.pattern += '|' + asset.name;
}
input.pattern += ')$)[a-zA-Z0-9 ]*$';
};
const nameOnChange = (input) => {
assetInProgress.name = input.value;
};
makeField('Name', nameSetup, nameOnChange);
// Make the icon field.
const iconSetup = (input, container) => {
if (assetInProgress.iconUri) {
input.value = assetInProgress.iconUri;
const img = document.createElement('IMG');
img.src = input.value;
iconDiv.appendChild(img);
}
};
const iconOnChange = (input) => {
shaka.ui.Utils.removeAllChildren(iconDiv);
assetInProgress.iconUri = input.value;
if (input.value) {
const img = document.createElement('IMG');
img.src = input.value;
iconDiv.appendChild(img);
}
};
makeField('Icon URL', iconSetup, iconOnChange);
// Create the buttons at the bottom of the dialog.
const buttonsDiv = document.createElement('tr');
inputDiv.appendChild(buttonsDiv);
buttonsDiv.appendChild(this.makeButton_('Save', /* isFAB = */ false, () => {
for (const input of inputsToCheck) {
if (!input.validity.valid) {
return;
}
}
this.assets_.add(assetInProgress);
this.saveAssetInfos_(this.assets_);
this.remakeSavedList_();
this.dialog_.close();
}));
buttonsDiv.appendChild(this.makeButton_(
'Cancel', /* isFAB = */ false, () => {
this.dialog_.close();
}));
// Update the componentHandler, to account for the new MDL elements.
componentHandler.upgradeDom();
// Show the dialog last, so that it knows where to place it.
this.dialog_.showModal();
}
/**
* @return {!Set.<!ShakaDemoAssetInfo>}
* @private
*/
loadAssetInfos_() {
const savedString = window.localStorage.getItem(ShakaDemoCustom.saveId_);
if (savedString) {
const assets = JSON.parse(savedString);
return new Set(assets.map((asset) => ShakaDemoAssetInfo.fromJSON(asset)));
}
return new Set();
}
/**
* @param {!Set.<!ShakaDemoAssetInfo>} assetInfos
* @private
*/
saveAssetInfos_(assetInfos) {
const saveId = ShakaDemoCustom.saveId_;
const assets = Array.from(assetInfos);
window.localStorage.setItem(saveId, JSON.stringify(assets));
}
/**
* @param {string} name
* @param {boolean} isFAB Should this button be styled as a Material Design
* Floating Action Button (FAB)?
* @param {function()} callback
* @return {!Element}
* @private
*/
makeButton_(name, isFAB, callback) {
const button = document.createElement('button');
if (isFAB) {
button.classList.add('mdl-button--fab');
button.classList.add('mdl-button--colored');
const icon = document.createElement('i');
icon.classList.add('material-icons');
icon.textContent = name;
button.appendChild(icon);
} else {
button.textContent = name;
button.classList.add('mdl-button--raised');
}
button.addEventListener('click', callback);
button.classList.add('mdl-button');
button.classList.add('mdl-js-button');
button.classList.add('mdl-js-ripple-effect');
return button;
}
/**
* @param {!ShakaDemoAssetInfo} asset
* @return {!AssetCard}
* @private
*/
createAssetCardFor_(asset) {
const savedList = this.savedList_;
const card = new AssetCard(savedList, asset, /* isFeatured = */ false);
card.addButton('Play', () => {
shakaDemoMain.loadAsset(asset);
this.updateSelected_();
});
// TODO: Add offline support.
// TODO: Be sure to un-store if the asset is deleted or edited.
card.addButton('Edit', () => {
this.showAssetDialog_(asset);
});
card.addButton('Delete', () => {
this.assets_.delete(asset);
this.saveAssetInfos_(this.assets_);
this.remakeSavedList_();
});
return card;
}
/**
* Updates which asset card is selected.
* @private
*/
updateSelected_() {
for (const card of this.assetCards_) {
card.selectByAsset(shakaDemoMain.selectedAsset);
}
}
/** @private */
remakeSavedList_() {
shaka.ui.Utils.removeAllChildren(this.savedList_);
if (this.assets_.size == 0) {
// Add in a message telling you what to do.
const makeMessage = (textClass, text) => {
const textElement = document.createElement('h2');
textElement.classList.add('mdl-typography--' + textClass);
// TODO: Localize these messages.
textElement.textContent = text;
this.savedList_.appendChild(textElement);
};
makeMessage('title',
'Try Shaka Player with your own content!');
makeMessage('body-2',
'Press the button below to add a custom asset.');
makeMessage('body-1',
'Custom assets will remain even after reloading the page.');
} else {
// Make asset cards for the assets.
this.assetCards_ = Array.from(this.assets_).map((asset) => {
return this.createAssetCardFor_(asset);
});
this.updateSelected_();
}
}
}
/**
* The name of the field in window.localStorage that is used to store a user's
* custom assets.
* @const {string}
*/
ShakaDemoCustom.saveId_ = 'shakaPlayerDemoSavedAssets';
document.addEventListener('shaka-main-loaded', ShakaDemoCustom.init);
-390
View File
@@ -1,390 +0,0 @@
/**
* Copyright 2016 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
html {
/* this allows containers inside this to be constrained by percent height */
height: 100%;
}
body {
/* This is explicit because Chromecast has a different default (black). */
background-color: white;
margin: 0.5em;
padding: 0.5em 1em;
font-family: Roboto, sans-serif;
font-weight: 300;
word-wrap: break-word;
}
body.noinput {
/* noinput mode. */
/* Add some additional padding so that TV overscan doesn't cut off the version
* number or other information we might need. */
padding: 2.5em 2em;
/* Constrain the body to its size minus margin and padding, so that nested
* elements can constrain to the body. */
width: 100%;
width: calc(100% - 4em);
height: 100%;
height: calc(100% - 5em);
}
a {
color: #0070b0;
text-decoration: none;
}
a:hover {
color: #0040b0;
text-decoration: underline;
}
a.disabled_link {
/* Allow pointer events, so that we can show a tooltip on hover */
cursor: not-allowed;
color: Gray;
}
#loadButton, #unloadButton {
background-color: #d04030;
border: none;
border-radius: 2px;
color: white;
font-family: Roboto, sans-serif;
font-size: 0.9em;
font-weight: 600;
margin: 0;
padding: 0.5em 0 0.5em 0;
width: 7.5em;
}
#loadButton:active, #unloadButton:active {
background-color: #e02020;
}
#loadButton:hover, #unloadButton:hover {
background-color: #e02020;
}
button[disabled] {
background-color: #888;
color: #ccc;
}
details {
margin: 1em 0 1em 0;
}
details div {
margin: 0 0 1.2em 0;
}
h1 {
border-bottom: 1px solid #ccc;
font-family: 'Roboto Condensed', sans-serif;
font-size: 2.0em;
font-weight: 600;
margin: 0 0 0.5em 0;
padding: 0 0 0.2em 0;
}
input[type=number],
input[type=text] {
font-family: Roboto, sans-serif;
font-size: 0.9em;
padding: 1px 2px 3px 6px;
height: 19px;
}
label, .label {
color: #444;
display: inline-block;
font-family: 'Roboto Condensed';
font-weight: 400;
width: 11em;
}
input[type=checkbox] + label {
width: auto;
}
p {
color: #444;
font-size: 0.9em;
font-weight: 300;
line-height: 1.6em;
margin: 0.5em 0;
}
p#compiled_links {
border-bottom: 1px solid #eee;
margin: 0 0 1.5em 0;
padding: 0 0 0.5em 0;
}
p.links {
border-bottom: 1px solid #eee;
border-top: 1px solid #eee;
margin: 1.5em 0 1.5em 0;
padding: 0.5em 0 0.5em 0;
}
select {
font-family: sans-serif;
height: 2em;
-webkit-appearance: menulist-button;
}
strong {
font-weight: 600;
}
summary {
cursor: pointer;
display: block;
font-family: 'Roboto Condensed', sans-serif;
font-size: 1.1em;
outline: none;
margin: 0 0 1em 0;
}
#assetList, #customAsset div {
margin: 0 0 1.2em 0;
/* Otherwise, this causes horizontal scrolling on narrow screens */
max-width: 80%;
}
#container {
margin: 0 auto 0 auto;
max-width: 40em;
}
#container.noinput {
/* noinput mode: use all available space */
max-width: 100%;
max-height: 100%;
width: 100%;
height: 100%;
margin: 0;
padding: 0;
}
#container.noinput .input {
/* noinput mode: hide all input fields */
display: none;
}
#container.noinput h1 {
/* noinput mode: restrict h1 to 50px tall, center it */
height: 50px;
width: 100%;
margin: 0 auto;
padding: 0;
text-align: center;
}
#container.noinput #videoContainer {
/* noinput mode: use all available space,
but also constrain to the screen size */
max-width: 100%;
max-height: 100%;
width: 100%;
height: 100%;
height: calc(100% - 50px);
margin: 0;
padding: 0;
}
body.noinput #logSection #log {
/* noinput mode: if displayed, put the logs higher up on screen, over the top
of the video. */
position: fixed;
top: 0;
left: 0;
margin: 10px;
width: auto;
height: calc(100% - 30px);
}
#customAsset {
display: none;
}
#customAsset input {
/* Otherwise, this becomes a two lines tall on narrow screens */
height: 1.4em;
}
#errorDisplay {
background-color: #d84a38;
margin: 0;
padding: 1em;
line-height: 2em;
text-align: center;
width: 100%;
display: none;
}
#errorDisplayLink {
color: white;
}
#errorDisplayCloseButton {
background-color: #d84a38;
color: white;
position: relative;
padding: 0 0;
top: 0em;
right: 0.5em;
float: right;
font-weight: bold;
text-decoration: none;
cursor: pointer;
}
#progressDiv, #offlineNameDiv {
display: none;
}
#loadButton {
margin: 0 1em 1em 0;
}
#logSection {
display: none;
}
#log {
background-color: #f4f4f4;
border: 1px solid #aaa;
color: #000;
font-family: monospace;
height: 200px;
overflow-x: hidden;
overflow-y: scroll;
padding: 5px;
}
#log div {
border-bottom: 1px solid #ddd;
line-height: 1.4em;
margin: 0;
padding: 0 0.5em;
width: 100%;
}
#log div span {
padding-right: 0.5em;
white-space: pre-wrap;
}
#videoContainer {
background-color: transparent;
}
#video {
/* height and margin needed in fullscreen mode */
width: 100%;
height: 100%;
margin: auto;
}
.errorLog {
background-color: #fee;
color: #f00;
}
.warnLog {
background-color: #ffc;
}
.infoLog {
background-color: #eff;
}
.flex {
display: flex;
}
.flex-grow {
flex-grow: 1;
}
.for-screen-readers {
/* Hide the content from sighted users, but not screen readers */
position: absolute;
left: -10000px;
top: auto;
width: 1px;
height: 1px;
overflow: hidden;
}
@media screen and (max-width: 650px) {
h1 {
font-size: 1.5em;
}
p {
font-size: 0.8em;
}
}
@media screen and (max-width: 550px) {
button.appButton {
margin: 0 0.6em 0.8em 0;
padding: 9px 0 10px 0;
}
button.appButton:active {
background-color: darkRed;
}
h1 {
font-size: 1.4em;
}
input[type=number],
input[type=text] {
font-size: 0.8em;
}
label, .label {
font-weight: 500;
margin: 0 0 0.4em 0;
}
select {
display: block;
margin: 0 1em 0.3em 0;
}
summary {
font-weight: 600;
}
}
@media screen and (max-width: 400px) {
button.appButton {
font-size: 0.8em;
margin: 0 0.4em 1em 0;
padding: 5px 0 7px 0;
}
}
+387
View File
@@ -0,0 +1,387 @@
/**
* Copyright 2016 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
@accent-background-color: #ddf;
@error-color: #d84a38;
@drawer-width: 550px;
@footer-link-color: #9e9e9e; /* copied from MDL stylesheet */
@footer-link-color-disabled: darken(@footer-link-color, 20%);
/* This is a less mixin only, rather than a CSS class. MDL has an equivalent
* class with the same name, which can be used in the application directly. */
.hidden() {
display: none;
}
.borderless {
outline: none;
}
#contents {
padding: 20px;
text-align: center;
}
.close-button {
/* Move the button to the top-right. */
position: absolute;
top: 10px;
right: 10px;
/* Give the button a round background, meant to look like the play button. */
border-radius: 50%;
width: 32px;
color: #000;
background: rgba(255, 255, 255, 0.85);
}
html, body {
/* Ensure everything has a consistent font. */
font-family: "Roboto", "Helvetica", "Arial", sans-serif;
}
/* Give the FAB a drop shadow, that expands a little bit when moused over. */
.mdl-button--fab {
filter: drop-shadow(0 2px 5px #333333);
transition: 0.2s ease-in-out;
}
.mdl-button--fab:hover {
filter: drop-shadow(0 2px 8px #333333);
}
/* Remove vertical padding on MDL text fields, but only while they are in
* the hamburger menu. */
.hamburger-menu .mdl-textfield {
padding: 0;
// The default width of 300px is a bit too wide for us.
width: 200px;
}
.hamburger-menu .mdl-textfield__label {
top: 4px;
}
.hamburger-menu .mdl-textfield__label:after {
bottom: 0;
}
.hamburger-menu .mdl-layout-title {
/* The line-height style in mdl-layout-title looks weird on narrow displays,
* so remove it in the hamburger menu. */
line-height: unset;
}
.hamburger-menu .input-container-label {
/* Give the labels for input rows a left margin. This keeps them from directly
* touching the left side of the screen, for improved readability. */
margin-left: 1em;
}
.hamburger-menu .mdl-button--raised {
/* Left-align the text content of the section header buttons. */
text-align: left;
}
/* Styles for error display. */
#error-display {
background-color: @error-color;
margin: 0;
padding: 1em;
line-height: 2em;
text-align: center;
width: 100%;
}
#error-display-link {
color: white;
}
.input-disabled {
pointer-events: none;
}
#error-display-close-button {
background-color: @error-color;
color: white;
position: relative;
padding: 0 0;
top: 0em;
right: 2em;
float: right;
font-weight: bold;
text-decoration: none;
cursor: pointer;
}
/* Styles for asset cards. */
.asset-card {
display: inline-block;
margin: 1em;
}
.asset-card-unsupported {
display: inline-block;
margin: 1em;
background-color: #ddd;
}
/* Asset icons. */
.feature-icon {
width: 24px;
height: 24px;
background-size: contain;
background-repeat: no-repeat;
display: inline-block;
/* features */
&[icon="high_definition"] {
background-image: data-uri('icons/custom_high_definition.svg');
}
&[icon="ultra_high_definition"] {
background-image: data-uri('icons/custom_ultra_high_definition.svg');
}
&[icon="subtitles"] {
background-image: data-uri('icons/baseline-subtitles-24px.svg');
}
&[icon="closed_caption"] {
background-image: data-uri('icons/baseline-closed_caption-24px.svg');
}
&[icon="live"] {
background-image: data-uri('icons/baseline-live_tv-24px.svg');
}
&[icon="trick_mode"] {
background-image: data-uri('icons/baseline-fast_forward-24px.svg');
}
&[icon="surround_sound"] {
background-image: data-uri('icons/baseline-surround_sound-24px.svg');
}
&[icon="multiple_languages"] {
background-image: data-uri('icons/baseline-language-24px.svg');
}
&[icon="audio_only"] {
background-image: data-uri('icons/baseline-audiotrack-24px.svg');
}
/* key systems */
&[icon="widevine"] {
background-image: data-uri('icons/custom_widevine.svg');
}
&[icon="clear_key"] {
background-image: data-uri('icons/custom_clear_key.svg');
}
&[icon="playready"] {
background-image: data-uri('icons/custom_playready.svg');
}
}
@media screen and (max-width: 400px) {
/* On screens less than 400px, the 330px-wide cards need to shrink.
* This makes them shrink linearly below that threshold. */
.asset-card {
width: 82.5%;
}
}
.asset-card div {
/* Override the default value of "stretch" for mdl cards. */
justify-content: center;
}
.asset-card img {
/* Icons are 300px by 210px (10:7 aspect ratio). */
width: 300px;
/* Constrain to space if necessary. */
max-width: 100%;
display: block;
margin-left: auto;
margin-right: auto;
}
.asset-card-unsupported img {
/* Set opacity to 50%, so the image is greyed out also. */
opacity: 0.5;
}
.asset-card.selected {
background-color: @accent-background-color;
}
/* Override some MDL styles to get the desired look and feel. */
.app-header {
background-color: white;
color: black;
}
@media screen and (max-width: 780px) {
/* On smaller screens, the header should expand and the elements in it should
* wrap to remain accessible. */
#nav-button-container {
height: auto;
flex-wrap: wrap;
}
/* The spacer should be hidden in this mode, so that the version number is no
* longer being forced to the right. */
.app-header .mdl-layout-spacer {
.hidden();
}
}
.significant-right-padding {
padding-right: 8em;
}
.mdl-dialog {
/* Override MDL dialog width, so that input elements don't overflow. */
width: 320px;
}
.mdl-dialog img {
width: 300px;
max-width: 100%;
}
.app-header .mdl-layout__drawer-button {
color: black;
}
.app-header .logo {
max-height: 80%;
/* Match the padding of the buttons next to the logo. */
padding: 0 16px;
}
footer li {
list-style: square outside;
}
footer a {
color: @footer-link-color;
}
footer a[disabled] {
color: @footer-link-color-disabled;
cursor: not-allowed;
}
/* Style the container that contains the "add custom assets" button. */
.add-button-container {
text-align: right;
margin: 1em;
}
.disabled-by-fail {
pointer-events: none;
user-select: none;
}
/* Style the hamburger menu (mdl drawer). */
.hamburger-menu {
/* To properly change the width of an MDL drawer, you also have to change the
* transform applied to hide it. */
width: @drawer-width;
transform: translateX(-@drawer-width);
/* If the main app page is scrollable, we don't want see "under" the drawer.
* Making the position fixed means it won't move, no matter what.
* Within the drawer itself, the drawer content can still be scrolled. */
position: fixed;
/* Constrain to the window if necessary, so that it doesn't overflow small
* mobile screens. */
max-width: 100%;
}
/* When the drawer is open, MDL sets overflow: hidden on the main content
* in order to hide the scroll bar.
*
* This also causes most of the elements on screen to jump to the right,
* though, which is very visually distracting. This overrides that style, to
* prevent that behavior.
*
* The class name mdl-layout__content is repeated twice to make the selector
* more specific than what MDL is using. */
.mdl-layout__drawer.is-visible ~ .mdl-layout__content.mdl-layout__content {
overflow: auto;
}
/* The title contains the close button, so use flex to center it.
* Also use right-padding to keep it off the right edge. */
#hamburger-menu-title {
display: flex;
align-items: center;
padding-right: 16px;
}
.mdl-layout__obfuscator.is-visible {
/* If the main app page is scrollable, we don't want see "under" the layout
* obfuscator (which grays out the app while the drawer is open).
* Making the position fixed means it won't move, no matter what. */
position: fixed;
}
/* Control the size of the video. */
#video-bar {
/* The video bar fills the horizontal space, but its height depends on the
* contents. */
width: 100%;
/* Add a little bit of padding on top, to make the video not look cropped. */
padding-top: 1em;
}
#video-container {
/* Fixed width, but height will expand based on video aspect ratio.
* Does not affect fullscreen size. */
width: 640px;
margin: auto;
/* Constrain to the window if necessary, so that it doesn't overflow small
* mobile screens. */
max-width: 100%;
}
#video {
/* Fill whatever space we're given, whether fullscreen or not. */
width: 100%;
height: 100%;
margin: auto;
}
/* Style the intermediate tooltip attach points, required for tooltips to be
* added to disabled buttons. */
.tooltip-attach-point {
display: inline-block;
}
/* Style the input containers. */
.input-container-row {
display: inline-block;
}
.input-container-style-flex {
display: flex;
flex-wrap: wrap;
}
.input-container-style-vertical {
text-align: left;
}
.input-container-style-accordion {
text-align: left;
opacity: 0;
max-height: 0px;
transition: 0.3s ease-in-out;
}
.input-container-style-accordion.show {
opacity: 1;
/* If the max-height is too high (e.g. set to 100%), the "sliding out"
* animation is too fast to make out with the eye.
* So give it a fixed maximum instead. */
max-height: 1000px;
}
.input-container-label {
padding-right: 1em;
}
@@ -19,106 +19,6 @@
/** @namespace */
let ShakaDemoUtils = {};
/**
* @param {shakaAssets.AssetInfo} asset
* @param {shaka.Player} player
*/
ShakaDemoUtils.setupAssetMetadata = function(asset, player) {
let config = /** @type {shaka.extern.PlayerConfiguration} */(
{drm: {}, manifest: {dash: {}}});
// Add config from this asset.
if (asset.licenseServers) {
config.drm.servers = asset.licenseServers;
}
if (asset.drmCallback) {
config.manifest.dash.customScheme = asset.drmCallback;
}
if (asset.clearKeys) {
config.drm.clearKeys = asset.clearKeys;
}
player.configure(config);
// Configure network filters.
let networkingEngine = player.getNetworkingEngine();
networkingEngine.clearAllRequestFilters();
networkingEngine.clearAllResponseFilters();
if (asset.licenseRequestHeaders) {
let filter = ShakaDemoUtils.addLicenseRequestHeaders_.bind(
null, asset.licenseRequestHeaders);
networkingEngine.registerRequestFilter(filter);
}
if (asset.requestFilter) {
networkingEngine.registerRequestFilter(asset.requestFilter);
}
if (asset.responseFilter) {
networkingEngine.registerResponseFilter(asset.responseFilter);
}
if (asset.extraConfig) {
player.configure(
/** @type {shaka.extern.PlayerConfiguration} */ (asset.extraConfig));
}
};
/**
* @param {!Object.<string, string>} headers
* @param {shaka.net.NetworkingEngine.RequestType} requestType
* @param {shaka.extern.Request} request
* @private
*/
ShakaDemoUtils.addLicenseRequestHeaders_ =
function(headers, requestType, request) {
if (requestType != shaka.net.NetworkingEngine.RequestType.LICENSE) return;
// Add these to the existing headers. Do not clobber them!
// For PlayReady, there will already be headers in the request.
for (let k in headers) {
request.headers[k] = headers[k];
}
};
/**
* Creates a number of asset buttons, with selection functionality.
* Clicking one of these elements will add the "selected" tag to it, and remove
* the "selected" tag from the previously selected element.
* @param {!Element} parentDiv The div to place the buttons in.
* @param {!Array.<shakaAssets.AssetInfo>} assets The assets that should be
* given buttons.
* @param {?shakaAssets.AssetInfo} selectedAsset An asset that should start out
* selected.
* @param {function(!Element, shakaAssets.AssetInfo)} layout A function that is
* called to lay out the contents of a button.
* @param {function(shakaAssets.AssetInfo)} onclick A function that is called
* when a button is clicked. This is after giving the button the "selected"
* tag.
*/
ShakaDemoUtils.createAssetButtons = function(
parentDiv, assets, selectedAsset, layout, onclick) {
let assetButtons = [];
for (let asset of assets) {
let button = document.createElement('div');
layout(button, asset);
button.onclick = () => {
onclick(asset);
for (let button of assetButtons) {
button.removeAttribute('selected');
}
button.setAttribute('selected', '');
};
parentDiv.appendChild(button);
assetButtons.push(button);
if (asset == selectedAsset) {
button.setAttribute('selected', '');
}
}
};
/**
* Goes through the various values in shaka.extern.PlayerConfiguration, and
* calls the given callback on them so that they can be stored to or read from
@@ -151,6 +51,8 @@ ShakaDemoUtils.runThroughHashParams = (callback, config) => {
// Override config values that are handled manually.
overridden.push('abr.enabled');
overridden.push('streaming.jumpLargeGaps');
overridden.push('drm.advanced');
overridden.push('drm.servers');
// Determine which config values should be given full namespace names.
// This is to remove ambiguity in situations where there are two objects in
+137
View File
@@ -0,0 +1,137 @@
/**
* @license
* Copyright 2016 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/** @type {?ShakaDemoFront} */
let shakaDemoFront;
/**
* Shaka Player demo, front page layout.
*/
class ShakaDemoFront {
/**
* Register the page configuration.
*/
static init() {
const container = shakaDemoMain.addNavButton('front');
shakaDemoFront = new ShakaDemoFront(container);
}
/** @param {!Element} container */
constructor(container) {
/** @private {!Array.<!AssetCard>} */
this.assetCards_ = [];
/** @private {!Element} */
this.messageDiv_ = document.createElement('div');
/** @private {!Element} */
this.assetCardDiv_ = document.createElement('div');
container.appendChild(this.messageDiv_);
this.makeMessage_();
container.appendChild(this.assetCardDiv_);
this.remakeAssetCards_();
document.addEventListener('shaka-main-selected-asset-changed', () => {
this.updateSelected_();
});
document.addEventListener('shaka-main-offline-progress', () => {
this.updateOfflineProgress_();
});
document.addEventListener('shaka-main-offline-changed', () => {
this.remakeAssetCards_();
});
}
/** @private */
makeMessage_() {
// Add in a message telling you what to do.
const makeMessage = (textClass, text) => {
const textElement = document.createElement('h2');
textElement.classList.add('mdl-typography--' + textClass);
// TODO: Localize these messages.
textElement.textContent = text;
this.messageDiv_.appendChild(textElement);
};
makeMessage('body-2',
'This is a demo of Google\'s Shaka Player, a JavaScript ' +
'library for adaptive video streaming.');
makeMessage('body-1',
'Choose a video to playback; more assets are available via ' +
'the search tab.');
}
/** @private */
remakeAssetCards_() {
shaka.ui.Utils.removeAllChildren(this.assetCardDiv_);
const assets = shakaAssets.testAssets.filter((asset) => {
return asset.isFeatured && !asset.disabled;
});
this.assetCards_ = assets.map((asset) => {
return this.createAssetCardFor_(asset, this.assetCardDiv_);
});
}
/**
* @param {!ShakaDemoAssetInfo} asset
* @param {!Element} container
* @return {!AssetCard}
* @private
*/
createAssetCardFor_(asset, container) {
const card = new AssetCard(container, asset, /* isFeatured = */ true);
const unsupportedReason = shakaDemoMain.getAssetUnsupportedReason(
asset, /* needOffline= */ false);
if (unsupportedReason) {
card.markAsUnsupported(unsupportedReason);
} else {
card.addButton('Play', () => {
shakaDemoMain.loadAsset(asset);
this.updateSelected_();
});
card.addStoreButton();
}
return card;
}
/**
* Updates progress bars on asset cards.
* @private
*/
updateOfflineProgress_() {
for (const card of this.assetCards_) {
card.updateProgress();
}
}
/**
* Updates which asset card is selected.
* @private
*/
updateSelected_() {
for (const card of this.assetCards_) {
card.selectByAsset(shakaDemoMain.selectedAsset);
}
}
}
document.addEventListener('shaka-main-loaded', ShakaDemoFront.init);
+4
View File
@@ -0,0 +1,4 @@
All of the icon files prefixed with "baseline_" are assets from the MDL icons.
All of the icon files prefixed with "custom_" are new assets we made.
All custom icons were made using the font "Fishmonger Cb Plain"
+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24"><path d="M0 0h24v24H0z" fill="none"/><path d="M12 3v9.28a4.39 4.39 0 0 0-1.5-.28C8.01 12 6 14.01 6 16.5S8.01 21 10.5 21c2.31 0 4.2-1.75 4.45-4H15V6h4V3h-7z"/></svg>

After

Width:  |  Height:  |  Size: 227 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24"><path d="M0 0h24v24H0z" fill="none"/><path d="M19 4H5a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h14c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2zm-8 7H9.5v-.5h-2v3h2V13H11v1c0 .55-.45 1-1 1H7c-.55 0-1-.45-1-1v-4c0-.55.45-1 1-1h3c.55 0 1 .45 1 1v1zm7 0h-1.5v-.5h-2v3h2V13H18v1c0 .55-.45 1-1 1h-3c-.55 0-1-.45-1-1v-4c0-.55.45-1 1-1h3c.55 0 1 .45 1 1v1z"/></svg>

After

Width:  |  Height:  |  Size: 397 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24"><path d="M4 18l8.5-6L4 6v12zm9-12v12l8.5-6L13 6z"/><path d="M0 0h24v24H0z" fill="none"/></svg>

After

Width:  |  Height:  |  Size: 157 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24"><path d="M0 0h24v24H0z" fill="none"/><path d="M11.99 2C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2zm6.93 6h-2.95a15.65 15.65 0 0 0-1.38-3.56A8.03 8.03 0 0 1 18.92 8zM12 4.04c.83 1.2 1.48 2.53 1.91 3.96h-3.82c.43-1.43 1.08-2.76 1.91-3.96zM4.26 14C4.1 13.36 4 12.69 4 12s.1-1.36.26-2h3.38c-.08.66-.14 1.32-.14 2 0 .68.06 1.34.14 2H4.26zm.82 2h2.95c.32 1.25.78 2.45 1.38 3.56A7.987 7.987 0 0 1 5.08 16zm2.95-8H5.08a7.987 7.987 0 0 1 4.33-3.56A15.65 15.65 0 0 0 8.03 8zM12 19.96c-.83-1.2-1.48-2.53-1.91-3.96h3.82c-.43 1.43-1.08 2.76-1.91 3.96zM14.34 14H9.66c-.09-.66-.16-1.32-.16-2 0-.68.07-1.35.16-2h4.68c.09.65.16 1.32.16 2 0 .68-.07 1.34-.16 2zm.25 5.56c.6-1.11 1.06-2.31 1.38-3.56h2.95a8.03 8.03 0 0 1-4.33 3.56zM16.36 14c.08-.66.14-1.32.14-2 0-.68-.06-1.34-.14-2h3.38c.16.64.26 1.31.26 2s-.1 1.36-.26 2h-3.38z"/></svg>

After

Width:  |  Height:  |  Size: 913 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24"><path fill="none" d="M0 0h24v24H0V0z"/><path d="M21 6h-7.59l3.29-3.29L16 2l-4 4-4-4-.71.71L10.59 6H3a2 2 0 0 0-2 2v12c0 1.1.9 2 2 2h18c1.1 0 2-.9 2-2V8a2 2 0 0 0-2-2zm0 14H3V8h18v12zM9 10v8l7-4z"/></svg>

After

Width:  |  Height:  |  Size: 266 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24"><path d="M0 0h24v24H0z" fill="none"/><path d="M20 4H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2zM4 12h4v2H4v-2zm10 6H4v-2h10v2zm6 0h-4v-2h4v2zm0-4H10v-2h10v2z"/></svg>

After

Width:  |  Height:  |  Size: 256 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24"><path d="M0 0h24v24H0z" fill="none"/><path d="M20 4H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2zM7.76 16.24l-1.41 1.41A7.909 7.909 0 0 1 4 12c0-2.05.78-4.1 2.34-5.66l1.41 1.41C6.59 8.93 6 10.46 6 12s.59 3.07 1.76 4.24zM12 16c-2.21 0-4-1.79-4-4s1.79-4 4-4 4 1.79 4 4-1.79 4-4 4zm5.66 1.66l-1.41-1.41C17.41 15.07 18 13.54 18 12s-.59-3.07-1.76-4.24l1.41-1.41A7.909 7.909 0 0 1 20 12c0 2.05-.78 4.1-2.34 5.66zM12 10c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z"/></svg>

After

Width:  |  Height:  |  Size: 553 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24"><path d="M0 0h24v24H0z" fill="none"/><path d="M19 4H5a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h14c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2z"/><text style="line-height:1.25;-inkscape-font-specification:'Fishmonger CB, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start" x="3.612" y="17.092" transform="scale(.99083 1.00926)" font-weight="400" font-size="14.409" font-family="Fishmonger CB" letter-spacing="0" word-spacing="0" fill="#fffffe" stroke-width="1.081"><tspan x="3.612" y="17.092" style="-inkscape-font-specification:'Fishmonger CB, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start">HD</tspan></text></svg>

After

Width:  |  Height:  |  Size: 832 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24"><path d="M0 0h24v24H0z" fill="none"/><path d="M5 4a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h14c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2H5zm3.053 3.389h2.521c.339 0 .514.178.514.523v.754c0 .345-.175.523-.514.523h-1.63c-.502 0-.803.282-.803.844v3.934c0 .562.275.844.84.844h1.593c.339 0 .514.178.514.523v.754c0 .345-.175.523-.514.523H8.04c-1.166 0-1.969-.83-1.969-2.017v-5.2c0-1.175.816-2.005 1.983-2.005zm4.549 0h1.066c.339 0 .502.178.502.523v3.143h.326c.25 0 .463-.166.525-.409l.754-2.914c.05-.204.2-.343.489-.343h1.117c.338 0 .6.243.5.588l-.826 2.988a1.97 1.97 0 0 1-.653.984c.301.243.54.562.653.97l.851 3.118c.1.32-.15.574-.488.574h-1.117c-.301 0-.452-.152-.502-.343l-.778-3.03a.556.556 0 0 0-.525-.394h-.326v3.258c0 .344-.163.51-.502.51h-1.066c-.34 0-.502-.166-.502-.51V7.898c0-.344.163-.51.502-.51z"/></svg>

After

Width:  |  Height:  |  Size: 854 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24"><path d="M0 0h24v24H0z" fill="none"/><path d="M5 4a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h14c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2H5zm-.064 2.75h1.212c.386 0 .573.19.573.582v3.592h2.27V7.332c0-.393.184-.582.57-.582h1.214c.386 0 .57.19.57.582v9.336c0 .393-.184.582-.57.582H9.561c-.386 0-.57-.19-.57-.582v-3.709H6.72v3.709c0 .393-.187.582-.573.582H4.936c-.386 0-.57-.19-.57-.582V7.332c0-.393.184-.582.57-.582zm8.574 0h3.885c1.327 0 2.24.946 2.24 2.27v5.933c0 1.353-.913 2.297-2.24 2.297H13.51c-.386 0-.584-.203-.584-.596V7.346c0-.393.198-.596.584-.596zm2.027 2.05c-.157 0-.256.103-.256.262v5.876c0 .16.1.261.256.261h.785c.643 0 .957-.319.957-.959V9.76c0-.64-.298-.96-.898-.96h-.844z"/></svg>

After

Width:  |  Height:  |  Size: 738 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24"><path d="M0 0h24v24H0z" fill="none"/><path d="M5 4a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h14c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2H5zm1.293 3.389h3.11c1.153 0 1.968.83 1.968 2.006v1.544c0 1.189-.802 2.02-1.969 2.02H7.873v3.129c0 .345-.163.523-.502.523H6.316c-.338 0-.513-.165-.513-.51V7.899c0-.344.164-.51.49-.51zm6.564 0h3.098c1.166 0 1.982.83 1.982 2.006v1.277c0 .651-.376 1.329-1.091 1.674.288.255.514.766.652 1.226l.678 2.465c.088.294-.088.574-.477.574h-1.168c-.288 0-.414-.126-.476-.343l-.752-3.03c-.05-.204-.29-.394-.528-.394h-.338v3.244c0 .345-.163.523-.501.523h-1.053c-.339 0-.516-.165-.516-.51V7.899c0-.344.164-.51.49-.51zm-4.76 1.8a.226.226 0 0 0-.224.23v1.75h.588c.564 0 .842-.28.842-.843v-.332c0-.575-.289-.805-.803-.805h-.402zm6.59 0c-.137 0-.25.116-.25.23v1.636h.59c.565 0 .84-.281.84-.83v-.23c0-.563-.288-.806-.803-.806h-.377z"/></svg>

After

Width:  |  Height:  |  Size: 899 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24"><path d="M0 0h24v24H0z" fill="none"/><path d="M5 4a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h14c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2H5zm2.188 2.75h2.656c.385 0 .57.19.57.582v5.803h.629c.385 0 .572.218.572.553v.916c0 .392-.187.582-.572.582h-.629v1.482c0 .393-.185.582-.57.582h-.985c-.385 0-.572-.19-.572-.582v-1.482H5.262c-.386 0-.573-.19-.573-.582v-1.208c0-.189.043-.393.1-.568L6.66 7.172c.1-.247.228-.422.527-.422zm6.058 0h1.213c.385 0 .57.203.57.596v3.578h.371a.614.614 0 0 0 .6-.465l.857-3.316c.058-.233.229-.393.557-.393h1.272c.385 0 .684.277.57.67l-.942 3.402c-.128.466-.4.829-.742 1.12.343.276.614.64.742 1.105l.971 3.549c.114.363-.171.654-.556.654h-1.272c-.343 0-.513-.174-.57-.393L16 13.41a.636.636 0 0 0-.6-.451h-.37v3.709c0 .393-.186.582-.571.582h-1.213c-.385 0-.572-.19-.572-.582V7.332c0-.393.187-.582.572-.582zM8.287 8.7l-1.47 4.435h1.47V8.699z"/></svg>

After

Width:  |  Height:  |  Size: 912 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24"><path d="M0 0h24v24H0z" fill="none"/><path d="M5 4a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h14c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2H5zm-.764 3.389h1.092c.289 0 .414.203.451.459l.678 6.746.965-6.783c.038-.243.201-.422.44-.422h1.267c.238 0 .413.179.451.422l.965 6.77.678-6.733c.037-.256.187-.46.45-.46h1.093c.288 0 .463.204.425.46l-.877 8.304c-.037.256-.2.46-.45.46H9.68c-.239 0-.4-.18-.438-.423l-.74-5.34-.74 5.34c-.038.243-.213.422-.451.422H5.14c-.251 0-.414-.203-.452-.459L3.8 7.848c-.038-.256.149-.46.437-.46zm10.069 0h1.142c.239 0 .402.165.44.382l1.203 7.104 1.203-7.104a.452.452 0 0 1 .453-.382h1.016a.45.45 0 0 1 .44.523l-1.495 8.317c-.038.255-.187.382-.426.382h-2.496c-.238 0-.402-.165-.44-.382l-1.491-8.305c-.063-.345.2-.535.45-.535z"/></svg>

After

Width:  |  Height:  |  Size: 797 B

+84 -185
View File
@@ -22,20 +22,25 @@
<meta name="mobile-web-app-capable" content="yes">
<meta name="theme-color" content="#ffffff">
<base target="_blank">
<title>Shaka Player Demo</title>
<link rel="manifest" href="app_manifest.json">
<link rel="icon" href="favicon.ico">
<link rel="stylesheet" href="demo.css">
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto">
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto+Condensed">
<!-- Load MDL, with the desired color scheme. -->
<link rel="stylesheet" href="https://code.getmdl.io/1.3.0/material.indigo-blue.min.css">
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
<script defer src="//www.gstatic.com/cv/js/sender/v1/cast_sender.js"></script>
<!-- transmuxing support is enabled by including this: -->
<script defer src="../node_modules/mux.js/dist/mux.js"></script>
<!-- MDL is enabled by including this: -->
<script defer src="https://code.getmdl.io/1.3.0/material.min.js"></script>
<!-- MDL modal dialogs are enabled by including these: -->
<script defer src="../node_modules/dialog-polyfill/dialog-polyfill.js"></script>
<link rel="stylesheet" href="../node_modules/dialog-polyfill/dialog-polyfill.css">
<!-- Datalist-like fields are enabled by including these: -->
<link rel="stylesheet" href="../node_modules/awesomplete/awesomplete.css" />
<script src="../node_modules/awesomplete/awesomplete.js" async></script>
<script>
COMPILED_JS = [
@@ -54,203 +59,97 @@ UNCOMPILED_JS = [
// Bootstrap the Shaka Player library through the Closure library.
'../third_party/closure/goog/base.js',
'../dist/deps.js',
// This is required for goog.asserts.
'../lib/debug/asserts.js',
// This file contains goog.require calls for all exported library classes.
'../shaka-player.uncompiled.js',
// Enable less, the CSS pre-processor.
'../node_modules/less/dist/less.js',
// These are the individual parts of the demo app.
'common/asset.js',
'common/assets.js',
'common/demo_utils.js',
'demo_utils.js',
'asset_card.js',
'close_button.js',
'input.js',
'input_container.js',
'tooltip.js',
'main.js',
'asset_section.js',
'configuration_section.js',
'info_section.js',
'log_section.js',
'offline_section.js',
'front.js',
'search.js',
'custom.js',
'config.js'
];
</script>
</script>
<!-- Load the compiled or uncompiled version of the code. -->
<script defer src="load.js"></script>
</head>
<body>
<div id="container">
<h1>Shaka Player <span id="version"></span></h1>
<div class="input">
<p id="compiled_links">
<a target="_self" href="?build=uncompiled" id="uncompiled_link">uncompiled</a> |
<a target="_self" href="?build=debug_compiled" id="debug_compiled_link">compiled (debug)</a> |
<a target="_self" href="?build=compiled" id="compiled_link">compiled (release)</a>
</p>
<p>This is a demo of Google's Shaka Player, a JavaScript library for
adaptive video streaming.</p>
<p>Choose an asset and tap <strong>Load</strong>.
(On Android, you may also need to press the play button on the
video.)</p>
<p class="links">
<a href="../docs/api/index.html">Documentation</a> |
<a href="https://github.com/google/shaka-player">Repo</a> |
<a href="https://www.npmjs.com/package/shaka-player">NPM</a> |
<a href="https://cdnjs.com/libraries/shaka-player">CDNJS</a>
</p>
<div class="flex">
<label for="assetList">Asset:</label>
<select id="assetList" class="flex-grow"></select>
<div id="main-layout" class="mdl-layout mdl-js-layout mdl-layout--fixed-header">
<header class="app-header mdl-layout__header">
<header class="mdl-layout__header-row" id="nav-button-container">
<img alt="Shaka Player Logo" src="shaka_logo_trans.png" class="logo" />
<button id="nav-button-front" class="mdl-button mdl-js-button mdl-js-ripple-effect should-disable-on-fail" defaultselected>HOME</button>
<button id="nav-button-search" class="mdl-button mdl-js-button mdl-js-ripple-effect should-disable-on-fail">SEARCH</button>
<button id="nav-button-custom" class="mdl-button mdl-js-button mdl-js-ripple-effect should-disable-on-fail">CUSTOM CONTENT</button>
<div class="mdl-layout-spacer"></div>
<div id="version-block">
<span id="version-string"></span>
</div>
</header>
</header>
<div class="mdl-layout__drawer hamburger-menu">
<div id="hamburger-menu-title" class="mdl-layout-title">
Shaka Player Demo Config
<div class="mdl-layout-spacer"></div>
<button id="drawer-close-button" class="mdl-button mdl-js-button mdl-button--icon should-disable-on-fail"><i class="material-icons">close</i></button>
</div>
<div id="customAsset">
<div class="flex">
<label for="manifestInput">Custom manifest:</label>
<input id="manifestInput" type="text" class="flex-grow">
</div>
<div class="flex">
<label for="licenseServerInput">Custom license server:</label>
<input id="licenseServerInput" type="text" class="flex-grow">
</div>
<div class="flex">
<label for="certificateInput">Custom license certificate URL:</label>
<input id="certificateInput" type="text" class="flex-grow">
<nav class="mdl-navigation" id="hamburger-menu-contents">
</nav>
</div>
<div id="error-display" class="hidden">
<div id="error-display-close-button">x</div>
<a id="error-display-link" href="#"></a>
</div>
<main class="mdl-layout__content" id="main-div">
<div id="video-bar" class="hidden">
<div id="video-container">
<video autoplay playsinline id="video"></video>
</div>
</div>
<div id="contents"></div>
<footer class="mdl-mega-footer">
<div class="mdl-mega-footer__middle-section">
<div>
<button id="loadButton" class="appButton">Load</button>
<button id="unloadButton" class="appButton">Unload</button>
</div>
</div>
<div id="errorDisplay">
<div id="errorDisplayCloseButton">x</div>
<a id="errorDisplayLink" href="#"></a>
</div>
<div data-shaka-player-container data-shaka-player-cast-receiver-id="7B25EC44">
<video autoplay playsinline data-shaka-player id="video"></video>
</div>
<details id="logSection">
<summary>Logs</summary>
<div id="log"></div>
</details>
<details class="input">
<summary>Configuration</summary>
<div class="flex">
<label for="preferredAudioLanguage">Preferred audio language:</label>
<input id="preferredAudioLanguage" class="flex-grow" type="text">
</div>
<div class="flex">
<label for="preferredTextLanguage">Preferred text language:</label>
<input id="preferredTextLanguage" class="flex-grow" type="text">
</div>
<div class="flex">
<label for="preferredUILanguage">Preferred UI language:</label>
<input id="preferredUILanguage" class="flex-grow" type="text">
</div>
<div class="flex">
<label for="preferredAudioChannelCount">Preferred audio channel count:</label>
<input id="preferredAudioChannelCount" type="number">
</div>
<div>
<label for="showNative">Show native browser controls:</label>
<input id="showNative" type="checkbox">
</div>
<div>
<label for="enableAdaptation">Enable adaptation:</label>
<input id="enableAdaptation" type="checkbox" checked>
</div>
<div>
<label for="enableLoadOnRefresh">Auto-load on page refresh:</label>
<input id="enableLoadOnRefresh" type="checkbox">
</div>
<div>
<label for="logToScreen">Log to the screen:</label>
<input id="logToScreen" type="checkbox">
</div>
<div>
<label for="smallGapLimit">Maximum small gap size:</label>
<input id="smallGapLimit" class="flex-grow" type="text">
</div>
<div>
<label for="jumpLargeGaps">Jump large gaps:</label>
<input id="jumpLargeGaps" type="checkbox">
</div>
<div id="logLevelListDiv" hidden>
<label for="logLevelList">Log Level:</label>
<select id="logLevelList" class="flex-grow">
<option value="info">Info</option>
<option value="debug">Debug</option>
<option value="v">Verbose</option>
<option value="vv">Very Verbose</option>
</select>
</div>
<div>
<label for="drmSettingsVideoRobustness">Video Robustness:</label>
<input id="drmSettingsVideoRobustness" type="text" class="flex-grow" list="robustnessSuggestions">
</div>
<div>
<label for="drmSettingsAudioRobustness">Audio Robustness:</label>
<input id="drmSettingsAudioRobustness" type="text" class="flex-grow" list="robustnessSuggestions">
</div>
<div>
<label for="availabilityWindowOverride">Availability Window Override:</label>
<input id="availabilityWindowOverride" type="number" step="30" min="0">
</div>
<datalist id="robustnessSuggestions"></datalist>
</details>
<details class="input">
<summary>Info</summary>
<div class="flex">
<label for="audioLanguages">Audio languages:</label>
<select id="audioLanguages" class="flex-grow"></select>
</div>
<div class="flex">
<label for="variantTracks">Video+audio track combinations:</label>
<select id="variantTracks" class="flex-grow"></select>
</div>
<div class="flex">
<label for="textLanguages">Text languages:</label>
<select id="textLanguages" class="flex-grow"></select>
</div>
<div class="flex">
<label for="textTracks">Text tracks:</label>
<select id="textTracks" class="flex-grow"></select>
</div>
<div>
<span class="label">Active resolution:</span>
<span id="videoResDebug"></span>
</div>
<div>
<span class="label">Buffered:</span>
<span id="bufferedDebug"></span>
</div>
</details>
<details id="offlineSection" class="input">
<summary>Offline</summary>
<div>
<button id="storeDeleteButton" class="appButton">Store</button>
<span id="storeDeleteHelpText"></span>
</div>
<div id="progressDiv">
<span class="label">Progress:</span>
<span id="progress">0</span>%
</div>
<div id="offlineNameDiv">
<label for="offlineName">Name:</label>
<input id="offlineName" class="flex-grow" type="text">
</div>
</details>
<div class="mdl-mega-footer__drop-down-section">
<h1 class="mdl-mega-footer__heading">PROJECT LINKS</h1>
<ul class="mdl-mega-footer__link-list">
<li><a href="../docs/api/index.html" target="_blank">Documentation</a></li>
<li><a href="https://www.apache.org/licenses/LICENSE-2.0" target="_blank">Apache License</a></li>
<li><a href="https://github.com/google/shaka-player" target="_blank">Source on GitHub</a></li>
<li><a href="https://www.npmjs.com/package/shaka-player" target="_blank">Package on NPM</a></li>
</ul>
</div>
<div class="mdl-mega-footer__drop-down-section">
<h1 class="mdl-mega-footer__heading">CDN</h1>
<ul class="mdl-mega-footer__link-list">
<li><a href="https://developers.google.com/speed/libraries/#shaka-player" target="_blank">Google Hosted Libraries</a></li>
<li><a href="https://www.jsdelivr.com/package/npm/shaka-player" target="_blank">jsDelivr</a></li>
<li><a href="https://cdnjs.com/libraries/shaka-player" target="_blank">CDNJS</a></li>
</ul>
</div>
<div class="mdl-mega-footer__drop-down-section">
<h1 class="mdl-mega-footer__heading">DEMO MODE</h1>
<ul class="mdl-mega-footer__link-list" id="compiled-links">
<li><a href="#build=compiled" id="compiled-link">Compiled (Release)</a></li>
<li><a href="#build=debug_compiled" id="debug-compiled-link">Compiled (Debug)</a></li>
<li><a href="#build=uncompiled" id="uncompiled-link">Uncompiled</a></li>
</ul>
</div>
</div>
</footer>
</main>
</div>
</body>
</html>
-356
View File
@@ -1,356 +0,0 @@
/**
* @license
* Copyright 2016 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @fileoverview Shaka Player demo, main section.
*
* @suppress {visibility} to work around compiler errors until we can
* refactor the demo into classes that talk via public method. TODO
*/
/** @suppress {duplicate} */
var shakaDemo = shakaDemo || {}; // eslint-disable-line no-var
/** @private */
shakaDemo.setupInfo_ = function() {
window.setInterval(shakaDemo.updateDebugInfo_, 125);
shakaDemo.player_.addEventListener(
'trackschanged', shakaDemo.onTracksChanged_);
shakaDemo.player_.addEventListener(
'adaptation', shakaDemo.onAdaptation_);
document.getElementById('variantTracks').addEventListener(
'change', shakaDemo.onTrackSelected_);
document.getElementById('textTracks').addEventListener(
'change', shakaDemo.onTrackSelected_);
document.getElementById('audioLanguages').addEventListener(
'change', shakaDemo.onAudioLanguageSelected_);
document.getElementById('textLanguages').addEventListener(
'change', shakaDemo.onTextLanguageSelected_);
};
/**
* @param {!Event} event
* @private
*/
shakaDemo.onTracksChanged_ = function(event) {
// Update language options first and then populate new tracks with
// respect to the chosen languages.
shakaDemo.updateLanguages_();
shakaDemo.updateVariantTracks_();
shakaDemo.updateTextTracks_();
};
/**
* @private
*/
shakaDemo.updateVariantTracks_ = function() {
let trackList = document.getElementById('variantTracks');
let langList = document.getElementById('audioLanguages');
let languageAndRole = langList.selectedIndex >= 0 ?
langList.options[langList.selectedIndex].value :
'';
let tracks = shakaDemo.player_.getVariantTracks();
tracks.sort(function(t1, t2) {
// Sort by bandwidth.
return t1.bandwidth - t2.bandwidth;
});
shakaDemo.updateTrackOptions_(trackList, tracks, languageAndRole);
};
/**
* @private
*/
shakaDemo.updateTextTracks_ = function() {
let trackList = document.getElementById('textTracks');
let langList = document.getElementById('textLanguages');
let languageAndRole = langList.selectedIndex >= 0 ?
langList.options[langList.selectedIndex].value :
'';
let tracks = shakaDemo.player_.getTextTracks();
shakaDemo.updateTrackOptions_(trackList, tracks, languageAndRole);
};
/**
* @param {Element} list
* @param {!Array.<!shaka.extern.Track>} tracks
* @param {string} languageAndRole
* @private
*/
shakaDemo.updateTrackOptions_ = function(list, tracks, languageAndRole) {
let formatters = {
variant: function(track) {
let trackInfo = '';
if (track.language) trackInfo += 'language: ' + track.language + ', ';
if (track.label) trackInfo += 'label: ' + track.label + ', ';
if (track.roles.length) {
trackInfo += 'roles: [' + track.roles.join() + '], ';
}
if (track.width && track.height) {
trackInfo += track.width + 'x' + track.height + ', ';
}
if (track.channelsCount) {
trackInfo += 'channels: ' + track.channelsCount + ', ';
}
trackInfo += track.bandwidth + ' bits/s';
return trackInfo;
},
text: function(track) {
let trackInfo = 'language: ' + track.language + ', ';
if (track.label) trackInfo += 'label: ' + track.label + ', ';
if (track.roles.length) {
trackInfo += 'roles: [' + track.roles.join() + '], ';
}
trackInfo += 'kind: ' + track.kind;
return trackInfo;
},
};
// Remove old tracks.
while (list.firstChild) {
list.removeChild(list.firstChild);
}
// Split language and role.
let res = languageAndRole.split(':');
let language = res[0];
let role = res[1] || '';
tracks = tracks.filter(function(track) {
let langMatch = track.language == language;
let roleMatch = role == '' || track.roles.includes(role);
return langMatch && roleMatch;
});
tracks.forEach(function(track) {
let option = document.createElement('option');
option.textContent = formatters[track.type](track);
option.track = track;
option.value = track.id;
option.selected = track.active;
list.appendChild(option);
});
};
/**
* @private
*/
shakaDemo.updateLanguages_ = function() {
shakaDemo.updateTextLanguages_();
shakaDemo.updateAudioLanguages_();
};
/**
* Updates options for text language selection.
* @private
*/
shakaDemo.updateTextLanguages_ = function() {
let player = shakaDemo.player_;
let list = document.getElementById('textLanguages');
let languagesAndRoles = player.getTextLanguagesAndRoles();
let tracks = player.getTextTracks();
shakaDemo.updateLanguageOptions_(list, languagesAndRoles, tracks);
};
/**
* Updates options for audio language selection.
* @private
*/
shakaDemo.updateAudioLanguages_ = function() {
let player = shakaDemo.player_;
let list = document.getElementById('audioLanguages');
let languagesAndRoles = player.getAudioLanguagesAndRoles();
let tracks = player.getVariantTracks();
shakaDemo.updateLanguageOptions_(list, languagesAndRoles, tracks);
};
/**
* @param {Element} list
* @param {!Array.<{language: string, role: string}>} languagesAndRoles
* @param {!Array.<shaka.extern.Track>} tracks
* @private
*/
shakaDemo.updateLanguageOptions_ =
function(list, languagesAndRoles, tracks) {
// Remove old options
while (list.firstChild) {
list.removeChild(list.firstChild);
}
// Using array.filter(f)[0] as an alternative to array.find(f) which is
// not supported in IE11.
let activeTracks = tracks.filter(function(track) {
return track.active == true;
});
let selectedTrack = activeTracks[0];
// Populate list with new options.
languagesAndRoles.forEach(function(langAndRole) {
let language = langAndRole.language;
let role = langAndRole.role;
let label = language;
if (role) {
label += ' (role: ' + role + ')';
}
let option = document.createElement('option');
option.textContent = label;
option.value = language + ':' + role;
let isSelected = false;
if (selectedTrack && selectedTrack.language == language) {
if (selectedTrack.roles.length) {
selectedTrack.roles.forEach(function(selectedRole) {
if (selectedRole == role) {
isSelected = true;
}
});
} else {
isSelected = true;
}
}
option.selected = isSelected;
list.appendChild(option);
});
};
/**
* @param {!Event} event
* @private
*/
shakaDemo.onAdaptation_ = function(event) {
let list = document.getElementById('variantTracks');
// Find the row for the active track and select it.
let tracks = shakaDemo.player_.getVariantTracks();
tracks.forEach(function(track) {
if (!track.active) return;
for (let i = 0; i < list.options.length; ++i) {
let option = list.options[i];
if (option.value == track.id) {
option.selected = true;
break;
}
}
});
};
/**
* @param {!Event} event
* @private
*/
shakaDemo.onTrackSelected_ = function(event) {
let list = event.target;
let option = list.options[list.selectedIndex];
let track = option.track;
let player = shakaDemo.player_;
if (list.id == 'variantTracks') {
// Disable abr manager before changing tracks.
let config = {abr: {enabled: false}};
player.configure(config);
player.selectVariantTrack(track, /* clearBuffer */ true);
} else {
player.selectTextTrack(track);
}
// Adaptation might have been changed by calling selectTrack().
// Update the adaptation checkbox.
let enableAdaptation = player.getConfiguration().abr.enabled;
document.getElementById('enableAdaptation').checked = enableAdaptation;
};
/**
* @param {!Event} event
* @private
*/
shakaDemo.onAudioLanguageSelected_ = function(event) {
let list = event.target;
let option = list.options[list.selectedIndex].value;
let player = shakaDemo.player_;
let res = option.split(':');
let language = res[0];
let role = res[1] || '';
player.selectAudioLanguage(language, role);
shakaDemo.updateVariantTracks_();
};
/**
* @param {!Event} event
* @private
*/
shakaDemo.onTextLanguageSelected_ = function(event) {
let list = event.target;
let option = list.options[list.selectedIndex].value;
let player = shakaDemo.player_;
let res = option.split(':');
let language = res[0];
let role = res[1] || '';
player.selectTextLanguage(language, role);
shakaDemo.updateTextTracks_();
};
/** @private */
shakaDemo.updateDebugInfo_ = function() {
let video = shakaDemo.video_;
document.getElementById('videoResDebug').textContent =
video.videoWidth + ' x ' + video.videoHeight;
let behind = 0;
let ahead = 0;
let currentTime = video.currentTime;
let buffered = video.buffered;
for (let i = 0; i < buffered.length; ++i) {
if (buffered.start(i) <= currentTime && buffered.end(i) >= currentTime) {
ahead = buffered.end(i) - currentTime;
behind = currentTime - buffered.start(i);
break;
}
}
document.getElementById('bufferedDebug').textContent =
'- ' + behind.toFixed(0) + 's / + ' + ahead.toFixed(0) + 's';
};
+238
View File
@@ -0,0 +1,238 @@
/**
* @license
* Copyright 2016 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Creates and contains the MDL elements of a type of input.
*/
class ShakaDemoInput {
/**
* @param {!ShakaDemoInputContainer} parentContainer
* @param {string} inputType The element type for the input object.
* @param {string} containerType The element type for the container containing
* the input object.
* @param {string} extraType The element type for the "sibling element" to the
* input object. If null, it adds no such element.
* @param {function(!Element)} onChange
*/
constructor(parentContainer, inputType, containerType, extraType, onChange) {
/** @private {!Element} */
this.container_ = document.createElement(containerType);
parentContainer.latestElementContainer.appendChild(this.container_);
/** @private {!Element} */
this.input_ = document.createElement(inputType);
this.input_.onchange = () => { onChange(this.input_); };
this.input_.id = ShakaDemoInput.generateNewId_('input');
this.container_.appendChild(this.input_);
if (parentContainer.latestTooltip) {
// Since the row isn't focusable, add the tooltip information into the
// accessibility data of the input, so it can be accessed by users using
// screen readers.
const extraInfo = document.createElement('span');
extraInfo.textContent = parentContainer.latestTooltip;
extraInfo.classList.add('hidden');
extraInfo.id = ShakaDemoInput.generateNewId_('extra-info');
this.container_.appendChild(extraInfo);
this.input_.setAttribute('aria-describedby', extraInfo.id);
}
/**
* Most MDL inputs require some sort of "sibling element" that exists at
* the same level as the input itself. These other elements are used to
* create various visual effects, such as the ripple effect.
* @private {?Element}
*/
this.extra_ = null;
if (extraType) {
this.extra_ = document.createElement(extraType);
this.container_.appendChild(this.extra_);
}
}
/** @return {!Element} */
input() {
return this.input_;
}
/** @return {!Element} */
container() {
return this.container_;
}
/** @return {?Element} */
extra() {
return this.extra_;
}
/**
* @param {string} prefix
* @return {string}
* @private
*/
static generateNewId_(prefix) {
const idNumber = ShakaDemoInput.lastId_;
ShakaDemoInput.lastId_ += 1;
return prefix + '-labeled-' + idNumber;
}
}
/** @private {number} */
ShakaDemoInput.lastId_ = 0;
/**
* Creates and contains the MDL elements of a select input.
*/
class ShakaDemoSelectInput extends ShakaDemoInput {
/**
* @param {!ShakaDemoInputContainer} parentContainer
* @param {string} name
* @param {function(!Element)} onChange
* @param {!Object.<string, string>} values
*/
constructor(parentContainer, name, onChange, values) {
super(parentContainer, 'select', 'div', 'label', onChange);
this.container_.classList.add('mdl-textfield');
this.container_.classList.add('mdl-js-textfield');
this.container_.classList.add('mdl-textfield--floating-label');
this.input_.classList.add('mdl-textfield__input');
this.extra_.classList.add('mdl-textfield__label');
this.extra_.setAttribute('for', this.input_.id);
for (let value of Object.keys(values)) {
const option = document.createElement('option');
option.textContent = values[value];
option.value = value;
this.input_.appendChild(option);
}
}
}
/**
* Creates and contains the MDL elements of a bool input.
*/
class ShakaDemoBoolInput extends ShakaDemoInput {
/**
* @param {!ShakaDemoInputContainer} parentContainer
* @param {string} name
* @param {function(!Element)} onChange
*/
constructor(parentContainer, name, onChange) {
super(parentContainer, 'input', 'label', 'span', onChange);
this.input_.type = 'checkbox';
this.container_.classList.add('mdl-switch');
this.container_.classList.add('mdl-js-switch');
this.container_.classList.add('mdl-js-ripple-effect');
this.container_.setAttribute('for', this.input_.id);
this.input_.classList.add('mdl-switch__input');
this.extra_.classList.add('mdl-switch__label');
}
}
/**
* Creates and contains the MDL elements of a text input.
*/
class ShakaDemoTextInput extends ShakaDemoInput {
/**
* @param {!ShakaDemoInputContainer} parentContainer
* @param {string} name
* @param {function(!Element)} onChange
*/
constructor(parentContainer, name, onChange) {
super(parentContainer, 'input', 'div', 'label', onChange);
this.container_.classList.add('mdl-textfield');
this.container_.classList.add('mdl-js-textfield');
this.container_.classList.add('mdl-textfield--floating-label');
this.input_.classList.add('mdl-textfield__input');
this.extra_.classList.add('mdl-textfield__label');
this.extra_.setAttribute('for', this.input_.id);
}
}
/**
* Creates and contains the MDL elements of a datalist input.
*/
class ShakaDemoDatalistInput extends ShakaDemoTextInput {
/**
* @param {!ShakaDemoInputContainer} parentContainer
* @param {string} name
* @param {function(!Element)} onChange
* @param {!Array.<string>} values
*/
constructor(parentContainer, name, onChange, values) {
super(parentContainer, name, onChange);
// This element is not literally a datalist, as those are not supported on
// all platforms (and they also have no MDL style support).
// Instead, this is using the third-party "awesomplete" module, which acts
// as a text field with autocomplete selection.
const awesomplete = new Awesomplete(this.input_);
awesomplete.list = values.slice(); // Make a local copy of the values list.
awesomplete.minChars = 0;
this.input_.addEventListener('focus', () => {
// By default, awesomplete does not show suggestions on focusing on the
// input, only on typing something.
// This manually updates the suggestions, so that they will show up.
awesomplete.evaluate();
});
}
}
/**
* Creates and contains the MDL elements of a number input.
*/
class ShakaDemoNumberInput extends ShakaDemoTextInput {
/**
* @param {!ShakaDemoInputContainer} parentContainer
* @param {string} name
* @param {function(!Element)} onChange
* @param {boolean} canBeDecimal
* @param {boolean} canBeZero
* @param {boolean} canBeUnset
*/
constructor(
parentContainer, name, onChange, canBeDecimal, canBeZero, canBeUnset) {
super(parentContainer, name, onChange);
const error = document.createElement('span');
error.classList.add('mdl-textfield__error');
this.container_.appendChild(error);
error.textContent = 'Must be a positive';
this.input_.pattern = '(Infinity|';
if (canBeZero) {
this.input_.pattern += '[0-9]*';
} else {
this.input_.pattern += '[0-9]*[1-9][0-9]*';
error.textContent += ', nonzero';
}
if (canBeDecimal) {
// TODO: Handle commas as decimal delimeters, for appropriate regions?
this.input_.pattern += '(.[0-9]+)?';
error.textContent += ' number.';
} else {
error.textContent += ' integer.';
}
this.input_.pattern += ')';
if (canBeUnset) {
this.input_.pattern += '?';
}
}
}
+198
View File
@@ -0,0 +1,198 @@
/**
* @license
* Copyright 2016 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Creates elements for containing inputs. It represents a single "section" of
* input.
* It contains a number of "rows", each of which contains an optional label and
* an input.
* It also has an optional header, which can contain style-dependent
* functionality.
*/
class ShakaDemoInputContainer {
/**
* @param {!Element} parentDiv
* @param {?string} headerText The text to be displayed by the header. If
* null, there will be no header.
* @param {!ShakaDemoInputContainer.Style} style
* @param {?string} docLink
*/
constructor(parentDiv, headerText, style, docLink) {
/** @private {!ShakaDemoInputContainer.Style} */
this.style_ = style;
/** @private {?Element} */
this.header_;
/** @private {!Element} */
this.table_ = document.createElement('div');
/** @private {?Element} */
this.latestRow_;
/** @type {?Element} */
this.latestElementContainer;
/** @type {?string} */
this.latestTooltip;
/** @private {number} */
this.numRows_ = 0;
if (headerText) {
this.createHeader_(parentDiv, headerText);
}
this.table_.classList.add(style);
if (style == ShakaDemoInputContainer.Style.ACCORDION) {
this.table_.classList.add('hidden');
}
parentDiv.appendChild(this.table_);
if (docLink) {
this.addDocLink_(this.table_, docLink);
}
}
/**
* @return {boolean} true if this is an open accordion menu, false otherwise
*/
getIsOpen() {
if (this.style_ == ShakaDemoInputContainer.Style.ACCORDION) {
return this.table_.classList.contains('show');
}
return false;
}
/** If this is an accordion menu, open it. */
open() {
if (!this.style_ == ShakaDemoInputContainer.Style.ACCORDION) {
return;
}
this.table_.classList.remove('hidden');
setTimeout(() => {
this.table_.classList.add('show');
}, /* milliseconds= */ 20);
this.header_.classList.add('mdl-button--colored');
}
/** If this is an accordion menu, close it. */
close() {
if (this.style_ != ShakaDemoInputContainer.Style.ACCORDION) {
return;
}
this.table_.classList.remove('show');
this.table_.addEventListener('transitionend', (e) => {
this.table_.classList.add('hidden');
}, {once: true});
this.header_.classList.remove('mdl-button--colored');
}
/**
* @param {!Element} parentDiv
* @param {string} headerText
* @private
*/
createHeader_(parentDiv, headerText) {
if (this.style_ == ShakaDemoInputContainer.Style.ACCORDION) {
this.header_ = document.createElement('button');
this.header_.classList.add('mdl-button--raised');
this.header_.classList.add('mdl-button');
this.header_.classList.add('mdl-js-button');
this.header_.classList.add('mdl-js-ripple-effect');
this.header_.addEventListener('click', () => {
// Show/hide the table.
if (this.getIsOpen()) {
this.close();
} else {
this.open();
}
});
} else {
this.header_ = document.createElement('h3');
}
this.header_.textContent = headerText;
parentDiv.appendChild(this.header_);
}
/**
* Creates a link that links to a section within the Shaka Player docs.
* @param {!Element} parentDiv
* @param {string} docLink
* @private
*/
addDocLink_(parentDiv, docLink) {
const link = document.createElement('a');
link.href = docLink;
link.target = '_blank';
link.classList.add('mdl-button');
link.classList.add('mdl-js-button');
link.classList.add('mdl-js-ripple-effect');
link.classList.add('mdl-button--colored');
const icon = document.createElement('i');
icon.classList.add('material-icons');
icon.textContent = 'help';
link.appendChild(icon);
parentDiv.appendChild(link);
}
/**
* Makes a row, for storing an input.
* @param {?string} labelString
* @param {?string} tooltipString
* @param {string=} rowClass
*/
addRow(labelString, tooltipString, rowClass) {
this.latestRow_ = document.createElement('div');
if (rowClass) {
this.latestRow_.classList.add(rowClass);
}
this.table_.appendChild(this.latestRow_);
const elementId = 'input-container-row-' + this.numRows_;
this.numRows_ += 1;
if (labelString) {
const label = document.createElement('label');
label.setAttribute('for', elementId);
label.classList.add('input-container-label');
const labelText = document.createElement('b');
labelText.textContent = labelString;
label.appendChild(labelText);
this.latestRow_.appendChild(label);
}
this.latestElementContainer = document.createElement('div');
this.latestRow_.appendChild(this.latestElementContainer);
this.latestElementContainer.classList.add('input-container-row');
this.latestElementContainer.id = elementId;
this.latestTooltip = tooltipString;
if (tooltipString) {
ShakaDemoTooltips.make(this.table_, this.latestRow_, tooltipString);
// Keep the row from being focused.
this.latestRow_.setAttribute('tabindex', -1);
this.latestRow_.classList.add('borderless');
}
}
}
/** @enum {string} */
ShakaDemoInputContainer.Style = {
VERTICAL: 'input-container-style-vertical',
ACCORDION: 'input-container-style-accordion',
FLEX: 'input-container-style-flex',
};
+12 -8
View File
@@ -49,19 +49,23 @@ function shakaUncompiledModeSupported() {
// NOTE: This is a quick-and-easy hack based on assumption that the old
// demo page will be replaced in the near future.
function loadCss(buildType) {
function loadSpecificCss(href, linkRel) {
var link = document.createElement('link');
link.type = 'text/css';
if (buildType == 'uncompiled') {
link.rel = 'stylesheet/less';
link.href = baseUrl + '../ui/controls.less';
} else {
link.rel = 'stylesheet';
link.href = baseUrl + '../dist/controls.css';
}
link.href = baseUrl + href;
link.rel = linkRel;
document.head.appendChild(link);
}
function loadCss(buildType) {
if (buildType == 'uncompiled') {
loadSpecificCss('../ui/controls.less', 'stylesheet/less');
loadSpecificCss('../demo/demo.less', 'stylesheet/less');
} else {
loadSpecificCss('../dist/controls.css', 'stylesheet');
loadSpecificCss('../dist/demo.css', 'stylesheet');
}
}
function importScript(src) {
var script = document.createElement('script');
-138
View File
@@ -1,138 +0,0 @@
/**
* @license
* Copyright 2016 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @fileoverview Shaka Player demo, main section.
*
* @suppress {visibility} to work around compiler errors until we can
* refactor the demo into classes that talk via public method. TODO
*/
/** @suppress {duplicate} */
var shakaDemo = shakaDemo || {}; // eslint-disable-line no-var
/** @private {!Object.<string, Function>} */
shakaDemo.originalConsoleMethods_ = {
'error': function() {},
'warn': function() {},
'info': function() {},
'log': function() {},
'debug': function() {},
};
/** @private {!Object.<string, Function>} */
shakaDemo.patchedConsoleMethods_ = {
'error': function() {},
'warn': function() {},
'info': function() {},
'log': function() {},
'debug': function() {},
};
/** @private */
shakaDemo.setupLogging_ = function() {
let logToScreen = document.getElementById('logToScreen');
let log = document.getElementById('log');
if (!shaka['log']) {
// This may be the compiled library, which has no logging by default.
logToScreen.parentElement.style.display = 'none';
return;
}
if (!Object.keys || !window.console || !console.log || !console.log.bind) {
// This may be a very old browser that we can't support anyway.
return;
}
// Store the original and to-screen versions of logging methods.
Object.keys(shakaDemo.originalConsoleMethods_).forEach(function(k) {
let original = console[k].bind(console);
let className = k + 'Log';
shakaDemo.originalConsoleMethods_[k] = original;
shakaDemo.patchedConsoleMethods_[k] = function() {
// Pass the call on to the original:
original.apply(console, arguments);
shakaDemo.formatLog_(log, className, arguments);
};
});
logToScreen.addEventListener('change', shakaDemo.onLogChange_);
// Set the initial state.
shakaDemo.onLogChange_();
};
/** @private */
shakaDemo.onLogChange_ = function() {
if (!shaka['log']) return;
let logToScreen = document.getElementById('logToScreen');
let logSection = document.getElementById('logSection');
if (logToScreen.checked) {
logSection.style.display = 'block';
logSection.open = true; // Open the details to show the logs.
for (let k in shakaDemo.patchedConsoleMethods_) {
console[k] = shakaDemo.patchedConsoleMethods_[k];
}
} else {
logSection.style.display = 'none';
for (let k in shakaDemo.originalConsoleMethods_) {
console[k] = shakaDemo.originalConsoleMethods_[k];
}
}
// Re-initialize Shaka library logging to the freshly-patched console methods.
shaka['log']['setLevel'](shaka['log']['currentLevel']);
// Change the hash, to mirror this.
shakaDemo.hashShouldChange_();
};
/**
* @param {Element} log
* @param {string} className
* @param {Arguments} logArguments
* @private
*/
shakaDemo.formatLog_ = function(log, className, logArguments) {
// Format the log and append it to the HTML:
let div = document.createElement('div');
div.className = className;
for (let i = 0; i < logArguments.length; ++i) {
let span = document.createElement('span');
let arg = logArguments[i];
let text;
if (arg === null) {
text = 'null';
} else if (arg === undefined) {
text = 'undefined';
} else {
text = arg.toString();
}
if (Array.isArray(arg) || text == '[object Object]') {
text = JSON.stringify(arg);
}
span.textContent = text;
div.appendChild(span);
}
log.appendChild(div);
log.scrollTop = log.scrollHeight;
};
+960 -748
View File
File diff suppressed because it is too large Load Diff
-293
View File
@@ -1,293 +0,0 @@
/**
* @license
* Copyright 2016 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @fileoverview Shaka Player demo, main section.
*
* @suppress {visibility} to work around compiler errors until we can
* refactor the demo into classes that talk via public method. TODO
*/
/** @suppress {duplicate} */
var shakaDemo = shakaDemo || {}; // eslint-disable-line no-var
/** @private {?HTMLOptGroupElement} */
shakaDemo.offlineOptGroup_ = null;
/** @private {boolean} */
shakaDemo.offlineOperationInProgress_ = false;
/**
* @param {boolean} canHide True to hide the progress value if there isn't an
* operation going.
* @private
*/
shakaDemo.updateButtons_ = function(canHide) {
let assetList = document.getElementById('assetList');
let inProgress = shakaDemo.offlineOperationInProgress_;
document.getElementById('progressDiv').style.display =
canHide && !inProgress ? 'none' : 'block';
let option = assetList.options[assetList.selectedIndex];
let storedContent = option.storedContent;
let supportsPersistentStateForAsset = true;
let supportsPersistentState = true;
let supportsOfflineStorage = true;
// Persistent state support only matters if the asset has DRM.
if (option.asset && option.asset.drm && option.asset.drm.length) {
supportsPersistentStateForAsset = option.asset.drm.some(function(drm) {
return shakaDemo.support_.drm[drm] &&
shakaDemo.support_.drm[drm].persistentState;
});
supportsPersistentState =
Object.keys(shakaDemo.support_.drm).some((drm) => {
return shakaDemo.support_.drm[drm] &&
shakaDemo.support_.drm[drm].persistentState;
});
}
// Note that offline assets are synthetic and do not have a "features" field.
if (option.asset && option.asset.features) {
if (!option.asset.features.includes(shakaAssets.Feature.OFFLINE)) {
// For whatever reason, this asset can't handle offline storage.
supportsOfflineStorage = false;
}
}
// Only show when the custom asset option is selected.
document.getElementById('offlineNameDiv').style.display =
option.asset ? 'none' : 'block';
let button = document.getElementById('storeDeleteButton');
button.disabled = false;
button.textContent = storedContent ? 'Delete' : 'Store';
let helpText = document.getElementById('storeDeleteHelpText');
if (inProgress) {
button.disabled = true;
helpText.textContent = 'Operation is in progress...';
} else if (!supportsPersistentState) {
button.disabled = true;
helpText.textContent = 'This browser does not support persistent licenses.';
} else if (!supportsPersistentStateForAsset) {
button.disabled = true;
helpText.textContent = 'This browser does not support persistent ' +
'licenses for any DRM system in this asset.';
} else if (option.isStored) {
button.disabled = true;
helpText.textContent = 'The asset is stored offline. ' +
'Checkout the "Offline" section in the "Asset" list';
} else if (!supportsOfflineStorage) {
button.disabled = true;
helpText.textContent = 'The asset does not support offline storage.';
} else {
helpText.textContent = '';
}
};
/** @private */
shakaDemo.setupOffline_ = function() {
document.getElementById('storeDeleteButton')
.addEventListener('click', shakaDemo.storeDeleteAsset_);
document.getElementById('assetList')
.addEventListener('change', shakaDemo.updateButtons_.bind(null, true));
shakaDemo.updateButtons_(true);
};
/**
* @return {!Promise}
* @private
*/
shakaDemo.setupOfflineAssets_ = function() {
const Storage = shaka.offline.Storage;
if (!Storage.support()) {
let section = document.getElementById('offlineSection');
section.style.display = 'none';
return Promise.resolve();
}
/** @type {!HTMLOptGroupElement} */
let group;
let assetList = document.getElementById('assetList');
if (!shakaDemo.offlineOptGroup_) {
group =
/** @type {!HTMLOptGroupElement} */ (
document.createElement('optgroup'));
shakaDemo.offlineOptGroup_ = group;
group.label = 'Offline';
assetList.appendChild(group);
} else {
group = shakaDemo.offlineOptGroup_;
}
let db = new Storage(/** @type {!shaka.Player} */ (shakaDemo.localPlayer_));
return db.list().then(function(storedContents) {
storedContents.forEach(function(storedContent) {
for (let i = 0; i < assetList.options.length; i++) {
let option = assetList.options[i];
if (option.asset &&
option.asset.manifestUri == storedContent.originalManifestUri) {
option.isStored = true;
break;
}
}
let asset = {manifestUri: storedContent.offlineUri};
let option = document.createElement('option');
option.textContent =
storedContent.appMetadata ? storedContent.appMetadata.name : '';
option.asset = asset;
option.storedContent = storedContent;
group.appendChild(option);
});
shakaDemo.updateButtons_(true);
return db.destroy();
});
};
/** @private */
shakaDemo.storeDeleteAsset_ = function() {
shakaDemo.closeError();
shakaDemo.offlineOperationInProgress_ = true;
shakaDemo.updateButtons_(false);
let assetList = document.getElementById('assetList');
let option = assetList.options[assetList.selectedIndex];
// This will use the configuration from the player, so we need to set all
// our configurations on the player and not here.
let storage = new shaka.offline.Storage(
/** @type {!shaka.Player} */ (shakaDemo.localPlayer_));
// Clear the progress display so that we will start at zero.
let percent = 0;
let progress = document.getElementById('progress');
progress.textContent = (percent * 100).toFixed(2);
let p;
if (option.storedContent) {
let offlineUri = option.storedContent.offlineUri;
let originalManifestUri = option.storedContent.originalManifestUri;
// If this is a stored demo asset, we'll need to configure the player with
// license server authentication so we can delete the offline license.
for (let i = 0; i < shakaAssets.testAssets.length; i++) {
let originalAsset = shakaAssets.testAssets[i];
if (originalManifestUri == originalAsset.manifestUri) {
shakaDemo.preparePlayer_(originalAsset);
break;
}
}
p = storage.remove(offlineUri).then(function() {
for (let i = 0; i < assetList.options.length; i++) {
let option = assetList.options[i];
if (option.asset && option.asset.manifestUri == originalManifestUri) {
option.isStored = false;
}
}
return shakaDemo.refreshAssetList_();
});
} else {
let configureCertificate = Promise.resolve();
let asset = shakaDemo.preparePlayer_(option.asset);
if (asset.certificateUri) {
configureCertificate = shakaDemo.requestCertificate_(asset.certificateUri)
.then(shakaDemo.configureCertificate_);
}
p = configureCertificate.then(function() {
let nameField = document.getElementById('offlineName').value;
let assetName = asset.name ? '[OFFLINE] ' + asset.name : null;
let metadata = {name: assetName || nameField || asset.manifestUri};
return storage.store(asset.manifestUri, metadata).then(function() {
if (option.asset) {
option.isStored = true;
}
return shakaDemo.refreshAssetList_().then(function() {
// Auto-select offline copy of asset after storing.
let group = shakaDemo.offlineOptGroup_;
for (let i = 0; i < group.childNodes.length; i++) {
let option = group.childNodes[i];
if (option.textContent == assetName) {
assetList.selectedIndex = option.index;
}
}
});
});
});
}
p.catch(function(reason) {
let error = /** @type {!shaka.util.Error} */(reason);
shakaDemo.onError_(error);
}).then(function() {
shakaDemo.offlineOperationInProgress_ = false;
shakaDemo.updateButtons_(true /* canHide */);
return storage.destroy();
});
};
/**
* @return {!Promise}
* @private
*/
shakaDemo.refreshAssetList_ = function() {
// Remove all child elements.
let group = shakaDemo.offlineOptGroup_;
while (group.firstChild) {
group.removeChild(group.firstChild);
}
return shakaDemo.setupOfflineAssets_();
};
/**
* @param {boolean} connected
* @private
*/
shakaDemo.onCastStatusChange_ = function(connected) {
if (!shakaDemo.offlineOptGroup_) {
// No offline support.
return;
}
// When we are casting, offline assets become unavailable.
shakaDemo.offlineOptGroup_.disabled = connected;
if (connected) {
let assetList = document.getElementById('assetList');
let option = assetList.options[assetList.selectedIndex];
if (option.storedContent) {
// This is an offline asset. Select something else.
assetList.selectedIndex = 0;
}
}
};
+337
View File
@@ -0,0 +1,337 @@
/**
* @license
* Copyright 2016 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/** @type {?ShakaDemoSearch} */
let shakaDemoSearch;
/**
* Shaka Player demo, feature discovery page layout.
*/
class ShakaDemoSearch {
/**
* Register the page configuration.
*/
static init() {
const container = shakaDemoMain.addNavButton('search');
shakaDemoSearch = new ShakaDemoSearch(container);
}
/** @param {!Element} container */
constructor(container) {
/** @private {!Array.<!shakaAssets.Feature>} */
this.desiredFeatures_ = [];
/** @private {?shakaAssets.Source} */
this.desiredSource_;
/** @private {?shakaAssets.KeySystem} */
this.desiredDRM_;
this.makeSearchDiv_(container);
/** @private {!Array.<!AssetCard>} */
this.assetCards_ = [];
this.resultsDiv_ = document.createElement('div');
container.appendChild(this.resultsDiv_);
this.remakeResultsDiv_();
document.addEventListener('shaka-main-selected-asset-changed', () => {
this.updateSelected_();
});
document.addEventListener('shaka-main-offline-progress', () => {
this.updateOfflineProgress_();
});
document.addEventListener('shaka-main-offline-changed', () => {
this.remakeResultsDiv_();
});
}
/**
* @param {!ShakaDemoAssetInfo} asset
* @return {!AssetCard}
* @private
*/
createAssetCardFor_(asset) {
const resultsDiv = this.resultsDiv_;
const card = new AssetCard(resultsDiv, asset, /* isFeatured = */ false);
const unsupportedReason = shakaDemoMain.getAssetUnsupportedReason(
asset, /* needOffline= */ false);
if (unsupportedReason) {
card.markAsUnsupported(unsupportedReason);
} else {
card.addButton('Play', () => {
shakaDemoMain.loadAsset(asset);
this.updateSelected_();
});
card.addStoreButton();
}
return card;
}
/**
* Updates progress bars on asset cards.
* @private
*/
updateOfflineProgress_() {
for (const card of this.assetCards_) {
card.updateProgress();
}
}
/**
* Updates which asset card is selected.
* @private
*/
updateSelected_() {
for (const card of this.assetCards_) {
card.selectByAsset(shakaDemoMain.selectedAsset);
}
}
/** @private */
remakeResultsDiv_() {
shaka.ui.Utils.removeAllChildren(this.resultsDiv_);
const assets = this.searchResults_();
this.assetCards_ = assets.map((asset) => this.createAssetCardFor_(asset));
this.updateSelected_();
}
/**
* @param {!ShakaDemoSearch.SearchTerm} term
* @param {ShakaDemoSearch.TermType} type
* @param {!Array.<!ShakaDemoSearch.SearchTerm>} others
* @private
*/
addDesiredTerm_(term, type, others) {
switch (type) {
case ShakaDemoSearch.TermType.DRM:
this.desiredDRM_ = /** @type {shakaAssets.KeySystem} */ (term);
break;
case ShakaDemoSearch.TermType.SOURCE:
this.desiredSource_ = /** @type {shakaAssets.Source} */ (term);
break;
case ShakaDemoSearch.TermType.FEATURE:
// Only this term should be in the desired features.
for (let term of others) {
const index = this.desiredFeatures_.indexOf(
/** @type {shakaAssets.Feature} */ (term));
if (index != -1) {
this.desiredFeatures_.splice(index, 1);
}
}
this.desiredFeatures_.push(/** @type {shakaAssets.Feature} */ (term));
break;
}
}
/**
* @param {!ShakaDemoSearch.SearchTerm} term
* @param {ShakaDemoSearch.TermType} type
* @private
*/
removeDesiredTerm_(term, type) {
let index;
switch (type) {
case ShakaDemoSearch.TermType.DRM:
this.desiredDRM_ = null;
break;
case ShakaDemoSearch.TermType.SOURCE:
this.desiredSource_ = null;
break;
case ShakaDemoSearch.TermType.FEATURE:
index = this.desiredFeatures_.indexOf(
/** @type {shakaAssets.Feature} */ (term));
if (index != -1) {
this.desiredFeatures_.splice(index, 1);
}
break;
}
}
/**
* Creates an input for a single search term.
* @param {!ShakaDemoInputContainer} searchContainer
* @param {!ShakaDemoSearch.SearchTerm} choice
* The term this represents.
* @param {ShakaDemoSearch.TermType} type
* The type of term that this term is.
* @private
*/
makeBooleanInput_(searchContainer, choice, type) {
// Give the container a significant amount of right padding, to make
// it clearer which toggle corresponds to which label.
searchContainer.addRow(choice, null, 'significant-right-padding');
const onChange = (input) => {
if (input.checked) {
this.addDesiredTerm_(choice, type, [choice]);
} else {
this.removeDesiredTerm_(choice, type);
}
this.remakeResultsDiv_();
// Update the componentHandler, to account for any new MDL elements
// added. Notably, tooltips.
componentHandler.upgradeDom();
};
// eslint-disable-next-line no-new
new ShakaDemoBoolInput(searchContainer, choice, onChange);
}
/**
* Creates an input for a group of related but mutually-exclusive search
* terms.
* @param {!ShakaDemoInputContainer} searchContainer
* @param {string} name
* @param {!Array.<!ShakaDemoSearch.SearchTerm>} choices
* An array of the terms in this term group.
* @param {ShakaDemoSearch.TermType} type
* The type of term that this term group contains. All of the
* terms in the "choices" array must be of this type.
* @private
*/
makeSelectInput_(searchContainer, name, choices, type) {
searchContainer.addRow(null, null);
const nullOption = 'Unspecified';
const valuesObject = {};
for (let term of choices) {
if (type == 'DRM') {
// The internal names of the keysystems aren't very readable, so use a
// common name instead.
// However, as we are basing this off of the key name, we have to remove
// any underscores, so that the user isn't presented with a button
// labeled CLEAR_KEY.
for (const key in shakaAssets.KeySystem) {
if (shakaAssets.KeySystem[key] == term) {
// TODO: It'd be better to have some table of "translations",
// instead of making a readable name here with string operations.
valuesObject[term] = key.split('_').map((name) => {
// Return everything but first character to lower-case.
return name[0] + name.substr(1).toLowerCase();
}).join(' ');
break;
}
}
} else {
valuesObject[term] = term;
}
}
valuesObject[nullOption] = nullOption;
let lastValue = nullOption;
const onChange = (input) => {
if (input.value != nullOption) {
this.addDesiredTerm_(input.value, type, choices);
} else {
this.removeDesiredTerm_(lastValue, type);
}
lastValue = input.value;
this.remakeResultsDiv_();
// Update the componentHandler, to account for any new MDL elements added.
// Notably, tooltips.
componentHandler.upgradeDom();
};
const input = new ShakaDemoSelectInput(
searchContainer, name, onChange, valuesObject);
input.extra().textContent = name;
input.input().value = nullOption;
}
/**
* @param {!Element} container
* @private
*/
makeSearchDiv_(container) {
const Feature = shakaAssets.Feature;
const FEATURE = ShakaDemoSearch.TermType.FEATURE;
const DRM = ShakaDemoSearch.TermType.DRM;
const SOURCE = ShakaDemoSearch.TermType.SOURCE;
// Core term inputs.
const coreContainer = new ShakaDemoInputContainer(
container, /* headerText = */ null, ShakaDemoInputContainer.Style.FLEX,
/* docLink = */ null);
this.makeSelectInput_(coreContainer, 'Manifest',
[Feature.DASH, Feature.HLS], FEATURE);
this.makeSelectInput_(coreContainer, 'Container',
[Feature.MP4, Feature.MP2TS, Feature.WEBM], FEATURE);
this.makeSelectInput_(coreContainer, 'DRM',
Object.values(shakaAssets.KeySystem), DRM);
this.makeSelectInput_(coreContainer, 'Source',
Object.values(shakaAssets.Source).filter((term) => term != 'Custom'),
SOURCE);
// Special terms.
const containerStyle = ShakaDemoInputContainer.Style.FLEX;
const specialContainer = new ShakaDemoInputContainer(
container, /* headerText = */ null, containerStyle,
/* docLink = */ null);
this.makeBooleanInput_(specialContainer, Feature.LIVE, FEATURE);
this.makeBooleanInput_(specialContainer, Feature.HIGH_DEFINITION, FEATURE);
this.makeBooleanInput_(specialContainer, Feature.XLINK, FEATURE);
this.makeBooleanInput_(specialContainer, Feature.SUBTITLES, FEATURE);
this.makeBooleanInput_(specialContainer, Feature.TRICK_MODE, FEATURE);
this.makeBooleanInput_(specialContainer, Feature.SURROUND, FEATURE);
this.makeBooleanInput_(specialContainer, Feature.OFFLINE, FEATURE);
this.makeBooleanInput_(specialContainer, Feature.STORED, FEATURE);
}
/**
* @return {!Array.<!ShakaDemoAssetInfo>}
* @private
*/
searchResults_() {
return shakaAssets.testAssets.filter((asset) => {
if (asset.disabled) {
return false;
}
if (this.desiredDRM_ && !asset.drm.includes(this.desiredDRM_)) {
return false;
}
if (this.desiredSource_ && asset.source != this.desiredSource_) {
return false;
}
for (let feature of this.desiredFeatures_) {
if (feature == shakaAssets.Feature.STORED) {
if (!asset.isStored()) {
return false;
}
} else if (!asset.features.includes(feature)) {
return false;
}
}
return true;
});
}
}
/** @typedef {shakaAssets.Feature|shakaAssets.Source} */
ShakaDemoSearch.SearchTerm;
/** @enum {string} */
ShakaDemoSearch.TermType = {
FEATURE: 'Feature',
DRM: 'DRM',
SOURCE: 'Source',
};
document.addEventListener('shaka-main-loaded', ShakaDemoSearch.init);
+10 -4
View File
@@ -22,12 +22,12 @@
/**
* The name of the cache for this version of the application.
* This should be updated when old, unneded application resources could be
* This should be updated when old, unneeded application resources could be
* cleaned up by a newer version of the application.
*
* @const {string}
*/
const CACHE_NAME = 'shaka-player-v2';
const CACHE_NAME = 'shaka-player-v2.5+';
/**
@@ -62,8 +62,7 @@ const CRITICAL_RESOURCES = [
'.', // This resolves to the page.
'index.html', // Another way to access the page.
'app_manifest.json',
'demo.css',
'shaka_logo_trans.png',
// These CSS files will reference Web Fonts which will be cached on sight
// thanks to CACHEABLE_URL_PREFIXES below. This means we don't have to
@@ -77,6 +76,11 @@ const CRITICAL_RESOURCES = [
'../dist/shaka-player.ui.js',
'../dist/demo.compiled.js',
'../dist/controls.css',
'../dist/demo.css',
// These files are required for the demo to include MDL.
'https://code.getmdl.io/1.3.0/material.indigo-blue.min.css',
'https://code.getmdl.io/1.3.0/material.min.js',
];
@@ -117,6 +121,8 @@ const CACHEABLE_URL_PREFIXES = [
// Google Web Fonts should be cached when first seen, without being explicitly
// listed, and should be preferred from cache for speed.
'https://fonts.gstatic.com/',
// Same goes for asset icons.
'https://storage.googleapis.com/shaka-asset-icons/',
];
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

+49
View File
@@ -0,0 +1,49 @@
/**
* @license
* Copyright 2016 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Creates and contains the MDL elements of a tooltip.
*/
class ShakaDemoTooltips {
/**
* @param {!Element} parentDiv
* @param {!Element} labeledElement
* @param {string} message
*/
static make(parentDiv, labeledElement, message) {
labeledElement.id = ShakaDemoTooltips.generateNewId_();
const tooltip = document.createElement('div');
tooltip.classList.add('mdl-tooltip');
tooltip.classList.add('mdl-tooltip--large');
tooltip.setAttribute('for', labeledElement.id);
tooltip.textContent = message;
parentDiv.appendChild(tooltip);
}
/**
* @return {string}
* @private
*/
static generateNewId_() {
const idNumber = ShakaDemoTooltips.lastId_;
ShakaDemoTooltips.lastId_ += 1;
return 'tooltip-labeled-' + idNumber;
}
}
/** @private {number} */
ShakaDemoTooltips.lastId_ = 0;
+2
View File
@@ -9,3 +9,5 @@
![Shaka offline diagram](offline.gv.png)
![PresentationTimeline diagram](timeline.svg)
![Demo page architecture](newdemo.gv.png)
+83
View File
@@ -0,0 +1,83 @@
# Generate png with: dot -Tpng -O newdemo.gv
digraph new_demo {
label = "Shaka Player New Demo Page Architecture Diagram"
node [ style = filled ]
"ShakaDemoMain" [ shape = oval ]
"Shaka Player" [ shape = Mdiamond ]
subgraph cluster_new_demo_panels {
label = "Panels"
style = filled
color = aliceblue
shape = rectangle
"ShakaDemoConfig" [ shape = oval ]
"ShakaDemoCustom" [ shape = oval ]
"ShakaDemoSearch" [ shape = oval ]
"ShakaDemoFront" [ shape = oval ]
}
subgraph cluster_new_demo_common {
label = "Common"
style = filled
color = lavenderblush
shape = rectangle
"ShakaDemoUtils" [ shape = Msquare ]
"ShakaAssets" [ shape = Msquare ]
"ShakaDemoAssetInfo" [ shape = Msquare ]
}
subgraph cluster_new_demo_utilities {
label = "Misc Utilities"
style = filled
color = palegoldenrod
shape = rectangle
"ShakaDemoInput" [ shape = Msquare ]
"ShakaDemoInputContainer" [ shape = Msquare ]
"AssetCard" [ shape = mSquare ]
}
# Dependencies on Shaka Player
ShakaDemoMain -> "Shaka Player"
# Dependencies on ShakaDemoMain
ShakaDemoConfig -> ShakaDemoMain
ShakaDemoCustom -> ShakaDemoMain
ShakaDemoSearch -> ShakaDemoMain
ShakaDemoFront -> ShakaDemoMain
# Dependencies on ShakaDemoUtils
ShakaDemoMain -> ShakaDemoUtils [ style = dotted ]
# Dependencies on ShakaAssets
ShakaDemoMain -> ShakaAssets [ style = dotted ]
ShakaDemoSearch -> ShakaAssets [ style = dotted ]
ShakaDemoFront -> ShakaAssets [ style = dotted ]
# Dependencies on ShakaDemoAssetInfo
ShakaAssets -> ShakaDemoAssetInfo [ style = dotted ]
AssetCard -> ShakaDemoAssetInfo [ style = dotted ]
ShakaDemoCustom -> ShakaDemoAssetInfo [ style = dotted ]
# Dependencies on ShakaDemoInput
ShakaDemoCustom -> ShakaDemoInput [ style = dotted ]
ShakaDemoConfig -> ShakaDemoInput [ style = dotted ]
ShakaDemoSearch -> ShakaDemoInput [ style = dotted ]
ShakaDemoInputContainer -> ShakaDemoInput
# Dependencies on ShakaDemoInputContainer
ShakaDemoCustom -> ShakaDemoInputContainer [ style = dotted ]
ShakaDemoConfig -> ShakaDemoInputContainer [ style = dotted ]
ShakaDemoSearch -> ShakaDemoInputContainer [ style = dotted ]
# Dependencies on AssetCard
ShakaDemoSearch -> AssetCard [ style = dotted ]
ShakaDemoCustom -> AssetCard [ style = dotted ]
ShakaDemoFront -> AssetCard [ style = dotted ]
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 118 KiB

+5
View File
@@ -25,3 +25,8 @@
alt="PresentationTimeline diagram"
style="max-width: 100%">
<img
src="../design/newdemo.gv.png"
alt="Demo page architecture"
style="max-width: 100%">
+7
View File
@@ -79,8 +79,15 @@ function onUIErrorEvent(errorEvent) {
// Handle UI error
}
function initFailed() {
// Handle the failure to load
}
// Listen to the custom shaka-ui-loaded event, to wait until the UI is loaded.
document.addEventListener('shaka-ui-loaded', init);
// Listen to the custom shaka-ui-load-failed event, in case Shaka Player fails
// to load (e.g. due to lack of browser support).
document.addEventListener('shaka-ui-load-failed, initFailed);
```
+36
View File
@@ -0,0 +1,36 @@
/**
* @license
* Copyright 2016 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @fileoverview Externs for Awesomplete methods.
* @externs
*/
/**
* @param {!Element} input
* @constructor
*/
const Awesomplete = function(input) {};
/** @type {!Array.<string>} */
Awesomplete.prototype.list;
/** @type {number} */
Awesomplete.prototype.minChars;
Awesomplete.prototype.evaluate = function() {};
+33
View File
@@ -0,0 +1,33 @@
/**
* @license
* Copyright 2016 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @fileoverview Externs for dialog-polyfill.js methods.
* @externs
*/
/** @const */
const dialogPolyfill = {};
/**
* @param {!Element} dialog
* @const
*/
dialogPolyfill.registerDialog = function(dialog) {};
+37
View File
@@ -0,0 +1,37 @@
/**
* @license
* Copyright 2016 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @fileoverview Externs for MDL methods.
* @externs
*/
/** @const */
const componentHandler = {};
/** @const */
componentHandler.upgradeDom = function() {};
/** @constructor */
const MaterialLayout = function() {};
MaterialLayout.prototype.toggleDrawer = function() {};
/** @const {?MaterialLayout} */
Element.prototype.MaterialLayout;
+1
View File
@@ -99,6 +99,7 @@ module.exports = function(config) {
'test/test/util/*.js',
// list of test assets next
'demo/common/asset.js',
'demo/common/assets.js',
// if --test-custom-asset *is not* present, we will add unit tests.
+2
View File
@@ -128,6 +128,8 @@ shaka.cast.CastUtils.PlayerGetterMethods = {
// TODO(vaage): Remove |getManifestUri| references in v2.6.
// NOTE: The 'getManifestUri' property is not proxied, as CastProxy has a
// handler for it.
// NOTE: The 'getManifestParserFactory' property is not proxied, as it would
// not serialize.
'getPlaybackRate': 2,
'getTextLanguages': 2,
'getTextLanguagesAndRoles': 2,
+12
View File
@@ -3571,6 +3571,18 @@ shaka.Player.prototype.getManifest = function() {
};
/**
* Get the type of manifest parser that the player is using. If the player has
* not loaded any content, this will return |null|.
*
* @return {?shaka.extern.ManifestParser.Factory}
* @export
*/
shaka.Player.prototype.getManifestParserFactory = function() {
return this.parser_ ? this.parser_.constructor : null;
};
/**
* @param {shaka.extern.Period} period
* @param {shaka.extern.Variant} variant
+2
View File
@@ -12,10 +12,12 @@
],
"devDependencies": {
"array-includes": "~3.0.3",
"awesomplete": "~1.1.1",
"babel-core": "^6.26.0",
"babel-polyfill": "^6.26.0",
"babel-preset-env": "^1.6.1",
"cajon": "^0.4.4",
"dialog-polyfill": "^0.4.10",
"es6-promise-polyfill": "^1.2.0",
"es6-shim": "~0.35.3",
"eslint": "^4.14.0",
+1
View File
@@ -31,6 +31,7 @@ describe('CastUtils', function() {
'getManifest', // Too large to proxy
// TODO(vaage): Remove |getManifestUri| references in v2.6.
'getManifestUri', // Handled specially by CastProxy
'getManifestParserFactory', // Would not serialize.
// Test helper methods (not @export'd)
'createDrmEngine',
+15 -8
View File
@@ -80,6 +80,7 @@ describe('Player', () => {
});
describe('plays', () => {
/** @param {!ShakaDemoAssetInfo} asset */
function createAssetTest(asset) {
if (asset.disabled) return;
@@ -211,14 +212,20 @@ describe('Player', () => {
/** @type {Object} */
const licenseServers = getClientArg('testCustomLicenseServer');
const keySystems = Object.keys(licenseServers || {});
const asset = {
source: 'command line',
name: 'custom',
manifestUri: testCustomAsset,
focus: true,
licenseServers: licenseServers,
drm: keySystems,
};
const asset = new ShakaDemoAssetInfo(
/* name= */ 'custom',
/* iconUri= */ '',
/* manifestUri= */ testCustomAsset,
/* source= */ shakaAssets.Source.CUSTOM);
if (keySystems.length) {
for (let keySystem of keySystems) {
asset.addKeySystem(/** @type {!shakaAssets.KeySystem} */ (keySystem));
const licenseServer = licenseServers[keySystem];
if (licenseServer) {
asset.addLicenseServer(keySystem, licenseServer);
}
}
}
createAssetTest(asset);
} else {
// No custom assets? Create a test for each asset in the demo asset list.
+5 -2
View File
@@ -80,8 +80,11 @@ var pending = function(message) {};
jasmine.Matchers.prototype.not;
/** @param {*} value */
jasmine.Matchers.prototype.toBe = function(value) {};
/**
* @param {*} value
* @param {string=} message
*/
jasmine.Matchers.prototype.toBe = function(value, message) {};
/**
+1 -3
View File
@@ -49,9 +49,7 @@ shaka.ui.LanguageUtils = class {
langMenu, 'shaka-back-to-overflow-button');
// 2. Remove everything
while (langMenu.firstChild) {
langMenu.removeChild(langMenu.firstChild);
}
shaka.ui.Utils.removeAllChildren(langMenu);
// 3. Add the backTo Menu button back
langMenu.appendChild(backButton);
+2 -2
View File
@@ -56,8 +56,8 @@
* flips. CSS is just great like that. :-( */
.shaka-video {
/* At the moment, nothing special is required here.
* Note that this should NOT be an overlay-child, as its size should dictate
* the size of the container, not vice-versa. */
* Note that this should NOT be an overlay-child, as its size could dictate
* the size of the container for some applications. */
}
/* A container for all controls, including the giant play button, seek bar, etc.
+1 -3
View File
@@ -174,9 +174,7 @@ shaka.ui.ResolutionSelection = class extends shaka.ui.Element {
this.resolutionMenu_, 'shaka-back-to-overflow-button');
// 2. Remove everything
while (this.resolutionMenu_.firstChild) {
this.resolutionMenu_.removeChild(this.resolutionMenu_.firstChild);
}
shaka.ui.Utils.removeAllChildren(this.resolutionMenu_);
// 3. Add the backTo Menu button back
this.resolutionMenu_.appendChild(backButton);
+10 -8
View File
@@ -192,10 +192,9 @@ shaka.ui.Overlay.scanPageForShakaElements_ = function() {
'Please see https://tinyurl.com/y7s4j9tr for the list of ' +
'supported browsers.');
// Although this has failed, fire the "loaded" event. This will let apps
// get on with the business of startup, check isBrowserSupported()
// themselves, and show an appropriate error message at the app level.
shaka.ui.Overlay.dispatchLoadedEvent_();
// After scanning the page for elements, fire a special "loaded" event for
// when the load fails. This will allow the page to react to the failure.
shaka.ui.Overlay.dispatchLoadedEvent_('shaka-ui-load-failed');
return;
}
@@ -290,16 +289,19 @@ shaka.ui.Overlay.scanPageForShakaElements_ = function() {
// After scanning the page for elements, fire the "loaded" event. This will
// let apps know they can use the UI library programmatically now, even if
// they didn't have any Shaka-related elements declared in their HTML.
shaka.ui.Overlay.dispatchLoadedEvent_();
shaka.ui.Overlay.dispatchLoadedEvent_('shaka-ui-loaded');
};
/** @private */
shaka.ui.Overlay.dispatchLoadedEvent_ = function() {
/**
* @param {string} eventName
* @private
*/
shaka.ui.Overlay.dispatchLoadedEvent_ = function(eventName) {
// "Event" is not constructable on IE, so we use this CustomEvent pattern.
const uiLoadedEvent = /** @type {!CustomEvent} */(
document.createEvent('CustomEvent'));
uiLoadedEvent.initCustomEvent('shaka-ui-loaded', false, false, null);
uiLoadedEvent.initCustomEvent(eventName, false, false, null);
document.dispatchEvent(uiLoadedEvent);
};
+10
View File
@@ -110,3 +110,13 @@ shaka.ui.Utils.setDisplay = function(element, display) {
}
};
/**
* Remove all of the child nodes of an elements.
* @param {!Element} element
* @export
*/
shaka.ui.Utils.removeAllChildren = function(element) {
while (element.firstChild) {
element.removeChild(element.firstChild);
}
};