| 1 | import * as mediaFiles from "#src/file-viewer/models/media-files.ts"; |
| 2 | import type { MediaFile } from "#src/file-viewer/models/media-files.ts"; |
| 3 | import "./clover-video.css"; |
| 4 | |
| 5 | export interface Input { |
| 6 | id?: string; |
| 7 | file: MediaFile | string; |
| 8 | poster?: MediaFile | string; |
| 9 | header?: Marko.AttrTag<{ content: Marko.Body }>; |
| 10 | minimal?: boolean; |
| 11 | noFigure?: boolean; |
| 12 | } |
| 13 | |
| 14 | <const/{ id, header, minimal, noFigure }=input> |
| 15 | <const/file=( |
| 16 | typeof UNWRAP(input.file) === "object" |
| 17 | ? input.file |
| 18 | : UNWRAP( |
| 19 | mediaFiles.getByPath(input.file), |
| 20 | `No file ${JSON.stringify(input.file)}`, |
| 21 | ) |
| 22 | )> |
| 23 | <const/poster=( |
| 24 | input.poster |
| 25 | ? typeof input.poster === "object" |
| 26 | ? input.poster |
| 27 | : UNWRAP( |
| 28 | mediaFiles.getByPath(input.poster), |
| 29 | `No file ${JSON.stringify(input.poster)}`, |
| 30 | ) |
| 31 | : null |
| 32 | )> |
| 33 | |
| 34 | <const/{ width, height }=mediaFiles.parseDimensions(file) ?? { |
| 35 | width: 1920, |
| 36 | height: 1080, |
| 37 | }> |
| 38 | <!-- which streaming manifests exist ("dash", "hls", or both). the player |
| 39 | must only pick a backend whose manifest is actually present: dash can fail |
| 40 | or lag behind hls for the same file. "pending" tells it to show a |
| 41 | come-back-later note instead of silently failing --> |
| 42 | <const/streams=( |
| 43 | [ |
| 44 | derived.get(file, "dash.mpd") && "dash", |
| 45 | derived.get(file, "master.m3u8") && "hls", |
| 46 | ] |
| 47 | .filter(Boolean) |
| 48 | .join(",") || (file.pending ? "pending" : "none") |
| 49 | )> |
| 50 | |
| 51 | <define/Inner> |
| 52 | <video |
| 53 | ...{ id } |
| 54 | class={ "clover-video": noFigure, minimal: noFigure && minimal } |
| 55 | data-src=file.path |
| 56 | data-streams=streams |
| 57 | poster=poster ? `/file${poster.path}` : null |
| 58 | width=width |
| 59 | height=height |
| 60 | /> |
| 61 | </define> |
| 62 | |
| 63 | <if=!noFigure> |
| 64 | <figure.clover-video class={ minimal }> |
| 65 | <if=!minimal> |
| 66 | <figcaption> |
| 67 | <if=header> |
| 68 | <${header.content}/> |
| 69 | </if> |
| 70 | <else>${file.basename}</else> |
| 71 | </figcaption> |
| 72 | </if> |
| 73 | <div.stack style=`aspect-ratio:${width}/${height}`> |
| 74 | <Inner/> |
| 75 | <noscript.fallback> |
| 76 | <h2>scripting disabled!</h2> |
| 77 | <p> |
| 78 | to play this video, JavaScript must be enabled in your browser. the |
| 79 | default video playback behavior is not enabled here as it results in a |
| 80 | worse experience on slower internet connections, and |
| 81 | <a href="http://caniuse.com/mpeg-dash"> |
| 82 | MPEG-DASH streaming is not natively supported in any browser |
| 83 | </a> |
| 84 | without JavaScript. |
| 85 | </p> |
| 86 | <p> |
| 87 | since disabling JavaScript is not the default in any browser, you |
| 88 | likely wanted to download the raw video file anyways. those links are |
| 89 | available below, but note that you will need an AV1-compatible video |
| 90 | player, such as VLC Media Player or QuickTime Player (M3 chip or |
| 91 | newer) |
| 92 | </p> |
| 93 | </noscript> |
| 94 | </div> |
| 95 | </figure> |
| 96 | </if> |
| 97 | <else> |
| 98 | <Inner/> |
| 99 | </else> |
| 100 | |
| 101 | client import "./clover-video.client.ts"; |
| 102 | |
| 103 | import * as derived from "#src/file-viewer/models/derived.ts"; |
| 104 | import { UNWRAP, ASSERT } from "@clo/lib/assert"; |