From d068288391130c632b1005b05f38f3ab8fcbed49 Mon Sep 17 00:00:00 2001 From: austinried <4966622+austinried@users.noreply.github.com> Date: Tue, 17 Aug 2021 13:27:24 +0900 Subject: [PATCH] move track player hooks and mapping into state --- app/components/ListItem.tsx | 4 +- app/components/ListPlayerControls.tsx | 6 +- app/hooks/trackplayer.ts | 233 +------------------------- app/playbackservice.ts | 2 +- app/screens/ArtistView.tsx | 5 +- app/screens/NowPlayingQueue.tsx | 4 +- app/screens/NowPlayingView.tsx | 17 +- app/screens/Search.tsx | 4 +- app/screens/SongListView.tsx | 5 +- app/state/store.ts | 3 + app/state/trackplayer.ts | 178 ++++++++++++++++++-- app/state/trackplayermap.ts | 50 ++++++ 12 files changed, 245 insertions(+), 266 deletions(-) create mode 100644 app/state/trackplayermap.ts diff --git a/app/components/ListItem.tsx b/app/components/ListItem.tsx index 239e633..38fa21a 100644 --- a/app/components/ListItem.tsx +++ b/app/components/ListItem.tsx @@ -135,7 +135,9 @@ const ListItem: React.FC<{ const starItem = useStore(selectMusic.starItem) const toggleStarred = useCallback(() => { - starItem(item.id, item.itemType, starred) + if (item.itemType !== 'playlist') { + starItem(item.id, item.itemType, starred) + } }, [item.id, item.itemType, starItem, starred]) let title = <> diff --git a/app/components/ListPlayerControls.tsx b/app/components/ListPlayerControls.tsx index 5b15a85..3a94983 100644 --- a/app/components/ListPlayerControls.tsx +++ b/app/components/ListPlayerControls.tsx @@ -1,7 +1,7 @@ import Button from '@app/components/Button' -import { useSetQueue } from '@app/hooks/trackplayer' import { Song } from '@app/models/music' -import { QueueContextType } from '@app/state/trackplayer' +import { useStore } from '@app/state/store' +import { QueueContextType, selectTrackPlayer } from '@app/state/trackplayer' import colors from '@app/styles/colors' import React, { useState } from 'react' import { StyleProp, StyleSheet, View, ViewStyle } from 'react-native' @@ -17,7 +17,7 @@ const ListPlayerControls = React.memo<{ style?: StyleProp }>(({ songs, typeName, queueName, queueContextType, queueContextId, style }) => { const [downloaded, setDownloaded] = useState(false) - const setQueue = useSetQueue() + const setQueue = useStore(selectTrackPlayer.setQueue) return ( diff --git a/app/hooks/trackplayer.ts b/app/hooks/trackplayer.ts index 083119c..12f1848 100644 --- a/app/hooks/trackplayer.ts +++ b/app/hooks/trackplayer.ts @@ -1,17 +1,6 @@ -import { Song } from '@app/models/music' -import { selectCache } from '@app/state/cache' import { useStore } from '@app/state/store' -import { - getCurrentTrack, - getQueue, - getRepeatMode, - QueueContextType, - selectTrackPlayer, - TrackExt, - trackPlayerCommands, -} from '@app/state/trackplayer' -import { useCallback } from 'react' -import TrackPlayer, { RepeatMode } from 'react-native-track-player' +import { getQueue, selectTrackPlayer, trackPlayerCommands } from '@app/state/trackplayer' +import TrackPlayer from 'react-native-track-player' export const usePlay = () => { return () => trackPlayerCommands.enqueue(() => TrackPlayer.play()) @@ -67,33 +56,8 @@ export const useSeekTo = () => { }) } -export const useToggleRepeat = () => { - const setRepeatMode = useStore(selectTrackPlayer.setRepeatMode) - - return () => - trackPlayerCommands.enqueue(async () => { - const repeatMode = await getRepeatMode() - let nextMode = RepeatMode.Off - - switch (repeatMode) { - case RepeatMode.Off: - nextMode = RepeatMode.Queue - break - case RepeatMode.Queue: - nextMode = RepeatMode.Track - break - default: - nextMode = RepeatMode.Off - break - } - - await TrackPlayer.setRepeatMode(nextMode) - setRepeatMode(nextMode) - }) -} - export const useReset = (enqueue = true) => { - const resetStore = useStore(selectTrackPlayer.reset) + const resetStore = useStore(selectTrackPlayer.resetTrackPlayerState) const reset = async () => { await TrackPlayer.reset() @@ -103,166 +67,6 @@ export const useReset = (enqueue = true) => { return enqueue ? () => trackPlayerCommands.enqueue(reset) : reset } -function shuffleTracks(tracks: TrackExt[], firstTrack?: number) { - if (tracks.length === 0) { - return { tracks, shuffleOrder: [] } - } - - const trackIndexes = tracks.map((_t, i) => i) - let shuffleOrder: number[] = [] - - for (let i = trackIndexes.length; i--; i > 0) { - const randi = Math.floor(Math.random() * (i + 1)) - shuffleOrder.push(trackIndexes.splice(randi, 1)[0]) - } - - if (firstTrack !== undefined) { - shuffleOrder.splice(shuffleOrder.indexOf(firstTrack), 1) - shuffleOrder = [firstTrack, ...shuffleOrder] - } - - tracks = shuffleOrder.map(i => tracks[i]) - - return { tracks, shuffleOrder } -} - -function unshuffleTracks(tracks: TrackExt[], shuffleOrder: number[]): TrackExt[] { - if (tracks.length === 0 || shuffleOrder.length === 0) { - return tracks - } - - return shuffleOrder.map((_v, i) => tracks[shuffleOrder.indexOf(i)]) -} - -export const useToggleShuffle = () => { - const setCurrentTrackIdx = useStore(selectTrackPlayer.setCurrentTrackIdx) - const setQueue = useStore(selectTrackPlayer.setQueue) - const setShuffleOrder = useStore(selectTrackPlayer.setShuffleOrder) - const getShuffleOrder = useCallback(() => useStore.getState().shuffleOrder, []) - const setProgress = useStore(selectTrackPlayer.setProgress) - const getProgress = useCallback(() => useStore.getState().progress, []) - - return async () => { - return trackPlayerCommands.enqueue(async () => { - const queue = await getQueue() - const current = await getCurrentTrack() - const queueShuffleOrder = getShuffleOrder() - const progress = getProgress() - - await TrackPlayer.remove(queue.map((_t, i) => i).filter(i => i !== current)) - - if (queueShuffleOrder === undefined) { - let { tracks, shuffleOrder } = shuffleTracks(queue, current) - if (tracks.length > 0) { - tracks = tracks.slice(1) - } - - await TrackPlayer.add(tracks) - setShuffleOrder(shuffleOrder) - } else { - const tracks = unshuffleTracks(queue, queueShuffleOrder) - - if (current !== undefined) { - const shuffledCurrent = queueShuffleOrder[current] - const tracks1 = tracks.slice(0, shuffledCurrent) - const tracks2 = tracks.slice(shuffledCurrent + 1) - - await TrackPlayer.add(tracks2) - await TrackPlayer.add(tracks1, 0) - } else { - await TrackPlayer.add(tracks) - } - - setShuffleOrder(undefined) - } - - setQueue(await getQueue()) - setCurrentTrackIdx(await getCurrentTrack()) - setProgress(progress) - }) - } -} - -export const useSetQueue = () => { - const setCurrentTrackIdx = useStore(selectTrackPlayer.setCurrentTrackIdx) - const setQueue = useStore(selectTrackPlayer.setQueue) - const setShuffleOrder = useStore(selectTrackPlayer.setShuffleOrder) - const setQueueName = useStore(selectTrackPlayer.setQueueName) - const getQueueShuffled = useCallback(() => !!useStore.getState().shuffleOrder, []) - const setQueueContextType = useStore(selectTrackPlayer.setQueueContextType) - const setQueueContextId = useStore(selectTrackPlayer.setQueueContextId) - const fetchCoverArtFilePath = useStore(selectCache.fetchCoverArtFilePath) - const buildStreamUri = useStore(selectTrackPlayer.buildStreamUri) - - return async ( - songs: Song[], - name: string, - contextType: QueueContextType, - contextId: string, - playTrack?: number, - shuffle?: boolean, - ) => - trackPlayerCommands.enqueue(async () => { - const shuffled = shuffle !== undefined ? shuffle : getQueueShuffled() - - await TrackPlayer.setupPlayer() - await TrackPlayer.reset() - - if (songs.length === 0) { - return - } - - const coverArtPaths: { [coverArt: string]: string | undefined } = {} - for (const s of songs) { - if (!s.coverArt) { - continue - } - - coverArtPaths[s.coverArt] = await fetchCoverArtFilePath(s.coverArt) - } - - let queue = songs.map(s => mapSongToTrack(s, coverArtPaths)) - - try { - for (const t of queue) { - t.url = buildStreamUri(t.id) - } - } catch { - return - } - - if (shuffled) { - const { tracks, shuffleOrder } = shuffleTracks(queue, playTrack) - setShuffleOrder(shuffleOrder) - queue = tracks - playTrack = 0 - } else { - setShuffleOrder(undefined) - } - - playTrack = playTrack || 0 - - setQueue(queue) - setCurrentTrackIdx(playTrack) - setQueueName(name) - setQueueContextType(contextType) - setQueueContextId(contextId) - - if (playTrack === 0) { - await TrackPlayer.add(queue) - await TrackPlayer.play() - } else { - const tracks1 = queue.slice(0, playTrack) - const tracks2 = queue.slice(playTrack) - - await TrackPlayer.add(tracks2) - await TrackPlayer.play() - - await TrackPlayer.add(tracks1, 0) - } - }) -} - export const useIsPlaying = (contextId: string | undefined, track: number) => { const queueContextId = useStore(selectTrackPlayer.queueContextId) const currentTrackIdx = useStore(selectTrackPlayer.currentTrackIdx) @@ -279,34 +83,3 @@ export const useIsPlaying = (contextId: string | undefined, track: number) => { return contextId === queueContextId && track === currentTrackIdx } - -function mapSongToTrack(song: Song, coverArtPaths: { [coverArt: string]: string | undefined }): TrackExt { - return { - id: song.id, - title: song.title, - artist: song.artist || 'Unknown Artist', - album: song.album || 'Unknown Album', - url: song.streamUri, - artwork: - song.coverArt && coverArtPaths[song.coverArt] ? coverArtPaths[song.coverArt] : require('@res/fallback.png'), - coverArt: song.coverArt, - duration: song.duration, - artistId: song.artistId, - albumId: song.albumId, - } -} - -export function mapTrackExtToSong(track: TrackExt): Song { - return { - itemType: 'song', - id: track.id, - title: track.title as string, - artist: track.artist, - album: track.album, - streamUri: track.url as string, - coverArt: track.coverArt, - duration: track.duration, - artistId: track.artistId, - albumId: track.albumId, - } -} diff --git a/app/playbackservice.ts b/app/playbackservice.ts index bc08f74..37f807e 100644 --- a/app/playbackservice.ts +++ b/app/playbackservice.ts @@ -6,7 +6,7 @@ import NetInfo, { NetInfoStateType } from '@react-native-community/netinfo' const reset = () => { unstable_batchedUpdates(() => { - useStore.getState().reset() + useStore.getState().resetTrackPlayerState() }) } diff --git a/app/screens/ArtistView.tsx b/app/screens/ArtistView.tsx index c7be010..6e93027 100644 --- a/app/screens/ArtistView.tsx +++ b/app/screens/ArtistView.tsx @@ -6,8 +6,9 @@ import Header from '@app/components/Header' import HeaderBar from '@app/components/HeaderBar' import ListItem from '@app/components/ListItem' import { useArtistInfo } from '@app/hooks/music' -import { useSetQueue } from '@app/hooks/trackplayer' import { Album, Song } from '@app/models/music' +import { useStore } from '@app/state/store' +import { selectTrackPlayer } from '@app/state/trackplayer' import colors from '@app/styles/colors' import dimensions from '@app/styles/dimensions' import font from '@app/styles/font' @@ -52,7 +53,7 @@ const TopSongs = React.memo<{ name: string artistId: string }>(({ songs, name, artistId }) => { - const setQueue = useSetQueue() + const setQueue = useStore(selectTrackPlayer.setQueue) return ( <> diff --git a/app/screens/NowPlayingQueue.tsx b/app/screens/NowPlayingQueue.tsx index bcddea7..ebcf30f 100644 --- a/app/screens/NowPlayingQueue.tsx +++ b/app/screens/NowPlayingQueue.tsx @@ -1,14 +1,16 @@ import GradientScrollView from '@app/components/GradientScrollView' import ListItem from '@app/components/ListItem' import NowPlayingBar from '@app/components/NowPlayingBar' -import { mapTrackExtToSong, useSkipTo } from '@app/hooks/trackplayer' +import { useSkipTo } from '@app/hooks/trackplayer' import { useStore } from '@app/state/store' import { selectTrackPlayer } from '@app/state/trackplayer' +import { selectTrackPlayerMap } from '@app/state/trackplayermap' import React from 'react' import { StyleSheet, View } from 'react-native' const NowPlayingQueue = React.memo<{}>(() => { const queue = useStore(selectTrackPlayer.queue) + const mapTrackExtToSong = useStore(selectTrackPlayerMap.mapTrackExtToSong) const skipTo = useSkipTo() return ( diff --git a/app/screens/NowPlayingView.tsx b/app/screens/NowPlayingView.tsx index 9680361..a2bd8a9 100644 --- a/app/screens/NowPlayingView.tsx +++ b/app/screens/NowPlayingView.tsx @@ -4,19 +4,11 @@ import ImageGradientBackground from '@app/components/ImageGradientBackground' import PressableOpacity from '@app/components/PressableOpacity' import Star from '@app/components/Star' import { useStarred } from '@app/hooks/music' -import { - mapTrackExtToSong, - useNext, - usePause, - usePlay, - usePrevious, - useSeekTo, - useToggleRepeat, - useToggleShuffle, -} from '@app/hooks/trackplayer' +import { useNext, usePause, usePlay, usePrevious, useSeekTo } from '@app/hooks/trackplayer' import { selectMusic } from '@app/state/music' import { useStore } from '@app/state/store' import { QueueContextType, selectTrackPlayer, TrackExt } from '@app/state/trackplayer' +import { selectTrackPlayerMap } from '@app/state/trackplayermap' import colors from '@app/styles/colors' import font from '@app/styles/font' import formatDuration from '@app/util/formatDuration' @@ -51,6 +43,7 @@ const NowPlayingHeader = React.memo<{ }>(({ track }) => { const queueName = useStore(selectTrackPlayer.queueName) const queueContextType = useStore(selectTrackPlayer.queueContextType) + const mapTrackExtToSong = useStore(selectTrackPlayerMap.mapTrackExtToSong) if (!track) { return <> @@ -272,9 +265,9 @@ const PlayerControls = () => { const next = useNext() const previous = usePrevious() const shuffled = useStore(selectTrackPlayer.shuffled) - const toggleShuffle = useToggleShuffle() + const toggleShuffle = useStore(selectTrackPlayer.toggleShuffle) const repeatMode = useStore(selectTrackPlayer.repeatMode) - const toggleRepeat = useToggleRepeat() + const toggleRepeat = useStore(selectTrackPlayer.toggleRepeatMode) const navigation = useNavigation() let playPauseIcon: string diff --git a/app/screens/Search.tsx b/app/screens/Search.tsx index 74b79ce..967c154 100644 --- a/app/screens/Search.tsx +++ b/app/screens/Search.tsx @@ -3,10 +3,10 @@ import Header from '@app/components/Header' import ListItem from '@app/components/ListItem' import NothingHere from '@app/components/NothingHere' import { useActiveListRefresh2 } from '@app/hooks/server' -import { useSetQueue } from '@app/hooks/trackplayer' import { ListableItem, SearchResults, Song } from '@app/models/music' import { selectMusic } from '@app/state/music' import { useStore } from '@app/state/store' +import { selectTrackPlayer } from '@app/state/trackplayer' import colors from '@app/styles/colors' import font from '@app/styles/font' import debounce from 'lodash.debounce' @@ -14,7 +14,7 @@ import React, { useCallback, useMemo, useState } from 'react' import { ActivityIndicator, StatusBar, StyleSheet, TextInput, View } from 'react-native' const SongItem = React.memo<{ item: Song }>(({ item }) => { - const setQueue = useSetQueue() + const setQueue = useStore(selectTrackPlayer.setQueue) return ( (({ songs, name, type, itemId }) => { - const setQueue = useSetQueue() + const setQueue = useStore(selectTrackPlayer.setQueue) const _songs = [...songs] let typeName = '' diff --git a/app/state/store.ts b/app/state/store.ts index 30e4757..a43f5e0 100644 --- a/app/state/store.ts +++ b/app/state/store.ts @@ -6,11 +6,13 @@ import { persist, StateStorage } from 'zustand/middleware' import { CacheSlice, createCacheSlice } from './cache' import { createMusicMapSlice, MusicMapSlice } from './musicmap' import { createTrackPlayerSlice, TrackPlayerSlice } from './trackplayer' +import { createTrackPlayerMapSlice, TrackPlayerMapSlice } from './trackplayermap' export type Store = SettingsSlice & MusicSlice & MusicMapSlice & TrackPlayerSlice & + TrackPlayerMapSlice & CacheSlice & { hydrated: boolean setHydrated: (hydrated: boolean) => void @@ -41,6 +43,7 @@ export const useStore = create( ...createMusicSlice(set, get), ...createMusicMapSlice(set, get), ...createTrackPlayerSlice(set, get), + ...createTrackPlayerMapSlice(set, get), ...createCacheSlice(set, get), hydrated: false, diff --git a/app/state/trackplayer.ts b/app/state/trackplayer.ts index b43ff95..789da4f 100644 --- a/app/state/trackplayer.ts +++ b/app/state/trackplayer.ts @@ -1,7 +1,7 @@ import { NoClientError } from '@app/models/error' +import { Song } from '@app/models/music' import PromiseQueue from '@app/util/PromiseQueue' import produce from 'immer' -import { ToastAndroid } from 'react-native' import TrackPlayer, { RepeatMode, State, Track } from 'react-native-track-player' import { GetState, SetState } from 'zustand' import { Store } from './store' @@ -32,10 +32,10 @@ export type TrackPlayerSlice = { setQueueContextId: (queueContextId?: string) => void shuffleOrder?: number[] - setShuffleOrder: (shuffleOrder?: number[]) => void + toggleShuffle: () => Promise repeatMode: RepeatMode - setRepeatMode: (repeatMode: RepeatMode) => void + toggleRepeatMode: () => Promise playerState: State setPlayerState: (playerState: State) => void @@ -45,7 +45,14 @@ export type TrackPlayerSlice = { setCurrentTrackIdx: (idx?: number) => void queue: TrackExt[] - setQueue: (queue: TrackExt[]) => void + setQueue: ( + songs: Song[], + name: string, + contextType: QueueContextType, + contextId: string, + playTrack?: number, + shuffle?: boolean, + ) => Promise progress: Progress setProgress: (progress: Progress) => void @@ -57,7 +64,7 @@ export type TrackPlayerSlice = { rebuildQueue: () => Promise buildStreamUri: (id: string) => string - reset: () => void + resetTrackPlayerState: () => void } export const selectTrackPlayer = { @@ -71,11 +78,11 @@ export const selectTrackPlayer = { setQueueContextId: (store: TrackPlayerSlice) => store.setQueueContextId, shuffleOrder: (store: TrackPlayerSlice) => store.shuffleOrder, - setShuffleOrder: (store: TrackPlayerSlice) => store.setShuffleOrder, shuffled: (store: TrackPlayerSlice) => !!store.shuffleOrder, + toggleShuffle: (store: TrackPlayerSlice) => store.toggleShuffle, repeatMode: (store: TrackPlayerSlice) => store.repeatMode, - setRepeatMode: (store: TrackPlayerSlice) => store.setRepeatMode, + toggleRepeatMode: (store: TrackPlayerSlice) => store.toggleRepeatMode, playerState: (store: TrackPlayerSlice) => store.playerState, setPlayerState: (store: TrackPlayerSlice) => store.setPlayerState, @@ -95,7 +102,7 @@ export const selectTrackPlayer = { setNetState: (store: TrackPlayerSlice) => store.setNetState, buildStreamUri: (store: TrackPlayerSlice) => store.buildStreamUri, - reset: (store: TrackPlayerSlice) => store.reset, + resetTrackPlayerState: (store: TrackPlayerSlice) => store.resetTrackPlayerState, } export const trackPlayerCommands = new PromiseQueue(1) @@ -111,10 +118,66 @@ export const createTrackPlayerSlice = (set: SetState, get: GetState set({ queueContextId }), shuffleOrder: undefined, - setShuffleOrder: shuffleOrder => set({ shuffleOrder }), + toggleShuffle: async () => { + return trackPlayerCommands.enqueue(async () => { + const queue = await getQueue() + const current = await getCurrentTrack() + const queueShuffleOrder = get().shuffleOrder + + await TrackPlayer.remove(queue.map((_t, i) => i).filter(i => i !== current)) + + if (queueShuffleOrder === undefined) { + let { tracks, shuffleOrder } = shuffleTracks(queue, current) + if (tracks.length > 0) { + tracks = tracks.slice(1) + } + + await TrackPlayer.add(tracks) + set({ shuffleOrder }) + } else { + const tracks = unshuffleTracks(queue, queueShuffleOrder) + + if (current !== undefined) { + const shuffledCurrent = queueShuffleOrder[current] + const tracks1 = tracks.slice(0, shuffledCurrent) + const tracks2 = tracks.slice(shuffledCurrent + 1) + + await TrackPlayer.add(tracks2) + await TrackPlayer.add(tracks1, 0) + } else { + await TrackPlayer.add(tracks) + } + + set({ shuffleOrder: undefined }) + } + + set({ queue: await getQueue() }) + get().setCurrentTrackIdx(await getCurrentTrack()) + }) + }, repeatMode: RepeatMode.Off, - setRepeatMode: repeatMode => set({ repeatMode }), + toggleRepeatMode: async () => { + return trackPlayerCommands.enqueue(async () => { + const repeatMode = await getRepeatMode() + let nextMode = RepeatMode.Off + + switch (repeatMode) { + case RepeatMode.Off: + nextMode = RepeatMode.Queue + break + case RepeatMode.Queue: + nextMode = RepeatMode.Track + break + default: + nextMode = RepeatMode.Off + break + } + + await TrackPlayer.setRepeatMode(nextMode) + set({ repeatMode: nextMode }) + }) + }, playerState: State.None, setPlayerState: playerState => set({ playerState }), @@ -131,7 +194,64 @@ export const createTrackPlayerSlice = (set: SetState, get: GetState set({ queue }), + setQueue: async (songs, name, contextType, contextId, playTrack, shuffle) => { + return trackPlayerCommands.enqueue(async () => { + const shuffled = shuffle !== undefined ? shuffle : !!get().shuffleOrder + + await TrackPlayer.setupPlayer() + await TrackPlayer.reset() + + if (songs.length === 0) { + return + } + + let queue = await get().mapSongstoTrackExts(songs) + + try { + for (const t of queue) { + t.url = get().buildStreamUri(t.id) + } + } catch { + return + } + + if (shuffled) { + const { tracks, shuffleOrder } = shuffleTracks(queue, playTrack) + set({ shuffleOrder }) + queue = tracks + playTrack = 0 + } else { + set({ shuffleOrder: undefined }) + } + + playTrack = playTrack || 0 + + try { + set({ + queue, + queueName: name, + queueContextType: contextType, + queueContextId: contextId, + }) + get().setCurrentTrackIdx(playTrack) + + if (playTrack === 0) { + await TrackPlayer.add(queue) + } else { + const tracks1 = queue.slice(0, playTrack) + const tracks2 = queue.slice(playTrack) + + await TrackPlayer.add(tracks2) + await TrackPlayer.add(tracks1, 0) + } + + await TrackPlayer.play() + } catch { + get().resetTrackPlayerState() + await TrackPlayer.reset() + } + }) + }, progress: { position: 0, duration: 0, buffered: 0 }, setProgress: progress => set({ progress }), @@ -194,10 +314,13 @@ export const createTrackPlayerSlice = (set: SetState, get: GetState, get: GetState { + resetTrackPlayerState: () => { set({ queueName: undefined, queueContextType: undefined, @@ -249,3 +372,34 @@ export const getPlayerState = async (): Promise => { export const getRepeatMode = async (): Promise => { return (await TrackPlayer.getRepeatMode()) || RepeatMode.Off } + +function shuffleTracks(tracks: TrackExt[], firstTrack?: number) { + if (tracks.length === 0) { + return { tracks, shuffleOrder: [] } + } + + const trackIndexes = tracks.map((_t, i) => i) + let shuffleOrder: number[] = [] + + for (let i = trackIndexes.length; i--; i > 0) { + const randi = Math.floor(Math.random() * (i + 1)) + shuffleOrder.push(trackIndexes.splice(randi, 1)[0]) + } + + if (firstTrack !== undefined) { + shuffleOrder.splice(shuffleOrder.indexOf(firstTrack), 1) + shuffleOrder = [firstTrack, ...shuffleOrder] + } + + tracks = shuffleOrder.map(i => tracks[i]) + + return { tracks, shuffleOrder } +} + +function unshuffleTracks(tracks: TrackExt[], shuffleOrder: number[]): TrackExt[] { + if (tracks.length === 0 || shuffleOrder.length === 0) { + return tracks + } + + return shuffleOrder.map((_v, i) => tracks[shuffleOrder.indexOf(i)]) +} diff --git a/app/state/trackplayermap.ts b/app/state/trackplayermap.ts new file mode 100644 index 0000000..8f5d094 --- /dev/null +++ b/app/state/trackplayermap.ts @@ -0,0 +1,50 @@ +import { Song } from '@app/models/music' +import { GetState, SetState } from 'zustand' +import { Store } from './store' +import { TrackExt } from './trackplayer' + +export type TrackPlayerMapSlice = { + mapSongtoTrackExt: (song: Song) => Promise + mapSongstoTrackExts: (songs: Song[]) => Promise + mapTrackExtToSong: (song: TrackExt) => Song +} + +export const selectTrackPlayerMap = { + mapTrackExtToSong: (store: TrackPlayerMapSlice) => store.mapTrackExtToSong, +} + +export const createTrackPlayerMapSlice = (set: SetState, get: GetState): TrackPlayerMapSlice => ({ + mapSongtoTrackExt: async song => { + return { + id: song.id, + title: song.title, + artist: song.artist || 'Unknown Artist', + album: song.album || 'Unknown Album', + url: song.streamUri, + artwork: song.coverArt ? await get().fetchCoverArtFilePath(song.coverArt) : require('@res/fallback.png'), + coverArt: song.coverArt, + duration: song.duration, + artistId: song.artistId, + albumId: song.albumId, + } + }, + + mapSongstoTrackExts: async songs => { + return await Promise.all(songs.map(get().mapSongtoTrackExt)) + }, + + mapTrackExtToSong: track => { + return { + itemType: 'song', + id: track.id, + title: track.title as string, + artist: track.artist, + album: track.album, + streamUri: track.url as string, + coverArt: track.coverArt, + duration: track.duration, + artistId: track.artistId, + albumId: track.albumId, + } + }, +})