@mattebox/player-cast 0.1.0 → 0.3.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/README.md CHANGED
@@ -5,14 +5,16 @@
5
5
 
6
6
  `<mbx-cast-button>` and `<mbx-cast-screen>` for the
7
7
  [Mattebox player](https://github.com/jboix/mattebox-player): Chromecast
8
- from the page, over Google's sender SDK. The button starts and ends a
9
- session, and the screen covers the picture while the receiver plays, with
10
- its name, play and pause, the seek slider, the time and a stop button.
11
- Importing the package registers both elements.
8
+ from the page, over Google's sender SDK.
12
9
 
13
- It is a package of its own because the button loads Google's script onto
14
- the page, which the player never does on an integrator's behalf. The AirPlay
15
- button, over Safari's own API, ships with the player.
10
+ - The button starts and ends a session.
11
+ - The screen covers the picture while the receiver plays. It shows the name of the receiver, play and pause, the seek slider, the time and a stop button.
12
+
13
+ The import registers both elements.
14
+
15
+ The cast is a package of its own because the button loads Google's script
16
+ onto the page. The player never loads third-party code for a page. The
17
+ AirPlay button uses Safari's own API, so it is part of the player.
16
18
 
17
19
  ```sh
18
20
  npm install @mattebox/player-cast @mattebox/player mattebox
@@ -35,12 +37,15 @@ npm install @mattebox/player-cast @mattebox/player mattebox
35
37
  </script>
36
38
  ```
37
39
 
38
- On start the player pauses the video, freezes the engine so nothing is
39
- fetched for a paused picture, and sends the session's source to the
40
- receiver at the video's time. On end the engine resumes and the video
41
- picks up where the receiver stopped. The receiver plays the URL by itself,
42
- so DRM and signed URLs are the page's to arrange through the `castload`
43
- event, which carries the load request before it goes out:
40
+ When a session starts, the player pauses the video and suspends the engine,
41
+ so the engine requests nothing during the cast. It then sends the source of
42
+ the session to the receiver, at the video's current time. When the session
43
+ ends, the engine resumes and the video continues from the time where the
44
+ receiver stopped.
45
+
46
+ The receiver plays the URL by itself, so DRM and signed URLs are the page's
47
+ to arrange, in the `castload` event. The event carries the load request
48
+ before the button sends it:
44
49
 
45
50
  ```ts
46
51
  button.addEventListener('castload', (event) => {
@@ -48,7 +53,8 @@ button.addEventListener('castload', (event) => {
48
53
  });
49
54
  ```
50
55
 
51
- `receiver` on the button names the receiver application, the Default Media
52
- Receiver without one; `sdk="none"` leaves loading the SDK to the page. The
56
+ `receiver` on the button names the receiver application. Without it, the
57
+ button uses the Default Media Receiver. With `sdk="none"`, the page loads
58
+ the SDK instead of the button. The
53
59
  [guide](https://github.com/jboix/mattebox-player/blob/main/docs/guide/03-the-element.md#casting)
54
- covers the rest.
60
+ explains the rest.
@@ -1,65 +1,59 @@
1
- import { cast } from './cast.js';
2
- import { Component } from './component.js';
3
- import { icon } from './icons.js';
4
- import { BUTTON_STYLE, iconSlots, show, style } from './shared.js';
5
- const STATES = ['default', 'active'];
6
- export class MbxCastButton extends Component {
7
- static get observedAttributes() {
8
- return ['label-start', 'label-stop'];
9
- }
10
- constructor() {
11
- super();
12
- this.api = null;
13
- const root = this.attachShadow({ mode: 'open' });
14
- this.button = document.createElement('button');
15
- this.button.type = 'button';
16
- this.button.setAttribute('part', 'button');
17
- this.slots = iconSlots(STATES, (state) => icon(state === 'active' ? 'cast-active' : 'cast'));
18
- for (const state of STATES)
19
- this.button.append(this.slots[state]);
20
- this.button.addEventListener('click', () => {
21
- const api = this.api;
22
- if (api === null)
23
- return;
24
- if (api.state() === 'casting')
25
- api.stop();
26
- else
27
- api.start(this);
28
- });
29
- root.append(style(BUTTON_STYLE), this.button);
30
- }
31
- attach(player) {
32
- const api = cast(player);
33
- this.api = api;
34
- api.connect(this.getAttribute('receiver'), this.getAttribute('sdk'));
35
- this.keep(api.watch(() => {
36
- this.render();
37
- }));
38
- // A session decides whether there is anything to send.
39
- this.follow(player, () => {
40
- this.render();
41
- return undefined;
42
- });
43
- }
44
- detach() {
45
- this.api?.disconnect();
46
- this.api = null;
47
- }
48
- render() {
49
- const player = this.player;
50
- const api = this.api;
51
- if (player === null || api === null)
52
- return;
53
- const state = api.state();
54
- const active = state === 'casting';
55
- this.hidden = state === 'unavailable';
56
- // Nothing loaded means nothing to send; the receiver would show its idle screen.
57
- this.button.disabled = !active && player.player?.session == null;
58
- this.button.toggleAttribute('aria-busy', state === 'connecting');
59
- show(this.slots, active ? 'active' : 'default');
60
- this.button.setAttribute('aria-label', active
61
- ? (this.getAttribute('label-stop') ?? 'Stop casting')
62
- : (this.getAttribute('label-start') ?? 'Cast'));
63
- }
64
- }
1
+ import { cast } from "./cast.js";
2
+ import { Component } from "./component.js";
3
+ import { icon } from "./icons.js";
4
+ import { BUTTON_STYLE, iconSlots, show, style } from "./shared.js";
5
+ //#region src/cast-button.ts
6
+ const STATES = ["default", "active"];
7
+ var MbxCastButton = class extends Component {
8
+ static get observedAttributes() {
9
+ return ["label-start", "label-stop"];
10
+ }
11
+ constructor() {
12
+ super();
13
+ this.api = null;
14
+ const root = this.attachShadow({ mode: "open" });
15
+ this.button = document.createElement("button");
16
+ this.button.type = "button";
17
+ this.button.setAttribute("part", "button");
18
+ this.slots = iconSlots(STATES, (state) => icon(state === "active" ? "cast-active" : "cast"));
19
+ for (const state of STATES) this.button.append(this.slots[state]);
20
+ this.button.addEventListener("click", () => {
21
+ const api = this.api;
22
+ if (api === null) return;
23
+ if (api.state() === "casting") api.stop();
24
+ else api.start(this);
25
+ });
26
+ root.append(style(BUTTON_STYLE), this.button);
27
+ }
28
+ attach(player) {
29
+ const api = cast(player);
30
+ this.api = api;
31
+ api.connect(this.getAttribute("receiver"), this.getAttribute("sdk"));
32
+ this.keep(api.watch(() => {
33
+ this.render();
34
+ }));
35
+ this.follow(player, () => {
36
+ this.render();
37
+ });
38
+ }
39
+ detach() {
40
+ this.api?.disconnect();
41
+ this.api = null;
42
+ }
43
+ render() {
44
+ const player = this.player;
45
+ const api = this.api;
46
+ if (player === null || api === null) return;
47
+ const state = api.state();
48
+ const active = state === "casting";
49
+ this.hidden = state === "unavailable";
50
+ this.button.disabled = !active && player.player?.session == null;
51
+ this.button.toggleAttribute("aria-busy", state === "connecting");
52
+ show(this.slots, active ? "active" : "default");
53
+ this.button.setAttribute("aria-label", active ? this.getAttribute("label-stop") ?? "Stop casting" : this.getAttribute("label-start") ?? "Cast");
54
+ }
55
+ };
56
+ //#endregion
57
+ export { MbxCastButton };
58
+
65
59
  //# sourceMappingURL=cast-button.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"cast-button.js","sourceRoot":"","sources":["../src/cast-button.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"cast-button.js","names":[],"sources":["../src/cast-button.ts"],"sourcesContent":["/**\n * <mbx-cast-button>: starts and ends a Chromecast session for the player,\n * and is hidden where there is no Cast: a browser without the SDK, or no\n * device on the network. `receiver` is the receiver application id, the\n * Default Media Receiver without one; `sdk=\"none\"` leaves loading the\n * sender SDK to the page. The glyph is `icon` or `icon-active`, the name\n * `label-start` or `label-stop`.\n *\n * Before the request goes to the receiver the button dispatches\n * `castload`, cancelable, with the request as `detail`, so a page adds\n * `customData` for its receiver or cancels. The player carries `casting`\n * while the session runs, and the cast screen drives the receiver.\n */\nimport type { Cast } from './cast.js';\nimport { cast } from './cast.js';\nimport { Component } from './component.js';\nimport type { PlayerHost } from './host.js';\nimport { icon } from './icons.js';\nimport { BUTTON_STYLE, iconSlots, show, style } from './shared.js';\n\ntype State = 'default' | 'active';\nconst STATES: readonly State[] = ['default', 'active'];\n\nexport class MbxCastButton extends Component {\n static get observedAttributes(): readonly string[] {\n return ['label-start', 'label-stop'];\n }\n\n declare private readonly button: HTMLButtonElement;\n declare private readonly slots: Record<State, HTMLSlotElement>;\n declare private api: Cast | null;\n\n constructor() {\n super();\n this.api = null;\n const root = this.attachShadow({ mode: 'open' });\n this.button = document.createElement('button');\n this.button.type = 'button';\n this.button.setAttribute('part', 'button');\n this.slots = iconSlots(STATES, (state) => icon(state === 'active' ? 'cast-active' : 'cast'));\n for (const state of STATES) this.button.append(this.slots[state]);\n this.button.addEventListener('click', () => {\n const api = this.api;\n if (api === null) return;\n if (api.state() === 'casting') api.stop();\n else api.start(this);\n });\n root.append(style(BUTTON_STYLE), this.button);\n }\n\n protected override attach(player: PlayerHost): void {\n const api = cast(player);\n this.api = api;\n api.connect(this.getAttribute('receiver'), this.getAttribute('sdk'));\n this.keep(\n api.watch(() => {\n this.render();\n }),\n );\n // A session decides whether there is anything to send.\n this.follow(player, () => {\n this.render();\n return undefined;\n });\n }\n\n protected override detach(): void {\n this.api?.disconnect();\n this.api = null;\n }\n\n protected override render(): void {\n const player = this.player;\n const api = this.api;\n if (player === null || api === null) return;\n const state = api.state();\n const active = state === 'casting';\n this.hidden = state === 'unavailable';\n // Nothing loaded means nothing to send; the receiver would show its idle screen.\n this.button.disabled = !active && player.player?.session == null;\n this.button.toggleAttribute('aria-busy', state === 'connecting');\n show(this.slots, active ? 'active' : 'default');\n this.button.setAttribute(\n 'aria-label',\n active\n ? (this.getAttribute('label-stop') ?? 'Stop casting')\n : (this.getAttribute('label-start') ?? 'Cast'),\n );\n }\n}\n"],"mappings":";;;;;AAqBA,MAAM,SAA2B,CAAC,WAAW,QAAQ;AAErD,IAAa,gBAAb,cAAmC,UAAU;CAC3C,WAAW,qBAAwC;EACjD,OAAO,CAAC,eAAe,YAAY;CACrC;CAMA,cAAc;EACZ,MAAM;EACN,KAAK,MAAM;EACX,MAAM,OAAO,KAAK,aAAa,EAAE,MAAM,OAAO,CAAC;EAC/C,KAAK,SAAS,SAAS,cAAc,QAAQ;EAC7C,KAAK,OAAO,OAAO;EACnB,KAAK,OAAO,aAAa,QAAQ,QAAQ;EACzC,KAAK,QAAQ,UAAU,SAAS,UAAU,KAAK,UAAU,WAAW,gBAAgB,MAAM,CAAC;EAC3F,KAAK,MAAM,SAAS,QAAQ,KAAK,OAAO,OAAO,KAAK,MAAM,MAAM;EAChE,KAAK,OAAO,iBAAiB,eAAe;GAC1C,MAAM,MAAM,KAAK;GACjB,IAAI,QAAQ,MAAM;GAClB,IAAI,IAAI,MAAM,MAAM,WAAW,IAAI,KAAK;QACnC,IAAI,MAAM,IAAI;EACrB,CAAC;EACD,KAAK,OAAO,MAAM,YAAY,GAAG,KAAK,MAAM;CAC9C;CAEA,OAA0B,QAA0B;EAClD,MAAM,MAAM,KAAK,MAAM;EACvB,KAAK,MAAM;EACX,IAAI,QAAQ,KAAK,aAAa,UAAU,GAAG,KAAK,aAAa,KAAK,CAAC;EACnE,KAAK,KACH,IAAI,YAAY;GACd,KAAK,OAAO;EACd,CAAC,CACH;EAEA,KAAK,OAAO,cAAc;GACxB,KAAK,OAAO;EAEd,CAAC;CACH;CAEA,SAAkC;EAChC,KAAK,KAAK,WAAW;EACrB,KAAK,MAAM;CACb;CAEA,SAAkC;EAChC,MAAM,SAAS,KAAK;EACpB,MAAM,MAAM,KAAK;EACjB,IAAI,WAAW,QAAQ,QAAQ,MAAM;EACrC,MAAM,QAAQ,IAAI,MAAM;EACxB,MAAM,SAAS,UAAU;EACzB,KAAK,SAAS,UAAU;EAExB,KAAK,OAAO,WAAW,CAAC,UAAU,OAAO,QAAQ,WAAW;EAC5D,KAAK,OAAO,gBAAgB,aAAa,UAAU,YAAY;EAC/D,KAAK,KAAK,OAAO,SAAS,WAAW,SAAS;EAC9C,KAAK,OAAO,aACV,cACA,SACK,KAAK,aAAa,YAAY,KAAK,iBACnC,KAAK,aAAa,aAAa,KAAK,MAC3C;CACF;AACF"}
@@ -1 +1 @@
1
- {"version":3,"file":"cast-screen.d.ts","sourceRoot":"","sources":["../src/cast-screen.ts"],"names":[],"mappings":"AAeA,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC3C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAwD5C,qBAAa,aAAc,SAAQ,SAAS;IAC1C,MAAM,KAAK,kBAAkB,IAAI,SAAS,MAAM,EAAE,CAEjD;IAED,iBAAyB,MAAM,CAAc;IAC7C,iBAAyB,IAAI,CAAoB;IACjD,iBAAyB,KAAK,CAAiC;IAC/D,iBAAyB,IAAI,CAAS;IACtC,iBAAyB,GAAG,CAAc;IAC1C,iBAAyB,IAAI,CAAc;IAC3C,iBAAyB,IAAI,CAAoB;IACjD,QAAgB,GAAG,CAAc;IACjC,kGAAkG;IAClG,QAAgB,IAAI,CAAgB;;IA8CpC,sEAAsE;IAC7D,iBAAiB,IAAI,IAAI;cAKf,MAAM,CAAC,MAAM,EAAE,UAAU,GAAG,IAAI;cAWhC,MAAM,IAAI,IAAI;IAKjC,yEAAyE;IACzE,OAAO,CAAC,KAAK;cAcM,MAAM,IAAI,IAAI;CAsBlC"}
1
+ {"version":3,"file":"cast-screen.d.ts","sourceRoot":"","sources":["../src/cast-screen.ts"],"names":[],"mappings":"AAgBA,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC3C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAuB5C,qBAAa,aAAc,SAAQ,SAAS;IAC1C,MAAM,KAAK,kBAAkB,IAAI,SAAS,MAAM,EAAE,CAEjD;IAED,iBAAyB,MAAM,CAAc;IAC7C,iBAAyB,IAAI,CAAoB;IACjD,iBAAyB,KAAK,CAAiC;IAC/D,iBAAyB,IAAI,CAAS;IACtC,iBAAyB,GAAG,CAAc;IAC1C,iBAAyB,IAAI,CAAc;IAC3C,iBAAyB,IAAI,CAAoB;IACjD,QAAgB,GAAG,CAAc;IACjC,kGAAkG;IAClG,QAAgB,IAAI,CAAgB;;IA8CpC,sEAAsE;IAC7D,iBAAiB,IAAI,IAAI;cAKf,MAAM,CAAC,MAAM,EAAE,UAAU,GAAG,IAAI;cAWhC,MAAM,IAAI,IAAI;IAKjC,yEAAyE;IACzE,OAAO,CAAC,KAAK;cAcM,MAAM,IAAI,IAAI;CAsBlC"}
@@ -1,148 +1,116 @@
1
- import { cast } from './cast.js';
2
- import { Component } from './component.js';
3
- import { icon } from './icons.js';
4
- import { BUTTON_STYLE, describe, el, fill, format, iconSlots, SLIDER_STYLE, show, style, } from './shared.js';
5
- import { slider } from './slider.js';
6
- const STATES = ['play', 'pause'];
1
+ import { cast } from "./cast.js";
2
+ import { Component } from "./component.js";
3
+ import { icon } from "./icons.js";
4
+ import { BUTTON_STYLE, SLIDER_STYLE, describe, el, fill, format, iconSlots, show, style } from "./shared.js";
5
+ import { slider } from "./slider.js";
6
+ //#region src/cast-screen.ts
7
+ const STYLE_CSS = ":host{color:var(--mbx-text);font:400 .9375em / 1.4 var(--mbx-font);text-align:center;background:#000000d9;justify-content:center;align-items:center;padding:1.0667em;display:flex;position:absolute;top:0;bottom:0;left:0;right:0}:host([hidden]){display:none}[part~=box]{flex-direction:column;align-items:center;width:100%;max-width:28em;display:flex}[part~=box]>:not([hidden])~:not([hidden]){margin-top:.8em}[part~=title]{font-size:1.2em;font-weight:600}[part~=row]{justify-content:center;align-items:center;width:100%;display:flex}[part~=row]>:not([hidden])~:not([hidden]){margin-left:.8em}[part~=slider]{flex:1}[part~=time]{font-variant-numeric:tabular-nums;color:var(--mbx-muted);font-size:.8667em}[part~=stop]{height:2.4em;font:inherit;color:inherit;border-radius:var(--mbx-radius);cursor:pointer;background:0 0;border:1px solid;padding:0 1.0667em;font-weight:600}[part~=stop]:focus{outline:2px solid var(--mbx-accent);outline-offset:-2px}[part~=stop]:focus:not(:focus-visible){outline:none}[hidden]{display:none}";
8
+ const STATES = ["play", "pause"];
7
9
  const STEP = 5;
8
10
  const PAGE = 30;
9
- const STYLE = `${BUTTON_STYLE}
10
- ${SLIDER_STYLE}
11
- :host {
12
- position: absolute;
13
- inset: 0;
14
- display: flex;
15
- align-items: center;
16
- justify-content: center;
17
- padding: 16px;
18
- background: rgba(0, 0, 0, 0.85);
19
- color: var(--mbx-text);
20
- font: 400 15px/1.4 var(--mbx-font);
21
- text-align: center;
22
- }
23
- :host([hidden]) { display: none; }
24
- [part~="box"] { display: flex; flex-direction: column; align-items: center; gap: 12px; width: min(100%, 420px); }
25
- [part~="title"] { font-size: 18px; font-weight: 600; }
26
- [part~="row"] { display: flex; align-items: center; justify-content: center; gap: 12px; width: 100%; }
27
- [part~="slider"] { flex: 1; }
28
- [part~="time"] { font-variant-numeric: tabular-nums; color: var(--mbx-muted); font-size: 13px; }
29
- [part~="stop"] {
30
- height: 36px;
31
- padding: 0 16px;
32
- font: inherit;
33
- font-weight: 600;
34
- color: inherit;
35
- background: transparent;
36
- border: 1px solid currentColor;
37
- border-radius: var(--mbx-radius);
38
- cursor: pointer;
39
- }
40
- [part~="stop"]:focus-visible { outline: 2px solid var(--mbx-accent); outline-offset: -2px; }
41
- [hidden] { display: none; }
42
- `;
43
- export class MbxCastScreen extends Component {
44
- static get observedAttributes() {
45
- return ['label', 'label-stop', 'label-play', 'label-pause', 'label-seek'];
46
- }
47
- constructor() {
48
- super();
49
- this.api = null;
50
- this.held = null;
51
- const root = this.attachShadow({ mode: 'open' });
52
- const box = el('div', 'box');
53
- this.title_ = el('div', 'title');
54
- this.play = document.createElement('button');
55
- this.play.type = 'button';
56
- this.play.setAttribute('part', 'button play');
57
- this.slots = iconSlots(STATES, icon);
58
- for (const state of STATES)
59
- this.play.append(this.slots[state]);
60
- this.play.addEventListener('click', () => {
61
- this.api?.playPause();
62
- });
63
- this.seek = slider({
64
- step: () => STEP,
65
- page: () => PAGE,
66
- onInput: (value) => {
67
- if (this.seek.dragging()) {
68
- this.held = value;
69
- this.paint();
70
- return;
71
- }
72
- this.held = null;
73
- this.api?.seek(value);
74
- },
75
- onDrag: (on) => {
76
- this.toggleAttribute('dragging', on);
77
- if (!on)
78
- this.held = null;
79
- },
80
- });
81
- this.time = el('span', 'time');
82
- this.row = el('div', 'row');
83
- this.row.append(this.play, this.seek.root, this.time);
84
- this.stop = el('button', 'stop');
85
- this.stop.type = 'button';
86
- this.stop.addEventListener('click', () => {
87
- this.api?.stop();
88
- });
89
- box.append(this.title_, this.row, this.stop);
90
- root.append(style(STYLE), box);
91
- }
92
- /** Hidden before attaching: a constructor must not add attributes. */
93
- connectedCallback() {
94
- if (this.player === null)
95
- this.hidden = true;
96
- super.connectedCallback();
97
- }
98
- attach(player) {
99
- const api = cast(player);
100
- this.api = api;
101
- this.keep(api.watch(() => {
102
- this.render();
103
- }));
104
- this.render();
105
- }
106
- detach() {
107
- this.api = null;
108
- this.hidden = true;
109
- }
110
- /** The slider and the time, from the receiver or from the held thumb. */
111
- paint() {
112
- const api = this.api;
113
- if (api === null)
114
- return;
115
- const remote = api.remote();
116
- const seekable = remote.loaded && Number.isFinite(remote.duration);
117
- this.seek.root.hidden = !seekable;
118
- this.time.hidden = !seekable;
119
- if (!seekable)
120
- return;
121
- const at = this.held ?? remote.time;
122
- this.seek.range(0, remote.duration);
123
- this.seek.set(at, describe(at, remote.duration));
124
- this.time.textContent = `${format(at)} / ${format(remote.duration)}`;
125
- }
126
- render() {
127
- const api = this.api;
128
- if (api === null)
129
- return;
130
- const remote = api.remote();
131
- // Named while hidden too: a screen reader walks the tree either way.
132
- const state = remote.loaded && !remote.paused ? 'pause' : 'play';
133
- show(this.slots, state);
134
- this.play.setAttribute('aria-label', this.getAttribute(`label-${state}`) ?? (state === 'play' ? 'Play' : 'Pause'));
135
- this.seek.label(this.getAttribute('label-seek') ?? 'Seek');
136
- this.stop.textContent = this.getAttribute('label-stop') ?? 'Stop casting';
137
- const casting = api.state() === 'casting';
138
- this.hidden = !casting;
139
- if (!casting)
140
- return;
141
- this.title_.textContent = fill(this.getAttribute('label') ?? 'Casting to {device}', {
142
- device: api.device() ?? '',
143
- });
144
- this.play.disabled = !remote.loaded;
145
- this.paint();
146
- }
147
- }
11
+ const STYLE = BUTTON_STYLE + SLIDER_STYLE + STYLE_CSS;
12
+ var MbxCastScreen = class extends Component {
13
+ static get observedAttributes() {
14
+ return [
15
+ "label",
16
+ "label-stop",
17
+ "label-play",
18
+ "label-pause",
19
+ "label-seek"
20
+ ];
21
+ }
22
+ constructor() {
23
+ super();
24
+ this.api = null;
25
+ this.held = null;
26
+ const root = this.attachShadow({ mode: "open" });
27
+ const box = el("div", "box");
28
+ this.title_ = el("div", "title");
29
+ this.play = document.createElement("button");
30
+ this.play.type = "button";
31
+ this.play.setAttribute("part", "button play");
32
+ this.slots = iconSlots(STATES, icon);
33
+ for (const state of STATES) this.play.append(this.slots[state]);
34
+ this.play.addEventListener("click", () => {
35
+ this.api?.playPause();
36
+ });
37
+ this.seek = slider({
38
+ step: () => STEP,
39
+ page: () => PAGE,
40
+ onInput: (value) => {
41
+ if (this.seek.dragging()) {
42
+ this.held = value;
43
+ this.paint();
44
+ return;
45
+ }
46
+ this.held = null;
47
+ this.api?.seek(value);
48
+ },
49
+ onDrag: (on) => {
50
+ this.toggleAttribute("dragging", on);
51
+ if (!on) this.held = null;
52
+ }
53
+ });
54
+ this.time = el("span", "time");
55
+ this.row = el("div", "row");
56
+ this.row.append(this.play, this.seek.root, this.time);
57
+ this.stop = el("button", "stop");
58
+ this.stop.type = "button";
59
+ this.stop.addEventListener("click", () => {
60
+ this.api?.stop();
61
+ });
62
+ box.append(this.title_, this.row, this.stop);
63
+ root.append(style(STYLE), box);
64
+ }
65
+ /** Hidden before attaching: a constructor must not add attributes. */
66
+ connectedCallback() {
67
+ if (this.player === null) this.hidden = true;
68
+ super.connectedCallback();
69
+ }
70
+ attach(player) {
71
+ const api = cast(player);
72
+ this.api = api;
73
+ this.keep(api.watch(() => {
74
+ this.render();
75
+ }));
76
+ this.render();
77
+ }
78
+ detach() {
79
+ this.api = null;
80
+ this.hidden = true;
81
+ }
82
+ /** The slider and the time, from the receiver or from the held thumb. */
83
+ paint() {
84
+ const api = this.api;
85
+ if (api === null) return;
86
+ const remote = api.remote();
87
+ const seekable = remote.loaded && Number.isFinite(remote.duration);
88
+ this.seek.root.hidden = !seekable;
89
+ this.time.hidden = !seekable;
90
+ if (!seekable) return;
91
+ const at = this.held ?? remote.time;
92
+ this.seek.range(0, remote.duration);
93
+ this.seek.set(at, describe(at, remote.duration));
94
+ this.time.textContent = `${format(at)} / ${format(remote.duration)}`;
95
+ }
96
+ render() {
97
+ const api = this.api;
98
+ if (api === null) return;
99
+ const remote = api.remote();
100
+ const state = remote.loaded && !remote.paused ? "pause" : "play";
101
+ show(this.slots, state);
102
+ this.play.setAttribute("aria-label", this.getAttribute(`label-${state}`) ?? (state === "play" ? "Play" : "Pause"));
103
+ this.seek.label(this.getAttribute("label-seek") ?? "Seek");
104
+ this.stop.textContent = this.getAttribute("label-stop") ?? "Stop casting";
105
+ const casting = api.state() === "casting";
106
+ this.hidden = !casting;
107
+ if (!casting) return;
108
+ this.title_.textContent = fill(this.getAttribute("label") ?? "Casting to {device}", { device: api.device() ?? "" });
109
+ this.play.disabled = !remote.loaded;
110
+ this.paint();
111
+ }
112
+ };
113
+ //#endregion
114
+ export { MbxCastScreen };
115
+
148
116
  //# sourceMappingURL=cast-screen.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"cast-screen.js","sourceRoot":"","sources":["../src/cast-screen.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"cast-screen.js","names":[],"sources":["../src/cast-screen.ts"],"sourcesContent":["/**\n * <mbx-cast-screen>: covers the picture while the player casts, with the\n * receiver's name, play and pause, the seek slider, the time, and a stop\n * button. It drives the receiver through the SDK's remote player; the\n * video underneath stays paused and untouched (rule 3). It sits in the\n * player beside the video, hidden until the player carries `casting`.\n *\n * The title comes from `label` with `{device}`, the stop button's text from\n * `label-stop`, the play button's name from `label-play` and `label-pause`\n * and its glyph from `icon-play` and `icon-pause`, the slider's name from\n * `label-seek`. A live stream has no duration on the receiver, so the\n * slider and the time hide for it.\n */\nimport type { Cast } from './cast.js';\nimport { cast } from './cast.js';\nimport STYLE_CSS from './cast-screen.css?inline';\nimport { Component } from './component.js';\nimport type { PlayerHost } from './host.js';\nimport { icon } from './icons.js';\nimport {\n BUTTON_STYLE,\n describe,\n el,\n fill,\n format,\n iconSlots,\n SLIDER_STYLE,\n show,\n style,\n} from './shared.js';\nimport type { Slider } from './slider.js';\nimport { slider } from './slider.js';\n\ntype State = 'play' | 'pause';\nconst STATES: readonly State[] = ['play', 'pause'];\nconst STEP = 5;\nconst PAGE = 30;\n\nconst STYLE = BUTTON_STYLE + SLIDER_STYLE + STYLE_CSS;\n\nexport class MbxCastScreen extends Component {\n static get observedAttributes(): readonly string[] {\n return ['label', 'label-stop', 'label-play', 'label-pause', 'label-seek'];\n }\n\n declare private readonly title_: HTMLElement;\n declare private readonly play: HTMLButtonElement;\n declare private readonly slots: Record<State, HTMLSlotElement>;\n declare private readonly seek: Slider;\n declare private readonly row: HTMLElement;\n declare private readonly time: HTMLElement;\n declare private readonly stop: HTMLButtonElement;\n declare private api: Cast | null;\n /** Where the thumb is while a pointer holds it, so the receiver's reports do not move it back. */\n declare private held: number | null;\n\n constructor() {\n super();\n this.api = null;\n this.held = null;\n const root = this.attachShadow({ mode: 'open' });\n const box = el('div', 'box');\n this.title_ = el('div', 'title');\n this.play = document.createElement('button');\n this.play.type = 'button';\n this.play.setAttribute('part', 'button play');\n this.slots = iconSlots(STATES, icon);\n for (const state of STATES) this.play.append(this.slots[state]);\n this.play.addEventListener('click', () => {\n this.api?.playPause();\n });\n this.seek = slider({\n step: () => STEP,\n page: () => PAGE,\n onInput: (value) => {\n if (this.seek.dragging()) {\n this.held = value;\n this.paint();\n return;\n }\n this.held = null;\n this.api?.seek(value);\n },\n onDrag: (on) => {\n this.toggleAttribute('dragging', on);\n if (!on) this.held = null;\n },\n });\n this.time = el('span', 'time');\n this.row = el('div', 'row');\n this.row.append(this.play, this.seek.root, this.time);\n this.stop = el('button', 'stop');\n this.stop.type = 'button';\n this.stop.addEventListener('click', () => {\n this.api?.stop();\n });\n box.append(this.title_, this.row, this.stop);\n root.append(style(STYLE), box);\n }\n\n /** Hidden before attaching: a constructor must not add attributes. */\n override connectedCallback(): void {\n if (this.player === null) this.hidden = true;\n super.connectedCallback();\n }\n\n protected override attach(player: PlayerHost): void {\n const api = cast(player);\n this.api = api;\n this.keep(\n api.watch(() => {\n this.render();\n }),\n );\n this.render();\n }\n\n protected override detach(): void {\n this.api = null;\n this.hidden = true;\n }\n\n /** The slider and the time, from the receiver or from the held thumb. */\n private paint(): void {\n const api = this.api;\n if (api === null) return;\n const remote = api.remote();\n const seekable = remote.loaded && Number.isFinite(remote.duration);\n this.seek.root.hidden = !seekable;\n this.time.hidden = !seekable;\n if (!seekable) return;\n const at = this.held ?? remote.time;\n this.seek.range(0, remote.duration);\n this.seek.set(at, describe(at, remote.duration));\n this.time.textContent = `${format(at)} / ${format(remote.duration)}`;\n }\n\n protected override render(): void {\n const api = this.api;\n if (api === null) return;\n const remote = api.remote();\n // Named while hidden too: a screen reader walks the tree either way.\n const state: State = remote.loaded && !remote.paused ? 'pause' : 'play';\n show(this.slots, state);\n this.play.setAttribute(\n 'aria-label',\n this.getAttribute(`label-${state}`) ?? (state === 'play' ? 'Play' : 'Pause'),\n );\n this.seek.label(this.getAttribute('label-seek') ?? 'Seek');\n this.stop.textContent = this.getAttribute('label-stop') ?? 'Stop casting';\n const casting = api.state() === 'casting';\n this.hidden = !casting;\n if (!casting) return;\n this.title_.textContent = fill(this.getAttribute('label') ?? 'Casting to {device}', {\n device: api.device() ?? '',\n });\n this.play.disabled = !remote.loaded;\n this.paint();\n }\n}\n"],"mappings":"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"}