ng-plyr
Version:
An HTML5 media player for developers, built using Angular, having interface similar to Youtube player.
1,040 lines (1,033 loc) • 107 kB
JavaScript
import * as i0 from '@angular/core';
import { Injectable, Directive, HostListener, EventEmitter, Component, Inject, Input, Output, ViewChild, NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import * as i3 from '@angular/common';
import { DOCUMENT } from '@angular/common';
class Error$1 {
constructor(message, code) {
this.message = message;
this.code = code;
}
}
var ErrorCodes;
(function (ErrorCodes) {
ErrorCodes["player/not-initialized"] = "Player not yet initialized";
ErrorCodes["video/element-not-initialized"] = "Video element is not yet initialized";
})(ErrorCodes || (ErrorCodes = {}));
class PlaylistItem {
constructor(media) {
this.media = media;
}
static hasSameMediaSrc(p1, p2) {
return p1.media.src === p2.media.src;
}
// Inquiry functions
hasNext() { return this.next ? true : false; }
hasPrev() { return this.prev ? true : false; }
}
class Playlist {
constructor(item) {
this.itemCount = 0;
if (item) {
this.head = item;
this.updateTail(item);
}
}
static getLastItemFrom(item) {
while (item.hasNext()) {
item = item.next;
}
return item;
}
static getItemsCount(item) {
let count = 1;
while (item.hasNext()) {
item = item.next;
count++;
}
return count;
}
static mediaArrToPaylistItems(playlist) {
let pl = new PlaylistItem(playlist[playlist.length - 1]);
for (let i = playlist.length - 2; i >= 0; i--) {
let tempPl = new PlaylistItem(playlist[i]);
tempPl.next = pl;
pl.prev = tempPl;
pl = tempPl;
}
return pl;
}
// Reset this playlist
resetPlaylist() {
this.head = undefined;
this.tail = undefined;
this.current = undefined;
this.itemCount = 0;
}
// Initialize playlist with mediaItems
initializePlaylist(mediaItems) {
// Reseting
this.resetPlaylist();
// Adding new items to playlist
let playlistItems = Playlist.mediaArrToPaylistItems(mediaItems);
this.appendPlaylist(playlistItems);
this.current = this.head;
}
// Update head to add items at beginning
updateHead(item) {
let oldHead = this.head;
this.head = item;
this.itemCount++;
while (item.hasNext()) {
item = item.next;
this.itemCount++;
}
if (oldHead) {
oldHead.prev = item;
item.next = oldHead;
}
else {
this.tail = item;
}
}
// Update tail after item is added at end
updateTail(item) {
while (item.hasNext()) {
item = item.next;
this.itemCount++;
}
this.tail = item;
this.itemCount++;
}
// Add items at end
appendPlaylist(item, atHead) {
if (atHead)
return this.updateHead(item);
if (this.isEmpty()) {
this.head = item;
}
else if (this.tail) {
item.prev = this.tail;
this.tail.next = item;
}
this.updateTail(item);
}
// Append item after current, update tail if req, update itemCount
addNext(item) {
if (this.current) {
this.itemCount += Playlist.getItemsCount(item);
let tempNext = this.current.next;
item.prev = this.current;
this.current.next = item;
// Update links at end of item
let lastItem = Playlist.getLastItemFrom(item);
if (tempNext) {
tempNext.prev = lastItem;
lastItem.next = tempNext;
}
else {
this.tail = lastItem;
}
}
else {
this.appendPlaylist(item, true);
this.current = item;
}
}
// Remove an existing PlaylistItem from Playlist
remove(item) {
if (!this.contains(item))
return;
let prev = item.prev;
let next = item.next;
if (next) {
next.prev = prev;
}
if (prev) {
prev.next = next;
}
this.itemCount--;
return item.media;
}
// Inquiry functions
// Check if Playlist is empty
isEmpty() { return this.head ? false : true; }
// Check whether this playlist contains the item
contains(item) {
let start = this.head;
while (start) {
if (item === start)
return true;
start = start.next;
}
return false;
}
// Get PlaylistItem at index
getPlaylistItemAt(index) {
let item = this.head;
while (index > 0 && (item === null || item === void 0 ? void 0 : item.hasNext())) {
item = item.next;
index--;
}
return item;
}
// Count number of items in this playlist
countPlaylistItems() {
let item = this.head;
let count = 0;
while (item) {
count++;
item = item.next;
}
this.itemCount = count;
return count;
}
}
class PlayerService {
constructor() { }
_checkForErrors(elements) {
let check = new Set(elements);
if (!this._plyr)
throw new Error(ErrorCodes['player/not-initialized']);
if ((check === null || check === void 0 ? void 0 : check.has('video')) && !this._plyr.video)
throw new Error(ErrorCodes['video/element-not-initialized']);
}
// Accessing `this` from player component
addComponentRef(plyrComponent) {
this._plyr = plyrComponent;
}
removeComponentRef() {
this._plyr = undefined;
}
// Public functions
// Actions
play() {
var _a;
this._checkForErrors(['video']);
(_a = this._plyr.video) === null || _a === void 0 ? void 0 : _a.nativeElement.play();
}
pause() {
var _a;
this._checkForErrors(['video']);
(_a = this._plyr.video) === null || _a === void 0 ? void 0 : _a.nativeElement.pause();
}
next() {
this._checkForErrors();
this._plyr.playNextMedia();
}
prev() {
this._checkForErrors();
this._plyr.playPrevMedia();
}
enableMediaLooping(loop = true) {
this._checkForErrors();
this._plyr.isLoopingEnabled = loop;
}
enablePlaylistLooping(loop = true) {
this._checkForErrors();
this._plyr.loopPlaylist = loop;
}
changeVolume(level) {
this._checkForErrors(['video']);
this._plyr.changeVolume(level);
}
seekTo(atSecond) {
this._checkForErrors(['video']);
this._plyr.seekTo(atSecond);
}
setPlaybackSpeed(rate) {
this._checkForErrors();
this._plyr.setPlaybackSpeed(rate);
}
// Getter functions
getCurrentlyPlaying() {
this._checkForErrors();
return this._plyr.media;
}
getNextMedia() {
this._checkForErrors();
return this._plyr.nextMedia;
}
getNumOfMediaInPlaylist() {
this._checkForErrors();
return this._plyr.playlist.itemCount;
}
// Modify playlist
addToPlaylist(mediaItems, atStart) {
this._checkForErrors();
let items = Playlist.mediaArrToPaylistItems(mediaItems);
this._plyr.playlist.appendPlaylist(items, atStart);
}
playNext(media) {
this._checkForErrors();
let items = Playlist.mediaArrToPaylistItems([...media]);
this._plyr.playlist.addNext(items);
}
}
PlayerService.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.2.0", ngImport: i0, type: PlayerService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
PlayerService.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "14.2.0", ngImport: i0, type: PlayerService, providedIn: 'root' });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.2.0", ngImport: i0, type: PlayerService, decorators: [{
type: Injectable,
args: [{
providedIn: 'root'
}]
}], ctorParameters: function () { return []; } });
class CastService {
constructor() {
this.isInitialized = false;
}
isSdkAvailable() {
return !!(cast === null || cast === void 0 ? void 0 : cast.framework);
}
initializeCastApi() {
// Check if the Cast SDK is already initialized
if (this.isInitialized)
return;
console.log('Initializing Cast API...');
// Initialize the Cast SDK
cast.framework.CastContext.getInstance().setOptions({
receiverApplicationId: chrome.cast.media.DEFAULT_MEDIA_RECEIVER_APP_ID,
autoJoinPolicy: chrome.cast.AutoJoinPolicy.ORIGIN_SCOPED
});
// Store the CastContext object
this.castContext = cast.framework.CastContext.getInstance();
// Initializing Remote Player
this.player = new cast.framework.RemotePlayer();
this.playerController = new cast.framework.RemotePlayerController(this.player);
this.isInitialized = true;
}
// Cast Media to receiver
loadMedia(mediaURL, contentType = 'video/mp4') {
if (!mediaURL) {
this.currentSession.endSession(false);
}
let mediaInfo = new chrome.cast.media.MediaInfo(mediaURL, contentType);
let request = new chrome.cast.media.LoadRequest(mediaInfo);
this.currentSession = this.castContext.getCurrentSession();
if (this.currentSession) {
this.currentSession.loadMedia(request).then(() => {
console.log('Media load succeed');
}).catch((error) => {
console.error('Error in casting media: ' + error);
});
}
else {
console.log('Session ended or not present.');
}
}
getCastContext() {
return this.castContext;
}
// getCurrentSession(): any {
// return this.currentSession;
// }
isCasting() {
return !!this.currentSession;
}
// Stop casting or just stop session
endCurrentSession(stopCasting) {
var _a;
(_a = this.currentSession) === null || _a === void 0 ? void 0 : _a.endSession(stopCasting);
this.stopListeningRemotePlayerEvents();
}
// Remote Player controls
playOrPauseCasting() {
if (this.currentSession && !this.player.isPlayingBreak) {
this.playerController.playOrPause();
}
}
play() {
var _a;
if ((_a = this.player) === null || _a === void 0 ? void 0 : _a.isPaused) {
this.playOrPauseCasting();
}
}
pause() {
var _a;
if (!((_a = this.player) === null || _a === void 0 ? void 0 : _a.isPaused)) {
this.playOrPauseCasting();
}
}
stopCasting() {
if (this.currentSession) {
this.playerController.stop();
}
}
// Seeks the media item to player currentTime value
seekTo(toSec) {
if (this.currentSession && this.player.canSeek) {
this.player.currentTime = toSec;
this.playerController.seek();
}
}
muteOrUnmute() {
if (this.currentSession) {
this.playerController.muteOrUnmute();
}
}
mute() {
var _a;
if (!((_a = this.player) === null || _a === void 0 ? void 0 : _a.isMuted)) {
this.muteOrUnmute();
}
}
unmute() {
var _a;
if ((_a = this.player) === null || _a === void 0 ? void 0 : _a.isMuted) {
this.muteOrUnmute();
}
}
setVolumeLevel(volume) {
if (this.currentSession) {
// Sets the volume level of the connected device to the player volumeLevel value.
this.player.volumeLevel = volume;
this.playerController.setVolumeLevel();
}
}
// Skip the ad currently playing on the receiver
skipAd() {
if (this.currentSession) {
this.playerController.skipAd();
}
}
// Create RemotePlayer events
onCastEvent(eventName, cb) {
return new Promise((resolve, reject) => {
this.playerController.addEventListener(cast.framework.RemotePlayerEventType[eventName], () => {
try {
cb();
}
catch (error) {
reject(error);
}
});
resolve('Added event listener on ' + eventName);
});
}
// Stop all event listeners on playerController
stopListeningRemotePlayerEvents() {
console.info('Stopped listening to cast events');
this.playerController.removeEventListener(cast.framework.RemotePlayerEventType.IS_PAUSED_CHANGED, () => { });
this.playerController.removeEventListener(cast.framework.RemotePlayerEventType.IS_PLAYING_BREAK_CHANGED, () => { });
this.playerController.removeEventListener(cast.framework.RemotePlayerEventType.CURRENT_TIME_CHANGED, () => { });
this.playerController.removeEventListener(cast.framework.RemotePlayerEventType.VOLUME_LEVEL_CHANGED, () => { });
this.playerController.removeEventListener(cast.framework.RemotePlayerEventType.IS_MUTED_CHANGED, () => { });
}
}
CastService.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.2.0", ngImport: i0, type: CastService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
CastService.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "14.2.0", ngImport: i0, type: CastService, providedIn: 'root' });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.2.0", ngImport: i0, type: CastService, decorators: [{
type: Injectable,
args: [{
providedIn: 'root'
}]
}], ctorParameters: function () { return []; } });
class Media {
constructor(src, type) {
this.playFrom = 0;
this.paused = true;
this.src = src;
this.type = type || MediaType.VIDEO;
}
}
var MediaType;
(function (MediaType) {
MediaType[MediaType["VIDEO"] = 0] = "VIDEO";
MediaType[MediaType["AUDIO"] = 1] = "AUDIO";
})(MediaType || (MediaType = {}));
class StopClickPropagationDirective {
constructor() { }
onClick(event) {
event === null || event === void 0 ? void 0 : event.stopPropagation();
}
}
StopClickPropagationDirective.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.2.0", ngImport: i0, type: StopClickPropagationDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
StopClickPropagationDirective.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "14.2.0", type: StopClickPropagationDirective, selector: "[stopClickPropagation]", host: { listeners: { "click": "onClick($event)" } }, ngImport: i0 });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.2.0", ngImport: i0, type: StopClickPropagationDirective, decorators: [{
type: Directive,
args: [{
selector: '[stopClickPropagation]'
}]
}], ctorParameters: function () { return []; }, propDecorators: { onClick: [{
type: HostListener,
args: ['click', ["$event"]]
}] } });
class NgPlyrComponent {
constructor(document, _plyrService, _castService, _cd) {
this.document = document;
this._plyrService = _plyrService;
this._castService = _castService;
this._cd = _cd;
// Flags
this.isPlaying = false;
this.isMuted = false;
this.isFullscreen = false;
this.isPIP = false;
this.isCasting = false;
// isAutoplayEnabled = false;
this.isLoopingEnabled = false;
this.isControlSettingsOpen = false;
this.isMenuSettingsOpen = false;
// Variables
this.seekStepInSec = 5;
this.playerVolume = 1;
this.currentTime = '0:00';
this.totalTime = '0:00';
this.progressPercent = 0;
this.mediaBuffers = [];
this.isMediaLoading = true;
this.playlist = new Playlist();
this.playingTrack = 0;
// TODO:
this.mediaMarkers = [];
// Inputs
this.mediaURL = '';
this.preload = 'none';
this.enableControls = true;
// Output events
this.playing = new EventEmitter();
this.paused = new EventEmitter();
this.ended = new EventEmitter();
this.onprev = new EventEmitter();
this.onnext = new EventEmitter();
this.fullscreen = new EventEmitter();
this.volumechange = new EventEmitter();
// Seek backward on mouseclick
this.bwdClickCount = 0;
this.bwdClickTimeout = setTimeout(() => this.bwdClickCount = 0, 500);
// Seek forward on mouseclick
this.fwdClickCount = 0;
this.fwdClickTimeout = setTimeout(() => this.fwdClickCount = 0, 500);
}
// Lifecycle hooks
// OnChanges LC hook
ngOnChanges(changes) {
var _a, _b;
this.inpChanges = changes;
// When both mediaURL & mediaItems are present, media will be set from mediaItems
if (changes['mediaURL'] && !changes['mediaItems']) {
this.changeMedia();
}
if (changes['mediaItems']) {
this.createPlaylist(this.mediaItems);
}
// nextMediaToAdd after playlist is initialized, or not present in same changes
if (changes['nextMediaToAdd'] && !changes['mediaItems']) {
this.onNextMediaInput();
}
this.mediaMarkers = (_a = changes['bookmarks']) === null || _a === void 0 ? void 0 : _a.currentValue;
this.isLoopingEnabled = (_b = changes['loopMedia']) === null || _b === void 0 ? void 0 : _b.currentValue;
}
ngOnInit() {
this._plyrService.addComponentRef(this);
}
ngAfterViewInit() {
this.isPlaying = !this.video.nativeElement.paused;
this.isMuted = this.video.nativeElement.muted;
this.isFullscreen = this.document.fullscreenElement ? true : false;
this.document.addEventListener('fullscreenchange', () => {
if (!this.document.fullscreenElement)
this.isFullscreen = false;
});
}
ngOnDestroy() {
this._plyrService.removeComponentRef();
}
resetPlayer() {
this.currentTime = '0:00';
this.totalTime = '0:00';
this.progressPercent = 0;
this.mediaBuffers = [];
this.isMediaLoading = true;
this.mediaMarkers = [];
}
changeMedia(media) {
this.resetPlayer();
this.media = media ? media : new Media(this.mediaURL, this.mediaType);
if (this.isCasting)
this._castService.loadMedia(this.media.src);
}
// Playlist functions
// Create new playlist / reinitialize playlist
createPlaylist(mediaItems) {
var _a, _b, _c;
if (mediaItems) {
this.playlist.initializePlaylist(mediaItems);
this.media = (_a = this.playlist.current) === null || _a === void 0 ? void 0 : _a.media;
if (this.nextMediaToAdd) {
this.playlist.addNext(new PlaylistItem(this.nextMediaToAdd));
}
this.nextMedia = (_c = (_b = this.playlist.current) === null || _b === void 0 ? void 0 : _b.next) === null || _c === void 0 ? void 0 : _c.media;
}
}
// nextMedia will be used to play next inboth src and playlist cases
onNextMediaInput() {
if (this.playlist.current && this.nextMediaToAdd) {
this.playlist.addNext(new PlaylistItem(this.nextMediaToAdd));
}
this.nextMedia = this.nextMediaToAdd;
}
// Output events for media
onPlay(event) {
this.playing.emit(true);
this.paused.emit(false);
this.isPlaying = true;
this.media.paused = false;
if (this.isCasting)
this.video.nativeElement.pause();
}
onPause(event) {
this.playing.emit(false);
this.paused.emit(true);
this.media.paused = true;
if (!this.isCasting)
this.isPlaying = false;
}
onEnd(event) {
this.ended.emit(true);
if (this.isAutoplayEnabled) {
this.playNextMedia();
}
}
onVolumeChange(event) {
this.volumechange.emit({
level: this.video.nativeElement.volume,
muted: this.video.nativeElement.muted
});
}
// TODO: Emit custom error
onError(event) {
console.error(event);
}
// Shortcut keys
doShortcutKeyAction(event) {
var _a;
if (this.enableControls === false)
return;
const tagName = this.document.activeElement.tagName.toLowerCase();
if (tagName === 'input')
return;
const key = event.key.toLowerCase();
if ((key === ' ' && tagName != 'button') || key === 'k') {
event.preventDefault();
this.togglePlay();
}
else if (key === 'm') {
this.toggleMute();
}
else if (key === 'f') {
this.toggleFullscreen();
}
else if (key === 'i') {
this.togglePIP();
}
else if (key === 'arrowleft') {
this.seekTo(Math.max(0, this.video.nativeElement.currentTime - 5));
}
else if (key === 'arrowright') {
this.seekTo(Math.min(this.video.nativeElement.duration, this.video.nativeElement.currentTime + 5));
}
else if (key === 'arrowup') {
this.changeVolume((this.playerVolume * 100 + 5) / 100);
}
else if (key === 'arrowdown') {
const vol = (this.playerVolume * 100 - 5) / 100;
this.changeVolume(vol < 0 ? 0 : vol);
}
else if (key === '0' || key === 'home') {
this.seekTo(0);
}
else if (Number(key) > 0 && Number(key) < 10 && ((_a = this.media) === null || _a === void 0 ? void 0 : _a.duration)) {
this.seekTo(this.media.duration * Number(key) / 10);
}
else if (key === 'end') {
this.stopVideo();
}
else if (event.key === 'N') {
this.playNextMedia();
}
else if (event.key === 'P') {
this.playPrevMedia();
}
else if (event.key === '<') {
this.setPlaybackSpeed(this.video.nativeElement.playbackRate - .25);
}
else if (event.key === '>') {
this.setPlaybackSpeed(this.video.nativeElement.playbackRate + .25);
}
}
// Volume control
changeVolume(value) {
value = Number(value).toPrecision(2);
if (!this.video || value > 1 || value < 0)
return;
if (this.isCasting) {
// Not yet working
this._castService.setVolumeLevel(value);
}
else {
this.video.nativeElement.volume = value;
this.playerVolume = value;
if (this.video.nativeElement.muted && value > 0) {
this.toggleMute();
}
}
}
// Update video metadata in player
updateAfterVideoInfoLoaded() {
var _a, _b;
if (this.inpChanges && this.inpChanges["mediaURL"]) {
this.video.nativeElement.currentTime = 0;
this.media.playFrom = this.playFrom;
this.media.duration = this.video.nativeElement.duration ? this.video.nativeElement.duration : this.video.nativeElement.currentTime;
this.media.captions = this.captions;
}
if (this.inpChanges && ((_a = this.inpChanges['volume']) === null || _a === void 0 ? void 0 : _a.currentValue)) {
this.changeVolume(this.inpChanges['volume'].currentValue);
}
if (this.inpChanges && ((_b = this.inpChanges['playFrom']) === null || _b === void 0 ? void 0 : _b.currentValue)) {
this.seekTo(this.inpChanges['playFrom'].currentValue);
}
if (this.isAutoplayEnabled)
this.video.nativeElement.play();
this.isPlaying = !this.video.nativeElement.paused;
this.currentTime = this.formatDuration(this.media.playFrom);
this.totalTime = this.formatDuration(this.media.duration);
this.showBuffers();
}
// Display video buffers on timeline
showBuffers() {
const buf = this.video.nativeElement.buffered;
if (buf.length === 1 && buf.end(0) - buf.start(0) === this.video.nativeElement.duration) {
this.isMediaLoading = false;
return;
}
this.isMediaLoading = true;
this.mediaBuffers = [];
for (let i = 0; i < buf.length; i++) {
let start = Number((buf.start(i) / this.video.nativeElement.duration).toPrecision(3)) * 100;
let end = Number((buf.end(i) / this.video.nativeElement.duration).toPrecision(3)) * 100;
this.mediaBuffers.push({ start, end });
if (this.video.nativeElement.currentTime >= buf.start(i) && this.video.nativeElement.currentTime <= buf.end(i)) {
this.isMediaLoading = false;
}
}
}
// Seek to specific time
seekTo(atSecond) {
if (!this.video)
return;
if (atSecond > this.video.nativeElement.duration) {
this.stopVideo();
return;
}
else if (Number(atSecond) < 0) {
this.video.nativeElement.currentTime = 0;
this._castService.seekTo(0);
}
else {
this.video.nativeElement.currentTime = Number(atSecond).toPrecision(3);
this._castService.seekTo(Number(atSecond));
}
this.progressPercent =
Number((this.video.nativeElement.currentTime / this.video.nativeElement.duration).toPrecision(3)) * 100;
}
// Call seekTo fn in specific direction and set count to 0
seekAfterTimeout(direction) {
if (direction === 'fwd') {
this.seekTo(this.video.nativeElement.currentTime + this.seekStepInSec * this.fwdClickCount);
this.fwdClickCount = 0;
}
else if (direction === 'bwd') {
this.seekTo(this.video.nativeElement.currentTime - this.seekStepInSec * this.bwdClickCount);
this.bwdClickCount = 0;
}
}
seekBwd(e) {
if (e.type === 'dblclick') {
this.bwdClickCount = 1;
clearTimeout(this.bwdClickTimeout);
this.bwdClickTimeout = setTimeout(() => this.seekAfterTimeout('bwd'), 500);
}
else if (e.type === 'click' && this.bwdClickCount > 0) {
this.bwdClickCount++;
clearTimeout(this.bwdClickTimeout);
this.bwdClickTimeout = setTimeout(() => this.seekAfterTimeout('bwd'), 500);
}
}
seekFwd(e) {
if (e.type === 'dblclick') {
this.fwdClickCount = 1;
clearTimeout(this.fwdClickTimeout);
this.fwdClickTimeout = setTimeout(() => this.seekAfterTimeout('fwd'), 500);
}
else if (e.type === 'click' && this.fwdClickCount > 0) {
this.fwdClickCount++;
clearTimeout(this.fwdClickTimeout);
this.fwdClickTimeout = setTimeout(() => this.seekAfterTimeout('fwd'), 500);
}
}
updateCurrentTime() {
this.currentTime = this.formatDuration(this.video.nativeElement.currentTime);
this.progressPercent =
Number((this.video.nativeElement.currentTime / this.video.nativeElement.duration).toPrecision(3)) * 100;
this.showBuffers();
}
// Play previous media from Q
playPrevMedia() {
var _a, _b, _c, _d, _e, _f, _g;
if (this.isLoopingEnabled)
return this.onprev.emit(this.media);
let mediaToPlay;
if ((_a = this.playlist.current) === null || _a === void 0 ? void 0 : _a.hasPrev()) {
this.playlist.current = this.playlist.current.prev;
mediaToPlay = (_b = this.playlist.current) === null || _b === void 0 ? void 0 : _b.media;
// Update prevMedia
if ((_c = this.playlist.current) === null || _c === void 0 ? void 0 : _c.hasPrev()) {
this.prevMedia = this.playlist.current.prev.media;
}
else if (this.loopPlaylist) {
this.prevMedia = (_d = this.playlist.tail) === null || _d === void 0 ? void 0 : _d.media;
}
}
else if (this.loopPlaylist) {
this.playlist.current = this.playlist.tail;
mediaToPlay = (_e = this.playlist.current) === null || _e === void 0 ? void 0 : _e.media;
this.prevMedia = (_g = (_f = this.playlist.current) === null || _f === void 0 ? void 0 : _f.prev) === null || _g === void 0 ? void 0 : _g.media;
}
else if (this.prevMedia) {
mediaToPlay = this.prevMedia;
this.prevMedia = undefined;
}
this.nextMedia = this.media;
this.changeMedia(mediaToPlay);
this.onprev.emit(mediaToPlay);
}
// Play next media from Q
playNextMedia() {
var _a, _b, _c, _d, _e, _f, _g;
if (this.isLoopingEnabled)
return this.onnext.emit(this.media);
let mediaToPlay;
if ((_a = this.playlist.current) === null || _a === void 0 ? void 0 : _a.hasNext()) {
this.playlist.current = this.playlist.current.next;
mediaToPlay = (_b = this.playlist.current) === null || _b === void 0 ? void 0 : _b.media;
// Updating nextMedia
if ((_c = this.playlist.current) === null || _c === void 0 ? void 0 : _c.hasNext()) {
this.nextMedia = this.playlist.current.next.media;
}
else if (this.loopPlaylist) {
this.nextMedia = (_d = this.playlist.head) === null || _d === void 0 ? void 0 : _d.media;
}
}
else if (this.loopPlaylist) {
this.playlist.current = this.playlist.head;
mediaToPlay = (_e = this.playlist.current) === null || _e === void 0 ? void 0 : _e.media;
this.nextMedia = (_g = (_f = this.playlist.current) === null || _f === void 0 ? void 0 : _f.next) === null || _g === void 0 ? void 0 : _g.media;
}
else if (this.nextMedia) {
mediaToPlay = this.nextMedia;
this.nextMedia = undefined;
}
this.prevMedia = this.media;
this.changeMedia(mediaToPlay);
this.onnext.emit(mediaToPlay);
}
// End/Stop current video
stopVideo() {
this.video.nativeElement.pause();
this.seekTo(this.video.nativeElement.duration);
this._castService.stopCasting();
}
setPlaybackSpeed(rate) {
if (rate < 0.25 || rate > 2)
return;
this.video.nativeElement.playbackRate = rate;
}
// Initialize Cast and requestSession
castToChromecast() {
// Check if the Cast SDK is available and initialized
if (this._castService.isSdkAvailable()) {
// Initialize Cast API
this._castService.initializeCastApi();
// Get the CastContext and show the Cast dialog to the user
let castContext = this._castService.getCastContext();
// Starts media casting after session starts
castContext.requestSession().then((session) => {
// Add event listeners
this.listenToPlayerEvents();
this.enableCastingMode();
// load media in receiver
this._castService.loadMedia(this.media.src, 'video/mp4');
}).catch((err) => {
console.error(err);
});
}
else {
console.warn('Cast SDK is not available or not initialized.');
}
}
// After successful connection, use this method to change mode to casting
enableCastingMode() {
this.isCasting = true;
// Stop playing on local device
this.video.nativeElement.pause();
this.isMuted = this._castService.player.isMuted;
this.isPlaying = !this._castService.player.isPaused;
this.playerVolume = this._castService.player.volumeLevel.toPrecision(2);
}
// Stop casting or just stop session
endCurrentSession(stopCasting) {
this._castService.endCurrentSession(stopCasting);
}
// Listening to remote events
listenToPlayerEvents() {
// Storing local player states in local variables to restore after receiver disconnects
let isMutedLocal = this.isMuted;
let playerVolumeLocal = this.playerVolume;
// Cast Connected/disconnected
this._castService.onCastEvent('IS_CONNECTED_CHANGED', () => {
if (this._castService.player.isConnected) {
console.log('Cast Player connected');
// Stop playing on local device
this.enableCastingMode();
}
else {
console.log('Cast Player disconnected');
this.isCasting = false;
this._castService.stopListeningRemotePlayerEvents();
// Start playing on local device
this.seekTo(this._castService.player.currentTime);
this.isPlaying ? this.video.nativeElement.play() : this.video.nativeElement.pause();
// Setting local UI to original state
this.isMuted = isMutedLocal;
this.playerVolume = playerVolumeLocal;
}
}).then((res) => console.log(res))
.catch((err) => console.error(err));
// Pause/Play
this._castService.onCastEvent('IS_PAUSED_CHANGED', () => {
if (this._castService.player.isPaused) {
console.log('Receiver paused');
this.isPlaying = false;
}
else {
console.log('Receiver playing');
this.isPlaying = true;
}
}).then((res) => console.log(res))
.catch((err) => console.error(err));
// MediaInfo changes
this._castService.onCastEvent('PLAYER_STATE_CHANGED', () => {
// console.log('PlayerState:', this._castService.player.playerState);
if (this._castService.player.playerState === 'IDLE') {
this.onEnd();
this._castService.play();
this.isPlaying = true;
}
}).then((res) => console.log(res))
.catch((err) => console.error(err));
// Paying break
this._castService.onCastEvent('IS_PLAYING_BREAK_CHANGED', () => {
if (this._castService.player.isPlayingBreak) {
console.log('Player is playing break');
// Change on local device
this.video.nativeElement.pause();
// Show Playing break, in UI
}
else {
console.log('Player break ended');
// Change on local device
}
}).then((res) => console.log(res))
.catch((err) => console.error(err));
// currentTime changed
this._castService.onCastEvent('CURRENT_TIME_CHANGED', () => {
// Change current time on local device
let currentTime = this._castService.player.currentTime;
this.progressPercent = Number((currentTime / this.video.nativeElement.duration).toPrecision(3)) * 100;
this.currentTime = this.formatDuration(currentTime);
// Update in UI on currentTime changes
this._cd.detectChanges();
}).then((res) => console.log(res))
.catch((err) => console.error(err));
// volumeLevel changed
this._castService.onCastEvent('VOLUME_LEVEL_CHANGED', () => {
// Change volume level in UI
this.playerVolume = this._castService.player.volumeLevel.toPrecision(2);
// Update in UI on volume changes
this._cd.detectChanges();
}).then((res) => console.log(res))
.catch((err) => console.error(err));
// Muted/unmuted
this._castService.onCastEvent('IS_MUTED_CHANGED', () => {
// Change mute icon in UI
this.isMuted = this._castService.player.isMuted;
}).then((res) => console.log(res))
.catch((err) => console.error(err));
}
// Utility functions
formatDuration(time) {
const sec = Math.floor(time % 60);
const min = Math.floor(time / 60) % 60;
const hr = Math.floor(time / 3600);
if (hr > 0) {
return `${hr}:${min < 10 ? '0' + min : min}:${sec < 10 ? '0' + sec : sec}`;
}
else {
return `${min}:${sec < 10 ? '0' + sec : sec}`;
}
}
// Toggles
toggleLoop() {
this.isLoopingEnabled = !this.isLoopingEnabled;
}
togglePlay() {
if (this.enableControls === false)
return;
if (!this.isCasting) {
if (this.video.nativeElement.currentTime === this.video.nativeElement.duration) {
this.video.nativeElement.currentTime = 0;
}
this.video.nativeElement.paused ? this.video.nativeElement.play() : this.video.nativeElement.pause();
}
else {
// Play/pause receiver
this.isPlaying ? this._castService.pause() : this._castService.play();
}
}
toggleMute() {
if (!this.isCasting) {
this.video.nativeElement.muted = !this.video.nativeElement.muted;
this.isMuted = this.video.nativeElement.muted;
}
else {
// mute/unmute receiver
this.isMuted ? this._castService.unmute() : this._castService.mute();
}
}
togglePIP() {
if (this.isPIP) {
this.document.exitPictureInPicture();
this.isPIP = false;
}
else {
this.video.nativeElement.requestPictureInPicture();
this.isPIP = true;
}
}
toggleFullscreen() {
if (this.document.fullscreenElement) {
this.document.exitFullscreen();
this.isFullscreen = false;
this.fullscreen.emit(false);
}
else {
this.videoContainer.nativeElement.requestFullscreen();
this.isFullscreen = true;
this.fullscreen.emit(true);
}
}
}
NgPlyrComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.2.0", ngImport: i0, type: NgPlyrComponent, deps: [{ token: DOCUMENT }, { token: PlayerService }, { token: CastService }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component });
NgPlyrComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "14.2.0", type: NgPlyrComponent, selector: "ng-plyr", inputs: { mediaURL: ["src", "mediaURL"], mediaType: ["type", "mediaType"], preload: "preload", playFrom: "playFrom", loadingImgSrc: "loadingImgSrc", captions: "captions", bookmarks: "bookmarks", volume: "volume", loopMedia: ["loop", "loopMedia"], isAutoplayEnabled: ["autoplay", "isAutoplayEnabled"], nextMediaToAdd: ["nextMedia", "nextMediaToAdd"], mediaItems: ["playlist", "mediaItems"], loopPlaylist: "loopPlaylist", posterUrl: ["poster", "posterUrl"], enableControls: ["controls", "enableControls"] }, outputs: { playing: "playing", paused: "paused", ended: "ended", onprev: "onprev", onnext: "onnext", fullscreen: "fullscreen", volumechange: "volumechange" }, host: { listeners: { "window:keydown": "doShortcutKeyAction($event)" } }, viewQueries: [{ propertyName: "videoContainer", first: true, predicate: ["videoContainer"], descendants: true }, { propertyName: "video", first: true, predicate: ["video"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "<div #videoContainer class=\"video-container\" [ngClass]=\"isPlaying ? '' : 'paused'\">\r\n <div class=\"video-header-container\" *ngIf=\"enableControls\">\r\n <button class=\"menu-icon-btn\" title=\"Menu\" (click)=\"isMenuSettingsOpen = !isMenuSettingsOpen\">\r\n <svg class=\"menu-icon\" viewBox=\"0 -960 960 960\" *ngIf=\"!isPlaying\">\r\n <path fill=\"currentColor\" d=\"M120-240v-80h720v80H120Zm0-200v-80h720v80H120Zm0-200v-80h720v80H120Z\"/>\r\n </svg>\r\n </button>\r\n <span class=\"title\">\r\n {{media.title}}\r\n </span>\r\n <div class=\"menu-box\" [ngClass]=\"isMenuSettingsOpen ? '': 'hidden'\">\r\n <!-- <input type=\"file\" name=\"Select file to play\" (change)=\"onLocalFileSelected($event)\"> -->\r\n <button (click)=\"stopVideo()\">Stop Playing</button>\r\n </div>\r\n </div>\r\n <div class=\"video-overlay\" (click)=\"togglePlay()\" *ngIf=\"enableControls\">\r\n <div stopClickPropagation class=\"loading\"\r\n [ngStyle]=\"{'background-image': loadingImgSrc ? 'url(' + loadingImgSrc + ')':'../assets/images/Spinner-multicolor.svg'}\"\r\n *ngIf=\"isMediaLoading\">\r\n <!-- <img [src]=\"loadingImgSrc\"> -->\r\n </div>\r\n <div class=\"video-actions\" *ngIf=\"!isMediaLoading && media.paused\">\r\n <button stopClickPropagation class=\"play-prev-btn\" title=\"Previous\" (click)=\"playPrevMedia()\" [disabled]=\"!prevMedia\">\r\n <svg class=\"play-prev-icon\" fill=\"currentColor\" viewBox=\"0 0 24 24\">\r\n <path d=\"M0 0h24v24H0z\" fill=\"none\" />\r\n <path d=\"M6 6h2v12H6zm3.5 6l8.5 6V6z\" />\r\n </svg>\r\n </button>\r\n <button stopClickPropagation class=\"play-btn\" (click)=\"togglePlay()\">\r\n <svg class=\"play-icon\" viewBox=\"0 0 24 24\" *ngIf=\"!isCasting\">\r\n <path fill=\"currentColor\" d=\"M8,5.14V19.14L19,12.14L8,5.14Z\" />\r\n </svg>\r\n <span *ngIf=\"isCasting\">\r\n <svg viewBox=\"0 -960 960 960\" title=\"Playing on TV\" *ngIf=\"isPlaying\">\r\n <path fill=\"currentColor\" d=\"M200-320h80q0-33-23.5-56.5T200-400v80Zm142 0h58q0-83-58.5-141.5T200-520v58q59 0 100.5 41.5T342-320Zm120 0h58q0-66-25-124.5t-68.5-102Q383-590 324.5-615T200-640v58q109 0 185.5 76.5T462-320ZM320-120v-80H160q-33 0-56.5-23.5T80-280v-480q0-33 23.5-56.5T160-840h640q33 0 56.5 23.5T880-760v480q0 33-23.5 56.5T800-200H640v80H320ZM160-280h640v-480H160v480Zm0 0v-480 480Z\"/>\r\n </svg>\r\n <svg class=\"cast-pause-icon\" viewBox=\"0 -960 960 960\" title=\"Casting paused\" *ngIf=\"!isPlaying\">\r\n <path fill=\"currentColor\" d=\"M750-640h40v-160h-40v160Zm-100 0h40v-160h-40v160ZM480-480ZM80-280q50 0 85 35t35 85H80v-120Zm0-160q117 0 198.5 81.5T360-160h-80q0-83-58.5-141.5T80-360v-80Zm0-160q91 0 171 34.5T391-471q60 60 94.5 140T520-160h-80q0-75-28.5-140.5t-77-114q-48.5-48.5-114-77T80-520v-80Zm720 440H600q0-20-1.5-40t-4.5-40h206v-212q22-7 42-16.5t38-22.5v251q0 33-23.5 56.5T800-160ZM80-680v-40q0-33 23.5-56.5T160-800h292q-6 19-9 39t-3 41H160v46q-20-3-40-4.5T80-680Zm640 160q-83 0-141.5-58.5T520-720q0-83 58.5-141.5T720-920q83 0 141.5 58.5T920-720q0 83-58.5 141.5T720-520Z\"/>\r\n </svg>\r\n </span>\r\n </button>\r\n <button stopClickPropagation class=\"play-next-btn\" title=\"Next\" (click)=\"playNextMedia()\" [disabled]=\"!nextMedia\">\r\n <svg class=\"play-next-icon\" fill=\"currentColor\" viewBox=\"0 0 24 24\">\r\n <path d=\"M0 0h24v24H0z\" fill=\"none\" />\r\n <path d=\"M6 18l8.5-6L6 6v12zM16 6v12h2V6h-2z\" />\r\n </svg>\r\n </button>\r\n </div>\r\n <div class=\"seek\" *ngIf=\"!isMediaLoading && !media.paused\">\r\n <button stopClickPropagation class=\"seek-back-btn\" (click)=\"seekBwd($event)\" (dblclick)=\"seekBwd($event)\">\r\n <div [ngStyle]=\"{'display': bwdClickCount>0 ? 'block' : 'none'}\">\r\n <div>{{bwdClickCount * seekStepInSec}} seconds</div>\r\n <svg class=\"seek-back-icon\" fill=\"currentColor\" viewBox=\"0 0 24 24\">\r\n <path d=\"M0 0h24v24H0z\" fill=\"none\" />\r\n <path d=\"M11 18V6l-8.5 6 8.5 6zm.5-6l8.5 6V6l-8.5 6z\" />\r\n </svg>\r\n </div>\r\n </button>\r\n <button stopClickPropagation class=\"seek-ahead-btn\" (click)=\"seekFwd($event)\" (dblclick)=\"seekFwd($event)\">\r\n <div [ngStyle]=\"{'display': fwdClickCount>0 ? 'block' : 'none'}\">\r\n <div>{{fwdClickCount * seekStepInSec}} seconds</div>\r\n <svg class=\"seek-ahead-icon\" fill=\"currentColor\" viewBox=\"0 0 24 24\">\r\n <path d=\"M0 0h24v24H0z\" fill=\"none\" />\r\n <path d=\"M4 18l8.5-6L4 6v12zm9-12v12l8.5-6L13 6z\" />\r\n </svg>\r\n </div> \r\n </button>\r\n </div>\r\n </div>\r\n <div class=\"video-controls-container\" *ngIf=\"enableControls\">\r\n <div class=\"timeline-container\">\r\n <img src=\"\" class=\"preview-img\">\r\n <input #progressBar type=\"range\" class=\"timeline-progress\" (input)=\"seekTo(progressBar.value)\"\r\n [value]=\"media.playFrom\" [max]=\"media.duration\">\r\n <div class=\"track\">\r\n <div class=\"track-progress\" [ngStyle]=\"{'width':progressPercent + '%'}\"></div>\r\n <div class=\"track-buffers\">\r\n <span class=\"buffer\" *ngFor=\"let buffer of mediaBuffers\"\r\n [ngStyle]=\"{'left':buffer.start + '%', 'width':buffer.end - buffer.start + '%'}\"></span>\r\n </div>\r\n <div class=\"track-markers\">\r\n <span class=\"marker\" *ngFor=\"let marker of mediaMarkers\" [ngStyle]=\"{'left':marker + '%'}\"></span>\r\n </div>\r\n </div>\r\n <div class=\"thumb\" [ngStyle]=\"{'left':progressPercent + '%', 'transform': 'translate(-'+ progressPercent + '%,-50%)'}\"></div>\r\n </div>\r\n <div class=\"controls\">\r\n <button class=\"play-pause-btn\" [title]=\"isPlaying ? 'Pause (k)' : 'Play (k)'\" (click)=\"togglePlay()\">\r\n <svg class=\"play-icon\" viewBox=\"0 0 24 24\" *ngIf=\"!isPlaying\">\r\n <path fill=\"currentColor\" d=\"M8,5.14V19.14L19,12.14L8,5.14Z\" />\r\n </svg>\r\n <svg class=\"pause-icon\" viewBox=\"0 0 24 24\" *ngIf=\"isPlaying\">\r\n <path fill=\"currentColor\" d=\"M14,19H18V5H14M6,19H10V5H6V19Z\" />\r\n </svg>\r\n </button>\r\n <button class=\"play-next-btn\" title=\"Next\" (click)=\"playNextMedia()\" [disabled]=\"!nextMedia\">\r\n <svg class=\"play-next-icon\" fill=\"currentColor\" viewBox=\"0 0 24 24\">\r\n <path d=\"M0 0h24v24H0z\" fill=\"none\" />\r\n <path d=\"M6 18l8.5-6L6 6v12zM16 6v12h2V6h-2z\" />\r\n </svg>\r\n </button>\r\n <div class=\"volume-container\">\r\n <button class=\"volume-btn\" [title]=\"isMuted ? 'Unmute (m)' : 'Mute (m)'\" (click)=\"toggleMute()\">\r\n <svg class=\"volume-high-icon\" viewBox=\"0 0 24 24\"\r\n *ngIf=\"!isMuted && playerVolume>.5 && playerVolume<=1\">\r\n <path fill=\"currentColor\"\r\n d=\"M14,3.23V5.29C16.89,6.15 19,8.83 19,12C19,15.17 16.89,17.84 14,18.7V20.77C18,19.86 21,16.28 21,12C21,7.72 18,4.14 14,3.23M16.5,12C16.5,10.23 15.5,8.71 14,7.97V16C15.5,15.29 16.5,13.76 16.5,12M3,9V15H7L12,20V4L7,9H3Z\" />\r\n </svg>\r\n <svg class=\"volume-low-icon\" viewBox=\"0 0 24 24\"\r\n *ngIf=\"!isMuted && playerVolume>0 && playerVolume<=.5\">\r\n <path fill=\"currentColor\"\r\n d=\"M5,9V15H9L14,20V4L9,9M18.5,12C18.5,10.23 17.5,8.71 16,7.97V16C17.5,15.29 18.5,13.76 18.5,12Z\" />\r\n </svg>\r\n <svg class=\"volume-muted-icon\" viewBox=\"0 0 24 24\" *ngIf=\"isMuted || playerVolume==0\">\r\n <path fill=\"currentColor\"\r\n d=\"M12,4L9.91,6.09L12,8.18M4.27,3L3,4.27L7.73,9H3V15H7L12,20V13.27L16.25,17.53C15.58,18.04 14.83,18.46 14,18.7V20.77C15.38,20.45 16.63,19.82 17.68,18.96L19.73,21L21,19.73L12,10.73M19,12C19,12.94 18.8,13.82 18.46,14.64L19.97,16.15C20.62,14.91 21,13.5 21,12C21,7.72 18,4.14