basic queue with skipTo support

This commit is contained in:
austinried 2021-08-10 11:15:57 +09:00
parent 075286e939
commit df3e913125
4 changed files with 124 additions and 50 deletions

View File

@ -47,6 +47,18 @@ export const useNext = () => {
}) })
} }
export const useSkipTo = () => {
return (track: number) =>
trackPlayerCommands.enqueue(async () => {
const queue = await getQueue()
if (track < 0 || track >= queue.length) {
return
}
await TrackPlayer.skip(track)
await TrackPlayer.play()
})
}
export const useToggleRepeat = () => { export const useToggleRepeat = () => {
const setRepeatMode = useStore(selectTrackPlayer.setRepeatMode) const setRepeatMode = useStore(selectTrackPlayer.setRepeatMode)
@ -209,10 +221,6 @@ export const useSetQueue = () => {
await TrackPlayer.add(tracks1, 0) await TrackPlayer.add(tracks1, 0)
} }
// setQueue(await getQueue())
// setCurrentTrackIdx(playTrack)
// setQueueName(name)
}) })
} }
@ -228,3 +236,16 @@ function mapSongToTrack(song: Song, coverArtUri: (coverArt?: string) => string |
duration: song.duration, duration: song.duration,
} }
} }
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,
}
}

View File

@ -1,10 +1,37 @@
import BottomTabNavigator from '@app/navigation/BottomTabNavigator' import BottomTabNavigator from '@app/navigation/BottomTabNavigator'
import NowPlayingQueue from '@app/screens/NowPlayingQueue'
import NowPlayingView from '@app/screens/NowPlayingView' import NowPlayingView from '@app/screens/NowPlayingView'
import colors from '@app/styles/colors' import colors from '@app/styles/colors'
import font from '@app/styles/font'
import { DarkTheme, NavigationContainer } from '@react-navigation/native' import { DarkTheme, NavigationContainer } from '@react-navigation/native'
import React from 'react' import React from 'react'
import { createNativeStackNavigator } from 'react-native-screens/native-stack' import { createNativeStackNavigator } from 'react-native-screens/native-stack'
const NowPlayingStack = createNativeStackNavigator()
const NowPlayingNavigator = () => (
<NowPlayingStack.Navigator>
<NowPlayingStack.Screen name="main" component={NowPlayingView} options={{ headerShown: false }} />
<NowPlayingStack.Screen
name="queue"
component={NowPlayingQueue}
options={{
title: 'Queue',
headerStyle: {
backgroundColor: colors.gradient.high,
},
headerTitleStyle: {
fontSize: 18,
fontFamily: font.semiBold,
color: colors.text.primary,
},
headerHideShadow: true,
headerTintColor: 'white',
}}
/>
</NowPlayingStack.Navigator>
)
const RootStack = createNativeStackNavigator() const RootStack = createNativeStackNavigator()
const theme = { ...DarkTheme } const theme = { ...DarkTheme }
@ -15,24 +42,14 @@ const RootNavigator = () => (
theme={theme} theme={theme}
linking={{ linking={{
prefixes: ['trackplayer'], prefixes: ['trackplayer'],
config: {
screens: {
main: {
path: ':/main',
},
'now-playing': {
path: ':/notification.click',
},
},
},
}}> }}>
<RootStack.Navigator <RootStack.Navigator
screenOptions={{ screenOptions={{
headerShown: false, headerShown: false,
}} }}
initialRouteName="main"> initialRouteName="top">
<RootStack.Screen name="main" component={BottomTabNavigator} /> <RootStack.Screen name="top" component={BottomTabNavigator} />
<RootStack.Screen name="now-playing" component={NowPlayingView} /> <RootStack.Screen name="now-playing" component={NowPlayingNavigator} />
</RootStack.Navigator> </RootStack.Navigator>
</NavigationContainer> </NavigationContainer>
) )

View File

@ -0,0 +1,48 @@
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 { useStore } from '@app/state/store'
import { selectTrackPlayer } from '@app/state/trackplayer'
import React from 'react'
import { StyleSheet, View } from 'react-native'
const NowPlayingQueue = React.memo<{}>(() => {
const queue = useStore(selectTrackPlayer.queue)
const skipTo = useSkipTo()
return (
<View style={styles.outerContainer}>
<GradientScrollView style={styles.container}>
<View style={styles.content}>
{queue.map(mapTrackExtToSong).map((song, i) => (
<ListItem
key={i}
item={song}
onPress={() => skipTo(i)}
showArt={true}
subtitle={`${song.artist}${song.album}`}
/>
))}
</View>
</GradientScrollView>
<NowPlayingBar />
</View>
)
})
const styles = StyleSheet.create({
outerContainer: {
flex: 1,
},
container: {
flex: 1,
},
content: {
alignItems: 'center',
paddingTop: 10,
paddingHorizontal: 20,
},
})
export default NowPlayingQueue

View File

