Skip to content

Commit

Permalink
[C-3561] Remove playback delay on native (#7893)
Browse files Browse the repository at this point in the history
  • Loading branch information
dylanjeffers authored Mar 20, 2024
1 parent 28f1150 commit 2a0a663
Showing 1 changed file with 85 additions and 65 deletions.
150 changes: 85 additions & 65 deletions packages/mobile/src/components/audio/AudioPlayer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,8 @@ import { useRef, useEffect, useCallback, useState } from 'react'

import { useAppContext } from '@audius/common/context'
import { SquareSizes } from '@audius/common/models'
import type { ID, Track } from '@audius/common/models'
import type { Track } from '@audius/common/models'
import { FeatureFlags } from '@audius/common/services'
import type { QueryParams } from '@audius/common/services'
import {
accountSelectors,
cacheTracksSelectors,
Expand Down Expand Up @@ -290,38 +289,6 @@ export const AudioPlayer = () => {
}
}, [reset])

// Map of user signature for gated tracks
const [gatedQueryParamsMap, setGatedQueryParamsMap] = useState<{
[trackId: ID]: QueryParams
}>({})

const handleGatedQueryParams = useCallback(
async (tracks: QueueableTrack[]) => {
const queryParamsMap: { [trackId: ID]: QueryParams } = {}

for (const { track } of tracks) {
if (!track) {
continue
}
const trackId = track.track_id

if (gatedQueryParamsMap[trackId]) {
queryParamsMap[trackId] = gatedQueryParamsMap[trackId]
} else {
const nftAccessSignature = nftAccessSignatureMap[trackId]?.mp3 ?? null
queryParamsMap[trackId] = await getQueryParams({
audiusBackendInstance,
nftAccessSignature
})
}
}

setGatedQueryParamsMap(queryParamsMap)
return queryParamsMap
},
[nftAccessSignatureMap, gatedQueryParamsMap, setGatedQueryParamsMap]
)

