shaka-player
Version:
DASH/EME video player library
639 lines (545 loc) • 20.3 kB
JavaScript
/**
* @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 || {};
/** @private {shaka.cast.CastProxy} */
shakaDemo.castProxy_ = null;
/** @private {HTMLMediaElement} */
shakaDemo.video_ = null;
/** @private {shaka.Player} */
shakaDemo.player_ = null;
/** @private {HTMLMediaElement} */
shakaDemo.localVideo_ = null;
/** @private {shaka.Player} */
shakaDemo.localPlayer_ = null;
/** @private {shakaExtern.SupportType} */
shakaDemo.support_;
/** @private {ShakaControls} */
shakaDemo.controls_ = null;
/** @private {boolean} */
shakaDemo.hashCanChange_ = false;
/** @private {boolean} */
shakaDemo.suppressHashChangeEvent_ = false;
/**
* @private
* @const {string}
*/
shakaDemo.mainPoster_ = '//shaka-player-demo.appspot.com/assets/poster.jpg';
/**
* @private
* @const {string}
*/
shakaDemo.audioOnlyPoster_ =
'//shaka-player-demo.appspot.com/assets/audioOnly.gif';
/**
* The registered ID of the v2.2 Chromecast receiver demo.
* @const {string}
* @private
*/
shakaDemo.CC_APP_ID_ = '91580C19';
/**
* Initialize the application.
*/
shakaDemo.init = function() {
document.getElementById('errorDisplayCloseButton').addEventListener(
'click', shakaDemo.closeError);
// Display the version number.
document.getElementById('version').textContent = shaka.Player.version;
// Fill in the language preferences based on browser config, if available.
var language = navigator.language || 'en-us';
document.getElementById('preferredAudioLanguage').value = language;
document.getElementById('preferredTextLanguage').value = language;
var params = shakaDemo.getParams_();
shakaDemo.setupLogging_();
shakaDemo.preBrowserCheckParams_(params);
shaka.polyfill.installAll();
// Display uncaught exceptions.
window.addEventListener('error', function(event) {
shakaDemo.onError_(/** @type {!shaka.util.Error} */ (event.error));
});
if (!shaka.Player.isBrowserSupported()) {
var errorDisplayLink = document.getElementById('errorDisplayLink');
var error = 'Your browser is not supported!';
// IE8 and other very old browsers don't have textContent.
if (errorDisplayLink.textContent === undefined) {
errorDisplayLink.innerText = error;
} else {
errorDisplayLink.textContent = error;
}
// Disable the load button.
var loadButton = document.getElementById('loadButton');
loadButton.disabled = true;
// Hide the error message's close button.
var errorDisplayCloseButton =
document.getElementById('errorDisplayCloseButton');
errorDisplayCloseButton.style.display = 'none';
// Make sure the error is seen.
errorDisplayLink.style.fontSize = '250%';
// TODO: Link to docs about browser support. For now, disable link.
errorDisplayLink.href = '#';
// Disable for newer browsers:
errorDisplayLink.style.pointerEvents = 'none';
// Disable for older browsers:
errorDisplayLink.style.textDecoration = 'none';
errorDisplayLink.style.cursor = 'default';
errorDisplayLink.onclick = function() { return false; };
var errorDisplay = document.getElementById('errorDisplay');
errorDisplay.style.display = 'block';
} else {
shaka.Player.probeSupport().then(function(support) {
shakaDemo.support_ = support;
var localVideo =
/** @type {!HTMLVideoElement} */(document.getElementById('video'));
var localPlayer = new shaka.Player(localVideo);
shakaDemo.castProxy_ = new shaka.cast.CastProxy(
localVideo, localPlayer, shakaDemo.CC_APP_ID_);
shakaDemo.video_ = shakaDemo.castProxy_.getVideo();
shakaDemo.player_ = shakaDemo.castProxy_.getPlayer();
shakaDemo.player_.addEventListener('error', shakaDemo.onErrorEvent_);
shakaDemo.localVideo_ = localVideo;
shakaDemo.localPlayer_ = localPlayer;
// Set the default poster.
shakaDemo.localVideo_.poster = shakaDemo.mainPoster_;
var asyncSetup = shakaDemo.setupAssets_();
shakaDemo.setupOffline_();
shakaDemo.setupConfiguration_();
shakaDemo.setupInfo_();
shakaDemo.controls_ = new ShakaControls();
shakaDemo.controls_.init(shakaDemo.castProxy_, shakaDemo.onError_,
shakaDemo.onCastStatusChange_);
asyncSetup.catch(function(error) {
// shakaDemo.setupOfflineAssets_ errored while trying to
// load the offline assets. Notify the user of this.
shakaDemo.onError_(/** @type {!shaka.util.Error} */ (error));
}).then(function() {
shakaDemo.postBrowserCheckParams_(params);
window.addEventListener('hashchange', shakaDemo.updateFromHash_);
});
}).catch(function(error) {
// Some part of the setup of the demo app threw an error.
// Notify the user of this.
shakaDemo.onError_(/** @type {!shaka.util.Error} */ (error));
});
}
};
/**
* @return {!Object.<string, string>} params
* @private
*/
shakaDemo.getParams_ = function() {
// Read URL parameters.
var fields = location.search.substr(1);
fields = fields ? fields.split(';') : [];
var fragments = location.hash.substr(1);
fragments = fragments ? fragments.split(';') : [];
// Because they are being concatenated in this order, if both an
// URL fragment and an URL parameter of the same type are present
// the URL fragment takes precendence.
/** @type {!Array.<string>} */
var combined = fields.concat(fragments);
var params = {};
for (var i = 0; i < combined.length; ++i) {
var kv = combined[i].split('=');
params[kv[0]] = kv.slice(1).join('=');
}
return params;
};
/**
* @param {!Object.<string, string>} params
* @private
*/
shakaDemo.preBrowserCheckParams_ = function(params) {
if ('videoRobustness' in params) {
document.getElementById('drmSettingsVideoRobustness').value =
params['videoRobustness'];
}
if ('audioRobustness' in params) {
document.getElementById('drmSettingsAudioRobustness').value =
params['audioRobustness'];
}
if ('lang' in params) {
document.getElementById('preferredAudioLanguage').value = params['lang'];
document.getElementById('preferredTextLanguage').value = params['lang'];
}
if ('audiolang' in params) {
document.getElementById('preferredAudioLanguage').value =
params['audiolang'];
}
if ('textlang' in params) {
document.getElementById('preferredTextLanguage').value = params['textlang'];
}
if ('asset' in params) {
document.getElementById('manifestInput').value = params['asset'];
}
if ('license' in params) {
document.getElementById('licenseServerInput').value = params['license'];
}
if ('certificate' in params) {
document.getElementById('certificateInput').value = params['certificate'];
}
if ('logtoscreen' in params) {
document.getElementById('logToScreen').checked = true;
// Call onLogChange_ manually, because setting checked
// programatically doesn't fire a 'change' event.
shakaDemo.onLogChange_();
}
if ('noinput' in params) {
// Both the content container and body need different styles in this mode.
document.getElementById('container').className = 'noinput';
document.body.className = 'noinput';
}
if ('play' in params) {
document.getElementById('enableLoadOnRefresh').checked = true;
}
// shaka.log is not set if logging isn't enabled.
// I.E. if using the compiled version of shaka.
if (shaka.log) {
// The log level selector is only visible if logging is available.
document.getElementById('logLevelListDiv').hidden = false;
// Set log level.
var toSelectValue;
if ('vv' in params) {
toSelectValue = 'vv';
shaka.log.setLevel(shaka.log.Level.V2);
} else if ('v' in params) {
toSelectValue = 'v';
shaka.log.setLevel(shaka.log.Level.V1);
} else if ('debug' in params) {
toSelectValue = 'debug';
shaka.log.setLevel(shaka.log.Level.DEBUG);
}
if (toSelectValue) {
// Set the log level selector to the proper value.
var logLevelList = document.getElementById('logLevelList');
for (var index = 0; index < logLevelList.length; index++) {
if (logLevelList[index].value == toSelectValue) {
logLevelList.selectedIndex = index;
break;
}
}
}
}
};
/**
* @param {!Object.<string, string>} params
* @private
*/
shakaDemo.postBrowserCheckParams_ = function(params) {
// If a custom asset was given in the URL, select it now.
if ('asset' in params) {
var assetList = document.getElementById('assetList');
var assetUri = params['asset'];
var isDefault = false;
// Check all options except the last, which is 'custom asset'.
for (var index = 0; index < assetList.options.length - 1; index++) {
if (assetList[index].asset &&
assetList[index].asset.manifestUri == assetUri) {
assetList.selectedIndex = index;
isDefault = true;
break;
}
}
if (isDefault) {
// Clear the custom fields.
document.getElementById('manifestInput').value = '';
document.getElementById('licenseServerInput').value = '';
} else {
// It was a custom asset, so put it into the custom field.
assetList.selectedIndex = assetList.options.length - 1;
var customAsset = document.getElementById('customAsset');
customAsset.style.display = 'block';
}
// Call updateButtons_ manually, because changing assetList
// programatically doesn't fire a 'change' event.
shakaDemo.updateButtons_(/* canHide */ true);
}
var smallGapLimit = document.getElementById('smallGapLimit');
smallGapLimit.placeholder = 0.5; // The default smallGapLimit.
if ('smallGapLimit' in params) {
smallGapLimit.value = params['smallGapLimit'];
// Call onGapInput_ manually, because setting the value
// programatically doesn't fire 'input' event.
var fakeEvent = /** @type {!Event} */({target: smallGapLimit});
shakaDemo.onGapInput_(fakeEvent);
}
var jumpLargeGaps = document.getElementById('jumpLargeGaps');
if ('jumpLargeGaps' in params) {
jumpLargeGaps.checked = true;
// Call onJumpLargeGapsChange_ manually, because setting checked
// programatically doesn't fire a 'change' event.
var fakeEvent = /** @type {!Event} */({target: jumpLargeGaps});
shakaDemo.onJumpLargeGapsChange_(fakeEvent);
} else {
jumpLargeGaps.checked =
shakaDemo.player_.getConfiguration().streaming.jumpLargeGaps;
}
if ('noadaptation' in params) {
var enableAdaptation = document.getElementById('enableAdaptation');
enableAdaptation.checked = false;
// Call onAdaptationChange_ manually, because setting checked
// programatically doesn't fire a 'change' event.
var fakeEvent = /** @type {!Event} */({target: enableAdaptation});
shakaDemo.onAdaptationChange_(fakeEvent);
}
if ('trickplay' in params) {
var showTrickPlay = document.getElementById('showTrickPlay');
showTrickPlay.checked = true;
// Call onTrickPlayChange_ manually, because setting checked
// programatically doesn't fire a 'change' event.
var fakeEvent = /** @type {!Event} */({target: showTrickPlay});
shakaDemo.onTrickPlayChange_(fakeEvent);
}
if ('nativecontrols' in params) {
var showNative = document.getElementById('showNative');
showNative.checked = true;
// Call onNativeChange_ manually, because setting checked
// programatically doesn't fire a 'change' event.
var fakeEvent = /** @type {!Event} */({target: showNative});
shakaDemo.onNativeChange_(fakeEvent);
}
// Allow the hash to be changed, and give it an initial change.
shakaDemo.hashCanChange_ = true;
shakaDemo.hashShouldChange_();
if ('noinput' in params || 'play' in params) {
shakaDemo.load();
}
};
/** @private */
shakaDemo.updateFromHash_ = function() {
// Hash changes made by us should be ignored. We only want to respond to hash
// changes made by the user in the URL bar.
if (shakaDemo.suppressHashChangeEvent_) {
shakaDemo.suppressHashChangeEvent_ = false;
return;
}
var params = shakaDemo.getParams_();
shakaDemo.preBrowserCheckParams_(params);
shakaDemo.postBrowserCheckParams_(params);
};
/** @private */
shakaDemo.hashShouldChange_ = function() {
if (!shakaDemo.hashCanChange_)
return;
var params = [];
// Save the current asset.
var assetUri;
var licenseServerUri;
if (shakaDemo.player_) {
assetUri = shakaDemo.player_.getManifestUri();
var drmInfo = shakaDemo.player_.drmInfo();
if (drmInfo)
licenseServerUri = drmInfo.licenseServerUri;
}
var assetList = document.getElementById('assetList');
if (assetUri) {
// Store the currently playing asset URI.
params.push('asset=' + assetUri);
// Is the asset a default asset?
var isDefault = false;
// Check all options except the last, which is 'custom asset'.
for (var index = 0; index < assetList.options.length - 1; index++) {
if (assetList[index].asset.manifestUri == assetUri) {
isDefault = true;
break;
}
}
// If it's a custom asset we should store whatever the license
// server URI is.
if (!isDefault && licenseServerUri) {
params.push('license=' + licenseServerUri);
}
} else {
if (assetList.selectedIndex == assetList.length - 1) {
// It's a custom asset.
var manifestInputValue = document.getElementById('manifestInput').value;
if (manifestInputValue) {
params.push('asset=' + manifestInputValue);
}
var licenseInputValue =
document.getElementById('licenseServerInput').value;
if (licenseInputValue) {
params.push('license=' + licenseInputValue);
}
var certificateInputValue =
document.getElementById('certificateInput').value;
if (certificateInputValue) {
params.push('certificate=' + certificateInputValue);
}
} else {
// It's a default asset.
params.push('asset=' +
assetList[assetList.selectedIndex].asset.manifestUri);
}
}
// Save config panel state.
if (document.getElementById('smallGapLimit').value.length) {
params.push('smallGapLimit=' +
document.getElementById('smallGapLimit').value);
}
if (document.getElementById('jumpLargeGaps').checked)
params.push('jumpLargeGaps');
var audioLang = document.getElementById('preferredAudioLanguage').value;
var textLang = document.getElementById('preferredTextLanguage').value;
if (textLang != audioLang) {
params.push('audiolang=' + audioLang);
params.push('textlang=' + textLang);
} else {
params.push('lang=' + audioLang);
}
if (document.getElementById('logToScreen').checked) {
params.push('logtoscreen');
}
if (!document.getElementById('enableAdaptation').checked) {
params.push('noadaptation');
}
if (document.getElementById('showTrickPlay').checked) {
params.push('trickplay');
}
if (document.getElementById('showNative').checked) {
params.push('nativecontrols');
}
if (shaka.log) {
var logLevelList = document.getElementById('logLevelList');
var logLevel = logLevelList[logLevelList.selectedIndex].value;
if (logLevel != 'info') {
params.push(logLevel);
}
}
if (document.getElementById('enableLoadOnRefresh').checked) {
params.push('play');
}
// This parameter must be added manually, so preserve it.
if ('noinput' in shakaDemo.getParams_()) {
params.push('noinput');
}
// This parameter must be added manually, so preserve it.
// This one is only used by the loader in load.js to decide which version of
// the library to load.
if ('compiled' in shakaDemo.getParams_()) {
params.push('compiled');
}
if ('debug_compiled' in shakaDemo.getParams_()) {
params.push('debug_compiled');
}
// Store values for drm configuration.
var videoRobustness =
document.getElementById('drmSettingsVideoRobustness').value;
if (videoRobustness)
params.push('videoRobustness=' + videoRobustness);
var audioRobustness =
document.getElementById('drmSettingsAudioRobustness').value;
if (audioRobustness)
params.push('audioRobustness=' + audioRobustness);
var newHash = '#' + params.join(';');
if (newHash != location.hash) {
// We want to suppress hashchange events triggered here. We only want to
// respond to hashchange events initiated by the user in the URL bar.
shakaDemo.suppressHashChangeEvent_ = true;
location.hash = newHash;
}
// If search is already blank, setting it triggers a navigation and reloads
// the page. Only blank out the search if we have just upgraded from search
// parameters to hash parameters.
if (location.search) {
location.search = '';
}
};
/**
* @param {!Event} event
* @private
*/
shakaDemo.onErrorEvent_ = function(event) {
var error = event.detail;
shakaDemo.onError_(error);
};
/**
* @param {!shaka.util.Error} error
* @private
*/
shakaDemo.onError_ = function(error) {
console.error('Player error', error);
var link = document.getElementById('errorDisplayLink');
// Don't let less serious or equally serious errors replace what is already
// shown. The first error is usually the most important one, and the others
// may distract us in bug reports.
// If this is an unexpected non-shaka.util.Error, severity is null.
if (error.severity == null) {
// Treat these as the most severe, since they should not happen.
error.severity = /** @type {shaka.util.Error.Severity} */(99);
}
// Always show the new error if:
// 1. there is no error showing currently
// 2. the new error is more severe than the old one
if (link.severity == null ||
error.severity > link.severity) {
var message = error.message || ('Error code ' + error.code);
if (error.code) {
link.href = '../docs/api/shaka.util.Error.html#value:' + error.code;
} else {
link.href = '';
}
link.textContent = message;
// By converting severity == null to 99, non-shaka errors will not be
// replaced by any subsequent error.
link.severity = error.severity || 99;
// Make the link clickable only if we have an error code.
link.style.pointerEvents = error.code ? 'auto' : 'none';
document.getElementById('errorDisplay').style.display = 'block';
}
};
/**
* Closes the error bar.
*/
shakaDemo.closeError = function() {
document.getElementById('errorDisplay').style.display = 'none';
var link = document.getElementById('errorDisplayLink');
link.href = '';
link.textContent = '';
link.severity = null;
};
// IE 9 fires DOMContentLoaded, and enters the "interactive"
// readyState, before document.body has been initialized, so wait
// for window.load.
if (document.readyState == 'loading' ||
document.readyState == 'interactive') {
if (window.attachEvent) {
// IE8
window.attachEvent('onload', shakaDemo.init);
} else {
window.addEventListener('load', shakaDemo.init);
}
} else {
/**
* Poll for Shaka Player on window. On IE 11, the document is "ready", but
* there are still deferred scripts being loaded. This does not occur on
* Chrome or Edge, which set the document's state at the correct time.
*/
var pollForShakaPlayer = function() {
if (window.shaka) {
shakaDemo.init();
} else {
setTimeout(pollForShakaPlayer, 100);
}
};
pollForShakaPlayer();
}