@mux/mux-player 0.1.0-beta.21 → 0.1.0-beta.24

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.
Files changed (71) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/README.md +36 -36
  3. package/coverage/lcov-report/index.html +29 -29
  4. package/coverage/lcov-report/src/dialog.ts.html +1 -1
  5. package/coverage/lcov-report/src/errors.ts.html +1 -1
  6. package/coverage/lcov-report/src/helpers.ts.html +20 -71
  7. package/coverage/lcov-report/src/html.ts.html +159 -9
  8. package/coverage/lcov-report/src/index.html +52 -52
  9. package/coverage/lcov-report/src/index.ts.html +79 -79
  10. package/coverage/lcov-report/src/logger.ts.html +1 -1
  11. package/coverage/lcov-report/src/media-chrome/dialog.ts.html +1 -1
  12. package/coverage/lcov-report/src/media-chrome/index.html +1 -1
  13. package/coverage/lcov-report/src/media-chrome/time-display.ts.html +1 -1
  14. package/coverage/lcov-report/src/media-theme-mux/icons/airplay.svg.html +1 -1
  15. package/coverage/lcov-report/src/media-theme-mux/icons/captions-off.svg.html +1 -1
  16. package/coverage/lcov-report/src/media-theme-mux/icons/captions-on.svg.html +1 -1
  17. package/coverage/lcov-report/src/media-theme-mux/icons/cast-enter.svg.html +103 -0
  18. package/coverage/lcov-report/src/media-theme-mux/icons/cast-exit.svg.html +106 -0
  19. package/coverage/lcov-report/src/media-theme-mux/icons/fullscreen-enter.svg.html +1 -1
  20. package/coverage/lcov-report/src/media-theme-mux/icons/fullscreen-exit.svg.html +1 -1
  21. package/coverage/lcov-report/src/media-theme-mux/icons/index.html +33 -3
  22. package/coverage/lcov-report/src/media-theme-mux/icons/pause.svg.html +1 -1
  23. package/coverage/lcov-report/src/media-theme-mux/icons/pip-enter.svg.html +1 -1
  24. package/coverage/lcov-report/src/media-theme-mux/icons/pip-exit.svg.html +1 -1
  25. package/coverage/lcov-report/src/media-theme-mux/icons/play.svg.html +1 -1
  26. package/coverage/lcov-report/src/media-theme-mux/icons/seek-backward.svg.html +1 -1
  27. package/coverage/lcov-report/src/media-theme-mux/icons/seek-forward.svg.html +1 -1
  28. package/coverage/lcov-report/src/media-theme-mux/icons/volume-high.svg.html +1 -1
  29. package/coverage/lcov-report/src/media-theme-mux/icons/volume-low.svg.html +1 -1
  30. package/coverage/lcov-report/src/media-theme-mux/icons/volume-medium.svg.html +1 -1
  31. package/coverage/lcov-report/src/media-theme-mux/icons/volume-off.svg.html +1 -1
  32. package/coverage/lcov-report/src/media-theme-mux/icons.ts.html +18 -6
  33. package/coverage/lcov-report/src/media-theme-mux/index.html +24 -24
  34. package/coverage/lcov-report/src/media-theme-mux/media-theme-mux.ts.html +129 -150
  35. package/coverage/lcov-report/src/media-theme-mux/styles.css.html +121 -19
  36. package/coverage/lcov-report/src/styles.css.html +1 -1
  37. package/coverage/lcov-report/src/template.ts.html +106 -64
  38. package/coverage/lcov-report/src/utils.ts.html +1 -1
  39. package/coverage/lcov-report/src/video-api.ts.html +91 -13
  40. package/coverage/lcov.info +1515 -1372
  41. package/dist/index.cjs.js +439 -301
  42. package/dist/index.mjs +245 -199
  43. package/dist/mux-player.js +494 -356
  44. package/dist/mux-player.mjs +494 -356
  45. package/dist/tsconfig.tsbuildinfo +1 -1
  46. package/dist/types/helpers.d.ts +1 -3
  47. package/dist/types/html.d.ts +5 -0
  48. package/dist/types/index.d.ts +3 -3
  49. package/dist/types/media-theme-mux/icons.d.ts +2 -0
  50. package/dist/types/media-theme-mux/media-theme-mux.d.ts +12 -13
  51. package/dist/types/video-api.d.ts +4 -0
  52. package/dist/types-ts3.4/helpers.d.ts +1 -3
  53. package/dist/types-ts3.4/html.d.ts +5 -0
  54. package/dist/types-ts3.4/index.d.ts +3 -3
  55. package/dist/types-ts3.4/media-theme-mux/icons.d.ts +2 -0
  56. package/dist/types-ts3.4/media-theme-mux/media-theme-mux.d.ts +12 -13
  57. package/dist/types-ts3.4/video-api.d.ts +2 -0
  58. package/package.json +5 -5
  59. package/src/helpers.ts +7 -24
  60. package/src/html.ts +50 -0
  61. package/src/index.ts +42 -42
  62. package/src/media-theme-mux/icons/cast-enter.svg +6 -0
  63. package/src/media-theme-mux/icons/cast-exit.svg +7 -0
  64. package/src/media-theme-mux/icons.ts +4 -0
  65. package/src/media-theme-mux/media-theme-mux.ts +76 -83
  66. package/src/media-theme-mux/styles.css +49 -15
  67. package/src/template.ts +70 -56
  68. package/src/types.d.ts +1 -3
  69. package/src/video-api.ts +27 -1
  70. package/test/player.test.js +34 -0
  71. package/test/template.test.js +3 -3
