up
This commit is contained in:
@@ -94,7 +94,7 @@ async function refreshPreview() {
|
||||
}
|
||||
}
|
||||
|
||||
const channels = ['main', 'music'] as const
|
||||
const channel = 'music' as const
|
||||
const playlist = computed(() =>
|
||||
state.value?.catalog.playlists.find((p) => p.id === state.value?.channels.music.playlistId),
|
||||
)
|
||||
@@ -296,6 +296,7 @@ onUnmounted(() => {
|
||||
:draft="planDraft"
|
||||
:is-editing="isPlanEditing"
|
||||
:is-busy="isBusy || isPlanSaving"
|
||||
:can-play-main="state.outputOpen && !!state.channels.main.source"
|
||||
:can-edit="planStatus.key === state.catalog.key && planStatus.editable"
|
||||
:notice="planStatus.key === state.catalog.key ? planStatus.notice : ''"
|
||||
@edit="editPlan"
|
||||
@@ -311,7 +312,49 @@ onUnmounted(() => {
|
||||
@clear="planDraft = []"
|
||||
@update-step="(index, patch) => Object.assign(planDraft[index]!, patch)"
|
||||
@command="send"
|
||||
/>
|
||||
>
|
||||
<template #main-controls>
|
||||
<label for="position-main">Позиция, секунды</label>
|
||||
<n-input-number
|
||||
v-model:value="positions.main"
|
||||
:input-props="{ id: 'position-main' }"
|
||||
:min="0"
|
||||
:max="state.channels.main.duration ?? undefined"
|
||||
/>
|
||||
<n-button
|
||||
:disabled="
|
||||
!state.outputOpen || !state.channels.main.source || isBusy || isPlanSaving
|
||||
"
|
||||
@click="send({ action: 'seek', channel: 'main', position: positions.main ?? 0 })"
|
||||
>Перемотать</n-button
|
||||
>
|
||||
<label for="volume-main">Громкость, %</label>
|
||||
<n-input-number
|
||||
:value="state.channels.main.volume"
|
||||
:input-props="{ id: 'volume-main' }"
|
||||
:min="0"
|
||||
:max="100"
|
||||
:disabled="isBusy || isPlanSaving"
|
||||
@update:value="send({ action: 'volume', channel: 'main', volume: $event ?? 0 })"
|
||||
/>
|
||||
<p role="status">
|
||||
{{ statusLabels[state.channels.main.status] }} ·
|
||||
{{ state.channels.main.position.toFixed(1) }} /
|
||||
{{ state.channels.main.duration?.toFixed(1) ?? '—' }} с
|
||||
</p>
|
||||
<n-alert v-if="state.channels.main.error" type="error">{{
|
||||
state.channels.main.error
|
||||
}}</n-alert>
|
||||
<details>
|
||||
<summary>Программные замеры запуска</summary>
|
||||
<p>{{ metrics('main') }}</p>
|
||||
<p>
|
||||
Приём команды → подтверждение проигрывателя. Физический звук и первый кадр
|
||||
проектора здесь не измеряются.
|
||||
</p>
|
||||
</details>
|
||||
</template>
|
||||
</ScreenPlanPanel>
|
||||
<p role="status">{{ sceneLabels[state.scene.status] }}</p>
|
||||
<p v-if="state.timer.durationMs > 0" role="status" class="timer-status">
|
||||
{{ state.timer.label }} · {{ timerDisplay }} · {{ timerLabels[state.timer.status] }}
|
||||
@@ -361,51 +404,36 @@ onUnmounted(() => {
|
||||
aria-label="Выключить весь звук"
|
||||
@update:value="send({ action: 'mute', enabled: $event })"
|
||||
/></label>
|
||||
<n-card
|
||||
v-for="channel in channels"
|
||||
:key="channel"
|
||||
:title="channel === 'main' ? 'Вступление и видео' : 'Музыка'"
|
||||
>
|
||||
<n-card title="Музыка">
|
||||
<n-space vertical>
|
||||
<n-select
|
||||
v-if="channel === 'main'"
|
||||
:value="state.channels.main.source?.id ?? null"
|
||||
:options="state.catalog.media.map((s) => ({ label: s.name, value: s.id }))"
|
||||
placeholder="Выберите медиа"
|
||||
:value="state.channels.music.playlistId"
|
||||
:options="state.catalog.playlists.map((p) => ({ label: p.name, value: p.id }))"
|
||||
placeholder="Выберите плейлист"
|
||||
:disabled="isBusy"
|
||||
:input-props="{ 'aria-label': 'Медиа показа' }"
|
||||
@update:value="send({ action: 'select', channel: 'main', sourceId: $event })"
|
||||
:input-props="{ 'aria-label': 'Плейлист' }"
|
||||
@update:value="send({ action: 'playlist', channel: 'music', playlistId: $event })"
|
||||
/>
|
||||
<template v-else>
|
||||
<n-select
|
||||
:value="state.channels.music.playlistId"
|
||||
:options="state.catalog.playlists.map((p) => ({ label: p.name, value: p.id }))"
|
||||
placeholder="Выберите плейлист"
|
||||
:disabled="isBusy"
|
||||
:input-props="{ 'aria-label': 'Плейлист' }"
|
||||
@update:value="send({ action: 'playlist', channel: 'music', playlistId: $event })"
|
||||
/>
|
||||
<n-select
|
||||
:value="state.channels.music.source ? state.channels.music.trackIndex : null"
|
||||
:options="
|
||||
(playlist?.tracks ?? []).map((t, index) => ({
|
||||
label: t.name || `Трек ${index + 1}`,
|
||||
value: index,
|
||||
}))
|
||||
"
|
||||
placeholder="Выберите трек"
|
||||
:disabled="isBusy"
|
||||
:input-props="{ 'aria-label': 'Трек' }"
|
||||
@update:value="send({ action: 'track', channel: 'music', trackIndex: $event })"
|
||||
/>
|
||||
<label
|
||||
>Повторять плейлист
|
||||
<n-switch
|
||||
:value="state.channels.music.loop"
|
||||
aria-label="Повторять плейлист"
|
||||
@update:value="send({ action: 'loop', channel: 'music', enabled: $event })"
|
||||
/></label>
|
||||
</template>
|
||||
<n-select
|
||||
:value="state.channels.music.source ? state.channels.music.trackIndex : null"
|
||||
:options="
|
||||
(playlist?.tracks ?? []).map((t, index) => ({
|
||||
label: t.name || `Трек ${index + 1}`,
|
||||
value: index,
|
||||
}))
|
||||
"
|
||||
placeholder="Выберите трек"
|
||||
:disabled="isBusy"
|
||||
:input-props="{ 'aria-label': 'Трек' }"
|
||||
@update:value="send({ action: 'track', channel: 'music', trackIndex: $event })"
|
||||
/>
|
||||
<label
|
||||
>Повторять плейлист
|
||||
<n-switch
|
||||
:value="state.channels.music.loop"
|
||||
aria-label="Повторять плейлист"
|
||||
@update:value="send({ action: 'loop', channel: 'music', enabled: $event })"
|
||||
/></label>
|
||||
<n-space>
|
||||
<n-button
|
||||
:disabled="!state.outputOpen || !state.channels[channel].source || isBusy"
|
||||
@@ -433,7 +461,6 @@ onUnmounted(() => {
|
||||
>Повторить с начала</n-button
|
||||
>
|
||||
<n-button
|
||||
v-if="channel === 'music'"
|
||||
:disabled="!state.outputOpen || !state.channels.music.source || isBusy"
|
||||
@click="send({ action: 'next', channel: 'music' })"
|
||||
>Следующий трек</n-button
|
||||
|
||||
@@ -9,6 +9,7 @@ defineProps<{
|
||||
draft: ScreenStep[]
|
||||
isEditing: boolean
|
||||
isBusy: boolean
|
||||
canPlayMain: boolean
|
||||
canEdit: boolean
|
||||
notice: string
|
||||
}>()
|
||||
@@ -150,32 +151,36 @@ const modeOptions = Object.entries(modeLabels).map(([mode, label]) => ({
|
||||
</template>
|
||||
<template v-if="currentIndex === index && (step.mode === 2 || step.mode === 4)">
|
||||
<n-button
|
||||
:disabled="isBusy"
|
||||
:disabled="isBusy || !canPlayMain"
|
||||
@click="emit('command', { action: 'play', channel: 'main' })"
|
||||
>Запуск</n-button
|
||||
>
|
||||
<n-button
|
||||
:disabled="isBusy"
|
||||
:disabled="isBusy || !canPlayMain"
|
||||
@click="emit('command', { action: 'pause', channel: 'main' })"
|
||||
>Пауза</n-button
|
||||
>
|
||||
<n-button
|
||||
:disabled="isBusy"
|
||||
:disabled="isBusy || !canPlayMain"
|
||||
@click="emit('command', { action: 'resume', channel: 'main' })"
|
||||
>Продолжить</n-button
|
||||
>
|
||||
<n-button
|
||||
:disabled="isBusy"
|
||||
:disabled="isBusy || !canPlayMain"
|
||||
@click="emit('command', { action: 'stop', channel: 'main' })"
|
||||
>Стоп</n-button
|
||||
>
|
||||
<n-button
|
||||
:disabled="isBusy"
|
||||
:disabled="isBusy || !canPlayMain"
|
||||
@click="emit('command', { action: 'replay', channel: 'main' })"
|
||||
>Повтор</n-button
|
||||
>
|
||||
</template>
|
||||
</n-space>
|
||||
<slot
|
||||
v-if="currentIndex === index && (step.mode === 2 || step.mode === 4)"
|
||||
name="main-controls"
|
||||
/>
|
||||
</n-space>
|
||||
<n-space>
|
||||
<n-button
|
||||
|
||||
Reference in New Issue
Block a user