comparison src/VideoPage.ts @ 45:df51269bcef4

fix back/fwd nav and player loading
author drewp@bigasterisk.com
date Fri, 06 Dec 2024 01:02:33 -0800
parents 1d2c65d260d1
children 882d0bb0f801
comparison
equal deleted inserted replaced
44:239a83d46d48 45:df51269bcef4
1 import { Router } from "@lit-labs/router";
1 import { LitElement, PropertyValues, TemplateResult, css, html, unsafeCSS } from "lit"; 2 import { LitElement, PropertyValues, TemplateResult, css, html, unsafeCSS } from "lit";
2 import { customElement, property } from "lit/decorators.js"; 3 import { customElement, property, queryAsync } from "lit/decorators.js";
3 import { PagePlayer, ShakaVideoElement } from "./PagePlayer"; 4 import { PagePlayer, ShakaVideoElement } from "./PagePlayer";
4 import maincss from "./main.css?inline"; 5 import maincss from "./main.css?inline";
5 export { SlProgressBar } from "@shoelace-style/shoelace"; 6 export { SlProgressBar } from "@shoelace-style/shoelace";
6 export { PagePlayer } from "./PagePlayer"; 7 export { PagePlayer } from "./PagePlayer";
8 export { VBreadcrumbs } from "./VBreadcrumbs";
7 export { VideoSection } from "./VideoSection"; 9 export { VideoSection } from "./VideoSection";
8 import { Routes, Router } from "@lit-labs/router"; 10
9 export { VBreadcrumbs } from "./VBreadcrumbs";
10 interface VideoFile { 11 interface VideoFile {
11 webRelPath: string; 12 webRelPath: string;
12 webDataPath: string; 13 webDataPath: string;
13 label: string; 14 label: string;
14 } 15 }
16 interface Subdir { 17 interface Subdir {
17 label: string; 18 label: string;
18 path: string; 19 path: string;
19 } 20 }
20 21
21 interface VideoListings { 22 interface PageData {
23 webDirRelPath: string;
24 dirLabel: string;
22 videos: VideoFile[]; 25 videos: VideoFile[];
23 subdirs: Subdir[]; 26 subdirs: Subdir[];
27 autoplay: VideoFile | null;
24 } 28 }
25 29
26 function subdirQuery(subdir: string): string { 30 function subdirQuery(subdir: string): string {
27 return "subdir=" + encodeURIComponent(subdir); 31 return "subdir=" + encodeURIComponent(subdir);
28 } 32 }
29 33
30 class route { 34 class route {
31 path = "/video/*"; 35 path = "/video/*";
32 videoListings: VideoListings | null = null; 36 pageData: PageData | null = null;
33 showVid: string | null = null;
34 dirName?: string;
35
36 link(wrp: string): string {
37 return "/video/" + wrp;
38 }
39 37
40 async enter(params: { [key: string]: string | undefined }): Promise<boolean> { 38 async enter(params: { [key: string]: string | undefined }): Promise<boolean> {
41 const webRelPath = "/" + params[0]!; 39 const webRelPath = "/" + params[0]!; // could be /a/dir/ or /a/video
42 this.dirName = webRelPath.replace(/.*\/([^/]+)/, "$1"); 40 console.log("enter", webRelPath);
43 const resp = await fetch("/video/api/videos?" + subdirQuery(webRelPath)); 41 const resp = await fetch("/video/api/videos?" + subdirQuery(webRelPath));
44 if (resp.status == 404) { 42 if (resp.status == 404) {
45 return false; 43 return false;
46 } 44 }
47 this.videoListings = await resp.json(); 45 this.pageData = await resp.json();
48 46 if (!this.pageData) return false;
49 if (webRelPath.endsWith("/")) {
50 this.showVid = null;
51 } else {
52 this.showVid = webRelPath;
53 }
54 47
55 return true; 48 return true;
56 } 49 }
57 50
58 render(p: { [key: string]: string | undefined }) { 51 render(p: { [key: string]: string | undefined }) {
59 return html`<video-page2 52 console.log("render", this.pageData);
60 .link=${this.link.bind(this)} 53 return html`<video-page2 .pageData=${this.pageData}></video-page2>`;
61 .showVid=${this.showVid}
62 .videoListings=${this.videoListings}
63 .dirName=${this.dirName}
64 ></video-page2>`;
65 } 54 }
66 } 55 }
67 56
68 @customElement("video-page") 57 @customElement("video-page")
69 export class VideoPage extends LitElement { 58 export class VideoPage extends LitElement {
70 static styles = [unsafeCSS(maincss)]; 59 static styles = [unsafeCSS(maincss)];
71 private _router = new Router(this, [new route()], {}); 60 private _router = new Router(this, [new route()], {});
72 61
62 constructor() {
63 super();
64 window.rr = this._router;
65 }
66
73 render() { 67 render() {
74 const requestedPath = "/" + this._router.params[0]; 68 const pd = (this._router.routes[0] as route).pageData;
75 69 if (!pd) {
70 console.log("no page data", this._router.routes);
71 return html`loading...`;
72 throw new Error("no page data");
73 }
74 console.log("yes page data", pd);
76 return html` 75 return html`
77 <header> 76 <header>
78 <img src=${this._router.link("/video/logo1.png")} title="JelloBello" /> 77 <img src="/video/logo1.png" title="JelloBello" />
79 <v-breadcrumbs .link=${this._router.link} dirPath=${requestedPath}></v-breadcrumbs> 78 <v-breadcrumbs dirPath=${pd.webDirRelPath}></v-breadcrumbs>
80 </header> 79 </header>
81 <main>${this._router.outlet()}</main> 80 <main>${this._router.outlet()}</main>
82 <footer> 81 <footer>
83 <bigast-loginbar></bigast-loginbar> 82 <bigast-loginbar></bigast-loginbar>
84 <span><a href="ingest/">Add new videos...</a></span> 83 <span><a href="ingest/">Add new videos...</a></span>
87 } 86 }
88 } 87 }
89 88
90 @customElement("video-page2") 89 @customElement("video-page2")
91 export class VideoPage2 extends LitElement { 90 export class VideoPage2 extends LitElement {
92 @property() showVid?: string; 91 @property() pageData?: PageData;
93 @property() videoListings?: VideoListings;
94 @property() link!: (s: string) => string;
95 @property() dirName?: string;
96 92
97 protected firstUpdated(_changedProperties: PropertyValues): void { 93 protected firstUpdated(_changedProperties: PropertyValues): void {
98 document.addEventListener("keydown", (e) => { 94 document.addEventListener("keydown", (e) => {
99 if (e.key == "Escape") { 95 if (e.key == "Escape") {
100 this.closePlayer(); 96 this.gotoDirListingPage();
101 } 97 }
102 }); 98 });
103 } 99 }
104 100
105 protected update(changedProperties: PropertyValues<this>): void { 101 async connectedCallback() {
106 const resp = changedProperties.has("videoListings"); 102 super.connectedCallback();
107 if (resp) { 103 if (this.pageData?.autoplay) {
108 // if (this.showVid) { 104 //this.playVideo({ detail: { manifest: this.pageData.autoplay.webDataPath } } as CustomEvent);
109 // this.openPlayer(); 105 console.log("we're a player page now", this.pageData.autoplay);
110 // } else { 106 this.startPlayer(this.pageData.autoplay);
111 // this.closePlayer(); 107 // this might not autoplay
112 // } 108 } else {
113 } 109 this.closePlayer();
110 }
111 }
112 protected update(changedProperties: PropertyValues): void {
114 super.update(changedProperties); 113 super.update(changedProperties);
114 if (changedProperties.has("pageData")) {
115 if (this.pageData?.autoplay) {
116 this.startPlayer(this.pageData.autoplay);
117 } else {
118 this.closePlayer();
119 }
120 }
121 console.log("updated", this.pageData?.autoplay);
115 } 122 }
116 123
117 static styles = [ 124 static styles = [
118 unsafeCSS(maincss), 125 unsafeCSS(maincss),
119 css` 126 css`
147 color: white; 154 color: white;
148 padding: 11px; 155 padding: 11px;
149 display: inline-block; 156 display: inline-block;
150 width: 300px; 157 width: 300px;
151 margin: 5px; 158 margin: 5px;
152 border-radius: 2px; 159 border-radius: 5px;
153 } 160 }
154 161
155 .subdir:after { 162 .subdir:after {
156 content: " › "; 163 content: " › ";
157 color: #979797; 164 color: #979797;
164 display: none; 171 display: none;
165 } 172 }
166 `, 173 `,
167 ]; 174 ];
168 175
169 thumbSrc(v: VideoFile) { 176 thumbSrc(vf: VideoFile) {
170 return "/video/api/thumbnail?webRelPath=" + encodeURIComponent(v.webRelPath); 177 return "/video/api/thumbnail?webRelPath=" + encodeURIComponent(vf.webRelPath);
171 } 178 }
172 179
173 renderSubdir(subdir: Subdir): TemplateResult { 180 renderSubdir(subdir: Subdir): TemplateResult {
174 return html`<a href="${this.link(subdir.path) + "/"}"><div class="subdir">${subdir.label}</div></a>`; 181 return html`<a href="/video${subdir.path}"><div class="subdir">${subdir.label}</div></a>`;
175 } 182 }
176 183
177 renderVideoListing(video: VideoFile) { 184 renderVideoListing(video: VideoFile) {
178 return html`<video-section 185 return html`<video-section
179 @playVideo=${this.playVideo} 186 @playVideo=${this.gotoVideoPlayerPage}
187 webRelPath=${video.webRelPath}
180 thumbRelPath=${this.thumbSrc(video)} 188 thumbRelPath=${this.thumbSrc(video)}
181 title="${video.label}" 189 title="${video.label}"
182 manifest="/video/files/${video.webDataPath}" 190 manifest="/video/files${video.webDataPath}"
183 ></video-section>`; 191 ></video-section>`;
184 } 192 }
185 193
186 render() { 194 render() {
187 if (this.videoListings == null) { 195 if (this.pageData == null) {
188 return html`<div>Loading...</div>`; 196 return html`<div>Loading...</div>`;
189 } 197 }
190 const listings = [ 198 const listings = [
191 html`${this.videoListings.subdirs.map((s) => this.renderSubdir(s))}`, // 199 html`${this.pageData.subdirs.map((s) => this.renderSubdir(s))}`, //
192 html`${this.videoListings.videos.map((v) => this.renderVideoListing(v))}`, 200 html`<div>${this.pageData.videos.map((v) => this.renderVideoListing(v))}</div>`,
193 ]; 201 ];
194 const dirTitle = this.dirName ? html`<h2>${this.dirName.slice(0, -1)}</h2>` : html``; 202 const dirTitle = this.pageData.dirLabel ? html`<h2>${this.pageData.dirLabel}</h2>` : html``;
195 return html` 203 return html`
196 ${dirTitle} 204 ${dirTitle}
197 <div class="listing">${listings}</div> 205 <div class="listing">${listings}</div>
198 206
199 <div id="scrim" @click=${this.closePlayer}></div> 207 <div id="scrim" @click=${this.gotoDirListingPage}></div>
200 <page-player manifest=""></page-player> 208 <page-player manifest=""></page-player>
201 `; 209 `;
202 } 210 }
203 211
204 escapeALittle(fileUri: string): string { 212 escapeALittle(fileUri: string): string {
205 return fileUri.replace("#", encodeURIComponent("#")); 213 return fileUri.replace("#", encodeURIComponent("#"));
206 } 214 }
207 215 @queryAsync("page-player") pagePlayer: Promise<PagePlayer>;
208 playVideo(ev: CustomEvent) { 216
209 const player = this.shadowRoot!.querySelector("page-player")! as PagePlayer; 217 async gotoVideoPlayerPage(ev: CustomEvent) {
210 218 const player = await this.pagePlayer;
211 player.manifest = this.escapeALittle(ev.detail.manifest); 219 console.log("playing", player, ev.detail.manifest, ev.detail.webRelPath);
220 this.goto("/video" + ev.detail.webRelPath);
221 }
222 async gotoDirListingPage() {
223 this.goto("/video" + this.pageData?.webDirRelPath);
224 }
225
226 async startPlayer(p: VideoFile) {
227 const player = await this.pagePlayer;
228 player.manifest = this.escapeALittle("/video/files" + p.webDataPath);
212 const sv = player.shadowRoot!.querySelector("shaka-video")! as ShakaVideoElement; 229 const sv = player.shadowRoot!.querySelector("shaka-video")! as ShakaVideoElement;
213 230
214 sv.src = player.manifest; 231 sv.src = player.manifest;
215 sv.autoplay = true; 232 sv.autoplay = true;
216 player.size = "big"; 233 player.size = "big";
217 this.shadowRoot!.querySelector("#scrim")!.style.display = "block"; 234 this.shadowRoot!.querySelector("#scrim")!.style.display = "block";
218 } 235 }
219 closePlayer() { 236 goto(url: string) {
220 const player = this.shadowRoot!.querySelector("page-player")! as PagePlayer; 237 window.rr.goto(url);
238 window.history.pushState({}, "", url);
239 }
240 async closePlayer() {
241 const player = await this.pagePlayer;
242 if (player.size === "hidden") {
243 console.log("wasn't playing", player.size);
244 return;
245 }
246 // return;
247 // this.goto("/video/" + this.pageData?.webDirRelPath);
221 player.size = "hidden"; 248 player.size = "hidden";
222 249
223 this.shadowRoot!.querySelector("#scrim")!.style.display = "none"; 250 this.shadowRoot!.querySelector("#scrim")!.style.display = "none";
224 } 251 }
225 } 252 }