@@ -1,4 +1,4 @@
1
- import 'media-chrome';
1
+ import { MediaTheme } from 'media-chrome';
2
2
  import { html, render } from '../html';
3
3
  import '../media-chrome/time-display';
4
4
 
@@ -14,27 +14,60 @@ const MediaChromeSizes = {
14
14
  };
15
15
 
16
16
  type ThemeMuxTemplateProps = {
17
- streamType: string;
17
+ streamType: string | null;
18
18
  audio: boolean;
19
- playerSize: string;
19
+ playerSize: string | null;
20
20
  defaultHiddenCaptions: boolean;
21
- hasCaptions: boolean;
22
- forwardSeekOffset: number;
23
- backwardSeekOffset: number;
21
+ forwardSeekOffset: string | null;
22
+ backwardSeekOffset: string | null;
24
23
  };
25
24
 
26
- const template = (props: ThemeMuxTemplateProps) => html`
27
- <style>
28
- ${cssStr}
29
- </style>
25
+ export default class MediaThemeMux extends MediaTheme {
26
+ static get observedAttributes() {
27
+ return [
28
+ 'audio',
29
+ 'stream-type',
30
+ 'player-size',
31
+ 'default-hidden-captions',
32
+ 'forward-seek-offset',
33
+ 'backward-seek-offset',
34
+ ];
35
+ }
30
36
 
31
- <media-controller audio="${props.audio || false}" class="size-${props.playerSize}">
32
- <slot name="media" slot="media"></slot>
33
- <media-loading-indicator slot="centered-chrome" no-auto-hide></media-loading-indicator>
34
- ${ChromeRenderer(props)}
35
- <slot></slot>
36
- </media-controller>
37
- `;
37
+ attributeChangedCallback() {
38
+ this.render();
39
+ }
40
+
41
+ render() {
42
+ const props = {
43
+ audio: this.hasAttribute('audio'),
44
+ streamType: this.getAttribute('stream-type'),
45
+ playerSize: this.getAttribute('player-size'),
46
+ defaultHiddenCaptions: this.hasAttribute('default-hidden-captions'),
47
+ forwardSeekOffset: this.getAttribute('forward-seek-offset'),
48
+ backwardSeekOffset: this.getAttribute('backward-seek-offset'),
49
+ };
50
+
51
+ render(
52
+ html`
53
+ <style>
54
+ ${cssStr}
55
+ </style>
56
+ <media-controller audio="${props.audio || false}" class="size-${props.playerSize}">
57
+ <slot name="media" slot="media"></slot>
58
+ <media-loading-indicator slot="centered-chrome" no-auto-hide></media-loading-indicator>
59
+ ${ChromeRenderer(props)}
60
+ <slot></slot>
61
+ </media-controller>
62
+ `,
63
+ this.shadowRoot as Node
64
+ );
65
+ }
66
+ }
67
+
68
+ if (!customElements.get('media-theme-mux')) {
69
+ customElements.define('media-theme-mux', MediaThemeMux);
70
+ }
38
71
 
