mirror of
https://github.com/jeffvli/feishin.git
synced 2026-05-07 04:20:12 +02:00
handle radio metadata for mpris / mediasession (#1586)
This commit is contained in:
@@ -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(
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user