From 7edbd29fec0a12a751b69432a264ca84d95db131 Mon Sep 17 00:00:00 2001 From: hawken Date: Fri, 1 Jan 2021 05:19:12 +0100 Subject: [PATCH] refactor window.playlist into playbackmanager and make playbackmanager static --- src/app.ts | 2 - src/components/commandHandler.ts | 25 ++---- src/components/maincontroller.ts | 27 +++--- src/components/playbackManager.ts | 141 +++++++++++++++++++++--------- src/helpers.ts | 49 +---------- src/types/global.d.ts | 2 - 6 files changed, 125 insertions(+), 121 deletions(-) diff --git a/src/app.ts b/src/app.ts index add4403b..c63630c3 100644 --- a/src/app.ts +++ b/src/app.ts @@ -5,6 +5,4 @@ import './css/jellyfin.css'; window.mediaElement = document.getElementById('video-player'); -window.playlist = []; -window.currentPlaylistIndex = -1; window.repeatMode = RepeatMode.RepeatNone; diff --git a/src/components/commandHandler.ts b/src/components/commandHandler.ts index cb307787..4b2c0cc5 100644 --- a/src/components/commandHandler.ts +++ b/src/components/commandHandler.ts @@ -19,13 +19,12 @@ import { import { reportPlaybackProgress } from './jellyfinActions'; -import { playbackManager } from './playbackManager'; +import { PlaybackManager } from './playbackManager'; import { DocumentManager } from './documentManager'; export abstract class CommandHandler { private static playerManager: cast.framework.PlayerManager; - private static playbackManager: playbackManager; private static supportedCommands: SupportedCommands = { PlayNext: CommandHandler.playNextHandler, PlayNow: CommandHandler.playNowHandler, @@ -52,12 +51,8 @@ export abstract class CommandHandler { Unpause: CommandHandler.UnpauseHandler }; - static configure( - playerManager: cast.framework.PlayerManager, - playbackManager: playbackManager - ): void { + static configure(playerManager: cast.framework.PlayerManager): void { this.playerManager = playerManager; - this.playbackManager = playbackManager; } static playNextHandler(data: DataMessage): void { @@ -89,24 +84,18 @@ export abstract class CommandHandler { } static displayContentHandler(data: DataMessage): void { - if (!this.playbackManager.isPlaying()) { + if (!PlaybackManager.isPlaying()) { DocumentManager.showItemId((data.options).ItemId); } } static nextTrackHandler(): void { - if ( - window.playlist && - window.currentPlaylistIndex < window.playlist.length - 1 - ) { - this.playbackManager.playNextItem({}, true); - } + if (PlaybackManager.hasNextItem()) + PlaybackManager.playNextItem({}, true); } static previousTrackHandler(): void { - if (window.playlist && window.currentPlaylistIndex > 0) { - this.playbackManager.playPreviousItem({}); - } + if (PlaybackManager.hasPrevItem()) PlaybackManager.playPreviousItem({}); } static setAudioStreamIndexHandler(data: DataMessage): void { @@ -138,7 +127,7 @@ export abstract class CommandHandler { } static IdentifyHandler(): void { - if (!this.playbackManager.isPlaying()) { + if (!PlaybackManager.isPlaying()) { DocumentManager.startBackdropInterval(); } else { // When a client connects send back the initial device state (volume etc) via a playbackstop message diff --git a/src/components/maincontroller.ts b/src/components/maincontroller.ts index 488476d5..70b09817 100644 --- a/src/components/maincontroller.ts +++ b/src/components/maincontroller.ts @@ -21,7 +21,7 @@ import { } from './jellyfinActions'; import { getDeviceProfile } from './deviceprofileBuilder'; import { JellyfinApi } from './jellyfinApi'; -import { playbackManager } from './playbackManager'; +import { PlaybackManager } from './playbackManager'; import { CommandHandler } from './commandHandler'; import { getMaxBitrateSupport } from './codecSupportHelper'; import { DocumentManager } from './documentManager'; @@ -34,9 +34,9 @@ import { GlobalScope, PlayRequest } from '~/types/global'; window.castReceiverContext = cast.framework.CastReceiverContext.getInstance(); window.playerManager = window.castReceiverContext.getPlayerManager(); -const playbackMgr = new playbackManager(window.playerManager); +PlaybackManager.setPlayerManager(window.playerManager); -CommandHandler.configure(window.playerManager, playbackMgr); +CommandHandler.configure(window.playerManager); resetPlaybackScope($scope); @@ -146,7 +146,7 @@ window.playerManager.addEventListener( ); function defaultOnStopped(): void { - playbackMgr.onStopped(true); + PlaybackManager.onStopped(); } window.playerManager.addEventListener( @@ -168,10 +168,9 @@ window.playerManager.addEventListener( reportPlaybackStopped($scope, getReportingParams($scope)); resetPlaybackScope($scope); - if (!playbackMgr.playNextItem()) { - window.playlist = []; - window.currentPlaylistIndex = -1; - DocumentManager.startBackdropInterval(); + if (!PlaybackManager.playNextItem()) { + PlaybackManager.resetPlaylist(); + PlaybackManager.onStopped(); } } ); @@ -403,7 +402,7 @@ export async function changeStream( // await stopActiveEncodings($scope.playSessionId); //} - return await playbackMgr.playItemInternal($scope.item, { + return await PlaybackManager.playItemInternal($scope.item, { audioStreamIndex: params.AudioStreamIndex == null ? $scope.audioStreamIndex @@ -458,10 +457,10 @@ export function translateItems( i < length; i++ ) { - window.playlist.push(options.items[i]); + PlaybackManager.enqueue(options.items[i]); } } else { - playbackMgr.playFromOptions(data.options); + PlaybackManager.playFromOptions(data.options); } } ); @@ -474,7 +473,7 @@ export function instantMix( ): Promise { return getInstantMixItems(data.userId, item).then(function (result) { options.items = result.Items; - playbackMgr.playFromOptions(data.options); + PlaybackManager.playFromOptions(data.options); }); } @@ -485,7 +484,7 @@ export function shuffle( ): Promise { return getShuffleItems(data.userId, item).then(function (result) { options.items = result.Items; - playbackMgr.playFromOptions(data.options); + PlaybackManager.playFromOptions(data.options); }); } @@ -508,7 +507,7 @@ export function onStopPlayerBeforePlaybackDone( dataType: 'json', type: 'GET' }).then( - (data) => playbackMgr.playItemInternal(data, options), + (data) => PlaybackManager.playItemInternal(data, options), broadcastConnectionErrorMessage ); } diff --git a/src/components/playbackManager.ts b/src/components/playbackManager.ts index 9635c4b5..6523ddbb 100644 --- a/src/components/playbackManager.ts +++ b/src/components/playbackManager.ts @@ -1,5 +1,4 @@ import { - getNextPlaybackItemInfo, getIntros, broadcastConnectionErrorMessage, createStreamInfo @@ -26,19 +25,18 @@ import { DocumentManager } from './documentManager'; import { BaseItemDto } from '~/api/generated/models/base-item-dto'; import { MediaSourceInfo } from '~/api/generated/models/media-source-info'; -export class playbackManager { - private playerManager: cast.framework.PlayerManager; - // TODO remove any - private activePlaylist: Array; - private activePlaylistIndex: number; +import { ItemIndex } from '~/types/global'; - constructor(playerManager: cast.framework.PlayerManager) { +export abstract class PlaybackManager { + private static playerManager: cast.framework.PlayerManager; + private static activePlaylist: Array; + private static activePlaylistIndex: number; + + static setPlayerManager(playerManager: cast.framework.PlayerManager): void { // Parameters this.playerManager = playerManager; - // Properties - this.activePlaylist = []; - this.activePlaylistIndex = 0; + this.resetPlaylist(); } /* This is used to check if we can switch to @@ -47,7 +45,7 @@ export class playbackManager { * Returns true when playing or paused. * (before: true only when playing) * */ - isPlaying(): boolean { + static isPlaying(): boolean { return ( this.playerManager.getPlayerState() === cast.framework.messages.PlayerState.PLAYING || @@ -56,7 +54,7 @@ export class playbackManager { ); } - async playFromOptions(options: any): Promise { + static async playFromOptions(options: any): Promise { const firstItem = options.items[0]; if (options.startPositionTicks || firstItem.MediaType !== 'Video') { @@ -68,46 +66,64 @@ export class playbackManager { return this.playFromOptionsInternal(options); } - playFromOptionsInternal(options: any): boolean { + private static playFromOptionsInternal(options: any): boolean { const stopPlayer = this.activePlaylist && this.activePlaylist.length > 0; this.activePlaylist = options.items; - window.currentPlaylistIndex = -1; - window.playlist = this.activePlaylist; + // We need to set -1 so the next index will be 0 + this.activePlaylistIndex = -1; + + console.log('Loaded new playlist:', this.activePlaylist); return this.playNextItem(options, stopPlayer); } - playNextItem(options: any = {}, stopPlayer = false): boolean { - const nextItemInfo = getNextPlaybackItemInfo(); + // add item to playlist + static enqueue(item: BaseItemDto): void { + this.activePlaylist.push(item); + } + + static resetPlaylist(): void { + this.activePlaylistIndex = -1; + this.activePlaylist = []; + } + + // If there are items in the queue after the current one + static hasNextItem(): boolean { + return this.activePlaylistIndex < this.activePlaylist.length - 1; + } + + // If there are items in the queue before the current one + static hasPrevItem(): boolean { + return this.activePlaylistIndex > 0; + } + + static playNextItem(options: any = {}, stopPlayer = false): boolean { + const nextItemInfo = this.getNextPlaybackItemInfo(); if (nextItemInfo) { this.activePlaylistIndex = nextItemInfo.index; - const item = nextItemInfo.item; - - this.playItem(item, options, stopPlayer); + this.playItem(options, stopPlayer); return true; } return false; } - playPreviousItem(options: any = {}): boolean { + static playPreviousItem(options: any = {}): boolean { if (this.activePlaylist && this.activePlaylistIndex > 0) { this.activePlaylistIndex--; - const item = this.activePlaylist[this.activePlaylistIndex]; - - this.playItem(item, options, true); + this.playItem(options, true); return true; } return false; } - async playItem( - item: BaseItemDto, + // play item from playlist + private static async playItem( options: any, stopPlayer = false ): Promise { @@ -115,10 +131,18 @@ export class playbackManager { this.stop(); } + const item = this.activePlaylist[this.activePlaylistIndex]; + + console.log(`Playing index ${this.activePlaylistIndex}`, item); + return await onStopPlayerBeforePlaybackDone(item, options); } - async playItemInternal(item: BaseItemDto, options: any): Promise { + // Would set private, but some refactorings need to happen first. + static async playItemInternal( + item: BaseItemDto, + options: any + ): Promise { $scope.isChangingStream = false; DocumentManager.setAppStatus('loading'); @@ -175,8 +199,7 @@ export class playbackManager { ); } - // TODO eradicate any - playMediaSource( + private static playMediaSource( playSessionId: string, item: BaseItemDto, mediaSource: MediaSourceInfo, @@ -225,7 +248,7 @@ export class playbackManager { /** * stop playback, as requested by the client */ - stop(): void { + static stop(): void { this.playerManager.stop(); // onStopped will be called when playback comes to a halt. } @@ -233,18 +256,58 @@ export class playbackManager { /** * Called when media stops playing. * TODO avoid doing this between tracks in a playlist - * - * @param {boolean} _continue If we have another item coming up in the playlist. Only used for notifying the cast sender. */ - onStopped(_continue: boolean): void { - $scope.playNextItem = _continue; + static onStopped(): void { + if (this.getNextPlaybackItemInfo()) { + $scope.playNextItem = true; + } else { + $scope.playNextItem = false; - DocumentManager.setAppStatus('waiting'); + DocumentManager.setAppStatus('waiting'); - stopPingInterval(); + stopPingInterval(); - this.activePlaylist = []; - this.activePlaylistIndex = -1; - DocumentManager.startBackdropInterval(); + DocumentManager.startBackdropInterval(); + } + } + + /** + * Get information about the next item to play from window.playlist + * + * @returns {ItemIndex | null} item and index, or null to end playback + */ + static getNextPlaybackItemInfo(): ItemIndex | null { + if (this.activePlaylist.length < 1) { + return null; + } + + let newIndex: number; + + if (this.activePlaylistIndex < 0) { + // negative = play the first item + newIndex = 0; + } else + switch (window.repeatMode) { + case 'RepeatOne': + newIndex = this.activePlaylistIndex; + break; + case 'RepeatAll': + newIndex = this.activePlaylistIndex + 1; + if (newIndex >= this.activePlaylist.length) { + newIndex = 0; + } + break; + default: + newIndex = this.activePlaylistIndex + 1; + break; + } + + if (newIndex < this.activePlaylist.length) { + return { + item: this.activePlaylist[newIndex], + index: newIndex + }; + } + return null; } } diff --git a/src/helpers.ts b/src/helpers.ts index 77cb6a0c..7dc3ebf3 100644 --- a/src/helpers.ts +++ b/src/helpers.ts @@ -1,5 +1,6 @@ import { JellyfinApi } from './components/jellyfinApi'; import { DocumentManager } from './components/documentManager'; +import { PlaybackManager } from './components/playbackManager'; import { BaseItemDtoQueryResult } from './api/generated/models/base-item-dto-query-result'; import { PlaybackProgressInfo } from './api/generated/models/playback-progress-info'; @@ -7,7 +8,7 @@ import { MediaSourceInfo } from './api/generated/models/media-source-info'; import { BaseItemDto } from './api/generated/models/base-item-dto'; import { BaseItemPerson } from './api/generated/models/base-item-person'; import { UserDto } from './api/generated/models/user-dto'; -import { GlobalScope, BusMessage, ItemIndex, ItemQuery } from './types/global'; +import { GlobalScope, BusMessage, ItemQuery } from './types/global'; /** * Get current playback position in ticks, adjusted for server seeking @@ -56,50 +57,6 @@ export function getReportingParams($scope: GlobalScope): PlaybackProgressInfo { }; } -/** - * Get information about the next item to play from window.playlist - * - * @returns ItemIndex including item and index, or null to end playback - */ -export function getNextPlaybackItemInfo(): ItemIndex | null { - const playlist = window.playlist; - - if (!playlist) { - return null; - } - - let newIndex: number; - - if (window.currentPlaylistIndex == -1) { - newIndex = 0; - } else { - switch (window.repeatMode) { - case 'RepeatOne': - newIndex = window.currentPlaylistIndex; - break; - case 'RepeatAll': - newIndex = window.currentPlaylistIndex + 1; - if (newIndex >= window.playlist.length) { - newIndex = 0; - } - break; - default: - newIndex = window.currentPlaylistIndex + 1; - break; - } - } - - if (newIndex < playlist.length) { - const item = playlist[newIndex]; - - return { - item: item, - index: newIndex - }; - } - return null; -} - /** * This is used in playback reporting to find out information * about the item that is currently playing. This is sent over the cast protocol over to @@ -192,7 +149,7 @@ export function getSenderReportingData( } if ($scope.playNextItem) { - const nextItemInfo = getNextPlaybackItemInfo(); + const nextItemInfo = PlaybackManager.getNextPlaybackItemInfo(); if (nextItemInfo) { state.NextMediaType = nextItemInfo.item.MediaType; diff --git a/src/types/global.d.ts b/src/types/global.d.ts index beee21b7..76711620 100644 --- a/src/types/global.d.ts +++ b/src/types/global.d.ts @@ -93,8 +93,6 @@ declare global { mediaElement: HTMLElement | null; playerManager: cast.framework.PlayerManager; castReceiverContext: cast.framework.CastReceiverContext; - playlist: Array; - currentPlaylistIndex: number; repeatMode: RepeatMode; reportEventType: 'repeatmodechange'; subtitleAppearance: any;