39
72
  const ChromeRenderer = (props: ThemeMuxTemplateProps) => {
40
73
  const { streamType, playerSize, audio } = props;
@@ -42,7 +75,7 @@ const ChromeRenderer = (props: ThemeMuxTemplateProps) => {
42
75
  switch (streamType) {
43
76
  case StreamTypes.LIVE:
44
77
  case StreamTypes.LL_LIVE: {
45
- return AudioLiveChrome(props);
78
+ return AudioLiveChrome();
46
79
  }
47
80
  case StreamTypes.DVR:
48
81
  case StreamTypes.LL_DVR: {
@@ -152,6 +185,13 @@ const MediaFullscreenButton = () => html`
152
185
  ${icons.FullscreenExit()}
153
186
  </media-fullscreen-button>`;
154
187
 
188
+ // prettier-ignore
189
+ const MediaCastButton = () => html`
190
+ <media-cast-button>
191
+ ${icons.CastEnter()}
192
+ ${icons.CastExit()}
193
+ </media-cast-button>`;
194
+
155
195
  export const AudioVodChrome = (props: ThemeMuxTemplateProps) => html`
156
196
  <media-control-bar>
157
197
  ${MediaPlayButton()} ${MediaSeekBackwardButton(props)} ${MediaSeekForwardButton(props)}
@@ -160,8 +200,7 @@ export const AudioVodChrome = (props: ThemeMuxTemplateProps) => html`
160
200
  ${MediaMuteButton()}
161
201
  <media-volume-range></media-volume-range>
162
202
  <media-playback-rate-button></media-playback-rate-button>
163
- ${MediaAirplayButton()}
164
- <media-cast-button></media-cast-button>
203
+ ${MediaAirplayButton()} ${MediaCastButton()}
165
204
  </media-control-bar>
166
205
  `;
167
206
 
@@ -175,29 +214,27 @@ export const AudioDvrChrome = (props: ThemeMuxTemplateProps) => html`
175
214
  ${MediaMuteButton()}
176
215
  <media-volume-range></media-volume-range>
177
216
  <media-playback-rate-button></media-playback-rate-button>
178
- ${MediaAirplayButton()}
179
- <media-cast-button></media-cast-button>
217
+ ${MediaAirplayButton()} ${MediaCastButton()}
180
218
  </media-control-bar>
181
219
  `;
182
220
 
183
- export const AudioLiveChrome = (_props: ThemeMuxTemplateProps) => html`
221
+ export const AudioLiveChrome = () => html`
184
222
  <media-control-bar>
185
223
  ${MediaPlayButton()}
186
224
  <slot name="seek-to-live-button"></slot>
187
225
  ${MediaMuteButton()}
188
226
  <media-volume-range></media-volume-range>
189
- ${MediaAirplayButton()}
190
- <media-cast-button></media-cast-button>
227
+ ${MediaAirplayButton()} ${MediaCastButton()}
191
228
  </media-control-bar>
192
229
  `;
193
230
 
194
231
  // prettier-ignore
195
232
  export const VodChromeExtraSmall = (props: ThemeMuxTemplateProps) => html`
196
233
  <media-control-bar slot="top-chrome">
197
- ${props.hasCaptions ? MediaCaptionsButton(props) : html``}
234
+ ${MediaCaptionsButton(props)}
198
235
  <div class="mxp-spacer"></div>
199
236
  ${MediaAirplayButton()}
200
- <media-cast-button></media-cast-button>
237
+ ${MediaCastButton()}
201
238
  ${MediaPipButton()}
202
239
  </media-control-bar>
203
240
  <div slot="centered-chrome" class="mxp-center-controls">
@@ -213,9 +250,9 @@ export const VodChromeExtraSmall = (props: ThemeMuxTemplateProps) => html`
213
250
  // prettier-ignore
214
251
  export const VodChromeSmall = (props: ThemeMuxTemplateProps) => html`
215
252
  <media-control-bar slot="top-chrome" style="justify-content: flex-end;">
216
- ${props.hasCaptions ? MediaCaptionsButton(props) : html``}
253
+ ${MediaCaptionsButton(props)}
217
254
  ${MediaAirplayButton()}
218
- <media-cast-button></media-cast-button>
255
+ ${MediaCastButton()}
219
256
  ${MediaPipButton()}
220
257
  </media-control-bar>
221
258
  <div slot="centered-chrome" class="mxp-center-controls">
@@ -250,9 +287,9 @@ export const VodChromeLarge = (props: ThemeMuxTemplateProps) => html`
250
287
  <media-volume-range></media-volume-range>
251
288
  <div class="mxp-spacer"></div>
252
289
  <media-playback-rate-button></media-playback-rate-button>
253
- ${props.hasCaptions ? MediaCaptionsButton(props) : html``}
290
+ ${MediaCaptionsButton(props)}
254
291
  ${MediaAirplayButton()}
255
- <media-cast-button></media-cast-button>
292
+ ${MediaCastButton()}
256
293
  ${MediaPipButton()}
257
294
  ${MediaFullscreenButton()}
258
295
  <div class="mxp-padding-2"></div>
@@ -267,9 +304,9 @@ export const LiveChromeSmall = (props: ThemeMuxTemplateProps) => html`
267
304
  <media-control-bar slot="top-chrome">
268
305
  <slot name="seek-to-live-button"></slot>
269
306
  <div class="mxp-spacer"></div>
270
- ${props.hasCaptions ? MediaCaptionsButton(props) : html``}
307
+ ${MediaCaptionsButton(props)}
271
308
  ${MediaAirplayButton()}
272
- <media-cast-button></media-cast-button>
309
+ ${MediaCastButton()}
273
310
  ${MediaPipButton()}
274
311
  </media-control-bar>
275
312
  <div slot="centered-chrome" class="mxp-center-controls">
@@ -295,9 +332,9 @@ export const LiveChromeLarge = (props: ThemeMuxTemplateProps) => html`
295
332
  ${MediaMuteButton()}
296
333
  <media-volume-range></media-volume-range>
297
334
  <div class="mxp-spacer"></div>
298
- ${props.hasCaptions ? MediaCaptionsButton(props) : html``}
335
+ ${MediaCaptionsButton(props)}
299
336
  ${MediaAirplayButton()}
300
- <media-cast-button></media-cast-button>
337
+ ${MediaCastButton()}
301
338
  ${MediaPipButton()}
302
339
  ${MediaFullscreenButton()}
303
340
  </media-control-bar>
@@ -309,9 +346,9 @@ export const DvrChromeExtraSmall = VodChromeExtraSmall;
309
346
  // prettier-ignore
310
347
  export const DvrChromeSmall = (props: ThemeMuxTemplateProps) => html`
311
348
  <media-control-bar slot="top-chrome" style="justify-content: flex-end;">
312
- ${props.hasCaptions ? MediaCaptionsButton(props) : html``}
349
+ ${MediaCaptionsButton(props)}
313
350
  ${MediaAirplayButton()}
314
- <media-cast-button></media-cast-button>
351
+ ${MediaCastButton()}
315
352
  ${MediaPipButton()}
316
353
  </media-control-bar>
317
354
  <div slot="centered-chrome" class="mxp-center-controls">
@@ -344,55 +381,11 @@ export const DvrChromeLarge = (props: ThemeMuxTemplateProps) => html`
344
381
  <media-volume-range></media-volume-range>
345
382
  <slot name="seek-to-live-button"></slot>
346
383
  <div class="mxp-spacer"></div>
347
- ${props.hasCaptions ? MediaCaptionsButton(props) : html``}
384
+ ${MediaCaptionsButton(props)}
348
385
  ${MediaAirplayButton()}
349
- <media-cast-button></media-cast-button>
386
+ ${MediaCastButton()}
350
387
  ${MediaPipButton()}
351
388
  ${MediaFullscreenButton()}
352
389
  <div class="mxp-padding-2"></div>
353
390
  </media-control-bar>
354
391
  `;
355
-
356
- function getProps(el: MediaThemeMux, state?: any): ThemeMuxTemplateProps {
357
- return {
358
- audio: el.hasAttribute('audio'),
359
- streamType: el.getAttribute('stream-type'),
360
- playerSize: el.getAttribute('player-size'),
361
- defaultHiddenCaptions: el.hasAttribute('default-hidden-captions'),
362
- hasCaptions: el.hasAttribute('has-captions'),
363
- forwardSeekOffset: el.getAttribute('forward-seek-offset'),
364
- backwardSeekOffset: el.getAttribute('backward-seek-offset'),
365
- ...state,
366
- };
367
- }
368
-
369
- class MediaThemeMux extends HTMLElement {
370
- static get observedAttributes() {
371
- return [
372
- 'audio',
373
- 'stream-type',
374
- 'player-size',
375
- 'default-hidden-captions',
376
- 'has-captions',
377
- 'forward-seek-offset',
378
- 'backward-seek-offset',
379
- ];
380
- }
381
-
382
- constructor() {
383
- super();
384
-
385
- this.attachShadow({ mode: 'open' });
386
- render(template(getProps(this)), this.shadowRoot as Node);
387
- }
388
-
389
- attributeChangedCallback() {
390
- render(template(getProps(this)), this.shadowRoot as Node);
391
- }
392
- }
393
-
394
- if (!customElements.get('media-theme-mux')) {
395
- customElements.define('media-theme-mux', MediaThemeMux);
396
- }
397
-
398
- export default MediaThemeMux;
@@ -3,20 +3,44 @@
3
3
  }
4
4
 
5
5
  :host {
6
- color: var(--primary-color);
7
- --media-icon-color: var(--primary-color);
8
- --media-range-thumb-background: var(--primary-color);
9
- --media-range-bar-color: var(--primary-color);
6
+ --_primary-color: var(--primary-color, #fff);
7
+
8
+ --media-icon-color: var(--_primary-color);
9
+ --media-range-thumb-background: var(--_primary-color);
10
+ --media-range-bar-color: var(--_primary-color);
10
11
  --media-control-background: var(--secondary-color);
11
12
  --media-control-hover-background: var(--secondary-color);
12
- --media-time-buffered-color: rgba(255, 255, 255, 0.7);
13
+ --media-time-buffered-color: rgba(255, 255, 255, 0.4);
13
14
  --media-range-track-background: rgba(255, 255, 255, 0.5);
14
15
  --media-range-track-border-radius: 3px;
16
+ --media-preview-thumbnail-border: 1px solid #fff;
17
+ --media-preview-thumbnail-border-radius: 2px;
18
+ --media-preview-time-margin: 5px 0 2px;
19
+ color: var(--_primary-color);
15
20
  display: inline-block;
16
21
  width: 100%;
17
22
  height: 100%;
18
23
  }
19
24
 
25
+ :host(.two-tone:not([audio])) {
26
+ --mux-time-range-padding: 0px; /* px is needed in calc() */
27
+ --media-preview-thumbnail-border: 0;
28
+ --media-preview-thumbnail-border-radius: 2px 2px 0 0;
29
+ --media-preview-time-border-radius: 0 0 2px 2px;
30
+ --media-preview-time-margin: 0 0 8px;
31
+ --media-preview-time-text-shadow: none;
32
+ }
33
+
34
+ :host([audio]) {
35
+ --media-preview-time-border-radius: 3px;
36
+ --media-preview-time-margin: 0 0 5px;
37
+ --media-preview-time-text-shadow: none;
38
+ }
39
+
40
+ :host(.two-tone:not([audio])) media-time-range {
41
+ --media-range-track-border-radius: 0;
42
+ }
43
+
20
44
  :host([audio]) ::slotted([slot='media']) {
21
45
  height: 0px;
22
46
  }
@@ -36,7 +60,8 @@
36
60
  media-airplay-button[media-airplay-unavailable],
37
61
  media-cast-button[media-cast-unavailable],
38
62
  media-volume-range[media-volume-unavailable],
39
- media-airplay-button[media-airplay-unavailable] {
63
+ media-airplay-button[media-airplay-unavailable],
64
+ media-captions-button:not(:is([media-captions-list], [media-subtitles-list])) {
40
65
  display: none;
41
66
  }
42
67
 
@@ -53,13 +78,26 @@ media-controller {
53
78
  height: 100%;
54
79
  }
55
80
 
81
+ :host media-time-range {
82
+ color: var(--_primary-color);
83
+ }
84
+
56
85
  :host(:not([audio])) media-time-range {
57
- padding: var(--mux-time-range-padding, 0 10px);
58
- z-index: 10;
86
+ --media-range-padding-left: var(--mux-time-range-padding, 10px);
87
+ --media-range-padding-right: var(--mux-time-range-padding, 10px);
59
88
  width: 100%;
60
- height: 22px;
61
- --media-range-track-translate-y: 6px;
62
- background: linear-gradient(180deg, transparent, transparent 15px, var(--media-control-background) 15px);
89
+ z-index: 10;
90
+ height: 10px;
91
+ bottom: -2px;
92
+ background: linear-gradient(
93
+ 180deg,
94
+ transparent,
95
+ transparent 3px,
96
+ var(--media-control-background) 3px,
97
+ var(--media-control-background) 8px,
98
+ transparent 8px,
99
+ transparent
100
+ );
63
101
  }
64
102
 
65
103
  media-control-bar {
@@ -70,10 +108,6 @@ media-control-bar :is([role='button'], [role='switch'], button) {
70
108
  height: 44px;
71
109
  }
72
110
 
73
- media-cast-button {
74
- width: 40px;
75
- }
76
-
77
111
  .size-extra-small media-control-bar [role='button'],
78
112
  .size-extra-small media-control-bar [role='switch'] {
79
113
  height: auto;
package/src/template.ts CHANGED
@@ -1,7 +1,12 @@
1
1
  import './media-theme-mux/media-theme-mux';
2
2
  import './dialog';
3
- import { getSrcFromPlaybackId, getPosterURLFromPlaybackId, getStoryboardURLFromPlaybackId } from './helpers';
4
- import { html } from './html';
3
+ import {
4
+ castThemeName,
5
+ getSrcFromPlaybackId,
6
+ getPosterURLFromPlaybackId,
7
+ getStoryboardURLFromPlaybackId,
8
+ } from './helpers';
9
+ import { html, unsafeStatic } from './html';
5
10
  // @ts-ignore
6
11
  import cssStr from './styles.css';
7
12
  import { i18n, stylePropsToString } from './utils';
@@ -17,18 +22,17 @@ export const template = (props: MuxTemplateProps) => html`
17
22
  `;
18
23
 
19
24
  export const content = (props: MuxTemplateProps) => html`
20
- <media-theme-mux
25
+ <${unsafeStatic(castThemeName(props.theme) ?? 'media-theme-mux')}
21
26
  audio="${props.audio || false}"
22
- style="${stylePropsToString({
23
- '--primary-color': props.primaryColor,
24
- '--secondary-color': props.secondaryColor,
25
- '--mux-time-range-padding': props.secondaryColor ? '0' : null,
26
- '--media-range-track-border-radius': props.secondaryColor ? '0' : null,
27
- }) ?? false}"
28
- class="size-${props.playerSize}"
27
+ style="${
28
+ stylePropsToString({
29
+ '--primary-color': props.primaryColor,
30
+ '--secondary-color': props.secondaryColor,
31
+ }) ?? false
32
+ }"
33
+ class="size-${props.playerSize}${props.secondaryColor ? ' two-tone' : ''}"
29
34
  stream-type="${props.streamType}"