@ -1,26 +1,26 @@
import CoverArt from '@app/components/CoverArt'
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 { useNext, usePause, usePlay, usePrevious, useToggleRepeat, useToggleShuffle } from '@app/hooks/trackplayer'
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 dimensions from '@app/styles/dimensions'
import font from '@app/styles/font'
import formatDuration from '@app/util/formatDuration'
import { useNavigation } from '@react-navigation/native'
import React, { useCallback, useEffect } from 'react' import React, { useCallback, useEffect } from 'react'
import { BackHandler, StatusBar, StyleSheet, Text, View } from 'react-native' import { StatusBar, StyleSheet, Text, View } from 'react-native'
import { NativeStackScreenProps } from 'react-native-screens/native-stack'
import { RepeatMode, State } from 'react-native-track-player' import { RepeatMode, State } from 'react-native-track-player'
import IconFA from 'react-native-vector-icons/FontAwesome' import IconFA from 'react-native-vector-icons/FontAwesome'
import IconFA5 from 'react-native-vector-icons/FontAwesome5' import IconFA5 from 'react-native-vector-icons/FontAwesome5'
import Icon from 'react-native-vector-icons/Ionicons' import Icon from 'react-native-vector-icons/Ionicons'
import IconMatCom from 'react-native-vector-icons/MaterialCommunityIcons' import IconMatCom from 'react-native-vector-icons/MaterialCommunityIcons'
import IconMat from 'react-native-vector-icons/MaterialIcons' import IconMat from 'react-native-vector-icons/MaterialIcons'
import colors from '@app/styles/colors'
import font from '@app/styles/font'
import formatDuration from '@app/util/formatDuration'
import CoverArt from '@app/components/CoverArt'
import ImageGradientBackground from '@app/components/ImageGradientBackground'
import PressableOpacity from '@app/components/PressableOpacity'
import dimensions from '@app/styles/dimensions'
import { NativeStackScreenProps } from 'react-native-screens/native-stack'
import { useFocusEffect } from '@react-navigation/native'
import { useStore } from '@app/state/store'
import { selectTrackPlayer } from '@app/state/trackplayer'
import { useNext, usePause, usePlay, usePrevious, useToggleRepeat, useToggleShuffle } from '@app/hooks/trackplayer'
import Star from '@app/components/Star'
import { useStarred } from '@app/hooks/music'
import { selectMusic } from '@app/state/music'
const NowPlayingHeader = React.memo<{ const NowPlayingHeader = React.memo<{
backHandler: () => void backHandler: () => void
@ -210,6 +210,7 @@ const PlayerControls = () => {
const toggleShuffle = useToggleShuffle() const toggleShuffle = useToggleShuffle()
const repeatMode = useStore(selectTrackPlayer.repeatMode) const repeatMode = useStore(selectTrackPlayer.repeatMode)
const toggleRepeat = useToggleRepeat() const toggleRepeat = useToggleRepeat()
const navigation = useNavigation()
let playPauseIcon: string let playPauseIcon: string
let playPauseAction: undefined | (() => void) let playPauseAction: undefined | (() => void)
@ -262,7 +263,7 @@ const PlayerControls = () => {
<PressableOpacity onPress={undefined} disabled={disabled}> <PressableOpacity onPress={undefined} disabled={disabled}>
<IconMatCom name="cast-audio" size={20} color="white" /> <IconMatCom name="cast-audio" size={20} color="white" />
</PressableOpacity> </PressableOpacity>
<PressableOpacity onPress={undefined} disabled={disabled}> <PressableOpacity onPress={() => navigation.navigate('queue')} disabled={disabled}>
<IconMatCom name="playlist-play" size={24} color="white" /> <IconMatCom name="playlist-play" size={24} color="white" />
</PressableOpacity> </PressableOpacity>
</View> </View>
@ -306,21 +307,16 @@ const controlsStyles = StyleSheet.create({
}) })
type RootStackParamList = { type RootStackParamList = {
top: undefined
main: undefined main: undefined
'now-playing': undefined
} }
type NowPlayingProps = NativeStackScreenProps<RootStackParamList, 'now-playing'> type NowPlayingProps = NativeStackScreenProps<RootStackParamList, 'main'>
const NowPlayingView: React.FC<NowPlayingProps> = ({ navigation }) => { const NowPlayingView: React.FC<NowPlayingProps> = ({ navigation }) => {
const track = useStore(selectTrackPlayer.currentTrack) const track = useStore(selectTrackPlayer.currentTrack)
const back = useCallback(() => { const back = useCallback(() => {
if (navigation.canGoBack()) { navigation.navigate('top')
navigation.popToTop()
} else {
navigation.navigate('main')
}
return true
}, [navigation]) }, [navigation])
useEffect(() => { useEffect(() => {
@ -329,14 +325,6 @@ const NowPlayingView: React.FC<NowPlayingProps> = ({ navigation }) => {
} }
}) })
useFocusEffect(
useCallback(() => {
BackHandler.addEventListener('hardwareBackPress', back)
return () => BackHandler.removeEventListener('hardwareBackPress', back)
}, [back]),
)
return ( return (
<View style={styles.container}> <View style={styles.container}>
<ImageGradientBackground imageUri={track?.artwork as string} imageKey={`${track?.album}${track?.artist}`} /> <ImageGradientBackground imageUri={track?.artwork as string} imageKey={`${track?.album}${track?.artist}`} />