This commit is contained in:
Boof2015
2026-06-19 17:45:31 -04:00
parent 96702ba7f0
commit 0e3e46293c
26 changed files with 1644 additions and 9 deletions
+26 -9
View File
@@ -1,4 +1,5 @@
import TrackPlayer, { Event } from 'react-native-track-player';
import { syncWidgetNowPlayingFromTrackPlayer } from './widgetSync';
/**
* RNTP playback service — registered in `index.js`. Runs in a headless context
@@ -6,15 +7,31 @@ import TrackPlayer, { Event } from 'react-native-track-player';
* controls to the player. Must not depend on React or the JS UI tree.
*/
export async function PlaybackService(): Promise<void> {
TrackPlayer.addEventListener(Event.RemotePlay, () => TrackPlayer.play());
TrackPlayer.addEventListener(Event.RemotePause, () => TrackPlayer.pause());
TrackPlayer.addEventListener(Event.RemoteStop, () => TrackPlayer.stop());
TrackPlayer.addEventListener(Event.RemoteNext, () =>
TrackPlayer.skipToNext().catch(() => {}),
);
TrackPlayer.addEventListener(Event.RemotePrevious, () =>
TrackPlayer.skipToPrevious().catch(() => {}),
);
TrackPlayer.addEventListener(Event.PlaybackActiveTrackChanged, () => {
void syncWidgetNowPlayingFromTrackPlayer();
});
TrackPlayer.addEventListener(Event.PlaybackState, () => {
void syncWidgetNowPlayingFromTrackPlayer();
});
TrackPlayer.addEventListener(Event.RemotePlay, () => {
void TrackPlayer.play().finally(() => syncWidgetNowPlayingFromTrackPlayer());
});
TrackPlayer.addEventListener(Event.RemotePause, () => {
void TrackPlayer.pause().finally(() => syncWidgetNowPlayingFromTrackPlayer());
});
TrackPlayer.addEventListener(Event.RemoteStop, () => {
void TrackPlayer.stop().finally(() => syncWidgetNowPlayingFromTrackPlayer());
});
TrackPlayer.addEventListener(Event.RemoteNext, () => {
void TrackPlayer.skipToNext()
.catch(() => {})
.finally(() => syncWidgetNowPlayingFromTrackPlayer());
});
TrackPlayer.addEventListener(Event.RemotePrevious, () => {
void TrackPlayer.skipToPrevious()
.catch(() => {})
.finally(() => syncWidgetNowPlayingFromTrackPlayer());
});
TrackPlayer.addEventListener(Event.RemoteSeek, ({ position }) =>
TrackPlayer.seekTo(position),
);
+11
View File
@@ -9,6 +9,7 @@ import { usePlayerStore } from '@/stores/playerStore';
import { useLibraryStore } from '@/stores/libraryStore';
import type { PlaybackState } from '@/types/audio';
import { rntpToTrack } from './sampleTracks';
import { buildWidgetRecentItems, setWidgetNowPlaying } from './widgetSync';
const RECENT_PLAY_THRESHOLD_MS = 15_000;
@@ -54,6 +55,7 @@ export function usePlaybackSync(): void {
const setProgress = usePlayerStore((s) => s.setProgress);
const setPlaybackState = usePlayerStore((s) => s.setPlaybackState);
const recordTrackPlayed = useLibraryStore((s) => s.recordTrackPlayed);
const recentlyPlayedTracks = useLibraryStore((s) => s.recentlyPlayedTracks);
useEffect(() => {
setCurrentTrack(activeTrack ? rntpToTrack(activeTrack) : null);
@@ -67,6 +69,15 @@ export function usePlaybackSync(): void {
setPlaybackState(mappedPlaybackState);
}, [mappedPlaybackState, setPlaybackState]);
useEffect(() => {
const track = activeTrack ? rntpToTrack(activeTrack) : null;
setWidgetNowPlaying(
track,
mappedPlaybackState,
buildWidgetRecentItems(recentlyPlayedTracks, track?.path),
);
}, [activeTrack, mappedPlaybackState, recentlyPlayedTracks]);
useEffect(() => {
const path = activeTrack?.url ? String(activeTrack.url) : null;
const now = Date.now();
+83
View File
@@ -0,0 +1,83 @@
import TrackPlayer, { State, type Track as RntpTrack } from 'react-native-track-player';
import type { PlaybackState, Track } from '@/types/audio';
import type { DbTrack } from '@/types/library';
import { artworkThumbUri } from '@/library/artwork';
import { AstraWidget, type AstraWidgetRecentItem } from '../../modules/astra-widget';
function mapRntpState(state?: State): PlaybackState {
switch (state) {
case State.Playing:
return 'playing';
case State.Buffering:
case State.Loading:
return 'loading';
case State.Paused:
case State.Ready:
return 'paused';
default:
return 'stopped';
}
}
export function setWidgetNowPlaying(
track: Pick<Track, 'title' | 'artist' | 'artworkData'> | null,
playbackState: PlaybackState,
recentlyPlayed?: AstraWidgetRecentItem[],
): void {
AstraWidget.setNowPlaying({
title: track?.title ?? null,
artist: track?.artist ?? null,
artworkUri: track?.artworkData ?? null,
playbackState,
hasTrack: Boolean(track),
...(recentlyPlayed === undefined
? {}
: {
recentlyPlayed,
replaceRecentlyPlayed: true,
}),
});
}
export function setWidgetNowPlayingFromRntpTrack(
track: RntpTrack | null | undefined,
playbackState: PlaybackState,
): void {
setWidgetNowPlaying(
track
? {
title: track.title ?? 'Unknown title',
artist: track.artist ?? 'Unknown artist',
artworkData: typeof track.artwork === 'string' ? track.artwork : undefined,
}
: null,
playbackState,
);
}
export function buildWidgetRecentItems(
tracks: readonly DbTrack[],
currentPath?: string,
): AstraWidgetRecentItem[] {
return tracks
.filter((track) => track.path !== currentPath)
.slice(0, 8)
.map((track) => ({
title: track.title,
artist: track.artist,
artworkUri: track.artwork_hash ? artworkThumbUri(track.artwork_hash) : null,
}));
}
export async function syncWidgetNowPlayingFromTrackPlayer(): Promise<void> {
try {
const [activeTrack, playbackState] = await Promise.all([
TrackPlayer.getActiveTrack(),
TrackPlayer.getPlaybackState(),
]);
setWidgetNowPlayingFromRntpTrack(activeTrack, mapRntpState(playbackState.state));
} catch {
setWidgetNowPlaying(null, 'stopped');
}
}