feat(derush): add -1 s / +1 s buttons under the preview
931d2cb parent: 23a0a14 modified
web/src/i18n/locales/en/derush.ts +2 -0 | @@ -41,6 +41,8 @@ export default { | ||
| 41 | 41 | player: { |
| 42 | 42 | restart: "Back to start", |
| 43 | 43 | playPause: "Play / pause", |
| 44 | + back1: "−1 s (Shift+←)", | |
| 45 | + fwd1: "+1 s (Shift+→)", | |
| 44 | 46 | }, |
| 45 | 47 | legend: { |
| 46 | 48 | speech: "Speech", |
| @@ -41,6 +41,8 @@ export default { | |||
| 41 | player: { | 41 | player: { |
| 42 | restart: "Back to start", | 42 | restart: "Back to start", |
| 43 | playPause: "Play / pause", | 43 | playPause: "Play / pause", |
| 44 | + back1: "−1 s (Shift+←)", | ||
| 45 | + fwd1: "+1 s (Shift+→)", | ||
| 44 | }, | 46 | }, |
| 45 | legend: { | 47 | legend: { |
| 46 | speech: "Speech", | 48 | speech: "Speech", |
modified
web/src/i18n/locales/fr/derush.ts +2 -0 | @@ -41,6 +41,8 @@ export default { | ||
| 41 | 41 | player: { |
| 42 | 42 | restart: "Revenir au début", |
| 43 | 43 | playPause: "Lecture / pause", |
| 44 | + back1: "−1 s (Maj+←)", | |
| 45 | + fwd1: "+1 s (Maj+→)", | |
| 44 | 46 | }, |
| 45 | 47 | legend: { |
| 46 | 48 | speech: "Parole", |
| @@ -41,6 +41,8 @@ export default { | |||
| 41 | player: { | 41 | player: { |
| 42 | restart: "Revenir au début", | 42 | restart: "Revenir au début", |
| 43 | playPause: "Lecture / pause", | 43 | playPause: "Lecture / pause", |
| 44 | + back1: "−1 s (Maj+←)", | ||
| 45 | + fwd1: "+1 s (Maj+→)", | ||
| 44 | }, | 46 | }, |
| 45 | legend: { | 47 | legend: { |
| 46 | speech: "Parole", | 48 | speech: "Parole", |
modified
web/src/project/steps/derush/PlayerFrame.vue +5 -1 | @@ -1,5 +1,5 @@ | ||
| 1 | 1 | <script setup lang="ts"> |
| 2 | -/** Le lecteur sombre : vidéo 16/9, sous-titre de la phrase en cours, début / lecture / temps. */ | |
| 2 | +/** Le lecteur sombre : vidéo 16/9, sous-titre de la phrase en cours, début / ±1 s / lecture / temps. */ | |
| 3 | 3 | import { onBeforeUnmount, onMounted, ref } from "vue"; |
| 4 | 4 | import { useI18n } from "vue-i18n"; |
| 5 | 5 | import { mmss } from "../../../lib/format"; |
| @@ -11,6 +11,7 @@ const props = defineProps<{ player: Playlist; height: number; caption?: string } | ||
| 11 | 11 | const el = ref<HTMLVideoElement | null>(null); |
| 12 | 12 | onMounted(() => { props.player.video.value = el.value; }); |
| 13 | 13 | onBeforeUnmount(() => { props.player.video.value = null; }); |
| 14 | +const step = (s: number) => props.player.seek(Math.max(0, Math.min(props.player.t.value + s, props.player.rawTotal.value))); | |
| 14 | 15 | </script> |
| 15 | 16 | |
| 16 | 17 | <template> |
| @@ -23,7 +24,9 @@ onBeforeUnmount(() => { props.player.video.value = null; }); | ||
| 23 | 24 | </div> |
| 24 | 25 | <div class="bar"> |
| 25 | 26 | <button class="ctl" :title="t('derush.player.restart')" @click="player.restart()">⏮</button> |
| 27 | + <button class="ctl wide" :title="t('derush.player.back1')" @click="step(-1)">−1 s</button> | |
| 26 | 28 | <button class="ctl play" :title="t('derush.player.playPause')" @click="player.toggle()">{{ player.playing.value ? "❚❚" : "▶" }}</button> |
| 29 | + <button class="ctl wide" :title="t('derush.player.fwd1')" @click="step(1)">+1 s</button> | |
| 27 | 30 | <span class="time now">{{ mmss(player.elapsed.value) }}</span> |
| 28 | 31 | <span class="time dim">/ {{ mmss(player.total.value) }}</span> |
| 29 | 32 | <div class="spacer" /> |
| @@ -54,6 +57,7 @@ video { width: 100%; height: 100%; object-fit: contain; display: block; cursor: | ||
| 54 | 57 | color: #fff; font-size: 13px; cursor: pointer; |
| 55 | 58 | } |
| 56 | 59 | .ctl:hover { background: var(--ink-2); } |
| 60 | +.ctl.wide { width: auto; padding: 0 8px; font-family: var(--mono); font-size: 12px; } | |
| 57 | 61 | .ctl.play { width: 42px; background: #fff; color: var(--ink); } |
| 58 | 62 | .time { font-family: var(--mono); font-size: 13px; color: #fff; } |
| 59 | 63 | .time.now { padding-left: 6px; } |
| @@ -1,5 +1,5 @@ | |||
| 1 | <script setup lang="ts"> | 1 | <script setup lang="ts"> |
| 2 | -/** Le lecteur sombre : vidéo 16/9, sous-titre de la phrase en cours, début / lecture / temps. */ | 2 | +/** Le lecteur sombre : vidéo 16/9, sous-titre de la phrase en cours, début / ±1 s / lecture / temps. */ |
| 3 | import { onBeforeUnmount, onMounted, ref } from "vue"; | 3 | import { onBeforeUnmount, onMounted, ref } from "vue"; |
| 4 | import { useI18n } from "vue-i18n"; | 4 | import { useI18n } from "vue-i18n"; |
| 5 | import { mmss } from "../../../lib/format"; | 5 | import { mmss } from "../../../lib/format"; |
| @@ -11,6 +11,7 @@ const props = defineProps<{ player: Playlist; height: number; caption?: string } | |||
| 11 | const el = ref<HTMLVideoElement | null>(null); | 11 | const el = ref<HTMLVideoElement | null>(null); |
| 12 | onMounted(() => { props.player.video.value = el.value; }); | 12 | onMounted(() => { props.player.video.value = el.value; }); |
| 13 | onBeforeUnmount(() => { props.player.video.value = null; }); | 13 | onBeforeUnmount(() => { props.player.video.value = null; }); |
| 14 | +const step = (s: number) => props.player.seek(Math.max(0, Math.min(props.player.t.value + s, props.player.rawTotal.value))); | ||
| 14 | </script> | 15 | </script> |
| 15 | 16 | ||
| 16 | <template> | 17 | <template> |
| @@ -23,7 +24,9 @@ onBeforeUnmount(() => { props.player.video.value = null; }); | |||
| 23 | </div> | 24 | </div> |
| 24 | <div class="bar"> | 25 | <div class="bar"> |
| 25 | <button class="ctl" :title="t('derush.player.restart')" @click="player.restart()">⏮</button> | 26 | <button class="ctl" :title="t('derush.player.restart')" @click="player.restart()">⏮</button> |
| 27 | + <button class="ctl wide" :title="t('derush.player.back1')" @click="step(-1)">−1 s</button> | ||
| 26 | <button class="ctl play" :title="t('derush.player.playPause')" @click="player.toggle()">{{ player.playing.value ? "❚❚" : "▶" }}</button> | 28 | <button class="ctl play" :title="t('derush.player.playPause')" @click="player.toggle()">{{ player.playing.value ? "❚❚" : "▶" }}</button> |
| 29 | + <button class="ctl wide" :title="t('derush.player.fwd1')" @click="step(1)">+1 s</button> | ||
| 27 | <span class="time now">{{ mmss(player.elapsed.value) }}</span> | 30 | <span class="time now">{{ mmss(player.elapsed.value) }}</span> |
| 28 | <span class="time dim">/ {{ mmss(player.total.value) }}</span> | 31 | <span class="time dim">/ {{ mmss(player.total.value) }}</span> |
| 29 | <div class="spacer" /> | 32 | <div class="spacer" /> |
| @@ -54,6 +57,7 @@ video { width: 100%; height: 100%; object-fit: contain; display: block; cursor: | |||
| 54 | color: #fff; font-size: 13px; cursor: pointer; | 57 | color: #fff; font-size: 13px; cursor: pointer; |
| 55 | } | 58 | } |
| 56 | .ctl:hover { background: var(--ink-2); } | 59 | .ctl:hover { background: var(--ink-2); } |
| 60 | +.ctl.wide { width: auto; padding: 0 8px; font-family: var(--mono); font-size: 12px; } | ||
| 57 | .ctl.play { width: 42px; background: #fff; color: var(--ink); } | 61 | .ctl.play { width: 42px; background: #fff; color: var(--ink); } |
| 58 | .time { font-family: var(--mono); font-size: 13px; color: #fff; } | 62 | .time { font-family: var(--mono); font-size: 13px; color: #fff; } |
| 59 | .time.now { padding-left: 6px; } | 63 | .time.now { padding-left: 6px; } |