mirror of
https://github.com/austinried/subtracks.git
synced 2025-12-27 00:59:28 +01:00
rebuild queue with new urls on net state change
This commit is contained in:
parent
88d0c6089e
commit
44617740fd
@ -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;
|
||||
|
||||
@ -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'
|
||||
|
||||
@ -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
5
app/models/error.ts
Normal file
@ -0,0 +1,5 @@
|
||||
export class NoClientError extends Error {
|
||||
constructor() {
|
||||
super('no client in state')
|
||||
}
|
||||
}
|
||||
@ -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)
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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),
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user