@clockworkdog/cogs-client 0.3.1

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.
@@ -0,0 +1,198 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const valueTypes_1 = require("./types/valueTypes");
7
+ const reconnecting_websocket_1 = __importDefault(require("reconnecting-websocket"));
8
+ const urls_1 = require("./helpers/urls");
9
+ class CogsConnection {
10
+ constructor({ hostname = document.location.hostname, port = urls_1.COGS_SERVER_PORT } = {}, outputPortValues = undefined) {
11
+ var _a;
12
+ this.eventTarget = new EventTarget();
13
+ this.currentConfig = {}; // Received on open connection
14
+ this.currentInputPortValues = {}; // Received on open connection
15
+ this.currentOutputPortValues = {}; // Sent on open connection
16
+ this._showPhase = valueTypes_1.ShowPhase.Setup;
17
+ this._timerState = null;
18
+ /**
19
+ * Cached audio outputs use to look up the device/sink ID when a different device label is requested
20
+ */
21
+ this.audioOutputs = undefined;
22
+ this._selectedAudioOutput = '';
23
+ this.currentOutputPortValues = { ...outputPortValues };
24
+ const { useReconnectingWebsocket, path, pathParams } = websocketParametersFromUrl(document.location.href);
25
+ const socketUrl = `ws://${hostname}:${port}${path}${pathParams ? '?' + pathParams : ''}`;
26
+ this.websocket = useReconnectingWebsocket ? new reconnecting_websocket_1.default(socketUrl) : new WebSocket(socketUrl);
27
+ this.websocket.onopen = () => {
28
+ this.currentConfig = {}; // Received on open connection
29
+ this.currentInputPortValues = {}; // Received on open connection
30
+ this.dispatchEvent('open', undefined);
31
+ this.setOutputPortValues(this.currentOutputPortValues);
32
+ };
33
+ this.websocket.onclose = () => {
34
+ this.dispatchEvent('close', undefined);
35
+ };
36
+ this.websocket.onmessage = ({ data }) => {
37
+ try {
38
+ const parsed = JSON.parse(data);
39
+ try {
40
+ if (parsed.config) {
41
+ this.currentConfig = parsed.config;
42
+ this.dispatchEvent('config', this.currentConfig);
43
+ }
44
+ else if (parsed.updates) {
45
+ this.currentInputPortValues = { ...this.currentInputPortValues, ...parsed.updates };
46
+ this.dispatchEvent('updates', parsed.updates);
47
+ }
48
+ else if (parsed.event && parsed.event.key) {
49
+ this.dispatchEvent('event', parsed.event);
50
+ }
51
+ else if (typeof parsed.message === 'object') {
52
+ switch (parsed.message.type) {
53
+ case 'adjustable_timer_update':
54
+ this._timerState = {
55
+ startedAt: Date.now(),
56
+ durationMillis: parsed.message.durationMillis,
57
+ ticking: parsed.message.ticking,
58
+ };
59
+ break;
60
+ case 'show_phase':
61
+ this._showPhase = parsed.message.phase;
62
+ break;
63
+ }
64
+ this.dispatchEvent('message', parsed.message);
65
+ }
66
+ }
67
+ catch (e) {
68
+ console.warn('Error handling data', data, e);
69
+ }
70
+ }
71
+ catch (e) {
72
+ console.error('Unable to parse incoming data from server', data, e);
73
+ }
74
+ };
75
+ // Send a list of audio outputs to COGS and keep it up to date
76
+ {
77
+ const refreshAudioOutputs = async () => {
78
+ // `navigator.mediaDevices` is undefined on COGS AV <= 4.5 because of secure origin permissions
79
+ if (navigator.mediaDevices) {
80
+ const audioOutputs = (await navigator.mediaDevices.enumerateDevices()).filter(({ kind }) => kind === 'audiooutput');
81
+ this.sendAudioOutputs(audioOutputs);
82
+ this.audioOutputs = audioOutputs;
83
+ }
84
+ };
85
+ this.addEventListener('open', refreshAudioOutputs);
86
+ (_a = navigator.mediaDevices) === null || _a === void 0 ? void 0 : _a.addEventListener('devicechange', refreshAudioOutputs);
87
+ refreshAudioOutputs();
88
+ }
89
+ }
90
+ get config() {
91
+ return { ...this.currentConfig };
92
+ }
93
+ get inputPortValues() {
94
+ return { ...this.currentInputPortValues };
95
+ }
96
+ get outputPortValues() {
97
+ return { ...this.currentOutputPortValues };
98
+ }
99
+ get showPhase() {
100
+ return this._showPhase;
101
+ }
102
+ get timerState() {
103
+ return this._timerState ? { ...this._timerState } : null;
104
+ }
105
+ get selectedAudioOutput() {
106
+ return this._selectedAudioOutput;
107
+ }
108
+ get isConnected() {
109
+ return this.websocket.readyState === WebSocket.OPEN;
110
+ }
111
+ close() {
112
+ this.websocket.close();
113
+ }
114
+ sendEvent(eventName, ...[eventValue]) {
115
+ if (this.isConnected) {
116
+ this.websocket.send(JSON.stringify({
117
+ event: {
118
+ key: eventName,
119
+ value: eventValue,
120
+ },
121
+ }));
122
+ }
123
+ }
124
+ setOutputPortValues(values) {
125
+ this.currentOutputPortValues = { ...this.currentOutputPortValues, ...values };
126
+ if (this.isConnected) {
127
+ this.websocket.send(JSON.stringify({ updates: values }));
128
+ }
129
+ }
130
+ getAudioSinkId(audioOutput) {
131
+ var _a, _b;
132
+ return audioOutput ? (_b = (_a = this.audioOutputs) === null || _a === void 0 ? void 0 : _a.find(({ label }) => label === audioOutput)) === null || _b === void 0 ? void 0 : _b.deviceId : '';
133
+ }
134
+ sendInitialMediaClipStates(allMediaClipStates) {
135
+ if (this.isConnected) {
136
+ this.websocket.send(JSON.stringify({ allMediaClipStates }));
137
+ }
138
+ }
139
+ sendMediaClipState(mediaClipState) {
140
+ if (this.isConnected) {
141
+ this.websocket.send(JSON.stringify({ mediaClipState }));
142
+ }
143
+ }
144
+ sendAudioOutputs(audioOutputs) {
145
+ if (this.isConnected) {
146
+ this.websocket.send(JSON.stringify({ audioOutputs }));
147
+ }
148
+ }
149
+ // Type-safe wrapper around EventTarget
150
+ addEventListener(type, listener, options) {
151
+ this.eventTarget.addEventListener(type, listener, options);
152
+ }
153
+ removeEventListener(type, listener, options) {
154
+ this.eventTarget.removeEventListener(type, listener, options);
155
+ }
156
+ dispatchEvent(type, detail) {
157
+ this.eventTarget.dispatchEvent(new CustomEvent(type, { detail }));
158
+ }
159
+ }
160
+ exports.default = CogsConnection;
161
+ function websocketParametersFromUrl(url) {
162
+ var _a, _b, _c, _d, _e;
163
+ const parsedUrl = new URL(url);
164
+ const pathParams = new URLSearchParams(parsedUrl.searchParams);
165
+ const localClientId = pathParams.get('local_id');
166
+ const isSimulator = pathParams.get('simulator') === 'true';
167
+ const display = (_a = pathParams.get('display')) !== null && _a !== void 0 ? _a : '';
168
+ const pluginId = parsedUrl.pathname.startsWith('/plugin/') ? parsedUrl.pathname.split('/')[2] : undefined;
169
+ if (localClientId) {
170
+ const type = (_b = pathParams.get('t')) !== null && _b !== void 0 ? _b : '';
171
+ pathParams.delete('local_id');
172
+ return {
173
+ path: `/local/${encodeURIComponent(localClientId)}`,
174
+ pathParams: new URLSearchParams({ t: type }),
175
+ useReconnectingWebsocket: true,
176
+ };
177
+ }
178
+ else if (isSimulator) {
179
+ const name = (_c = pathParams.get('name')) !== null && _c !== void 0 ? _c : '';
180
+ pathParams.delete('simulator');
181
+ pathParams.delete('name');
182
+ return { path: `/simulator/${encodeURIComponent(name)}`, pathParams, useReconnectingWebsocket: true };
183
+ }
184
+ else if (display) {
185
+ const displayIdIndex = (_d = pathParams.get('displayIdIndex')) !== null && _d !== void 0 ? _d : '';
186
+ pathParams.delete('display');
187
+ pathParams.delete('displayIdIndex');
188
+ return { path: `/display/${encodeURIComponent(display)}/${encodeURIComponent(displayIdIndex)}` };
189
+ }
190
+ else if (pluginId) {
191
+ return { path: `/plugin/${encodeURIComponent(pluginId)}`, useReconnectingWebsocket: true };
192
+ }
193
+ else {
194
+ const serial = (_e = pathParams.get('serial')) !== null && _e !== void 0 ? _e : '';
195
+ pathParams.delete('serial');
196
+ return { path: `/client/${encodeURIComponent(serial)}`, pathParams };
197
+ }
198
+ }
@@ -0,0 +1,50 @@
1
+ import CogsConnection from './CogsConnection';
2
+ import { VideoState } from './types/VideoState';
3
+ import MediaClipStateMessage from './types/MediaClipStateMessage';
4
+ import { MediaObjectFit } from '.';
5
+ declare type EventTypes = {
6
+ state: VideoState;
7
+ videoClipState: MediaClipStateMessage;
8
+ };
9
+ export default class VideoPlayer {
10
+ private eventTarget;
11
+ private globalVolume;
12
+ private videoClipPlayers;
13
+ private activeClip?;
14
+ private parentElement;
15
+ private sinkId;
16
+ constructor(cogsConnection: CogsConnection, parentElement?: HTMLElement);
17
+ setParentElement(parentElement: HTMLElement): void;
18
+ resetParentElement(): void;
19
+ setGlobalVolume(globalVolume: number): void;
20
+ playVideoClip(path: string, { playId, volume, loop, fit }: {
21
+ playId: string;
22
+ volume: number;
23
+ loop: boolean;
24
+ fit: MediaObjectFit;
25
+ }): void;
26
+ pauseVideoClip(): void;
27
+ stopVideoClip(): void;
28
+ setVideoClipVolume({ volume }: {
29
+ volume: number;
30
+ }): void;
31
+ setVideoClipLoop({ loop }: {
32
+ loop: true | undefined;
33
+ }): void;
34
+ setVideoClipFit({ fit }: {
35
+ fit: MediaObjectFit;
36
+ }): void;
37
+ private handleStoppedClip;
38
+ private updateVideoClipPlayer;
39
+ setAudioSink(sinkId: string): void;
40
+ private updateConfig;
41
+ private notifyStateListeners;
42
+ private notifyClipStateListeners;
43
+ addEventListener<EventName extends keyof EventTypes>(type: EventName, listener: (ev: CustomEvent<EventTypes[EventName]>) => void, options?: boolean | AddEventListenerOptions): void;
44
+ removeEventListener<EventName extends keyof EventTypes>(type: EventName, listener: (ev: CustomEvent<EventTypes[EventName]>) => void, options?: boolean | EventListenerOptions): void;
45
+ private dispatchEvent;
46
+ private createVideoElement;
47
+ private createClipPlayer;
48
+ private unloadClip;
49
+ }
50
+ export {};
@@ -0,0 +1,325 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const urls_1 = require("./helpers/urls");
4
+ const VideoState_1 = require("./types/VideoState");
5
+ const DEFAULT_PARENT_ELEMENT = document.body;
6
+ class VideoPlayer {
7
+ constructor(cogsConnection, parentElement = DEFAULT_PARENT_ELEMENT) {
8
+ this.eventTarget = new EventTarget();
9
+ this.globalVolume = 1;
10
+ this.videoClipPlayers = {};
11
+ this.sinkId = '';
12
+ this.parentElement = parentElement;
13
+ // Send the current status of each clip to COGS
14
+ this.addEventListener('videoClipState', ({ detail }) => {
15
+ cogsConnection.sendMediaClipState(detail);
16
+ });
17
+ // Listen for video control messages
18
+ cogsConnection.addEventListener('message', (event) => {
19
+ const message = event.detail;
20
+ switch (message.type) {
21
+ case 'media_config_update':
22
+ this.setGlobalVolume(message.globalVolume);
23
+ if (message.audioOutput !== undefined) {
24
+ const sinkId = cogsConnection.getAudioSinkId(message.audioOutput);
25
+ this.setAudioSink(sinkId !== null && sinkId !== void 0 ? sinkId : '');
26
+ }
27
+ this.updateConfig(message.files);
28
+ break;
29
+ case 'video_play':
30
+ this.playVideoClip(message.file, {
31
+ playId: message.playId,
32
+ volume: message.volume,
33
+ loop: Boolean(message.loop),
34
+ fit: message.fit,
35
+ });
36
+ break;
37
+ case 'video_pause':
38
+ this.pauseVideoClip();
39
+ break;
40
+ case 'video_stop':
41
+ this.stopVideoClip();
42
+ break;
43
+ case 'video_set_volume':
44
+ this.setVideoClipVolume({ volume: message.volume });
45
+ break;
46
+ case 'video_set_fit':
47
+ this.setVideoClipFit({ fit: message.fit });
48
+ break;
49
+ }
50
+ });
51
+ // On connection, send the current playing state of all clips
52
+ // (Usually empty unless websocket is reconnecting)
53
+ const sendInitialClipStates = () => {
54
+ const files = Object.entries(this.videoClipPlayers).map(([file, player]) => {
55
+ const status = !player.videoElement.paused
56
+ ? 'playing'
57
+ : player.videoElement.currentTime === 0 || player.videoElement.currentTime === player.videoElement.duration
58
+ ? 'paused'
59
+ : 'stopped';
60
+ return [file, status];
61
+ });
62
+ cogsConnection.sendInitialMediaClipStates({ mediaType: 'video', files });
63
+ };
64
+ cogsConnection.addEventListener('open', sendInitialClipStates);
65
+ sendInitialClipStates();
66
+ }
67
+ setParentElement(parentElement) {
68
+ this.parentElement = parentElement;
69
+ Object.values(this.videoClipPlayers).forEach((clipPlayer) => {
70
+ parentElement.appendChild(clipPlayer.videoElement);
71
+ });
72
+ }
73
+ resetParentElement() {
74
+ this.setParentElement(DEFAULT_PARENT_ELEMENT);
75
+ }
76
+ setGlobalVolume(globalVolume) {
77
+ Object.values(this.videoClipPlayers).forEach((clipPlayer) => {
78
+ clipPlayer.videoElement.volume = clipPlayer.volume * globalVolume;
79
+ });
80
+ this.globalVolume = globalVolume;
81
+ this.notifyStateListeners();
82
+ }
83
+ playVideoClip(path, { playId, volume, loop, fit }) {
84
+ if (this.activeClip) {
85
+ if (this.activeClip.path !== path) {
86
+ this.stopVideoClip();
87
+ }
88
+ }
89
+ if (!this.videoClipPlayers[path]) {
90
+ this.videoClipPlayers[path] = this.createClipPlayer(path, { preload: 'none', ephemeral: true, fit });
91
+ }
92
+ this.activeClip = { path, playId };
93
+ this.updateVideoClipPlayer(path, (clipPlayer) => {
94
+ clipPlayer.volume = volume;
95
+ clipPlayer.videoElement.volume = volume * this.globalVolume;
96
+ clipPlayer.videoElement.loop = loop;
97
+ clipPlayer.videoElement.style.objectFit = fit;
98
+ if (clipPlayer.videoElement.currentTime === clipPlayer.videoElement.duration) {
99
+ clipPlayer.videoElement.currentTime = 0;
100
+ }
101
+ clipPlayer.videoElement.play();
102
+ clipPlayer.videoElement.style.display = 'block';
103
+ return clipPlayer;
104
+ });
105
+ }
106
+ pauseVideoClip() {
107
+ if (this.activeClip) {
108
+ const { playId, path } = this.activeClip;
109
+ this.updateVideoClipPlayer(path, (clipPlayer) => {
110
+ var _a;
111
+ (_a = clipPlayer.videoElement) === null || _a === void 0 ? void 0 : _a.pause();
112
+ return clipPlayer;
113
+ });
114
+ this.notifyClipStateListeners(playId, path, 'paused');
115
+ }
116
+ }
117
+ stopVideoClip() {
118
+ if (this.activeClip) {
119
+ this.handleStoppedClip(this.activeClip.path);
120
+ }
121
+ }
122
+ setVideoClipVolume({ volume }) {
123
+ if (!this.activeClip) {
124
+ return;
125
+ }
126
+ if (!(volume >= 0 && volume <= 1)) {
127
+ console.warn('Invalid volume', volume);
128
+ return;
129
+ }
130
+ this.updateVideoClipPlayer(this.activeClip.path, (clipPlayer) => {
131
+ if (clipPlayer.videoElement) {
132
+ clipPlayer.videoElement.volume = volume * this.globalVolume;
133
+ }
134
+ return clipPlayer;
135
+ });
136
+ }
137
+ setVideoClipLoop({ loop }) {
138
+ if (!this.activeClip) {
139
+ return;
140
+ }
141
+ this.updateVideoClipPlayer(this.activeClip.path, (clipPlayer) => {
142
+ if (clipPlayer.videoElement) {
143
+ clipPlayer.videoElement.loop = loop || false;
144
+ }
145
+ return clipPlayer;
146
+ });
147
+ }
148
+ setVideoClipFit({ fit }) {
149
+ if (!this.activeClip) {
150
+ return;
151
+ }
152
+ this.updateVideoClipPlayer(this.activeClip.path, (clipPlayer) => {
153
+ if (clipPlayer.videoElement) {
154
+ clipPlayer.videoElement.style.objectFit = fit;
155
+ }
156
+ return clipPlayer;
157
+ });
158
+ }
159
+ handleStoppedClip(path) {
160
+ var _a;
161
+ if (!this.activeClip || this.activeClip.path !== path) {
162
+ return;
163
+ }
164
+ const playId = this.activeClip.playId;
165
+ // Once an ephemeral clip stops, cleanup and remove the player
166
+ if ((_a = this.videoClipPlayers[this.activeClip.path]) === null || _a === void 0 ? void 0 : _a.config.ephemeral) {
167
+ this.unloadClip(path);
168
+ }
169
+ this.activeClip = undefined;
170
+ this.updateVideoClipPlayer(path, (clipPlayer) => {
171
+ clipPlayer.videoElement.pause();
172
+ clipPlayer.videoElement.currentTime = 0;
173
+ clipPlayer.videoElement.style.display = 'none';
174
+ return clipPlayer;
175
+ });
176
+ this.notifyClipStateListeners(playId, path, 'stopped');
177
+ }
178
+ updateVideoClipPlayer(path, update) {
179
+ if (this.videoClipPlayers[path]) {
180
+ const newPlayer = update(this.videoClipPlayers[path]);
181
+ if (newPlayer) {
182
+ this.videoClipPlayers[path] = newPlayer;
183
+ }
184
+ else {
185
+ delete this.videoClipPlayers[path];
186
+ }
187
+ this.notifyStateListeners();
188
+ }
189
+ }
190
+ setAudioSink(sinkId) {
191
+ for (const clipPlayer of Object.values(this.videoClipPlayers)) {
192
+ setPlayerSinkId(clipPlayer, sinkId);
193
+ }
194
+ this.sinkId = sinkId;
195
+ }
196
+ updateConfig(newPaths) {
197
+ const newVideoPaths = Object.fromEntries(Object.entries(newPaths).filter(([, { type }]) => type === 'video' || !type));
198
+ const previousClipPlayers = this.videoClipPlayers;
199
+ this.videoClipPlayers = (() => {
200
+ const clipPlayers = { ...previousClipPlayers };
201
+ const removedClips = Object.keys(previousClipPlayers).filter((previousPath) => !(previousPath in newVideoPaths));
202
+ removedClips.forEach((path) => {
203
+ var _a, _b;
204
+ if (((_a = this.activeClip) === null || _a === void 0 ? void 0 : _a.path) === path && ((_b = previousClipPlayers[path]) === null || _b === void 0 ? void 0 : _b.config.ephemeral) === false) {
205
+ this.updateVideoClipPlayer(path, (player) => {
206
+ player.config = { ...player.config, ephemeral: true };
207
+ return player;
208
+ });
209
+ }
210
+ else {
211
+ this.unloadClip(path);
212
+ delete clipPlayers[path];
213
+ }
214
+ });
215
+ const addedClips = Object.entries(newVideoPaths).filter(([newFile]) => !previousClipPlayers[newFile]);
216
+ addedClips.forEach(([path, config]) => {
217
+ clipPlayers[path] = this.createClipPlayer(path, { ...config, preload: preloadString(config.preload), ephemeral: false, fit: 'contain' });
218
+ });
219
+ const updatedClips = Object.entries(previousClipPlayers).filter(([previousPath]) => previousPath in newVideoPaths);
220
+ updatedClips.forEach(([path, previousClipPlayer]) => {
221
+ if (previousClipPlayer.config.preload !== newVideoPaths[path].preload) {
222
+ this.updateVideoClipPlayer(path, (player) => {
223
+ player.config = {
224
+ ...player.config,
225
+ preload: preloadString(newVideoPaths[path].preload),
226
+ ephemeral: false,
227
+ };
228
+ player.videoElement.preload = player.config.preload ? 'auto' : 'none';
229
+ return player;
230
+ });
231
+ }
232
+ });
233
+ return clipPlayers;
234
+ })();
235
+ this.notifyStateListeners();
236
+ }
237
+ notifyStateListeners() {
238
+ var _a, _b, _c, _d, _e, _f;
239
+ const VideoState = {
240
+ globalVolume: this.globalVolume,
241
+ isPlaying: this.activeClip ? !((_a = this.videoClipPlayers[this.activeClip.path].videoElement) === null || _a === void 0 ? void 0 : _a.paused) : false,
242
+ clips: { ...this.videoClipPlayers },
243
+ activeClip: this.activeClip
244
+ ? {
245
+ path: this.activeClip.path,
246
+ state: !((_b = this.videoClipPlayers[this.activeClip.path].videoElement) === null || _b === void 0 ? void 0 : _b.paused) ? VideoState_1.ActiveVideoClipState.Playing : VideoState_1.ActiveVideoClipState.Paused,
247
+ loop: (_d = (_c = this.videoClipPlayers[this.activeClip.path].videoElement) === null || _c === void 0 ? void 0 : _c.loop) !== null && _d !== void 0 ? _d : false,
248
+ volume: (_f = (_e = this.videoClipPlayers[this.activeClip.path].videoElement) === null || _e === void 0 ? void 0 : _e.volume) !== null && _f !== void 0 ? _f : 0,
249
+ }
250
+ : undefined,
251
+ };
252
+ this.dispatchEvent('state', VideoState);
253
+ }
254
+ notifyClipStateListeners(playId, file, status) {
255
+ this.dispatchEvent('videoClipState', { playId, mediaType: 'video', file, status });
256
+ }
257
+ // Type-safe wrapper around EventTarget
258
+ addEventListener(type, listener, options) {
259
+ this.eventTarget.addEventListener(type, listener, options);
260
+ }
261
+ removeEventListener(type, listener, options) {
262
+ this.eventTarget.removeEventListener(type, listener, options);
263
+ }
264
+ dispatchEvent(type, detail) {
265
+ this.eventTarget.dispatchEvent(new CustomEvent(type, { detail }));
266
+ }
267
+ createVideoElement(path, config, { volume }) {
268
+ const videoElement = document.createElement('video');
269
+ videoElement.src = urls_1.assetUrl(path);
270
+ videoElement.autoplay = false;
271
+ videoElement.loop = false;
272
+ videoElement.volume = this.globalVolume * volume;
273
+ videoElement.preload = config.preload;
274
+ videoElement.addEventListener('playing', () => {
275
+ var _a;
276
+ if (((_a = this.activeClip) === null || _a === void 0 ? void 0 : _a.path) === path) {
277
+ this.notifyClipStateListeners(this.activeClip.playId, path, 'playing');
278
+ }
279
+ });
280
+ videoElement.addEventListener('ended', () => {
281
+ if (!videoElement.loop) {
282
+ this.handleStoppedClip(path);
283
+ }
284
+ });
285
+ videoElement.style.position = 'absolute';
286
+ videoElement.style.top = '0';
287
+ videoElement.style.left = '0';
288
+ videoElement.style.width = '100%';
289
+ videoElement.style.height = '100%';
290
+ videoElement.style.objectFit = config.fit;
291
+ videoElement.style.display = 'none';
292
+ this.parentElement.appendChild(videoElement);
293
+ return videoElement;
294
+ }
295
+ createClipPlayer(path, config) {
296
+ const volume = 1;
297
+ const player = {
298
+ config,
299
+ videoElement: this.createVideoElement(path, config, { volume }),
300
+ volume,
301
+ };
302
+ setPlayerSinkId(player, this.sinkId);
303
+ return player;
304
+ }
305
+ unloadClip(path) {
306
+ var _a, _b;
307
+ if (((_a = this.activeClip) === null || _a === void 0 ? void 0 : _a.path) === path) {
308
+ const playId = this.activeClip.playId;
309
+ this.activeClip = undefined;
310
+ this.notifyClipStateListeners(playId, path, 'stopped');
311
+ }
312
+ (_b = this.videoClipPlayers[path]) === null || _b === void 0 ? void 0 : _b.videoElement.remove();
313
+ this.updateVideoClipPlayer(path, () => null);
314
+ }
315
+ }
316
+ exports.default = VideoPlayer;
317
+ function preloadString(preload) {
318
+ return typeof preload === 'string' ? preload : preload ? 'metadata' : 'none';
319
+ }
320
+ function setPlayerSinkId(player, sinkId) {
321
+ if (sinkId === undefined) {
322
+ return;
323
+ }
324
+ player.videoElement.setSinkId(sinkId);
325
+ }