mirror of
https://github.com/DanielnetoDotCom/YouPHPTube
synced 2025-10-03 09:49:28 +02:00
611 lines
16 KiB
JavaScript
611 lines
16 KiB
JavaScript
/**
|
|
* Copyright 2017 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.
|
|
*
|
|
* IMA SDK integration plugin for Video.js. For more information see
|
|
* https://www.github.com/googleads/videojs-ima
|
|
*/
|
|
|
|
/**
|
|
* Ad UI implementation.
|
|
*
|
|
* @param {Controller} controller Plugin controller.
|
|
* @constructor
|
|
* @struct
|
|
* @final
|
|
*/
|
|
const AdUi = function(controller) {
|
|
/**
|
|
* Plugin controller.
|
|
*/
|
|
this.controller = controller;
|
|
|
|
/**
|
|
* Div used as an ad container.
|
|
*/
|
|
this.adContainerDiv = document.createElement('div');
|
|
|
|
/**
|
|
* Div used to display ad controls.
|
|
*/
|
|
this.controlsDiv = document.createElement('div');
|
|
|
|
/**
|
|
* Div used to display ad countdown timer.
|
|
*/
|
|
this.countdownDiv = document.createElement('div');
|
|
|
|
/**
|
|
* Div used to display add seek bar.
|
|
*/
|
|
this.seekBarDiv = document.createElement('div');
|
|
|
|
/**
|
|
* Div used to display ad progress (in seek bar).
|
|
*/
|
|
this.progressDiv = document.createElement('div');
|
|
|
|
/**
|
|
* Div used to display ad play/pause button.
|
|
*/
|
|
this.playPauseDiv = document.createElement('div');
|
|
|
|
/**
|
|
* Div used to display ad mute button.
|
|
*/
|
|
this.muteDiv = document.createElement('div');
|
|
|
|
/**
|
|
* Div used by the volume slider.
|
|
*/
|
|
this.sliderDiv = document.createElement('div');
|
|
|
|
/**
|
|
* Volume slider level visuals
|
|
*/
|
|
this.sliderLevelDiv = document.createElement('div');
|
|
|
|
/**
|
|
* Div used to display ad fullscreen button.
|
|
*/
|
|
this.fullscreenDiv = document.createElement('div');
|
|
|
|
/**
|
|
* Bound event handler for onMouseUp.
|
|
*/
|
|
this.boundOnMouseUp = this.onMouseUp.bind(this);
|
|
|
|
/**
|
|
* Bound event handler for onMouseMove.
|
|
*/
|
|
this.boundOnMouseMove = this.onMouseMove.bind(this);
|
|
|
|
/**
|
|
* Stores data for the ad playhead tracker.
|
|
*/
|
|
this.adPlayheadTracker = {
|
|
'currentTime': 0,
|
|
'duration': 0,
|
|
'isPod': false,
|
|
'adPosition': 0,
|
|
'totalAds': 0,
|
|
};
|
|
|
|
/**
|
|
* Used to prefix videojs ima controls.
|
|
*/
|
|
this.controlPrefix = this.controller.getPlayerId() + '_';
|
|
|
|
/**
|
|
* Boolean flag to show or hide the ad countdown timer.
|
|
*/
|
|
this.showCountdown = true;
|
|
if (this.controller.getSettings().showCountdown === false) {
|
|
this.showCountdown = false;
|
|
}
|
|
|
|
/**
|
|
* Boolean flag if the current ad is nonlinear.
|
|
*/
|
|
this.isAdNonlinear = false;
|
|
|
|
this.createAdContainer();
|
|
};
|
|
|
|
/**
|
|
* Creates the ad container.
|
|
*/
|
|
AdUi.prototype.createAdContainer = function() {
|
|
this.assignControlAttributes(
|
|
this.adContainerDiv, 'ima-ad-container');
|
|
this.adContainerDiv.style.position = 'absolute';
|
|
this.adContainerDiv.style.zIndex = 1111;
|
|
this.adContainerDiv.addEventListener(
|
|
'mouseenter',
|
|
this.showAdControls.bind(this),
|
|
false);
|
|
this.adContainerDiv.addEventListener(
|
|
'mouseleave',
|
|
this.hideAdControls.bind(this),
|
|
false);
|
|
this.adContainerDiv.addEventListener(
|
|
'click',
|
|
this.onAdContainerClick.bind(this),
|
|
false);
|
|
this.createControls();
|
|
this.controller.injectAdContainerDiv(this.adContainerDiv);
|
|
};
|
|
|
|
|
|
/**
|
|
* Create the controls.
|
|
*/
|
|
AdUi.prototype.createControls = function() {
|
|
this.assignControlAttributes(this.controlsDiv, 'ima-controls-div');
|
|
this.controlsDiv.style.width = '100%';
|
|
|
|
if (!this.controller.getIsMobile()) {
|
|
this.assignControlAttributes(this.countdownDiv, 'ima-countdown-div');
|
|
this.countdownDiv.innerHTML = this.controller.getSettings().adLabel;
|
|
this.countdownDiv.style.display = this.showCountdown ? 'block' : 'none';
|
|
} else {
|
|
this.countdownDiv.style.display = 'none';
|
|
}
|
|
|
|
this.assignControlAttributes(this.seekBarDiv, 'ima-seek-bar-div');
|
|
this.seekBarDiv.style.width = '100%';
|
|
|
|
this.assignControlAttributes(this.progressDiv, 'ima-progress-div');
|
|
|
|
this.assignControlAttributes(this.playPauseDiv, 'ima-play-pause-div');
|
|
this.addClass(this.playPauseDiv, 'ima-playing');
|
|
this.playPauseDiv.addEventListener(
|
|
'click',
|
|
this.onAdPlayPauseClick.bind(this),
|
|
false);
|
|
|
|
this.assignControlAttributes(this.muteDiv, 'ima-mute-div');
|
|
this.addClass(this.muteDiv, 'ima-non-muted');
|
|
this.muteDiv.addEventListener(
|
|
'click',
|
|
this.onAdMuteClick.bind(this),
|
|
false);
|
|
|
|
this.assignControlAttributes(this.sliderDiv, 'ima-slider-div');
|
|
this.sliderDiv.addEventListener(
|
|
'mousedown',
|
|
this.onAdVolumeSliderMouseDown.bind(this),
|
|
false);
|
|
|
|
// Hide volume slider controls on iOS as they aren't supported.
|
|
if (this.controller.getIsIos()) {
|
|
this.sliderDiv.style.display = 'none';
|
|
}
|
|
|
|
this.assignControlAttributes(this.sliderLevelDiv, 'ima-slider-level-div');
|
|
|
|
this.assignControlAttributes(this.fullscreenDiv, 'ima-fullscreen-div');
|
|
this.addClass(this.fullscreenDiv, 'ima-non-fullscreen');
|
|
this.fullscreenDiv.addEventListener(
|
|
'click',
|
|
this.onAdFullscreenClick.bind(this),
|
|
false);
|
|
|
|
this.adContainerDiv.appendChild(this.controlsDiv);
|
|
this.controlsDiv.appendChild(this.countdownDiv);
|
|
this.controlsDiv.appendChild(this.seekBarDiv);
|
|
this.controlsDiv.appendChild(this.playPauseDiv);
|
|
this.controlsDiv.appendChild(this.muteDiv);
|
|
this.controlsDiv.appendChild(this.sliderDiv);
|
|
this.controlsDiv.appendChild(this.fullscreenDiv);
|
|
this.seekBarDiv.appendChild(this.progressDiv);
|
|
this.sliderDiv.appendChild(this.sliderLevelDiv);
|
|
};
|
|
|
|
|
|
/**
|
|
* Listener for clicks on the play/pause button during ad playback.
|
|
*/
|
|
AdUi.prototype.onAdPlayPauseClick = function() {
|
|
this.controller.onAdPlayPauseClick();
|
|
};
|
|
|
|
|
|
/**
|
|
* Listener for clicks on the play/pause button during ad playback.
|
|
*/
|
|
AdUi.prototype.onAdMuteClick = function() {
|
|
this.controller.onAdMuteClick();
|
|
};
|
|
|
|
|
|
/**
|
|
* Listener for clicks on the fullscreen button during ad playback.
|
|
*/
|
|
AdUi.prototype.onAdFullscreenClick = function() {
|
|
this.controller.toggleFullscreen();
|
|
};
|
|
|
|
|
|
/**
|
|
* Show pause and hide play button
|
|
*/
|
|
AdUi.prototype.onAdsPaused = function() {
|
|
this.controller.sdkImpl.adPlaying = false;
|
|
this.addClass(this.playPauseDiv, 'ima-paused');
|
|
this.removeClass(this.playPauseDiv, 'ima-playing');
|
|
this.showAdControls();
|
|
};
|
|
|
|
|
|
/**
|
|
* Show pause and hide play button
|
|
*/
|
|
AdUi.prototype.onAdsResumed = function() {
|
|
this.onAdsPlaying();
|
|
this.showAdControls();
|
|
};
|
|
|
|
|
|
/**
|
|
* Show play and hide pause button
|
|
*/
|
|
AdUi.prototype.onAdsPlaying = function() {
|
|
this.controller.sdkImpl.adPlaying = true;
|
|
this.addClass(this.playPauseDiv, 'ima-playing');
|
|
this.removeClass(this.playPauseDiv, 'ima-paused');
|
|
};
|
|
|
|
|
|
/**
|
|
* Takes data from the controller to update the UI.
|
|
*
|
|
* @param {number} currentTime Current time of the ad.
|
|
* @param {number} remainingTime Remaining time of the ad.
|
|
* @param {number} duration Duration of the ad.
|
|
* @param {number} adPosition Index of the ad in the pod.
|
|
* @param {number} totalAds Total number of ads in the pod.
|
|
*/
|
|
AdUi.prototype.updateAdUi =
|
|
function(currentTime, remainingTime, duration, adPosition, totalAds) {
|
|
// Update countdown timer data
|
|
const remainingMinutes = Math.floor(remainingTime / 60);
|
|
let remainingSeconds = Math.floor(remainingTime % 60);
|
|
if (remainingSeconds.toString().length < 2) {
|
|
remainingSeconds = '0' + remainingSeconds;
|
|
}
|
|
let podCount = ': ';
|
|
if (totalAds > 1) {
|
|
podCount = ' (' + adPosition + ' ' +
|
|
this.controller.getSettings().adLabelNofN + ' ' + totalAds + '): ';
|
|
}
|
|
this.countdownDiv.innerHTML =
|
|
this.controller.getSettings().adLabel + podCount +
|
|
remainingMinutes + ':' + remainingSeconds;
|
|
|
|
// Update UI
|
|
const playProgressRatio = currentTime / duration;
|
|
const playProgressPercent = playProgressRatio * 100;
|
|
this.progressDiv.style.width = playProgressPercent + '%';
|
|
};
|
|
|
|
|
|
/**
|
|
* Handles UI changes when the ad is unmuted.
|
|
*/
|
|
AdUi.prototype.unmute = function() {
|
|
this.addClass(this.muteDiv, 'ima-non-muted');
|
|
this.removeClass(this.muteDiv, 'ima-muted');
|
|
this.sliderLevelDiv.style.width =
|
|
this.controller.getPlayerVolume() * 100 + '%';
|
|
};
|
|
|
|
|
|
/**
|
|
* Handles UI changes when the ad is muted.
|
|
*/
|
|
AdUi.prototype.mute = function() {
|
|
this.addClass(this.muteDiv, 'ima-muted');
|
|
this.removeClass(this.muteDiv, 'ima-non-muted');
|
|
this.sliderLevelDiv.style.width = '0%';
|
|
};
|
|
|
|
|
|
/*
|
|
* Listener for mouse down events during ad playback. Used for volume.
|
|
*/
|
|
AdUi.prototype.onAdVolumeSliderMouseDown = function() {
|
|
document.addEventListener('mouseup', this.boundOnMouseUp, false);
|
|
document.addEventListener('mousemove', this.boundOnMouseMove, false);
|
|
};
|
|
|
|
|
|
/*
|
|
* Mouse movement listener used for volume slider.
|
|
*/
|
|
AdUi.prototype.onMouseMove = function(event) {
|
|
this.changeVolume(event);
|
|
};
|
|
|
|
|
|
/*
|
|
* Mouse release listener used for volume slider.
|
|
*/
|
|
AdUi.prototype.onMouseUp = function(event) {
|
|
this.changeVolume(event);
|
|
document.removeEventListener('mouseup', this.boundOnMouseUp);
|
|
document.removeEventListener('mousemove', this.boundOnMouseMove);
|
|
};
|
|
|
|
|
|
/*
|
|
* Utility function to set volume and associated UI
|
|
*/
|
|
AdUi.prototype.changeVolume = function(event) {
|
|
let percent =
|
|
(event.clientX - this.sliderDiv.getBoundingClientRect().left) /
|
|
this.sliderDiv.offsetWidth;
|
|
percent *= 100;
|
|
// Bounds value 0-100 if mouse is outside slider region.
|
|
percent = Math.min(Math.max(percent, 0), 100);
|
|
this.sliderLevelDiv.style.width = percent + '%';
|
|
if (this.percent == 0) {
|
|
this.addClass(this.muteDiv, 'ima-muted');
|
|
this.removeClass(this.muteDiv, 'ima-non-muted');
|
|
} else {
|
|
this.addClass(this.muteDiv, 'ima-non-muted');
|
|
this.removeClass(this.muteDiv, 'ima-muted');
|
|
}
|
|
this.controller.setVolume(percent / 100); // 0-1
|
|
};
|
|
|
|
|
|
/**
|
|
* Show the ad container.
|
|
*/
|
|
AdUi.prototype.showAdContainer = function() {
|
|
this.adContainerDiv.style.display = 'block';
|
|
};
|
|
|
|
|
|
/**
|
|
* Hide the ad container
|
|
*/
|
|
AdUi.prototype.hideAdContainer = function() {
|
|
this.adContainerDiv.style.display = 'none';
|
|
};
|
|
|
|
/**
|
|
* Handles clicks on the ad container
|
|
*/
|
|
AdUi.prototype.onAdContainerClick = function() {
|
|
if (this.isAdNonlinear) {
|
|
this.controller.togglePlayback();
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Resets the state of the ad ui.
|
|
*/
|
|
AdUi.prototype.reset = function() {
|
|
this.hideAdContainer();
|
|
};
|
|
|
|
|
|
/**
|
|
* Handles ad errors.
|
|
*/
|
|
AdUi.prototype.onAdError = function() {
|
|
this.hideAdContainer();
|
|
};
|
|
|
|
|
|
/**
|
|
* Handles ad break starting.
|
|
*
|
|
* @param {Object} adEvent The event fired by the IMA SDK.
|
|
*/
|
|
AdUi.prototype.onAdBreakStart = function(adEvent) {
|
|
this.showAdContainer();
|
|
|
|
const contentType = adEvent.getAd().getContentType();
|
|
if ((contentType === 'application/javascript') &&
|
|
!this.controller.getSettings().showControlsForJSAds) {
|
|
this.controlsDiv.style.display = 'none';
|
|
} else {
|
|
this.controlsDiv.style.display = 'block';
|
|
}
|
|
this.onAdsPlaying();
|
|
// Start with the ad controls minimized.
|
|
this.hideAdControls();
|
|
};
|
|
|
|
|
|
/**
|
|
* Handles ad break ending.
|
|
*/
|
|
AdUi.prototype.onAdBreakEnd = function() {
|
|
const currentAd = this.controller.getCurrentAd();
|
|
if (currentAd == null || // hide for post-roll only playlist
|
|
currentAd.isLinear()) { // don't hide for non-linear ads
|
|
this.hideAdContainer();
|
|
}
|
|
this.controlsDiv.style.display = 'none';
|
|
this.countdownDiv.innerHTML = '';
|
|
};
|
|
|
|
|
|
/**
|
|
* Handles when all ads have finished playing.
|
|
*/
|
|
AdUi.prototype.onAllAdsCompleted = function() {
|
|
this.hideAdContainer();
|
|
};
|
|
|
|
|
|
/**
|
|
* Handles when a linear ad starts.
|
|
*/
|
|
AdUi.prototype.onLinearAdStart = function() {
|
|
// Don't bump container when controls are shown
|
|
this.removeClass(this.adContainerDiv, 'bumpable-ima-ad-container');
|
|
this.isAdNonlinear = false;
|
|
};
|
|
|
|
|
|
/**
|
|
* Handles when a non-linear ad starts.
|
|
*/
|
|
AdUi.prototype.onNonLinearAdLoad = function() {
|
|
// For non-linear ads that show after a linear ad. For linear ads, we show the
|
|
// ad container in onAdBreakStart to prevent blinking in pods.
|
|
this.adContainerDiv.style.display = 'block';
|
|
// Bump container when controls are shown
|
|
this.addClass(this.adContainerDiv, 'bumpable-ima-ad-container');
|
|
this.isAdNonlinear = true;
|
|
};
|
|
|
|
|
|
AdUi.prototype.onPlayerEnterFullscreen = function() {
|
|
this.addClass(this.fullscreenDiv, 'ima-fullscreen');
|
|
this.removeClass(this.fullscreenDiv, 'ima-non-fullscreen');
|
|
};
|
|
|
|
|
|
AdUi.prototype.onPlayerExitFullscreen = function() {
|
|
this.addClass(this.fullscreenDiv, 'ima-non-fullscreen');
|
|
this.removeClass(this.fullscreenDiv, 'ima-fullscreen');
|
|
};
|
|
|
|
|
|
/**
|
|
* Called when the player volume changes.
|
|
*
|
|
* @param {number} volume The new player volume.
|
|
*/
|
|
AdUi.prototype.onPlayerVolumeChanged = function(volume) {
|
|
if (volume == 0) {
|
|
this.addClass(this.muteDiv, 'ima-muted');
|
|
this.removeClass(this.muteDiv, 'ima-non-muted');
|
|
this.sliderLevelDiv.style.width = '0%';
|
|
} else {
|
|
this.addClass(this.muteDiv, 'ima-non-muted');
|
|
this.removeClass(this.muteDiv, 'ima-muted');
|
|
this.sliderLevelDiv.style.width = volume * 100 + '%';
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Shows ad controls on mouseover.
|
|
*/
|
|
AdUi.prototype.showAdControls = function() {
|
|
const {disableAdControls} = this.controller.getSettings();
|
|
if (!disableAdControls) {
|
|
this.addClass(this.controlsDiv, 'ima-controls-div-showing');
|
|
}
|
|
};
|
|
|
|
|
|
/**
|
|
* Hide the ad controls.
|
|
*/
|
|
AdUi.prototype.hideAdControls = function() {
|
|
this.removeClass(this.controlsDiv, 'ima-controls-div-showing');
|
|
};
|
|
|
|
|
|
/**
|
|
* Assigns the unique id and class names to the given element as well as the
|
|
* style class.
|
|
* @param {HTMLElement} element Element that needs the controlName assigned.
|
|
* @param {string} controlName Control name to assign.
|
|
*/
|
|
AdUi.prototype.assignControlAttributes = function(element, controlName) {
|
|
element.id = this.controlPrefix + controlName;
|
|
element.className = this.controlPrefix + controlName + ' ' + controlName;
|
|
};
|
|
|
|
|
|
/**
|
|
* Returns a regular expression to test a string for the given className.
|
|
*
|
|
* @param {string} className The name of the class.
|
|
* @return {RegExp} The regular expression used to test for that class.
|
|
*/
|
|
AdUi.prototype.getClassRegexp = function(className) {
|
|
// Matches on
|
|
// (beginning of string OR NOT word char)
|
|
// classname
|
|
// (negative lookahead word char OR end of string)
|
|
return new RegExp('(^|[^A-Za-z-])' + className +
|
|
'((?![A-Za-z-])|$)', 'gi');
|
|
};
|
|
|
|
|
|
/**
|
|
* Returns whether or not the provided element has the provied class in its
|
|
* className.
|
|
* @param {HTMLElement} element Element to tes.t
|
|
* @param {string} className Class to look for.
|
|
* @return {boolean} True if element has className in class list. False
|
|
* otherwise.
|
|
*/
|
|
AdUi.prototype.elementHasClass = function(element, className) {
|
|
const classRegexp = this.getClassRegexp(className);
|
|
return classRegexp.test(element.className);
|
|
};
|
|
|
|
|
|
/**
|
|
* Adds a class to the given element if it doesn't already have the class
|
|
* @param {HTMLElement} element Element to which the class will be added.
|
|
* @param {string} classToAdd Class to add.
|
|
*/
|
|
AdUi.prototype.addClass = function(element, classToAdd) {
|
|
element.className = element.className.trim() + ' ' + classToAdd;
|
|
};
|
|
|
|
|
|
/**
|
|
* Removes a class from the given element if it has the given class
|
|
*
|
|
* @param {HTMLElement} element Element from which the class will be removed.
|
|
* @param {string} classToRemove Class to remove.
|
|
*/
|
|
AdUi.prototype.removeClass = function(element, classToRemove) {
|
|
const classRegexp = this.getClassRegexp(classToRemove);
|
|
element.className =
|
|
element.className.trim().replace(classRegexp, '');
|
|
};
|
|
|
|
|
|
/**
|
|
* @return {HTMLElement} The div for the ad container.
|
|
*/
|
|
AdUi.prototype.getAdContainerDiv = function() {
|
|
return this.adContainerDiv;
|
|
};
|
|
|
|
|
|
/**
|
|
* Changes the flag to show or hide the ad countdown timer.
|
|
*
|
|
* @param {boolean} showCountdownIn Show or hide the countdown timer.
|
|
*/
|
|
AdUi.prototype.setShowCountdown = function(showCountdownIn) {
|
|
this.showCountdown = showCountdownIn;
|
|
this.countdownDiv.style.display = this.showCountdown ? 'block' : 'none';
|
|
};
|
|
|
|
export default AdUi;
|