Mercurial > code > home > repos > video
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 } |