Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 8 additions & 13 deletions packages/mobile/src/components/audio/AudioPlayer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ import {
} from 'app/store/offline-downloads/slice'

import { useChromecast } from './GoogleCast'
import { addTracksAroundCurrent } from './addTracksAroundCurrent'
import { useSavePodcastProgress } from './useSavePodcastProgress'

export const DEFAULT_IMAGE_URL =
Expand Down Expand Up @@ -340,20 +341,14 @@ const useQueueSync = (isAudioSetup: boolean) => {
// Sort by original index so RNTP order matches redux order
resolved.sort((a, b) => a.i - b.i)

const before = resolved.filter(({ i }) => i < startIndex)
const after = resolved.filter(({ i }) => i > startIndex)
const before = resolved
.filter(({ i }) => i < startIndex)
.map(({ data }) => data)
const after = resolved
.filter(({ i }) => i > startIndex)
.map(({ data }) => data)

// Insert "before" tracks at position 0. Iterate from last to first so
// that earliest ends up at index 0 (each insert pushes prior entries
// right by one).
for (let j = before.length - 1; j >= 0; j--) {
await TrackPlayer.add(before[j].data, 0)
}

// Append "after" tracks at the end
if (after.length > 0) {
await TrackPlayer.add(after.map(({ data }) => data))
}
await addTracksAroundCurrent(TrackPlayer, before, after)
}
},
[makeTrackData]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { addTracksAroundCurrent } from './addTracksAroundCurrent'

// Mirrors SwiftAudioEx 1.x QueueManager: an insert in front of the current
// item only shifts currentIndex when the queue already has more than one item.
const makeSwiftAudioExQueue = (current: string) => {
const queue = { items: [current], currentIndex: 0 }
const player = {
add: async (tracks: string[], index?: number) => {
const at = index ?? queue.items.length
if (queue.items.length > 1 && queue.currentIndex >= at) {
queue.currentIndex += tracks.length
}
queue.items.splice(at, 0, ...tracks)
},
remove: async (indexes: number[]) => {
for (const index of [...indexes].sort((a, b) => b - a)) {
queue.items.splice(index, 1)
if (index < queue.currentIndex) queue.currentIndex -= 1
}
}
}
return { queue, player }
}

const cases: Array<[string, string[], string[]]> = [
['first track', [], ['b', 'c']],
['middle track', ['a'], ['c']],
['middle track with several before', ['a', 'b', 'c'], ['e', 'f']],
['last track', ['a', 'b'], []],
['last of two', ['a'], []],
['only track', [], []]
]

describe('addTracksAroundCurrent', () => {
it.each(cases)(
'keeps order and current index for the %s',
async (_, before, after) => {
const { queue, player } = makeSwiftAudioExQueue('current')
await addTracksAroundCurrent(player, before, after)

expect(queue.items).toEqual([...before, 'current', ...after])
expect(queue.items[queue.currentIndex]).toBe('current')
}
)
})
39 changes: 39 additions & 0 deletions packages/mobile/src/components/audio/addTracksAroundCurrent.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
type QueuePlayer<T> = {
add: (tracks: T[], insertBeforeIndex?: number) => Promise<unknown>
remove: (indexes: number[]) => Promise<unknown>
}

/**
* Adds the tracks before and after the current track, which must already be
* the only item in the queue, so the final order is [...before, current,
* ...after].
*
* On iOS, SwiftAudioEx only shifts its current index for an insert in front
* of the current track when the queue already holds more than one item.
* Inserting in front of a lone track leaves the index pointing one slot too
* early, so when the track ends the player replays it instead of advancing.
* To avoid that, the "after" tracks go in first, and when there are none a
* placeholder is appended for the duration of the insert.
*/
export const addTracksAroundCurrent = async <T>(
player: QueuePlayer<T>,
before: T[],
after: T[]
) => {
if (after.length > 0) {
await player.add(after)
}
if (before.length === 0) return

const needsPlaceholder = after.length === 0
if (needsPlaceholder) {
await player.add([before[before.length - 1]])
}

await player.add(before, 0)

if (needsPlaceholder) {
// Queue is now [...before, current, placeholder]
await player.remove([before.length + 1])
}
}