handle radio metadata for mpris / mediasession (#1586)

This commit is contained in:
jeffvli
2026-01-22 01:30:45 -08:00
parent a5541745c3
commit 81af324260
2 changed files with 181 additions and 10 deletions
@@ -4,6 +4,10 @@ import React, { 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 {
useIsRadioActive,
useRadioPlayer,
} from '/@/renderer/features/radio/hooks/use-radio-player';
import {
usePlaybackSettings,
usePlaybackType,
@@ -22,6 +26,8 @@ export const useMediaSession = () => {
const player = usePlayer();
const skip = useSkipButtons();
const playbackType = useSettingsStore((state) => state.playback.type);
const isRadioActive = useIsRadioActive();
const { isPlaying: isRadioPlaying, metadata: radioMetadata, stationName } = useRadioPlayer();
const isMediaSessionEnabled = useMemo(() => {
// Always enable media session on web
@@ -38,6 +44,10 @@ export const useMediaSession = () => {
}
mediaSession.setActionHandler('nexttrack', () => {
if (isRadioActive && isRadioPlaying) {
return;
}
player.mediaNext();
});
@@ -50,10 +60,18 @@ export const useMediaSession = () => {
});
mediaSession.setActionHandler('previoustrack', () => {
if (isRadioActive && isRadioPlaying) {
return;
}
player.mediaPrevious();
});
mediaSession.setActionHandler('seekto', (e) => {
if (isRadioActive && isRadioPlaying) {
return;
}
if (e.seekTime) {
player.mediaSeekToTimestamp(e.seekTime);
} else if (e.seekOffset) {
@@ -67,6 +85,10 @@ export const useMediaSession = () => {
});
mediaSession.setActionHandler('seekbackward', (e) => {
if (isRadioActive && isRadioPlaying) {
return;
}
const currentTimestamp = useTimestampStoreBase.getState().timestamp;
player.mediaSeekToTimestamp(
currentTimestamp - (e.seekOffset || skip?.skipBackwardSeconds || 5),
@@ -74,6 +96,10 @@ export const useMediaSession = () => {
});
mediaSession.setActionHandler('seekforward', (e) => {
if (isRadioActive && isRadioPlaying) {
return;
}
const currentTimestamp = useTimestampStoreBase.getState().timestamp;
player.mediaSeekToTimestamp(
currentTimestamp + (e.seekOffset || skip?.skipForwardSeconds || 5),
@@ -90,11 +116,37 @@ export const useMediaSession = () => {
mediaSession.setActionHandler('seekbackward', null);
mediaSession.setActionHandler('seekforward', null);
};
}, [player, skip?.skipBackwardSeconds, skip?.skipForwardSeconds, isMediaSessionEnabled]);
}, [
player,
skip?.skipBackwardSeconds,
skip?.skipForwardSeconds,
isMediaSessionEnabled,
isRadioActive,
isRadioPlaying,
]);
const updateMediaSessionMetadata = useCallback(
(song: QueueSong | undefined) => {
if (!isMediaSessionEnabled || !song) {
if (!isMediaSessionEnabled) {
return;
}
// Handle radio metadata when radio is active and playing
if (isRadioActive && isRadioPlaying) {
const title = radioMetadata?.title || stationName || 'Radio';
const artist = radioMetadata?.artist || stationName || '';
mediaSession.metadata = new MediaMetadata({
album: stationName || '',
artist: artist,
artwork: [],
title: title,
});
return;
}
// Handle regular song metadata
if (!song) {
return;
}
@@ -112,9 +164,27 @@ export const useMediaSession = () => {
title: song?.name ?? '',
});
},
[isMediaSessionEnabled],
[isMediaSessionEnabled, isRadioActive, isRadioPlaying, radioMetadata, stationName],
);
// Update metadata when radio metadata changes
useEffect(() => {
if (!isMediaSessionEnabled) {
return;
}
if (isRadioActive && isRadioPlaying) {
updateMediaSessionMetadata(undefined);
}
}, [
isMediaSessionEnabled,
isRadioActive,
isRadioPlaying,
radioMetadata,
stationName,
updateMediaSessionMetadata,
]);
usePlayerEvents(
{
onCurrentSongChange: (properties) => {
@@ -122,6 +192,10 @@ export const useMediaSession = () => {
return;
}
if (isRadioActive && isRadioPlaying) {
return;
}
updateMediaSessionMetadata(properties.song);
},
onPlayerRepeated: () => {
@@ -129,6 +203,10 @@ export const useMediaSession = () => {
return;
}
if (isRadioActive && isRadioPlaying) {
return;
}
const currentSong = usePlayerStore.getState().getCurrentSong();
updateMediaSessionMetadata(currentSong);
},
@@ -141,7 +219,7 @@ export const useMediaSession = () => {
mediaSession.playbackState = status === PlayerStatus.PLAYING ? 'playing' : 'paused';
},
},
[isMediaSessionEnabled, mediaSession],
[isMediaSessionEnabled, isRadioActive, isRadioPlaying, updateMediaSessionMetadata],
);
};
@@ -1,11 +1,15 @@
import isElectron from 'is-electron';
import React, { useEffect } from 'react';
import React, { useEffect, useMemo } from 'react';
import { useItemImageUrl } from '/@/renderer/components/item-image/item-image';
import { usePlayerEvents } from '/@/renderer/features/player/audio-player/hooks/use-player-events';
import {
useIsRadioActive,
useRadioPlayer,
} from '/@/renderer/features/radio/hooks/use-radio-player';
import { usePlayerSong, usePlayerStore } from '/@/renderer/store';
import { LibraryItem } from '/@/shared/types/domain-types';
import { PlayerShuffle } from '/@/shared/types/types';
import { LibraryItem, QueueSong } from '/@/shared/types/domain-types';
import { PlayerShuffle, ServerType } from '/@/shared/types/types';
const ipc = isElectron() ? window.api.ipc : null;
const utils = isElectron() ? window.api.utils : null;
@@ -14,6 +18,8 @@ const mpris = isElectron() && utils?.isLinux() ? window.api.mpris : null;
export const useMPRIS = () => {
const player = usePlayerStore();
const currentSong = usePlayerSong();
const isRadioActive = useIsRadioActive();
const { isPlaying: isRadioPlaying, metadata: radioMetadata, stationName } = useRadioPlayer();
const imageUrl = useItemImageUrl({
id: currentSong?.imageId || undefined,
@@ -22,6 +28,89 @@ export const useMPRIS = () => {
type: 'itemCard',
});
const radioSong = useMemo((): QueueSong | undefined => {
if (!isRadioActive || !isRadioPlaying) {
return undefined;
}
const title = radioMetadata?.title || stationName || 'Radio';
const artist = radioMetadata?.artist || stationName || null;
const album = stationName || null;
const radioId = `radio-${stationName || 'unknown'}`;
return {
_itemType: LibraryItem.SONG,
_serverId: '',
_serverType: ServerType.NAVIDROME,
_uniqueId: radioId,
album: album || null,
albumArtistName: artist || '',
albumArtists: artist
? [
{
id: '',
imageId: null,
imageUrl: null,
name: artist,
userFavorite: false,
userRating: null,
},
]
: [],
albumId: '',
artistName: artist || '',
artists: artist
? [
{
id: '',
imageId: null,
imageUrl: null,
name: artist,
userFavorite: false,
userRating: null,
},
]
: [],
bitDepth: null,
bitRate: 0,
bpm: null,
channels: null,
comment: null,
compilation: null,
container: null,
createdAt: '',
discNumber: 0,
discSubtitle: null,
duration: 0,
explicitStatus: null,
gain: null,
genres: [],
id: radioId,
imageId: null,
imageUrl: null,
lastPlayedAt: null,
lyrics: null,
mbzRecordingId: null,
mbzTrackId: null,
name: title,
participants: null,
path: null,
peak: null,
playCount: 0,
releaseDate: null,
releaseYear: null,
sampleRate: null,
size: 0,
tags: null,
trackNumber: 0,
trackSubtitle: null,
updatedAt: new Date().toISOString(),
userFavorite: false,
userRating: null,
};
}, [isRadioActive, isRadioPlaying, radioMetadata, stationName]);
useEffect(() => {
if (!mpris) {
return;
@@ -56,14 +145,18 @@ export const useMPRIS = () => {
};
}, [player]);
// Update MPRIS when song or imageUrl changes
// Update MPRIS when song, imageUrl, or radio metadata changes
useEffect(() => {
if (!mpris) {
return;
}
mpris?.updateSong(currentSong, imageUrl);
}, [currentSong, imageUrl]);
// Use radio song if radio is active and playing, otherwise use current song
const songToUpdate = isRadioActive && isRadioPlaying ? radioSong : currentSong;
const imageUrlToUpdate = isRadioActive && isRadioPlaying ? null : imageUrl;
mpris?.updateSong(songToUpdate, imageUrlToUpdate);
}, [currentSong, imageUrl, isRadioActive, isRadioPlaying, radioSong]);
usePlayerEvents(
{