← Commit history

adom-video: take the video as video= (the wiki inlines a src file into the frame)

John Lauer ·1df1bc2e8d ·7d ago ·parent 48b6fc4
3 files changed +11−10
README.md+2−2
@@ -45,7 +45,7 @@ Registry caps: 32 widgets, 12 attrs each. A malformed entry is dropped, never br  ## Current widgets -Five load from adom/component-widgets and take a JSON `src`; adom-stock and adom-video load from this page (adom-video takes a video `src`); adom-symbol-variants loads from adom/adom-symbol and takes a component page.json `src`:+Five load from adom/component-widgets and take a JSON `src`; adom-stock and adom-video load from this page (adom-video takes `video`, not `src`: the wiki inlines a `src` file into the frame); adom-symbol-variants loads from adom/adom-symbol and takes a component page.json `src`:  | tag | shows | |---|---|@@ -59,5 +59,5 @@ Five load from adom/component-widgets and take a JSON `src`; adom-stock and adom | `::adom-stock{src="/api/pages/OWNER/SLUG/stock"}` | live stock and price at Mouser, DigiKey and LCSC for a hardware page, from the wiki's cached vendor lookups (adom/wiki#186); component pages show it in their sidebar by themselves; `heading` | | `::adom-verified{src="..."}` | the "verified by" trust mark: big green check, who verified the page and when, per-variant coverage, and the full verification log with evidence; turns amber when a verified file changes; takes the page's `verification.json` — loads from adom/component-widgets | | `::adom-models{src="..."}` | every 3D file on a component page as a card: thumbnail, STEP or GLB, etched or unmarked and where the part number sits, what makes it different from its siblings, what it was derived from, a download, and whether the page still serves the file that was described (checked against the file listing, nothing downloaded); previous versions fold away; takes the page's `models.json` (adom/models@1) — loads from adom/component-widgets |-| `::adom-video{src="..."}` | a YouTube-style video player: every YouTube keyboard shortcut (Space/K, J/L, arrows, 0 to 9, `,` `.` frames, `<` `>` speed, C captions, F, I, M, ?), a 24px timeline grab zone with drag-to-scrub and a hover frame preview, chapters as bar segments, captions, speed and loop; `title` `poster` `chapters` (JSON `[{"t":12,"title":"..."}]` or WebVTT) `captions` (.vtt or .srt) `start` (`90` or `1m30s`) `loop` `muted` `autoplay`; F opens the player full-window in a new tab (widget frames cannot go fullscreen); loads from this page |+| `::adom-video{video="..."}` | a YouTube-style video player: every YouTube keyboard shortcut (Space/K, J/L, arrows, 0 to 9, `,` `.` frames, `<` `>` speed, C captions, F, I, M, ?), a 24px timeline grab zone with drag-to-scrub and a hover frame preview, chapters as bar segments, captions, speed and loop; `title` `poster` `chapters` (JSON `[{"t":12,"title":"..."}]` or WebVTT) `captions` (.vtt or .srt) `start` (`90` or `1m30s`) `loop` `muted` `autoplay`; F opens the player full-window in a new tab (widget frames cannot go fullscreen); loads from this page | | `::adom-3d{src="..."}` | any GLB in the canonical Adom 3D Viewer, click to load (nothing loads until the reader asks; it unloads offscreen and releases the WebGL context); `poster` `heading` `caption`; publish the GLB without Draco, the frame cannot reach the decoder CDN; loads from adom/component-widgets |
widgets.json+1−1
@@ -161,7 +161,7 @@   "script": "https://wiki.adom.inc/adom/widgets/render/widgets/video-widget.js",   "element": "adom-video",   "attrs": [-   "src",+   "video",    "title",    "poster",    "chapters",
widgets/video-widget.js+8−7
@@ -1,13 +1,14 @@ // <adom-video>: a YouTube-style video player for wiki pages. //-//   ::adom-video{src="/OWNER/SLUG/render/demo.mp4" title="Demo" chapters="/OWNER/SLUG/render/demo.chapters.json"}+//   ::adom-video{video="/OWNER/SLUG/render/demo.mp4" title="Demo" chapters="/OWNER/SLUG/render/demo.chapters.json"} // // Every YouTube keyboard shortcut, a timeline you can actually hit (a 24px grab zone around a // 4px bar, drag to scrub, hover for a frame preview and the time), chapters as bar segments, // captions (WebVTT or SRT, drawn by the player so + / - can resize them), speed and loop in a // settings menu, picture-in-picture, fullscreen. Self-contained: Shadow DOM, no dependencies. //-// Attributes: src (required), title, poster, chapters (JSON [{t,title}] or a WebVTT chapter+// Attributes: video (required; `src` also works outside the registry, but the wiki inlines a `src`+// file into the widget frame, so a registered tag must use `video`), title, poster, chapters (JSON [{t,title}] or a WebVTT chapter // file), captions (.vtt or .srt), start (seconds or 1m30s), loop, muted, autoplay. // // Inside a wiki widget frame the browser blocks fullscreen (the frame carries no allow=@@ -206,7 +207,7 @@   let active = null;    class AdomVideo extends HTMLElement {-    static get observedAttributes() { return ['src', 'title', 'poster', 'chapters', 'captions', 'start', 'loop', 'muted']; }+    static get observedAttributes() { return ['video', 'src', 'title', 'poster', 'chapters', 'captions', 'start', 'loop', 'muted']; }      constructor() {       super();@@ -265,17 +266,17 @@       this._load();     }     disconnectedCallback() { if (active === this) active = null; clearTimeout(this._t.hide); this.v.pause(); }-    attributeChangedCallback(n, a, b) { if (a !== b && this.isConnected && this._loaded) { if (n === 'src') this._load(); else if (n === 'chapters') this._chapters(); else if (n === 'captions') this._captions(); else if (n === 'title') this.$('.top').textContent = b || ''; } }+    attributeChangedCallback(n, a, b) { if (a !== b && this.isConnected && this._loaded) { if (n === 'video' || n === 'src') this._load(); else if (n === 'chapters') this._chapters(); else if (n === 'captions') this._captions(); else if (n === 'title') this.$('.top').textContent = b || ''; } }      // absolute URL for a wiki path (the frame is opaque-origin, so resolve against the real location)     _abs(u) { try { return new URL(u, location.href).href; } catch { return u; } }      _load() {       this._loaded = true;-      const src = this.getAttribute('src');+      const src = this.getAttribute('video') || this.getAttribute('src');       const v = this.v;       this.$('.top').textContent = this.getAttribute('title') || '';-      if (!src) return this._error('No video: add <code>src="/OWNER/SLUG/render/video.mp4"</code> to the tag.');+      if (!src) return this._error('No video: add <code>video="/OWNER/SLUG/render/video.mp4"</code> to the tag.');       this.$('.err').classList.remove('on');       if (this.getAttribute('poster')) v.poster = this._abs(this.getAttribute('poster'));       v.loop = this.hasAttribute('loop');@@ -568,7 +569,7 @@       ['playing', 'canplay', 'seeked', 'pause'].forEach((e) => v.addEventListener(e, () => p.classList.remove('wait')));       v.addEventListener('seeked', () => { this._seekBusy = false; if (this._seekPending != null) { const t = this._seekPending; this._seekPending = null; this._seekBusy = true; v.fastSeek ? v.fastSeek(t) : (v.currentTime = t); } });       v.addEventListener('error', () => {-        const s = this.getAttribute('src') || '';+        const s = this.getAttribute('video') || this.getAttribute('src') || '';         this._error(`This video could not be loaded.<br><code>${esc(s)}</code><br><br>Check the path is <code>/OWNER/SLUG/render/FILE.mp4</code> and that the file is on the page. On a private page the widget frame may not be signed in.`);       });       document.addEventListener('fullscreenchange', () => this._sync());