@mattebox/player 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/NOTICE +24 -0
- package/README.md +22 -0
- package/dist/cdn/mattebox-player.min.js +340 -0
- package/dist/controls/bar.d.ts +56 -0
- package/dist/controls/bar.d.ts.map +1 -0
- package/dist/controls/bar.js +245 -0
- package/dist/controls/bar.js.map +1 -0
- package/dist/controls/buttons.d.ts +13 -0
- package/dist/controls/buttons.d.ts.map +1 -0
- package/dist/controls/buttons.js +157 -0
- package/dist/controls/buttons.js.map +1 -0
- package/dist/controls/control.d.ts +12 -0
- package/dist/controls/control.d.ts.map +1 -0
- package/dist/controls/control.js +2 -0
- package/dist/controls/control.js.map +1 -0
- package/dist/controls/cues.d.ts +33 -0
- package/dist/controls/cues.d.ts.map +1 -0
- package/dist/controls/cues.js +170 -0
- package/dist/controls/cues.js.map +1 -0
- package/dist/controls/fullscreen.d.ts +21 -0
- package/dist/controls/fullscreen.d.ts.map +1 -0
- package/dist/controls/fullscreen.js +78 -0
- package/dist/controls/fullscreen.js.map +1 -0
- package/dist/controls/icons.d.ts +35 -0
- package/dist/controls/icons.d.ts.map +1 -0
- package/dist/controls/icons.js +76 -0
- package/dist/controls/icons.js.map +1 -0
- package/dist/controls/keys.d.ts +26 -0
- package/dist/controls/keys.d.ts.map +1 -0
- package/dist/controls/keys.js +66 -0
- package/dist/controls/keys.js.map +1 -0
- package/dist/controls/live.d.ts +10 -0
- package/dist/controls/live.d.ts.map +1 -0
- package/dist/controls/live.js +59 -0
- package/dist/controls/live.js.map +1 -0
- package/dist/controls/menu.d.ts +34 -0
- package/dist/controls/menu.d.ts.map +1 -0
- package/dist/controls/menu.js +225 -0
- package/dist/controls/menu.js.map +1 -0
- package/dist/controls/menus.d.ts +43 -0
- package/dist/controls/menus.d.ts.map +1 -0
- package/dist/controls/menus.js +262 -0
- package/dist/controls/menus.js.map +1 -0
- package/dist/controls/options.d.ts +44 -0
- package/dist/controls/options.d.ts.map +1 -0
- package/dist/controls/options.js +54 -0
- package/dist/controls/options.js.map +1 -0
- package/dist/controls/pip.d.ts +16 -0
- package/dist/controls/pip.d.ts.map +1 -0
- package/dist/controls/pip.js +53 -0
- package/dist/controls/pip.js.map +1 -0
- package/dist/controls/ranges.d.ts +24 -0
- package/dist/controls/ranges.d.ts.map +1 -0
- package/dist/controls/ranges.js +48 -0
- package/dist/controls/ranges.js.map +1 -0
- package/dist/controls/screens.d.ts +22 -0
- package/dist/controls/screens.d.ts.map +1 -0
- package/dist/controls/screens.js +69 -0
- package/dist/controls/screens.js.map +1 -0
- package/dist/controls/seek.d.ts +52 -0
- package/dist/controls/seek.d.ts.map +1 -0
- package/dist/controls/seek.js +215 -0
- package/dist/controls/seek.js.map +1 -0
- package/dist/controls/slider.d.ts +28 -0
- package/dist/controls/slider.d.ts.map +1 -0
- package/dist/controls/slider.js +145 -0
- package/dist/controls/slider.js.map +1 -0
- package/dist/controls/speed.d.ts +8 -0
- package/dist/controls/speed.d.ts.map +1 -0
- package/dist/controls/speed.js +34 -0
- package/dist/controls/speed.js.map +1 -0
- package/dist/controls/time.d.ts +8 -0
- package/dist/controls/time.d.ts.map +1 -0
- package/dist/controls/time.js +25 -0
- package/dist/controls/time.js.map +1 -0
- package/dist/controls/volume.d.ts +8 -0
- package/dist/controls/volume.d.ts.map +1 -0
- package/dist/controls/volume.js +29 -0
- package/dist/controls/volume.js.map +1 -0
- package/dist/dom.d.ts +18 -0
- package/dist/dom.d.ts.map +1 -0
- package/dist/dom.js +45 -0
- package/dist/dom.js.map +1 -0
- package/dist/element.d.ts +101 -0
- package/dist/element.d.ts.map +1 -0
- package/dist/element.js +327 -0
- package/dist/element.js.map +1 -0
- package/dist/es2015/_virtual/_@oxc-project_runtime@0.148.0/helpers/esm/asyncToGenerator.js +27 -0
- package/dist/es2015/_virtual/_@oxc-project_runtime@0.148.0/helpers/esm/defineProperty.js +12 -0
- package/dist/es2015/_virtual/_@oxc-project_runtime@0.148.0/helpers/esm/objectSpread2.js +25 -0
- package/dist/es2015/_virtual/_@oxc-project_runtime@0.148.0/helpers/esm/toPrimitive.js +14 -0
- package/dist/es2015/_virtual/_@oxc-project_runtime@0.148.0/helpers/esm/toPropertyKey.js +9 -0
- package/dist/es2015/_virtual/_@oxc-project_runtime@0.148.0/helpers/esm/typeof.js +11 -0
- package/dist/es2015/controls/bar.js +227 -0
- package/dist/es2015/controls/buttons.js +146 -0
- package/dist/es2015/controls/cues.js +153 -0
- package/dist/es2015/controls/fullscreen.js +54 -0
- package/dist/es2015/controls/icons.js +50 -0
- package/dist/es2015/controls/keys.js +60 -0
- package/dist/es2015/controls/live.js +60 -0
- package/dist/es2015/controls/menu.js +226 -0
- package/dist/es2015/controls/menus.js +258 -0
- package/dist/es2015/controls/options.js +46 -0
- package/dist/es2015/controls/pip.js +39 -0
- package/dist/es2015/controls/ranges.js +51 -0
- package/dist/es2015/controls/screens.js +74 -0
- package/dist/es2015/controls/seek.js +203 -0
- package/dist/es2015/controls/slider.js +136 -0
- package/dist/es2015/controls/speed.js +41 -0
- package/dist/es2015/controls/time.js +27 -0
- package/dist/es2015/controls/volume.js +30 -0
- package/dist/es2015/dom.js +43 -0
- package/dist/es2015/element.js +311 -0
- package/dist/es2015/index.js +10 -0
- package/dist/es2015/namespaces.js +7 -0
- package/dist/es2015/panels/drm.js +29 -0
- package/dist/es2015/panels/error.js +25 -0
- package/dist/es2015/panels/live.js +48 -0
- package/dist/es2015/panels/quality.js +52 -0
- package/dist/es2015/panels/tracks.js +47 -0
- package/dist/es2015/presets.js +46 -0
- package/dist/es2015/style.js +347 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -0
- package/dist/namespaces.d.ts +45 -0
- package/dist/namespaces.d.ts.map +1 -0
- package/dist/namespaces.js +5 -0
- package/dist/namespaces.js.map +1 -0
- package/dist/panels/drm.d.ts +8 -0
- package/dist/panels/drm.d.ts.map +1 -0
- package/dist/panels/drm.js +36 -0
- package/dist/panels/drm.js.map +1 -0
- package/dist/panels/error.d.ts +13 -0
- package/dist/panels/error.d.ts.map +1 -0
- package/dist/panels/error.js +23 -0
- package/dist/panels/error.js.map +1 -0
- package/dist/panels/live.d.ts +3 -0
- package/dist/panels/live.d.ts.map +1 -0
- package/dist/panels/live.js +56 -0
- package/dist/panels/live.js.map +1 -0
- package/dist/panels/panel.d.ts +13 -0
- package/dist/panels/panel.d.ts.map +1 -0
- package/dist/panels/panel.js +2 -0
- package/dist/panels/panel.js.map +1 -0
- package/dist/panels/quality.d.ts +3 -0
- package/dist/panels/quality.d.ts.map +1 -0
- package/dist/panels/quality.js +59 -0
- package/dist/panels/quality.js.map +1 -0
- package/dist/panels/tracks.d.ts +3 -0
- package/dist/panels/tracks.d.ts.map +1 -0
- package/dist/panels/tracks.js +49 -0
- package/dist/panels/tracks.js.map +1 -0
- package/dist/presets.d.ts +23 -0
- package/dist/presets.d.ts.map +1 -0
- package/dist/presets.js +36 -0
- package/dist/presets.js.map +1 -0
- package/dist/style.d.ts +13 -0
- package/dist/style.d.ts.map +1 -0
- package/dist/style.js +345 -0
- package/dist/style.js.map +1 -0
- package/package.json +58 -0
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The bar's knobs. Every one has a default, every one can come from
|
|
3
|
+
* JavaScript through `define({ controls })` or the constructor, and every
|
|
4
|
+
* one has an attribute in kebab-case that wins over both, so a page that
|
|
5
|
+
* writes only markup still gets to set them.
|
|
6
|
+
*/
|
|
7
|
+
export interface ControlsOptions {
|
|
8
|
+
/** Milliseconds of pointer stillness before the bar hides while playing. */
|
|
9
|
+
readonly idleMs?: number;
|
|
10
|
+
/** Seconds an arrow key moves the playhead. */
|
|
11
|
+
readonly seekStep?: number;
|
|
12
|
+
/** Seconds Page Up and Page Down move it. */
|
|
13
|
+
readonly seekPage?: number;
|
|
14
|
+
/** Seconds the skip-back button moves it. Zero leaves that button out. */
|
|
15
|
+
readonly skipBack?: number;
|
|
16
|
+
/** Seconds the skip-forward button moves it. Zero leaves that button out. */
|
|
17
|
+
readonly skipForward?: number;
|
|
18
|
+
/** Whether the large play sits over the picture while paused. Zero leaves it out. */
|
|
19
|
+
readonly start?: number;
|
|
20
|
+
/**
|
|
21
|
+
* How many forward buffer goals long the availability window must be for
|
|
22
|
+
* a live stream to be seekable. Below it the bar shows no seek bar: a
|
|
23
|
+
* window barely wider than the buffer is nowhere to go. Zero makes every
|
|
24
|
+
* live stream seekable.
|
|
25
|
+
*/
|
|
26
|
+
readonly liveWindow?: number;
|
|
27
|
+
/**
|
|
28
|
+
* Which controls the buttons row carries, in order, by name, with `|`
|
|
29
|
+
* between the left and the right cluster. A name left out is a control
|
|
30
|
+
* left out. See LAYOUT for the default and the names.
|
|
31
|
+
*/
|
|
32
|
+
readonly layout?: string;
|
|
33
|
+
}
|
|
34
|
+
export type Controls = Required<ControlsOptions>;
|
|
35
|
+
/** The buttons row by default. `drm`, the lock, is a control the row can carry and does not by default. */
|
|
36
|
+
export declare const LAYOUT = "skip-back play skip-forward volume | speed subtitles audio quality pip fullscreen";
|
|
37
|
+
export declare const DEFAULTS: Controls;
|
|
38
|
+
/** Each option's attribute. */
|
|
39
|
+
export declare const ATTRIBUTES: Readonly<Record<keyof Controls, string>>;
|
|
40
|
+
/** The attribute where it is a non-negative number, else the option, else the default; the layout as given. */
|
|
41
|
+
export declare function resolve(options: ControlsOptions | undefined, attribute: (name: string) => string | null): Controls;
|
|
42
|
+
/** A layout as two lists of names, left and right of the `|`. Unknown names are the caller's to drop. */
|
|
43
|
+
export declare function clusters(layout: string): [string[], string[]];
|
|
44
|
+
//# sourceMappingURL=options.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"options.d.ts","sourceRoot":"","sources":["../../src/controls/options.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,MAAM,WAAW,eAAe;IAC9B,4EAA4E;IAC5E,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,+CAA+C;IAC/C,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,6CAA6C;IAC7C,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,0EAA0E;IAC1E,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,6EAA6E;IAC7E,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,qFAAqF;IACrF,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB;;;;;OAKG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B;;;;OAIG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,MAAM,QAAQ,GAAG,QAAQ,CAAC,eAAe,CAAC,CAAC;AAEjD,2GAA2G;AAC3G,eAAO,MAAM,MAAM,sFACkE,CAAC;AAEtF,eAAO,MAAM,QAAQ,EAAE,QAStB,CAAC;AAEF,+BAA+B;AAC/B,eAAO,MAAM,UAAU,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,QAAQ,EAAE,MAAM,CAAC,CAS/D,CAAC;AAOF,+GAA+G;AAC/G,wBAAgB,OAAO,CACrB,OAAO,EAAE,eAAe,GAAG,SAAS,EACpC,SAAS,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI,GACzC,QAAQ,CAeV;AAED,yGAAyG;AACzG,wBAAgB,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,CAAC,MAAM,EAAE,EAAE,MAAM,EAAE,CAAC,CAI7D"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The bar's knobs. Every one has a default, every one can come from
|
|
3
|
+
* JavaScript through `define({ controls })` or the constructor, and every
|
|
4
|
+
* one has an attribute in kebab-case that wins over both, so a page that
|
|
5
|
+
* writes only markup still gets to set them.
|
|
6
|
+
*/
|
|
7
|
+
/** The buttons row by default. `drm`, the lock, is a control the row can carry and does not by default. */
|
|
8
|
+
export const LAYOUT = 'skip-back play skip-forward volume | speed subtitles audio quality pip fullscreen';
|
|
9
|
+
export const DEFAULTS = {
|
|
10
|
+
idleMs: 3000,
|
|
11
|
+
seekStep: 5,
|
|
12
|
+
seekPage: 30,
|
|
13
|
+
skipBack: 10,
|
|
14
|
+
skipForward: 10,
|
|
15
|
+
start: 1,
|
|
16
|
+
liveWindow: 3,
|
|
17
|
+
layout: LAYOUT,
|
|
18
|
+
};
|
|
19
|
+
/** Each option's attribute. */
|
|
20
|
+
export const ATTRIBUTES = {
|
|
21
|
+
idleMs: 'idle-ms',
|
|
22
|
+
seekStep: 'seek-step',
|
|
23
|
+
seekPage: 'seek-page',
|
|
24
|
+
skipBack: 'skip-back',
|
|
25
|
+
skipForward: 'skip-forward',
|
|
26
|
+
start: 'start',
|
|
27
|
+
liveWindow: 'live-window',
|
|
28
|
+
layout: 'layout',
|
|
29
|
+
};
|
|
30
|
+
const NUMERIC = Object.keys(DEFAULTS).filter((key) => key !== 'layout');
|
|
31
|
+
/** The attribute where it is a non-negative number, else the option, else the default; the layout as given. */
|
|
32
|
+
export function resolve(options, attribute) {
|
|
33
|
+
const out = {};
|
|
34
|
+
for (const key of NUMERIC) {
|
|
35
|
+
const raw = attribute(ATTRIBUTES[key]);
|
|
36
|
+
const set = raw === null ? Number.NaN : Number(raw);
|
|
37
|
+
const given = options?.[key];
|
|
38
|
+
out[key] =
|
|
39
|
+
Number.isFinite(set) && set >= 0
|
|
40
|
+
? set
|
|
41
|
+
: given !== undefined && Number.isFinite(given) && given >= 0
|
|
42
|
+
? given
|
|
43
|
+
: DEFAULTS[key];
|
|
44
|
+
}
|
|
45
|
+
out.layout = attribute(ATTRIBUTES.layout) ?? options?.layout ?? DEFAULTS.layout;
|
|
46
|
+
return out;
|
|
47
|
+
}
|
|
48
|
+
/** A layout as two lists of names, left and right of the `|`. Unknown names are the caller's to drop. */
|
|
49
|
+
export function clusters(layout) {
|
|
50
|
+
const [left = '', right = ''] = layout.split('|');
|
|
51
|
+
const names = (part) => part.split(/\s+/).filter((name) => name !== '');
|
|
52
|
+
return [names(left), names(right)];
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=options.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"options.js","sourceRoot":"","sources":["../../src/controls/options.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAgCH,2GAA2G;AAC3G,MAAM,CAAC,MAAM,MAAM,GACjB,mFAAmF,CAAC;AAEtF,MAAM,CAAC,MAAM,QAAQ,GAAa;IAChC,MAAM,EAAE,IAAI;IACZ,QAAQ,EAAE,CAAC;IACX,QAAQ,EAAE,EAAE;IACZ,QAAQ,EAAE,EAAE;IACZ,WAAW,EAAE,EAAE;IACf,KAAK,EAAE,CAAC;IACR,UAAU,EAAE,CAAC;IACb,MAAM,EAAE,MAAM;CACf,CAAC;AAEF,+BAA+B;AAC/B,MAAM,CAAC,MAAM,UAAU,GAA6C;IAClE,MAAM,EAAE,SAAS;IACjB,QAAQ,EAAE,WAAW;IACrB,QAAQ,EAAE,WAAW;IACrB,QAAQ,EAAE,WAAW;IACrB,WAAW,EAAE,cAAc;IAC3B,KAAK,EAAE,OAAO;IACd,UAAU,EAAE,aAAa;IACzB,MAAM,EAAE,QAAQ;CACjB,CAAC;AAGF,MAAM,OAAO,GAAI,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAmC,CAAC,MAAM,CAC7E,CAAC,GAAG,EAAkB,EAAE,CAAC,GAAG,KAAK,QAAQ,CAC1C,CAAC;AAEF,+GAA+G;AAC/G,MAAM,UAAU,OAAO,CACrB,OAAoC,EACpC,SAA0C;IAE1C,MAAM,GAAG,GAAoC,EAAE,CAAC;IAChD,KAAK,MAAM,GAAG,IAAI,OAAO,EAAE,CAAC;QAC1B,MAAM,GAAG,GAAG,SAAS,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC;QACvC,MAAM,GAAG,GAAG,GAAG,KAAK,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QACpD,MAAM,KAAK,GAAG,OAAO,EAAE,CAAC,GAAG,CAAC,CAAC;QAC7B,GAAG,CAAC,GAAG,CAAC;YACN,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,GAAG,IAAI,CAAC;gBAC9B,CAAC,CAAC,GAAG;gBACL,CAAC,CAAC,KAAK,KAAK,SAAS,IAAI,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,KAAK,IAAI,CAAC;oBAC3D,CAAC,CAAC,KAAK;oBACP,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;IACxB,CAAC;IACD,GAAG,CAAC,MAAM,GAAG,SAAS,CAAC,UAAU,CAAC,MAAM,CAAC,IAAI,OAAO,EAAE,MAAM,IAAI,QAAQ,CAAC,MAAM,CAAC;IAChF,OAAO,GAA0B,CAAC;AACpC,CAAC;AAED,yGAAyG;AACzG,MAAM,UAAU,QAAQ,CAAC,MAAc;IACrC,MAAM,CAAC,IAAI,GAAG,EAAE,EAAE,KAAK,GAAG,EAAE,CAAC,GAAG,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAClD,MAAM,KAAK,GAAG,CAAC,IAAY,EAAY,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,KAAK,EAAE,CAAC,CAAC;IAC1F,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC;AACrC,CAAC"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Picture-in-Picture on the video, through whichever API the browser has:
|
|
3
|
+
* the standard one, or Safari's presentation mode. Neither means no
|
|
4
|
+
* button. Both live on the video itself, which is where the browser draws
|
|
5
|
+
* the floating window from.
|
|
6
|
+
*/
|
|
7
|
+
export interface PictureInPicture {
|
|
8
|
+
/** Whether any API exists. Without one the button is hidden. */
|
|
9
|
+
readonly supported: boolean;
|
|
10
|
+
active(): boolean;
|
|
11
|
+
toggle(): void;
|
|
12
|
+
/** Runs `fn` on every change. Returns the unsubscribe. */
|
|
13
|
+
watch(fn: () => void): () => void;
|
|
14
|
+
}
|
|
15
|
+
export declare function pictureInPicture(video: HTMLVideoElement): PictureInPicture;
|
|
16
|
+
//# sourceMappingURL=pip.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pip.d.ts","sourceRoot":"","sources":["../../src/controls/pip.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAQH,MAAM,WAAW,gBAAgB;IAC/B,gEAAgE;IAChE,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;IAC5B,MAAM,IAAI,OAAO,CAAC;IAClB,MAAM,IAAI,IAAI,CAAC;IACf,0DAA0D;IAC1D,KAAK,CAAC,EAAE,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC;CACnC;AAWD,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,gBAAgB,GAAG,gBAAgB,CA0C1E"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Picture-in-Picture on the video, through whichever API the browser has:
|
|
3
|
+
* the standard one, or Safari's presentation mode. Neither means no
|
|
4
|
+
* button. Both live on the video itself, which is where the browser draws
|
|
5
|
+
* the floating window from.
|
|
6
|
+
*/
|
|
7
|
+
function watching(target, names) {
|
|
8
|
+
return (fn) => {
|
|
9
|
+
for (const name of names)
|
|
10
|
+
target.addEventListener(name, fn);
|
|
11
|
+
return () => {
|
|
12
|
+
for (const name of names)
|
|
13
|
+
target.removeEventListener(name, fn);
|
|
14
|
+
};
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
export function pictureInPicture(video) {
|
|
18
|
+
const safari = video;
|
|
19
|
+
if (typeof video.requestPictureInPicture === 'function' && document.pictureInPictureEnabled) {
|
|
20
|
+
return {
|
|
21
|
+
supported: true,
|
|
22
|
+
active: () => document.pictureInPictureElement === video,
|
|
23
|
+
toggle() {
|
|
24
|
+
// Rejected outside a user gesture, or before the video has metadata;
|
|
25
|
+
// nothing to do with that but drop it.
|
|
26
|
+
const request = document.pictureInPictureElement === video
|
|
27
|
+
? document.exitPictureInPicture()
|
|
28
|
+
: video.requestPictureInPicture();
|
|
29
|
+
request.catch(() => undefined);
|
|
30
|
+
},
|
|
31
|
+
watch: watching(video, ['enterpictureinpicture', 'leavepictureinpicture']),
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
if (typeof safari.webkitSetPresentationMode === 'function' &&
|
|
35
|
+
safari.webkitSupportsPresentationMode?.('picture-in-picture') === true) {
|
|
36
|
+
return {
|
|
37
|
+
supported: true,
|
|
38
|
+
active: () => safari.webkitPresentationMode === 'picture-in-picture',
|
|
39
|
+
toggle() {
|
|
40
|
+
const next = safari.webkitPresentationMode === 'picture-in-picture' ? 'inline' : 'picture-in-picture';
|
|
41
|
+
safari.webkitSetPresentationMode?.(next);
|
|
42
|
+
},
|
|
43
|
+
watch: watching(video, ['webkitpresentationmodechanged']),
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
return {
|
|
47
|
+
supported: false,
|
|
48
|
+
active: () => false,
|
|
49
|
+
toggle() { },
|
|
50
|
+
watch: () => () => { },
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
//# sourceMappingURL=pip.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pip.js","sourceRoot":"","sources":["../../src/controls/pip.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAiBH,SAAS,QAAQ,CAAC,MAAmB,EAAE,KAAwB;IAC7D,OAAO,CAAC,EAAc,EAAgB,EAAE;QACtC,KAAK,MAAM,IAAI,IAAI,KAAK;YAAE,MAAM,CAAC,gBAAgB,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;QAC5D,OAAO,GAAG,EAAE;YACV,KAAK,MAAM,IAAI,IAAI,KAAK;gBAAE,MAAM,CAAC,mBAAmB,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;QACjE,CAAC,CAAC;IACJ,CAAC,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,KAAuB;IACtD,MAAM,MAAM,GAAG,KAA6C,CAAC;IAE7D,IAAI,OAAO,KAAK,CAAC,uBAAuB,KAAK,UAAU,IAAI,QAAQ,CAAC,uBAAuB,EAAE,CAAC;QAC5F,OAAO;YACL,SAAS,EAAE,IAAI;YACf,MAAM,EAAE,GAAG,EAAE,CAAC,QAAQ,CAAC,uBAAuB,KAAK,KAAK;YACxD,MAAM;gBACJ,qEAAqE;gBACrE,uCAAuC;gBACvC,MAAM,OAAO,GACX,QAAQ,CAAC,uBAAuB,KAAK,KAAK;oBACxC,CAAC,CAAC,QAAQ,CAAC,oBAAoB,EAAE;oBACjC,CAAC,CAAC,KAAK,CAAC,uBAAuB,EAAE,CAAC;gBACtC,OAAO,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;YACjC,CAAC;YACD,KAAK,EAAE,QAAQ,CAAC,KAAK,EAAE,CAAC,uBAAuB,EAAE,uBAAuB,CAAC,CAAC;SAC3E,CAAC;IACJ,CAAC;IAED,IACE,OAAO,MAAM,CAAC,yBAAyB,KAAK,UAAU;QACtD,MAAM,CAAC,8BAA8B,EAAE,CAAC,oBAAoB,CAAC,KAAK,IAAI,EACtE,CAAC;QACD,OAAO;YACL,SAAS,EAAE,IAAI;YACf,MAAM,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,sBAAsB,KAAK,oBAAoB;YACpE,MAAM;gBACJ,MAAM,IAAI,GACR,MAAM,CAAC,sBAAsB,KAAK,oBAAoB,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,oBAAoB,CAAC;gBAC3F,MAAM,CAAC,yBAAyB,EAAE,CAAC,IAAI,CAAC,CAAC;YAC3C,CAAC;YACD,KAAK,EAAE,QAAQ,CAAC,KAAK,EAAE,CAAC,+BAA+B,CAAC,CAAC;SAC1D,CAAC;IACJ,CAAC;IAED,OAAO;QACL,SAAS,EAAE,KAAK;QAChB,MAAM,EAAE,GAAG,EAAE,CAAC,KAAK;QACnB,MAAM,KAAU,CAAC;QACjB,KAAK,EAAE,GAAG,EAAE,CAAC,GAAG,EAAE,GAAE,CAAC;KACtB,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The seek bar's arithmetic, pure and node-tested: which span of time the
|
|
3
|
+
* bar maps, and where a time or a range lands on it as a fraction.
|
|
4
|
+
*/
|
|
5
|
+
export interface Span {
|
|
6
|
+
readonly start: number;
|
|
7
|
+
readonly end: number;
|
|
8
|
+
}
|
|
9
|
+
export declare const EMPTY: Span;
|
|
10
|
+
/** A `TimeRanges` as spans. */
|
|
11
|
+
export declare function spans(ranges: TimeRanges): Span[];
|
|
12
|
+
/**
|
|
13
|
+
* The span the bar maps: the last seekable range for live, which is the
|
|
14
|
+
* availability window the engine sets on the MediaSource; zero to the
|
|
15
|
+
* duration for VOD; empty while neither is known.
|
|
16
|
+
*/
|
|
17
|
+
export declare function window(duration: number, seekable: readonly Span[], live: boolean): Span;
|
|
18
|
+
/** Where a time sits on a span, clamped to it. Zero on an empty span. */
|
|
19
|
+
export declare function fraction(time: number, span: Span): number;
|
|
20
|
+
/** The time at a fraction of a span. */
|
|
21
|
+
export declare function at(part: number, span: Span): number;
|
|
22
|
+
/** Ranges clipped to a span, as fraction pairs, dropping what falls outside or collapses. */
|
|
23
|
+
export declare function clip(ranges: readonly Span[], span: Span): Array<readonly [number, number]>;
|
|
24
|
+
//# sourceMappingURL=ranges.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ranges.d.ts","sourceRoot":"","sources":["../../src/controls/ranges.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,WAAW,IAAI;IACnB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;CACtB;AAED,eAAO,MAAM,KAAK,EAAE,IAA2B,CAAC;AAEhD,+BAA+B;AAC/B,wBAAgB,KAAK,CAAC,MAAM,EAAE,UAAU,GAAG,IAAI,EAAE,CAKhD;AAED;;;;GAIG;AACH,wBAAgB,MAAM,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,SAAS,IAAI,EAAE,EAAE,IAAI,EAAE,OAAO,GAAG,IAAI,CAMvF;AAED,yEAAyE;AACzE,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,GAAG,MAAM,CAIzD;AAED,wCAAwC;AACxC,wBAAgB,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,GAAG,MAAM,CAEnD;AAED,6FAA6F;AAC7F,wBAAgB,IAAI,CAAC,MAAM,EAAE,SAAS,IAAI,EAAE,EAAE,IAAI,EAAE,IAAI,GAAG,KAAK,CAAC,SAAS,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAQ1F"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The seek bar's arithmetic, pure and node-tested: which span of time the
|
|
3
|
+
* bar maps, and where a time or a range lands on it as a fraction.
|
|
4
|
+
*/
|
|
5
|
+
export const EMPTY = { start: 0, end: 0 };
|
|
6
|
+
/** A `TimeRanges` as spans. */
|
|
7
|
+
export function spans(ranges) {
|
|
8
|
+
const out = [];
|
|
9
|
+
for (let i = 0; i < ranges.length; i += 1)
|
|
10
|
+
out.push({ start: ranges.start(i), end: ranges.end(i) });
|
|
11
|
+
return out;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* The span the bar maps: the last seekable range for live, which is the
|
|
15
|
+
* availability window the engine sets on the MediaSource; zero to the
|
|
16
|
+
* duration for VOD; empty while neither is known.
|
|
17
|
+
*/
|
|
18
|
+
export function window(duration, seekable, live) {
|
|
19
|
+
if (live) {
|
|
20
|
+
const last = seekable[seekable.length - 1];
|
|
21
|
+
if (last !== undefined && last.end > last.start)
|
|
22
|
+
return last;
|
|
23
|
+
}
|
|
24
|
+
return Number.isFinite(duration) && duration > 0 ? { start: 0, end: duration } : EMPTY;
|
|
25
|
+
}
|
|
26
|
+
/** Where a time sits on a span, clamped to it. Zero on an empty span. */
|
|
27
|
+
export function fraction(time, span) {
|
|
28
|
+
const length = span.end - span.start;
|
|
29
|
+
if (length <= 0)
|
|
30
|
+
return 0;
|
|
31
|
+
return Math.min(1, Math.max(0, (time - span.start) / length));
|
|
32
|
+
}
|
|
33
|
+
/** The time at a fraction of a span. */
|
|
34
|
+
export function at(part, span) {
|
|
35
|
+
return span.start + Math.min(1, Math.max(0, part)) * (span.end - span.start);
|
|
36
|
+
}
|
|
37
|
+
/** Ranges clipped to a span, as fraction pairs, dropping what falls outside or collapses. */
|
|
38
|
+
export function clip(ranges, span) {
|
|
39
|
+
const out = [];
|
|
40
|
+
for (const range of ranges) {
|
|
41
|
+
const start = fraction(range.start, span);
|
|
42
|
+
const end = fraction(range.end, span);
|
|
43
|
+
if (end > start)
|
|
44
|
+
out.push([start, end]);
|
|
45
|
+
}
|
|
46
|
+
return out;
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=ranges.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ranges.js","sourceRoot":"","sources":["../../src/controls/ranges.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAOH,MAAM,CAAC,MAAM,KAAK,GAAS,EAAE,KAAK,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,CAAC;AAEhD,+BAA+B;AAC/B,MAAM,UAAU,KAAK,CAAC,MAAkB;IACtC,MAAM,GAAG,GAAW,EAAE,CAAC;IACvB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC;QACvC,GAAG,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IAC3D,OAAO,GAAG,CAAC;AACb,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,MAAM,CAAC,QAAgB,EAAE,QAAyB,EAAE,IAAa;IAC/E,IAAI,IAAI,EAAE,CAAC;QACT,MAAM,IAAI,GAAG,QAAQ,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;QAC3C,IAAI,IAAI,KAAK,SAAS,IAAI,IAAI,CAAC,GAAG,GAAG,IAAI,CAAC,KAAK;YAAE,OAAO,IAAI,CAAC;IAC/D,CAAC;IACD,OAAO,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,QAAQ,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,GAAG,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC;AACzF,CAAC;AAED,yEAAyE;AACzE,MAAM,UAAU,QAAQ,CAAC,IAAY,EAAE,IAAU;IAC/C,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC;IACrC,IAAI,MAAM,IAAI,CAAC;QAAE,OAAO,CAAC,CAAC;IAC1B,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC;AAChE,CAAC;AAED,wCAAwC;AACxC,MAAM,UAAU,EAAE,CAAC,IAAY,EAAE,IAAU;IACzC,OAAO,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC;AAC/E,CAAC;AAED,6FAA6F;AAC7F,MAAM,UAAU,IAAI,CAAC,MAAuB,EAAE,IAAU;IACtD,MAAM,GAAG,GAAqC,EAAE,CAAC;IACjD,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC3B,MAAM,KAAK,GAAG,QAAQ,CAAC,KAAK,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;QAC1C,MAAM,GAAG,GAAG,QAAQ,CAAC,KAAK,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;QACtC,IAAI,GAAG,GAAG,KAAK;YAAE,GAAG,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,CAAC;IAC1C,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What sits over the picture, apart from the bar: the start button and the
|
|
3
|
+
* error screen. Both live in the stage, so they are inside fullscreen and
|
|
4
|
+
* over the poster, and both are the bar's, mounted and dropped with it.
|
|
5
|
+
*
|
|
6
|
+
* The start button is the large play in the middle of the picture: shown
|
|
7
|
+
* while the video is paused, a replay once it has ended, gone while it
|
|
8
|
+
* plays. The error screen replaces the row under the video: the category
|
|
9
|
+
* and the code, and a retry.
|
|
10
|
+
*/
|
|
11
|
+
import type { PlayerError } from '@mattebox/player-core';
|
|
12
|
+
import type { Control, Flag } from './control.js';
|
|
13
|
+
/** `shown` false keeps it out entirely, which is what the `start` knob at zero asks. */
|
|
14
|
+
export declare function startButton(video: HTMLVideoElement, flag: Flag, shown?: boolean): Control;
|
|
15
|
+
export interface ErrorScreen extends Control {
|
|
16
|
+
show(error: PlayerError): void;
|
|
17
|
+
clear(): void;
|
|
18
|
+
/** Whether an error is on screen, so the start button stays out of its way. */
|
|
19
|
+
shown(): boolean;
|
|
20
|
+
}
|
|
21
|
+
export declare function errorScreen(retry: () => void): ErrorScreen;
|
|
22
|
+
//# sourceMappingURL=screens.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"screens.d.ts","sourceRoot":"","sources":["../../src/controls/screens.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAEzD,OAAO,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,cAAc,CAAC;AAIlD,wFAAwF;AACxF,wBAAgB,WAAW,CAAC,KAAK,EAAE,gBAAgB,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,UAAO,GAAG,OAAO,CAgCtF;AAED,MAAM,WAAW,WAAY,SAAQ,OAAO;IAC1C,IAAI,CAAC,KAAK,EAAE,WAAW,GAAG,IAAI,CAAC;IAC/B,KAAK,IAAI,IAAI,CAAC;IACd,+EAA+E;IAC/E,KAAK,IAAI,OAAO,CAAC;CAClB;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,IAAI,GAAG,WAAW,CAkC1D"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { el } from '../dom.js';
|
|
2
|
+
import { glyph, icon } from './icons.js';
|
|
3
|
+
/** `shown` false keeps it out entirely, which is what the `start` knob at zero asks. */
|
|
4
|
+
export function startButton(video, flag, shown = true) {
|
|
5
|
+
const root = el('button', 'control start-button');
|
|
6
|
+
root.type = 'button';
|
|
7
|
+
const svg = icon('play');
|
|
8
|
+
root.append(svg);
|
|
9
|
+
function render() {
|
|
10
|
+
const paused = video.paused;
|
|
11
|
+
const name = video.ended ? 'replay' : 'play';
|
|
12
|
+
glyph(svg, name);
|
|
13
|
+
root.setAttribute('aria-label', video.ended ? 'Replay' : 'Play');
|
|
14
|
+
root.hidden = !shown || !paused;
|
|
15
|
+
flag('paused', paused);
|
|
16
|
+
}
|
|
17
|
+
function click() {
|
|
18
|
+
video.play().catch(() => undefined);
|
|
19
|
+
}
|
|
20
|
+
const names = ['play', 'pause', 'ended', 'emptied'];
|
|
21
|
+
root.addEventListener('click', click);
|
|
22
|
+
for (const name of names)
|
|
23
|
+
video.addEventListener(name, render);
|
|
24
|
+
render();
|
|
25
|
+
return {
|
|
26
|
+
root,
|
|
27
|
+
dispose() {
|
|
28
|
+
root.removeEventListener('click', click);
|
|
29
|
+
for (const name of names)
|
|
30
|
+
video.removeEventListener(name, render);
|
|
31
|
+
root.remove();
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
export function errorScreen(retry) {
|
|
36
|
+
const root = el('div', 'error-screen');
|
|
37
|
+
root.setAttribute('role', 'alert');
|
|
38
|
+
const box = el('div', 'error-box');
|
|
39
|
+
const title = el('div', 'error-title', 'Playback failed');
|
|
40
|
+
const category = el('span', 'value error-category');
|
|
41
|
+
const code = el('span', 'value error-code');
|
|
42
|
+
const detail = el('div', 'error-detail');
|
|
43
|
+
detail.append(category, el('span', 'error-separator', ' · '), code);
|
|
44
|
+
const button = el('button', 'control error-retry', 'Retry');
|
|
45
|
+
button.type = 'button';
|
|
46
|
+
button.addEventListener('click', retry);
|
|
47
|
+
box.append(title, detail, button);
|
|
48
|
+
root.append(box);
|
|
49
|
+
root.hidden = true;
|
|
50
|
+
return {
|
|
51
|
+
root,
|
|
52
|
+
show(error) {
|
|
53
|
+
category.textContent = error.category;
|
|
54
|
+
code.textContent = error.code;
|
|
55
|
+
root.hidden = false;
|
|
56
|
+
},
|
|
57
|
+
clear() {
|
|
58
|
+
root.hidden = true;
|
|
59
|
+
},
|
|
60
|
+
shown() {
|
|
61
|
+
return !root.hidden;
|
|
62
|
+
},
|
|
63
|
+
dispose() {
|
|
64
|
+
button.removeEventListener('click', retry);
|
|
65
|
+
root.remove();
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
//# sourceMappingURL=screens.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"screens.js","sourceRoot":"","sources":["../../src/controls/screens.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The seek row: the time on the left, the bar, and the duration on the
|
|
3
|
+
* right, with the live button appended by the bar. Four layers on the
|
|
4
|
+
* track, every one a part: the buffered ranges from `video.buffered`, the
|
|
5
|
+
* played span, the hover position, and for live the edge from
|
|
6
|
+
* `engine.live`. The span it maps is the availability window for live,
|
|
7
|
+
* read from `video.seekable`, and the duration for VOD.
|
|
8
|
+
*
|
|
9
|
+
* A live stream is seekable only when the window is worth it: at least
|
|
10
|
+
* `liveWindow` forward buffer goals long, the goal read from the engine.
|
|
11
|
+
* Below that the bar and the time stay hidden and the row is the live
|
|
12
|
+
* button alone. VOD is always seekable. The window is the engine's own
|
|
13
|
+
* where the session offers it: the browser's seekable range on a live
|
|
14
|
+
* MediaSource is the union of that window and whatever is buffered, and
|
|
15
|
+
* grows behind a paused playhead until it crosses any threshold.
|
|
16
|
+
*
|
|
17
|
+
* Reads happen on the events the video already fires; the live window has
|
|
18
|
+
* no event of its own and moves with the clock, so `timeupdate` covers it.
|
|
19
|
+
* While a pointer holds the thumb, the played layer follows the pointer and
|
|
20
|
+
* `currentTime` is written at most once per frame, so the bar never fights
|
|
21
|
+
* a slow seek.
|
|
22
|
+
*
|
|
23
|
+
* The preview above the pointer carries the time and, when the session's
|
|
24
|
+
* `engine.thumbnails` answers for that time, the tile: a rectangle of a
|
|
25
|
+
* sprite, drawn by background position at the sprite's own size and scaled
|
|
26
|
+
* down to `--mbx-preview-width`, so a page sets the size with one token.
|
|
27
|
+
*
|
|
28
|
+
* On a seekable live stream the time and the preview read the wall clock
|
|
29
|
+
* when `engine.pdt` can say it, "14:23:05", which is what a viewer of a
|
|
30
|
+
* broadcast wants to know; the distance behind the edge otherwise.
|
|
31
|
+
*/
|
|
32
|
+
import type { ThumbnailsApi } from 'mattebox/stages/thumbnails';
|
|
33
|
+
import type { LiveApi, PdtApi } from '../namespaces.js';
|
|
34
|
+
import type { Control, Flag } from './control.js';
|
|
35
|
+
import type { Controls } from './options.js';
|
|
36
|
+
import type { Span } from './ranges.js';
|
|
37
|
+
/** What the session feeding the video offers the row, each when it has one. */
|
|
38
|
+
export interface SeekSource {
|
|
39
|
+
readonly live?: LiveApi | undefined;
|
|
40
|
+
readonly thumbnails?: ThumbnailsApi | undefined;
|
|
41
|
+
readonly pdt?: PdtApi | undefined;
|
|
42
|
+
/** The engine's forward buffer goal in seconds, read when the row decides whether a live window is worth seeking. */
|
|
43
|
+
readonly bufferGoal?: (() => number) | undefined;
|
|
44
|
+
/** The engine's sliding availability window, in presentation time, or null before it opens. */
|
|
45
|
+
readonly window?: (() => Span | null) | undefined;
|
|
46
|
+
}
|
|
47
|
+
export interface SeekBar extends Control {
|
|
48
|
+
attach(source: SeekSource): void;
|
|
49
|
+
detach(): void;
|
|
50
|
+
}
|
|
51
|
+
export declare function seekBar(video: HTMLVideoElement, flag: Flag, options: Controls): SeekBar;
|
|
52
|
+
//# sourceMappingURL=seek.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"seek.d.ts","sourceRoot":"","sources":["../../src/controls/seek.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAEhE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AACxD,OAAO,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,cAAc,CAAC;AAClD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAC7C,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;AAKxC,+EAA+E;AAC/E,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IACpC,QAAQ,CAAC,UAAU,CAAC,EAAE,aAAa,GAAG,SAAS,CAAC;IAChD,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAClC,qHAAqH;IACrH,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC,MAAM,MAAM,CAAC,GAAG,SAAS,CAAC;IACjD,+FAA+F;IAC/F,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,SAAS,CAAC;CACnD;AAED,MAAM,WAAW,OAAQ,SAAQ,OAAO;IACtC,MAAM,CAAC,MAAM,EAAE,UAAU,GAAG,IAAI,CAAC;IACjC,MAAM,IAAI,IAAI,CAAC;CAChB;AAuBD,wBAAgB,OAAO,CAAC,KAAK,EAAE,gBAAgB,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,QAAQ,GAAG,OAAO,CAwMvF"}
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
import { el } from '../dom.js';
|
|
2
|
+
import { at, clip, EMPTY, fraction, spans, window } from './ranges.js';
|
|
3
|
+
import { slider } from './slider.js';
|
|
4
|
+
import { clock, describe, format } from './time.js';
|
|
5
|
+
/** What the tile scales down to when the page sets no `--mbx-preview-width`. */
|
|
6
|
+
const PREVIEW_WIDTH = 160;
|
|
7
|
+
/** The buffer goal assumed for a session that reports none. */
|
|
8
|
+
const BUFFER_GOAL = 30;
|
|
9
|
+
/** The events on which the row re-reads the video. */
|
|
10
|
+
const EVENTS = [
|
|
11
|
+
'timeupdate',
|
|
12
|
+
'progress',
|
|
13
|
+
'durationchange',
|
|
14
|
+
'loadedmetadata',
|
|
15
|
+
'seeking',
|
|
16
|
+
'seeked',
|
|
17
|
+
'emptied',
|
|
18
|
+
];
|
|
19
|
+
function percent(part) {
|
|
20
|
+
return `${part * 100}%`;
|
|
21
|
+
}
|
|
22
|
+
export function seekBar(video, flag, options) {
|
|
23
|
+
let source = {};
|
|
24
|
+
let span = EMPTY;
|
|
25
|
+
let pending = null;
|
|
26
|
+
let frame = 0;
|
|
27
|
+
function isLive() {
|
|
28
|
+
const live = source.live;
|
|
29
|
+
return live !== undefined && live.edge !== null;
|
|
30
|
+
}
|
|
31
|
+
/** The wall clock at a presentation time, when the session can say it. */
|
|
32
|
+
function wall(time) {
|
|
33
|
+
const epoch = source.pdt?.toWallClock(time) ?? null;
|
|
34
|
+
return epoch === null ? null : clock(epoch);
|
|
35
|
+
}
|
|
36
|
+
/** What a screen reader hears at a time. */
|
|
37
|
+
function say(time) {
|
|
38
|
+
if (!isLive())
|
|
39
|
+
return describe(time, span.end);
|
|
40
|
+
const behind = `${format(span.end - time)} behind live`;
|
|
41
|
+
const at = wall(time);
|
|
42
|
+
return at === null ? behind : `${at}, ${behind}`;
|
|
43
|
+
}
|
|
44
|
+
/** What the preview and the time show at a time. */
|
|
45
|
+
function label(time) {
|
|
46
|
+
if (!isLive())
|
|
47
|
+
return format(time);
|
|
48
|
+
return wall(time) ?? `-${format(span.end - time)}`;
|
|
49
|
+
}
|
|
50
|
+
function flush() {
|
|
51
|
+
frame = 0;
|
|
52
|
+
if (pending === null)
|
|
53
|
+
return;
|
|
54
|
+
video.currentTime = pending;
|
|
55
|
+
pending = null;
|
|
56
|
+
}
|
|
57
|
+
const current = el('span', 'current-time', '0:00');
|
|
58
|
+
const duration = el('span', 'duration', '0:00');
|
|
59
|
+
const bar = slider({
|
|
60
|
+
name: 'seek',
|
|
61
|
+
label: 'Seek',
|
|
62
|
+
step: options.seekStep,
|
|
63
|
+
page: options.seekPage,
|
|
64
|
+
onInput(value) {
|
|
65
|
+
bar.set(value, say(value));
|
|
66
|
+
current.textContent = label(value);
|
|
67
|
+
if (!bar.dragging()) {
|
|
68
|
+
// The release, or a key: written now, and it supersedes whatever a
|
|
69
|
+
// frame queued during the drag was about to write.
|
|
70
|
+
pending = null;
|
|
71
|
+
video.currentTime = value;
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
pending = value;
|
|
75
|
+
if (frame === 0)
|
|
76
|
+
frame = requestAnimationFrame(flush);
|
|
77
|
+
},
|
|
78
|
+
});
|
|
79
|
+
const root = el('div', 'row seek-row');
|
|
80
|
+
root.append(current, bar.root, duration);
|
|
81
|
+
const buffered = el('div', 'buffered seek-buffered');
|
|
82
|
+
const hover = el('div', 'hover seek-hover');
|
|
83
|
+
const edge = el('div', 'edge seek-edge');
|
|
84
|
+
hover.hidden = true;
|
|
85
|
+
edge.hidden = true;
|
|
86
|
+
// Under the fill, so what is played reads over what is buffered.
|
|
87
|
+
bar.track.prepend(buffered);
|
|
88
|
+
bar.track.append(hover, edge);
|
|
89
|
+
const preview = el('div', 'preview seek-preview');
|
|
90
|
+
const image = el('div', 'preview-image seek-preview-image');
|
|
91
|
+
const tile = el('div', 'preview-tile seek-preview-tile');
|
|
92
|
+
const time = el('span', 'preview-time seek-preview-time');
|
|
93
|
+
image.append(tile);
|
|
94
|
+
image.hidden = true;
|
|
95
|
+
preview.append(image, time);
|
|
96
|
+
preview.hidden = true;
|
|
97
|
+
bar.root.append(preview);
|
|
98
|
+
/** The width the page asked for through the token, or the default. */
|
|
99
|
+
function previewWidth() {
|
|
100
|
+
const set = Number.parseFloat(getComputedStyle(preview).getPropertyValue('--mbx-preview-width'));
|
|
101
|
+
return Number.isFinite(set) && set > 0 ? set : PREVIEW_WIDTH;
|
|
102
|
+
}
|
|
103
|
+
/** The tile for a time, if the track has one, drawn at its size and scaled to fit. */
|
|
104
|
+
function paintTile(when) {
|
|
105
|
+
const found = source.thumbnails?.at(when) ?? null;
|
|
106
|
+
image.hidden = found === null;
|
|
107
|
+
if (found === null)
|
|
108
|
+
return;
|
|
109
|
+
const scale = previewWidth() / found.width;
|
|
110
|
+
image.style.width = `${found.width * scale}px`;
|
|
111
|
+
image.style.height = `${found.height * scale}px`;
|
|
112
|
+
tile.style.width = `${found.width}px`;
|
|
113
|
+
tile.style.height = `${found.height}px`;
|
|
114
|
+
tile.style.backgroundImage = `url("${found.url}")`;
|
|
115
|
+
tile.style.backgroundPosition = `-${found.x}px -${found.y}px`;
|
|
116
|
+
tile.style.transform = `scale(${scale})`;
|
|
117
|
+
}
|
|
118
|
+
function paintBuffered() {
|
|
119
|
+
const parts = clip(spans(video.buffered), span);
|
|
120
|
+
while (buffered.childElementCount > parts.length)
|
|
121
|
+
buffered.lastElementChild?.remove();
|
|
122
|
+
while (buffered.childElementCount < parts.length) {
|
|
123
|
+
buffered.append(el('div', 'buffered-range seek-buffered-range'));
|
|
124
|
+
}
|
|
125
|
+
let i = 0;
|
|
126
|
+
for (const child of buffered.children) {
|
|
127
|
+
const part = parts[i];
|
|
128
|
+
i += 1;
|
|
129
|
+
if (part === undefined)
|
|
130
|
+
break;
|
|
131
|
+
const node = child;
|
|
132
|
+
node.style.left = percent(part[0]);
|
|
133
|
+
node.style.width = percent(part[1] - part[0]);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
/** Whether a live window is wide enough to be worth a bar. */
|
|
137
|
+
function worthSeeking() {
|
|
138
|
+
if (options.liveWindow === 0)
|
|
139
|
+
return true;
|
|
140
|
+
const goal = source.bufferGoal?.() ?? BUFFER_GOAL;
|
|
141
|
+
return span.end - span.start >= options.liveWindow * goal;
|
|
142
|
+
}
|
|
143
|
+
function tick() {
|
|
144
|
+
const on = isLive();
|
|
145
|
+
const own = on ? (source.window?.() ?? null) : null;
|
|
146
|
+
span =
|
|
147
|
+
own !== null && own.end > own.start ? own : window(video.duration, spans(video.seekable), on);
|
|
148
|
+
bar.range(span.start, span.end);
|
|
149
|
+
paintBuffered();
|
|
150
|
+
const live = source.live;
|
|
151
|
+
edge.hidden = !on;
|
|
152
|
+
if (on && live !== undefined && live.edge !== null) {
|
|
153
|
+
edge.style.left = percent(fraction(live.edge, span));
|
|
154
|
+
}
|
|
155
|
+
const seekable = !on || worthSeeking();
|
|
156
|
+
bar.root.hidden = !seekable;
|
|
157
|
+
current.hidden = !seekable;
|
|
158
|
+
duration.hidden = on;
|
|
159
|
+
flag('live', on);
|
|
160
|
+
flag('seekable', seekable);
|
|
161
|
+
if (!on)
|
|
162
|
+
duration.textContent = format(span.end);
|
|
163
|
+
if (!bar.dragging()) {
|
|
164
|
+
bar.set(video.currentTime, say(video.currentTime));
|
|
165
|
+
current.textContent = label(video.currentTime);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
/** The hover marker and the preview follow the pointer, clamped to the bar. */
|
|
169
|
+
function point(event) {
|
|
170
|
+
const rect = bar.rail.getBoundingClientRect();
|
|
171
|
+
if (rect.width === 0 || span.end <= span.start)
|
|
172
|
+
return;
|
|
173
|
+
const part = Math.min(1, Math.max(0, (event.clientX - rect.left) / rect.width));
|
|
174
|
+
hover.hidden = false;
|
|
175
|
+
hover.style.left = percent(part);
|
|
176
|
+
preview.hidden = false;
|
|
177
|
+
const when = at(part, span);
|
|
178
|
+
time.textContent = label(when);
|
|
179
|
+
paintTile(when);
|
|
180
|
+
const whole = bar.root.getBoundingClientRect();
|
|
181
|
+
const half = preview.offsetWidth / 2;
|
|
182
|
+
const x = rect.left - whole.left + part * rect.width;
|
|
183
|
+
preview.style.left = `${Math.min(whole.width - half, Math.max(half, x))}px`;
|
|
184
|
+
}
|
|
185
|
+
function leave() {
|
|
186
|
+
hover.hidden = true;
|
|
187
|
+
preview.hidden = true;
|
|
188
|
+
}
|
|
189
|
+
bar.root.addEventListener('pointermove', point);
|
|
190
|
+
bar.root.addEventListener('pointerleave', leave);
|
|
191
|
+
for (const name of EVENTS)
|
|
192
|
+
video.addEventListener(name, tick);
|
|
193
|
+
tick();
|
|
194
|
+
return {
|
|
195
|
+
root,
|
|
196
|
+
attach(found) {
|
|
197
|
+
source = found;
|
|
198
|
+
tick();
|
|
199
|
+
},
|
|
200
|
+
detach() {
|
|
201
|
+
source = {};
|
|
202
|
+
leave();
|
|
203
|
+
tick();
|
|
204
|
+
},
|
|
205
|
+
dispose() {
|
|
206
|
+
cancelAnimationFrame(frame);
|
|
207
|
+
bar.root.removeEventListener('pointermove', point);
|
|
208
|
+
bar.root.removeEventListener('pointerleave', leave);
|
|
209
|
+
for (const name of EVENTS)
|
|
210
|
+
video.removeEventListener(name, tick);
|
|
211
|
+
bar.dispose();
|
|
212
|
+
},
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
//# sourceMappingURL=seek.js.map
|