video: samodejna poglavja za intervjuje + Clip/SeekToAction oznacba
Poglavja se zdaj generirajo iz YouTube podnapisov (chapters.py, dnevno ob 9:20) in gredo tudi v opis videa na YouTubu. Na strani so poglavja klikljiva (?t=<sekunde>, predvajalnik se odpre na tem mestu), v VideoObject pa gresta hasPart/Clip in SeekToAction, iz katerih Google naredi Wichtige Momente.
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
import { useState } from "react";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { Link, useRoute } from "wouter";
|
||||
import { ArrowLeft, Play, Clock, Youtube } from "lucide-react";
|
||||
@@ -29,6 +30,11 @@ export interface YtVideo {
|
||||
youtubeUrl: string;
|
||||
}
|
||||
|
||||
export function chapSecs(t: string) {
|
||||
const p = String(t).split(":").map((x) => parseInt(x, 10) || 0);
|
||||
return p.length === 3 ? p[0] * 3600 + p[1] * 60 + p[2] : p[0] * 60 + p[1];
|
||||
}
|
||||
|
||||
export function dauer(s: number) {
|
||||
const m = Math.floor(s / 60);
|
||||
const r = s % 60;
|
||||
@@ -58,6 +64,12 @@ function RelatedCard({ v }: { v: YtVideo }) {
|
||||
|
||||
export default function VideoDetailPage() {
|
||||
const [, params] = useRoute("/video/:slug");
|
||||
// ?t=<sekunde> odpre predvajalnik na tem mestu; enak parameter uporablja
|
||||
// tudi Clip oznacba, iz katere Google naredi "Wichtige Momente".
|
||||
const [startAt, setStartAt] = useState(() => {
|
||||
const n = parseInt(new URLSearchParams(window.location.search).get("t") || "", 10);
|
||||
return Number.isFinite(n) && n > 0 ? n : 0;
|
||||
});
|
||||
const slug = params?.slug || "";
|
||||
const { data, isLoading } = useQuery<{ item: YtVideo; related: YtVideo[] }>({
|
||||
queryKey: [`/api/yt-videos/${slug}`],
|
||||
@@ -92,7 +104,8 @@ export default function VideoDetailPage() {
|
||||
YouTube v tem primeru oglasov ne servira. */}
|
||||
<div className="rounded-lg overflow-hidden bg-black aspect-video">
|
||||
<iframe
|
||||
src={v.embedUrl || `https://www.youtube-nocookie.com/embed/${v.ytId}`}
|
||||
key={startAt}
|
||||
src={`${v.embedUrl || `https://www.youtube-nocookie.com/embed/${v.ytId}`}${startAt ? `?start=${startAt}` : ""}`}
|
||||
title={v.shortTitle}
|
||||
className="w-full h-full"
|
||||
style={{ border: 0 }}
|
||||
@@ -135,8 +148,14 @@ export default function VideoDetailPage() {
|
||||
<ul className="space-y-1.5">
|
||||
{v.chapters.map((c) => (
|
||||
<li key={c.t} className="text-sm">
|
||||
<span className="inline-block w-14 font-mono text-primary">{c.t}</span>
|
||||
<span className="text-card-foreground">{c.label}</span>
|
||||
<a
|
||||
href={`?t=${chapSecs(c.t)}`}
|
||||
onClick={(e) => { e.preventDefault(); setStartAt(chapSecs(c.t)); window.scrollTo({ top: 0, behavior: "smooth" }); }}
|
||||
className="hover:underline"
|
||||
>
|
||||
<span className="inline-block w-14 font-mono text-primary">{c.t}</span>
|
||||
<span className="text-card-foreground">{c.label}</span>
|
||||
</a>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
|
||||
Reference in New Issue
Block a user