mirror of
https://github.com/austinried/subtracks.git
synced 2025-12-29 09:29:29 +01:00
trying to centralize all player logic around state
This commit is contained in:
parent
acc7759495
commit
eb4199de37
@ -1,17 +1,18 @@
|
|||||||
import { useAtomValue } from 'jotai/utils'
|
import { useAtomValue } from 'jotai/utils'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { Pressable, StatusBar, StyleSheet, Text, useWindowDimensions, View } from 'react-native'
|
import { StatusBar, StyleSheet, Text, useWindowDimensions, View } from 'react-native'
|
||||||
import FastImage from 'react-native-fast-image'
|
import FastImage from 'react-native-fast-image'
|
||||||
import TrackPlayer, { State, useProgress } from 'react-native-track-player'
|
import TrackPlayer, { State, useProgress } from 'react-native-track-player'
|
||||||
import { currentQueueNameAtom, currentTrackAtom, playerStateAtom } from '../state/trackplayer'
|
import { queueNameAtom, currentTrackAtom, playerStateAtom, useNext, usePrevious } from '../state/trackplayer'
|
||||||
import colors from '../styles/colors'
|
import colors from '../styles/colors'
|
||||||
import text, { Font } from '../styles/text'
|
import text, { Font } from '../styles/text'
|
||||||
import { formatDuration } from '../util'
|
import { formatDuration } from '../util'
|
||||||
import CoverArt from './common/CoverArt'
|
import CoverArt from './common/CoverArt'
|
||||||
import ImageGradientBackground from './common/ImageGradientBackground'
|
import ImageGradientBackground from './common/ImageGradientBackground'
|
||||||
|
import PressableImage from './common/PressableImage'
|
||||||
|
|
||||||
const NowPlayingHeader = () => {
|
const NowPlayingHeader = () => {
|
||||||
const queueName = useAtomValue(currentQueueNameAtom)
|
const queueName = useAtomValue(queueNameAtom)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={headerStyles.container}>
|
<View style={headerStyles.container}>
|
||||||
@ -163,45 +164,52 @@ const seekStyles = StyleSheet.create({
|
|||||||
|
|
||||||
const PlayerControls = () => {
|
const PlayerControls = () => {
|
||||||
const state = useAtomValue(playerStateAtom)
|
const state = useAtomValue(playerStateAtom)
|
||||||
|
const next = useNext()
|
||||||
|
const previous = usePrevious()
|
||||||
|
|
||||||
let playPauseIcon: number
|
let playPauseIcon: number
|
||||||
let playPauseStyle: any
|
let playPauseAction: undefined | (() => void)
|
||||||
let playPauseAction: () => void
|
let disabled: boolean
|
||||||
|
|
||||||
switch (state) {
|
switch (state) {
|
||||||
case State.Playing:
|
case State.Playing:
|
||||||
case State.Buffering:
|
case State.Buffering:
|
||||||
case State.Connecting:
|
case State.Connecting:
|
||||||
|
disabled = false
|
||||||
playPauseIcon = require('../../res/pause_circle-fill.png')
|
playPauseIcon = require('../../res/pause_circle-fill.png')
|
||||||
playPauseStyle = controlsStyles.enabled
|
|
||||||
playPauseAction = () => TrackPlayer.pause()
|
playPauseAction = () => TrackPlayer.pause()
|
||||||
break
|
break
|
||||||
case State.Paused:
|
case State.Paused:
|
||||||
|
disabled = false
|
||||||
playPauseIcon = require('../../res/play_circle-fill.png')
|
playPauseIcon = require('../../res/play_circle-fill.png')
|
||||||
playPauseStyle = controlsStyles.enabled
|
|
||||||
playPauseAction = () => TrackPlayer.play()
|
playPauseAction = () => TrackPlayer.play()
|
||||||
break
|
break
|
||||||
default:
|
default:
|
||||||
|
disabled = true
|
||||||
playPauseIcon = require('../../res/play_circle-fill.png')
|
playPauseIcon = require('../../res/play_circle-fill.png')
|
||||||
playPauseStyle = controlsStyles.disabled
|
playPauseAction = undefined
|
||||||
playPauseAction = () => {}
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={controlsStyles.container}>
|
<View style={controlsStyles.container}>
|
||||||
<FastImage
|
<PressableImage
|
||||||
|
onPress={disabled ? undefined : previous}
|
||||||
source={require('../../res/previous-fill.png')}
|
source={require('../../res/previous-fill.png')}
|
||||||
tintColor="white"
|
style={controlsStyles.skip}
|
||||||
style={{ ...controlsStyles.skip, ...playPauseStyle }}
|
disabled={disabled}
|
||||||
/>
|
/>
|
||||||
<Pressable onPress={playPauseAction}>
|
<PressableImage
|
||||||
<FastImage source={playPauseIcon} tintColor="white" style={{ ...controlsStyles.play, ...playPauseStyle }} />
|
onPress={playPauseAction}
|
||||||
</Pressable>
|
source={playPauseIcon}
|
||||||
<FastImage
|
style={controlsStyles.play}
|
||||||
|
disabled={disabled}
|
||||||
|
/>
|
||||||
|
<PressableImage
|
||||||
|
onPress={disabled ? undefined : next}
|
||||||
source={require('../../res/next-fill.png')}
|
source={require('../../res/next-fill.png')}
|
||||||
tintColor="white"
|
style={controlsStyles.skip}
|
||||||
style={{ ...controlsStyles.skip, ...playPauseStyle }}
|
disabled={disabled}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
@ -224,12 +232,6 @@ const controlsStyles = StyleSheet.create({
|
|||||||
height: 90,
|
height: 90,
|
||||||
width: 90,
|
width: 90,
|
||||||
},
|
},
|
||||||
enabled: {
|
|
||||||
opacity: 1,
|
|
||||||
},
|
|
||||||
disabled: {
|
|
||||||
opacity: 0.35,
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const NowPlayingLayout = () => {
|
const NowPlayingLayout = () => {
|
||||||
|
|||||||
@ -1,19 +1,47 @@
|
|||||||
import React, { useCallback, useEffect } from 'react'
|
|
||||||
import TrackPlayer, { Event, State, useTrackPlayerEvents } from 'react-native-track-player'
|
|
||||||
import { useAppState } from '@react-native-community/hooks'
|
import { useAppState } from '@react-native-community/hooks'
|
||||||
import { useUpdateAtom, useAtomValue } from 'jotai/utils'
|
import { useAtomValue, useUpdateAtom } from 'jotai/utils'
|
||||||
import { currentQueueNameAtom, currentTrackAtom, playerStateAtom } from '../state/trackplayer'
|
import React, { useEffect } from 'react'
|
||||||
import { View } from 'react-native'
|
import { View } from 'react-native'
|
||||||
|
import TrackPlayer, { Event, useTrackPlayerEvents } from 'react-native-track-player'
|
||||||
|
import { currentTrackAtom, getQueue, getTrack, playerStateAtom, queueWriteAtom } from '../state/trackplayer'
|
||||||
|
|
||||||
|
const AppActiveResponder: React.FC<{
|
||||||
|
update: () => void
|
||||||
|
}> = ({ update }) => {
|
||||||
|
const appState = useAppState()
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (appState === 'active') {
|
||||||
|
update()
|
||||||
|
}
|
||||||
|
}, [appState, update])
|
||||||
|
|
||||||
|
return <></>
|
||||||
|
}
|
||||||
|
|
||||||
|
type Payload = { type: Event; [key: string]: any }
|
||||||
|
|
||||||
|
const TrackPlayerEventResponder: React.FC<{
|
||||||
|
update: (payload?: Payload) => void
|
||||||
|
events: Event[]
|
||||||
|
}> = ({ update, events }) => {
|
||||||
|
useTrackPlayerEvents(events, update)
|
||||||
|
|
||||||
|
return <AppActiveResponder update={update} />
|
||||||
|
}
|
||||||
|
|
||||||
const CurrentTrackState = () => {
|
const CurrentTrackState = () => {
|
||||||
const setCurrentTrack = useUpdateAtom(currentTrackAtom)
|
const setCurrentTrack = useUpdateAtom(currentTrackAtom)
|
||||||
const appState = useAppState()
|
|
||||||
|
|
||||||
const update = useCallback(async () => {
|
const update = async (payload?: Payload) => {
|
||||||
|
if (payload?.type === Event.PlaybackQueueEnded && 'track' in payload) {
|
||||||
|
setCurrentTrack(undefined)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
const index = await TrackPlayer.getCurrentTrack()
|
const index = await TrackPlayer.getCurrentTrack()
|
||||||
|
|
||||||
if (index !== null && index >= 0) {
|
if (index !== null && index >= 0) {
|
||||||
const track = await TrackPlayer.getTrack(index)
|
const track = await getTrack(index)
|
||||||
if (track !== null) {
|
if (track !== null) {
|
||||||
setCurrentTrack(track)
|
setCurrentTrack(track)
|
||||||
return
|
return
|
||||||
@ -21,102 +49,52 @@ const CurrentTrackState = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setCurrentTrack(undefined)
|
setCurrentTrack(undefined)
|
||||||
}, [setCurrentTrack])
|
}
|
||||||
|
|
||||||
useTrackPlayerEvents(
|
return (
|
||||||
[
|
<TrackPlayerEventResponder
|
||||||
// Event.PlaybackState,
|
events={[
|
||||||
// Event.PlaybackTrackChanged,
|
|
||||||
Event.PlaybackQueueEnded,
|
Event.PlaybackQueueEnded,
|
||||||
Event.PlaybackMetadataReceived,
|
Event.PlaybackMetadataReceived,
|
||||||
Event.RemoteDuck,
|
Event.RemoteDuck,
|
||||||
Event.RemoteNext,
|
Event.RemoteNext,
|
||||||
Event.RemotePrevious,
|
Event.RemotePrevious,
|
||||||
Event.RemoteStop,
|
Event.RemoteStop,
|
||||||
],
|
]}
|
||||||
event => {
|
update={update}
|
||||||
if (event.type === Event.PlaybackQueueEnded && 'track' in event) {
|
/>
|
||||||
setCurrentTrack(undefined)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
update()
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (appState === 'active') {
|
|
||||||
update()
|
|
||||||
}
|
|
||||||
}, [appState, update])
|
|
||||||
|
|
||||||
return <></>
|
|
||||||
}
|
|
||||||
|
|
||||||
const CurrentQueueName = () => {
|
|
||||||
const setCurrentQueueName = useUpdateAtom(currentQueueNameAtom)
|
|
||||||
const appState = useAppState()
|
|
||||||
|
|
||||||
const update = useCallback(async () => {
|
|
||||||
const queue = await TrackPlayer.getQueue()
|
|
||||||
|
|
||||||
if (queue !== null && queue.length > 0) {
|
|
||||||
setCurrentQueueName(queue[0].queueName)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
setCurrentQueueName(undefined)
|
|
||||||
}, [setCurrentQueueName])
|
|
||||||
|
|
||||||
useTrackPlayerEvents(
|
|
||||||
[Event.PlaybackState, Event.PlaybackQueueEnded, Event.PlaybackMetadataReceived, Event.RemoteDuck, Event.RemoteStop],
|
|
||||||
event => {
|
|
||||||
if (event.type === Event.PlaybackState) {
|
|
||||||
if (event.state === State.Stopped || event.state === State.None) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
update()
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (appState === 'active') {
|
|
||||||
update()
|
|
||||||
}
|
|
||||||
}, [appState, update])
|
|
||||||
|
|
||||||
return <></>
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const PlayerState = () => {
|
const PlayerState = () => {
|
||||||
const setPlayerState = useUpdateAtom(playerStateAtom)
|
const setPlayerState = useUpdateAtom(playerStateAtom)
|
||||||
const appState = useAppState()
|
|
||||||
|
|
||||||
const update = useCallback(
|
const update = async (payload?: Payload) => {
|
||||||
async (state?: State) => {
|
setPlayerState(payload?.state || (await TrackPlayer.getState()))
|
||||||
setPlayerState(state || (await TrackPlayer.getState()))
|
|
||||||
},
|
|
||||||
[setPlayerState],
|
|
||||||
)
|
|
||||||
|
|
||||||
useTrackPlayerEvents([Event.PlaybackState], event => {
|
|
||||||
update(event.state)
|
|
||||||
})
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (appState === 'active') {
|
|
||||||
update()
|
|
||||||
}
|
}
|
||||||
}, [appState, update])
|
|
||||||
|
|
||||||
return <></>
|
return <TrackPlayerEventResponder events={[Event.PlaybackState]} update={update} />
|
||||||
|
}
|
||||||
|
|
||||||
|
const QueueState = () => {
|
||||||
|
const setQueue = useUpdateAtom(queueWriteAtom)
|
||||||
|
|
||||||
|
const update = async (payload?: Payload) => {
|
||||||
|
if (payload) {
|
||||||
|
setQueue([])
|
||||||
|
return
|
||||||
|
}
|
||||||
|
setQueue(await getQueue())
|
||||||
|
}
|
||||||
|
|
||||||
|
return <TrackPlayerEventResponder events={[Event.RemoteStop]} update={update} />
|
||||||
}
|
}
|
||||||
|
|
||||||
const Debug = () => {
|
const Debug = () => {
|
||||||
const value = useAtomValue(currentQueueNameAtom)
|
const value = useAtomValue(queueWriteAtom)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
console.log(value)
|
console.log(value.map(t => t.title))
|
||||||
}, [value])
|
}, [value])
|
||||||
|
|
||||||
return <></>
|
return <></>
|
||||||
@ -125,8 +103,8 @@ const Debug = () => {
|
|||||||
const TrackPlayerState = () => (
|
const TrackPlayerState = () => (
|
||||||
<View>
|
<View>
|
||||||
<CurrentTrackState />
|
<CurrentTrackState />
|
||||||
<CurrentQueueName />
|
|
||||||
<PlayerState />
|
<PlayerState />
|
||||||
|
<QueueState />
|
||||||
<Debug />
|
<Debug />
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
|
|||||||
@ -10,9 +10,8 @@ import {
|
|||||||
useWindowDimensions,
|
useWindowDimensions,
|
||||||
View,
|
View,
|
||||||
} from 'react-native'
|
} from 'react-native'
|
||||||
import { useSetQueue } from '../../hooks/trackplayer'
|
|
||||||
import { albumAtomFamily } from '../../state/music'
|
import { albumAtomFamily } from '../../state/music'
|
||||||
import { currentTrackAtom } from '../../state/trackplayer'
|
import { currentTrackAtom, useSetQueue } from '../../state/trackplayer'
|
||||||
import colors from '../../styles/colors'
|
import colors from '../../styles/colors'
|
||||||
import text from '../../styles/text'
|
import text from '../../styles/text'
|
||||||
import AlbumArt from './AlbumArt'
|
import AlbumArt from './AlbumArt'
|
||||||
|
|||||||
55
src/components/common/PressableImage.tsx
Normal file
55
src/components/common/PressableImage.tsx
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
import React, { useEffect, useState } from 'react'
|
||||||
|
import { GestureResponderEvent, LayoutRectangle, Pressable, ViewStyle } from 'react-native'
|
||||||
|
import FastImage, { Source } from 'react-native-fast-image'
|
||||||
|
|
||||||
|
const PressableImage: React.FC<{
|
||||||
|
source: Source | number
|
||||||
|
onPress?: (event: GestureResponderEvent) => void
|
||||||
|
style?: ViewStyle
|
||||||
|
tintColor?: string
|
||||||
|
disabled?: boolean
|
||||||
|
}> = ({ source, onPress, style, tintColor, disabled }) => {
|
||||||
|
const [opacity, setOpacity] = useState(1)
|
||||||
|
const [dimensions, setDimensions] = useState<LayoutRectangle | undefined>(undefined)
|
||||||
|
|
||||||
|
disabled = disabled === undefined ? false : disabled
|
||||||
|
style = {
|
||||||
|
...(style || {}),
|
||||||
|
opacity,
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
disabled ? setOpacity(0.3) : setOpacity(1)
|
||||||
|
}, [disabled])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Pressable
|
||||||
|
style={style}
|
||||||
|
onPress={onPress}
|
||||||
|
disabled={disabled}
|
||||||
|
onPressIn={() => {
|
||||||
|
if (!disabled) {
|
||||||
|
setOpacity(0.4)
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onPressOut={() => {
|
||||||
|
if (!disabled) {
|
||||||
|
setOpacity(1)
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onLayout={event => setDimensions(event.nativeEvent.layout)}>
|
||||||
|
<FastImage
|
||||||
|
style={{
|
||||||
|
display: dimensions ? 'flex' : 'none',
|
||||||
|
height: dimensions?.height,
|
||||||
|
width: dimensions?.width,
|
||||||
|
}}
|
||||||
|
source={source}
|
||||||
|
tintColor={tintColor || 'white'}
|
||||||
|
resizeMode={FastImage.resizeMode.contain}
|
||||||
|
/>
|
||||||
|
</Pressable>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default PressableImage
|
||||||
@ -1,37 +1,164 @@
|
|||||||
import { atom } from 'jotai'
|
import { atom } from 'jotai'
|
||||||
import { State, Track } from 'react-native-track-player'
|
import TrackPlayer, { State, Track } from 'react-native-track-player'
|
||||||
import equal from 'fast-deep-equal'
|
import equal from 'fast-deep-equal'
|
||||||
|
import { useUpdateAtom } from 'jotai/utils'
|
||||||
|
import { Song } from '../models/music'
|
||||||
|
|
||||||
type OptionalTrack = Track | undefined
|
type TrackExt = Track & {
|
||||||
|
id: string
|
||||||
|
queueName: string
|
||||||
|
}
|
||||||
|
|
||||||
const currentTrack = atom<OptionalTrack>(undefined)
|
type OptionalTrackExt = TrackExt | undefined
|
||||||
export const currentTrackAtom = atom<OptionalTrack, OptionalTrack>(
|
|
||||||
get => get(currentTrack),
|
|
||||||
(get, set, value) => {
|
|
||||||
if (!equal(get(currentTrack), value)) {
|
|
||||||
set(currentTrack, value)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
type OptionalString = string | undefined
|
|
||||||
|
|
||||||
const currentQueueName = atom<OptionalString>(undefined)
|
|
||||||
export const currentQueueNameAtom = atom<OptionalString, OptionalString>(
|
|
||||||
get => get(currentQueueName),
|
|
||||||
(get, set, value) => {
|
|
||||||
if (get(currentQueueName) !== value) {
|
|
||||||
set(currentQueueName, value)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
const playerState = atom<State>(State.None)
|
const playerState = atom<State>(State.None)
|
||||||
export const playerStateAtom = atom<State, State>(
|
export const playerStateAtom = atom<State, State>(
|
||||||
get => get(playerState),
|
get => get(playerState),
|
||||||
(get, set, value) => {
|
(get, set, update) => {
|
||||||
if (get(playerState) !== value) {
|
if (get(playerState) !== update) {
|
||||||
set(playerState, value)
|
set(playerState, update)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const currentTrack = atom<OptionalTrackExt>(undefined)
|
||||||
|
export const currentTrackAtom = atom<OptionalTrackExt, OptionalTrackExt>(
|
||||||
|
get => get(currentTrack),
|
||||||
|
(get, set, update) => {
|
||||||
|
if (!equal(get(currentTrack), update)) {
|
||||||
|
set(currentTrack, update)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
const _queue = atom<TrackExt[]>([])
|
||||||
|
export const queueAtom = atom<TrackExt[]>(get => get(_queue))
|
||||||
|
export const queueWriteAtom = atom<TrackExt[], TrackExt[]>(
|
||||||
|
get => get(_queue),
|
||||||
|
(get, set, update) => {
|
||||||
|
if (get(_queue) !== update) {
|
||||||
|
set(_queue, update)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
export const queueNameAtom = atom<string | undefined>(get => {
|
||||||
|
const queue = get(_queue)
|
||||||
|
if (queue.length > 0) {
|
||||||
|
return queue[0].queueName
|
||||||
|
}
|
||||||
|
return undefined
|
||||||
|
})
|
||||||
|
|
||||||
|
export const getQueue = async (): Promise<TrackExt[]> => {
|
||||||
|
return ((await TrackPlayer.getQueue()) as TrackExt[]) || []
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getTrack = async (index: number): Promise<TrackExt> => {
|
||||||
|
return ((await TrackPlayer.getTrack(index)) as TrackExt) || undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
export const usePrevious = () => {
|
||||||
|
const setCurrentTrack = useUpdateAtom(currentTrackAtom)
|
||||||
|
|
||||||
|
return async () => {
|
||||||
|
try {
|
||||||
|
const [current, queue] = await Promise.all([await TrackPlayer.getCurrentTrack(), await getQueue()])
|
||||||
|
if (current > 0) {
|
||||||
|
await TrackPlayer.skipToPrevious()
|
||||||
|
setCurrentTrack(queue[current - 1])
|
||||||
|
} else {
|
||||||
|
await TrackPlayer.seekTo(0)
|
||||||
|
}
|
||||||
|
await TrackPlayer.play()
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useNext = () => {
|
||||||
|
const setCurrentTrack = useUpdateAtom(currentTrackAtom)
|
||||||
|
|
||||||
|
return async () => {
|
||||||
|
try {
|
||||||
|
const [current, queue] = await Promise.all([await TrackPlayer.getCurrentTrack(), await getQueue()])
|
||||||
|
if (current >= queue.length - 1) {
|
||||||
|
await TrackPlayer.skip(0)
|
||||||
|
await TrackPlayer.pause()
|
||||||
|
setCurrentTrack(queue[0])
|
||||||
|
} else {
|
||||||
|
await TrackPlayer.skipToNext()
|
||||||
|
setCurrentTrack(queue[current + 1])
|
||||||
|
await TrackPlayer.play()
|
||||||
|
}
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useAdd = () => {
|
||||||
|
const setQueue = useUpdateAtom(queueWriteAtom)
|
||||||
|
const setCurrentTrack = useUpdateAtom(currentTrackAtom)
|
||||||
|
|
||||||
|
return async (tracks: TrackExt | TrackExt[], insertBeforeindex?: number) => {
|
||||||
|
await TrackPlayer.add(tracks, insertBeforeindex)
|
||||||
|
|
||||||
|
const queue = await getQueue()
|
||||||
|
setQueue(queue)
|
||||||
|
setCurrentTrack(queue.length > 0 ? queue[await TrackPlayer.getCurrentTrack()] : undefined)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useReset = () => {
|
||||||
|
const setQueue = useUpdateAtom(queueWriteAtom)
|
||||||
|
const setCurrentTrack = useUpdateAtom(currentTrackAtom)
|
||||||
|
|
||||||
|
return async () => {
|
||||||
|
await TrackPlayer.reset()
|
||||||
|
setQueue([])
|
||||||
|
setCurrentTrack(undefined)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useSetQueue = () => {
|
||||||
|
const setCurrentTrack = useUpdateAtom(currentTrackAtom)
|
||||||
|
const setQueue = useUpdateAtom(queueWriteAtom)
|
||||||
|
|
||||||
|
return async (songs: Song[], name: string, playId?: string) => {
|
||||||
|
await TrackPlayer.reset()
|
||||||
|
const tracks = songs.map(s => mapSongToTrack(s, name))
|
||||||
|
|
||||||
|
if (playId) {
|
||||||
|
setCurrentTrack(tracks.find(t => t.id === playId))
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!playId) {
|
||||||
|
await TrackPlayer.add(tracks)
|
||||||
|
} else if (playId === tracks[0].id) {
|
||||||
|
await TrackPlayer.add(tracks)
|
||||||
|
await TrackPlayer.play()
|
||||||
|
} else {
|
||||||
|
const playIndex = tracks.findIndex(t => t.id === playId)
|
||||||
|
const tracks1 = tracks.slice(0, playIndex)
|
||||||
|
const tracks2 = tracks.slice(playIndex)
|
||||||
|
|
||||||
|
await TrackPlayer.add(tracks2)
|
||||||
|
await TrackPlayer.play()
|
||||||
|
|
||||||
|
await TrackPlayer.add(tracks1, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
setQueue(await getQueue())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapSongToTrack(song: Song, queueName: string): TrackExt {
|
||||||
|
return {
|
||||||
|
id: song.id,
|
||||||
|
queueName,
|
||||||
|
title: song.title,
|
||||||
|
artist: song.artist || 'Unknown Artist',
|
||||||
|
url: song.streamUri,
|
||||||
|
artwork: song.coverArtUri,
|
||||||
|
artworkThumb: song.coverArtThumbUri,
|
||||||
|
duration: song.duration,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user