rebuild queue with new urls on net state change

This commit is contained in:
austinried 2021-08-17 10:58:11 +09:00
parent 88d0c6089e
commit 44617740fd
11 changed files with 124 additions and 21 deletions

View File

@ -4,6 +4,7 @@ import android.app.Application;
import android.content.Context;
import com.facebook.react.PackageList;
import com.facebook.react.ReactApplication;
import com.reactnativecommunity.netinfo.NetInfoPackage;
import com.oblador.vectoricons.VectorIconsPackage;
import com.rnfs.RNFSPackage;
import com.facebook.react.ReactInstanceManager;

View File

@ -1,4 +1,6 @@
rootProject.name = 'SubSonify'
include ':@react-native-community_netinfo'
project(':@react-native-community_netinfo').projectDir = new File(rootProject.projectDir, '../node_modules/@react-native-community/netinfo/android')
include ':react-native-vector-icons'
project(':react-native-vector-icons').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-vector-icons/android')
include ':react-native-fs'

View File

@ -187,11 +187,12 @@ export const useSetQueue = () => {
const setCurrentTrackIdx = useStore(selectTrackPlayer.setCurrentTrackIdx)
const setQueue = useStore(selectTrackPlayer.setQueue)
const setShuffleOrder = useStore(selectTrackPlayer.setShuffleOrder)
const setQueueName = useStore(selectTrackPlayer.setName)
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[],
@ -222,6 +223,14 @@ export const useSetQueue = () => {
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)

5
app/models/error.ts Normal file
View File

@ -0,0 +1,5 @@
export class NoClientError extends Error {
constructor() {
super('no client in state')
}
}

View File

@ -2,6 +2,7 @@ import { getCurrentTrack, getPlayerState, TrackExt, trackPlayerCommands } from '
import TrackPlayer, { Event, State } from 'react-native-track-player'
import { useStore } from './state/store'
import { unstable_batchedUpdates } from 'react-native'
import NetInfo, { NetInfoStateType } from '@react-native-community/netinfo'
const reset = () => {
unstable_batchedUpdates(() => {
@ -21,6 +22,12 @@ const setCurrentTrackIdx = (idx?: number) => {
})
}
const setNetState = (netState: 'mobile' | 'wifi') => {
unstable_batchedUpdates(() => {
useStore.getState().setNetState(netState)
})
}
let serviceCreated = false
const createService = async () => {
useStore.subscribe(
@ -33,6 +40,14 @@ const createService = async () => {
(prev, next) => prev?.id === next?.id,
)
NetInfo.addEventListener(state => {
const currentType = useStore.getState().netState
const newType = state.type === NetInfoStateType.cellular ? 'mobile' : 'wifi'
if (currentType !== newType) {
setNetState(newType)
}
})
TrackPlayer.addEventListener(Event.RemoteStop, () => {
reset()
trackPlayerCommands.enqueue(TrackPlayer.destroy)

View File

@ -49,7 +49,7 @@ function getContextName(type?: QueueContextType) {
const NowPlayingHeader = React.memo<{
track?: TrackExt
}>(({ track }) => {
const queueName = useStore(selectTrackPlayer.name)
const queueName = useStore(selectTrackPlayer.queueName)
const queueContextType = useStore(selectTrackPlayer.queueContextType)
if (!track) {

View File

@ -35,19 +35,8 @@ export type MusicMapSlice = {
mapPlaylistWithSongs: (playlist: PlaylistWithSongsElement) => Promise<PlaylistWithSongs>
}
class NoClientError extends Error {
constructor() {
super('no client in state')
}
}
export const createMusicMapSlice = (set: SetState<Store>, get: GetState<Store>): MusicMapSlice => ({
mapChildToSong: async child => {
const client = get().client
if (!client) {
throw new NoClientError()
}
return {
itemType: 'song',
id: child.id,
@ -60,7 +49,7 @@ export const createMusicMapSlice = (set: SetState<Store>, get: GetState<Store>):
duration: child.duration,
starred: child.starred,
coverArt: await get().getAlbumCoverArt(child.albumId),
streamUri: client.streamUri({ id: child.id }),
streamUri: get().buildStreamUri(child.id),
}
},

View File

@ -1,5 +1,7 @@
import { NoClientError } from '@app/models/error'
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'
@ -20,8 +22,8 @@ export type Progress = {
export type QueueContextType = 'album' | 'playlist' | 'song' | 'artist'
export type TrackPlayerSlice = {
name?: string
setName: (name?: string) => void
queueName?: string
setQueueName: (name?: string) => void
queueContextType?: QueueContextType
setQueueContextType: (queueContextType?: QueueContextType) => void
@ -50,12 +52,17 @@ export type TrackPlayerSlice = {
scrobbleTrack: (id: string) => Promise<void>
netState: 'mobile' | 'wifi'
setNetState: (netState: 'mobile' | 'wifi') => Promise<void>
buildStreamUri: (id: string) => string
reset: () => void
}
export const selectTrackPlayer = {
name: (store: TrackPlayerSlice) => store.name,
setName: (store: TrackPlayerSlice) => store.setName,
queueName: (store: TrackPlayerSlice) => store.queueName,
setQueueName: (store: TrackPlayerSlice) => store.setQueueName,
queueContextType: (store: TrackPlayerSlice) => store.queueContextType,
setQueueContextType: (store: TrackPlayerSlice) => store.setQueueContextType,
@ -85,14 +92,17 @@ export const selectTrackPlayer = {
scrobbleTrack: (store: TrackPlayerSlice) => store.scrobbleTrack,
setNetState: (store: TrackPlayerSlice) => store.setNetState,
buildStreamUri: (store: TrackPlayerSlice) => store.buildStreamUri,
reset: (store: TrackPlayerSlice) => store.reset,
}
export const trackPlayerCommands = new PromiseQueue(1)
export const createTrackPlayerSlice = (set: SetState<Store>, get: GetState<Store>): TrackPlayerSlice => ({
name: undefined,
setName: name => set({ name }),
queueName: undefined,
setQueueName: name => set({ queueName: name }),
queueContextType: undefined,
setQueueContextType: queueContextType => set({ queueContextType }),
@ -141,9 +151,73 @@ export const createTrackPlayerSlice = (set: SetState<Store>, get: GetState<Store
} catch {}
},
netState: 'mobile',
setNetState: async netState => {
if (netState === get().netState) {
return
}
set({ netState })
ToastAndroid.show('switched netState to ' + netState, ToastAndroid.SHORT)
await trackPlayerCommands.enqueue(async () => {
const queue = await getQueue()
if (!queue.length) {
return
}
const currentTrack = await getCurrentTrack()
const state = await getPlayerState()
const position = (await TrackPlayer.getPosition()) || 0
const queueName = get().queueName
const queueContextId = get().queueContextId
const queueContextType = get().queueContextType
await TrackPlayer.reset()
try {
for (const track of queue) {
track.url = get().buildStreamUri(track.id)
}
} catch {
return
}
set({
queue,
queueName,
queueContextId,
queueContextType,
})
get().setCurrentTrackIdx(currentTrack)
await TrackPlayer.add(queue)
if (currentTrack) {
await TrackPlayer.skip(currentTrack)
}
await TrackPlayer.seekTo(position)
if (state === State.Playing) {
await TrackPlayer.play()
}
})
},
buildStreamUri: id => {
const client = get().client
if (!client) {
throw new NoClientError()
}
return client.streamUri({
id,
estimateContentLength: get().settings.estimateContentLength,
maxBitRate: get().netState === 'mobile' ? get().settings.maxBitrateMobile : get().settings.maxBitrateWifi,
})
},
reset: () => {
set({
name: undefined,
queueName: undefined,
queueContextType: undefined,
queueContextId: undefined,
shuffleOrder: undefined,

View File

@ -16,6 +16,8 @@ target 'SubSonify' do
pod 'RNVectorIcons', :path => '../node_modules/react-native-vector-icons'
pod 'react-native-netinfo', :path => '../node_modules/@react-native-community/netinfo'
target 'SubSonifyTests' do
inherit! :complete
# Pods for testing

View File

@ -13,6 +13,7 @@
"@react-native-async-storage/async-storage": "^1.15.5",
"@react-native-community/hooks": "^2.6.0",
"@react-native-community/masked-view": "^0.1.11",
"@react-native-community/netinfo": "^6.0.0",
"@react-native-community/slider": "^3.0.3",
"@react-navigation/bottom-tabs": "^5.11.11",
"@react-navigation/material-top-tabs": "^5.3.15",

View File

@ -1118,6 +1118,11 @@
resolved "https://registry.yarnpkg.com/@react-native-community/masked-view/-/masked-view-0.1.11.tgz#2f4c6e10bee0786abff4604e39a37ded6f3980ce"
integrity sha512-rQfMIGSR/1r/SyN87+VD8xHHzDYeHaJq6elOSCAD+0iLagXkSI2pfA0LmSXP21uw5i3em7GkkRjfJ8wpqWXZNw==
"@react-native-community/netinfo@^6.0.0":
version "6.0.0"
resolved "https://registry.yarnpkg.com/@react-native-community/netinfo/-/netinfo-6.0.0.tgz#2a4d7190b508dd0c2293656c9c1aa068f6f60a71"
integrity sha512-Z9M8VGcF2IZVOo2x+oUStvpCW/8HjIRi4+iQCu5n+PhC7OqCQX58KYAzdBr///alIfRXiu6oMb+lK+rXQH1FvQ==
"@react-native-community/slider@^3.0.3":
version "3.0.3"
resolved "https://registry.yarnpkg.com/@react-native-community/slider/-/slider-3.0.3.tgz#830167fd757ba70ac638747ba3169b2dbae60330"