30
35
  player-size="${props.playerSize}"
31
- has-captions="${props.hasCaptions}"
32
36
  default-hidden-captions="${props.defaultHiddenCaptions}"
33
37
  forward-seek-offset="${props.forwardSeekOffset}"
34
38
  backward-seek-offset="${props.backwardSeekOffset}"
@@ -44,49 +48,57 @@ export const content = (props: MuxTemplateProps) => html`
44
48
  debug="${props.debug ?? false}"
45
49
  prefer-mse="${props.preferMse ?? false}"
46
50
  start-time="${props.startTime != null ? props.startTime : false}"
47
- metadata-video-id="${props.metadataVideoId ?? props.metadata?.video_id ?? false}"
48
- metadata-video-title="${props.metadataVideoTitle ?? props.metadata?.video_title ?? false}"
49
- metadata-viewer-user-id="${props.metadataViewerUserId ?? props.metadata?.viewer_user_id ?? false}"
51
+ metadata-video-id="${props.metadataVideoId ?? false}"
52
+ metadata-video-title="${props.metadataVideoTitle ?? false}"
53
+ metadata-viewer-user-id="${props.metadataViewerUserId ?? false}"
50
54
  beacon-collection-domain="${props.beaconCollectionDomain ?? false}"
51
55
  player-software-name="${props.playerSoftwareName}"
52
56
  player-software-version="${props.playerSoftwareVersion}"
53
57
  env-key="${props.envKey ?? false}"
54
58
  stream-type="${props.streamType ?? false}"
55
59
  custom-domain="${props.customDomain ?? false}"
56
- src="${!!props.src
57
- ? props.src
58
- : props.playbackId
59
- ? getSrcFromPlaybackId(props.playbackId, { domain: props.customDomain, token: props.tokens.playback })
60
- : false}"
61
- poster="${!!props.poster
62
- ? props.poster
63
- : props.playbackId && !props.audio
64
- ? getPosterURLFromPlaybackId(props.playbackId, {
65
- domain: props.customDomain,
66
- thumbnailTime: props.thumbnailTime ?? props.startTime,
67
- token: props.tokens.thumbnail,
68
- })
69
- : false}"
70
- cast-src="${!!props.src
71
- ? props.src
72
- : props.playbackId
73
- ? getSrcFromPlaybackId(props.playbackId, { domain: props.customDomain, token: props.tokens.playback })
74
- : false}"
60
+ src="${
61
+ !!props.src
62
+ ? props.src
63
+ : props.playbackId
64
+ ? getSrcFromPlaybackId(props.playbackId, { domain: props.customDomain, token: props.tokens.playback })
65
+ : false
66
+ }"
67
+ poster="${
68
+ !!props.poster
69
+ ? props.poster
70
+ : props.playbackId && !props.audio
71
+ ? getPosterURLFromPlaybackId(props.playbackId, {
72
+ domain: props.customDomain,
73
+ thumbnailTime: props.thumbnailTime ?? props.startTime,
74
+ token: props.tokens.thumbnail,
75
+ })
76
+ : false
77
+ }"
78
+ cast-src="${
79
+ !!props.src
80
+ ? props.src
81
+ : props.playbackId
82
+ ? getSrcFromPlaybackId(props.playbackId, { domain: props.customDomain, token: props.tokens.playback })
83
+ : false
84
+ }"
75
85
  cast-stream-type="${[StreamTypes.LIVE, StreamTypes.LL_LIVE].includes(props.streamType as any) ? 'live' : false}"
76
86
  >
77
- ${props.playbackId &&
78
- !props.audio &&
79
- ![StreamTypes.LIVE, StreamTypes.LL_LIVE, StreamTypes.DVR, StreamTypes.LL_DVR].includes(props.streamType as any)
80
- ? html`<track
81
- label="thumbnails"
82
- default
83
- kind="metadata"
84
- src="${getStoryboardURLFromPlaybackId(props.playbackId, {
85
- domain: props.customDomain,
86
- token: props.tokens.storyboard,
87
- })}"
88
- />`
89
- : html``}
87
+ ${
88
+ props.playbackId &&
89
+ !props.audio &&
90
+ ![StreamTypes.LIVE, StreamTypes.LL_LIVE, StreamTypes.DVR, StreamTypes.LL_DVR].includes(props.streamType as any)
91
+ ? html`<track
92
+ label="thumbnails"
93
+ default
94
+ kind="metadata"
95
+ src="${getStoryboardURLFromPlaybackId(props.playbackId, {
96
+ domain: props.customDomain,
97
+ token: props.tokens.storyboard,
98
+ })}"
99
+ />`
100
+ : html``
101
+ }
90
102
  </mux-video>
91
103
  <button
92
104
  slot="seek-to-live-button"
@@ -111,16 +123,18 @@ export const content = (props: MuxTemplateProps) => html`
111
123
  ${props.dialog?.title ? html`<h3>${props.dialog.title}</h3>` : html``}
