From aaf840d3580ff7a91ea0f6a0aa0f7483db91854d Mon Sep 17 00:00:00 2001 From: jeffvli Date: Thu, 1 Jan 2026 20:17:49 -0800 Subject: [PATCH] resend mediasession on player repeat (#1472) --- src/renderer/events/events.ts | 5 ++ .../audio-player/hooks/use-player-events.ts | 8 +++ .../player/hooks/use-media-session.ts | 55 +++++++++++++------ src/renderer/store/player.store.ts | 6 ++ 4 files changed, 58 insertions(+), 16 deletions(-) diff --git a/src/renderer/events/events.ts b/src/renderer/events/events.ts index 29d745b68..45b7f9a58 100644 --- a/src/renderer/events/events.ts +++ b/src/renderer/events/events.ts @@ -11,6 +11,7 @@ export type EventMap = { MEDIA_NEXT: MediaNextEventPayload; MEDIA_PREV: MediaPrevEventPayload; PLAYER_PLAY: PlayerPlayEventPayload; + PLAYER_REPEATED: PlayerRepeatedEventPayload; PLAYLIST_MOVE_DOWN: PlaylistMoveEventPayload; PLAYLIST_MOVE_TO_BOTTOM: PlaylistMoveEventPayload; PLAYLIST_MOVE_TO_TOP: PlaylistMoveEventPayload; @@ -46,6 +47,10 @@ export type PlayerPlayEventPayload = { index: number; }; +export type PlayerRepeatedEventPayload = { + index: number; +}; + export type PlaylistMoveEventPayload = { playlistId: string; sourceIds: string[]; diff --git a/src/renderer/features/player/audio-player/hooks/use-player-events.ts b/src/renderer/features/player/audio-player/hooks/use-player-events.ts index 66d668623..15fda73bf 100644 --- a/src/renderer/features/player/audio-player/hooks/use-player-events.ts +++ b/src/renderer/features/player/audio-player/hooks/use-player-events.ts @@ -35,6 +35,7 @@ interface PlayerEventsCallbacks { onPlayerProgress?: (properties: { timestamp: number }, prev: { timestamp: number }) => void; onPlayerQueueChange?: (queue: QueueData, prev: QueueData) => void; onPlayerRepeat?: (properties: { repeat: PlayerRepeat }, prev: { repeat: PlayerRepeat }) => void; + onPlayerRepeated?: (properties: { index: number }) => void; onPlayerSeek?: (properties: { seconds: number }, prev: { seconds: number }) => void; onPlayerSeekToTimestamp?: ( properties: { timestamp: number }, @@ -161,6 +162,10 @@ function createPlayerEvents(callbacks: PlayerEventsCallbacks): PlayerEvents { eventEmitter.on('PLAYER_PLAY', callbacks.onPlayerPlay); } + if (callbacks.onPlayerRepeated) { + eventEmitter.on('PLAYER_REPEATED', callbacks.onPlayerRepeated); + } + if (callbacks.onQueueRestored) { eventEmitter.on('QUEUE_RESTORED', callbacks.onQueueRestored); } @@ -185,6 +190,9 @@ function createPlayerEvents(callbacks: PlayerEventsCallbacks): PlayerEvents { if (callbacks.onPlayerPlay) { eventEmitter.off('PLAYER_PLAY', callbacks.onPlayerPlay); } + if (callbacks.onPlayerRepeated) { + eventEmitter.off('PLAYER_REPEATED', callbacks.onPlayerRepeated); + } if (callbacks.onQueueRestored) { eventEmitter.off('QUEUE_RESTORED', callbacks.onQueueRestored); } diff --git a/src/renderer/features/player/hooks/use-media-session.ts b/src/renderer/features/player/hooks/use-media-session.ts index 592b26d89..fbc6e5151 100644 --- a/src/renderer/features/player/hooks/use-media-session.ts +++ b/src/renderer/features/player/hooks/use-media-session.ts @@ -1,11 +1,16 @@ import isElectron from 'is-electron'; -import { useEffect, useMemo } from 'react'; +import { useCallback, useEffect, useMemo } from 'react'; import { getItemImageUrl } from '/@/renderer/components/item-image/item-image'; import { usePlayerEvents } from '/@/renderer/features/player/audio-player/hooks/use-player-events'; import { usePlayer } from '/@/renderer/features/player/context/player-context'; -import { usePlaybackSettings, useSettingsStore, useTimestampStoreBase } from '/@/renderer/store'; -import { LibraryItem } from '/@/shared/types/domain-types'; +import { + usePlaybackSettings, + usePlayerStore, + useSettingsStore, + useTimestampStoreBase, +} from '/@/renderer/store'; +import { LibraryItem, QueueSong } from '/@/shared/types/domain-types'; import { PlayerStatus, PlayerType } from '/@/shared/types/types'; const mediaSession = navigator.mediaSession; @@ -85,6 +90,29 @@ export const useMediaSession = () => { }; }, [player, skip?.skipBackwardSeconds, skip?.skipForwardSeconds, isMediaSessionEnabled]); + const updateMediaSessionMetadata = useCallback( + (song: QueueSong | undefined) => { + if (!isMediaSessionEnabled || !song) { + return; + } + + const imageUrl = getItemImageUrl({ + id: song?.imageId || undefined, + imageUrl: song?.imageUrl, + itemType: LibraryItem.SONG, + type: 'itemCard', + }); + + mediaSession.metadata = new MediaMetadata({ + album: song?.album ?? '', + artist: song?.artistName ?? '', + artwork: imageUrl ? [{ src: imageUrl, type: 'image/png' }] : [], + title: song?.name ?? '', + }); + }, + [isMediaSessionEnabled], + ); + usePlayerEvents( { onCurrentSongChange: (properties) => { @@ -92,20 +120,15 @@ export const useMediaSession = () => { return; } - const song = properties.song; - const imageUrl = getItemImageUrl({ - id: song?.imageId || undefined, - imageUrl: song?.imageUrl, - itemType: LibraryItem.SONG, - type: 'itemCard', - }); + updateMediaSessionMetadata(properties.song); + }, + onPlayerRepeated: () => { + if (!isMediaSessionEnabled) { + return; + } - mediaSession.metadata = new MediaMetadata({ - album: song?.album ?? '', - artist: song?.artistName ?? '', - artwork: imageUrl ? [{ src: imageUrl, type: 'image/png' }] : [], - title: song?.name ?? '', - }); + const currentSong = usePlayerStore.getState().getCurrentSong(); + updateMediaSessionMetadata(currentSong); }, onPlayerStatus: (properties) => { if (!isMediaSessionEnabled) { diff --git a/src/renderer/store/player.store.ts b/src/renderer/store/player.store.ts index 74ecc41f1..e2f7fdff5 100644 --- a/src/renderer/store/player.store.ts +++ b/src/renderer/store/player.store.ts @@ -1367,6 +1367,12 @@ export const usePlayerStoreBase = createWithEqualityFn()( state.player.status = newStatus; }); + if (repeat === PlayerRepeat.ONE && nextIndex === currentIndex) { + eventEmitter.emit('PLAYER_REPEATED', { + index: nextIndex, + }); + } + const nextSong = calculateNextSong(nextIndex, queue.items, repeat); return {