diff --git a/frontend/src/components/Playback/PlayerElement.vue b/frontend/src/components/Playback/PlayerElement.vue index c9aa9f6f681..d6613205134 100644 --- a/frontend/src/components/Playback/PlayerElement.vue +++ b/frontend/src/components/Playback/PlayerElement.vue @@ -203,8 +203,7 @@ watch(mediaElementRef, async () => { } }); -watch( - () => playbackManager.currentSourceUrl, +watch(playbackManager.currentSourceUrl, (newUrl) => { if (hls) { hls.stopLoad(); diff --git a/frontend/src/store/playback-manager.ts b/frontend/src/store/playback-manager.ts index bc4ef30d8c4..e7aa7d65f55 100644 --- a/frontend/src/store/playback-manager.ts +++ b/frontend/src/store/playback-manager.ts @@ -20,9 +20,9 @@ import { getItemsApi } from '@jellyfin/sdk/lib/utils/api/items-api'; import { getMediaInfoApi } from '@jellyfin/sdk/lib/utils/api/media-info-api'; import { getPlaystateApi } from '@jellyfin/sdk/lib/utils/api/playstate-api'; import { getTvShowsApi } from '@jellyfin/sdk/lib/utils/api/tv-shows-api'; -import { useEventListener, watchThrottled } from '@vueuse/core'; +import { computedAsync, useEventListener, watchThrottled } from '@vueuse/core'; import { v4 } from 'uuid'; -import { watch, watchEffect } from 'vue'; +import { computed, watch, watchEffect } from 'vue'; import { isNil, sealed } from '@/utils/validation'; import { useBaseItem } from '@/composables/apis'; import { useSnackbar } from '@/composables/use-snackbar'; @@ -78,7 +78,6 @@ export interface PlaybackExternalTrack extends PlaybackTrack { interface PlaybackManagerState { status: PlaybackStatus; - currentSourceUrl: string | undefined; currentItemIndex: number | undefined; currentMediaSource: MediaSourceInfo | undefined; currentMediaSourceIndex: number | undefined; @@ -112,7 +111,6 @@ class PlaybackManagerStore extends CommonStore { * Amount of time to wait between playback reports */ private readonly _progressReportInterval = 3500; - private _mediaSourceRequestId: string | undefined = undefined; /** * == GETTERS AND SETTERS == */ @@ -183,9 +181,29 @@ class PlaybackManagerStore extends CommonStore { return this.queue[this._state.currentItemIndex ?? 0]; } - public get currentSourceUrl(): string | undefined { - return this._state.currentSourceUrl; - } + private readonly _currentPlaybackInfo = computedAsync(async ( + ) => { + if (this.currentItem?.Id && this.currentItem.MediaSources) { + return ( + await remote.sdk.newUserApi(getMediaInfoApi).getPostedPlaybackInfo({ + itemId: this.currentItem.Id, + userId: remote.auth.currentUserId, + autoOpenLiveStream: true, + playbackInfoDto: { DeviceProfile: playbackProfile }, + mediaSourceId: + this.currentItem.MediaSources[this._state.currentMediaSourceIndex ?? 0]?.Id ?? this.currentItem.Id, + audioStreamIndex: this.currentAudioStreamIndex, + subtitleStreamIndex: this.currentSubtitleStreamIndex + }) + ).data; + } + }); + + public readonly currentSourceUrl = computed(() => + this._currentPlaybackInfo.value?.MediaSources?.[0] + ? this.getItemPlaybackUrl(this._currentPlaybackInfo.value.MediaSources[0]) + : undefined + ); private get _previousItemIndex(): number | undefined { if (this._state.repeatMode === RepeatMode.RepeatAll && this._state.currentItemIndex === 0) { @@ -270,8 +288,9 @@ class PlaybackManagerStore extends CommonStore { public get currentItemParsedSubtitleTracks(): PlaybackTrack[] | undefined { if (!isNil(this._state.currentMediaSource)) { - /* - * TODO: There is currently a bug in Jellyfin server when adding external subtitles may play the incorrect subtitle + /** + * TODO: There is currently a bug in Jellyfin server when adding external subtitles + * may play the incorrect subtitle * https://github.com/jellyfin/jellyfin/issues/13198 */ return this._state.currentMediaSource.MediaStreams?.filter( @@ -783,7 +802,7 @@ class PlaybackManagerStore extends CommonStore { this._state.originalQueue = []; this._state.isShuffling = false; } else { - const queue = await runGenericWorkerFunc('shuffle')(this._state.queue) ?? this._state.originalQueue; + const queue = await runGenericWorkerFunc('shuffle')(this._state.queue) as string[]; this._state.originalQueue = this._state.queue; @@ -846,28 +865,6 @@ class PlaybackManagerStore extends CommonStore { } }; - public readonly getItemPlaybackInfo = async ( - item = this.currentItem, - mediaSourceIndex = this._state.currentMediaSourceIndex ?? 0, - audioStreamIndex = this.currentAudioStreamIndex, - subtitleStreamIndex = this.currentSubtitleStreamIndex - ): Promise => { - if (item?.Id && item.MediaSources) { - return ( - await remote.sdk.newUserApi(getMediaInfoApi).getPostedPlaybackInfo({ - itemId: item.Id, - userId: remote.auth.currentUserId, - autoOpenLiveStream: true, - playbackInfoDto: { DeviceProfile: playbackProfile }, - mediaSourceId: - item.MediaSources[mediaSourceIndex]?.Id ?? item.Id, - audioStreamIndex, - subtitleStreamIndex - }) - ).data; - } - }; - /** * Builds an array of item ids based on a collection item (i.e album, tv show, etc...) * @@ -969,52 +966,9 @@ class PlaybackManagerStore extends CommonStore { } }; - private readonly _setCurrentMediaSource = async (): Promise => { - /** - * Generate an identifier that can be compared with the class' one. - * If they don't match, we assume the playing item has been changed while this function - * has been running. Hence, it's results are stale and another run will take effect instead. - */ - const requestId = v4(); - - /** - * Saves the current playback time because it gets reset when changing audio source / burnt in subs. - */ - const currentTime = this.currentTime; - - this._mediaSourceRequestId = requestId; - this._state.status = PlaybackStatus.Buffering; - /** - * Set values to undefined so the next item doesn't play the previous one while the requests are in progress - */ - this._state.playSessionId = undefined; - this._state.currentMediaSource = undefined; - this._state.currentSourceUrl = undefined; - - const playbackInfo = await this.getItemPlaybackInfo(); - - if (playbackInfo && requestId === this._mediaSourceRequestId) { - const mediaSource = playbackInfo.MediaSources?.[0]; - const playbackUrl = this.getItemPlaybackUrl(mediaSource); - - if (mediaSource && playbackInfo.PlaySessionId && playbackUrl) { - this._state.playSessionId = playbackInfo.PlaySessionId; - this._state.currentMediaSource = mediaSource; - this._state.currentSourceUrl = playbackUrl; - this.currentTime = currentTime; - } else { - this._state.status = PlaybackStatus.Error; - useSnackbar(i18n.t('cantPlayItem'), 'error'); - } - - this._mediaSourceRequestId = undefined; - } - }; - public constructor() { super('playbackManager', () => ({ status: PlaybackStatus.Stopped, - currentSourceUrl: undefined, currentItemIndex: undefined, currentMediaSource: undefined, currentMediaSourceIndex: undefined, @@ -1174,21 +1128,6 @@ class PlaybackManagerStore extends CommonStore { /** * == Server interaction == */ - /** - * Update media source, taking into account that currentItemIndex updates - * that occur when shuffling must be skipped - */ - watch( - [ - (): typeof this.currentItemIndex => this.currentItemIndex, - (): typeof this.isShuffling => this.isShuffling - ], - async (newValue, oldValue) => { - if (newValue[1] === oldValue[1] || isNil(this.currentMediaSource)) { - await this._setCurrentMediaSource(); - } - } - ); /** * Report stop for the old item and start for the new one */ @@ -1208,40 +1147,6 @@ class PlaybackManagerStore extends CommonStore { } ); - watch( - () => ({ - currentSubtitleStreamIndex: this.currentSubtitleStreamIndex, - currentSubtitleTrack: this.currentSubtitleTrack - }), - async (oldVal, newVal) => { - if ( - oldVal.currentSubtitleStreamIndex - !== newVal.currentSubtitleStreamIndex - && (oldVal.currentSubtitleTrack?.DeliveryMethod - === SubtitleDeliveryMethod.Encode - || newVal.currentSubtitleTrack?.DeliveryMethod - === SubtitleDeliveryMethod.Encode) - ) { - /** - * We need to set a new media source when: - * - Subs change and - * - Going from or to a situation where subs are burnt in. - */ - await this._setCurrentMediaSource(); - } - } - ); - - watch(() => this.currentAudioStreamIndex, async (oldVal, newVal) => { - if (!isNil(newVal) && oldVal !== newVal) { - /** - * We need to set a new media source when: - * - The audio stream index changes - */ - await this._setCurrentMediaSource(); - } - }); - watchThrottled( () => this.currentTime, this._reportPlaybackProgress,