mirror of
https://github.com/jeffvli/feishin.git
synced 2026-05-10 04:30:25 +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 { getItemImageUrl } from '/@/renderer/components/item-image/item-image';
|
||||||
import { usePlayerEvents } from '/@/renderer/features/player/audio-player/hooks/use-player-events';
|
import { usePlayerEvents } from '/@/renderer/features/player/audio-player/hooks/use-player-events';
|
||||||
import { usePlayer } from '/@/renderer/features/player/context/player-context';
|
import { usePlayer } from '/@/renderer/features/player/context/player-context';
|
||||||
|
import {
|
||||||
|
useIsRadioActive,
|
||||||
|
useRadioPlayer,
|
||||||
|
} from '/@/renderer/features/radio/hooks/use-radio-player';
|
||||||
import {
|
import {
|
||||||
usePlaybackSettings,
|
usePlaybackSettings,
|
||||||
usePlaybackType,
|
usePlaybackType,
|
||||||
@@ -22,6 +26,8 @@ export const useMediaSession = () => {
|
|||||||
const player = usePlayer();
|
const player = usePlayer();
|
||||||
const skip = useSkipButtons();
|
const skip = useSkipButtons();
|
||||||
const playbackType = useSettingsStore((state) => state.playback.type);
|
const playbackType = useSettingsStore((state) => state.playback.type);
|
||||||
|
const isRadioActive = useIsRadioActive();
|
||||||
|
const { isPlaying: isRadioPlaying, metadata: radioMetadata, stationName } = useRadioPlayer();
|
||||||
|
|
||||||
const isMediaSessionEnabled = useMemo(() => {
|
const isMediaSessionEnabled = useMemo(() => {
|
||||||
// Always enable media session on web
|
// Always enable media session on web
|
||||||
@@ -38,6 +44,10 @@ export const useMediaSession = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
mediaSession.setActionHandler('nexttrack', () => {
|
mediaSession.setActionHandler('nexttrack', () => {
|
||||||
|
if (isRadioActive && isRadioPlaying) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
player.mediaNext();
|
player.mediaNext();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -50,10 +60,18 @@ export const useMediaSession = () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
mediaSession.setActionHandler('previoustrack', () => {
|
mediaSession.setActionHandler('previoustrack', () => {
|
||||||
|
if (isRadioActive && isRadioPlaying) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
player.mediaPrevious();
|
player.mediaPrevious();
|
||||||
});
|
});
|
||||||
|
|
||||||
mediaSession.setActionHandler('seekto', (e) => {
|
mediaSession.setActionHandler('seekto', (e) => {
|
||||||
|
if (isRadioActive && isRadioPlaying) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (e.seekTime) {
|
if (e.seekTime) {
|
||||||
player.mediaSeekToTimestamp(e.seekTime);
|
player.mediaSeekToTimestamp(e.seekTime);
|
||||||
} else if (e.seekOffset) {
|
} else if (e.seekOffset) {
|
||||||
@@ -67,6 +85,10 @@ export const useMediaSession = () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
mediaSession.setActionHandler('seekbackward', (e) => {
|
mediaSession.setActionHandler('seekbackward', (e) => {
|
||||||
|
if (isRadioActive && isRadioPlaying) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const currentTimestamp = useTimestampStoreBase.getState().timestamp;
|
const currentTimestamp = useTimestampStoreBase.getState().timestamp;
|
||||||
player.mediaSeekToTimestamp(
|
player.mediaSeekToTimestamp(
|
||||||
currentTimestamp - (e.seekOffset || skip?.skipBackwardSeconds || 5),
|
currentTimestamp - (e.seekOffset || skip?.skipBackwardSeconds || 5),
|
||||||
@@ -74,6 +96,10 @@ export const useMediaSession = () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
mediaSession.setActionHandler('seekforward', (e) => {
|
mediaSession.setActionHandler('seekforward', (e) => {
|
||||||
|
if (isRadioActive && isRadioPlaying) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const currentTimestamp = useTimestampStoreBase.getState().timestamp;
|
const currentTimestamp = useTimestampStoreBase.getState().timestamp;
|
||||||
player.mediaSeekToTimestamp(
|
player.mediaSeekToTimestamp(
|
||||||
currentTimestamp + (e.seekOffset || skip?.skipForwardSeconds || 5),
|
currentTimestamp + (e.seekOffset || skip?.skipForwardSeconds || 5),
|
||||||
@@ -90,11 +116,37 @@ export const useMediaSession = () => {
|
|||||||
mediaSession.setActionHandler('seekbackward', null);
|
mediaSession.setActionHandler('seekbackward', null);
|
||||||
mediaSession.setActionHandler('seekforward', null);
|
mediaSession.setActionHandler('seekforward', null);
|
||||||
};
|
};
|
||||||
}, [player, skip?.skipBackwardSeconds, skip?.skipForwardSeconds, isMediaSessionEnabled]);
|
}, [
|
||||||
|
player,
|
||||||
|
skip?.skipBackwardSeconds,
|
||||||
|
skip?.skipForwardSeconds,
|
||||||
|
isMediaSessionEnabled,
|
||||||
|
isRadioActive,
|
||||||
|
isRadioPlaying,
|
||||||
|
]);
|
||||||
|
|
||||||
const updateMediaSessionMetadata = useCallback(
|
const updateMediaSessionMetadata = useCallback(
|
||||||
(song: QueueSong | undefined) => {
|
(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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -112,9 +164,27 @@ export const useMediaSession = () => {
|
|||||||
title: song?.name ?? '',
|
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(
|
usePlayerEvents(
|
||||||
{
|
{
|
||||||
onCurrentSongChange: (properties) => {
|
onCurrentSongChange: (properties) => {
|
||||||
@@ -122,6 +192,10 @@ export const useMediaSession = () => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isRadioActive && isRadioPlaying) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
updateMediaSessionMetadata(properties.song);
|
updateMediaSessionMetadata(properties.song);
|
||||||
},
|
},
|
||||||
onPlayerRepeated: () => {
|
onPlayerRepeated: () => {
|
||||||
@@ -129,6 +203,10 @@ export const useMediaSession = () => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isRadioActive && isRadioPlaying) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const currentSong = usePlayerStore.getState().getCurrentSong();
|
const currentSong = usePlayerStore.getState().getCurrentSong();
|
||||||
updateMediaSessionMetadata(currentSong);
|
updateMediaSessionMetadata(currentSong);
|
||||||
},
|
},
|
||||||
@@ -141,7 +219,7 @@ export const useMediaSession = () => {
|
|||||||
mediaSession.playbackState = status === PlayerStatus.PLAYING ? 'playing' : 'paused';
|
mediaSession.playbackState = status === PlayerStatus.PLAYING ? 'playing' : 'paused';
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
[isMediaSessionEnabled, mediaSession],
|
[isMediaSessionEnabled, isRadioActive, isRadioPlaying, updateMediaSessionMetadata],
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,15 @@
|
|||||||
import isElectron from 'is-electron';
|
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 { useItemImageUrl } from '/@/renderer/components/item-image/item-image';
|
||||||
import { usePlayerEvents } from '/@/renderer/features/player/audio-player/hooks/use-player-events';
|
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 { usePlayerSong, usePlayerStore } from '/@/renderer/store';
|
||||||
import { LibraryItem } from '/@/shared/types/domain-types';
|
import { LibraryItem, QueueSong } from '/@/shared/types/domain-types';
|
||||||
import { PlayerShuffle } from '/@/shared/types/types';
|
import { PlayerShuffle, ServerType } from '/@/shared/types/types';
|
||||||
|
|
||||||
const ipc = isElectron() ? window.api.ipc : null;
|
const ipc = isElectron() ? window.api.ipc : null;
|
||||||
const utils = isElectron() ? window.api.utils : null;
|
const utils = isElectron() ? window.api.utils : null;
|
||||||
@@ -14,6 +18,8 @@ const mpris = isElectron() && utils?.isLinux() ? window.api.mpris : null;
|
|||||||
export const useMPRIS = () => {
|
export const useMPRIS = () => {
|
||||||
const player = usePlayerStore();
|
const player = usePlayerStore();
|
||||||
const currentSong = usePlayerSong();
|
const currentSong = usePlayerSong();
|
||||||
|
const isRadioActive = useIsRadioActive();
|
||||||
|
const { isPlaying: isRadioPlaying, metadata: radioMetadata, stationName } = useRadioPlayer();
|
||||||
|
|
||||||
const imageUrl = useItemImageUrl({
|
const imageUrl = useItemImageUrl({
|
||||||
id: currentSong?.imageId || undefined,
|
id: currentSong?.imageId || undefined,
|
||||||
@@ -22,6 +28,89 @@ export const useMPRIS = () => {
|
|||||||
type: 'itemCard',
|
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(() => {
|
useEffect(() => {
|
||||||
if (!mpris) {
|
if (!mpris) {
|
||||||
return;
|
return;
|
||||||
@@ -56,14 +145,18 @@ export const useMPRIS = () => {
|
|||||||
};
|
};
|
||||||
}, [player]);
|
}, [player]);
|
||||||
|
|
||||||
// Update MPRIS when song or imageUrl changes
|
// Update MPRIS when song, imageUrl, or radio metadata changes
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!mpris) {
|
if (!mpris) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
mpris?.updateSong(currentSong, imageUrl);
|
// Use radio song if radio is active and playing, otherwise use current song
|
||||||
}, [currentSong, imageUrl]);
|
const songToUpdate = isRadioActive && isRadioPlaying ? radioSong : currentSong;
|
||||||
|
const imageUrlToUpdate = isRadioActive && isRadioPlaying ? null : imageUrl;
|
||||||
|
|
||||||
|
mpris?.updateSong(songToUpdate, imageUrlToUpdate);
|
||||||
|
}, [currentSong, imageUrl, isRadioActive, isRadioPlaying, radioSong]);
|
||||||
|
|
||||||
usePlayerEvents(
|
usePlayerEvents(
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user