mirror of
https://github.com/shaka-project/shaka-player.git
synced 2026-06-16 16:16:40 +03:00
6bfb35fc69
Also adds a getConfiguration method to the ui, so that the demo does not need to construct a config object from scratch. Change-Id: Ic583fbcc0815494c7668242f6c798b8c15f09697
352 lines
10 KiB
JavaScript
352 lines
10 KiB
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.
|
|
*/
|
|
|
|
|
|
goog.provide('shaka.ui.Overlay');
|
|
|
|
goog.require('goog.asserts');
|
|
goog.require('shaka.polyfill.installAll');
|
|
goog.require('shaka.ui.Controls');
|
|
goog.require('shaka.ui.TextDisplayer');
|
|
|
|
|
|
/**
|
|
* @param {!shaka.Player} player
|
|
* @param {!HTMLElement} videoContainer
|
|
* @param {!HTMLMediaElement} video
|
|
* @implements {shaka.util.IDestroyable}
|
|
* @constructor
|
|
* @export
|
|
*/
|
|
shaka.ui.Overlay = function(player, videoContainer, video) {
|
|
/** @private {shaka.Player} */
|
|
this.player_ = player;
|
|
|
|
/** @private {!HTMLElement} */
|
|
this.videoContainer_ = videoContainer;
|
|
|
|
/** @private {!HTMLMediaElement} */
|
|
this.video_ = video;
|
|
|
|
/** @private {!shaka.extern.UIConfiguration} */
|
|
this.config_ = this.defaultConfig_();
|
|
|
|
// Make sure this container is discoverable and that the UI can be reached
|
|
// through it.
|
|
videoContainer['dataset']['shakaPlayerContainer'] = '';
|
|
videoContainer['ui'] = this;
|
|
};
|
|
|
|
|
|
/**
|
|
* @override
|
|
* @export
|
|
*/
|
|
shaka.ui.Overlay.prototype.destroy = async function() {
|
|
await this.controls_.destroy();
|
|
this.controls_ = null;
|
|
|
|
await this.player_.destroy();
|
|
this.player_ = null;
|
|
};
|
|
|
|
|
|
/** @return {!shaka.extern.UIConfiguration} */
|
|
shaka.ui.Overlay.prototype.getConfiguration = function() {
|
|
let ret = this.defaultConfig_();
|
|
shaka.util.ConfigUtils.mergeConfigObjects(
|
|
ret, this.config_, this.defaultConfig_(),
|
|
/* overrides (only used for player config)*/ {}, /* path */ '');
|
|
return ret;
|
|
};
|
|
|
|
|
|
/**
|
|
* @param {!Object} config This should follow the form of
|
|
* {@link shaka.extern.UIConfiguration}, but you may omit
|
|
* any field you do not wish to change.
|
|
* @export
|
|
*/
|
|
shaka.ui.Overlay.prototype.configure = function(config) {
|
|
// TODO: accept flattened config "configure(addSeekBar, false);"
|
|
const DomUtils = shaka.util.Dom;
|
|
// Deconstruct the old layout.
|
|
|
|
// Save the text container, so subtitles can be displayed with
|
|
// the new layout.
|
|
const textContainer =
|
|
this.videoContainer_.querySelector('.shaka-text-container');
|
|
|
|
// Remember whether the controls were shown
|
|
let shown = false;
|
|
let controlsContainer =
|
|
this.videoContainer_.querySelector('.shaka-controls-container');
|
|
if (controlsContainer != null) {
|
|
shown = controlsContainer.getAttribute('shown');
|
|
}
|
|
|
|
// Destroy the old layout.
|
|
shaka.util.Dom.removeAllChildren(this.videoContainer_);
|
|
|
|
// Add the video back in
|
|
this.videoContainer_.appendChild(this.video_);
|
|
|
|
shaka.util.ConfigUtils.mergeConfigObjects(
|
|
this.config_, config, this.defaultConfig_(),
|
|
/* overrides (only used for player config)*/ {}, /* path */ '');
|
|
|
|
// If a cast receiver app id has been given, add a cast button to the UI
|
|
if (this.config_.castReceiverAppId &&
|
|
!this.config_.overflowMenuButtons.includes('cast')) {
|
|
this.config_.overflowMenuButtons.push('cast');
|
|
}
|
|
|
|
goog.asserts.assert(this.player_ != null, 'Should have a player!');
|
|
|
|
/** @private {shaka.ui.Controls} */
|
|
this.controls_ = new shaka.ui.Controls(
|
|
this.player_, this.videoContainer_, this.video_, this.config_);
|
|
|
|
controlsContainer = DomUtils.getElementByClassName(
|
|
'shaka-controls-container', this.videoContainer_);
|
|
controlsContainer.setAttribute('shown', shown);
|
|
|
|
// Add the text container back.
|
|
if (textContainer) {
|
|
this.videoContainer_.appendChild(textContainer);
|
|
}
|
|
|
|
this.controls_.dispatchEvent(new shaka.util.FakeEvent('uiupdated'));
|
|
};
|
|
|
|
|
|
/**
|
|
* @return {shaka.Player}
|
|
* @export
|
|
*/
|
|
shaka.ui.Overlay.prototype.getPlayer = function() {
|
|
return this.player_;
|
|
};
|
|
|
|
|
|
/**
|
|
* @return {shaka.ui.Controls}
|
|
* @export
|
|
*/
|
|
shaka.ui.Overlay.prototype.getControls = function() {
|
|
return this.controls_;
|
|
};
|
|
|
|
|
|
/**
|
|
* Enable or disable the custom controls.
|
|
*
|
|
* @param {boolean} enabled
|
|
* @export
|
|
*/
|
|
shaka.ui.Overlay.prototype.setEnabled = function(enabled) {
|
|
this.controls_.setEnabledShakaControls(enabled);
|
|
};
|
|
|
|
|
|
/**
|
|
* @return {!shaka.extern.UIConfiguration}
|
|
* @private
|
|
*/
|
|
shaka.ui.Overlay.prototype.defaultConfig_ = function() {
|
|
return {
|
|
controlPanelElements: [
|
|
'time_and_duration',
|
|
'spacer',
|
|
'mute',
|
|
'volume',
|
|
'fullscreen',
|
|
'overflow_menu',
|
|
],
|
|
overflowMenuButtons: [
|
|
'captions',
|
|
'quality',
|
|
'language',
|
|
'picture_in_picture',
|
|
'cast',
|
|
],
|
|
addSeekBar: true,
|
|
castReceiverAppId: '',
|
|
};
|
|
};
|
|
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
shaka.ui.Overlay.scanPageForShakaElements_ = function() {
|
|
// Install built-in polyfills to patch browser incompatibilities.
|
|
shaka.polyfill.installAll();
|
|
// Check to see if the browser supports the basic APIs Shaka needs.
|
|
if (!shaka.Player.isBrowserSupported()) {
|
|
shaka.log.error('Shaka Player does not support this browser. ' +
|
|
'Please see https://tinyurl.com/y7s4j9tr for the list of ' +
|
|
'supported browsers.');
|
|
|
|
// 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;
|
|
}
|
|
|
|
// Look for elements marked 'data-shaka-player-container'
|
|
// on the page. These will be used to create our default
|
|
// UI.
|
|
const containers = document.querySelectorAll(
|
|
'[data-shaka-player-container]');
|
|
|
|
// Look for elements marked 'data-shaka-player'. They will
|
|
// either be used in our default UI or with native browser
|
|
// controls.
|
|
const videos = document.querySelectorAll(
|
|
'[data-shaka-player]');
|
|
|
|
if (!videos.length && !containers.length) {
|
|
// No elements have been tagged with shaka attributes.
|
|
} else if (videos.length && !containers.length) {
|
|
// Just the video elements were provided.
|
|
for (let i = 0; i < videos.length; i++) {
|
|
const video = videos[i];
|
|
video.classList.add('video');
|
|
goog.asserts.assert(video.tagName.toLowerCase() == 'video',
|
|
'Should be a video element!');
|
|
|
|
const container = document.createElement('div');
|
|
const videoParent = video.parentElement;
|
|
videoParent.replaceChild(container, video);
|
|
container.appendChild(video);
|
|
|
|
let castAppId = '';
|
|
|
|
// If cast receiver application id was provided, pass it to the
|
|
// UI constructor.
|
|
if (video['dataset'] && video['dataset']['shakaPlayerCastReceiverId']) {
|
|
castAppId = video['dataset']['shakaPlayerCastReceiverId'];
|
|
}
|
|
|
|
const ui = shaka.ui.Overlay.createUI_(
|
|
shaka.util.Dom.asHTMLElement(container),
|
|
shaka.util.Dom.asHTMLMediaElement(video));
|
|
|
|
ui.configure({castReceiverAppId: castAppId});
|
|
|
|
if (shaka.util.Dom.asHTMLMediaElement(video).controls) {
|
|
ui.getControls().setEnabledNativeControls(true);
|
|
}
|
|
}
|
|
} else {
|
|
for (let i = 0; i < containers.length; i++) {
|
|
const container = containers[i];
|
|
goog.asserts.assert(container.tagName.toLowerCase() == 'div',
|
|
'Container should be a div!');
|
|
|
|
let castAppId = '';
|
|
|
|
// Cast receiver id can be specified on either container or video.
|
|
// It should not be provided on both. If it was, we will use the last
|
|
// one we saw.
|
|
if (container['dataset'] &&
|
|
container['dataset']['shakaPlayerCastReceiverId']) {
|
|
castAppId = container['dataset']['shakaPlayerCastReceiverId'];
|
|
}
|
|
|
|
let video = null;
|
|
for (let j = 0; j < videos.length; j++) {
|
|
goog.asserts.assert(videos[j].tagName.toLowerCase() == 'video',
|
|
'Should be a video element!');
|
|
if (videos[j].parentElement == container) {
|
|
video = videos[j];
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!video) {
|
|
video = document.createElement('video');
|
|
video.setAttribute('playsinline', '');
|
|
container.appendChild(video);
|
|
}
|
|
|
|
if (video['dataset'] && video['dataset']['shakaPlayerCastReceiverId']) {
|
|
castAppId = video['dataset']['shakaPlayerCastReceiverId'];
|
|
}
|
|
const ui = shaka.ui.Overlay.createUI_(
|
|
shaka.util.Dom.asHTMLElement(container),
|
|
shaka.util.Dom.asHTMLMediaElement(video));
|
|
|
|
ui.configure({castReceiverAppId: castAppId});
|
|
}
|
|
}
|
|
|
|
// 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-loaded');
|
|
};
|
|
|
|
|
|
/**
|
|
* @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(eventName, false, false, null);
|
|
|
|
document.dispatchEvent(uiLoadedEvent);
|
|
};
|
|
|
|
|
|
/**
|
|
* @param {!HTMLElement} container
|
|
* @param {!HTMLMediaElement} video
|
|
* @return {!shaka.ui.Overlay}
|
|
* @private
|
|
*/
|
|
shaka.ui.Overlay.createUI_ = function(container, video) {
|
|
const player = new shaka.Player(video);
|
|
const ui = new shaka.ui.Overlay(player, container, video);
|
|
|
|
// If the browser's native controls are disabled, use UI TextDisplayer. Right
|
|
// now because the factory must be a constructor and () => {} can't be a
|
|
// constructor.
|
|
if (!video.controls) {
|
|
player.configure(
|
|
'textDisplayFactory',
|
|
function() { return new shaka.ui.TextDisplayer(video, container); });
|
|
}
|
|
|
|
container['ui'] = ui;
|
|
video['ui'] = ui;
|
|
return ui;
|
|
};
|
|
|
|
|
|
if (document.readyState == 'complete') {
|
|
// Don't fire this event synchronously. In a compiled bundle, the "shaka"
|
|
// namespace might not be exported to the window until after this point.
|
|
Promise.resolve().then(shaka.ui.Overlay.scanPageForShakaElements_);
|
|
} else {
|
|
window.addEventListener('load', shaka.ui.Overlay.scanPageForShakaElements_);
|
|
}
|