import infMultiplayer from "./Interfaces/infMultiPlayer";
import infPlayer from "./Interfaces/infPlayer";
import { sleep, waitFor } from "../Libraries/Utilities/waitFor";
export default class PlayerEventsManager {
private players: any[] = [];
private noFill: boolean = true;
private adPlaying: string = '';
private multiplayerParams: infMultiplayer = null;
private playerParams: infPlayer = null;
private hidden: string = '';
private visibilityChange: string = '';
private adPause: boolean = false;
private pauseOnClick: boolean = false;
public constructor(playerParams: infPlayer, multiplayer: infMultiplayer) {
this.setVisibilitEnv();
this.videoEvents();
this.playerParams = playerParams;
this.multiplayerParams = multiplayer;
}
private setVisibilitEnv() {
if (typeof document.hidden !== "undefined") { // Opera 12.10 and Firefox 18 and later support
this.hidden = "hidden";
this.visibilityChange = "visibilitychange";
//@ts-ignore
} else if (typeof document.msHidden !== "undefined") {
this.hidden = "msHidden";
this.visibilityChange = "msvisibilitychange";
//@ts-ignore
} else if (typeof document.webkitHidden !== "undefined") {
this.hidden = "webkitHidden";
this.visibilityChange = "webkitvisibilitychange";
}
}
/**
* Listen to video events from Dailymotion player
*/
private async videoEvents() {
// Ignore 'cpeready' event because this event is from outside the script
// @ts-ignore
window.addEventListener('cpeready', ({ detail: { players } }) => {
this.players = players;
for (let i=0; i < players.length; i++) {
const player = players[i];
player.addEventListener('videochange', async (e) => {
const video = player.video;
const videoUpdated = new CustomEvent('dm-video-updated', { detail: { videoId: video.videoId }})
document.dispatchEvent(videoUpdated);
});
player.addEventListener('ad_start', (e: Event) => {
this.noFill = false;
});
/**
* Listen to an ad_play
*
* - Cover others player when the ad is played
*/
player.addEventListener('ad_play', (e: Event) => {
if (this.adPlaying === '') {
this.adPlaying = player.id;
// Disable the player that not playing yet
if (this.multiplayerParams.adCoverPlay === true) {
this.toggleDisable();
}
// Toggle playing video and hide the PiP
if (this.multiplayerParams.closePip === true) {
this.togglePlay(player.id);
}
if (this.playerParams.adHideControls === true) {
player.setControls(false);
}
}
});
/**
* Listen to an ad_end event
*
* - Remove player cover when the ad is ended
*/
player.addEventListener('ad_end', (e: Event) => {
if (this.adPlaying !== '') {
this.adPlaying = '';
// Toggle disabled player
if (this.multiplayerParams.adCoverPlay) {
this.toggleDisable();
}
if (this.playerParams.adHideControls === true) {
player.setControls(true);
}
}
});
/**
* Listen to ad_pause to handle the pause event
* when player is clicked by user
*
* - Tell the listener that the ad is paused by user click
*/
player.addEventListener('ad_pause', (e: Event) => {
this.adPause = true;
});
/**
* Listening to playing event
*
* - Close the PiP if there are multiple players and the closePip is true
*/
player.addEventListener('playing', async (e: Event) => {
if (this.multiplayerParams.closePip === true) {
this.togglePlay(player.id);
}
});
/**
* Listen to video end, and process the next thing
* It will load new video from the playlist
*/
player.addEventListener('end', (e: Event) => {
const videoEnd = new CustomEvent("dm-video-end", {detail: player.video.videoId});
document.dispatchEvent(videoEnd);
});
/**
* Listen to `playback_ready` to show the player
*/
player.addEventListener('playback_ready', async (e: Event) => {
const dmPlayer = player.parentNode.parentNode.parentNode;
/**
* It's only to show video when the ad is filled
*
* Because we don't showing the video at first, the video won't play anyway.
* So we play it programmatically via JS and start to listen to `waitForAdStart`
* to listen to ad request
*/
if (this.playerParams.showAdOnly === true) {
dmPlayer.classList.add('dm-wait-for-ad');
player.play();
await this.waitForAdStart();
} else {
const showPlayer = new CustomEvent('dm-show-player');
document.dispatchEvent(showPlayer);
}
/**
* Force close button PiP to show
*/
if (this.playerParams.showCloseButtonPip === true) {
const customClosePip = document.createElement('button');
customClosePip.className = 'dm__close-button';
customClosePip.innerHTML = '';
customClosePip.addEventListener('click', () => {
const cpeClose = new CustomEvent('customClosePip');
document.dispatchEvent(cpeClose);
dmPlayer.classList.add('dm--pip-closed');
});
await sleep(3000);
dmPlayer.classList.add('dm--has-close-pip');
const playerContainer = dmPlayer.childNodes[0].childNodes[0];
playerContainer.appendChild(customClosePip);
}
/**
* Build the custom close button
*/
if (this.playerParams.closeButton === true) {
const closeButton = document.createElement('button');
closeButton.className = 'dm__close-button';
closeButton.innerHTML = '';
closeButton.addEventListener('click', () => {
dmPlayer.classList.add('dm--hidding-player');
setTimeout( () => {
dmPlayer.classList.add('dm--player-hidden');
dmPlayer.remove();
}, 1000);
});
// Wait for 2 seconds if ad exist
if ( this.noFill !== true ) await sleep(2000);
dmPlayer.classList.add('dm--has-close-button');
const playerContainer = dmPlayer.childNodes[0].childNodes[0];
playerContainer.appendChild(closeButton);
}
});
/**
* Handle player error as well to avoid bad UX
*/
player.addEventListener('error', (e) => {
// console.log(e);
});
}
});
/**
* Listen to PiP close to pause the video player
*/
// @ts-ignore
window.addEventListener('cpepipclose', ({ detail: { player } }) => {
// Do pause when cpe PiP is closed
player.pause();
});
document.addEventListener('customClosePip', (e: Event) => {
// Do pause when cpe PiP is closed
this.players[0].pause();
});
/**
* Listen to slide changes to set the video to play
*/
// TODO: support multiplayer for next development
document.addEventListener('dm-slide-changes', ( e: Event) => {
// @ts-ignore
this.players[0].load({ video: e.detail});
});
/**
* Destroy the player if there is no ad to serve
*/
document.addEventListener('dm-destroy-player', (e: Event) => {
// @ts-ignore
this.players[0].parentNode.parentNode.parentNode.remove(); // Get dm-player first
});
/**
* Add new class `dm-playback-ready` to show the player
*/
document.addEventListener('dm-show-player', (e: Event) => {
this.players[0].parentNode.parentNode.parentNode.classList.add('dm-playback-ready');
});
/**
* Handle change tab by user
*/
document.addEventListener(this.visibilityChange, (e: Event) => {
if (!document[this.hidden] && this.adPause === true) {
this.players[0].play();
this.adPause = false;
}
});
}
/**
* Wait and check if noFill is true or not.
* This function is related to `ad_start` listener as well
*/
private async waitForAdStart() {
// Waiting for 1 second to interact with ad
// await sleep(2000);
/**
* noFill means no ad to serve
* It will send a custom event that let
* the script continue show the player or destroy it
*/
if ( this.noFill === true ) {
const destroyPlayer = new CustomEvent('dm-destroy-player');
document.dispatchEvent(destroyPlayer);
} else {
const showPlayer = new CustomEvent('dm-show-player');
document.dispatchEvent(showPlayer);
}
}
/**
* Toggle play and remove all PiP active
*
* @param playerId
*/
private togglePlay(playerId: string): void {
// Check every player available
for (let i=0; i < this.players.length; i++) {
// close the PiP if other player is start playing
if (this.players[i].id !== playerId) {
const parent = this.players[i].parentNode;
this.players[i].pause();
parent.classList.remove('pip');
}
}
}
/**
* Add cover to others player to be not clickable by the user
*/
private toggleDisable(): void {
// Check every player available
for (let i=0; i < this.players.length; i++) {
// get parent player
const parent = this.players[i].parentNode;
if (this.adPlaying !== '' && this.adPlaying !== this.players[i].id) {
parent.classList.add('dm-disabled');
} else {
parent.classList.remove('dm-disabled');
}
}
}
}