useTrackPlayerEvents(playerEvents, async (event) => {
const duration = (await TrackPlayer.getProgress()).duration
const position = (await TrackPlayer.getProgress()).position
Expand Down Expand Up @@ -357,6 +324,7 @@ export const AudioPlayer = () => {
}

if (event.type === Event.PlaybackActiveTrackChanged) {
await enqueueTracksJobRef.current
const playerIndex = await TrackPlayer.getActiveTrackIndex()
if (playerIndex === undefined) return

Expand Down Expand Up @@ -524,8 +492,13 @@ export const AudioPlayer = () => {

// Ref to keep track of the queue in the track player vs the queue in state
const queueListRef = useRef<string[]>([])
// Ref to ensure that we do not try to update while we are already updating
const updatingQueueRef = useRef<boolean>(false)

// A ref to the enqueue task to await before either requeing or appending to queue
const enqueueTracksJobRef = useRef<Promise<void>>()
// A way to abort the enqeue tracks job if a new lineup is played
const abortEnqueueControllerRef = useRef(new AbortController())
// The ref of trackQueryParams to avoid re-generating query params for the same track
const trackQueryParams = useRef({})

const handleQueueChange = useCallback(async () => {
const refUids = queueListRef.current
Expand All @@ -536,7 +509,6 @@ export const AudioPlayer = () => {
return
}

updatingQueueRef.current = true
queueListRef.current = queueTrackUids

// Checks to allow for continuous playback while making queue updates
Expand All @@ -545,6 +517,18 @@ export const AudioPlayer = () => {
refUids.length > 0 &&
isEqual(queueTrackUids.slice(0, refUids.length), refUids)

// If not an append, cancel the enqueue task first
if (!isQueueAppend) {
abortEnqueueControllerRef.current.abort()
}
// wait for enqueue task to either shut down or finish
if (enqueueTracksJobRef.current) {
await enqueueTracksJobRef.current
}

// Re-init the abort controller now that the enqueue job is done
abortEnqueueControllerRef.current = new AbortController()

// TODO: Queue removal logic was firing too often previously and causing playback issues when at the end of queues. Need to fix
// Check if we are removing from the end of the queue
// const isQueueRemoval =
Expand All @@ -568,11 +552,7 @@ export const AudioPlayer = () => {
? queueTracks.slice(refUids.length)
: queueTracks

const queryParamsMap = isReachable
? await handleGatedQueryParams(newQueueTracks)
: null

const newTrackData = newQueueTracks.map(({ track, isPreview }) => {
const makeTrackData = async ({ track, isPreview }: QueueableTrack) => {
if (!track) {
return unlistedTrackFallbackTrackData
}
Expand All @@ -587,10 +567,19 @@ export const AudioPlayer = () => {
const audioFilePath = getLocalAudioPath(trackId)
url = `file://${audioFilePath}`
} else {
const queryParams = {
...queryParamsMap?.[track.track_id],
preview: isPreview
let queryParams = trackQueryParams.current[trackId]

if (!queryParams) {
const nftAccessSignature = nftAccessSignatureMap[trackId]?.mp3 ?? null
queryParams = await getQueryParams({
audiusBackendInstance,
nftAccessSignature
})
trackQueryParams.current[trackId] = queryParams
}

queryParams = { ...queryParams, preview: isPreview }

url = apiClient.makeUrl(
`/tracks/${encodeHashId(track.track_id)}/stream`,
queryParams
Expand Down Expand Up @@ -624,44 +613,75 @@ export const AudioPlayer = () => {
artwork: imageUrl,
duration: isPreview ? getTrackPreviewDuration(track) : track.duration
}
})
}

// Enqueue tracks using 'middle-out' to ensure user can ready skip forward or backwards
const enqueueTracks = async (
queuableTracks: QueueableTrack[],
queueIndex = 0
) => {
let currentPivot = 1
while (
queueIndex - currentPivot >= 0 ||
queueIndex + currentPivot < queueTracks.length
) {
if (abortEnqueueControllerRef.current.signal.aborted) {
return
}

const nextTrack = queuableTracks[queueIndex + currentPivot]
if (nextTrack) {
await TrackPlayer.add(await makeTrackData(nextTrack))
}

const previousTrack = queuableTracks[queueIndex - currentPivot]
if (previousTrack) {
await TrackPlayer.add(await makeTrackData(previousTrack), 0)
}
currentPivot++
}
}

if (isQueueAppend) {
await TrackPlayer.add(newTrackData)
enqueueTracksJobRef.current = enqueueTracks(newQueueTracks)
await enqueueTracksJobRef.current
enqueueTracksJobRef.current = undefined
} else {
// New queue, reset before adding new tracks
// NOTE: Should only happen when the user selects a new lineup so reset should never be called in the background and cause an error
await TrackPlayer.reset()
await TrackPlayer.add(newTrackData)
if (queueIndex < newQueueTracks.length) {
await TrackPlayer.skip(queueIndex)

const firstTrack = newQueueTracks[queueIndex]
if (!firstTrack) return
await TrackPlayer.add(await makeTrackData(firstTrack))

if (playing) {
await TrackPlayer.play()
}
}

if (playing) await TrackPlayer.play()
updatingQueueRef.current = false
enqueueTracksJobRef.current = enqueueTracks(newQueueTracks, queueIndex)
await enqueueTracksJobRef.current
enqueueTracksJobRef.current = undefined
}
}, [
isNotReachable,
isOfflineModeEnabled,
offlineAvailabilityByTrackId,
playing,
queueIndex,
queueTrackOwnersMap,
queueTrackUids,
queueTracks,
didOfflineToggleChange,
queueTracks,
queueTrackOwnersMap,
isOfflineModeEnabled,
offlineAvailabilityByTrackId,
isCollectionMarkedForDownload,
handleGatedQueryParams,
isReachable,
storageNodeSelector
isNotReachable,
storageNodeSelector,
nftAccessSignatureMap,
playing
])

const handleQueueIdxChange = useCallback(async () => {
await enqueueTracksJobRef.current
const playerIdx = await TrackPlayer.getActiveTrackIndex()
const queue = await TrackPlayer.getQueue()

if (
!updatingQueueRef.current &&
queueIndex !== -1 &&
queueIndex !== playerIdx &&
queueIndex < queue.length
Expand Down

0 comments on commit 2a0a663

Please sign in to comment.