mirror of
https://github.com/austinried/subtracks.git
synced 2025-12-28 17:19:27 +01:00
impl scrobble & scrobble setting
works even in background thanks zustand
This commit is contained in:
parent
706e57aa77
commit
efc7e5c799
@ -4,6 +4,7 @@ export interface Server {
|
|||||||
username: string
|
username: string
|
||||||
token: string
|
token: string
|
||||||
salt: string
|
salt: string
|
||||||
|
scrobble: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AppSettings {
|
export interface AppSettings {
|
||||||
|
|||||||
@ -1,8 +1,36 @@
|
|||||||
import { getCurrentTrack, getPlayerState, trackPlayerCommands } from '@app/state/trackplayer'
|
import { getCurrentTrack, getPlayerState, TrackExt, trackPlayerCommands } from '@app/state/trackplayer'
|
||||||
import TrackPlayer, { Event } from 'react-native-track-player'
|
import TrackPlayer, { Event, State } from 'react-native-track-player'
|
||||||
import { useStore } from './state/store'
|
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 () {
|
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.RemotePlay, () => trackPlayerCommands.enqueue(TrackPlayer.play))
|
||||||
TrackPlayer.addEventListener(Event.RemotePause, () => trackPlayerCommands.enqueue(TrackPlayer.pause))
|
TrackPlayer.addEventListener(Event.RemotePause, () => trackPlayerCommands.enqueue(TrackPlayer.pause))
|
||||||
|
|
||||||
@ -27,30 +55,31 @@ module.exports = async function () {
|
|||||||
})
|
})
|
||||||
|
|
||||||
TrackPlayer.addEventListener(Event.RemoteStop, () => {
|
TrackPlayer.addEventListener(Event.RemoteStop, () => {
|
||||||
useStore.getState().reset()
|
unsubCurrentTrack()
|
||||||
|
reset()
|
||||||
trackPlayerCommands.enqueue(TrackPlayer.destroy)
|
trackPlayerCommands.enqueue(TrackPlayer.destroy)
|
||||||
})
|
})
|
||||||
|
|
||||||
TrackPlayer.addEventListener(Event.PlaybackState, () => {
|
TrackPlayer.addEventListener(Event.PlaybackState, () => {
|
||||||
trackPlayerCommands.enqueue(async () => {
|
trackPlayerCommands.enqueue(async () => {
|
||||||
useStore.getState().setPlayerState(await getPlayerState())
|
setPlayerState(await getPlayerState())
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
TrackPlayer.addEventListener(Event.PlaybackTrackChanged, () => {
|
TrackPlayer.addEventListener(Event.PlaybackTrackChanged, () => {
|
||||||
useStore.getState().setProgress({ position: 0, duration: 0, buffered: 0 })
|
useStore.getState().setProgress({ position: 0, duration: 0, buffered: 0 })
|
||||||
trackPlayerCommands.enqueue(async () => {
|
trackPlayerCommands.enqueue(async () => {
|
||||||
useStore.getState().setCurrentTrackIdx(await getCurrentTrack())
|
setCurrentTrackIdx(await getCurrentTrack())
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
TrackPlayer.addEventListener(Event.PlaybackQueueEnded, () => {
|
TrackPlayer.addEventListener(Event.PlaybackQueueEnded, () => {
|
||||||
trackPlayerCommands.enqueue(async () => {
|
trackPlayerCommands.enqueue(async () => {
|
||||||
useStore.getState().setCurrentTrackIdx(await getCurrentTrack())
|
setCurrentTrackIdx(await getCurrentTrack())
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
TrackPlayer.addEventListener(Event.PlaybackMetadataReceived, () => {
|
TrackPlayer.addEventListener(Event.PlaybackMetadataReceived, () => {
|
||||||
useStore.getState().setCurrentTrackIdx(useStore.getState().currentTrackIdx)
|
setCurrentTrackIdx(useStore.getState().currentTrackIdx)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@ -32,6 +32,7 @@ const ServerView: React.FC<{
|
|||||||
const [address, setAddress] = useState(server?.address || '')
|
const [address, setAddress] = useState(server?.address || '')
|
||||||
const [username, setUsername] = useState(server?.username || '')
|
const [username, setUsername] = useState(server?.username || '')
|
||||||
const [password, setPassword] = useState(server?.token ? 'password' : '')
|
const [password, setPassword] = useState(server?.token ? 'password' : '')
|
||||||
|
const [scrobble, setScrobble] = useState(server?.scrobble || false)
|
||||||
|
|
||||||
const validate = useCallback(() => {
|
const validate = useCallback(() => {
|
||||||
return !!address && !!username && !!password
|
return !!address && !!username && !!password
|
||||||
@ -49,7 +50,7 @@ const ServerView: React.FC<{
|
|||||||
}
|
}
|
||||||
}, [navigation])
|
}, [navigation])
|
||||||
|
|
||||||
const save = () => {
|
const save = useCallback(() => {
|
||||||
if (!validate()) {
|
if (!validate()) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -68,6 +69,7 @@ const ServerView: React.FC<{
|
|||||||
username,
|
username,
|
||||||
salt,
|
salt,
|
||||||
token,
|
token,
|
||||||
|
scrobble,
|
||||||
}
|
}
|
||||||
|
|
||||||
if (server) {
|
if (server) {
|
||||||
@ -87,7 +89,20 @@ const ServerView: React.FC<{
|
|||||||
}
|
}
|
||||||
|
|
||||||
exit()
|
exit()
|
||||||
}
|
}, [
|
||||||
|
activeServer,
|
||||||
|
address,
|
||||||
|
exit,
|
||||||
|
id,
|
||||||
|
password,
|
||||||
|
scrobble,
|
||||||
|
server,
|
||||||
|
servers,
|
||||||
|
setActiveServer,
|
||||||
|
setServers,
|
||||||
|
username,
|
||||||
|
validate,
|
||||||
|
])
|
||||||
|
|
||||||
const remove = useCallback(() => {
|
const remove = useCallback(() => {
|
||||||
if (!canRemove()) {
|
if (!canRemove()) {
|
||||||
@ -140,14 +155,17 @@ const ServerView: React.FC<{
|
|||||||
value={password}
|
value={password}
|
||||||
onChangeText={setPassword}
|
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
|
<Switch
|
||||||
trackColor={{
|
trackColor={{
|
||||||
false: colors.accentLow,
|
false: colors.accentLow,
|
||||||
true: colors.accent,
|
true: colors.accent,
|
||||||
}}
|
}}
|
||||||
thumbColor={colors.text.primary}
|
thumbColor={colors.text.primary}
|
||||||
value={false}
|
value={scrobble}
|
||||||
|
onValueChange={setScrobble}
|
||||||
/>
|
/>
|
||||||
</SettingsItem>
|
</SettingsItem>
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@ -9,6 +9,7 @@ export type SettingsSlice = {
|
|||||||
client?: SubsonicApiClient
|
client?: SubsonicApiClient
|
||||||
createClient: (id?: string) => void
|
createClient: (id?: string) => void
|
||||||
setActiveServer: (id?: string) => void
|
setActiveServer: (id?: string) => void
|
||||||
|
getActiveServer: () => Server | undefined
|
||||||
setServers: (servers: Server[]) => void
|
setServers: (servers: Server[]) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -25,7 +26,7 @@ export const createSettingsSlice = (set: SetState<Store>, get: GetState<Store>):
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const server = get().settings.servers.find(s => s.id === id)
|
const server = get().getActiveServer()
|
||||||
if (!server) {
|
if (!server) {
|
||||||
set({ client: undefined })
|
set({ client: undefined })
|
||||||
return
|
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 =>
|
setServers: servers =>
|
||||||
set(
|
set(
|
||||||
produce<SettingsSlice>(state => {
|
produce<SettingsSlice>(state => {
|
||||||
state.settings.servers = servers
|
state.settings.servers = servers
|
||||||
|
const activeServer = servers.find(s => s.id === state.settings.activeServer)
|
||||||
|
if (activeServer) {
|
||||||
|
state.client = new SubsonicApiClient(activeServer)
|
||||||
|
}
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
})
|
})
|
||||||
|
|||||||
@ -35,6 +35,8 @@ export type TrackPlayerSlice = {
|
|||||||
progress: Progress
|
progress: Progress
|
||||||
setProgress: (progress: Progress) => void
|
setProgress: (progress: Progress) => void
|
||||||
|
|
||||||
|
scrobbleTrack: (id: string) => Promise<void>
|
||||||
|
|
||||||
reset: () => void
|
reset: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -59,12 +61,14 @@ export const selectTrackPlayer = {
|
|||||||
progress: (store: TrackPlayerSlice) => store.progress,
|
progress: (store: TrackPlayerSlice) => store.progress,
|
||||||
setProgress: (store: TrackPlayerSlice) => store.setProgress,
|
setProgress: (store: TrackPlayerSlice) => store.setProgress,
|
||||||
|
|
||||||
|
scrobbleTrack: (store: TrackPlayerSlice) => store.scrobbleTrack,
|
||||||
|
|
||||||
reset: (store: TrackPlayerSlice) => store.reset,
|
reset: (store: TrackPlayerSlice) => store.reset,
|
||||||
}
|
}
|
||||||
|
|
||||||
export const trackPlayerCommands = new PromiseQueue(1)
|
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,
|
name: undefined,
|
||||||
setName: name => set({ name }),
|
setName: name => set({ name }),
|
||||||
|
|
||||||
@ -91,6 +95,21 @@ export const createTrackPlayerSlice = (set: SetState<Store>, _get: GetState<Stor
|
|||||||
progress: { position: 0, duration: 0, buffered: 0 },
|
progress: { position: 0, duration: 0, buffered: 0 },
|
||||||
setProgress: progress => set({ progress }),
|
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: () => {
|
reset: () => {
|
||||||
set({
|
set({
|
||||||
name: undefined,
|
name: undefined,
|
||||||
|
|||||||
@ -13,6 +13,7 @@ import {
|
|||||||
GetPlaylistParams,
|
GetPlaylistParams,
|
||||||
GetPlaylistsParams,
|
GetPlaylistsParams,
|
||||||
GetTopSongsParams,
|
GetTopSongsParams,
|
||||||
|
ScrobbleParams,
|
||||||
Search3Params,
|
Search3Params,
|
||||||
StreamParams,
|
StreamParams,
|
||||||
} from '@app/subsonic/params'
|
} from '@app/subsonic/params'
|
||||||
@ -223,6 +224,15 @@ export class SubsonicApiClient {
|
|||||||
return this.buildUrl('stream', params)
|
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
|
// Searching
|
||||||
//
|
//
|
||||||
|
|||||||
@ -100,6 +100,16 @@ export type StreamParams = {
|
|||||||
estimateContentLength?: boolean
|
estimateContentLength?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Media annotation
|
||||||
|
//
|
||||||
|
|
||||||
|
export type ScrobbleParams = {
|
||||||
|
id: string
|
||||||
|
time?: Date
|
||||||
|
submission?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// Searching
|
// Searching
|
||||||
//
|
//
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user