Mercurial > code > home > repos > video
view src/VideoPage.ts @ 49:1bd17c2e5517 default tip
video.py must sign video urls for serve-files.js to serve them
author | drewp@bigasterisk.com |
---|---|
date | Fri, 06 Dec 2024 17:13:51 -0800 |
parents | 046673b1cc24 |
children |
line wrap: on
line source
import { Router } from "@lit-labs/router"; import { LitElement, PropertyValues, TemplateResult, css, html, unsafeCSS } from "lit"; import { customElement, property, queryAsync } from "lit/decorators.js"; import { PagePlayer, ShakaVideoElement } from "./PagePlayer"; import maincss from "./main.css?inline"; export { SlProgressBar } from "@shoelace-style/shoelace"; export { PagePlayer } from "./PagePlayer"; export { VBreadcrumbs } from "./VBreadcrumbs"; export { VideoSection } from "./VideoSection"; interface VideoFile { webRelPath: string; webDataPath: string; label: string; } interface AutoplayVideoFile extends VideoFile { sig: string; } interface Subdir { label: string; path: string; } interface PageData { webDirRelPath: string; dirLabel: string; videos: VideoFile[]; subdirs: Subdir[]; autoplay: AutoplayVideoFile | null; } function subdirQuery(subdir: string): string { return "subdir=" + encodeURIComponent(subdir); } const PATH_PREFIX = "/video"; let routerShared: Router; class route { path = PATH_PREFIX + "/*"; pageData: PageData | null = null; async enter(params: { [key: string]: string | undefined }): Promise<boolean> { const webRelPath = "/" + params[0]!; // could be /a/dir/ or /a/video const resp = await fetch(PATH_PREFIX + "/api/videos?" + subdirQuery(webRelPath)); if (resp.status == 404) { return false; } this.pageData = await resp.json(); if (!this.pageData) return false; return true; } render(p: { [key: string]: string | undefined }) { return html`<video-page2 .pageData=${this.pageData}></video-page2>`; } } @customElement("video-page") export class VideoPage extends LitElement { static styles = [unsafeCSS(maincss)]; private _router = new Router(this, [new route()], {}); constructor() { super(); routerShared = this._router; } render() { const pd = (this._router.routes[0] as route).pageData; if (!pd) { return html`loading...`; throw new Error("no page data"); } return html` <header> <img src="${PATH_PREFIX}/logo1.png" title="JelloBello" /> <v-breadcrumbs dirPath=${pd.webDirRelPath}></v-breadcrumbs> </header> <main>${this._router.outlet()}</main> <footer> <bigast-loginbar></bigast-loginbar> <span><a href="ingest/">Add new videos...</a></span> </footer> `; } } @customElement("video-page2") export class VideoPage2 extends LitElement { @property() pageData?: PageData; @queryAsync("page-player") pagePlayer!: Promise<PagePlayer>; protected firstUpdated(_changedProperties: PropertyValues): void { document.addEventListener("keydown", (e) => { if (e.key == "Escape") { this.gotoDirListingPage(); } }); } async connectedCallback() { super.connectedCallback(); if (this.pageData?.autoplay) { this.startPlayer(this.pageData.autoplay); // this might not autoplay } else { this.closePlayer(); } } protected update(changedProperties: PropertyValues): void { super.update(changedProperties); if (changedProperties.has("pageData")) { if (this.pageData?.autoplay) { this.startPlayer(this.pageData.autoplay); } else { this.closePlayer(); } } } static styles = [ unsafeCSS(maincss), css` :host { display: block; } `, ]; thumbSrc(vf: VideoFile) { return PATH_PREFIX + "/api/thumbnail?webRelPath=" + encodeURIComponent(vf.webRelPath); } renderSubdir(subdir: Subdir): TemplateResult { return html`<a href="${PATH_PREFIX}${subdir.path}"><div class="subdir">${subdir.label}</div></a>`; } renderVideoListing(video: VideoFile) { return html`<video-section @playVideo=${this.gotoVideoPlayerPage} webRelPath=${video.webRelPath} thumbRelPath=${this.thumbSrc(video)} title="${video.label}" ></video-section>`; } render() { if (this.pageData == null) { return html`<div>Loading...</div>`; } const listings = [ html`${this.pageData.subdirs.map((s) => this.renderSubdir(s))}`, // html`<div>${this.pageData.videos.map((v) => this.renderVideoListing(v))}</div>`, ]; const dirTitle = this.pageData.dirLabel ? html`<h2>${this.pageData.dirLabel}</h2>` : html``; return html` ${dirTitle} <div class="listing">${listings}</div> <div id="scrim" @click=${this.gotoDirListingPage}></div> <page-player manifest=""></page-player> `; } escapeALittle(fileUri: string): string { return fileUri.replace("#", encodeURIComponent("#")); } async gotoVideoPlayerPage(ev: CustomEvent) { const player = await this.pagePlayer; this.goto(PATH_PREFIX + "" + ev.detail.webRelPath); } gotoDirListingPage() { this.goto(PATH_PREFIX + "" + this.pageData?.webDirRelPath); } getScrim(): HTMLElement { return this.shadowRoot!.querySelector("#scrim")!; } async startPlayer(p: AutoplayVideoFile) { const player = await this.pagePlayer; player.manifest = PATH_PREFIX + "/files" + this.escapeALittle(p.webDataPath) + "?sig=" + encodeURIComponent(p.sig); const sv = player.shadowRoot!.querySelector("shaka-video")! as ShakaVideoElement; sv.src = player.manifest; sv.autoplay = true; player.size = "big"; this.getScrim().style.display = "block"; } goto(url: string) { routerShared.goto(url); window.history.pushState({}, "", url); } async closePlayer() { const player = await this.pagePlayer; if (player.size === "hidden") { return; } player.size = "hidden"; this.getScrim().style.display = "none"; } }