112
124
  <p>
113
125
  ${props.dialog?.message}
114
- ${props.dialog?.linkUrl
115
- ? html`<a
116
- href="${props.dialog.linkUrl}"
117
- target="_blank"
118
- rel="external noopener"
119
- aria-label="${props.dialog.linkText ?? ''} ${i18n(`(opens in a new window)`)}"
120
- >${props.dialog.linkText ?? props.dialog.linkUrl}</a
121
- >`
122
- : html``}
126
+ ${
127
+ props.dialog?.linkUrl
128
+ ? html`<a
129
+ href="${props.dialog.linkUrl}"
130
+ target="_blank"
131
+ rel="external noopener"
132
+ aria-label="${props.dialog.linkText ?? ''} ${i18n(`(opens in a new window)`)}"
133
+ >${props.dialog.linkText ?? props.dialog.linkUrl}</a
134
+ >`
135
+ : html``
136
+ }
123
137
  </p>
124
138
  </mxp-dialog>
125
- </media-theme-mux>
139
+ </${unsafeStatic(castThemeName(props.theme) ?? 'media-theme-mux')}>
126
140
  `;
package/src/types.d.ts CHANGED
@@ -6,11 +6,9 @@ export type MuxPlayerProps = Partial<MuxVideoElement> & {
6
6
 
7
7
  export type MuxTemplateProps = Partial<MuxPlayerProps> & {
8
8
  audio: boolean;
9
+ theme?: string;
9
10
  playerSize: string;
10
11
  showLoading: boolean;
11
- hasCaptions: boolean;
12
- supportsAirPlay: boolean;
13
- supportsVolume: boolean;
14
12
  thumbnailTime: number;
15
13
  primaryColor: string;
16
14
  secondaryColor: string;
package/src/video-api.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import { VideoEvents } from '@mux/mux-video';
2
2
  import type MuxVideoElement from '@mux/mux-video';
3
3
  import * as logger from './logger';
4
+ import { toNumberOrUndefined } from './utils';
4
5
 
5
6
  export type CastOptions = {
6
7
  receiverApplicationId: string;
@@ -99,6 +100,7 @@ class VideoApiElement extends HTMLElement {
99
100
  case CustomVideoAttributes.MUTED: {
100
101
  if (this.media) {
101
102
  this.media.muted = newValue != null;
103
+ this.media.defaultMuted = newValue != null;
102
104
  }
103
105
  return;
104
106
  }
@@ -113,6 +115,7 @@ class VideoApiElement extends HTMLElement {
113
115
  const val = +newValue;
114
116
  if (this.media && !Number.isNaN(val)) {
115
117
  this.media.playbackRate = val;
118
+ this.media.defaultPlaybackRate = val;
116
119
  }
117
120
  return;
118
121
  }
@@ -217,6 +220,19 @@ class VideoApiElement extends HTMLElement {
217
220
  }
218
221
  }
219
222
 
223
+ get defaultPlaybackRate() {
224
+ return toNumberOrUndefined(this.getAttribute(CustomVideoAttributes.PLAYBACKRATE)) ?? 1;
225
+ }
226
+
227
+ set defaultPlaybackRate(val) {
228
+ if (val != null) {
229
+ this.setAttribute(CustomVideoAttributes.PLAYBACKRATE, `${val}`);
230
+ } else {
231
+ // Remove boolean attribute if false, 0, '', null, undefined.
232
+ this.removeAttribute(CustomVideoAttributes.PLAYBACKRATE);
233
+ }
234
+ }
235
+
220
236
  get crossOrigin() {
221
237
  return getVideoAttribute(this, AllowedVideoAttributes.CROSSORIGIN);
222
238
  }
@@ -252,10 +268,20 @@ class VideoApiElement extends HTMLElement {
252
268
  }
253
269
 
254
270
  get muted() {
255
- return getVideoAttribute(this, AllowedVideoAttributes.MUTED) != null;
271
+ return this.media?.muted ?? false;
256
272
  }
257
273
 
258
274
  set muted(val) {
275
+ if (this.media) {
276
+ this.media.muted = Boolean(val);
277
+ }
278
+ }
279
+
280
+ get defaultMuted() {
281
+ return getVideoAttribute(this, AllowedVideoAttributes.MUTED) != null;
282
+ }
283
+
284
+ set defaultMuted(val) {
259
285
  if (val) {
260
286
  this.setAttribute(AllowedVideoAttributes.MUTED, '');
261
287
  } else {
@@ -283,17 +283,29 @@ describe('<mux-player>', () => {
283
283
  assert(muxVideo.muted, 'muxVideo.muted is true');
284
284
  assert(nativeVideo.muted, 'nativeVideo.muted is true');
285
285
 
286
+ assert(player.defaultMuted, 'player.defaultMuted is true');
287
+ assert(muxVideo.defaultMuted, 'muxVideo.defaultMuted is true');
288
+ assert(nativeVideo.defaultMuted, 'nativeVideo.defaultMuted is true');
289
+
286
290
  player.removeAttribute('muted');
287
291
 
288
292
  assert(!player.muted, 'player.muted is false');
289
293
  assert(!muxVideo.muted, 'muxVideo.muted is false');
290
294
  assert(!nativeVideo.muted, 'nativeVideo.muted is false');
291
295
 
296
+ assert(!player.defaultMuted, 'player.defaultMuted is false');
297
+ assert(!muxVideo.defaultMuted, 'muxVideo.defaultMuted is false');
298
+ assert(!nativeVideo.defaultMuted, 'nativeVideo.defaultMuted is false');
299
+
292
300
  player.setAttribute('muted', '');
293
301
 
294
302
  assert(player.muted, 'player.muted is true');
295
303
  assert(muxVideo.muted, 'muxVideo.muted is true');
296
304
  assert(nativeVideo.muted, 'nativeVideo.muted is true');
305
+
306
+ assert(player.defaultMuted, 'player.defaultMuted is true');
307
+ assert(muxVideo.defaultMuted, 'muxVideo.defaultMuted is true');
308
+ assert(nativeVideo.defaultMuted, 'nativeVideo.defaultMuted is true');
297
309
  });
298
310
 
299
311
  it('volume attribute behaves like expected', async function () {
@@ -342,6 +354,28 @@ describe('<mux-player>', () => {
342
354
  assert.equal(nativeVideo.playbackRate, 0.7, 'nativeVideo.playbackRate is 0.7');
343
355
  });
344
356
 
357
+ it('defaultPlaybackRate property behaves like expected', async function () {
358
+ const player = await fixture(`<mux-player
359
+ playback-id="DS00Spx1CV902MCtPj5WknGlR102V5HFkDe"
360
+ stream-type="on-demand"
361
+ ></mux-player>`);
362
+
363
+ assert.equal(player.defaultPlaybackRate, 1);
364
+
365
+ const muxVideo = player.media;
366
+ const nativeVideo = muxVideo.shadowRoot.querySelector('video');
367
+
368
+ assert.equal(player.defaultPlaybackRate, 1, 'player.defaultPlaybackRate is 1');
369
+ assert.equal(muxVideo.defaultPlaybackRate, 1, 'muxVideo.defaultPlaybackRate is 1');
370
+ assert.equal(nativeVideo.defaultPlaybackRate, 1, 'nativeVideo.defaultPlaybackRate is 1');
371
+
372
+ player.defaultPlaybackRate = 0.7;
373
+
374
+ assert.equal(player.defaultPlaybackRate, 0.7, 'player.defaultPlaybackRate is 0.7');
375
+ assert.equal(muxVideo.defaultPlaybackRate, 0.7, 'muxVideo.defaultPlaybackRate is 0.7');
376
+ assert.equal(nativeVideo.defaultPlaybackRate, 0.7, 'nativeVideo.defaultPlaybackRate is 0.7');
377
+ });
378
+
345
379
  it("signing tokens generate correct asset URL's", async function () {
346
380
  // tokens expire in 10 years
347
381
  const player = await fixture(`<mux-player