impl scrobble & scrobble setting

works even in background thanks zustand
This commit is contained in:
austinried 2021-08-04 17:59:43 +09:00
parent 706e57aa77
commit efc7e5c799
7 changed files with 106 additions and 13 deletions

View File

@ -4,6 +4,7 @@ export interface Server {
username: string
token: string
salt: string
scrobble: boolean
}
export interface AppSettings {

View File

@ -1,8 +1,36 @@
import { getCurrentTrack, getPlayerState, trackPlayerCommands } from '@app/state/trackplayer'
import TrackPlayer, { Event } from 'react-native-track-player'
import { getCurrentTrack, getPlayerState, TrackExt, 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'
const reset = () => {
unstable_batchedUpdates(() => {
useStore.getState().reset()
})
}
const setPlayerState = (state: State) => {
unstable_batchedUpdates(() => {
useStore.getState().setPlayerState(state)
})
}
const setCurrentTrackIdx = (idx?: number) => {
unstable_batchedUpdates(() => {
useStore.getState().setCurrentTrackIdx(idx)
})
}
module.exports = async function () {
const unsubCurrentTrack = useStore.subscribe(
(currentTrack?: TrackExt) => {
if (currentTrack) {
useStore.getState().scrobbleTrack(currentTrack.id)
}
},
state => state.currentTrack,
)
TrackPlayer.addEventListener(Event.RemotePlay, () => trackPlayerCommands.enqueue(TrackPlayer.play))
TrackPlayer.addEventListener(Event.RemotePause, () => trackPlayerCommands.enqueue(TrackPlayer.pause))
@ -27,30 +55,31 @@ module.exports = async function () {
})
TrackPlayer.addEventListener(Event.RemoteStop, () => {
useStore.getState().reset()
unsubCurrentTrack()
reset()
trackPlayerCommands.enqueue(TrackPlayer.destroy)
})
TrackPlayer.addEventListener(Event.PlaybackState, () => {
trackPlayerCommands.enqueue(async () => {
useStore.getState().setPlayerState(await getPlayerState())
setPlayerState(await getPlayerState())
})
})
TrackPlayer.addEventListener(Event.PlaybackTrackChanged, () => {
useStore.getState().setProgress({ position: 0, duration: 0, buffered: 0 })
trackPlayerCommands.enqueue(async () => {
useStore.getState().setCurrentTrackIdx(await getCurrentTrack())
setCurrentTrackIdx(await getCurrentTrack())
})
})
TrackPlayer.addEventListener(Event.PlaybackQueueEnded, () => {
trackPlayerCommands.enqueue(async () => {
useStore.getState().setCurrentTrackIdx(await getCurrentTrack())
setCurrentTrackIdx(await getCurrentTrack())
})
})
TrackPlayer.addEventListener(Event.PlaybackMetadataReceived, () => {
useStore.getState().setCurrentTrackIdx(useStore.getState().currentTrackIdx)
setCurrentTrackIdx(useStore.getState().currentTrackIdx)
})
}

View File

@ -32,6 +32,7 @@ const ServerView: React.FC<{
const [address, setAddress] = useState(server?.address || '')
const [username, setUsername] = useState(server?.username || '')
const [password, setPassword] = useState(server?.token ? 'password' : '')
const [scrobble, setScrobble] = useState(server?.scrobble || false)
const validate = useCallback(() => {
return !!address && !!username && !!password
@ -49,7 +50,7 @@ const ServerView: React.FC<{
}
}, [navigation])
const save = () => {
const save = useCallback(() => {
if (!validate()) {
return
}
@ -68,6 +69,7 @@ const ServerView: React.FC<{
username,
salt,
token,
scrobble,
}
if (server) {
@ -87,7 +89,20 @@ const ServerView: React.FC<{
}
exit()
}
}, [
activeServer,
address,
exit,
id,
password,
scrobble,
server,
servers,
setActiveServer,
setServers,
username,
validate,
])
const remove = useCallback(() => {
if (!canRemove()) {
@ -140,14 +155,17 @@ const ServerView: React.FC<{
value={password}
onChangeText={setPassword}
/>
<SettingsItem title="Scrobble" subtitle="Don't scrobble play history">
<SettingsItem
title="Scrobble plays"
subtitle={scrobble ? 'Scrobble play history' : "Don't scrobble play history"}>
<Switch
trackColor={{
false: colors.accentLow,
true: colors.accent,
}}
thumbColor={colors.text.primary}
value={false}
value={scrobble}
onValueChange={setScrobble}
/>
</SettingsItem>
<Button

View File

@ -9,6 +9,7 @@ export type SettingsSlice = {
client?: SubsonicApiClient
createClient: (id?: string) => void
setActiveServer: (id?: string) => void
getActiveServer: () => Server | undefined
setServers: (servers: Server[]) => void
}
@ -25,7 +26,7 @@ export const createSettingsSlice = (set: SetState<Store>, get: GetState<Store>):
return
}
const server = get().settings.servers.find(s => s.id === id)
const server = get().getActiveServer()
if (!server) {
set({ client: undefined })
return
@ -52,10 +53,15 @@ export const createSettingsSlice = (set: SetState<Store>, get: GetState<Store>):
}),
)
},
getActiveServer: () => get().settings.servers.find(s => s.id === get().settings.activeServer),
setServers: servers =>
set(
produce<SettingsSlice>(state => {
state.settings.servers = servers
const activeServer = servers.find(s => s.id === state.settings.activeServer)
if (activeServer) {
state.client = new SubsonicApiClient(activeServer)
}
}),
),
})

View File

@ -35,6 +35,8 @@ export type TrackPlayerSlice = {
progress: Progress
setProgress: (progress: Progress) => void
scrobbleTrack: (id: string) => Promise<void>
reset: () => void
}
@ -59,12 +61,14 @@ export const selectTrackPlayer = {
progress: (store: TrackPlayerSlice) => store.progress,
setProgress: (store: TrackPlayerSlice) => store.setProgress,
scrobbleTrack: (store: TrackPlayerSlice) => store.scrobbleTrack,
reset: (store: TrackPlayerSlice) => store.reset,
}
export const trackPlayerCommands = new PromiseQueue(1)
export const createTrackPlayerSlice = (set: SetState<Store>, _get: GetState<Store>): TrackPlayerSlice => ({
export const createTrackPlayerSlice = (set: SetState<Store>, get: GetState<Store>): TrackPlayerSlice => ({
name: undefined,
setName: name => set({ name }),
@ -91,6 +95,21 @@ export const createTrackPlayerSlice = (set: SetState<Store>, _get: GetState<Stor
progress: { position: 0, duration: 0, buffered: 0 },
setProgress: progress => set({ progress }),
scrobbleTrack: async id => {
const client = get().client
if (!client) {
return
}
if (!get().getActiveServer()?.scrobble) {
return
}
try {
await client.scrobble({ id })
} catch {}
},
reset: () => {
set({
name: undefined,

View File

@ -13,6 +13,7 @@ import {
GetPlaylistParams,
GetPlaylistsParams,
GetTopSongsParams,
ScrobbleParams,
Search3Params,
StreamParams,
} from '@app/subsonic/params'
@ -223,6 +224,15 @@ export class SubsonicApiClient {
return this.buildUrl('stream', params)
}
//
// Media annotation
//
async scrobble(params: ScrobbleParams): Promise<SubsonicResponse<undefined>> {
const xml = await this.apiGetXml('scrobble', params)
return new SubsonicResponse<undefined>(xml, undefined)
}
//
// Searching
//

View File

@ -100,6 +100,16 @@ export type StreamParams = {
estimateContentLength?: boolean
}
//
// Media annotation
//
export type ScrobbleParams = {
id: string
time?: Date
submission?: boolean
}
//
// Searching
//