update
This commit is contained in:
@@ -144,7 +144,7 @@ const tabs = computed(() => [
|
||||
{ id: 'merkliste', label: 'Sammlungen', count: null },
|
||||
{ id: 'wheel', label: 'Rad', count: null },
|
||||
{ id: 'stream', label: 'Stream', count: null },
|
||||
...(angemeldet.value || host.value ? [{ id: 'clips', label: 'Clips', count: null }] : []),
|
||||
{ id: 'clips', label: 'Clips', count: null },
|
||||
{ id: 'watched', label: 'Gesehen', count: gesehenZahl.value },
|
||||
{
|
||||
id: 'wuensche',
|
||||
|
||||
58
frontend/src/components/ClipBild.vue
Normal file
58
frontend/src/components/ClipBild.vue
Normal file
@@ -0,0 +1,58 @@
|
||||
<script setup>
|
||||
// Das Vorschaubild eines geschnittenen Clips: der erste Frame des Videos.
|
||||
// Ein Video-Element im Hintergrund lädt den Anfang, der Frame wandert
|
||||
// auf ein Canvas und von dort als Bild in die Karte. So gibt es keine
|
||||
// Player-Steuerung, keine Bild-in-Bild-Hinweise und kein schwarzes Feld.
|
||||
import { onMounted, onUnmounted, ref } from 'vue'
|
||||
|
||||
const props = defineProps({
|
||||
src: { type: String, required: true },
|
||||
})
|
||||
|
||||
const bild = ref(null)
|
||||
let video = null
|
||||
|
||||
onMounted(() => {
|
||||
video = document.createElement('video')
|
||||
video.muted = true
|
||||
video.playsInline = true
|
||||
video.preload = 'auto'
|
||||
video.addEventListener('loadeddata', () => {
|
||||
const canvas = document.createElement('canvas')
|
||||
canvas.width = video.videoWidth
|
||||
canvas.height = video.videoHeight
|
||||
canvas.getContext('2d').drawImage(video, 0, 0)
|
||||
try {
|
||||
bild.value = canvas.toDataURL('image/jpeg', 0.82)
|
||||
} catch {
|
||||
bild.value = null
|
||||
}
|
||||
// Das Video wird nicht mehr gebraucht
|
||||
video.removeAttribute('src')
|
||||
video.load()
|
||||
})
|
||||
video.src = props.src
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
if (video) {
|
||||
video.removeAttribute('src')
|
||||
video.load()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<img v-if="bild" :src="bild" alt="" />
|
||||
<span v-else class="leer"></span>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
img,
|
||||
.leer {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
</style>
|
||||
@@ -6,6 +6,9 @@ import ClipTrimmer from './ClipTrimmer.vue'
|
||||
|
||||
const props = defineProps({
|
||||
clip: { type: Object, required: true },
|
||||
// Besitzer und Host dürfen schneiden, ändern und löschen — alle
|
||||
// anderen schauen nur
|
||||
darf: Boolean,
|
||||
})
|
||||
const emit = defineEmits(['zu', 'geaendert'])
|
||||
|
||||
@@ -72,6 +75,7 @@ onUnmounted(() => window.removeEventListener('keydown', escape))
|
||||
<div class="editor">
|
||||
<header>
|
||||
<input
|
||||
v-if="darf"
|
||||
v-model="titel"
|
||||
type="text"
|
||||
maxlength="80"
|
||||
@@ -79,12 +83,20 @@ onUnmounted(() => window.removeEventListener('keydown', escape))
|
||||
class="titel"
|
||||
@change="patch({ titel })"
|
||||
/>
|
||||
<h2 v-else class="titel">{{ clip.titel || 'Ohne Titel' }}</h2>
|
||||
<button type="button" class="zu" title="Schließen" @click="emit('zu')">×</button>
|
||||
</header>
|
||||
|
||||
<video :src="vorschauUrl" controls playsinline class="vorschau"></video>
|
||||
<!-- Wer nur schaut, bekommt bei geschnittenen Clips das fertige Video -->
|
||||
<video
|
||||
:src="!darf && clip.geschnitten ? `/api/clips/${clip.id}/video` : vorschauUrl"
|
||||
controls
|
||||
playsinline
|
||||
class="vorschau"
|
||||
></video>
|
||||
|
||||
<ClipTrimmer
|
||||
v-if="darf"
|
||||
v-model:vorher="vorher"
|
||||
v-model:dauer="dauer"
|
||||
:segmente="segmente"
|
||||
@@ -95,14 +107,14 @@ onUnmounted(() => window.removeEventListener('keydown', escape))
|
||||
<p v-if="fehler" class="fehler">{{ fehler }}</p>
|
||||
|
||||
<div class="knoepfe">
|
||||
<button type="button" class="haupt" :disabled="schneidet" @click="schneiden">
|
||||
<button v-if="darf" type="button" class="haupt" :disabled="schneidet" @click="schneiden">
|
||||
{{ schneidet ? 'Schneidet …' : clip.geschnitten ? 'Speichern' : 'Schneiden' }}
|
||||
</button>
|
||||
<a v-if="clip.geschnitten" :href="`/api/clips/${clip.id}/video`" download class="link"
|
||||
>Herunterladen</a
|
||||
>
|
||||
<span class="rest"></span>
|
||||
<button type="button" class="weg" @click="loeschen">Löschen</button>
|
||||
<button v-if="darf" type="button" class="weg" @click="loeschen">Löschen</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -5,6 +5,7 @@ import { computed, onMounted, onUnmounted, ref } from 'vue'
|
||||
import { abonnieren } from '../live'
|
||||
import { backdropUrl, posterUrl } from '../images'
|
||||
import ClipEditor from './ClipEditor.vue'
|
||||
import ClipBild from './ClipBild.vue'
|
||||
import PersonName from './PersonName.vue'
|
||||
|
||||
const props = defineProps({
|
||||
@@ -149,7 +150,7 @@ onUnmounted(() => abbestellen?.())
|
||||
<p v-if="laden" class="note">Lade Clips…</p>
|
||||
<p v-else-if="fehler" class="note error">{{ fehler }}</p>
|
||||
<p v-else-if="!clips.length" class="note">
|
||||
Noch keine Marken. Im Stream-Reiter setzt die Taste C eine.
|
||||
Noch keine Clips. Im Stream-Reiter setzt die Taste C eine Marke.
|
||||
</p>
|
||||
|
||||
<section v-for="g in gruppen" :key="g.key">
|
||||
@@ -159,13 +160,7 @@ onUnmounted(() => abbestellen?.())
|
||||
<!-- Geschnitten: das Video selbst als Bild. Sonst das Filmbild
|
||||
mit dem Hinweis, dass hier erst eine Marke steht. -->
|
||||
<span class="bild">
|
||||
<video
|
||||
v-if="c.geschnitten"
|
||||
:src="`/api/clips/${c.id}/video#t=0.5`"
|
||||
preload="metadata"
|
||||
muted
|
||||
playsinline
|
||||
></video>
|
||||
<ClipBild v-if="c.geschnitten" :src="`/api/clips/${c.id}/video`" />
|
||||
<img v-else-if="c.poster_path" :src="backdropUrl(c.poster_path, 'w342')" alt="" />
|
||||
<span v-else class="leer"></span>
|
||||
<span class="dauer">{{ Math.round(c.dauer) }} s</span>
|
||||
@@ -183,7 +178,13 @@ onUnmounted(() => abbestellen?.())
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<ClipEditor v-if="offen" :clip="offen" @zu="offen = null" @geaendert="laden_" />
|
||||
<ClipEditor
|
||||
v-if="offen"
|
||||
:clip="offen"
|
||||
:darf="offen.mine || host"
|
||||
@zu="offen = null"
|
||||
@geaendert="laden_"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user