subtracks/app/playbackservice.ts
austinried 081251061d
Library store refactor (#76)
* start of music store refactor

moving stuff into a state cache
better separate it from view logic

* added paginated list/album list

* reworked fetchAlbumList to remove ui state

refactored home screen to use new method
i broke playing songs somehow, JS thread goes into a loop

* don't reset parts manually, do it all at once

* fixed perf issue related to too many rerenders

rerenders were caused by strict equality check on object/array picks
switched artistInfo to new store
updated zustand and fixed deprecation warnings

* update typescript

and use workspace tsc version for vscode

* remove old artistInfo

* switched to new playlist w/songs

removed more unused stuff

* remove unused + (slightly) rework search

* refactor star

* use only original/large imges for covers/artist

fix view artist from context menu
add loading indicators to song list and artist views (show info we have right away)

* set starred/unstar assuming it works

and correct state on error

* reorg, remove old music slice files

* added back fix for song cover art

* sort artists by localCompare name

* update licenses

* fix now playing background grey bar

* update react-native-gesture-handler

for node-fetch security alert

* fix another gradient height grey bar issue

* update licenses again

* remove thumbnail cache

* rename to remove "Library" from methods

* Revert "remove thumbnail cache"

This reverts commit e0db4931f11bbf4cd8e73102d06505c6ae85f4a6.

* use ids for lists, pull state later

* Revert "use only original/large imges for covers/artist"

This reverts commit c9aea9065ce6ebe3c8b09c10dd74d4de153d76fd.

* deep equal ListItem props for now

this needs a bigger refactor

* use immer as middleware

* refactor api client to use string method

hoping to use this for requestKey/deduping next

* use thumbnails in list items

* Revert "refactor api client to use string method"

This reverts commit 234326135b7af96cb91b941e7ca515f45c632556.

* rename/cleanup

* store servers by id

* get rid of settings selectors

* renames for clarity

remove unused estimateContentLength setting

* remove trackplayer selectors

* fix migration for library filter settings

* fixed shuffle order reporting wrong track/queue

* removed the other selectors

* don't actually need es6/react for our state

* fix slow artist sort on star

localeCompare is too slow for large lists
2022-03-28 13:30:57 +09:00

153 lines
4.3 KiB
TypeScript

import { getCurrentTrack, getPlayerState, trackPlayerCommands } from '@app/state/trackplayer'
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(() => {
useStore.getState().resetTrackPlayerState()
})
}
const setPlayerState = (state: State) => {
unstable_batchedUpdates(() => {
useStore.getState().setPlayerState(state)
})
}
const setCurrentTrackIdx = (idx?: number) => {
unstable_batchedUpdates(() => {
useStore.getState().setCurrentTrackIdx(idx)
})
}
const setNetState = (netState: 'mobile' | 'wifi') => {
unstable_batchedUpdates(() => {
useStore.getState().setNetState(netState)
})
}
const rebuildQueue = () => {
unstable_batchedUpdates(() => {
useStore.getState().rebuildQueue(useStore.getState().playerState === State.Playing)
})
}
const setDuckPaused = (duckPaused: boolean) => {
unstable_batchedUpdates(() => {
useStore.getState().setDuckPaused(duckPaused)
})
}
let serviceCreated = false
const createService = async () => {
useStore.subscribe(
state => state.currentTrack?.id,
(currentTrackId?: string) => {
if (currentTrackId) {
useStore.getState().scrobbleTrack(currentTrackId)
}
},
)
NetInfo.fetch().then(state => {
setNetState(state.type === NetInfoStateType.cellular ? 'mobile' : 'wifi')
})
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)
})
TrackPlayer.addEventListener(Event.RemotePlay, () => trackPlayerCommands.enqueue(TrackPlayer.play))
TrackPlayer.addEventListener(Event.RemotePause, () => trackPlayerCommands.enqueue(TrackPlayer.pause))
TrackPlayer.addEventListener(Event.RemoteNext, () =>
trackPlayerCommands.enqueue(() => TrackPlayer.skipToNext().catch(() => {})),
)
TrackPlayer.addEventListener(Event.RemotePrevious, () =>
trackPlayerCommands.enqueue(() => TrackPlayer.skipToPrevious().catch(() => {})),
)
TrackPlayer.addEventListener(Event.RemoteDuck, data => {
if (data.permanent) {
trackPlayerCommands.enqueue(TrackPlayer.stop)
return
}
if (data.paused) {
let state = useStore.getState().playerState
if (state === State.Playing || state === State.Buffering || state === State.Connecting) {
trackPlayerCommands.enqueue(TrackPlayer.pause)
setDuckPaused(true)
}
} else if (useStore.getState().duckPaused) {
trackPlayerCommands.enqueue(TrackPlayer.play)
setDuckPaused(false)
}
})
TrackPlayer.addEventListener(Event.PlaybackState, () => {
trackPlayerCommands.enqueue(async () => {
setPlayerState(await getPlayerState())
})
})
TrackPlayer.addEventListener(Event.PlaybackTrackChanged, () => {
useStore.getState().setProgress({ position: 0, duration: 0, buffered: 0 })
trackPlayerCommands.enqueue(async () => {
setCurrentTrackIdx(await getCurrentTrack())
})
})
TrackPlayer.addEventListener(Event.PlaybackQueueEnded, event => {
const { position, track } = event
// bogus event that fires when queue is changed
if (!track && position === 0) {
return
}
trackPlayerCommands.enqueue(async () => {
await TrackPlayer.stop()
await TrackPlayer.skip(0)
})
})
TrackPlayer.addEventListener(Event.PlaybackMetadataReceived, () => {
setCurrentTrackIdx(useStore.getState().currentTrackIdx)
})
TrackPlayer.addEventListener(Event.RemoteSeek, data => {
trackPlayerCommands.enqueue(async () => {
await TrackPlayer.seekTo(data.position)
})
})
TrackPlayer.addEventListener(Event.PlaybackError, data => {
const { code, message } = data as Record<string, string>
// fix for ExoPlayer aborting playback while esimating content length
if (code === 'playback-source' && message.includes('416')) {
rebuildQueue()
}
})
}
module.exports = async function () {
if (!serviceCreated) {
createService()
serviceCreated = true
}
}