From 1a59aa2bb3768daebde327170a1850b610b7c1ee Mon Sep 17 00:00:00 2001 From: Matthew Haughton <3flex@users.noreply.github.com> Date: Mon, 4 Nov 2024 11:00:44 +1100 Subject: [PATCH 1/2] Report playback stopped when media finishes This consolidates the logic for reporting stopped playback to the server. The MEDIA_FINISHED event is fired when: * End of stream is reached * Error encountered * Playback has been stopped * Playback was interrupted because a new item was loaded https://developers.google.com/cast/docs/reference/web_receiver/cast.framework.events#.EndedReason The current position reported to the server will be the current time reported by the Cast player which will be the most accurate representation. If this is not available, it will fall back to the existing logic of checking the current playback position from the receiver's playback state. --- src/components/maincontroller.ts | 32 +++++++++++++++----------------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/src/components/maincontroller.ts b/src/components/maincontroller.ts index 685df040..5c070d4e 100644 --- a/src/components/maincontroller.ts +++ b/src/components/maincontroller.ts @@ -12,7 +12,8 @@ import { getInstantMixItems, translateRequestedItems, broadcastToMessageBus, - ticksToSeconds + ticksToSeconds, + TicksPerSecond } from '../helpers'; import { reportPlaybackStart, @@ -153,11 +154,7 @@ export function disableTimeUpdateListener(): void { enableTimeUpdateListener(); window.addEventListener('beforeunload', () => { - // Try to cleanup after ourselves before the page closes - const playbackState = PlaybackManager.playbackState; - disableTimeUpdateListener(); - reportPlaybackStopped(playbackState, getReportingParams(playbackState)); }); window.playerManager.addEventListener( @@ -194,8 +191,20 @@ function defaultOnStop(): void { window.playerManager.addEventListener( cast.framework.events.EventType.MEDIA_FINISHED, - defaultOnStop + (mediaFinishedEvent): void => { + const playbackState = PlaybackManager.playbackState; + + reportPlaybackStopped(playbackState, { + ...getReportingParams(playbackState), + PositionTicks: + (mediaFinishedEvent.currentMediaTime ?? + getCurrentPositionTicks(playbackState)) * TicksPerSecond + }); + + defaultOnStop(); + } ); + window.playerManager.addEventListener( cast.framework.events.EventType.ABORT, defaultOnStop @@ -211,7 +220,6 @@ window.playerManager.addEventListener( return; } - reportPlaybackStopped(playbackState, getReportingParams(playbackState)); PlaybackManager.resetPlaybackScope(); if (!PlaybackManager.playNextItem()) { @@ -231,16 +239,6 @@ window.playerManager.addEventListener( ); } ); -// 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( - PlaybackManager.playbackState, - getReportingParams(PlaybackManager.playbackState) - ); - } -); // Set the active subtitle track once the player has loaded window.playerManager.addEventListener( From 3cd9151ce80dde7dba42765f12acb6d7d460ffd0 Mon Sep 17 00:00:00 2001 From: Matthew Haughton <3flex@users.noreply.github.com> Date: Mon, 4 Nov 2024 13:24:02 +1100 Subject: [PATCH 2/2] Don't report stopped playback if changing stream --- src/components/maincontroller.ts | 21 ++++++++++++++------- src/components/playbackManager.ts | 4 +++- 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/src/components/maincontroller.ts b/src/components/maincontroller.ts index 5c070d4e..546922da 100644 --- a/src/components/maincontroller.ts +++ b/src/components/maincontroller.ts @@ -194,14 +194,19 @@ window.playerManager.addEventListener( (mediaFinishedEvent): void => { const playbackState = PlaybackManager.playbackState; - reportPlaybackStopped(playbackState, { - ...getReportingParams(playbackState), - PositionTicks: - (mediaFinishedEvent.currentMediaTime ?? - getCurrentPositionTicks(playbackState)) * TicksPerSecond - }); + // Don't notify server or client if changing streams, but notify next time. + if (!playbackState.isChangingStream) { + reportPlaybackStopped(playbackState, { + ...getReportingParams(playbackState), + PositionTicks: + (mediaFinishedEvent.currentMediaTime ?? + getCurrentPositionTicks(playbackState)) * TicksPerSecond + }); - defaultOnStop(); + defaultOnStop(); + } else { + playbackState.isChangingStream = false; + } } ); @@ -511,6 +516,8 @@ export async function changeStream( // await stopActiveEncodings($scope.playSessionId); //} + state.isChangingStream = true; + // @ts-expect-error is possible here return await PlaybackManager.playItemInternal(state.item, { audioStreamIndex: params.AudioStreamIndex ?? state.audioStreamIndex, diff --git a/src/components/playbackManager.ts b/src/components/playbackManager.ts index b0406c9d..51b5ead1 100644 --- a/src/components/playbackManager.ts +++ b/src/components/playbackManager.ts @@ -194,7 +194,6 @@ export abstract class PlaybackManager { item: BaseItemDto, options: any // eslint-disable-line @typescript-eslint/no-explicit-any ): Promise { - this.playbackState.isChangingStream = false; DocumentManager.setAppStatus(AppStatus.Loading); const maxBitrate = await getMaxBitrate(); @@ -286,7 +285,10 @@ export abstract class PlaybackManager { loadRequestData.currentTime = ticksToSeconds(startPositionTicks); } + const isChangingStream = this.playbackState.isChangingStream; + load(mediaInfo.customData, item); + this.playbackState.isChangingStream = isChangingStream; this.playerManager.load(loadRequestData); this.playbackState.PlaybackMediaSource = mediaSource;