up
This commit is contained in:
@@ -6,6 +6,7 @@ import { MediaPlayer } from '../src/utils/media-player.js'
|
||||
import { buildScreenPackage } from './screen-package.js'
|
||||
import { packageKey, type LocalPackage } from './package-store.js'
|
||||
import type { Intent, Report } from '../src/types/screen.js'
|
||||
import { applyRemote, parseRemote } from './remote-state.js'
|
||||
|
||||
function send(c: Controller, intent: Intent) {
|
||||
c.command({ session: c.state.session, sequence: c.state.sequence + 1, ...intent })
|
||||
@@ -291,3 +292,176 @@ test('DOM: следующий трек не предзагружается до
|
||||
assert.equal(spare.paused, false)
|
||||
assert.equal(playing.paused, true)
|
||||
})
|
||||
|
||||
test('DOM: пауза музыки затихает за секунду, snapshots не возвращают громкость и позицию', async (t) => {
|
||||
const f = await fixture(t)
|
||||
send(f.c, { action: 'volume', channel: 'music', volume: 80 })
|
||||
send(f.c, { action: 'play', channel: 'music' })
|
||||
f.apply()
|
||||
await setImmediate()
|
||||
const media = [f.a, f.b].find((m) => !m.paused)!
|
||||
media.currentTime = 44
|
||||
t.mock.timers.enable({ apis: ['setInterval', 'Date'], now: 10000 })
|
||||
send(f.c, { action: 'pause', channel: 'music' })
|
||||
f.apply()
|
||||
await setImmediate()
|
||||
assert.equal(media.paused, false)
|
||||
t.mock.timers.tick(500)
|
||||
assert.ok(Math.abs(media.volume - 0.4) < 0.01)
|
||||
assert.equal(f.c.state.channels.music.volume, 80)
|
||||
f.c.state.revision++
|
||||
f.apply()
|
||||
assert.ok(Math.abs(media.volume - 0.4) < 0.01)
|
||||
// Повтор команды тоже не продлевает затухание.
|
||||
send(f.c, { action: 'pause', channel: 'music' })
|
||||
f.apply()
|
||||
await setImmediate()
|
||||
media.currentTime = 45
|
||||
t.mock.timers.tick(500)
|
||||
await setImmediate()
|
||||
assert.equal(media.paused, true)
|
||||
assert.equal(media.volume, 0.8)
|
||||
assert.equal(media.currentTime, 45)
|
||||
const report = f.reports.at(-1)!
|
||||
assert.equal(report.status, 'paused')
|
||||
assert.equal(report.position, 45)
|
||||
f.c.report(report)
|
||||
assert.equal(f.c.state.channels.music.position, 45)
|
||||
})
|
||||
|
||||
test('DOM: продолжение отменяет затухание, изменение громкости не сбрасывает его', async (t) => {
|
||||
const f = await fixture(t)
|
||||
send(f.c, { action: 'play', channel: 'music' })
|
||||
f.apply()
|
||||
await setImmediate()
|
||||
const media = [f.a, f.b].find((m) => !m.paused)!
|
||||
t.mock.timers.enable({ apis: ['setInterval', 'Date'], now: 10000 })
|
||||
send(f.c, { action: 'pause', channel: 'music' })
|
||||
f.apply()
|
||||
await setImmediate()
|
||||
t.mock.timers.tick(500)
|
||||
send(f.c, { action: 'volume', channel: 'music', volume: 60 })
|
||||
f.apply()
|
||||
assert.ok(Math.abs(media.volume - 0.3) < 0.01)
|
||||
send(f.c, { action: 'resume', channel: 'music' })
|
||||
f.apply()
|
||||
await setImmediate()
|
||||
assert.equal(media.volume, 0.6)
|
||||
assert.equal(media.paused, false)
|
||||
t.mock.timers.tick(1500)
|
||||
assert.equal(media.paused, false)
|
||||
assert.equal(media.volume, 0.6)
|
||||
})
|
||||
|
||||
for (const action of ['stop', 'next', 'error', 'session', 'destroy'] as const) {
|
||||
test(`DOM: затухание отменяется при ${action}, поздняя пауза не затрагивает новый звук`, async (t) => {
|
||||
const f = await fixture(t)
|
||||
send(f.c, { action: 'play', channel: 'music' })
|
||||
f.apply()
|
||||
await setImmediate()
|
||||
const media = [f.a, f.b].find((m) => !m.paused)!
|
||||
t.mock.timers.enable({ apis: ['setInterval', 'Date'], now: 10000 })
|
||||
send(f.c, { action: 'pause', channel: 'music' })
|
||||
f.apply()
|
||||
await setImmediate()
|
||||
t.mock.timers.tick(400)
|
||||
if (action === 'destroy') f.player.destroy()
|
||||
else if (action === 'error') {
|
||||
media.error = { code: 3 } as MediaError
|
||||
media.dispatchEvent(new Event('error'))
|
||||
} else if (action === 'session') {
|
||||
f.c.selectPackage(testPackage())
|
||||
f.apply()
|
||||
} else {
|
||||
send(f.c, { action, channel: 'music' })
|
||||
f.apply()
|
||||
}
|
||||
await setImmediate()
|
||||
assert.equal(media.paused, true)
|
||||
if (action === 'next' || action === 'stop') {
|
||||
send(f.c, { action: 'play', channel: 'music' })
|
||||
f.apply()
|
||||
await setImmediate()
|
||||
const playing = [f.a, f.b].find((m) => !m.paused)!
|
||||
assert.ok(playing)
|
||||
t.mock.timers.tick(1500)
|
||||
assert.equal(playing.paused, false)
|
||||
assert.equal(playing.volume, f.c.state.channels.music.volume / 100)
|
||||
} else {
|
||||
const reports = f.reports.length
|
||||
t.mock.timers.tick(1500)
|
||||
assert.equal(f.reports.length, reports)
|
||||
assert.equal(f.a.paused, true)
|
||||
assert.equal(f.b.paused, true)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
for (const silent of ['muted', 'zero', 'stopped'] as const) {
|
||||
test(`DOM: беззвучная музыка ставится на паузу сразу: ${silent}`, async (t) => {
|
||||
const f = await fixture(t)
|
||||
send(f.c, { action: 'play', channel: 'music' })
|
||||
f.apply()
|
||||
await setImmediate()
|
||||
const media = [f.a, f.b].find((m) => !m.paused)!
|
||||
if (silent === 'muted') send(f.c, { action: 'mute', enabled: true })
|
||||
if (silent === 'zero') send(f.c, { action: 'volume', channel: 'music', volume: 0 })
|
||||
if (silent === 'stopped') {
|
||||
send(f.c, { action: 'stop', channel: 'music' })
|
||||
f.apply()
|
||||
await setImmediate()
|
||||
}
|
||||
send(f.c, { action: 'pause', channel: 'music' })
|
||||
f.apply()
|
||||
await setImmediate()
|
||||
assert.equal(media.paused, true)
|
||||
assert.equal(f.reports.at(-1)!.status, 'paused')
|
||||
})
|
||||
}
|
||||
|
||||
for (const mode of [2, 4]) {
|
||||
test(`DOM: серверная автопауза при режиме ${mode} затихает и возобновляется без остановки основного канала`, async (t) => {
|
||||
const f = await fixture(t)
|
||||
const music = (version: number, playback: number, positionMs: number) => ({
|
||||
stateVersion: String(version), playlist: { ID: 1, tracks: [{ ID: 1 }, { ID: 2 }] },
|
||||
trackIndex: 0, playback, positionMs, volume: 80, loop: true,
|
||||
})
|
||||
const playing = parseRemote({ mode: 1, stateVersion: '1', music: music(1, 2, 0) })
|
||||
applyRemote(f.c, parseRemote({}), playing)
|
||||
f.apply()
|
||||
await setImmediate()
|
||||
const media = [f.a, f.b].find((m) => !m.paused)!
|
||||
media.currentTime = 12
|
||||
t.mock.timers.enable({ apis: ['setInterval', 'Date'], now: 10000 })
|
||||
const paused = parseRemote({ mode, command: 1, stateVersion: '2', music: music(2, 3, 11500) })
|
||||
const seekOperation = f.c.state.channels.music.seekOperation
|
||||
applyRemote(f.c, playing, paused)
|
||||
assert.equal(f.c.state.channels.music.seekOperation, seekOperation)
|
||||
f.apply()
|
||||
await setImmediate()
|
||||
assert.equal(media.paused, false)
|
||||
t.mock.timers.tick(500)
|
||||
assert.ok(Math.abs(media.volume - 0.4) < 0.01)
|
||||
assert.equal((mode === 2 ? f.audio : f.video).paused, false)
|
||||
media.currentTime = 13
|
||||
t.mock.timers.tick(500)
|
||||
await setImmediate()
|
||||
assert.equal(media.paused, true)
|
||||
assert.equal(media.currentTime, 13)
|
||||
const resumed = parseRemote({ mode: 1, stateVersion: '3', music: music(3, 2, 11500) })
|
||||
applyRemote(f.c, paused, resumed)
|
||||
f.apply()
|
||||
await setImmediate()
|
||||
assert.equal(media.paused, false)
|
||||
assert.equal(media.volume, 0.8)
|
||||
assert.equal(media.currentTime, 11.5)
|
||||
// A separate seek in the paused state still synchronizes position.
|
||||
applyRemote(f.c, resumed, parseRemote({ mode: 1, stateVersion: '4', music: music(4, 3, 14000) }))
|
||||
const previous = parseRemote({ mode: 1, stateVersion: '4', music: music(4, 3, 14000) })
|
||||
applyRemote(f.c, previous, parseRemote({ mode: 1, stateVersion: '5', music: music(5, 3, 15000) }))
|
||||
f.apply()
|
||||
await setImmediate()
|
||||
assert.equal(media.currentTime, 15)
|
||||
assert.equal(media.paused, true)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -295,7 +295,13 @@ export function applyRemote(controller: Controller, previous: RemoteState, next:
|
||||
action: m.playback === 2 ? 'resume' : m.playback === 3 ? 'pause' : 'stop',
|
||||
channel: 'music',
|
||||
})
|
||||
if (m.playback !== 1)
|
||||
const pausingSameTrack =
|
||||
m.playback === 3 &&
|
||||
old?.playback === 2 &&
|
||||
old.playlistId === m.playlistId &&
|
||||
old.trackId === m.trackId
|
||||
// A pause keeps the audible position so a seek cannot cancel its fade-out.
|
||||
if (m.playback !== 1 && !pausingSameTrack)
|
||||
send(controller, { action: 'seek', channel: 'music', position: m.position })
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user