@mux/mux-player 0.1.0-beta.21
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/CHANGELOG.md +281 -0
- package/LICENSE +9 -0
- package/README.md +231 -0
- package/coverage/lcov-report/base.css +224 -0
- package/coverage/lcov-report/block-navigation.js +87 -0
- package/coverage/lcov-report/favicon.png +0 -0
- package/coverage/lcov-report/index.html +161 -0
- package/coverage/lcov-report/prettify.css +1 -0
- package/coverage/lcov-report/prettify.js +2 -0
- package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
- package/coverage/lcov-report/sorter.js +196 -0
- package/coverage/lcov-report/src/dialog.ts.html +247 -0
- package/coverage/lcov-report/src/errors.ts.html +574 -0
- package/coverage/lcov-report/src/helpers.ts.html +478 -0
- package/coverage/lcov-report/src/html.ts.html +580 -0
- package/coverage/lcov-report/src/index.html +251 -0
- package/coverage/lcov-report/src/index.ts.html +2941 -0
- package/coverage/lcov-report/src/logger.ts.html +163 -0
- package/coverage/lcov-report/src/media-chrome/dialog.ts.html +661 -0
- package/coverage/lcov-report/src/media-chrome/index.html +131 -0
- package/coverage/lcov-report/src/media-chrome/time-display.ts.html +295 -0
- package/coverage/lcov-report/src/media-theme-mux/icons/airplay.svg.html +109 -0
- package/coverage/lcov-report/src/media-theme-mux/icons/captions-off.svg.html +100 -0
- package/coverage/lcov-report/src/media-theme-mux/icons/captions-on.svg.html +100 -0
- package/coverage/lcov-report/src/media-theme-mux/icons/fullscreen-enter.svg.html +100 -0
- package/coverage/lcov-report/src/media-theme-mux/icons/fullscreen-exit.svg.html +100 -0
- package/coverage/lcov-report/src/media-theme-mux/icons/index.html +326 -0
- package/coverage/lcov-report/src/media-theme-mux/icons/pause.svg.html +100 -0
- package/coverage/lcov-report/src/media-theme-mux/icons/pip-enter.svg.html +100 -0
- package/coverage/lcov-report/src/media-theme-mux/icons/pip-exit.svg.html +100 -0
- package/coverage/lcov-report/src/media-theme-mux/icons/play.svg.html +100 -0
- package/coverage/lcov-report/src/media-theme-mux/icons/seek-backward.svg.html +124 -0
- package/coverage/lcov-report/src/media-theme-mux/icons/seek-forward.svg.html +124 -0
- package/coverage/lcov-report/src/media-theme-mux/icons/volume-high.svg.html +103 -0
- package/coverage/lcov-report/src/media-theme-mux/icons/volume-low.svg.html +103 -0
- package/coverage/lcov-report/src/media-theme-mux/icons/volume-medium.svg.html +103 -0
- package/coverage/lcov-report/src/media-theme-mux/icons/volume-off.svg.html +103 -0
- package/coverage/lcov-report/src/media-theme-mux/icons.ts.html +184 -0
- package/coverage/lcov-report/src/media-theme-mux/index.html +146 -0
- package/coverage/lcov-report/src/media-theme-mux/media-theme-mux.ts.html +1279 -0
- package/coverage/lcov-report/src/media-theme-mux/styles.css.html +586 -0
- package/coverage/lcov-report/src/styles.css.html +211 -0
- package/coverage/lcov-report/src/template.ts.html +463 -0
- package/coverage/lcov-report/src/utils.ts.html +385 -0
- package/coverage/lcov-report/src/video-api.ts.html +979 -0
- package/coverage/lcov.info +4058 -0
- package/dist/index.cjs.js +1432 -0
- package/dist/index.mjs +709 -0
- package/dist/mux-player.js +1478 -0
- package/dist/mux-player.mjs +1478 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/dist/types/dialog.d.ts +6 -0
- package/dist/types/errors.d.ts +6 -0
- package/dist/types/helpers.d.ts +26 -0
- package/dist/types/html.d.ts +18 -0
- package/dist/types/index.d.ts +199 -0
- package/dist/types/logger.d.ts +5 -0
- package/dist/types/media-chrome/dialog.d.ts +12 -0
- package/dist/types/media-chrome/time-display.d.ts +9 -0
- package/dist/types/media-theme-mux/icons.d.ts +15 -0
- package/dist/types/media-theme-mux/media-theme-mux.d.ts +29 -0
- package/dist/types/template.d.ts +5 -0
- package/dist/types/utils.d.ts +10 -0
- package/dist/types/video-api.d.ts +64 -0
- package/dist/types-ts3.4/dialog.d.ts +6 -0
- package/dist/types-ts3.4/errors.d.ts +6 -0
- package/dist/types-ts3.4/helpers.d.ts +26 -0
- package/dist/types-ts3.4/html.d.ts +18 -0
- package/dist/types-ts3.4/index.d.ts +180 -0
- package/dist/types-ts3.4/logger.d.ts +5 -0
- package/dist/types-ts3.4/media-chrome/dialog.d.ts +12 -0
- package/dist/types-ts3.4/media-chrome/time-display.d.ts +9 -0
- package/dist/types-ts3.4/media-theme-mux/icons.d.ts +15 -0
- package/dist/types-ts3.4/media-theme-mux/media-theme-mux.d.ts +29 -0
- package/dist/types-ts3.4/template.d.ts +5 -0
- package/dist/types-ts3.4/utils.d.ts +10 -0
- package/dist/types-ts3.4/video-api.d.ts +53 -0
- package/lang/en.json +32 -0
- package/lang/nl.json +31 -0
- package/package.json +107 -0
- package/src/dialog.ts +54 -0
- package/src/errors.ts +163 -0
- package/src/helpers.ts +131 -0
- package/src/html.ts +165 -0
- package/src/index.ts +952 -0
- package/src/logger.ts +26 -0
- package/src/media-chrome/dialog.ts +192 -0
- package/src/media-chrome/time-display.ts +70 -0
- package/src/media-theme-mux/icons/airplay.svg +8 -0
- package/src/media-theme-mux/icons/captions-off.svg +5 -0
- package/src/media-theme-mux/icons/captions-on.svg +5 -0
- package/src/media-theme-mux/icons/fullscreen-enter.svg +5 -0
- package/src/media-theme-mux/icons/fullscreen-exit.svg +5 -0
- package/src/media-theme-mux/icons/pause.svg +5 -0
- package/src/media-theme-mux/icons/pip-enter.svg +5 -0
- package/src/media-theme-mux/icons/pip-exit.svg +5 -0
- package/src/media-theme-mux/icons/play.svg +5 -0
- package/src/media-theme-mux/icons/seek-backward.svg +13 -0
- package/src/media-theme-mux/icons/seek-forward.svg +13 -0
- package/src/media-theme-mux/icons/volume-high.svg +6 -0
- package/src/media-theme-mux/icons/volume-low.svg +6 -0
- package/src/media-theme-mux/icons/volume-medium.svg +6 -0
- package/src/media-theme-mux/icons/volume-off.svg +6 -0
- package/src/media-theme-mux/icons.ts +33 -0
- package/src/media-theme-mux/media-theme-mux.ts +398 -0
- package/src/media-theme-mux/styles.css +167 -0
- package/src/styles.css +42 -0
- package/src/template.ts +126 -0
- package/src/types.d.ts +52 -0
- package/src/utils.ts +100 -0
- package/src/video-api.ts +298 -0
- package/test/errors.test.js +169 -0
- package/test/helpers.test.js +78 -0
- package/test/player.test.js +696 -0
- package/test/template.test.js +70 -0
- package/test/utils.test.js +21 -0
- package/test/web-test-runner.config.mjs +29 -0
- package/tsconfig.json +21 -0
package/src/index.ts
ADDED
|
@@ -0,0 +1,952 @@
|
|
|
1
|
+
// import playback-core here to make sure that the polyfill is loaded
|
|
2
|
+
import '@mux/playback-core';
|
|
3
|
+
// @ts-ignore
|
|
4
|
+
import { MediaController } from 'media-chrome';
|
|
5
|
+
import MediaThemeMux from './media-theme-mux/media-theme-mux';
|
|
6
|
+
import MuxVideoElement, { MediaError } from '@mux/mux-video';
|
|
7
|
+
import { Metadata, StreamTypes } from '@mux/playback-core';
|
|
8
|
+
import VideoApiElement, { initVideoApi } from './video-api';
|
|
9
|
+
import {
|
|
10
|
+
getCcSubTracks,
|
|
11
|
+
getPlayerVersion,
|
|
12
|
+
hasVolumeSupportAsync,
|
|
13
|
+
isInLiveWindow,
|
|
14
|
+
seekToLive,
|
|
15
|
+
toPropName,
|
|
16
|
+
} from './helpers';
|
|
17
|
+
import { template } from './template';
|
|
18
|
+
import { render } from './html';
|
|
19
|
+
import { getErrorLogs } from './errors';
|
|
20
|
+
import { toNumberOrUndefined, i18n, parseJwt, containsComposedNode } from './utils';
|
|
21
|
+
import * as logger from './logger';
|
|
22
|
+
import type { MuxTemplateProps, ErrorEvent } from './types';
|
|
23
|
+
|
|
24
|
+
export { MediaError };
|
|
25
|
+
export type Tokens = {
|
|
26
|
+
playback?: string;
|
|
27
|
+
thumbnail?: string;
|
|
28
|
+
storyboard?: string;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
type MediaController = Element & { media: HTMLVideoElement };
|
|
32
|
+
|
|
33
|
+
const streamTypeValues = Object.values(StreamTypes);
|
|
34
|
+
|
|
35
|
+
const SMALL_BREAKPOINT = 700;
|
|
36
|
+
const XSMALL_BREAKPOINT = 300;
|
|
37
|
+
const MediaChromeSizes = {
|
|
38
|
+
LG: 'large',
|
|
39
|
+
SM: 'small',
|
|
40
|
+
XS: 'extra-small',
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
function getPlayerSize(el: Element) {
|
|
44
|
+
const muxPlayerRect = el.getBoundingClientRect();
|
|
45
|
+
return muxPlayerRect.width < XSMALL_BREAKPOINT
|
|
46
|
+
? MediaChromeSizes.XS
|
|
47
|
+
: muxPlayerRect.width < SMALL_BREAKPOINT
|
|
48
|
+
? MediaChromeSizes.SM
|
|
49
|
+
: MediaChromeSizes.LG;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const VideoAttributes = {
|
|
53
|
+
SRC: 'src',
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const MuxVideoAttributes = {
|
|
57
|
+
ENV_KEY: 'env-key',
|
|
58
|
+
DEBUG: 'debug',
|
|
59
|
+
PLAYBACK_ID: 'playback-id',
|
|
60
|
+
METADATA_URL: 'metadata-url',
|
|
61
|
+
PREFER_MSE: 'prefer-mse',
|
|
62
|
+
PLAYER_SOFTWARE_VERSION: 'player-software-version',
|
|
63
|
+
PLAYER_SOFTWARE_NAME: 'player-software-name',
|
|
64
|
+
METADATA_VIDEO_ID: 'metadata-video-id',
|
|
65
|
+
METADATA_VIDEO_TITLE: 'metadata-video-title',
|
|
66
|
+
METADATA_VIEWER_USER_ID: 'metadata-viewer-user-id',
|
|
67
|
+
BEACON_COLLECTION_DOMAIN: 'beacon-collection-domain',
|
|
68
|
+
CUSTOM_DOMAIN: 'custom-domain',
|
|
69
|
+
TYPE: 'type',
|
|
70
|
+
STREAM_TYPE: 'stream-type',
|
|
71
|
+
START_TIME: 'start-time',
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
const PlayerAttributes = {
|
|
75
|
+
DEFAULT_HIDDEN_CAPTIONS: 'default-hidden-captions',
|
|
76
|
+
PRIMARY_COLOR: 'primary-color',
|
|
77
|
+
SECONDARY_COLOR: 'secondary-color',
|
|
78
|
+
FORWARD_SEEK_OFFSET: 'forward-seek-offset',
|
|
79
|
+
BACKWARD_SEEK_OFFSET: 'backward-seek-offset',
|
|
80
|
+
PLAYBACK_TOKEN: 'playback-token',
|
|
81
|
+
THUMBNAIL_TOKEN: 'thumbnail-token',
|
|
82
|
+
STORYBOARD_TOKEN: 'storyboard-token',
|
|
83
|
+
THUMBNAIL_TIME: 'thumbnail-time',
|
|
84
|
+
AUDIO: 'audio',
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
function getProps(el: MuxPlayerElement, state?: any): MuxTemplateProps {
|
|
88
|
+
return {
|
|
89
|
+
// Give priority to playbackId derrived asset URL's if playbackId is set.
|
|
90
|
+
src: !el.playbackId && el.src,
|
|
91
|
+
// NOTE: Always use the externally set poster attribute here to guarantee
|
|
92
|
+
// it's used if/when it's been explicitly set "from the outside"
|
|
93
|
+
// (See template.ts for additional context) (CJP)
|
|
94
|
+
poster: el.getAttribute('poster'),
|
|
95
|
+
thumbnailTime: !el.tokens.thumbnail && el.thumbnailTime,
|
|
96
|
+
autoplay: el.autoplay,
|
|
97
|
+
crossOrigin: el.crossOrigin,
|
|
98
|
+
loop: el.loop,
|
|
99
|
+
muted: el.muted,
|
|
100
|
+
paused: el.paused,
|
|
101
|
+
playsInline: el.playsInline,
|
|
102
|
+
preload: el.preload,
|
|
103
|
+
playbackId: el.playbackId,
|
|
104
|
+
envKey: el.envKey,
|
|
105
|
+
debug: el.debug,
|
|
106
|
+
tokens: el.tokens,
|
|
107
|
+
beaconCollectionDomain: el.beaconCollectionDomain,
|
|
108
|
+
metadata: el.metadata,
|
|
109
|
+
playerSoftwareName: el.playerSoftwareName,
|
|
110
|
+
playerSoftwareVersion: el.playerSoftwareVersion,
|
|
111
|
+
startTime: el.startTime,
|
|
112
|
+
preferMse: el.preferMse,
|
|
113
|
+
audio: el.audio,
|
|
114
|
+
streamType: el.streamType,
|
|
115
|
+
primaryColor: el.primaryColor,
|
|
116
|
+
secondaryColor: el.secondaryColor,
|
|
117
|
+
forwardSeekOffset: el.forwardSeekOffset,
|
|
118
|
+
backwardSeekOffset: el.backwardSeekOffset,
|
|
119
|
+
defaultHiddenCaptions: el.defaultHiddenCaptions,
|
|
120
|
+
customDomain: el.getAttribute(MuxVideoAttributes.CUSTOM_DOMAIN) ?? undefined,
|
|
121
|
+
playerSize: getPlayerSize(el.mediaController ?? el),
|
|
122
|
+
hasCaptions: !!getCcSubTracks(el).length,
|
|
123
|
+
// NOTE: In order to guarantee all expected metadata props are set "from the outside" when used
|
|
124
|
+
// and to guarantee they'll all be set *before* the playback id is set, using attr values here (CJP)
|
|
125
|
+
metadataVideoId: el.getAttribute(MuxVideoAttributes.METADATA_VIDEO_ID),
|
|
126
|
+
metadataVideoTitle: el.getAttribute(MuxVideoAttributes.METADATA_VIDEO_TITLE),
|
|
127
|
+
metadataViewerUserId: el.getAttribute(MuxVideoAttributes.METADATA_VIEWER_USER_ID),
|
|
128
|
+
...state,
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const MuxVideoAttributeNames = Object.values(MuxVideoAttributes);
|
|
133
|
+
const PlayerAttributeNames = Object.values(PlayerAttributes);
|
|
134
|
+
const playerSoftwareVersion = getPlayerVersion();
|
|
135
|
+
const playerSoftwareName = 'mux-player';
|
|
136
|
+
|
|
137
|
+
const initialState = {
|
|
138
|
+
dialog: undefined,
|
|
139
|
+
isDialogOpen: false,
|
|
140
|
+
inLiveWindow: false,
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
class MuxPlayerElement extends VideoApiElement {
|
|
144
|
+
#isInit = false;
|
|
145
|
+
#tokens = {};
|
|
146
|
+
#userInactive = true;
|
|
147
|
+
#resizeObserver?: ResizeObserver;
|
|
148
|
+
#state: Partial<MuxTemplateProps> = {
|
|
149
|
+
...initialState,
|
|
150
|
+
supportsAirPlay: false,
|
|
151
|
+
supportsVolume: false,
|
|
152
|
+
onCloseErrorDialog: () => this.#setState({ dialog: undefined, isDialogOpen: false }),
|
|
153
|
+
onInitFocusDialog: (e) => {
|
|
154
|
+
const isFocusedElementInPlayer = containsComposedNode(this, document.activeElement);
|
|
155
|
+
if (!isFocusedElementInPlayer) e.preventDefault();
|
|
156
|
+
},
|
|
157
|
+
onSeekToLive: () => seekToLive(this),
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
static get observedAttributes() {
|
|
161
|
+
return [...(VideoApiElement.observedAttributes ?? []), ...MuxVideoAttributeNames, ...PlayerAttributeNames];
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
constructor() {
|
|
165
|
+
super();
|
|
166
|
+
|
|
167
|
+
this.attachShadow({ mode: 'open' });
|
|
168
|
+
|
|
169
|
+
// If the custom element is defined before the <mux-player> HTML is parsed
|
|
170
|
+
// no attributes will be available in the constructor (construction process).
|
|
171
|
+
// Wait until initializing attributes in the attributeChangedCallback.
|
|
172
|
+
// If this element is connected to the DOM, the attributes will be available.
|
|
173
|
+
if (this.isConnected) {
|
|
174
|
+
this.#init();
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
#init() {
|
|
179
|
+
if (this.#isInit) return;
|
|
180
|
+
this.#isInit = true;
|
|
181
|
+
|
|
182
|
+
// The next line triggers the first render of the template.
|
|
183
|
+
this.#setState({ playerSize: getPlayerSize(this) });
|
|
184
|
+
|
|
185
|
+
// Fixes a bug in React where mux-player's CE children were not upgraded yet.
|
|
186
|
+
// These lines ensure the rendered mux-video and media-controller are upgraded,
|
|
187
|
+
// even before they are connected to the main document.
|
|
188
|
+
customElements.upgrade(this.theme as Node);
|
|
189
|
+
if (!(this.theme instanceof MediaThemeMux)) {
|
|
190
|
+
logger.error('<media-theme-mux> failed to upgrade!');
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
customElements.upgrade(this.media as Node);
|
|
194
|
+
if (!(this.media instanceof MuxVideoElement)) {
|
|
195
|
+
logger.error('<mux-video> failed to upgrade!');
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
customElements.upgrade(this.mediaController as Node);
|
|
199
|
+
if (!(this.mediaController instanceof MediaController)) {
|
|
200
|
+
logger.error('<media-controller> failed to upgrade!');
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
initVideoApi(this);
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* @todo determine sensible defaults for preloading buffer
|
|
207
|
+
* @see https://github.com/muxinc/elements/issues/51
|
|
208
|
+
*/
|
|
209
|
+
// if (this.media?._hls) {
|
|
210
|
+
// // Temporarily here to load less segments on page load, remove later!!!!
|
|
211
|
+
// this.media._hls.config.maxMaxBufferLength = 2;
|
|
212
|
+
// }
|
|
213
|
+
|
|
214
|
+
this.#setUpErrors();
|
|
215
|
+
this.#setUpCaptionsButton();
|
|
216
|
+
this.#setUpAirplayButton();
|
|
217
|
+
this.#setUpVolumeRange();
|
|
218
|
+
this.#monitorLiveWindow();
|
|
219
|
+
this.#userInactive = this.mediaController?.hasAttribute('user-inactive') ?? true;
|
|
220
|
+
this.#setUpCaptionsMovement();
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
get theme(): Element | null | undefined {
|
|
224
|
+
return Array.from(this.shadowRoot?.children ?? []).find(({ localName }) => localName.startsWith('media-theme-'));
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
get mediaController(): MediaController | null | undefined {
|
|
228
|
+
return this.theme?.shadowRoot?.querySelector('media-controller');
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
connectedCallback() {
|
|
232
|
+
this.#renderChrome();
|
|
233
|
+
this.#initResizing();
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
disconnectedCallback() {
|
|
237
|
+
this.#deinitResizing();
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
#setState(newState: Record<string, any>) {
|
|
241
|
+
Object.assign(this.#state, newState);
|
|
242
|
+
this.#render();
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
#render(props: Record<string, any> = {}) {
|
|
246
|
+
render(template(getProps(this, { ...this.#state, ...props })), this.shadowRoot as Node);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
#renderChrome() {
|
|
250
|
+
if (this.#state.playerSize != getPlayerSize(this.mediaController ?? this)) {
|
|
251
|
+
this.#setState({ playerSize: getPlayerSize(this.mediaController ?? this) });
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
#initResizing() {
|
|
256
|
+
this.#resizeObserver = new ResizeObserver(() => this.#renderChrome());
|
|
257
|
+
this.#resizeObserver.observe(this.mediaController ?? this);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
#deinitResizing() {
|
|
261
|
+
this.#resizeObserver?.disconnect();
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
#monitorLiveWindow() {
|
|
265
|
+
this.mediaController?.addEventListener('mediaplayrequest', (event) => {
|
|
266
|
+
if (
|
|
267
|
+
(event.target as Element)?.localName === 'media-play-button' &&
|
|
268
|
+
this.streamType &&
|
|
269
|
+
[StreamTypes.LIVE, StreamTypes.LL_LIVE, StreamTypes.DVR, StreamTypes.LL_DVR].includes(this.streamType as any)
|
|
270
|
+
) {
|
|
271
|
+
// playback core should handle the seek to live on first play
|
|
272
|
+
if (this.hasPlayed) {
|
|
273
|
+
seekToLive(this);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
const updateLiveWindow = () => {
|
|
279
|
+
const nextInLiveWindow = isInLiveWindow(this);
|
|
280
|
+
const prevInLiveWindow = this.#state.inLiveWindow;
|
|
281
|
+
if (nextInLiveWindow !== prevInLiveWindow) {
|
|
282
|
+
this.#setState({ inLiveWindow: nextInLiveWindow });
|
|
283
|
+
this.dispatchEvent(
|
|
284
|
+
new CustomEvent('inlivewindowchange', { composed: true, bubbles: true, detail: this.inLiveWindow })
|
|
285
|
+
);
|
|
286
|
+
}
|
|
287
|
+
};
|
|
288
|
+
this.media?.addEventListener('progress', updateLiveWindow);
|
|
289
|
+
this.media?.addEventListener('waiting', updateLiveWindow);
|
|
290
|
+
this.media?.addEventListener('timeupdate', updateLiveWindow);
|
|
291
|
+
this.media?.addEventListener('emptied', updateLiveWindow);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
#setUpErrors() {
|
|
295
|
+
const onError = (event: Event) => {
|
|
296
|
+
let { detail: error }: { detail: any } = event as CustomEvent;
|
|
297
|
+
|
|
298
|
+
if (!(error instanceof MediaError)) {
|
|
299
|
+
error = new MediaError(error.message, error.code, error.fatal);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// Don't show an error dialog if it's not fatal.
|
|
303
|
+
if (!error?.fatal) {
|
|
304
|
+
logger.warn(error);
|
|
305
|
+
if (error.data) {
|
|
306
|
+
logger.warn(`${error.name} data:`, error.data);
|
|
307
|
+
}
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
const { dialog, devlog } = getErrorLogs(error, !window.navigator.onLine, this.playbackId, this.playbackToken);
|
|
312
|
+
|
|
313
|
+
if (devlog.message) {
|
|
314
|
+
logger.devlog(devlog);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
logger.error(error);
|
|
318
|
+
if (error.data) {
|
|
319
|
+
logger.error(`${error.name} data:`, error.data);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
this.#setState({ isDialogOpen: true, dialog });
|
|
323
|
+
};
|
|
324
|
+
|
|
325
|
+
// Keep this event listener on mux-player instead of calling onError directly
|
|
326
|
+
// from video.onerror. This allows us to simulate errors from the outside.
|
|
327
|
+
this.addEventListener('error', onError);
|
|
328
|
+
|
|
329
|
+
if (this.media) {
|
|
330
|
+
this.media.errorTranslator = (errorEvent: ErrorEvent = {}) => {
|
|
331
|
+
if (!this.media?.error) return errorEvent;
|
|
332
|
+
|
|
333
|
+
const { devlog } = getErrorLogs(
|
|
334
|
+
this.media?.error,
|
|
335
|
+
!window.navigator.onLine,
|
|
336
|
+
this.playbackId,
|
|
337
|
+
this.playbackToken,
|
|
338
|
+
false
|
|
339
|
+
);
|
|
340
|
+
|
|
341
|
+
return {
|
|
342
|
+
player_error_code: this.media?.error.code,
|
|
343
|
+
player_error_message: devlog.message ? String(devlog.message) : errorEvent.player_error_message,
|
|
344
|
+
};
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
this.media?.addEventListener('error', (event: Event) => {
|
|
349
|
+
let { detail: error }: { detail: any } = event as CustomEvent;
|
|
350
|
+
|
|
351
|
+
// If it is a hls.js error event there will be an error object in the event.
|
|
352
|
+
// If it is a native video error event there will be no error object.
|
|
353
|
+
if (!error) {
|
|
354
|
+
const { message, code } = this.media?.error ?? {};
|
|
355
|
+
error = new MediaError(message, code);
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
// Don't fire a mux-player error event for non-fatal errors.
|
|
359
|
+
if (!error?.fatal) return;
|
|
360
|
+
|
|
361
|
+
this.dispatchEvent(
|
|
362
|
+
new CustomEvent('error', {
|
|
363
|
+
detail: error,
|
|
364
|
+
})
|
|
365
|
+
);
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
#setUpCaptionsButton() {
|
|
370
|
+
const onTrackCountChange = () => this.#render();
|
|
371
|
+
this.media?.textTracks?.addEventListener('addtrack', onTrackCountChange);
|
|
372
|
+
this.media?.textTracks?.addEventListener('removetrack', onTrackCountChange);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
#setUpCaptionsMovement() {
|
|
376
|
+
type Maybe<T> = T | null | undefined;
|
|
377
|
+
|
|
378
|
+
const mc: Maybe<MediaController> = this.mediaController;
|
|
379
|
+
|
|
380
|
+
// Any Safari
|
|
381
|
+
const isSafari = /.*Version\/.*Safari\/.*/.test(navigator.userAgent);
|
|
382
|
+
const isIphone = /.*iPhone.*/.test(navigator.userAgent);
|
|
383
|
+
|
|
384
|
+
// ignore iphones
|
|
385
|
+
if (isIphone) return;
|
|
386
|
+
|
|
387
|
+
let selectedTrack: TextTrack;
|
|
388
|
+
const cuesmap = new WeakMap();
|
|
389
|
+
|
|
390
|
+
const shouldSkipLineToggle = () => {
|
|
391
|
+
// skip line toggle when:
|
|
392
|
+
// - streamType is live, unless secondary color is set or player size is too small
|
|
393
|
+
// - native fullscreen on iPhones
|
|
394
|
+
return (
|
|
395
|
+
this.streamType &&
|
|
396
|
+
[StreamTypes.LIVE, StreamTypes.LL_LIVE].includes(this.streamType as any) &&
|
|
397
|
+
!this.secondaryColor &&
|
|
398
|
+
this.offsetWidth >= 800
|
|
399
|
+
);
|
|
400
|
+
};
|
|
401
|
+
|
|
402
|
+
// toggles activeCues for a particular track depending on whether the user is active or not
|
|
403
|
+
const toggleLines = (track: TextTrack, userInactive: boolean) => {
|
|
404
|
+
if (shouldSkipLineToggle()) {
|
|
405
|
+
return;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
const cues = Array.from((track && track.activeCues) || []) as VTTCue[];
|
|
409
|
+
|
|
410
|
+
cues.forEach((cue) => {
|
|
411
|
+
// ignore cues that are
|
|
412
|
+
// - positioned vertically via percentage.
|
|
413
|
+
// - cues that are not at the bottom
|
|
414
|
+
// - line is less than -5
|
|
415
|
+
// - line is between 0 and 10
|
|
416
|
+
if (!cue.snapToLines || cue.line < -5 || (cue.line >= 0 && cue.line < 10)) {
|
|
417
|
+
return;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
// if the user is active or if the player is paused, the captions should be moved up
|
|
421
|
+
if (!userInactive || this.paused) {
|
|
422
|
+
// for cues that have more than one line, we want to push the cue further up
|
|
423
|
+
const lines = cue.text.split('\n').length;
|
|
424
|
+
// start at -3 to account for thumbnails as well.
|
|
425
|
+
// default safari styles are taller than other browsers
|
|
426
|
+
let offset = isSafari ? -2 : -3;
|
|
427
|
+
|
|
428
|
+
if (this.streamType && [StreamTypes.LIVE, StreamTypes.LL_LIVE].includes(this.streamType as any)) {
|
|
429
|
+
offset = isSafari ? -1 : -2;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
const setTo = offset - lines;
|
|
433
|
+
|
|
434
|
+
// if the line is already set to -4, we don't want to update it again
|
|
435
|
+
// this can happen in the same tick on chrome and safari which fire a cuechange
|
|
436
|
+
// event when the line property is changed to a different value.
|
|
437
|
+
if (cue.line === setTo) {
|
|
438
|
+
return;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
if (!cuesmap.has(cue)) {
|
|
442
|
+
cuesmap.set(cue, cue.line);
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
// we have to set line to 0 first due to a chrome bug https://crbug.com/1308892
|
|
446
|
+
cue.line = 0;
|
|
447
|
+
cue.line = setTo;
|
|
448
|
+
} else {
|
|
449
|
+
setTimeout(() => {
|
|
450
|
+
cue.line = cuesmap.get(cue) || 'auto';
|
|
451
|
+
}, 500);
|
|
452
|
+
}
|
|
453
|
+
});
|
|
454
|
+
};
|
|
455
|
+
|
|
456
|
+
// this is necessary so that if a cue becomes active while the user is active, we still position it above the control bar
|
|
457
|
+
const cuechangeHandler = () => {
|
|
458
|
+
toggleLines(selectedTrack, mc?.hasAttribute('user-inactive') ?? false);
|
|
459
|
+
};
|
|
460
|
+
|
|
461
|
+
const selectTrack = () => {
|
|
462
|
+
const tracks = Array.from(mc?.media?.textTracks || []) as TextTrack[];
|
|
463
|
+
const newSelectedTrack = tracks.filter(
|
|
464
|
+
(t) => ['subtitles', 'captions'].includes(t.kind) && t.mode === 'showing'
|
|
465
|
+
)[0] as TextTrack;
|
|
466
|
+
|
|
467
|
+
if (newSelectedTrack !== selectedTrack) {
|
|
468
|
+
selectedTrack?.removeEventListener('cuechange', cuechangeHandler);
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
selectedTrack = newSelectedTrack;
|
|
472
|
+
selectedTrack?.addEventListener('cuechange', cuechangeHandler);
|
|
473
|
+
// it's possible there are currently active cues on the new track
|
|
474
|
+
toggleLines(selectedTrack, this.#userInactive);
|
|
475
|
+
};
|
|
476
|
+
|
|
477
|
+
selectTrack();
|
|
478
|
+
// update the selected track as necessary
|
|
479
|
+
mc?.media?.textTracks.addEventListener('change', selectTrack);
|
|
480
|
+
mc?.media?.textTracks.addEventListener('addtrack', selectTrack);
|
|
481
|
+
|
|
482
|
+
mc?.addEventListener('userinactivechange', () => {
|
|
483
|
+
const newUserInactive = mc?.hasAttribute('user-inactive');
|
|
484
|
+
|
|
485
|
+
if (this.#userInactive === newUserInactive) {
|
|
486
|
+
return;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
this.#userInactive = newUserInactive;
|
|
490
|
+
|
|
491
|
+
toggleLines(selectedTrack, this.#userInactive);
|
|
492
|
+
});
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
#setUpAirplayButton() {
|
|
496
|
+
if (!!(globalThis as any).WebKitPlaybackTargetAvailabilityEvent) {
|
|
497
|
+
const onPlaybackTargetAvailability = (evt: any) => {
|
|
498
|
+
const supportsAirPlay = evt.availability === 'available';
|
|
499
|
+
this.#setState({ supportsAirPlay });
|
|
500
|
+
};
|
|
501
|
+
|
|
502
|
+
this.media?.addEventListener('webkitplaybacktargetavailabilitychanged', onPlaybackTargetAvailability);
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
async #setUpVolumeRange() {
|
|
507
|
+
const supportsVolume = await hasVolumeSupportAsync();
|
|
508
|
+
this.#setState({ supportsVolume });
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
attributeChangedCallback(attrName: string, oldValue: string | null, newValue: string) {
|
|
512
|
+
if (!this.#isInit) {
|
|
513
|
+
// Initialize right after construction when the attributes become available.
|
|
514
|
+
this.#init();
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
super.attributeChangedCallback(attrName, oldValue, newValue);
|
|
518
|
+
|
|
519
|
+
const shouldClearState = [
|
|
520
|
+
MuxVideoAttributes.PLAYBACK_ID,
|
|
521
|
+
VideoAttributes.SRC,
|
|
522
|
+
PlayerAttributes.PLAYBACK_TOKEN,
|
|
523
|
+
].includes(attrName);
|
|
524
|
+
|
|
525
|
+
if (shouldClearState && oldValue !== newValue) {
|
|
526
|
+
this.#state = { ...this.#state, ...initialState };
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
this.#render({ [toPropName(attrName)]: newValue });
|
|
530
|
+
|
|
531
|
+
switch (attrName) {
|
|
532
|
+
case PlayerAttributes.THUMBNAIL_TIME: {
|
|
533
|
+
if (newValue != null && this.tokens.thumbnail) {
|
|
534
|
+
logger.warn(
|
|
535
|
+
i18n(`Use of thumbnail-time with thumbnail-token is currently unsupported. Ignore thumbnail-time.`).format(
|
|
536
|
+
{}
|
|
537
|
+
)
|
|
538
|
+
);
|
|
539
|
+
}
|
|
540
|
+
break;
|
|
541
|
+
}
|
|
542
|
+
case PlayerAttributes.THUMBNAIL_TOKEN: {
|
|
543
|
+
const { aud } = parseJwt(newValue);
|
|
544
|
+
if (newValue && aud !== 't') {
|
|
545
|
+
logger.warn(
|
|
546
|
+
i18n(`The provided thumbnail-token should have audience value 't' instead of '{aud}'.`).format({ aud })
|
|
547
|
+
);
|
|
548
|
+
}
|
|
549
|
+
break;
|
|
550
|
+
}
|
|
551
|
+
case PlayerAttributes.STORYBOARD_TOKEN: {
|
|
552
|
+
const { aud } = parseJwt(newValue);
|
|
553
|
+
if (newValue && aud !== 's') {
|
|
554
|
+
logger.warn(
|
|
555
|
+
i18n(`The provided storyboard-token should have audience value 's' instead of '{aud}'.`).format({ aud })
|
|
556
|
+
);
|
|
557
|
+
}
|
|
558
|
+
break;
|
|
559
|
+
}
|
|
560
|
+
case MuxVideoAttributes.PLAYBACK_ID: {
|
|
561
|
+
if (newValue.includes('?token')) {
|
|
562
|
+
logger.error(
|
|
563
|
+
i18n(
|
|
564
|
+
'The specificed playback ID {playbackId} contains a token which must be provided via the playback-token attribute.'
|
|
565
|
+
).format({
|
|
566
|
+
playbackId: newValue,
|
|
567
|
+
})
|
|
568
|
+
);
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
if (!this.streamType) {
|
|
572
|
+
logger.devlog({
|
|
573
|
+
file: 'invalid-stream-type.md',
|
|
574
|
+
message: String(
|
|
575
|
+
i18n(
|
|
576
|
+
`No stream-type value supplied. Defaulting to \`on-demand\`. Please provide stream-type as either: \`on-demand\`, \`live\` or \`ll-live\``
|
|
577
|
+
)
|
|
578
|
+
),
|
|
579
|
+
});
|
|
580
|
+
} else if (this.streamType != null && !streamTypeValues.includes(this.streamType as any)) {
|
|
581
|
+
logger.devlog({
|
|
582
|
+
file: 'invalid-stream-type.md',
|
|
583
|
+
message: i18n(
|
|
584
|
+
`Invalid stream-type value supplied: \`{streamType}\`. Please provide stream-type as either: \`on-demand\`, \`live\` or \`ll-live\``
|
|
585
|
+
).format({ streamType: this.streamType }),
|
|
586
|
+
});
|
|
587
|
+
}
|
|
588
|
+
break;
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
get hasPlayed() {
|
|
594
|
+
return this.mediaController?.hasAttribute('media-has-played') ?? false;
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
get inLiveWindow() {
|
|
598
|
+
return this.#state.inLiveWindow;
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
/**
|
|
602
|
+
* @deprecated please use ._hls instead
|
|
603
|
+
*/
|
|
604
|
+
get hls() {
|
|
605
|
+
logger.warn('<mux-player>.hls is deprecated, please use ._hls instead');
|
|
606
|
+
return this._hls;
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
get _hls() {
|
|
610
|
+
return this.media?._hls;
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
get mux() {
|
|
614
|
+
return this.media?.mux;
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
/**
|
|
618
|
+
* Get the thumbnailTime offset used for the poster image.
|
|
619
|
+
*/
|
|
620
|
+
get audio() {
|
|
621
|
+
return this.hasAttribute(PlayerAttributes.AUDIO);
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
/**
|
|
625
|
+
* Set the thumbnailTime offset used for the poster image.
|
|
626
|
+
*/
|
|
627
|
+
set audio(val: boolean) {
|
|
628
|
+
if (!val) {
|
|
629
|
+
this.removeAttribute(PlayerAttributes.AUDIO);
|
|
630
|
+
}
|
|
631
|
+
this.setAttribute(PlayerAttributes.AUDIO, '');
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
/**
|
|
635
|
+
* Get the thumbnailTime offset used for the poster image.
|
|
636
|
+
*/
|
|
637
|
+
get thumbnailTime() {
|
|
638
|
+
return toNumberOrUndefined(this.getAttribute(PlayerAttributes.THUMBNAIL_TIME));
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
/**
|
|
642
|
+
* Set the thumbnailTime offset used for the poster image.
|
|
643
|
+
*/
|
|
644
|
+
set thumbnailTime(val: number | undefined) {
|
|
645
|
+
this.setAttribute(PlayerAttributes.THUMBNAIL_TIME, `${val}`);
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
/**
|
|
649
|
+
* Get the primary color used by the player.
|
|
650
|
+
*/
|
|
651
|
+
get primaryColor() {
|
|
652
|
+
return this.getAttribute(PlayerAttributes.PRIMARY_COLOR) ?? undefined;
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
/**
|
|
656
|
+
* Set the primary color used by the player.
|
|
657
|
+
*/
|
|
658
|
+
set primaryColor(val: string | undefined) {
|
|
659
|
+
this.setAttribute(PlayerAttributes.PRIMARY_COLOR, `${val}`);
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
/**
|
|
663
|
+
* Get the secondary color used by the player.
|
|
664
|
+
*/
|
|
665
|
+
get secondaryColor() {
|
|
666
|
+
return this.getAttribute(PlayerAttributes.SECONDARY_COLOR) ?? undefined;
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
/**
|
|
670
|
+
* Set the secondary color used by the player.
|
|
671
|
+
*/
|
|
672
|
+
set secondaryColor(val: string | undefined) {
|
|
673
|
+
this.setAttribute(PlayerAttributes.SECONDARY_COLOR, `${val}`);
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
/**
|
|
677
|
+
* Get the offset applied to the forward seek button.
|
|
678
|
+
*/
|
|
679
|
+
get forwardSeekOffset() {
|
|
680
|
+
return toNumberOrUndefined(this.getAttribute(PlayerAttributes.FORWARD_SEEK_OFFSET)) ?? 10;
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
/**
|
|
684
|
+
* Set the offset applied to the forward seek button.
|
|
685
|
+
*/
|
|
686
|
+
set forwardSeekOffset(val: number | undefined) {
|
|
687
|
+
this.setAttribute(PlayerAttributes.FORWARD_SEEK_OFFSET, `${val}`);
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
/**
|
|
691
|
+
* Get the offset applied to the backward seek button.
|
|
692
|
+
*/
|
|
693
|
+
get backwardSeekOffset() {
|
|
694
|
+
return toNumberOrUndefined(this.getAttribute(PlayerAttributes.BACKWARD_SEEK_OFFSET)) ?? 10;
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
/**
|
|
698
|
+
* Set the offset applied to the forward seek button.
|
|
699
|
+
*/
|
|
700
|
+
set backwardSeekOffset(val: number | undefined) {
|
|
701
|
+
this.setAttribute(PlayerAttributes.BACKWARD_SEEK_OFFSET, `${val}`);
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
/**
|
|
705
|
+
* Get the boolean value of default hidden captions.
|
|
706
|
+
* By default returns false so captions are enabled on initial load.
|
|
707
|
+
*/
|
|
708
|
+
get defaultHiddenCaptions() {
|
|
709
|
+
return this.hasAttribute(PlayerAttributes.DEFAULT_HIDDEN_CAPTIONS);
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
/**
|
|
713
|
+
* Get the player software name. Used by Mux Data.
|
|
714
|
+
*/
|
|
715
|
+
get playerSoftwareName() {
|
|
716
|
+
return this.getAttribute(MuxVideoAttributes.PLAYER_SOFTWARE_NAME) ?? playerSoftwareName;
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
/**
|
|
720
|
+
* Get the player software version. Used by Mux Data.
|
|
721
|
+
*/
|
|
722
|
+
get playerSoftwareVersion() {
|
|
723
|
+
return this.getAttribute(MuxVideoAttributes.PLAYER_SOFTWARE_VERSION) ?? playerSoftwareVersion;
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
/**
|
|
727
|
+
* Get the beacon collection domain. Used by Mux Data.
|
|
728
|
+
*/
|
|
729
|
+
get beaconCollectionDomain() {
|
|
730
|
+
return this.getAttribute(MuxVideoAttributes.BEACON_COLLECTION_DOMAIN) ?? undefined;
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
/**
|
|
734
|
+
* Set the beacon collection domain. Used by Mux Data.
|
|
735
|
+
*/
|
|
736
|
+
set beaconCollectionDomain(val: string | undefined) {
|
|
737
|
+
// don't cause an infinite loop
|
|
738
|
+
if (val === this.beaconCollectionDomain) return;
|
|
739
|
+
|
|
740
|
+
if (val) {
|
|
741
|
+
this.setAttribute(MuxVideoAttributes.BEACON_COLLECTION_DOMAIN, val);
|
|
742
|
+
} else {
|
|
743
|
+
this.removeAttribute(MuxVideoAttributes.BEACON_COLLECTION_DOMAIN);
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
/**
|
|
748
|
+
* Get Mux asset playback id.
|
|
749
|
+
*/
|
|
750
|
+
get playbackId() {
|
|
751
|
+
// Don't get the mux-video attribute here because it could have the
|
|
752
|
+
// playback token appended to it.
|
|
753
|
+
return this.getAttribute(MuxVideoAttributes.PLAYBACK_ID) ?? undefined;
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
/**
|
|
757
|
+
* Set Mux asset playback id.
|
|
758
|
+
*/
|
|
759
|
+
set playbackId(val: string | undefined) {
|
|
760
|
+
this.setAttribute(MuxVideoAttributes.PLAYBACK_ID, `${val}`);
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
/**
|
|
764
|
+
* Get Mux asset custom domain.
|
|
765
|
+
*/
|
|
766
|
+
get customDomain() {
|
|
767
|
+
return this.getAttribute(MuxVideoAttributes.CUSTOM_DOMAIN) ?? undefined;
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
/**
|
|
771
|
+
* Set Mux asset custom domain.
|
|
772
|
+
*/
|
|
773
|
+
set customDomain(val: string | undefined) {
|
|
774
|
+
// dont' cause an infinite loop
|
|
775
|
+
if (val === this.customDomain) return;
|
|
776
|
+
|
|
777
|
+
if (val) {
|
|
778
|
+
this.setAttribute(MuxVideoAttributes.CUSTOM_DOMAIN, val);
|
|
779
|
+
} else {
|
|
780
|
+
this.removeAttribute(MuxVideoAttributes.CUSTOM_DOMAIN);
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
/**
|
|
785
|
+
* Get Mux Data env key.
|
|
786
|
+
*/
|
|
787
|
+
get envKey() {
|
|
788
|
+
return getVideoAttribute(this, MuxVideoAttributes.ENV_KEY) ?? undefined;
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
/**
|
|
792
|
+
* Set Mux Data env key.
|
|
793
|
+
*/
|
|
794
|
+
set envKey(val: string | undefined) {
|
|
795
|
+
this.setAttribute(MuxVideoAttributes.ENV_KEY, `${val}`);
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
/**
|
|
799
|
+
* Get video engine debug flag.
|
|
800
|
+
*/
|
|
801
|
+
get debug() {
|
|
802
|
+
return getVideoAttribute(this, MuxVideoAttributes.DEBUG) != null;
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
/**
|
|
806
|
+
* Set video engine debug flag.
|
|
807
|
+
*/
|
|
808
|
+
set debug(val) {
|
|
809
|
+
if (val) {
|
|
810
|
+
this.setAttribute(MuxVideoAttributes.DEBUG, '');
|
|
811
|
+
} else {
|
|
812
|
+
this.removeAttribute(MuxVideoAttributes.DEBUG);
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
/**
|
|
817
|
+
* Get stream type.
|
|
818
|
+
*/
|
|
819
|
+
get streamType() {
|
|
820
|
+
return getVideoAttribute(this, MuxVideoAttributes.STREAM_TYPE);
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
/**
|
|
824
|
+
* Set stream type.
|
|
825
|
+
*/
|
|
826
|
+
set streamType(val) {
|
|
827
|
+
this.setAttribute(MuxVideoAttributes.STREAM_TYPE, `${val}`);
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
/**
|
|
831
|
+
* Get the start time.
|
|
832
|
+
*/
|
|
833
|
+
get startTime() {
|
|
834
|
+
return toNumberOrUndefined(getVideoAttribute(this, MuxVideoAttributes.START_TIME));
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
/**
|
|
838
|
+
* Set the start time.
|
|
839
|
+
*/
|
|
840
|
+
set startTime(val) {
|
|
841
|
+
this.setAttribute(MuxVideoAttributes.START_TIME, `${val}`);
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
/**
|
|
845
|
+
* Get the preference flag for using media source.
|
|
846
|
+
*/
|
|
847
|
+
get preferMse() {
|
|
848
|
+
return getVideoAttribute(this, MuxVideoAttributes.PREFER_MSE) != null;
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
/**
|
|
852
|
+
* Set the preference flag for using media source.
|
|
853
|
+
*/
|
|
854
|
+
set preferMse(val) {
|
|
855
|
+
if (val) {
|
|
856
|
+
this.setAttribute(MuxVideoAttributes.PREFER_MSE, '');
|
|
857
|
+
} else {
|
|
858
|
+
this.removeAttribute(MuxVideoAttributes.PREFER_MSE);
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
/**
|
|
863
|
+
* Get the metadata object for Mux Data.
|
|
864
|
+
*/
|
|
865
|
+
get metadata(): Readonly<Metadata> | undefined {
|
|
866
|
+
return this.media?.metadata;
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
/**
|
|
870
|
+
* Set the metadata object for Mux Data.
|
|
871
|
+
*/
|
|
872
|
+
set metadata(val: Readonly<Metadata> | undefined) {
|
|
873
|
+
if (this.media) this.media.metadata = val;
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
/**
|
|
877
|
+
* Get the signing tokens for the Mux asset URL's.
|
|
878
|
+
*/
|
|
879
|
+
get tokens(): Tokens {
|
|
880
|
+
const playback = this.getAttribute(PlayerAttributes.PLAYBACK_TOKEN);
|
|
881
|
+
const thumbnail = this.getAttribute(PlayerAttributes.THUMBNAIL_TOKEN);
|
|
882
|
+
const storyboard = this.getAttribute(PlayerAttributes.STORYBOARD_TOKEN);
|
|
883
|
+
return {
|
|
884
|
+
...this.#tokens,
|
|
885
|
+
...(playback != null ? { playback } : {}),
|
|
886
|
+
...(thumbnail != null ? { thumbnail } : {}),
|
|
887
|
+
...(storyboard != null ? { storyboard } : {}),
|
|
888
|
+
};
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
/**
|
|
892
|
+
* Set the signing tokens for the Mux asset URL's.
|
|
893
|
+
*/
|
|
894
|
+
set tokens(val: Tokens | undefined) {
|
|
895
|
+
this.#tokens = val ?? {};
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
/**
|
|
899
|
+
* Get the playback token for signing the src URL.
|
|
900
|
+
*/
|
|
901
|
+
get playbackToken() {
|
|
902
|
+
return this.getAttribute(PlayerAttributes.PLAYBACK_TOKEN) ?? undefined;
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
/**
|
|
906
|
+
* Set the playback token for signing the src URL.
|
|
907
|
+
*/
|
|
908
|
+
set playbackToken(val) {
|
|
909
|
+
this.setAttribute(PlayerAttributes.PLAYBACK_TOKEN, `${val}`);
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
/**
|
|
913
|
+
* Get the thumbnail token for signing the poster URL.
|
|
914
|
+
*/
|
|
915
|
+
get thumbnailToken() {
|
|
916
|
+
return this.getAttribute(PlayerAttributes.THUMBNAIL_TOKEN) ?? undefined;
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
/**
|
|
920
|
+
* Set the thumbnail token for signing the poster URL.
|
|
921
|
+
*/
|
|
922
|
+
set thumbnailToken(val) {
|
|
923
|
+
this.setAttribute(PlayerAttributes.THUMBNAIL_TOKEN, `${val}`);
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
/**
|
|
927
|
+
* Get the storyboard token for signing the storyboard URL.
|
|
928
|
+
*/
|
|
929
|
+
get storyboardToken() {
|
|
930
|
+
return this.getAttribute(PlayerAttributes.STORYBOARD_TOKEN) ?? undefined;
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
/**
|
|
934
|
+
* Set the storyboard token for signing the storyboard URL.
|
|
935
|
+
*/
|
|
936
|
+
set storyboardToken(val) {
|
|
937
|
+
this.setAttribute(PlayerAttributes.STORYBOARD_TOKEN, `${val}`);
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
export function getVideoAttribute(el: MuxPlayerElement, name: string) {
|
|
942
|
+
return el.media ? el.media.getAttribute(name) : el.getAttribute(name);
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
/** @TODO Refactor once using `globalThis` polyfills */
|
|
946
|
+
if (!globalThis.customElements.get('mux-player')) {
|
|
947
|
+
globalThis.customElements.define('mux-player', MuxPlayerElement);
|
|
948
|
+
/** @TODO consider externalizing this (breaks standard modularity) */
|
|
949
|
+
(globalThis as any).MuxPlayerElement = MuxPlayerElement;
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
export default MuxPlayerElement;
|