feat(playlist): exact-format-per-item selection
Each video row in the playlist panel gets an exact-format button that lazily probes just that entry's URL (reusing the existing single-video probe IPC, no new main-process code) and shows a native Select of its real formats. The choice flows through addMany -> formatId per queue item. Probing is on-demand per row, never all up front, since a playlist can hold thousands of entries and per-entry probes are rate-limited. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,95 @@
|
||||
import { describe, it, expect } from 'vitest'
|
||||
import type { PlaylistEntry } from '../src/shared/ipc'
|
||||
import { buildPlaylistAddEntries } from '../src/renderer/src/components/downloadBar/playlistEntries'
|
||||
import type { ChosenFormat } from '../src/renderer/src/store/downloadTypes'
|
||||
import type { MediaKind } from '../src/renderer/src/store/downloads'
|
||||
|
||||
function entry(index: number, over: Partial<PlaylistEntry> = {}): PlaylistEntry {
|
||||
return {
|
||||
index,
|
||||
id: `v${index}`,
|
||||
title: `Item ${index}`,
|
||||
url: `https://youtube.com/watch?v=v${index}`,
|
||||
durationLabel: '10:00',
|
||||
uploader: 'Chan',
|
||||
...over
|
||||
}
|
||||
}
|
||||
|
||||
/** Build the ctx the way useDownloadBar does, from per-entry override maps. */
|
||||
function ctx(opts: {
|
||||
barKind?: MediaKind
|
||||
barQuality?: string
|
||||
kinds?: Record<number, MediaKind>
|
||||
formats?: Record<number, ChosenFormat>
|
||||
}) {
|
||||
const { barKind = 'video', barQuality = '1080p', kinds = {}, formats = {} } = opts
|
||||
return {
|
||||
barKind,
|
||||
barQuality,
|
||||
effKind: (i: number): MediaKind => kinds[i] ?? barKind,
|
||||
itemFormats: formats
|
||||
}
|
||||
}
|
||||
|
||||
describe('buildPlaylistAddEntries', () => {
|
||||
it('uses the bar preset when an entry matches the bar kind and has no override', () => {
|
||||
const [row] = buildPlaylistAddEntries([entry(1)], ctx({ barKind: 'video', barQuality: '720p' }))
|
||||
expect(row.kind).toBe('video')
|
||||
expect(row.quality).toBe('720p')
|
||||
expect(row.opts?.format).toBeUndefined()
|
||||
// metadata is forwarded so the queue row shows a title before probing
|
||||
expect(row.opts?.title).toBe('Item 1')
|
||||
expect(row.opts?.channel).toBe('Chan')
|
||||
})
|
||||
|
||||
it('falls back to that kind default when an entry override differs from the bar kind', () => {
|
||||
const [row] = buildPlaylistAddEntries(
|
||||
[entry(1)],
|
||||
ctx({ barKind: 'video', barQuality: '720p', kinds: { 1: 'audio' } })
|
||||
)
|
||||
expect(row.kind).toBe('audio')
|
||||
// audio's first quality option, not the bar's video '720p'
|
||||
expect(row.quality).toBe('Best')
|
||||
expect(row.opts?.format).toBeUndefined()
|
||||
})
|
||||
|
||||
it('carries an exact per-item format on a video row', () => {
|
||||
const fmt: ChosenFormat = { id: '299', hasAudio: false, label: '1080p60 · mp4' }
|
||||
const [row] = buildPlaylistAddEntries([entry(1)], ctx({ formats: { 1: fmt } }))
|
||||
expect(row.kind).toBe('video')
|
||||
expect(row.opts?.format).toEqual(fmt)
|
||||
// the chosen format's label doubles as the queue-row quality text
|
||||
expect(row.quality).toBe('1080p60 · mp4')
|
||||
})
|
||||
|
||||
it('ignores a stored format on a row that is set to audio', () => {
|
||||
const fmt: ChosenFormat = { id: '299', hasAudio: false, label: '1080p60 · mp4' }
|
||||
const [row] = buildPlaylistAddEntries(
|
||||
[entry(1)],
|
||||
ctx({ kinds: { 1: 'audio' }, formats: { 1: fmt } })
|
||||
)
|
||||
expect(row.kind).toBe('audio')
|
||||
expect(row.opts?.format).toBeUndefined()
|
||||
expect(row.quality).toBe('Best')
|
||||
})
|
||||
|
||||
it('maps a mixed batch independently per entry', () => {
|
||||
const fmt: ChosenFormat = { id: '136', hasAudio: false, label: '720p · mp4' }
|
||||
const rows = buildPlaylistAddEntries(
|
||||
[entry(1), entry(2), entry(3)],
|
||||
ctx({
|
||||
barKind: 'video',
|
||||
barQuality: '1080p',
|
||||
kinds: { 2: 'audio' },
|
||||
formats: { 3: fmt }
|
||||
})
|
||||
)
|
||||
expect(rows.map((r) => r.kind)).toEqual(['video', 'audio', 'video'])
|
||||
expect(rows[0].opts?.format).toBeUndefined()
|
||||
expect(rows[0].quality).toBe('1080p')
|
||||
expect(rows[1].quality).toBe('Best')
|
||||
expect(rows[2].opts?.format).toEqual(fmt)
|
||||
expect(rows[2].quality).toBe('720p · mp4')
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user