This commit is contained in:
2026-10-18 19:25:21 +07:00
parent c10ed2db01
commit 7b8badd140
5 changed files with 202 additions and 1 deletions
+43
View File
@@ -177,6 +177,9 @@ export class Controller {
}
private measured = new Map<Channel, { operation: number; startedAt: number }>()
private timerDeadline: number | null = null
private applyingRemote = false
private musicAutoPaused = false
private musicOverride = false
constructor(
private publish: (state: State) => void,
private now: () => number = () => performance.now(),
@@ -184,6 +187,7 @@ export class Controller {
this.state.channels.main.source = this.state.catalog.media[0]!
}
private broadcast() {
if (this.applyingRemote) return
this.state.revision++
this.publish(structuredClone(this.state))
}
@@ -199,6 +203,8 @@ export class Controller {
this.measured.delete(channel)
}
private newSession() {
this.musicAutoPaused = false
this.musicOverride = false
this.updateTimer()
this.state.session = randomUUID()
this.state.sequence = 0
@@ -314,6 +320,8 @@ export class Controller {
this.state.scene.error = ''
}
private mode(mode: ScreenMode, stepIndex: number | null = null, text = this.state.scene.text) {
if ([2, 4].includes(mode) && ![2, 4].includes(this.state.scene.mode))
this.musicOverride = false
this.state.scene = { ...this.state.scene, mode, stepIndex, text }
const c = this.state.channels.main
const kind = mode === 2 ? 'audio' : mode === 4 ? 'video' : null
@@ -327,6 +335,36 @@ export class Controller {
})
this.invalidateScene()
}
private overrideMusic() {
this.musicAutoPaused = false
this.musicOverride = [2, 4].includes(this.state.scene.mode)
}
private reconcileSceneMusic() {
const music = this.state.channels.music
if ([2, 4].includes(this.state.scene.mode)) {
if (!this.musicOverride && music.source && music.desired === 'playing') {
this.musicAutoPaused = true
this.change('music', { desired: 'paused', status: 'pending' })
}
} else {
if (this.musicAutoPaused && music.source && music.desired === 'paused')
this.change('music', { desired: 'playing', status: 'pending' })
this.musicAutoPaused = false
this.musicOverride = false
}
}
synchronizeRemote(apply: () => void, musicChanged: boolean) {
// Применить снимок целиком, прежде чем публиковать локальную автопаузу.
this.applyingRemote = true
try {
apply()
} finally {
this.applyingRemote = false
if (musicChanged) this.overrideMusic()
this.reconcileSceneMusic()
this.broadcast()
}
}
private configureTimer(label: string, seconds: number) {
this.timerDeadline = null
this.state.timer = {
@@ -598,6 +636,10 @@ export class Controller {
}
}
}
if (!this.applyingRemote) {
if ('channel' in command && command.channel === 'music') this.overrideMusic()
this.reconcileSceneMusic()
}
this.state.sequence = command.sequence
this.broadcast()
}
@@ -645,6 +687,7 @@ export class Controller {
this.measured.delete(report.channel)
}
if (report.status === 'error') {
if (report.channel === 'music') this.overrideMusic()
c.desired = 'paused'
if (report.channel === 'main' && [2, 4].includes(this.state.scene.mode)) {
this.state.scene.status = 'error'