From 5f96fe0a77eba95deb4080024cd8a704118f0299 Mon Sep 17 00:00:00 2001 From: hawken Date: Thu, 31 Dec 2020 23:56:30 +0100 Subject: [PATCH 01/29] use window width instead because HLS is strict --- src/components/codecSupportHelper.ts | 34 +++++++++++++++----------- src/components/deviceprofileBuilder.ts | 2 +- 2 files changed, 21 insertions(+), 15 deletions(-) diff --git a/src/components/codecSupportHelper.ts b/src/components/codecSupportHelper.ts index bc4ffb2f..bf15e024 100644 --- a/src/components/codecSupportHelper.ts +++ b/src/components/codecSupportHelper.ts @@ -102,22 +102,28 @@ export function getMaxBitrateSupport(): number { /** * Get the max supported video width the active Cast device supports. * - * @param deviceId - Cast device id. * @returns Max supported width. */ -export function getMaxWidthSupport(deviceId: number): number { - switch (deviceId) { - case deviceIds.ULTRA: - case deviceIds.CCGTV: - return 3840; - case deviceIds.GEN1AND2: - case deviceIds.GEN3: - return 1920; - case deviceIds.NESTHUBANDMAX: - return 1280; - } - - return 0; +export function getMaxWidthSupport(): number { + // with HLS, it will produce a manifest error if we + // send any stream larger than the screen size... + return window.innerWidth; + + // mkv playback can use the device limitations. + // The devices are capable of decoding and downscaling, + // they just refuse to do it with HLS. This increases + // the rate of direct playback. + //switch (deviceId) { + // case deviceIds.ULTRA: + // case deviceIds.CCGTV: + // return 3840; + // case deviceIds.GEN1AND2: + // case deviceIds.GEN3: + // return 1920; + // case deviceIds.NESTHUBANDMAX: + // return 1280; + //} + //return 0; } /** diff --git a/src/components/deviceprofileBuilder.ts b/src/components/deviceprofileBuilder.ts index 7ba74082..52afb353 100644 --- a/src/components/deviceprofileBuilder.ts +++ b/src/components/deviceprofileBuilder.ts @@ -195,7 +195,7 @@ function getCodecProfiles(): Array { CodecProfiles.push(aacConditions); - const maxWidth: number = getMaxWidthSupport(currentDeviceId); + const maxWidth: number = getMaxWidthSupport(); const h26xLevel: number = getH26xLevelSupport(currentDeviceId); const h26xProfile: string = getH26xProfileSupport(currentDeviceId); From 114aab79137f2c0b32ac1cd3ed15ef7697dbd04b Mon Sep 17 00:00:00 2001 From: hawken Date: Mon, 21 Dec 2020 20:07:05 +0100 Subject: [PATCH 02/29] Fix start seek --- src/components/playbackManager.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/components/playbackManager.ts b/src/components/playbackManager.ts index f59b9786..70921137 100644 --- a/src/components/playbackManager.ts +++ b/src/components/playbackManager.ts @@ -213,6 +213,13 @@ export class playbackManager { loadRequestData.media = mediaInfo; loadRequestData.autoplay = true; + // If we should seek at the start, translate it + // to seconds and give it to loadRequestData :) + if (mediaInfo.customData.startPositionTicks > 0) { + loadRequestData.currentTime = + mediaInfo.customData.startPositionTicks / 10000000; + } + load($scope, mediaInfo.customData, item); this.playerManager.load(loadRequestData); From 18b3db2c1135d8363697dec3eab5888fbdcd4f79 Mon Sep 17 00:00:00 2001 From: hawken Date: Mon, 21 Dec 2020 21:16:31 +0100 Subject: [PATCH 03/29] add event listeners for start/stop reporting where the player probably has a useful position --- src/components/maincontroller.ts | 17 ++++++++++++++++- src/components/playbackManager.ts | 21 ++------------------- 2 files changed, 18 insertions(+), 20 deletions(-) diff --git a/src/components/maincontroller.ts b/src/components/maincontroller.ts index aef5bf27..33030cbd 100644 --- a/src/components/maincontroller.ts +++ b/src/components/maincontroller.ts @@ -12,6 +12,7 @@ import { broadcastConnectionErrorMessage } from '../helpers'; import { + reportPlaybackStart, reportPlaybackProgress, reportPlaybackStopped, play, @@ -154,7 +155,6 @@ window.playerManager.addEventListener( cast.framework.events.EventType.PLAY, (): void => { play($scope); - reportPlaybackProgress($scope, getReportingParams($scope)); } ); @@ -200,6 +200,21 @@ window.playerManager.addEventListener( } ); +// Notify of playback start as soon as the media is playing. Only then is the tick position good. +window.playerManager.addEventListener( + cast.framework.events.EventType.PLAYING, + (): void => { + reportPlaybackStart($scope, getReportingParams($scope)); + } +); +// Notify of playback end just before stopping it, to get a good tick position +window.playerManager.addEventListener( + cast.framework.events.EventType.REQUEST_STOP, + (): void => { + reportPlaybackStopped($scope, getReportingParams($scope)); + } +); + // Set the active subtitle track once the player has loaded window.playerManager.addEventListener( cast.framework.events.EventType.PLAYER_LOAD_COMPLETE, diff --git a/src/components/playbackManager.ts b/src/components/playbackManager.ts index 70921137..49b5bec2 100644 --- a/src/components/playbackManager.ts +++ b/src/components/playbackManager.ts @@ -2,7 +2,6 @@ import { getNextPlaybackItemInfo, getIntros, broadcastConnectionErrorMessage, - getReportingParams, createStreamInfo } from '../helpers'; @@ -10,10 +9,8 @@ import { getPlaybackInfo, getLiveStream, load, - reportPlaybackStart, stop, - stopPingInterval, - reportPlaybackStopped + stopPingInterval } from './jellyfinActions'; import { getDeviceProfile } from './deviceprofileBuilder'; @@ -230,36 +227,22 @@ export class playbackManager { DocumentManager.setPlayerBackdrop(item); - reportPlaybackStart($scope, getReportingParams($scope)); - // We use false as we do not want to broadcast the new status yet // we will broadcast manually when the media has been loaded, this // is to be sure the duration has been updated in the media element this.playerManager.setMediaInformation(mediaInfo, false); } - stop(continuing = false): Promise { + stop(continuing = false): void { $scope.playNextItem = continuing; stop(); - const reportingParams = getReportingParams($scope); - - let promise; - stopPingInterval(); - if (reportingParams.ItemId) { - promise = reportPlaybackStopped($scope, reportingParams); - } - this.playerManager.stop(); this.activePlaylist = []; this.activePlaylistIndex = -1; DocumentManager.startBackdropInterval(); - - promise = promise || Promise.resolve(); - - return promise; } } From a76e8b8bf81dff46844b2250cb2afbb008e71a0c Mon Sep 17 00:00:00 2001 From: hawken Date: Fri, 25 Dec 2020 17:30:12 +0100 Subject: [PATCH 04/29] fix stream change --- src/components/maincontroller.ts | 104 +++++++----------------------- src/components/playbackManager.ts | 9 +-- 2 files changed, 29 insertions(+), 84 deletions(-) diff --git a/src/components/maincontroller.ts b/src/components/maincontroller.ts index 33030cbd..ae147a47 100644 --- a/src/components/maincontroller.ts +++ b/src/components/maincontroller.ts @@ -3,7 +3,6 @@ import { getReportingParams, resetPlaybackScope, getMetadata, - createStreamInfo, getStreamByIndex, getShuffleItems, getInstantMixItems, @@ -16,8 +15,6 @@ import { reportPlaybackProgress, reportPlaybackStopped, play, - getPlaybackInfo, - stopActiveEncodings, detectBitrate } from './jellyfinActions'; import { getDeviceProfile } from './deviceprofileBuilder'; @@ -450,71 +447,31 @@ export async function changeStream( params = params || {}; - const playSessionId = $scope.playSessionId; - const liveStreamId = $scope.liveStreamId; - - const item = $scope.item; - const maxBitrate = await getMaxBitrate(); - - const deviceProfile = getDeviceProfile({ - bitrateSetting: maxBitrate, - enableHls: true + // TODO Could be useful for garbage collection. + // It needs to predict if the server side transcode needs + // to restart. + // Possibility: Always assume it will. Downside: VTT subs switching doesn't + // need to restart the transcode. + //const requiresStoppingTranscoding = false; + // + //if (requiresStoppingTranscoding) { + // window.playerManager.pause(); + // await stopActiveEncodings($scope.playSessionId); + //} + + return await playbackMgr.playItemInternal($scope.item, { + audioStreamIndex: + params.AudioStreamIndex == null + ? $scope.audioStreamIndex + : params.AudioStreamIndex, + liveStreamId: $scope.liveStreamId, + mediaSourceId: $scope.mediaSourceId, + startPositionTicks: ticks, + subtitleStreamIndex: + params.SubtitleStreamIndex == null + ? $scope.subtitleStreamIndex + : params.SubtitleStreamIndex }); - const audioStreamIndex = - params.AudioStreamIndex == null - ? $scope.audioStreamIndex - : params.AudioStreamIndex; - const subtitleStreamIndex = - params.SubtitleStreamIndex == null - ? $scope.subtitleStreamIndex - : params.SubtitleStreamIndex; - - const playbackInformation = await getPlaybackInfo( - item, - maxBitrate, - deviceProfile, - ticks, - $scope.mediaSourceId, - audioStreamIndex, - subtitleStreamIndex, - liveStreamId - ); - - if (!validatePlaybackInfoResult(playbackInformation)) { - return; - } - - const mediaSource = playbackInformation.MediaSources[0]; - const streamInfo = createStreamInfo(item, mediaSource, ticks); - - if (!streamInfo.url) { - showPlaybackInfoErrorMessage('NoCompatibleStream'); - - return; - } - - const mediaInformation = createMediaInformation( - playSessionId, - item, - streamInfo - ); - const loadRequest = new cast.framework.messages.LoadRequestData(); - - loadRequest.media = mediaInformation; - loadRequest.autoplay = true; - - // TODO something to do with HLS? - const requiresStoppingTranscoding = false; - - if (requiresStoppingTranscoding) { - window.playerManager.pause(); - await stopActiveEncodings(playSessionId); - } - - window.playerManager.load(loadRequest); - window.playerManager.play(); - $scope.subtitleStreamIndex = subtitleStreamIndex; - $scope.audioStreamIndex = audioStreamIndex; } // Create a message handler for the custome namespace channel @@ -665,19 +622,6 @@ export async function getMaxBitrate(): Promise { } } -/** - * @param result - */ -export function validatePlaybackInfoResult(result: any): boolean { - if (result.ErrorCode) { - showPlaybackInfoErrorMessage(result.ErrorCode); - - return false; - } - - return true; -} - /** * @param error */ diff --git a/src/components/playbackManager.ts b/src/components/playbackManager.ts index 49b5bec2..3e0b8249 100644 --- a/src/components/playbackManager.ts +++ b/src/components/playbackManager.ts @@ -140,7 +140,8 @@ export class playbackManager { options.startPositionTicks, options.mediaSourceId, options.audioStreamIndex, - options.subtitleStreamIndex + options.subtitleStreamIndex, + options.liveStreamId ).catch(broadcastConnectionErrorMessage); if (playbackInfo.ErrorCode) { @@ -198,8 +199,6 @@ export class playbackManager { options.startPositionTicks ); - const url = streamInfo.url; - const mediaInfo = createMediaInformation( playSessionId, item, @@ -220,10 +219,12 @@ export class playbackManager { load($scope, mediaInfo.customData, item); this.playerManager.load(loadRequestData); + console.log(`setting src to ${streamInfo.url}`); $scope.PlaybackMediaSource = mediaSource; - console.log(`setting src to ${url}`); $scope.mediaSource = mediaSource; + $scope.audioStreamIndex = streamInfo.audioStreamIndex; + $scope.subtitleStreamIndex = streamInfo.subtitleStreamIndex; DocumentManager.setPlayerBackdrop(item); From 94aba9c030b39ca0c34f2995d86e602c9bb55ee0 Mon Sep 17 00:00:00 2001 From: hawken Date: Fri, 25 Dec 2020 23:11:26 +0100 Subject: [PATCH 05/29] fix playerManager.stop -> onStopped to avoid telling the cast to stop when it shouldn't --- src/components/jellyfinActions.ts | 9 --------- src/components/maincontroller.ts | 8 ++++---- src/components/playbackManager.ts | 26 +++++++++++++++++++------- 3 files changed, 23 insertions(+), 20 deletions(-) diff --git a/src/components/jellyfinActions.ts b/src/components/jellyfinActions.ts index cbce5eb4..07ed833d 100644 --- a/src/components/jellyfinActions.ts +++ b/src/components/jellyfinActions.ts @@ -250,15 +250,6 @@ export function play($scope: GlobalScope): void { } } -/** - * Don't actually stop, just show the idle view after 20ms - */ -export function stop(): void { - setTimeout(() => { - DocumentManager.setAppStatus('waiting'); - }, 20); -} - /** * @param item * @param maxBitrate diff --git a/src/components/maincontroller.ts b/src/components/maincontroller.ts index ae147a47..92c1c9e6 100644 --- a/src/components/maincontroller.ts +++ b/src/components/maincontroller.ts @@ -165,17 +165,17 @@ window.playerManager.addEventListener( /** * */ -function defaultOnStop(): void { - playbackMgr.stop(); +function defaultOnStopped(): void { + playbackMgr.onStopped(true); } window.playerManager.addEventListener( cast.framework.events.EventType.MEDIA_FINISHED, - defaultOnStop + defaultOnStopped ); window.playerManager.addEventListener( cast.framework.events.EventType.ABORT, - defaultOnStop + defaultOnStopped ); window.playerManager.addEventListener( diff --git a/src/components/playbackManager.ts b/src/components/playbackManager.ts index 3e0b8249..ff9a4462 100644 --- a/src/components/playbackManager.ts +++ b/src/components/playbackManager.ts @@ -9,7 +9,6 @@ import { getPlaybackInfo, getLiveStream, load, - stop, stopPingInterval } from './jellyfinActions'; import { getDeviceProfile } from './deviceprofileBuilder'; @@ -118,7 +117,7 @@ export class playbackManager { stopPlayer = false ): Promise { if (stopPlayer) { - await this.stop(true); + this.stop(); } return await onStopPlayerBeforePlaybackDone(item, options); @@ -234,13 +233,26 @@ export class playbackManager { this.playerManager.setMediaInformation(mediaInfo, false); } - stop(continuing = false): void { - $scope.playNextItem = continuing; - stop(); + /** + * stop playback, as requested by the client + */ + stop(): void { + this.playerManager.stop(); + // onStopped will be called when playback comes to a halt. + } - stopPingInterval(); + /** + * Called when media stops playing. + * TODO avoid doing this between tracks in a playlist + * + * @param _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; - this.playerManager.stop(); + DocumentManager.setAppStatus('waiting'); + + stopPingInterval(); this.activePlaylist = []; this.activePlaylistIndex = -1; From 921467a4b9de97c3da6d06ba0fdc03fe83b41ab6 Mon Sep 17 00:00:00 2001 From: hawken Date: Tue, 29 Dec 2020 17:02:12 +0100 Subject: [PATCH 06/29] getMaxBitrate: Always cap to device max bitrate --- src/components/maincontroller.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/maincontroller.ts b/src/components/maincontroller.ts index 92c1c9e6..ca7a2244 100644 --- a/src/components/maincontroller.ts +++ b/src/components/maincontroller.ts @@ -613,7 +613,7 @@ export async function getMaxBitrate(): Promise { lastBitrateDetect = new Date().getTime(); detectedBitrate = bitrate; - return detectedBitrate; + return Math.min(detectedBitrate, getMaxBitrateSupport()); } catch (e) { // The client can set this number console.log('Error detecting bitrate, will return device maximum.'); From 482cf586c11cdc359160eed8121991cbee07b4e5 Mon Sep 17 00:00:00 2001 From: hawken Date: Tue, 29 Dec 2020 17:10:21 +0100 Subject: [PATCH 07/29] try to get more accurate bitrate --- src/components/jellyfinActions.ts | 30 ++++++++++++++++++++---------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/src/components/jellyfinActions.ts b/src/components/jellyfinActions.ts index 07ed833d..a94a30b6 100644 --- a/src/components/jellyfinActions.ts +++ b/src/components/jellyfinActions.ts @@ -369,11 +369,14 @@ export async function getDownloadSpeed(byteSize: number): Promise { const now = new Date().getTime(); - await JellyfinApi.authAjax(path, { + const response = await JellyfinApi.authAjax(path, { timeout: 5000, type: 'GET' }); + // Force javascript to download the whole response before calculating bitrate + await response.blob(); + const responseTimeSeconds = (new Date().getTime() - now) / 1000; const bytesPerSecond = byteSize / responseTimeSeconds; const bitrate = Math.round(bytesPerSecond * 8); @@ -383,22 +386,29 @@ export async function getDownloadSpeed(byteSize: number): Promise { /** * Function to detect the bitrate. - * It first tries 1MB and if bitrate is above 1Mbit/s it tries again with 2.4MB. + * It starts at 500kB and doubles it every time it takes under 2s, for max 10MB. + * This should get an accurate bitrate relatively fast on any connection * + * @param numBytes - Number of bytes to start with, default 500k * @returns bitrate in bits/s */ -export async function detectBitrate(): Promise { - // First try a small amount so that we don't hang up their mobile connection - - let bitrate = await getDownloadSpeed(1000000); +export async function detectBitrate(numBytes = 500000): Promise { + // Jellyfin has a 10MB limit on the test size + const byteLimit = 10000000; - if (bitrate < 1000000) { - return Math.round(bitrate * 0.8); + if (numBytes > byteLimit) { + numBytes = byteLimit; } - bitrate = await getDownloadSpeed(2400000); + const bitrate = await getDownloadSpeed(numBytes); - return Math.round(bitrate * 0.8); + if (bitrate * (2 / 8.0) < numBytes || numBytes >= byteLimit) { + // took > 2s, or numBytes hit the limit + return Math.round(bitrate * 0.8); + } else { + // If that produced a fairly high speed, try again with a larger size to get a more accurate result + return await detectBitrate(numBytes * 2); + } } /** From 4f17065bc24f5f7829d13b9041a29985a874e07c Mon Sep 17 00:00:00 2001 From: hawken Date: Fri, 1 Jan 2021 05:19:12 +0100 Subject: [PATCH 08/29] refactor window.playlist into playbackmanager and make playbackmanager static --- src/app.ts | 2 - src/components/commandHandler.ts | 24 ++--- src/components/maincontroller.ts | 28 +++--- src/components/playbackManager.ts | 143 ++++++++++++++++++++++-------- src/helpers.ts | 52 +---------- src/types/global.d.ts | 2 - 6 files changed, 129 insertions(+), 122 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 0e69dd1e..529cea5a 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: framework.PlayerManager; - private static playbackManager: playbackManager; private static supportedCommands: SupportedCommands = { DisplayContent: CommandHandler.displayContentHandler, Identify: CommandHandler.IdentifyHandler, @@ -52,12 +51,8 @@ export abstract class CommandHandler { VolumeUp: CommandHandler.VolumeUpHandler }; - static configure( - playerManager: framework.PlayerManager, - playbackManager: playbackManager - ): void { + static configure(playerManager: framework.PlayerManager): void { this.playerManager = playerManager; - this.playbackManager = playbackManager; } static playNextHandler(data: DataMessage): void { @@ -89,23 +84,20 @@ 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({}); } } @@ -138,7 +130,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 ca7a2244..9ff5ebf8 100644 --- a/src/components/maincontroller.ts +++ b/src/components/maincontroller.ts @@ -19,10 +19,9 @@ 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'; import { BaseItemDto } from '~/api/generated/models/base-item-dto'; import { MediaSourceInfo } from '~/api/generated/models/media-source-info'; import { GlobalScope, PlayRequest } from '~/types/global'; @@ -30,9 +29,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); @@ -166,7 +165,7 @@ window.playerManager.addEventListener( * */ function defaultOnStopped(): void { - playbackMgr.onStopped(true); + PlaybackManager.onStopped(); } window.playerManager.addEventListener( @@ -189,10 +188,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(); } } ); @@ -459,7 +457,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 @@ -522,10 +520,10 @@ export async function translateItems( if (method == 'PlayNext' || method == 'PlayLast') { for (let i = 0, length = options.items.length; i < length; i++) { - window.playlist.push(options.items[i]); + PlaybackManager.enqueue(options.items[i]); } } else { - playbackMgr.playFromOptions(data.options); + PlaybackManager.playFromOptions(data.options); } } @@ -542,7 +540,7 @@ export async function instantMix( const result = await getInstantMixItems(data.userId, item); options.items = result.Items; - playbackMgr.playFromOptions(data.options); + PlaybackManager.playFromOptions(data.options); } /** @@ -558,7 +556,7 @@ export async function shuffle( const result = await getShuffleItems(data.userId, item); options.items = result.Items; - playbackMgr.playFromOptions(data.options); + PlaybackManager.playFromOptions(data.options); } /** @@ -578,7 +576,7 @@ export async function onStopPlayerBeforePlaybackDone( type: 'GET' }); - playbackMgr.playItemInternal(data, options); + PlaybackManager.playItemInternal(data, options); broadcastConnectionErrorMessage(); } diff --git a/src/components/playbackManager.ts b/src/components/playbackManager.ts index ff9a4462..f3e0530a 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: framework.PlayerManager; - // TODO remove any - private activePlaylist: Array; - private activePlaylistIndex: number; +import { ItemIndex } from '~/types/global'; - constructor(playerManager: framework.PlayerManager) { +export abstract class PlaybackManager { + private static playerManager: framework.PlayerManager; + private static activePlaylist: Array; + private static activePlaylistIndex: number; + + static setPlayerManager(playerManager: 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') { @@ -70,26 +68,46 @@ 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; } @@ -97,13 +115,11 @@ export class playbackManager { 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; } @@ -111,8 +127,8 @@ export class playbackManager { return false; } - async playItem( - item: BaseItemDto, + // play item from playlist + private static async playItem( options: any, stopPlayer = false ): Promise { @@ -120,10 +136,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'); @@ -183,8 +207,7 @@ export class playbackManager { ); } - // TODO eradicate any - playMediaSource( + private static playMediaSource( playSessionId: string, item: BaseItemDto, mediaSource: MediaSourceInfo, @@ -236,7 +259,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. } @@ -244,18 +267,62 @@ export class playbackManager { /** * Called when media stops playing. * TODO avoid doing this between tracks in a playlist + */ + static onStopped(): void { + if (this.getNextPlaybackItemInfo()) { + $scope.playNextItem = true; + } else { + $scope.playNextItem = false; + + DocumentManager.setAppStatus('waiting'); + + stopPingInterval(); + + DocumentManager.startBackdropInterval(); + } + } + + /** + * Get information about the next item to play from window.playlist * - * @param _continue - If we have another item coming up in the playlist. Only used for notifying the cast sender. + * @returns item and index, or null to end playback */ - onStopped(_continue: boolean): void { - $scope.playNextItem = _continue; + static getNextPlaybackItemInfo(): ItemIndex | null { + if (this.activePlaylist.length < 1) { + return null; + } - DocumentManager.setAppStatus('waiting'); + 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; + } + } - stopPingInterval(); + if (newIndex < this.activePlaylist.length) { + return { + index: newIndex, + item: this.activePlaylist[newIndex] + }; + } - this.activePlaylist = []; - this.activePlaylistIndex = -1; - DocumentManager.startBackdropInterval(); + return null; } } diff --git a/src/helpers.ts b/src/helpers.ts index 787b269e..9f5f14e0 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 @@ -57,53 +58,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 { - index: newIndex, - item: item - }; - } - - 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 @@ -196,7 +150,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 f89689e5..0570ce93 100644 --- a/src/types/global.d.ts +++ b/src/types/global.d.ts @@ -99,8 +99,6 @@ declare global { mediaElement: HTMLElement | null; playerManager: PlayerManager; castReceiverContext: CastReceiverContext; - playlist: Array; - currentPlaylistIndex: number; repeatMode: RepeatMode; reportEventType: 'repeatmodechange'; subtitleAppearance: any; From c8b1b1504d3f4777db5d8ba5f4b8a6ea66e325f5 Mon Sep 17 00:00:00 2001 From: hawken Date: Mon, 4 Jan 2021 16:23:24 +0100 Subject: [PATCH 09/29] try to fix the startIndex issue --- src/components/playbackManager.ts | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/components/playbackManager.ts b/src/components/playbackManager.ts index f3e0530a..208dce33 100644 --- a/src/components/playbackManager.ts +++ b/src/components/playbackManager.ts @@ -54,7 +54,7 @@ export abstract class PlaybackManager { ); } - static async playFromOptions(options: any): Promise { + static async playFromOptions(options: any): Promise { const firstItem = options.items[0]; if (options.startPositionTicks || firstItem.MediaType !== 'Video') { @@ -68,17 +68,18 @@ export abstract class PlaybackManager { return this.playFromOptionsInternal(options); } - private static playFromOptionsInternal(options: any): boolean { + private static playFromOptionsInternal(options: any): Promise { const stopPlayer = this.activePlaylist && this.activePlaylist.length > 0; this.activePlaylist = options.items; - // We need to set -1 so the next index will be 0 - this.activePlaylistIndex = -1; + this.activePlaylistIndex = options.startIndex || 0; console.log('Loaded new playlist:', this.activePlaylist); - return this.playNextItem(options, stopPlayer); + // When starting playback initially, don't use + // the next item facility. + return this.playItem(options, stopPlayer); } // add item to playlist From f613aee9061c17243215179856c20a04e2710240 Mon Sep 17 00:00:00 2001 From: hawken Date: Thu, 14 Jan 2021 02:41:43 +0100 Subject: [PATCH 10/29] fix bug when client reconnects and chromecast is in details page --- src/components/commandHandler.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/commandHandler.ts b/src/components/commandHandler.ts index 529cea5a..84912a89 100644 --- a/src/components/commandHandler.ts +++ b/src/components/commandHandler.ts @@ -131,6 +131,7 @@ export abstract class CommandHandler { static IdentifyHandler(): void { if (!PlaybackManager.isPlaying()) { + DocumentManager.setAppStatus('waiting'); DocumentManager.startBackdropInterval(); } else { // When a client connects send back the initial device state (volume etc) via a playbackstop message From 019bb8c04aaa655a02a5463ebe7bdfdaedb712b6 Mon Sep 17 00:00:00 2001 From: thomas Date: Wed, 23 Mar 2022 23:57:54 -0400 Subject: [PATCH 11/29] fix errors from merge --- src/components/playbackManager.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/components/playbackManager.ts b/src/components/playbackManager.ts index 34279cea..7cc32a2b 100644 --- a/src/components/playbackManager.ts +++ b/src/components/playbackManager.ts @@ -1,6 +1,4 @@ import { - getIntros, - getNextPlaybackItemInfo, broadcastConnectionErrorMessage, createStreamInfo } from '../helpers'; From 23597e2aeb61202ac08ec96dbda63c2234d07401 Mon Sep 17 00:00:00 2001 From: thomas Date: Thu, 24 Mar 2022 00:34:28 -0400 Subject: [PATCH 12/29] make appstatus an enum --- src/components/commandHandler.ts | 3 ++- src/components/documentManager.ts | 9 +++++---- src/components/jellyfinActions.ts | 16 ++++++++-------- src/components/playbackManager.ts | 8 ++++---- src/helpers.ts | 4 ++-- src/types/global.d.ts | 21 ++++++++++++++++----- 6 files changed, 37 insertions(+), 24 deletions(-) diff --git a/src/components/commandHandler.ts b/src/components/commandHandler.ts index 84912a89..a9bf7e46 100644 --- a/src/components/commandHandler.ts +++ b/src/components/commandHandler.ts @@ -1,5 +1,6 @@ import { getReportingParams } from '../helpers'; import { + AppStatus, DataMessage, DisplayRequest, PlayRequest, @@ -131,7 +132,7 @@ export abstract class CommandHandler { static IdentifyHandler(): void { if (!PlaybackManager.isPlaying()) { - DocumentManager.setAppStatus('waiting'); + DocumentManager.setAppStatus(AppStatus.Waiting); DocumentManager.startBackdropInterval(); } else { // When a client connects send back the initial device state (volume etc) via a playbackstop message diff --git a/src/components/documentManager.ts b/src/components/documentManager.ts index a4f975bb..098e55be 100644 --- a/src/components/documentManager.ts +++ b/src/components/documentManager.ts @@ -2,6 +2,7 @@ import { parseISO8601Date } from '../helpers'; import { JellyfinApi } from './jellyfinApi'; import { deviceIds, getActiveDeviceId } from './castDevices'; import { BaseItemDto } from '~/api/generated/models/base-item-dto'; +import { AppStatus } from '~/types/global'; export abstract class DocumentManager { // Duration between each backdrop switch in ms @@ -10,7 +11,7 @@ export abstract class DocumentManager { private static backdropTimer = 0; // TODO make enum - private static status = ''; + private static status = AppStatus.Unset; /** * Hide the document body on chromecast audio to save resources @@ -185,7 +186,7 @@ export abstract class DocumentManager { } // Switch visible view! - this.setAppStatus('details'); + this.setAppStatus(AppStatus.Details); }); } @@ -285,7 +286,7 @@ export abstract class DocumentManager { * * @param status - to set */ - public static setAppStatus(status: string): void { + public static setAppStatus(status: AppStatus): void { this.status = status; document.body.className = status; } @@ -295,7 +296,7 @@ export abstract class DocumentManager { * * @returns app status */ - public static getAppStatus(): string { + public static getAppStatus(): AppStatus { return this.status; } diff --git a/src/components/jellyfinActions.ts b/src/components/jellyfinActions.ts index a94a30b6..fcc5384b 100644 --- a/src/components/jellyfinActions.ts +++ b/src/components/jellyfinActions.ts @@ -4,7 +4,7 @@ import { broadcastToMessageBus } from '../helpers'; -import { GlobalScope } from '../types/global'; +import { AppStatus, GlobalScope } from '../types/global'; import { PlaybackProgressInfo } from '../api/generated/models/playback-progress-info'; import { BaseItemDto } from '../api/generated/models/base-item-dto'; import { DeviceProfile } from '../api/generated/models/device-profile'; @@ -217,7 +217,7 @@ export function load( $scope.item = serverItem; - DocumentManager.setAppStatus('backdrop'); + DocumentManager.setAppStatus(AppStatus.Backdrop); $scope.mediaType = serverItem?.MediaType; } @@ -233,18 +233,18 @@ export function load( */ export function play($scope: GlobalScope): void { if ( - DocumentManager.getAppStatus() == 'backdrop' || - DocumentManager.getAppStatus() == 'playing-with-controls' || - DocumentManager.getAppStatus() == 'playing' || - DocumentManager.getAppStatus() == 'audio' + DocumentManager.getAppStatus() == AppStatus.Backdrop || + DocumentManager.getAppStatus() == AppStatus.PlayingWithControls || + DocumentManager.getAppStatus() == AppStatus.Playing || + DocumentManager.getAppStatus() == AppStatus.Audio ) { setTimeout(() => { window.playerManager.play(); if ($scope.mediaType == 'Audio') { - DocumentManager.setAppStatus('audio'); + DocumentManager.setAppStatus(AppStatus.Audio); } else { - DocumentManager.setAppStatus('playing-with-controls'); + DocumentManager.setAppStatus(AppStatus.PlayingWithControls); } }, 20); } diff --git a/src/components/playbackManager.ts b/src/components/playbackManager.ts index 7cc32a2b..f41aa485 100644 --- a/src/components/playbackManager.ts +++ b/src/components/playbackManager.ts @@ -24,7 +24,7 @@ import { DocumentManager } from './documentManager'; import { BaseItemDto } from '~/api/generated/models/base-item-dto'; import { MediaSourceInfo } from '~/api/generated/models/media-source-info'; -import { ItemIndex } from '~/types/global'; +import { AppStatus, ItemIndex } from '~/types/global'; export abstract class PlaybackManager { private static playerManager: framework.PlayerManager; @@ -145,7 +145,7 @@ export abstract class PlaybackManager { options: any ): Promise { $scope.isChangingStream = false; - DocumentManager.setAppStatus('loading'); + DocumentManager.setAppStatus(AppStatus.Loading); const maxBitrate = await getMaxBitrate(); const deviceProfile = getDeviceProfile({ @@ -209,7 +209,7 @@ export abstract class PlaybackManager { mediaSource: MediaSourceInfo, options: any ): void { - DocumentManager.setAppStatus('loading'); + DocumentManager.setAppStatus(AppStatus.Loading); const streamInfo = createStreamInfo( item, @@ -270,7 +270,7 @@ export abstract class PlaybackManager { } else { $scope.playNextItem = false; - DocumentManager.setAppStatus('waiting'); + DocumentManager.setAppStatus(AppStatus.Waiting); stopPingInterval(); diff --git a/src/helpers.ts b/src/helpers.ts index 9f5f14e0..9f7dc397 100644 --- a/src/helpers.ts +++ b/src/helpers.ts @@ -8,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, ItemQuery } from './types/global'; +import { GlobalScope, BusMessage, ItemQuery, AppStatus } from './types/global'; /** * Get current playback position in ticks, adjusted for server seeking @@ -167,7 +167,7 @@ export function getSenderReportingData( * @param $scope - global context variable */ export function resetPlaybackScope($scope: GlobalScope): void { - DocumentManager.setAppStatus('waiting'); + DocumentManager.setAppStatus(AppStatus.Waiting); $scope.startPositionTicks = 0; DocumentManager.setWaitingBackdrop(null, null); diff --git a/src/types/global.d.ts b/src/types/global.d.ts index 0570ce93..bdff6229 100644 --- a/src/types/global.d.ts +++ b/src/types/global.d.ts @@ -14,6 +14,17 @@ export interface Dictionary { [Key: string]: T; } +export enum AppStatus { + Audio = 'AUDIO', + Backdrop = 'BACKDROP', + Details = 'DETAILS', + Loading = 'LOADING', + PlayingWithControls = 'PLAYING-WITH-CONTROLS', + Playing = 'PLAYING', + Unset = '', + Waiting = 'WAITING', +} + // Jellyfin Server // Why doesn't the API have a type for this? /* Combined item query. @@ -78,11 +89,11 @@ export interface SeekRequest { export interface DataMessage { options: - | PlayRequest - | DisplayRequest - | SetIndexRequest - | SetRepeatModeRequest - | SeekRequest; + | PlayRequest + | DisplayRequest + | SetIndexRequest + | SetRepeatModeRequest + | SeekRequest; command: string; } From 166b9500bab738440a11b98d5eed8f5ed5dd65df Mon Sep 17 00:00:00 2001 From: thomas Date: Thu, 24 Mar 2022 00:39:59 -0400 Subject: [PATCH 13/29] fix case for enum --- src/types/global.d.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/types/global.d.ts b/src/types/global.d.ts index bdff6229..5733337e 100644 --- a/src/types/global.d.ts +++ b/src/types/global.d.ts @@ -15,14 +15,14 @@ export interface Dictionary { } export enum AppStatus { - Audio = 'AUDIO', - Backdrop = 'BACKDROP', - Details = 'DETAILS', - Loading = 'LOADING', - PlayingWithControls = 'PLAYING-WITH-CONTROLS', - Playing = 'PLAYING', + Audio = 'Audio', + Backdrop = 'Backdrop', + Details = 'Details', + Loading = 'Loading', + PlayingWithControls = 'PlayingWithControls', + Playing = 'Playing', Unset = '', - Waiting = 'WAITING', + Waiting = 'Waiting', } // Jellyfin Server From 6cb8f0dfdfd169a768ed7dfc72f671bd6cfd7bed Mon Sep 17 00:00:00 2001 From: thomas Date: Thu, 24 Mar 2022 00:46:55 -0400 Subject: [PATCH 14/29] remove TODO statement --- src/components/documentManager.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/documentManager.ts b/src/components/documentManager.ts index 098e55be..85ca83d7 100644 --- a/src/components/documentManager.ts +++ b/src/components/documentManager.ts @@ -10,7 +10,6 @@ export abstract class DocumentManager { // Timer state - so that we don't start the interval more than necessary private static backdropTimer = 0; - // TODO make enum private static status = AppStatus.Unset; /** From 323983aca5a3f2173cd05406d5398831c8e0f153 Mon Sep 17 00:00:00 2001 From: thomas Date: Thu, 24 Mar 2022 00:55:21 -0400 Subject: [PATCH 15/29] hopefully fix buffering bug --- src/components/commandHandler.ts | 4 +++- src/components/playbackManager.ts | 4 ++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/components/commandHandler.ts b/src/components/commandHandler.ts index a9bf7e46..0a7c7b2b 100644 --- a/src/components/commandHandler.ts +++ b/src/components/commandHandler.ts @@ -132,7 +132,9 @@ export abstract class CommandHandler { static IdentifyHandler(): void { if (!PlaybackManager.isPlaying()) { - DocumentManager.setAppStatus(AppStatus.Waiting); + if (!PlaybackManager.isBuffering()) { + DocumentManager.setAppStatus(AppStatus.Waiting); + } DocumentManager.startBackdropInterval(); } else { // When a client connects send back the initial device state (volume etc) via a playbackstop message diff --git a/src/components/playbackManager.ts b/src/components/playbackManager.ts index f41aa485..e49eb2b7 100644 --- a/src/components/playbackManager.ts +++ b/src/components/playbackManager.ts @@ -53,6 +53,10 @@ export abstract class PlaybackManager { ); } + static isBuffering(): boolean { + return this.playerManager.getPlayerState() === cast.framework.messages.PlayerState.BUFFERING; + } + static async playFromOptions(options: any): Promise { const firstItem = options.items[0]; From ffebe175a3f9fbca7180504af9e2d3828a7f5b48 Mon Sep 17 00:00:00 2001 From: thomas Date: Thu, 24 Mar 2022 01:23:46 -0400 Subject: [PATCH 16/29] add ticksToSeconds function --- src/components/commandHandler.ts | 4 ++-- src/components/documentManager.ts | 9 ++++----- src/components/maincontroller.ts | 7 ++++--- src/components/playbackManager.ts | 5 +++-- src/helpers.ts | 15 +++++++++++++-- 5 files changed, 26 insertions(+), 14 deletions(-) diff --git a/src/components/commandHandler.ts b/src/components/commandHandler.ts index 0a7c7b2b..f72e1b9c 100644 --- a/src/components/commandHandler.ts +++ b/src/components/commandHandler.ts @@ -1,4 +1,4 @@ -import { getReportingParams } from '../helpers'; +import { getReportingParams, TicksPerSecond } from '../helpers'; import { AppStatus, DataMessage, @@ -148,7 +148,7 @@ export abstract class CommandHandler { } static SeekHandler(data: DataMessage): void { - seek((data.options).position * 10000000); + seek((data.options).position * TicksPerSecond); } static MuteHandler(): void { diff --git a/src/components/documentManager.ts b/src/components/documentManager.ts index 85ca83d7..bcfc7317 100644 --- a/src/components/documentManager.ts +++ b/src/components/documentManager.ts @@ -1,4 +1,4 @@ -import { parseISO8601Date } from '../helpers'; +import { parseISO8601Date, TicksPerSecond, ticksToSeconds } from '../helpers'; import { JellyfinApi } from './jellyfinApi'; import { deviceIds, getActiveDeviceId } from './castDevices'; import { BaseItemDto } from '~/api/generated/models/base-item-dto'; @@ -628,9 +628,8 @@ export abstract class DocumentManager { * @returns human readable position */ private static formatRunningTime(ticks: number): string { - const ticksPerHour = 36000000000; - const ticksPerMinute = 600000000; - const ticksPerSecond = 10000000; + const ticksPerMinute = TicksPerSecond * 60; + const ticksPerHour = ticksPerMinute * 60; const parts: string[] = []; @@ -652,7 +651,7 @@ export abstract class DocumentManager { parts.push(minutes.toString()); } - const seconds: number = Math.floor(ticks / ticksPerSecond); + const seconds: number = Math.floor(ticksToSeconds(ticks)); if (seconds < 10) { parts.push(`0${seconds.toString()}`); diff --git a/src/components/maincontroller.ts b/src/components/maincontroller.ts index 4ac16a95..614ab794 100644 --- a/src/components/maincontroller.ts +++ b/src/components/maincontroller.ts @@ -8,7 +8,8 @@ import { getInstantMixItems, translateRequestedItems, broadcastToMessageBus, - broadcastConnectionErrorMessage + broadcastConnectionErrorMessage, + ticksToSeconds } from '../helpers'; import { reportPlaybackStart, @@ -437,7 +438,7 @@ export async function changeStream( window.playerManager.getMediaInformation().customData.canClientSeek && params == null ) { - window.playerManager.seek(ticks / 10000000); + window.playerManager.seek(ticksToSeconds(ticks)); reportPlaybackProgress($scope, getReportingParams($scope)); return Promise.resolve(); @@ -787,7 +788,7 @@ export function createMediaInformation( if (streamInfo.mediaSource.RunTimeTicks) { mediaInfo.duration = Math.floor( - streamInfo.mediaSource.RunTimeTicks / 10000000 + ticksToSeconds(streamInfo.mediaSource.RunTimeTicks) ); } diff --git a/src/components/playbackManager.ts b/src/components/playbackManager.ts index e49eb2b7..3ebd2b95 100644 --- a/src/components/playbackManager.ts +++ b/src/components/playbackManager.ts @@ -1,6 +1,7 @@ import { broadcastConnectionErrorMessage, - createStreamInfo + createStreamInfo, + ticksToSeconds } from '../helpers'; import { @@ -235,7 +236,7 @@ export abstract class PlaybackManager { // to seconds and give it to loadRequestData :) if (mediaInfo.customData.startPositionTicks > 0) { loadRequestData.currentTime = - mediaInfo.customData.startPositionTicks / 10000000; + ticksToSeconds(mediaInfo.customData.startPositionTicks); } load($scope, mediaInfo.customData, item); diff --git a/src/helpers.ts b/src/helpers.ts index 9f7dc397..8609d37f 100644 --- a/src/helpers.ts +++ b/src/helpers.ts @@ -10,6 +10,8 @@ import { BaseItemPerson } from './api/generated/models/base-item-person'; import { UserDto } from './api/generated/models/user-dto'; import { GlobalScope, BusMessage, ItemQuery, AppStatus } from './types/global'; +export const TicksPerSecond = 10000000 + /** * Get current playback position in ticks, adjusted for server seeking * @@ -17,7 +19,7 @@ import { GlobalScope, BusMessage, ItemQuery, AppStatus } from './types/global'; * @returns position in ticks */ export function getCurrentPositionTicks($scope: GlobalScope): number { - let positionTicks = window.playerManager.getCurrentTimeSec() * 10000000; + let positionTicks = window.playerManager.getCurrentTimeSec() * TicksPerSecond; const mediaInformation = window.playerManager.getMediaInformation(); if (mediaInformation && !mediaInformation.customData.canClientSeek) { @@ -321,7 +323,7 @@ export function createStreamInfo( // server seeking const startPositionInSeekParam = startPosition - ? startPosition / 10000000 + ? ticksToSeconds(startPosition) : 0; const seekParam = startPositionInSeekParam ? `#t=${startPositionInSeekParam}` @@ -776,6 +778,15 @@ export function parseISO8601Date(date: string): Date { return new Date(date); } +/** + * Convert ticks to seconds + * @param ticks - number of ticks to convert + * @returns number of seconds + */ +export function ticksToSeconds(ticks: number): number { + return ticks / TicksPerSecond +} + /** * Send a message over the custom message transport * From 308cc234b1cc062d5e37bf20cd8b41c210cd9b22 Mon Sep 17 00:00:00 2001 From: thomas Date: Thu, 24 Mar 2022 01:27:15 -0400 Subject: [PATCH 17/29] fix null exception --- src/components/jellyfinApi.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/components/jellyfinApi.ts b/src/components/jellyfinApi.ts index 18957279..b17b8c27 100644 --- a/src/components/jellyfinApi.ts +++ b/src/components/jellyfinApi.ts @@ -28,16 +28,16 @@ export abstract class JellyfinApi { ): void { console.debug( `JellyfinApi.setServerInfo: user:${userId}, token:${accessToken}, ` + - `server:${serverAddress}, name:${receiverName}` + `server:${serverAddress}, name:${receiverName}` ); this.userId = userId; this.accessToken = accessToken; this.serverAddress = serverAddress; - // remove special characters from the receiver name - receiverName = receiverName.replace(/[^\w\s]/gi, ''); - if (receiverName) { + // remove special characters from the receiver name + receiverName = receiverName.replace(/[^\w\s]/gi, ''); + this.deviceName = receiverName; // deviceId just needs to be unique-ish this.deviceId = btoa(receiverName); From 3fc8967c3ac66b612033e9b2dd09c01cf5168e6f Mon Sep 17 00:00:00 2001 From: thomas Date: Thu, 24 Mar 2022 01:36:57 -0400 Subject: [PATCH 18/29] onStopped -> onStop --- src/components/maincontroller.ts | 10 +++++----- src/components/playbackManager.ts | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/components/maincontroller.ts b/src/components/maincontroller.ts index 614ab794..ec870a01 100644 --- a/src/components/maincontroller.ts +++ b/src/components/maincontroller.ts @@ -165,17 +165,17 @@ window.playerManager.addEventListener( /** * */ -function defaultOnStopped(): void { - PlaybackManager.onStopped(); +function defaultOnStop(): void { + PlaybackManager.onStop(); } window.playerManager.addEventListener( cast.framework.events.EventType.MEDIA_FINISHED, - defaultOnStopped + defaultOnStop ); window.playerManager.addEventListener( cast.framework.events.EventType.ABORT, - defaultOnStopped + defaultOnStop ); window.playerManager.addEventListener( @@ -191,7 +191,7 @@ window.playerManager.addEventListener( if (!PlaybackManager.playNextItem()) { PlaybackManager.resetPlaylist(); - PlaybackManager.onStopped(); + PlaybackManager.onStop(); } } ); diff --git a/src/components/playbackManager.ts b/src/components/playbackManager.ts index 3ebd2b95..f680fa5c 100644 --- a/src/components/playbackManager.ts +++ b/src/components/playbackManager.ts @@ -262,14 +262,14 @@ export abstract class PlaybackManager { */ static stop(): void { this.playerManager.stop(); - // onStopped will be called when playback comes to a halt. + // onStop will be called when playback comes to a halt. } /** * Called when media stops playing. * TODO avoid doing this between tracks in a playlist */ - static onStopped(): void { + static onStop(): void { if (this.getNextPlaybackItemInfo()) { $scope.playNextItem = true; } else { From 8732560cb10621722eb50c013d80918c1675a614 Mon Sep 17 00:00:00 2001 From: thomas Date: Thu, 24 Mar 2022 01:40:03 -0400 Subject: [PATCH 19/29] use RepeatMode enum --- src/components/playbackManager.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/components/playbackManager.ts b/src/components/playbackManager.ts index f680fa5c..5f44dbef 100644 --- a/src/components/playbackManager.ts +++ b/src/components/playbackManager.ts @@ -26,6 +26,7 @@ import { BaseItemDto } from '~/api/generated/models/base-item-dto'; import { MediaSourceInfo } from '~/api/generated/models/media-source-info'; import { AppStatus, ItemIndex } from '~/types/global'; +import { RepeatMode } from '~/api/generated'; export abstract class PlaybackManager { private static playerManager: framework.PlayerManager; @@ -300,10 +301,10 @@ export abstract class PlaybackManager { newIndex = 0; } else { switch (window.repeatMode) { - case 'RepeatOne': + case RepeatMode.RepeatOne: newIndex = this.activePlaylistIndex; break; - case 'RepeatAll': + case RepeatMode.RepeatAll: newIndex = this.activePlaylistIndex + 1; if (newIndex >= this.activePlaylist.length) { From 12d2ae37bcc39b3865ae465414a027a0bebca10d Mon Sep 17 00:00:00 2001 From: thomas Date: Thu, 24 Mar 2022 02:08:58 -0400 Subject: [PATCH 20/29] isHlsStream function --- src/helpers.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/helpers.ts b/src/helpers.ts index 8609d37f..4874378a 100644 --- a/src/helpers.ts +++ b/src/helpers.ts @@ -304,6 +304,10 @@ export function getMetadata(item: BaseItemDto): any { return metadata; } +export function isHlsStream(mediaSource: MediaSourceInfo) { + return mediaSource.TranscodingSubProtocol == 'hls' +} + /** * Create the necessary information about an item * needed for playback @@ -354,7 +358,7 @@ export function createStreamInfo( mediaSource.TranscodingUrl ); - if (mediaSource.TranscodingSubProtocol == 'hls') { + if (isHlsStream(mediaSource)) { mediaUrl += seekParam; playerStartPositionTicks = startPosition || 0; contentType = 'application/x-mpegURL'; From 44447771b14125013495914bd55e899344034315 Mon Sep 17 00:00:00 2001 From: thomas Date: Thu, 24 Mar 2022 02:27:33 -0400 Subject: [PATCH 21/29] Fix some github actions lint errors --- src/components/codecSupportHelper.ts | 1 - src/components/deviceprofileBuilder.ts | 13 +++++++++++++ src/components/documentManager.ts | 16 +++++++--------- 3 files changed, 20 insertions(+), 10 deletions(-) diff --git a/src/components/codecSupportHelper.ts b/src/components/codecSupportHelper.ts index bf15e024..3aa5c9cf 100644 --- a/src/components/codecSupportHelper.ts +++ b/src/components/codecSupportHelper.ts @@ -26,7 +26,6 @@ export function hasEAC3Support(): boolean { * Currently it's disabled because of problems getting it to work with HLS. * * @returns true if AC-3 can be played - * */ export function hasAC3Support(): boolean { //return castContext.canDisplayType('audio/mp4', 'ac-3'); diff --git a/src/components/deviceprofileBuilder.ts b/src/components/deviceprofileBuilder.ts index 52afb353..49e516ca 100644 --- a/src/components/deviceprofileBuilder.ts +++ b/src/components/deviceprofileBuilder.ts @@ -41,6 +41,8 @@ let profileOptions: ProfileOptions; let currentDeviceId: number; /** + * Create and return a new ProfileCondition + * * @param Property - What property the condition should test. * @param Condition - The condition to test the values for. * @param Value - The value to compare against. @@ -62,6 +64,9 @@ function createProfileCondition( } /** + * Get container profiles + * + * @todo Why does this always return an empty array? * @returns Container profiles. */ function getContainerProfiles(): Array { @@ -69,6 +74,8 @@ function getContainerProfiles(): Array { } /** + * Get response profiles + * * @returns Response profiles. */ function getResponseProfiles(): Array { @@ -146,6 +153,8 @@ function getDirectPlayProfiles(): Array { } /** + * Get codec profiles + * * @returns Codec profiles. */ function getCodecProfiles(): Array { @@ -260,6 +269,8 @@ function getCodecProfiles(): Array { } /** + * Get transcoding profiles + * * @returns Transcoding profiles. */ function getTranscodingProfiles(): Array { @@ -338,6 +349,8 @@ function getTranscodingProfiles(): Array { } /** + * Get subtitle profiles + * * @returns Subtitle profiles. */ function getSubtitleProfiles(): Array { diff --git a/src/components/documentManager.ts b/src/components/documentManager.ts index bcfc7317..16793c41 100644 --- a/src/components/documentManager.ts +++ b/src/components/documentManager.ts @@ -8,7 +8,7 @@ export abstract class DocumentManager { // Duration between each backdrop switch in ms private static backdropPeriodMs: number | null = 30000; // Timer state - so that we don't start the interval more than necessary - private static backdropTimer = 0; + private static backdropTimer: NodeJS.Timer | null = null; private static status = AppStatus.Unset; @@ -402,9 +402,9 @@ export abstract class DocumentManager { * Stop the backdrop rotation */ public static clearBackdropInterval(): void { - if (this.backdropTimer !== 0) { + if (this.backdropTimer !== null) { clearInterval(this.backdropTimer); - this.backdropTimer = 0; + this.backdropTimer = null; } } @@ -429,11 +429,9 @@ export abstract class DocumentManager { return; } - this.backdropTimer = ( - setInterval( - () => DocumentManager.setRandomUserBackdrop(), - this.backdropPeriodMs - ) + this.backdropTimer = setInterval( + () => DocumentManager.setRandomUserBackdrop(), + this.backdropPeriodMs ); await this.setRandomUserBackdrop(); @@ -449,7 +447,7 @@ export abstract class DocumentManager { this.backdropPeriodMs = period; // If the timer was running, restart it - if (this.backdropTimer !== 0) { + if (this.backdropTimer !== null) { // startBackdropInterval will also clear the previous one this.startBackdropInterval(); } From 9e668e1e916a5bc123737344d8faa54fb98d03df Mon Sep 17 00:00:00 2001 From: thomas Date: Fri, 25 Mar 2022 01:05:28 -0400 Subject: [PATCH 22/29] try to fix hls width issue --- src/components/codecSupportHelper.ts | 32 ++++++++++++++------------ src/components/deviceprofileBuilder.ts | 5 ++-- src/helpers.ts | 6 +++++ 3 files changed, 25 insertions(+), 18 deletions(-) diff --git a/src/components/codecSupportHelper.ts b/src/components/codecSupportHelper.ts index 3aa5c9cf..45568f1a 100644 --- a/src/components/codecSupportHelper.ts +++ b/src/components/codecSupportHelper.ts @@ -103,26 +103,28 @@ export function getMaxBitrateSupport(): number { * * @returns Max supported width. */ -export function getMaxWidthSupport(): number { - // with HLS, it will produce a manifest error if we - // send any stream larger than the screen size... - return window.innerWidth; +export function getMaxWidthSupport(deviceId: number, codec?: string): number { + if (codec === 'h264') { + // with HLS, it will produce a manifest error if we + // send any stream larger than the screen size... + return window.innerWidth; + } // mkv playback can use the device limitations. // The devices are capable of decoding and downscaling, // they just refuse to do it with HLS. This increases // the rate of direct playback. - //switch (deviceId) { - // case deviceIds.ULTRA: - // case deviceIds.CCGTV: - // return 3840; - // case deviceIds.GEN1AND2: - // case deviceIds.GEN3: - // return 1920; - // case deviceIds.NESTHUBANDMAX: - // return 1280; - //} - //return 0; + switch (deviceId) { + case deviceIds.ULTRA: + case deviceIds.CCGTV: + return 3840; + case deviceIds.GEN1AND2: + case deviceIds.GEN3: + return 1920; + case deviceIds.NESTHUBANDMAX: + return 1280; + } + return 0; } /** diff --git a/src/components/deviceprofileBuilder.ts b/src/components/deviceprofileBuilder.ts index 49e516ca..c722a782 100644 --- a/src/components/deviceprofileBuilder.ts +++ b/src/components/deviceprofileBuilder.ts @@ -204,7 +204,6 @@ function getCodecProfiles(): Array { CodecProfiles.push(aacConditions); - const maxWidth: number = getMaxWidthSupport(); const h26xLevel: number = getH26xLevelSupport(currentDeviceId); const h26xProfile: string = getH26xProfileSupport(currentDeviceId); @@ -229,7 +228,7 @@ function getCodecProfiles(): Array { createProfileCondition( ProfileConditionValue.Width, ProfileConditionType.LessThanEqual, - maxWidth.toString(), + getMaxWidthSupport(currentDeviceId, 'h264').toString(), true ) ], @@ -243,7 +242,7 @@ function getCodecProfiles(): Array { createProfileCondition( ProfileConditionValue.Width, ProfileConditionType.LessThanEqual, - maxWidth.toString(), + getMaxWidthSupport(currentDeviceId).toString(), true ) ], diff --git a/src/helpers.ts b/src/helpers.ts index 4874378a..523b1bfd 100644 --- a/src/helpers.ts +++ b/src/helpers.ts @@ -304,6 +304,12 @@ export function getMetadata(item: BaseItemDto): any { return metadata; } +/** + * Check if a media source is an HLS stream + * + * @param mediaSource + * @returns + */ export function isHlsStream(mediaSource: MediaSourceInfo) { return mediaSource.TranscodingSubProtocol == 'hls' } From 310692657fc2aae708cbe7875f38bf79693624da Mon Sep 17 00:00:00 2001 From: Andreas B <6439218+YouKnowBlom@users.noreply.github.com> Date: Tue, 20 Dec 2022 22:52:36 +0100 Subject: [PATCH 23/29] Update old mentions of playbackMgr --- src/components/commandHandler.ts | 10 +++++----- src/components/jellyfinActions.ts | 7 +++---- src/components/maincontroller.ts | 24 ++++++++++++------------ src/components/playbackManager.ts | 2 +- 4 files changed, 21 insertions(+), 22 deletions(-) diff --git a/src/components/commandHandler.ts b/src/components/commandHandler.ts index 0b4e0bac..3fc0e006 100644 --- a/src/components/commandHandler.ts +++ b/src/components/commandHandler.ts @@ -104,14 +104,14 @@ export abstract class CommandHandler { static setAudioStreamIndexHandler(data: DataMessage): void { setAudioStreamIndex( - this.playbackManager.playbackState, + PlaybackManager.playbackState, (data.options).index ); } static setSubtitleStreamIndexHandler(data: DataMessage): void { setSubtitleStreamIndex( - this.playbackManager.playbackState, + PlaybackManager.playbackState, (data.options).index ); } @@ -145,8 +145,8 @@ export abstract class CommandHandler { } else { // When a client connects send back the initial device state (volume etc) via a playbackstop message reportPlaybackProgress( - this.playbackManager.playbackState, - getReportingParams(this.playbackManager.playbackState), + PlaybackManager.playbackState, + getReportingParams(PlaybackManager.playbackState), true, 'playbackstop' ); @@ -155,7 +155,7 @@ export abstract class CommandHandler { static SeekHandler(data: DataMessage): void { seek( - this.playbackManager.playbackState, + PlaybackManager.playbackState, (data.options).position * TicksPerSecond ); } diff --git a/src/components/jellyfinActions.ts b/src/components/jellyfinActions.ts index 52905adb..2a5c0e65 100644 --- a/src/components/jellyfinActions.ts +++ b/src/components/jellyfinActions.ts @@ -12,7 +12,7 @@ import { PlayRequest } from '../api/generated/models/play-request'; import { LiveStreamResponse } from '../api/generated/models/live-stream-response'; import { JellyfinApi } from './jellyfinApi'; import { DocumentManager } from './documentManager'; -import { PlaybackState } from './playbackManager'; +import { PlaybackManager, PlaybackState } from './playbackManager'; interface PlayRequestQuery extends PlayRequest { UserId?: string; @@ -193,13 +193,12 @@ export function pingTranscoder( * @param serverItem - item that is playing */ export function load( - playbackMgr: playbackManager, customData: any, serverItem: BaseItemDto ): void { - playbackMgr.resetPlaybackScope(); + PlaybackManager.resetPlaybackScope(); - const state = playbackMgr.playbackState; + const state = PlaybackManager.playbackState; // These are set up in maincontroller.createMediaInformation state.playSessionId = customData.playSessionId; diff --git a/src/components/maincontroller.ts b/src/components/maincontroller.ts index a13ea885..e9fd893b 100644 --- a/src/components/maincontroller.ts +++ b/src/components/maincontroller.ts @@ -33,7 +33,7 @@ PlaybackManager.setPlayerManager(window.playerManager); CommandHandler.configure(window.playerManager); -playbackMgr.resetPlaybackScope(); +PlaybackManager.resetPlaybackScope(); let broadcastToServer = new Date(); @@ -43,14 +43,14 @@ let hasReportedCapabilities = false; * */ export function onMediaElementTimeUpdate(): void { - if (playbackMgr.playbackState.isChangingStream) { + if (PlaybackManager.playbackState.isChangingStream) { return; } const now = new Date(); const elapsed = now.valueOf() - broadcastToServer.valueOf(); - const playbackState = playbackMgr.playbackState; + const playbackState = PlaybackManager.playbackState; if (elapsed > 5000) { // TODO use status as input @@ -73,7 +73,7 @@ export function onMediaElementTimeUpdate(): void { * */ export function onMediaElementPause(): void { - if (playbackMgr.playbackState.isChangingStream) { + if (PlaybackManager.playbackState.isChangingStream) { return; } @@ -84,7 +84,7 @@ export function onMediaElementPause(): void { * */ export function onMediaElementPlaying(): void { - if (playbackMgr.playbackState.isChangingStream) { + if (PlaybackManager.playbackState.isChangingStream) { return; } @@ -151,7 +151,7 @@ enableTimeUpdateListener(); window.addEventListener('beforeunload', () => { // Try to cleanup after ourselves before the page closes - const playbackState = playbackMgr.playbackState; + const playbackState = PlaybackManager.playbackState; disableTimeUpdateListener(); reportPlaybackStopped(playbackState, getReportingParams(playbackState)); @@ -160,7 +160,7 @@ window.addEventListener('beforeunload', () => { window.playerManager.addEventListener( cast.framework.events.EventType.PLAY, (): void => { - const playbackState = playbackMgr.playbackState; + const playbackState = PlaybackManager.playbackState; play(playbackState); reportPlaybackProgress( @@ -173,7 +173,7 @@ window.playerManager.addEventListener( window.playerManager.addEventListener( cast.framework.events.EventType.PAUSE, (): void => { - const playbackState = playbackMgr.playbackState; + const playbackState = PlaybackManager.playbackState; reportPlaybackProgress( playbackState, @@ -201,7 +201,7 @@ window.playerManager.addEventListener( window.playerManager.addEventListener( cast.framework.events.EventType.ENDED, (): void => { - const playbackState = playbackMgr.playbackState; + const playbackState = PlaybackManager.playbackState; // If we're changing streams, do not report playback ended. if (playbackState.isChangingStream) { @@ -209,7 +209,7 @@ window.playerManager.addEventListener( } reportPlaybackStopped(playbackState, getReportingParams(playbackState)); - playbackMgr.resetPlaybackScope(); + PlaybackManager.resetPlaybackScope(); if (!PlaybackManager.playNextItem()) { PlaybackManager.resetPlaylist(); @@ -319,7 +319,7 @@ export function processMessage(data: any): void { CommandHandler.processMessage(data, data.command); if (window.reportEventType) { - const playbackState = playbackMgr.playbackState; + const playbackState = PlaybackManager.playbackState; const report = (): void => { reportPlaybackProgress( @@ -348,7 +348,7 @@ export function reportEvent( name: string, reportToServer: boolean ): Promise { - const playbackState = playbackMgr.playbackState; + const playbackState = PlaybackManager.playbackState; return reportPlaybackProgress( playbackState, diff --git a/src/components/playbackManager.ts b/src/components/playbackManager.ts index 6be6a6b7..6782aa6a 100644 --- a/src/components/playbackManager.ts +++ b/src/components/playbackManager.ts @@ -283,7 +283,7 @@ export abstract class PlaybackManager { ticksToSeconds(mediaInfo.customData.startPositionTicks); } - load(this, mediaInfo.customData, item); + load(mediaInfo.customData, item); this.playerManager.load(loadRequestData); this.playbackState.PlaybackMediaSource = mediaSource; From 7fb0fd63d936e9065872971c6d8892ff209fe227 Mon Sep 17 00:00:00 2001 From: Andreas B <6439218+YouKnowBlom@users.noreply.github.com> Date: Tue, 20 Dec 2022 22:58:48 +0100 Subject: [PATCH 24/29] Update old mentions of $scope --- src/components/maincontroller.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/components/maincontroller.ts b/src/components/maincontroller.ts index e9fd893b..91307658 100644 --- a/src/components/maincontroller.ts +++ b/src/components/maincontroller.ts @@ -222,14 +222,19 @@ window.playerManager.addEventListener( window.playerManager.addEventListener( cast.framework.events.EventType.PLAYING, (): void => { - reportPlaybackStart($scope, getReportingParams($scope)); + reportPlaybackStart( + PlaybackManager.playbackState, + getReportingParams(PlaybackManager.playbackState) + ); } ); // Notify of playback end just before stopping it, to get a good tick position window.playerManager.addEventListener( cast.framework.events.EventType.REQUEST_STOP, (): void => { - reportPlaybackStopped($scope, getReportingParams($scope)); + reportPlaybackStopped( + PlaybackManager.playbackState, + getReportingParams(PlaybackManager.playbackState)); } ); From 625171d1e0ba3b356a9554e522356e5be17d874c Mon Sep 17 00:00:00 2001 From: Andreas B <6439218+YouKnowBlom@users.noreply.github.com> Date: Tue, 20 Dec 2022 23:49:33 +0100 Subject: [PATCH 25/29] Fix webpack failing to resolve global types and generated api --- src/components/commandHandler.ts | 7 +++---- src/components/documentManager.ts | 2 +- src/components/jellyfinActions.ts | 2 +- src/components/playbackManager.ts | 11 ++++++----- src/helpers.ts | 2 +- src/types/appStatus.ts | 11 +++++++++++ src/types/global.d.ts | 11 ----------- 7 files changed, 23 insertions(+), 23 deletions(-) create mode 100644 src/types/appStatus.ts diff --git a/src/components/commandHandler.ts b/src/components/commandHandler.ts index 3fc0e006..470591a7 100644 --- a/src/components/commandHandler.ts +++ b/src/components/commandHandler.ts @@ -1,6 +1,5 @@ import { getReportingParams, TicksPerSecond } from '../helpers'; -import { - AppStatus, +import type { DataMessage, DisplayRequest, PlayRequest, @@ -9,6 +8,7 @@ import { SetRepeatModeRequest, SupportedCommands } from '../types/global'; + import { translateItems, shuffle, @@ -19,9 +19,8 @@ import { } from './maincontroller'; import { reportPlaybackProgress } from './jellyfinActions'; - +import { AppStatus } from '../types/appStatus'; import { PlaybackManager } from './playbackManager'; - import { DocumentManager } from './documentManager'; export abstract class CommandHandler { diff --git a/src/components/documentManager.ts b/src/components/documentManager.ts index 16793c41..40db2b22 100644 --- a/src/components/documentManager.ts +++ b/src/components/documentManager.ts @@ -2,7 +2,7 @@ import { parseISO8601Date, TicksPerSecond, ticksToSeconds } from '../helpers'; import { JellyfinApi } from './jellyfinApi'; import { deviceIds, getActiveDeviceId } from './castDevices'; import { BaseItemDto } from '~/api/generated/models/base-item-dto'; -import { AppStatus } from '~/types/global'; +import { AppStatus } from '../types/appStatus'; export abstract class DocumentManager { // Duration between each backdrop switch in ms diff --git a/src/components/jellyfinActions.ts b/src/components/jellyfinActions.ts index 2a5c0e65..99e8516a 100644 --- a/src/components/jellyfinActions.ts +++ b/src/components/jellyfinActions.ts @@ -3,7 +3,7 @@ import { broadcastToMessageBus } from '../helpers'; -import { AppStatus } from '../types/global'; +import { AppStatus } from '../types/appStatus'; import { PlaybackProgressInfo } from '../api/generated/models/playback-progress-info'; import { BaseItemDto } from '../api/generated/models/base-item-dto'; import { DeviceProfile } from '../api/generated/models/device-profile'; diff --git a/src/components/playbackManager.ts b/src/components/playbackManager.ts index 6782aa6a..8d7c7829 100644 --- a/src/components/playbackManager.ts +++ b/src/components/playbackManager.ts @@ -22,9 +22,9 @@ import { } from './maincontroller'; import { DocumentManager } from './documentManager'; -import { BaseItemDto } from '~/api/generated/models/base-item-dto'; -import { MediaSourceInfo } from '~/api/generated/models/media-source-info'; -import { PlayMethod } from '~/api/generated'; +import type { BaseItemDto } from '../api/generated/models/base-item-dto'; +import type { MediaSourceInfo } from '../api/generated/models/media-source-info'; +import type { PlayMethod } from '../api/generated'; export interface PlaybackState { startPositionTicks: number; @@ -49,8 +49,9 @@ export interface PlaybackState { runtimeTicks: number; } -import { AppStatus, ItemIndex } from '~/types/global'; -import { RepeatMode } from '~/api/generated'; +import { ItemIndex } from '~/types/global'; +import { AppStatus } from '../types/appStatus'; +import { RepeatMode } from '../api/generated'; export abstract class PlaybackManager { private static playerManager: framework.PlayerManager; diff --git a/src/helpers.ts b/src/helpers.ts index f92f2595..b864d984 100644 --- a/src/helpers.ts +++ b/src/helpers.ts @@ -7,7 +7,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 { BusMessage, AppStatus, ItemQuery } from './types/global'; +import { BusMessage, ItemQuery } from './types/global'; export const TicksPerSecond = 10000000 /** diff --git a/src/types/appStatus.ts b/src/types/appStatus.ts new file mode 100644 index 00000000..9ab41578 --- /dev/null +++ b/src/types/appStatus.ts @@ -0,0 +1,11 @@ +export enum AppStatus { + Audio = 'Audio', + Backdrop = 'Backdrop', + Details = 'Details', + Loading = 'Loading', + PlayingWithControls = 'PlayingWithControls', + Playing = 'Playing', + Unset = '', + Waiting = 'Waiting', +} + diff --git a/src/types/global.d.ts b/src/types/global.d.ts index 81cfbdd8..74a7187b 100644 --- a/src/types/global.d.ts +++ b/src/types/global.d.ts @@ -10,17 +10,6 @@ export interface Dictionary { [Key: string]: T; } -export enum AppStatus { - Audio = 'Audio', - Backdrop = 'Backdrop', - Details = 'Details', - Loading = 'Loading', - PlayingWithControls = 'PlayingWithControls', - Playing = 'Playing', - Unset = '', - Waiting = 'Waiting', -} - // Jellyfin Server // Why doesn't the API have a type for this? /* Combined item query. From edbd06368e97169c636621fa53bd4f595874b304 Mon Sep 17 00:00:00 2001 From: Sky-High Date: Mon, 25 Sep 2023 17:39:30 +0200 Subject: [PATCH 26/29] pacify es-lint 1 --- src/components/__tests__/jellyfinApi.test.ts | 4 +- src/components/codecSupportHelper.ts | 1 + src/components/commandHandler.ts | 6 +- src/components/deviceprofileBuilder.ts | 7 +- src/components/documentManager.ts | 2 +- src/components/fetchhelper.ts | 4 +- src/components/jellyfinActions.ts | 53 ++++---- src/components/jellyfinApi.ts | 9 +- src/components/maincontroller.ts | 123 +++++++++++-------- src/components/playbackManager.ts | 39 +++--- src/helpers.ts | 34 +++-- src/types/appStatus.ts | 3 +- src/types/global.d.ts | 14 +-- 13 files changed, 148 insertions(+), 151 deletions(-) diff --git a/src/components/__tests__/jellyfinApi.test.ts b/src/components/__tests__/jellyfinApi.test.ts index 9c144f0d..fe959dd1 100644 --- a/src/components/__tests__/jellyfinApi.test.ts +++ b/src/components/__tests__/jellyfinApi.test.ts @@ -1,8 +1,8 @@ import { JellyfinApi } from '../jellyfinApi'; const setupMockCastSenders = (): void => { - const getSenders = (): Array => [{ id: 'thisIsSenderId' }]; - const getInstance = (): any => ({ getSenders }); + const getSenders = (): Array => [{ id: 'thisIsSenderId' }]; // eslint-disable-line no-explicit-any + const getInstance = (): any => ({ getSenders }); // eslint-disable-line no-explicit-any // @ts-expect-error cast is already defined globally, however since we're mocking it we need to redefine it. global.cast = { diff --git a/src/components/codecSupportHelper.ts b/src/components/codecSupportHelper.ts index 77083947..0845050f 100644 --- a/src/components/codecSupportHelper.ts +++ b/src/components/codecSupportHelper.ts @@ -117,6 +117,7 @@ export function getMaxWidthSupport(deviceId: number, codec?: string): number { case deviceIds.NESTHUBANDMAX: return 1280; } + return 0; } diff --git a/src/components/commandHandler.ts b/src/components/commandHandler.ts index 470591a7..4e44a0a9 100644 --- a/src/components/commandHandler.ts +++ b/src/components/commandHandler.ts @@ -8,7 +8,7 @@ import type { SetRepeatModeRequest, SupportedCommands } from '../types/global'; - +import { AppStatus } from '../types/appStatus'; import { translateItems, shuffle, @@ -17,9 +17,7 @@ import { setSubtitleStreamIndex, seek } from './maincontroller'; - import { reportPlaybackProgress } from './jellyfinActions'; -import { AppStatus } from '../types/appStatus'; import { PlaybackManager } from './playbackManager'; import { DocumentManager } from './documentManager'; @@ -140,6 +138,7 @@ export abstract class CommandHandler { if (!PlaybackManager.isBuffering()) { DocumentManager.setAppStatus(AppStatus.Waiting); } + DocumentManager.startBackdropInterval(); } else { // When a client connects send back the initial device state (volume etc) via a playbackstop message @@ -194,7 +193,6 @@ export abstract class CommandHandler { } static UnpauseHandler(): void { - this.playerManager.play(); } diff --git a/src/components/deviceprofileBuilder.ts b/src/components/deviceprofileBuilder.ts index 099058e5..91c33065 100644 --- a/src/components/deviceprofileBuilder.ts +++ b/src/components/deviceprofileBuilder.ts @@ -46,7 +46,6 @@ let currentDeviceId: number; /** * Create and return a new ProfileCondition - * * @param Property - What property the condition should test. * @param Condition - The condition to test the values for. * @param Value - The value to compare against. @@ -69,7 +68,6 @@ function createProfileCondition( /** * Get container profiles - * * @todo Why does this always return an empty array? * @returns Container profiles. */ @@ -79,7 +77,6 @@ function getContainerProfiles(): Array { /** * Get response profiles - * * @returns Response profiles. */ function getResponseProfiles(): Array { @@ -94,6 +91,7 @@ function getResponseProfiles(): Array { } /** + * Get direct play profiles * @returns Direct play profiles. */ function getDirectPlayProfiles(): Array { @@ -158,7 +156,6 @@ function getDirectPlayProfiles(): Array { /** * Get codec profiles - * * @returns Codec profiles. */ function getCodecProfiles(): Array { @@ -307,7 +304,6 @@ function getCodecProfiles(): Array { /** * Get transcoding profiles - * * @returns Transcoding profiles. */ function getTranscodingProfiles(): Array { @@ -387,7 +383,6 @@ function getTranscodingProfiles(): Array { /** * Get subtitle profiles - * * @returns Subtitle profiles. */ function getSubtitleProfiles(): Array { diff --git a/src/components/documentManager.ts b/src/components/documentManager.ts index a8b89de6..f19d2e12 100644 --- a/src/components/documentManager.ts +++ b/src/components/documentManager.ts @@ -1,8 +1,8 @@ import type { BaseItemDto } from '@jellyfin/sdk/lib/generated-client'; +import { AppStatus } from '../types/appStatus'; import { parseISO8601Date, TicksPerSecond, ticksToSeconds } from '../helpers'; import { JellyfinApi } from './jellyfinApi'; import { deviceIds, getActiveDeviceId } from './castDevices'; -import { AppStatus } from '../types/appStatus'; export abstract class DocumentManager { // Duration between each backdrop switch in ms diff --git a/src/components/fetchhelper.ts b/src/components/fetchhelper.ts index 582fb372..3366c17e 100644 --- a/src/components/fetchhelper.ts +++ b/src/components/fetchhelper.ts @@ -3,7 +3,7 @@ * @param request - Custom request object, mostly modeled after RequestInit. * @returns response promise */ -function getFetchPromise(request: any): Promise { +function getFetchPromise(request: any): Promise { // eslint-disable-line no-explicit-any const headers = request.headers || {}; if (request.dataType === 'json') { @@ -102,7 +102,7 @@ function paramsToString(params: Record): string { * @param request - RequestInit-like structure but with url/type/timeout parameters as well * @returns response promise, may be automatically unpacked based on request datatype */ -export async function ajax(request: any): Promise { +export async function ajax(request: any): Promise { // eslint-disable-line no-explicit-any if (!request) { throw new Error('Request cannot be null'); } diff --git a/src/components/jellyfinActions.ts b/src/components/jellyfinActions.ts index cd1063ff..159ec9c6 100644 --- a/src/components/jellyfinActions.ts +++ b/src/components/jellyfinActions.ts @@ -6,10 +6,7 @@ import type { PlaybackProgressInfo, PlayRequest } from '@jellyfin/sdk/lib/generated-client'; -import { - getSenderReportingData, - broadcastToMessageBus -} from '../helpers'; +import { getSenderReportingData, broadcastToMessageBus } from '../helpers'; import { AppStatus } from '../types/appStatus'; import { JellyfinApi } from './jellyfinApi'; import { DocumentManager } from './documentManager'; @@ -37,7 +34,7 @@ function restartPingInterval(reportingParams: PlaybackProgressInfo): void { stopPingInterval(); if (reportingParams.PlayMethod == 'Transcode') { - pingInterval = setInterval(() => { + pingInterval = setInterval(() => { // eslint-disable-line no-explicit-any pingTranscoder(reportingParams); }, 1000); } @@ -183,14 +180,10 @@ export function pingTranscoder( /** * Update the context about the item we are playing. - * @param playbackMgr - playback manager. * @param customData - data to set on playback state. * @param serverItem - item that is playing */ -export function load( - customData: any, - serverItem: BaseItemDto -): void { +export function load(customData: any, serverItem: BaseItemDto): void { // eslint-disable-line no-explicit-any PlaybackManager.resetPlaybackScope(); const state = PlaybackManager.playbackState; @@ -242,14 +235,16 @@ export function play(state: PlaybackState): void { } /** - * @param item - * @param maxBitrate - * @param deviceProfile - * @param startPosition - * @param mediaSourceId - * @param audioStreamIndex - * @param subtitleStreamIndex - * @param liveStreamId + * get PlaybackInfo + * @param item - item + * @param maxBitrate - maxBitrate + * @param deviceProfile - deviceProfile + * @param startPosition - startPosition + * @param mediaSourceId - mediaSourceId + * @param audioStreamIndex - audioStreamIndex + * @param subtitleStreamIndex - subtitleStreamIndex + * @param liveStreamId - liveStreamId + * @returns promise */ export function getPlaybackInfo( item: BaseItemDto, @@ -260,7 +255,7 @@ export function getPlaybackInfo( audioStreamIndex: number, subtitleStreamIndex: number, liveStreamId: string | null = null -): Promise { +): Promise { // eslint-disable-line no-explicit-any const postData = { DeviceProfile: deviceProfile }; @@ -298,14 +293,16 @@ export function getPlaybackInfo( } /** - * @param item - * @param playSessionId - * @param maxBitrate - * @param deviceProfile - * @param startPosition - * @param mediaSource - * @param audioStreamIndex - * @param subtitleStreamIndex + * get LiveStream + * @param item - item + * @param playSessionId - playSessionId + * @param maxBitrate - maxBitrate + * @param deviceProfile - deviceProfile + * @param startPosition - startPosition + * @param mediaSource - mediaSource + * @param audioStreamIndex - audioStreamIndex + * @param subtitleStreamIndex - subtitleStreamIndex + * @returns promise */ export function getLiveStream( item: BaseItemDto, @@ -349,7 +346,6 @@ export function getLiveStream( /** * Get download speed based on the jellyfin bitratetest api. - * * The API has a 10MB limit. * @param byteSize - number of bytes to request * @returns the bitrate in bits/s @@ -378,7 +374,6 @@ export async function getDownloadSpeed(byteSize: number): Promise { * Function to detect the bitrate. * It starts at 500kB and doubles it every time it takes under 2s, for max 10MB. * This should get an accurate bitrate relatively fast on any connection - * * @param numBytes - Number of bytes to start with, default 500k * @returns bitrate in bits/s */ diff --git a/src/components/jellyfinApi.ts b/src/components/jellyfinApi.ts index f1502bcb..871645a9 100644 --- a/src/components/jellyfinApi.ts +++ b/src/components/jellyfinApi.ts @@ -26,10 +26,7 @@ export abstract class JellyfinApi { serverAddress?: string, receiverName = '' ): void { - console.debug( - `JellyfinApi.setServerInfo: user:${userId}, token:${accessToken}, ` + - `server:${serverAddress}, name:${receiverName}` - ); + console.debug(`JellyfinApi.setServerInfo: user:${userId}, token:${accessToken}, server:${serverAddress}, name:${receiverName}`); this.userId = userId; this.accessToken = accessToken; this.serverAddress = serverAddress; @@ -127,7 +124,7 @@ export abstract class JellyfinApi { } // Authenticated ajax - public static authAjax(path: string, args: any): Promise { + public static authAjax(path: string, args: any): Promise { // eslint-disable-line no-explicit-any if ( this.userId === undefined || this.accessToken === undefined || @@ -149,7 +146,7 @@ export abstract class JellyfinApi { } // Authenticated ajax - public static authAjaxUser(path: string, args: any): Promise { + public static authAjaxUser(path: string, args: any): Promise { // eslint-disable-line no-explicit-any if ( this.userId === undefined || this.accessToken === undefined || diff --git a/src/components/maincontroller.ts b/src/components/maincontroller.ts index b5536447..8657b3ff 100644 --- a/src/components/maincontroller.ts +++ b/src/components/maincontroller.ts @@ -11,7 +11,6 @@ import { getInstantMixItems, translateRequestedItems, broadcastToMessageBus, - broadcastConnectionErrorMessage, ticksToSeconds } from '../helpers'; import { @@ -26,7 +25,6 @@ import { JellyfinApi } from './jellyfinApi'; import { PlaybackManager, PlaybackState } from './playbackManager'; import { CommandHandler } from './commandHandler'; import { getMaxBitrateSupport } from './codecSupportHelper'; -import { DocumentManager } from './documentManager'; import { PlayRequest } from '~/types/global'; window.castReceiverContext = cast.framework.CastReceiverContext.getInstance(); @@ -43,7 +41,7 @@ let broadcastToServer = new Date(); let hasReportedCapabilities = false; /** - * + * onMediaElementTimeUpdate */ export function onMediaElementTimeUpdate(): void { if (PlaybackManager.playbackState.isChangingStream) { @@ -73,7 +71,7 @@ export function onMediaElementTimeUpdate(): void { } /** - * + * onMediaElementPause */ export function onMediaElementPause(): void { if (PlaybackManager.playbackState.isChangingStream) { @@ -84,7 +82,7 @@ export function onMediaElementPause(): void { } /** - * + * onMediaElementPlaying */ export function onMediaElementPlaying(): void { if (PlaybackManager.playbackState.isChangingStream) { @@ -95,7 +93,8 @@ export function onMediaElementPlaying(): void { } /** - * @param event + * onMediaElementVolumeChange + * @param event - event */ function onMediaElementVolumeChange(event: framework.system.Event): void { window.volume = (event).data; @@ -107,7 +106,7 @@ function onMediaElementVolumeChange(event: framework.system.Event): void { } /** - * + * enableTimeUpdateListener */ export function enableTimeUpdateListener(): void { window.playerManager.addEventListener( @@ -129,7 +128,7 @@ export function enableTimeUpdateListener(): void { } /** - * + * disableTimeUpdateListener */ export function disableTimeUpdateListener(): void { window.playerManager.removeEventListener( @@ -186,7 +185,7 @@ window.playerManager.addEventListener( ); /** - * + * defaultOnStop */ function defaultOnStop(): void { PlaybackManager.onStop(); @@ -237,7 +236,8 @@ window.playerManager.addEventListener( (): void => { reportPlaybackStopped( PlaybackManager.playbackState, - getReportingParams(PlaybackManager.playbackState)); + getReportingParams(PlaybackManager.playbackState) + ); } ); @@ -253,7 +253,8 @@ window.playerManager.addEventListener( ); /** - * + * reportDeviceCapabilities + * @returns Promise */ export async function reportDeviceCapabilities(): Promise { const maxBitrate = await getMaxBitrate(); @@ -280,9 +281,10 @@ export async function reportDeviceCapabilities(): Promise { } /** - * @param data + * processMessage + * @param data - data */ -export function processMessage(data: any): void { +export function processMessage(data: any): void { // eslint-disable-line no-explicit-any if ( !data.command || !data.serverAddress || @@ -349,8 +351,10 @@ export function processMessage(data: any): void { } /** - * @param name - * @param reportToServer + * reportEvent + * @param name - name + * @param reportToServer - reportToServer + * @returns Promise */ export function reportEvent( name: string, @@ -367,8 +371,9 @@ export function reportEvent( } /** + * setSubtitleStreamIndex * @param state - playback state. - * @param index + * @param index - index */ export function setSubtitleStreamIndex( state: PlaybackState, @@ -380,7 +385,7 @@ export function setSubtitleStreamIndex( // FIXME: Possible index error when MediaStreams is undefined. const currentSubtitleStream = state.mediaSource?.MediaStreams?.filter( - (m: any) => { + (m: any) => { // eslint-disable-line no-explicit-any return m.Index == state.subtitleStreamIndex && m.Type == 'Subtitle'; } )[0]; @@ -408,7 +413,7 @@ export function setSubtitleStreamIndex( const mediaStreams = state.PlaybackMediaSource?.MediaStreams; const subtitleStream = getStreamByIndex( - mediaStreams, + mediaStreams, // eslint-disable-line no-explicit-any 'Subtitle', index ); @@ -448,8 +453,9 @@ export function setSubtitleStreamIndex( } /** + * setAudioStreamIndex * @param state - playback state. - * @param index + * @param index - index */ export function setAudioStreamIndex( state: PlaybackState, @@ -463,22 +469,26 @@ export function setAudioStreamIndex( } /** + * seek * @param state - playback state. - * @param ticks + * @param ticks - ticks + * @returns promise */ export function seek(state: PlaybackState, ticks: number): Promise { return changeStream(state, ticks); } /** + * changeStream * @param state - playback state. - * @param ticks - * @param params + * @param ticks - ticks + * @param params - params + * @returns promise */ export async function changeStream( state: PlaybackState, ticks: number, - params: any = undefined + params: any = undefined // eslint-disable-line no-explicit-any ): Promise { if ( window.playerManager.getMediaInformation()?.customData.canClientSeek && @@ -504,7 +514,7 @@ export async function changeStream( // await stopActiveEncodings($scope.playSessionId); //} - // @ts-expect-error + // @ts-expect-error is possible here return await PlaybackManager.playItemInternal(state.item, { audioStreamIndex: params.AudioStreamIndex == null @@ -524,8 +534,8 @@ export async function changeStream( // TODO save namespace somewhere global? window.castReceiverContext.addCustomMessageListener( 'urn:x-cast:com.connectsdk', - (evt: any) => { - let data: any = evt.data; + (evt: any) => { // eslint-disable-line no-explicit-any + let data: any = evt.data; // eslint-disable-line no-explicit-any // Apparently chromium likes to pass it as json, not as object. // chrome on android works fine @@ -545,12 +555,14 @@ window.castReceiverContext.addCustomMessageListener( ); /** - * @param data - * @param options - * @param method + * translateItems + * @param data - data + * @param options - options + * @param method - method + * @returns promise */ export async function translateItems( - data: any, + data: any, // eslint-disable-line no-explicit-any options: PlayRequest, method: string ): Promise { @@ -576,13 +588,15 @@ export async function translateItems( } /** - * @param data - * @param options - * @param item + * instantMix + * @param data - data + * @param options - options + * @param item - item + * @returns promise */ export async function instantMix( - data: any, - options: any, + data: any, // eslint-disable-line no-explicit-any + options: any, // eslint-disable-line no-explicit-any item: BaseItemDto ): Promise { const result = await getInstantMixItems(data.userId, item); @@ -592,13 +606,15 @@ export async function instantMix( } /** - * @param data - * @param options - * @param item + * shuffle + * @param data - data + * @param options - options + * @param item - item + * @returns promise */ export async function shuffle( - data: any, - options: any, + data: any, // eslint-disable-line no-explicit-any + options: any, // eslint-disable-line no-explicit-any item: BaseItemDto ): Promise { const result = await getShuffleItems(data.userId, item); @@ -608,6 +624,7 @@ export async function shuffle( } /** + * onStopPlayerBeforePlaybackDone * This function fetches the full information of an item before playing it. * Only item.Id needs to be set. * @param item - Item to look up @@ -616,7 +633,7 @@ export async function shuffle( */ export async function onStopPlayerBeforePlaybackDone( item: BaseItemDto, - options: any + options: any // eslint-disable-line no-explicit-any ): Promise { const data = await JellyfinApi.authAjaxUser(`Items/${item.Id}`, { dataType: 'json', @@ -629,7 +646,8 @@ export async function onStopPlayerBeforePlaybackDone( let lastBitrateDetect = 0; let detectedBitrate = 0; /** - * + * getMaxBitrate + * @returns promise */ export async function getMaxBitrate(): Promise { console.log('getMaxBitrate'); @@ -667,16 +685,19 @@ export async function getMaxBitrate(): Promise { } /** - * @param error + * showPlaybackInfoErrorMessage + * @param error - error */ export function showPlaybackInfoErrorMessage(error: string): void { broadcastToMessageBus({ message: error, type: 'playbackerror' }); } /** - * @param versions + * getOptimalMediaSource + * @param versions - versions + * @returns stream */ -export function getOptimalMediaSource(versions: Array): any { +export function getOptimalMediaSource(versions: Array): any { // eslint-disable-line no-explicit-any let optimalVersion = versions.filter((v) => { checkDirectPlay(v); @@ -699,6 +720,7 @@ export function getOptimalMediaSource(versions: Array): any { // Disable direct play on non-http sources /** + * checkDirectPlay * @param mediaSource */ export function checkDirectPlay(mediaSource: MediaSourceInfo): void { @@ -715,6 +737,7 @@ export function checkDirectPlay(mediaSource: MediaSourceInfo): void { } /** + * setTextTrack * @param index */ export function setTextTrack(index: number | null): void { @@ -798,14 +821,16 @@ export function setTextTrack(index: number | null): void { // TODO no any types /** - * @param playSessionId - * @param item - * @param streamInfo + * createMediaInformation + * @param playSessionId - playSessionId + * @param item - item + * @param streamInfo - streamInfo + * @returns media information */ export function createMediaInformation( playSessionId: string, item: BaseItemDto, - streamInfo: any + streamInfo: any // eslint-disable-line no-explicit-any ): framework.messages.MediaInformation { const mediaInfo = new cast.framework.messages.MediaInformation(); diff --git a/src/components/playbackManager.ts b/src/components/playbackManager.ts index e95ae592..8f405ad3 100644 --- a/src/components/playbackManager.ts +++ b/src/components/playbackManager.ts @@ -3,6 +3,9 @@ import type { MediaSourceInfo, PlayMethod } from '@jellyfin/sdk/lib/generated-client'; +import { RepeatMode } from '@jellyfin/sdk/lib/generated-client'; +import { AppStatus } from '../types/appStatus'; +import { ItemIndex } from '~/types/global'; import { broadcastConnectionErrorMessage, createStreamInfo, @@ -48,9 +51,6 @@ export interface PlaybackState { runtimeTicks: number; } -import { ItemIndex } from '~/types/global'; -import { AppStatus } from '../types/appStatus'; -import { RepeatMode } from '@jellyfin/sdk/lib/generated-client'; export abstract class PlaybackManager { private static playerManager: framework.PlayerManager; @@ -79,7 +79,6 @@ export abstract class PlaybackManager { static setPlayerManager(playerManager: framework.PlayerManager): void { // Parameters this.playerManager = playerManager; - this.resetPlaylist(); } @@ -88,13 +87,11 @@ export abstract class PlaybackManager { * * Returns true when playing or paused. * (before: true only when playing) - * */ + */ static isPlaying(): boolean { return ( - this.playerManager.getPlayerState() === - cast.framework.messages.PlayerState.PLAYING || - this.playerManager.getPlayerState() === - cast.framework.messages.PlayerState.PAUSED + this.playerManager.getPlayerState() === cast.framework.messages.PlayerState.PLAYING || + this.playerManager.getPlayerState() === cast.framework.messages.PlayerState.PAUSED ); } @@ -102,7 +99,7 @@ export abstract class PlaybackManager { return this.playerManager.getPlayerState() === cast.framework.messages.PlayerState.BUFFERING; } - static async playFromOptions(options: any): Promise { + static async playFromOptions(options: any): Promise { // eslint-disable-line no-explicit-any const firstItem = options.items[0]; if (options.startPositionTicks || firstItem.MediaType !== 'Video') { @@ -112,7 +109,7 @@ export abstract class PlaybackManager { return this.playFromOptionsInternal(options); } - private static playFromOptionsInternal(options: any): Promise { + private static playFromOptionsInternal(options: any): Promise { // eslint-disable-line no-explicit-any const stopPlayer = this.activePlaylist && this.activePlaylist.length > 0; @@ -146,26 +143,22 @@ export abstract class PlaybackManager { return this.activePlaylistIndex > 0; } - static playNextItem(options: any = {}, stopPlayer = false): boolean { + static playNextItem(options: any = {}, stopPlayer = false): boolean { // eslint-disable-line no-explicit-any const nextItemInfo = this.getNextPlaybackItemInfo(); if (nextItemInfo) { this.activePlaylistIndex = nextItemInfo.index; - this.playItem(options, stopPlayer); - return true; } return false; } - static playPreviousItem(options: any = {}): boolean { + static playPreviousItem(options: any = {}): boolean { // eslint-disable-line no-explicit-any if (this.activePlaylist && this.activePlaylistIndex > 0) { this.activePlaylistIndex--; - this.playItem(options, true); - return true; } @@ -174,7 +167,7 @@ export abstract class PlaybackManager { // play item from playlist private static async playItem( - options: any, + options: any, // eslint-disable-line no-explicit-any stopPlayer = false ): Promise { if (stopPlayer) { @@ -182,16 +175,14 @@ export abstract class PlaybackManager { } const item = this.activePlaylist[this.activePlaylistIndex]; - console.log(`Playing index ${this.activePlaylistIndex}`, item); - return await onStopPlayerBeforePlaybackDone(item, options); } // Would set private, but some refactorings need to happen first. static async playItemInternal( item: BaseItemDto, - options: any + options: any // eslint-disable-line no-explicit-any ): Promise { this.playbackState.isChangingStream = false; DocumentManager.setAppStatus(AppStatus.Loading); @@ -256,7 +247,7 @@ export abstract class PlaybackManager { playSessionId: string, item: BaseItemDto, mediaSource: MediaSourceInfo, - options: any + options: any // eslint-disable-line no-explicit-any ): void { DocumentManager.setAppStatus(AppStatus.Loading); @@ -279,8 +270,7 @@ export abstract class PlaybackManager { // If we should seek at the start, translate it // to seconds and give it to loadRequestData :) if (mediaInfo.customData.startPositionTicks > 0) { - loadRequestData.currentTime = - ticksToSeconds(mediaInfo.customData.startPositionTicks); + loadRequestData.currentTime = ticksToSeconds(mediaInfo.customData.startPositionTicks); } load(mediaInfo.customData, item); @@ -330,7 +320,6 @@ export abstract class PlaybackManager { /** * Get information about the next item to play from window.playlist - * * @returns item and index, or null to end playback */ static getNextPlaybackItemInfo(): ItemIndex | null { diff --git a/src/helpers.ts b/src/helpers.ts index 9122c140..1ce9266a 100644 --- a/src/helpers.ts +++ b/src/helpers.ts @@ -59,7 +59,7 @@ export function getReportingParams(state: PlaybackState): PlaybackProgressInfo { } /** - + * getSenderReportingData * 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 * the connected client (or clients?). @@ -70,8 +70,8 @@ export function getReportingParams(state: PlaybackState): PlaybackProgressInfo { export function getSenderReportingData( playbackState: PlaybackState, reportingData: PlaybackProgressInfo -): any { - const state: any = { +): any { // eslint-disable-line no-explicit-any + const state: any = { // eslint-disable-line no-explicit-any ItemId: reportingData.ItemId, PlayState: reportingData, QueueableMediaTypes: ['Audio', 'Video'] @@ -166,8 +166,8 @@ export function getSenderReportingData( * @param item - item to look up * @returns one of the metadata classes in cast.framework.messages.*Metadata */ -export function getMetadata(item: BaseItemDto): any { - let metadata: any; +export function getMetadata(item: BaseItemDto): any { // eslint-disable-line no-explicit-any + let metadata: any; // eslint-disable-line no-explicit-any let posterUrl = ''; if (item.SeriesPrimaryImageTag) { @@ -270,12 +270,11 @@ export function getMetadata(item: BaseItemDto): any { /** * Check if a media source is an HLS stream - * * @param mediaSource - * @returns + * @returns boolean */ -export function isHlsStream(mediaSource: MediaSourceInfo) { - return mediaSource.TranscodingSubProtocol == 'hls' +export function isHlsStream(mediaSource: MediaSourceInfo): boolean { + return mediaSource.TranscodingSubProtocol == 'hls'; } /** @@ -290,7 +289,7 @@ export function createStreamInfo( item: BaseItemDto, mediaSource: MediaSourceInfo, startPosition: number | null -): any { +): any { // eslint-disable-line no-explicit-any let mediaUrl; let contentType; @@ -302,7 +301,6 @@ export function createStreamInfo( ? `#t=${startPositionInSeekParam}` : ''; - let isStatic = false; let streamContainer = mediaSource.Container; @@ -379,7 +377,7 @@ export function createStreamInfo( // It is a pain and will require unbinding all event handlers during the operation const canSeek = (mediaSource.RunTimeTicks || 0) > 0; - const info: any = { + const info: any = { // eslint-disable-line no-explicit-any audioStreamIndex: mediaSource.DefaultAudioStreamIndex, canClientSeek: isStatic || (canSeek && streamContainer == 'm3u8'), canSeek: canSeek, @@ -394,12 +392,12 @@ export function createStreamInfo( }; const subtitleStreams = - mediaSource.MediaStreams?.filter((stream: any) => { + mediaSource.MediaStreams?.filter((stream: any) => { // eslint-disable-line no-explicit-any return stream.Type === 'Subtitle'; }) ?? []; const subtitleTracks: Array = []; - subtitleStreams.forEach((subtitleStream: any) => { + subtitleStreams.forEach((subtitleStream: any) => { // eslint-disable-line no-explicit-any if (subtitleStream.DeliveryUrl === undefined) { /* The CAF v3 player only supports vtt currently, * SRT subs can be "transcoded" to vtt by jellyfin. @@ -443,10 +441,10 @@ export function createStreamInfo( * @returns first first matching stream */ export function getStreamByIndex( - streams: Array, + streams: Array, // eslint-disable-line no-explicit-any type: string, index: number -): any { +): any { // eslint-disable-line no-explicit-any return streams.filter((s) => { return s.Type == type && s.Index == index; })[0]; @@ -507,7 +505,7 @@ export async function getInstantMixItems( userId: string, item: BaseItemDto ): Promise { - const query: any = { + const query: any = { // eslint-disable-line no-explicit-any Fields: requiredItemFields, Limit: 50, UserId: userId @@ -748,7 +746,7 @@ export function parseISO8601Date(date: string): Date { * @returns number of seconds */ export function ticksToSeconds(ticks: number): number { - return ticks / TicksPerSecond + return ticks / TicksPerSecond; } /** diff --git a/src/types/appStatus.ts b/src/types/appStatus.ts index 9ab41578..cf9256b1 100644 --- a/src/types/appStatus.ts +++ b/src/types/appStatus.ts @@ -6,6 +6,5 @@ export enum AppStatus { PlayingWithControls = 'PlayingWithControls', Playing = 'Playing', Unset = '', - Waiting = 'Waiting', + Waiting = 'Waiting' } - diff --git a/src/types/global.d.ts b/src/types/global.d.ts index 3f11a530..ddeed9c3 100644 --- a/src/types/global.d.ts +++ b/src/types/global.d.ts @@ -76,12 +76,12 @@ export interface SeekRequest { export interface DataMessage { options: - | PlayRequest - | DisplayRequest - | SetIndexRequest - | SetRepeatModeRequest - | SeekRequest; - command: string; + | PlayRequest + | DisplayRequest + | SetIndexRequest + | SetRepeatModeRequest + | SeekRequest; + command: string; } interface SupportedCommands { @@ -98,7 +98,7 @@ declare global { castReceiverContext: CastReceiverContext; repeatMode: RepeatMode; reportEventType: 'repeatmodechange'; - subtitleAppearance: any; + subtitleAppearance: any; // eslint-disable-line no-explicit-any MaxBitrate: number | undefined; senderId: string | undefined; volume: SystemVolumeData; From a909ba905f3d6cf78d5ada8caee9a9e3bad16856 Mon Sep 17 00:00:00 2001 From: Sky-High Date: Mon, 25 Sep 2023 18:25:27 +0200 Subject: [PATCH 27/29] pacify es-lint 2 --- src/components/__tests__/jellyfinApi.test.ts | 4 +-- src/components/fetchhelper.ts | 4 +-- src/components/jellyfinActions.ts | 6 ++--- src/components/jellyfinApi.ts | 4 +-- src/components/maincontroller.ts | 28 ++++++++++---------- src/components/playbackManager.ts | 14 +++++----- 6 files changed, 30 insertions(+), 30 deletions(-) diff --git a/src/components/__tests__/jellyfinApi.test.ts b/src/components/__tests__/jellyfinApi.test.ts index fe959dd1..616b0925 100644 --- a/src/components/__tests__/jellyfinApi.test.ts +++ b/src/components/__tests__/jellyfinApi.test.ts @@ -1,8 +1,8 @@ import { JellyfinApi } from '../jellyfinApi'; const setupMockCastSenders = (): void => { - const getSenders = (): Array => [{ id: 'thisIsSenderId' }]; // eslint-disable-line no-explicit-any - const getInstance = (): any => ({ getSenders }); // eslint-disable-line no-explicit-any + const getSenders = (): Array => [{ id: 'thisIsSenderId' }]; // eslint-disable-line @typescript-eslint/no-explicit-any + const getInstance = (): any => ({ getSenders }); // eslint-disable-line @typescript-eslint/no-explicit-any // @ts-expect-error cast is already defined globally, however since we're mocking it we need to redefine it. global.cast = { diff --git a/src/components/fetchhelper.ts b/src/components/fetchhelper.ts index 3366c17e..a8ac3ce4 100644 --- a/src/components/fetchhelper.ts +++ b/src/components/fetchhelper.ts @@ -3,7 +3,7 @@ * @param request - Custom request object, mostly modeled after RequestInit. * @returns response promise */ -function getFetchPromise(request: any): Promise { // eslint-disable-line no-explicit-any +function getFetchPromise(request: any): Promise { // eslint-disable-line @typescript-eslint/no-explicit-any const headers = request.headers || {}; if (request.dataType === 'json') { @@ -102,7 +102,7 @@ function paramsToString(params: Record): string { * @param request - RequestInit-like structure but with url/type/timeout parameters as well * @returns response promise, may be automatically unpacked based on request datatype */ -export async function ajax(request: any): Promise { // eslint-disable-line no-explicit-any +export async function ajax(request: any): Promise { // eslint-disable-line @typescript-eslint/no-explicit-any if (!request) { throw new Error('Request cannot be null'); } diff --git a/src/components/jellyfinActions.ts b/src/components/jellyfinActions.ts index 159ec9c6..0a276aae 100644 --- a/src/components/jellyfinActions.ts +++ b/src/components/jellyfinActions.ts @@ -34,7 +34,7 @@ function restartPingInterval(reportingParams: PlaybackProgressInfo): void { stopPingInterval(); if (reportingParams.PlayMethod == 'Transcode') { - pingInterval = setInterval(() => { // eslint-disable-line no-explicit-any + pingInterval = setInterval(() => { // eslint-disable-line @typescript-eslint/no-explicit-any pingTranscoder(reportingParams); }, 1000); } @@ -183,7 +183,7 @@ export function pingTranscoder( * @param customData - data to set on playback state. * @param serverItem - item that is playing */ -export function load(customData: any, serverItem: BaseItemDto): void { // eslint-disable-line no-explicit-any +export function load(customData: any, serverItem: BaseItemDto): void { // eslint-disable-line @typescript-eslint/no-explicit-any PlaybackManager.resetPlaybackScope(); const state = PlaybackManager.playbackState; @@ -255,7 +255,7 @@ export function getPlaybackInfo( audioStreamIndex: number, subtitleStreamIndex: number, liveStreamId: string | null = null -): Promise { // eslint-disable-line no-explicit-any +): Promise { // eslint-disable-line @typescript-eslint/no-explicit-any const postData = { DeviceProfile: deviceProfile }; diff --git a/src/components/jellyfinApi.ts b/src/components/jellyfinApi.ts index 871645a9..38f4afd3 100644 --- a/src/components/jellyfinApi.ts +++ b/src/components/jellyfinApi.ts @@ -124,7 +124,7 @@ export abstract class JellyfinApi { } // Authenticated ajax - public static authAjax(path: string, args: any): Promise { // eslint-disable-line no-explicit-any + public static authAjax(path: string, args: any): Promise { // eslint-disable-line @typescript-eslint/no-explicit-any if ( this.userId === undefined || this.accessToken === undefined || @@ -146,7 +146,7 @@ export abstract class JellyfinApi { } // Authenticated ajax - public static authAjaxUser(path: string, args: any): Promise { // eslint-disable-line no-explicit-any + public static authAjaxUser(path: string, args: any): Promise { // eslint-disable-line @typescript-eslint/no-explicit-any if ( this.userId === undefined || this.accessToken === undefined || diff --git a/src/components/maincontroller.ts b/src/components/maincontroller.ts index 8657b3ff..df187c0a 100644 --- a/src/components/maincontroller.ts +++ b/src/components/maincontroller.ts @@ -284,7 +284,7 @@ export async function reportDeviceCapabilities(): Promise { * processMessage * @param data - data */ -export function processMessage(data: any): void { // eslint-disable-line no-explicit-any +export function processMessage(data: any): void { // eslint-disable-line @typescript-eslint/no-explicit-any if ( !data.command || !data.serverAddress || @@ -385,7 +385,7 @@ export function setSubtitleStreamIndex( // FIXME: Possible index error when MediaStreams is undefined. const currentSubtitleStream = state.mediaSource?.MediaStreams?.filter( - (m: any) => { // eslint-disable-line no-explicit-any + (m: any) => { // eslint-disable-line @typescript-eslint/no-explicit-any return m.Index == state.subtitleStreamIndex && m.Type == 'Subtitle'; } )[0]; @@ -413,7 +413,7 @@ export function setSubtitleStreamIndex( const mediaStreams = state.PlaybackMediaSource?.MediaStreams; const subtitleStream = getStreamByIndex( - mediaStreams, // eslint-disable-line no-explicit-any + mediaStreams, // eslint-disable-line @typescript-eslint/no-explicit-any 'Subtitle', index ); @@ -488,7 +488,7 @@ export function seek(state: PlaybackState, ticks: number): Promise { export async function changeStream( state: PlaybackState, ticks: number, - params: any = undefined // eslint-disable-line no-explicit-any + params: any = undefined // eslint-disable-line @typescript-eslint/no-explicit-any ): Promise { if ( window.playerManager.getMediaInformation()?.customData.canClientSeek && @@ -534,8 +534,8 @@ export async function changeStream( // TODO save namespace somewhere global? window.castReceiverContext.addCustomMessageListener( 'urn:x-cast:com.connectsdk', - (evt: any) => { // eslint-disable-line no-explicit-any - let data: any = evt.data; // eslint-disable-line no-explicit-any + (evt: any) => { // eslint-disable-line @typescript-eslint/no-explicit-any + let data: any = evt.data; // eslint-disable-line @typescript-eslint/no-explicit-any // Apparently chromium likes to pass it as json, not as object. // chrome on android works fine @@ -562,7 +562,7 @@ window.castReceiverContext.addCustomMessageListener( * @returns promise */ export async function translateItems( - data: any, // eslint-disable-line no-explicit-any + data: any, // eslint-disable-line @typescript-eslint/no-explicit-any options: PlayRequest, method: string ): Promise { @@ -595,8 +595,8 @@ export async function translateItems( * @returns promise */ export async function instantMix( - data: any, // eslint-disable-line no-explicit-any - options: any, // eslint-disable-line no-explicit-any + data: any, // eslint-disable-line @typescript-eslint/no-explicit-any + options: any, // eslint-disable-line @typescript-eslint/no-explicit-any item: BaseItemDto ): Promise { const result = await getInstantMixItems(data.userId, item); @@ -613,8 +613,8 @@ export async function instantMix( * @returns promise */ export async function shuffle( - data: any, // eslint-disable-line no-explicit-any - options: any, // eslint-disable-line no-explicit-any + data: any, // eslint-disable-line @typescript-eslint/no-explicit-any + options: any, // eslint-disable-line @typescript-eslint/no-explicit-any item: BaseItemDto ): Promise { const result = await getShuffleItems(data.userId, item); @@ -633,7 +633,7 @@ export async function shuffle( */ export async function onStopPlayerBeforePlaybackDone( item: BaseItemDto, - options: any // eslint-disable-line no-explicit-any + options: any // eslint-disable-line @typescript-eslint/no-explicit-any ): Promise { const data = await JellyfinApi.authAjaxUser(`Items/${item.Id}`, { dataType: 'json', @@ -697,7 +697,7 @@ export function showPlaybackInfoErrorMessage(error: string): void { * @param versions - versions * @returns stream */ -export function getOptimalMediaSource(versions: Array): any { // eslint-disable-line no-explicit-any +export function getOptimalMediaSource(versions: Array): any { // eslint-disable-line @typescript-eslint/no-explicit-any let optimalVersion = versions.filter((v) => { checkDirectPlay(v); @@ -830,7 +830,7 @@ export function setTextTrack(index: number | null): void { export function createMediaInformation( playSessionId: string, item: BaseItemDto, - streamInfo: any // eslint-disable-line no-explicit-any + streamInfo: any // eslint-disable-line @typescript-eslint/no-explicit-any ): framework.messages.MediaInformation { const mediaInfo = new cast.framework.messages.MediaInformation(); diff --git a/src/components/playbackManager.ts b/src/components/playbackManager.ts index 8f405ad3..d3c38381 100644 --- a/src/components/playbackManager.ts +++ b/src/components/playbackManager.ts @@ -99,7 +99,7 @@ export abstract class PlaybackManager { return this.playerManager.getPlayerState() === cast.framework.messages.PlayerState.BUFFERING; } - static async playFromOptions(options: any): Promise { // eslint-disable-line no-explicit-any + static async playFromOptions(options: any): Promise { // eslint-disable-line @typescript-eslint/no-explicit-any const firstItem = options.items[0]; if (options.startPositionTicks || firstItem.MediaType !== 'Video') { @@ -109,7 +109,7 @@ export abstract class PlaybackManager { return this.playFromOptionsInternal(options); } - private static playFromOptionsInternal(options: any): Promise { // eslint-disable-line no-explicit-any + private static playFromOptionsInternal(options: any): Promise { // eslint-disable-line @typescript-eslint/no-explicit-any const stopPlayer = this.activePlaylist && this.activePlaylist.length > 0; @@ -143,7 +143,7 @@ export abstract class PlaybackManager { return this.activePlaylistIndex > 0; } - static playNextItem(options: any = {}, stopPlayer = false): boolean { // eslint-disable-line no-explicit-any + static playNextItem(options: any = {}, stopPlayer = false): boolean { // eslint-disable-line @typescript-eslint/no-explicit-any const nextItemInfo = this.getNextPlaybackItemInfo(); if (nextItemInfo) { @@ -155,7 +155,7 @@ export abstract class PlaybackManager { return false; } - static playPreviousItem(options: any = {}): boolean { // eslint-disable-line no-explicit-any + static playPreviousItem(options: any = {}): boolean { // eslint-disable-line @typescript-eslint/no-explicit-any if (this.activePlaylist && this.activePlaylistIndex > 0) { this.activePlaylistIndex--; this.playItem(options, true); @@ -167,7 +167,7 @@ export abstract class PlaybackManager { // play item from playlist private static async playItem( - options: any, // eslint-disable-line no-explicit-any + options: any, // eslint-disable-line @typescript-eslint/no-explicit-any stopPlayer = false ): Promise { if (stopPlayer) { @@ -182,7 +182,7 @@ export abstract class PlaybackManager { // Would set private, but some refactorings need to happen first. static async playItemInternal( item: BaseItemDto, - options: any // eslint-disable-line no-explicit-any + options: any // eslint-disable-line @typescript-eslint/no-explicit-any ): Promise { this.playbackState.isChangingStream = false; DocumentManager.setAppStatus(AppStatus.Loading); @@ -247,7 +247,7 @@ export abstract class PlaybackManager { playSessionId: string, item: BaseItemDto, mediaSource: MediaSourceInfo, - options: any // eslint-disable-line no-explicit-any + options: any // eslint-disable-line @typescript-eslint/no-explicit-any ): void { DocumentManager.setAppStatus(AppStatus.Loading); From db5eb70b33d3e66aa0737c0d692e4f99bb338461 Mon Sep 17 00:00:00 2001 From: Sky-High Date: Mon, 25 Sep 2023 19:38:27 +0200 Subject: [PATCH 28/29] continued pacification of es-lint --- package-lock.json | 2 +- package.json | 2 +- src/components/fetchhelper.ts | 6 ++-- src/components/jellyfinActions.ts | 9 ++++-- src/components/jellyfinApi.ts | 10 +++++-- src/components/maincontroller.ts | 37 +++++++++++++++--------- src/components/playbackManager.ts | 38 +++++++++++++++++-------- src/helpers.ts | 47 ++++++++++++++++++++----------- src/types/global.d.ts | 5 ++-- 9 files changed, 102 insertions(+), 54 deletions(-) diff --git a/package-lock.json b/package-lock.json index cfe03b6b..91f22a66 100644 --- a/package-lock.json +++ b/package-lock.json @@ -27,7 +27,7 @@ "eslint-config-prettier": "9.0.0", "eslint-import-resolver-typescript": "3.6.1", "eslint-plugin-import": "2.28.1", - "eslint-plugin-jsdoc": "46.8.2", + "eslint-plugin-jsdoc": "^46.8.2", "eslint-plugin-json": "3.1.0", "eslint-plugin-prettier": "5.0.0", "eslint-plugin-promise": "6.1.1", diff --git a/package.json b/package.json index 8ae87334..99e7555f 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,7 @@ "eslint-config-prettier": "9.0.0", "eslint-import-resolver-typescript": "3.6.1", "eslint-plugin-import": "2.28.1", - "eslint-plugin-jsdoc": "46.8.2", + "eslint-plugin-jsdoc": "^46.8.2", "eslint-plugin-json": "3.1.0", "eslint-plugin-prettier": "5.0.0", "eslint-plugin-promise": "6.1.1", diff --git a/src/components/fetchhelper.ts b/src/components/fetchhelper.ts index a8ac3ce4..c9ec5e9b 100644 --- a/src/components/fetchhelper.ts +++ b/src/components/fetchhelper.ts @@ -3,7 +3,8 @@ * @param request - Custom request object, mostly modeled after RequestInit. * @returns response promise */ -function getFetchPromise(request: any): Promise { // eslint-disable-line @typescript-eslint/no-explicit-any +// eslint-disable-next-line @typescript-eslint/no-explicit-any +function getFetchPromise(request: any): Promise { const headers = request.headers || {}; if (request.dataType === 'json') { @@ -102,7 +103,8 @@ function paramsToString(params: Record): string { * @param request - RequestInit-like structure but with url/type/timeout parameters as well * @returns response promise, may be automatically unpacked based on request datatype */ -export async function ajax(request: any): Promise { // eslint-disable-line @typescript-eslint/no-explicit-any +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export async function ajax(request: any): Promise { if (!request) { throw new Error('Request cannot be null'); } diff --git a/src/components/jellyfinActions.ts b/src/components/jellyfinActions.ts index 0a276aae..b5ffcbd9 100644 --- a/src/components/jellyfinActions.ts +++ b/src/components/jellyfinActions.ts @@ -34,7 +34,8 @@ function restartPingInterval(reportingParams: PlaybackProgressInfo): void { stopPingInterval(); if (reportingParams.PlayMethod == 'Transcode') { - pingInterval = setInterval(() => { // eslint-disable-line @typescript-eslint/no-explicit-any + // eslint-disable-next-line @typescript-eslint/no-explicit-any + pingInterval = setInterval(() => { pingTranscoder(reportingParams); }, 1000); } @@ -183,7 +184,8 @@ export function pingTranscoder( * @param customData - data to set on playback state. * @param serverItem - item that is playing */ -export function load(customData: any, serverItem: BaseItemDto): void { // eslint-disable-line @typescript-eslint/no-explicit-any +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export function load(customData: any, serverItem: BaseItemDto): void { PlaybackManager.resetPlaybackScope(); const state = PlaybackManager.playbackState; @@ -255,7 +257,8 @@ export function getPlaybackInfo( audioStreamIndex: number, subtitleStreamIndex: number, liveStreamId: string | null = null -): Promise { // eslint-disable-line @typescript-eslint/no-explicit-any + // eslint-disable-next-line @typescript-eslint/no-explicit-any +): Promise { const postData = { DeviceProfile: deviceProfile }; diff --git a/src/components/jellyfinApi.ts b/src/components/jellyfinApi.ts index 38f4afd3..49c95be8 100644 --- a/src/components/jellyfinApi.ts +++ b/src/components/jellyfinApi.ts @@ -26,7 +26,9 @@ export abstract class JellyfinApi { serverAddress?: string, receiverName = '' ): void { - console.debug(`JellyfinApi.setServerInfo: user:${userId}, token:${accessToken}, server:${serverAddress}, name:${receiverName}`); + console.debug( + `JellyfinApi.setServerInfo: user:${userId}, token:${accessToken}, server:${serverAddress}, name:${receiverName}` + ); this.userId = userId; this.accessToken = accessToken; this.serverAddress = serverAddress; @@ -124,7 +126,8 @@ export abstract class JellyfinApi { } // Authenticated ajax - public static authAjax(path: string, args: any): Promise { // eslint-disable-line @typescript-eslint/no-explicit-any + // eslint-disable-next-line @typescript-eslint/no-explicit-any + public static authAjax(path: string, args: any): Promise { if ( this.userId === undefined || this.accessToken === undefined || @@ -146,7 +149,8 @@ export abstract class JellyfinApi { } // Authenticated ajax - public static authAjaxUser(path: string, args: any): Promise { // eslint-disable-line @typescript-eslint/no-explicit-any + // eslint-disable-next-line @typescript-eslint/no-explicit-any + public static authAjaxUser(path: string, args: any): Promise { if ( this.userId === undefined || this.accessToken === undefined || diff --git a/src/components/maincontroller.ts b/src/components/maincontroller.ts index df187c0a..5aac5e0a 100644 --- a/src/components/maincontroller.ts +++ b/src/components/maincontroller.ts @@ -237,7 +237,7 @@ window.playerManager.addEventListener( reportPlaybackStopped( PlaybackManager.playbackState, getReportingParams(PlaybackManager.playbackState) - ); + ); } ); @@ -284,7 +284,8 @@ export async function reportDeviceCapabilities(): Promise { * processMessage * @param data - data */ -export function processMessage(data: any): void { // eslint-disable-line @typescript-eslint/no-explicit-any +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export function processMessage(data: any): void { if ( !data.command || !data.serverAddress || @@ -385,7 +386,8 @@ export function setSubtitleStreamIndex( // FIXME: Possible index error when MediaStreams is undefined. const currentSubtitleStream = state.mediaSource?.MediaStreams?.filter( - (m: any) => { // eslint-disable-line @typescript-eslint/no-explicit-any + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (m: any) => { return m.Index == state.subtitleStreamIndex && m.Type == 'Subtitle'; } )[0]; @@ -456,6 +458,7 @@ export function setSubtitleStreamIndex( * setAudioStreamIndex * @param state - playback state. * @param index - index + * @returns promise */ export function setAudioStreamIndex( state: PlaybackState, @@ -534,7 +537,8 @@ export async function changeStream( // TODO save namespace somewhere global? window.castReceiverContext.addCustomMessageListener( 'urn:x-cast:com.connectsdk', - (evt: any) => { // eslint-disable-line @typescript-eslint/no-explicit-any + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (evt: any) => { let data: any = evt.data; // eslint-disable-line @typescript-eslint/no-explicit-any // Apparently chromium likes to pass it as json, not as object. @@ -595,8 +599,10 @@ export async function translateItems( * @returns promise */ export async function instantMix( - data: any, // eslint-disable-line @typescript-eslint/no-explicit-any - options: any, // eslint-disable-line @typescript-eslint/no-explicit-any + // eslint-disable-next-line @typescript-eslint/no-explicit-any + data: any, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + options: any, item: BaseItemDto ): Promise { const result = await getInstantMixItems(data.userId, item); @@ -613,8 +619,10 @@ export async function instantMix( * @returns promise */ export async function shuffle( - data: any, // eslint-disable-line @typescript-eslint/no-explicit-any - options: any, // eslint-disable-line @typescript-eslint/no-explicit-any + // eslint-disable-next-line @typescript-eslint/no-explicit-any + data: any, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + options: any, item: BaseItemDto ): Promise { const result = await getShuffleItems(data.userId, item); @@ -633,7 +641,8 @@ export async function shuffle( */ export async function onStopPlayerBeforePlaybackDone( item: BaseItemDto, - options: any // eslint-disable-line @typescript-eslint/no-explicit-any + // eslint-disable-next-line @typescript-eslint/no-explicit-any + options: any ): Promise { const data = await JellyfinApi.authAjaxUser(`Items/${item.Id}`, { dataType: 'json', @@ -697,7 +706,8 @@ export function showPlaybackInfoErrorMessage(error: string): void { * @param versions - versions * @returns stream */ -export function getOptimalMediaSource(versions: Array): any { // eslint-disable-line @typescript-eslint/no-explicit-any +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export function getOptimalMediaSource(versions: Array): any { let optimalVersion = versions.filter((v) => { checkDirectPlay(v); @@ -721,7 +731,7 @@ export function getOptimalMediaSource(versions: Array): any { // eslint-dis // Disable direct play on non-http sources /** * checkDirectPlay - * @param mediaSource + * @param mediaSource - mediaSource */ export function checkDirectPlay(mediaSource: MediaSourceInfo): void { if ( @@ -738,7 +748,7 @@ export function checkDirectPlay(mediaSource: MediaSourceInfo): void { /** * setTextTrack - * @param index + * @param index - index */ export function setTextTrack(index: number | null): void { try { @@ -830,7 +840,8 @@ export function setTextTrack(index: number | null): void { export function createMediaInformation( playSessionId: string, item: BaseItemDto, - streamInfo: any // eslint-disable-line @typescript-eslint/no-explicit-any + // eslint-disable-next-line @typescript-eslint/no-explicit-any + streamInfo: any ): framework.messages.MediaInformation { const mediaInfo = new cast.framework.messages.MediaInformation(); diff --git a/src/components/playbackManager.ts b/src/components/playbackManager.ts index d3c38381..a746d326 100644 --- a/src/components/playbackManager.ts +++ b/src/components/playbackManager.ts @@ -5,19 +5,19 @@ import type { } from '@jellyfin/sdk/lib/generated-client'; import { RepeatMode } from '@jellyfin/sdk/lib/generated-client'; import { AppStatus } from '../types/appStatus'; -import { ItemIndex } from '~/types/global'; import { broadcastConnectionErrorMessage, createStreamInfo, ticksToSeconds } from '../helpers'; +import { DocumentManager } from './documentManager'; +import { getDeviceProfile } from './deviceprofileBuilder'; import { getPlaybackInfo, getLiveStream, load, stopPingInterval } from './jellyfinActions'; -import { getDeviceProfile } from './deviceprofileBuilder'; import { onStopPlayerBeforePlaybackDone, getMaxBitrate, @@ -26,7 +26,7 @@ import { checkDirectPlay, createMediaInformation } from './maincontroller'; -import { DocumentManager } from './documentManager'; +import { ItemIndex } from '~/types/global'; export interface PlaybackState { startPositionTicks: number; @@ -51,7 +51,6 @@ export interface PlaybackState { runtimeTicks: number; } - export abstract class PlaybackManager { private static playerManager: framework.PlayerManager; private static activePlaylist: Array; @@ -90,16 +89,22 @@ export abstract class PlaybackManager { */ static isPlaying(): boolean { return ( - this.playerManager.getPlayerState() === cast.framework.messages.PlayerState.PLAYING || - this.playerManager.getPlayerState() === cast.framework.messages.PlayerState.PAUSED + this.playerManager.getPlayerState() === + cast.framework.messages.PlayerState.PLAYING || + this.playerManager.getPlayerState() === + cast.framework.messages.PlayerState.PAUSED ); } static isBuffering(): boolean { - return this.playerManager.getPlayerState() === cast.framework.messages.PlayerState.BUFFERING; + return ( + this.playerManager.getPlayerState() === + cast.framework.messages.PlayerState.BUFFERING + ); } - static async playFromOptions(options: any): Promise { // eslint-disable-line @typescript-eslint/no-explicit-any + // eslint-disable-next-line @typescript-eslint/no-explicit-any + static async playFromOptions(options: any): Promise { const firstItem = options.items[0]; if (options.startPositionTicks || firstItem.MediaType !== 'Video') { @@ -109,7 +114,8 @@ export abstract class PlaybackManager { return this.playFromOptionsInternal(options); } - private static playFromOptionsInternal(options: any): Promise { // eslint-disable-line @typescript-eslint/no-explicit-any + // eslint-disable-next-line @typescript-eslint/no-explicit-any + private static playFromOptionsInternal(options: any): Promise { const stopPlayer = this.activePlaylist && this.activePlaylist.length > 0; @@ -143,22 +149,26 @@ export abstract class PlaybackManager { return this.activePlaylistIndex > 0; } - static playNextItem(options: any = {}, stopPlayer = false): boolean { // eslint-disable-line @typescript-eslint/no-explicit-any + // eslint-disable-next-line @typescript-eslint/no-explicit-any + static playNextItem(options: any = {}, stopPlayer = false): boolean { const nextItemInfo = this.getNextPlaybackItemInfo(); if (nextItemInfo) { this.activePlaylistIndex = nextItemInfo.index; this.playItem(options, stopPlayer); + return true; } return false; } - static playPreviousItem(options: any = {}): boolean { // eslint-disable-line @typescript-eslint/no-explicit-any + // eslint-disable-next-line @typescript-eslint/no-explicit-any + static playPreviousItem(options: any = {}): boolean { if (this.activePlaylist && this.activePlaylistIndex > 0) { this.activePlaylistIndex--; this.playItem(options, true); + return true; } @@ -175,7 +185,9 @@ export abstract class PlaybackManager { } const item = this.activePlaylist[this.activePlaylistIndex]; + console.log(`Playing index ${this.activePlaylistIndex}`, item); + return await onStopPlayerBeforePlaybackDone(item, options); } @@ -270,7 +282,9 @@ export abstract class PlaybackManager { // If we should seek at the start, translate it // to seconds and give it to loadRequestData :) if (mediaInfo.customData.startPositionTicks > 0) { - loadRequestData.currentTime = ticksToSeconds(mediaInfo.customData.startPositionTicks); + loadRequestData.currentTime = ticksToSeconds( + mediaInfo.customData.startPositionTicks + ); } load(mediaInfo.customData, item); diff --git a/src/helpers.ts b/src/helpers.ts index 1ce9266a..18fd2ff1 100644 --- a/src/helpers.ts +++ b/src/helpers.ts @@ -7,18 +7,19 @@ import type { UserDto } from '@jellyfin/sdk/lib/generated-client'; import { JellyfinApi } from './components/jellyfinApi'; -import { DocumentManager } from './components/documentManager'; import { PlaybackManager, PlaybackState } from './components/playbackManager'; import { BusMessage, ItemQuery } from './types/global'; -export const TicksPerSecond = 10000000 +export const TicksPerSecond = 10000000; + /** * Get current playback position in ticks, adjusted for server seeking * @param state - playback state. * @returns position in ticks */ export function getCurrentPositionTicks(state: PlaybackState): number { - let positionTicks = window.playerManager.getCurrentTimeSec() * TicksPerSecond; + let positionTicks = + window.playerManager.getCurrentTimeSec() * TicksPerSecond; const mediaInformation = window.playerManager.getMediaInformation(); if (mediaInformation && !mediaInformation.customData.canClientSeek) { @@ -70,8 +71,10 @@ export function getReportingParams(state: PlaybackState): PlaybackProgressInfo { export function getSenderReportingData( playbackState: PlaybackState, reportingData: PlaybackProgressInfo -): any { // eslint-disable-line no-explicit-any - const state: any = { // eslint-disable-line no-explicit-any + // eslint-disable-next-line @typescript-eslint/no-explicit-any +): any { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const state: any = { ItemId: reportingData.ItemId, PlayState: reportingData, QueueableMediaTypes: ['Audio', 'Video'] @@ -91,6 +94,7 @@ export function getSenderReportingData( nowPlayingItem.Chapters = item.Chapters || []; // TODO: Fill these + // eslint-disable-next-line @typescript-eslint/no-explicit-any const mediaSource = item.MediaSources?.filter((m: any) => { return m.Id == reportingData.MediaSourceId; })[0]; @@ -166,8 +170,10 @@ export function getSenderReportingData( * @param item - item to look up * @returns one of the metadata classes in cast.framework.messages.*Metadata */ -export function getMetadata(item: BaseItemDto): any { // eslint-disable-line no-explicit-any - let metadata: any; // eslint-disable-line no-explicit-any +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export function getMetadata(item: BaseItemDto): any { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + let metadata: any; let posterUrl = ''; if (item.SeriesPrimaryImageTag) { @@ -270,7 +276,7 @@ export function getMetadata(item: BaseItemDto): any { // eslint-disable-line no /** * Check if a media source is an HLS stream - * @param mediaSource + * @param mediaSource - mediaSource * @returns boolean */ export function isHlsStream(mediaSource: MediaSourceInfo): boolean { @@ -289,7 +295,8 @@ export function createStreamInfo( item: BaseItemDto, mediaSource: MediaSourceInfo, startPosition: number | null -): any { // eslint-disable-line no-explicit-any + // eslint-disable-next-line @typescript-eslint/no-explicit-any +): any { let mediaUrl; let contentType; @@ -377,7 +384,8 @@ export function createStreamInfo( // It is a pain and will require unbinding all event handlers during the operation const canSeek = (mediaSource.RunTimeTicks || 0) > 0; - const info: any = { // eslint-disable-line no-explicit-any + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const info: any = { audioStreamIndex: mediaSource.DefaultAudioStreamIndex, canClientSeek: isStatic || (canSeek && streamContainer == 'm3u8'), canSeek: canSeek, @@ -392,12 +400,14 @@ export function createStreamInfo( }; const subtitleStreams = - mediaSource.MediaStreams?.filter((stream: any) => { // eslint-disable-line no-explicit-any - return stream.Type === 'Subtitle'; - }) ?? []; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + mediaSource.MediaStreams?.filter((stream: any) => { + return stream.Type === 'Subtitle'; + }) ?? []; const subtitleTracks: Array = []; - subtitleStreams.forEach((subtitleStream: any) => { // eslint-disable-line no-explicit-any + // eslint-disable-next-line @typescript-eslint/no-explicit-any + subtitleStreams.forEach((subtitleStream: any) => { if (subtitleStream.DeliveryUrl === undefined) { /* The CAF v3 player only supports vtt currently, * SRT subs can be "transcoded" to vtt by jellyfin. @@ -441,10 +451,12 @@ export function createStreamInfo( * @returns first first matching stream */ export function getStreamByIndex( - streams: Array, // eslint-disable-line no-explicit-any + // eslint-disable-next-line @typescript-eslint/no-explicit-any + streams: Array, type: string, index: number -): any { // eslint-disable-line no-explicit-any + // eslint-disable-next-line @typescript-eslint/no-explicit-any +): any { return streams.filter((s) => { return s.Type == type && s.Index == index; })[0]; @@ -505,7 +517,8 @@ export async function getInstantMixItems( userId: string, item: BaseItemDto ): Promise { - const query: any = { // eslint-disable-line no-explicit-any + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const query: any = { Fields: requiredItemFields, Limit: 50, UserId: userId diff --git a/src/types/global.d.ts b/src/types/global.d.ts index ddeed9c3..893fb7be 100644 --- a/src/types/global.d.ts +++ b/src/types/global.d.ts @@ -81,7 +81,7 @@ export interface DataMessage { | SetIndexRequest | SetRepeatModeRequest | SeekRequest; - command: string; + command: string; } interface SupportedCommands { @@ -98,7 +98,8 @@ declare global { castReceiverContext: CastReceiverContext; repeatMode: RepeatMode; reportEventType: 'repeatmodechange'; - subtitleAppearance: any; // eslint-disable-line no-explicit-any + // eslint-disable-next-line @typescript-eslint/no-explicit-any + subtitleAppearance: any; MaxBitrate: number | undefined; senderId: string | undefined; volume: SystemVolumeData; From 6d3980a44a14389e4c3758dab96df5d0348a5deb Mon Sep 17 00:00:00 2001 From: Sky-High Date: Tue, 26 Sep 2023 22:39:45 +0200 Subject: [PATCH 29/29] revert unintended changes to package.json --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 91f22a66..cfe03b6b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -27,7 +27,7 @@ "eslint-config-prettier": "9.0.0", "eslint-import-resolver-typescript": "3.6.1", "eslint-plugin-import": "2.28.1", - "eslint-plugin-jsdoc": "^46.8.2", + "eslint-plugin-jsdoc": "46.8.2", "eslint-plugin-json": "3.1.0", "eslint-plugin-prettier": "5.0.0", "eslint-plugin-promise": "6.1.1", diff --git a/package.json b/package.json index 99e7555f..8ae87334 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,7 @@ "eslint-config-prettier": "9.0.0", "eslint-import-resolver-typescript": "3.6.1", "eslint-plugin-import": "2.28.1", - "eslint-plugin-jsdoc": "^46.8.2", + "eslint-plugin-jsdoc": "46.8.2", "eslint-plugin-json": "3.1.0", "eslint-plugin-prettier": "5.0.0", "eslint-plugin-promise": "6.1.1",