UNPKG

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
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