From affb19636a0fec1c8c29d0b070c79913dae236c0 Mon Sep 17 00:00:00 2001 From: Fedorov Vladimir Date: Wed, 7 Oct 2026 01:56:54 +0700 Subject: [PATCH] up --- dist/index.html | 2 +- src/components/GameScreensPanel.vue | 67 ++++++++++++++++++++++++----- src/components/ScreenPage.vue | 51 ++++++++++++++++++---- 3 files changed, 100 insertions(+), 20 deletions(-) diff --git a/dist/index.html b/dist/index.html index 17caf80..9b83d86 100644 --- a/dist/index.html +++ b/dist/index.html @@ -5,7 +5,7 @@ Вечерний детектив - + diff --git a/src/components/GameScreensPanel.vue b/src/components/GameScreensPanel.vue index 0687665..de6e96e 100644 --- a/src/components/GameScreensPanel.vue +++ b/src/components/GameScreensPanel.vue @@ -103,22 +103,30 @@ function uploadTrack(options: { file: UploadFileInfo }) { style="min-width: 220px" @update:value="(value) => emit('setMusic', { playlistID: value || 0, trackIndex: 0, playback: 'MUSIC_PLAYBACK_STOPPED' })" /> - - Удалить трек - +
+

В плейлисте пока нет треков.

+
+ + {{ track.name || track.filename || `Трек ${index + 1}` }} + + {{ track.filename }} + + Удалить + +
+
Старт Пауза Стоп @@ -187,4 +195,43 @@ function uploadTrack(options: { file: UploadFileInfo }) { color: #f2a2a2; font-size: 13px; } + +.track-list { + flex-basis: 100%; + margin-top: 8px; +} + +.track-empty { + color: #aaa; + margin: 0; +} + +.track-row { + align-items: center; + border-top: 1px solid rgb(255 255 255 / 10%); + display: flex; + gap: 8px; + padding: 6px 0; +} + +.track-row-active { + background: rgb(255 255 255 / 5%); +} + +.track-name { + justify-content: flex-start; + min-width: 180px; + overflow: hidden; + text-align: left; + text-overflow: ellipsis; + white-space: nowrap; +} + +.track-filename { + color: #aaa; + flex: 1; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} diff --git a/src/components/ScreenPage.vue b/src/components/ScreenPage.vue index b83be0a..c48ef6a 100644 --- a/src/components/ScreenPage.vue +++ b/src/components/ScreenPage.vue @@ -74,6 +74,11 @@ const musicTrack = computed(() => { const index = state.value?.music?.trackIndex || 0 return applicationFileURL(tracks[index]?.filename) }) +const musicSyncState = computed(() => [ + state.value?.music?.playlist?.ID, + state.value?.music?.trackIndex, + state.value?.music?.playback, +] as const) const screenBackground = computed(() => { const image = state.value?.backgroundImage || state.value?.coverImage return image ? { backgroundImage: `linear-gradient(rgb(0 0 0 / 55%), rgb(0 0 0 / 55%)), url("${image}")` } : {} @@ -194,16 +199,33 @@ watch(state, async (value) => { } }) +function waitForMusicMetadata(media: HTMLAudioElement): Promise { + if (media.readyState >= HTMLMediaElement.HAVE_METADATA) return Promise.resolve() + return new Promise((resolve) => { + const finish = () => { + media.removeEventListener('loadedmetadata', finish) + media.removeEventListener('error', finish) + resolve() + } + media.addEventListener('loadedmetadata', finish, { once: true }) + media.addEventListener('error', finish, { once: true }) + }) +} + watch( - () => state.value?.music, - async (value) => { - const media = musicAudioElement.value - if (!media || !value || !value.playlist?.tracks?.length) return + musicSyncState, + async (value, previous) => { + const music = state.value?.music + if (!music || !music.playlist?.tracks?.length) return await nextTick() - media.volume = Math.max(0, Math.min(1, (value.volume ?? 100) / 100)) - media.muted = !state.value?.soundEnabled - media.currentTime = (value.positionMs || 0) / 1000 - if (value.playback === 'MUSIC_PLAYBACK_PLAYING') { + const media = musicAudioElement.value + if (!media) return + const trackChanged = !previous || value[0] !== previous[0] || value[1] !== previous[1] + const playbackChanged = !previous || value[2] !== previous[2] + if (!trackChanged && !playbackChanged) return + await waitForMusicMetadata(media) + media.currentTime = trackChanged ? 0 : (music.positionMs || 0) / 1000 + if (music.playback === 'MUSIC_PLAYBACK_PLAYING') { void media.play().catch(() => { mediaError.value = 'Браузер заблокировал воспроизведение музыки. Нажмите «Подготовить экран».' }) @@ -211,7 +233,18 @@ watch( media.pause() } }, - { deep: true }, + { immediate: true }, +) + +watch( + () => [state.value?.music?.volume, state.value?.soundEnabled] as const, + ([volume, soundEnabled]) => { + const media = musicAudioElement.value + if (!media) return + media.volume = Math.max(0, Math.min(1, (volume ?? 100) / 100)) + media.muted = !soundEnabled + }, + { immediate: true }, ) async function advanceMusic() {