@clockworkdog/cogs-client 3.1.3 → 3.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/AudioPlayer.d.ts +49 -0
- package/dist/AudioPlayer.js +484 -0
- package/dist/RtspStreamer.d.ts +27 -0
- package/dist/RtspStreamer.js +101 -0
- package/dist/VideoPlayer.d.ts +49 -0
- package/dist/VideoPlayer.js +394 -0
- package/dist/browser/index.mjs +17 -16
- package/dist/browser/index.umd.js +2 -2
- package/dist/helpers/urls.d.ts +6 -0
- package/dist/helpers/urls.js +19 -0
- package/dist/types/AllMediaClipStatesMessage.d.ts +5 -0
- package/dist/types/AllMediaClipStatesMessage.js +2 -0
- package/dist/types/AudioState.d.ts +39 -0
- package/dist/types/AudioState.js +2 -0
- package/dist/types/MediaClipStateMessage.d.ts +7 -0
- package/dist/types/MediaClipStateMessage.js +2 -0
- package/dist/types/VideoState.d.ts +26 -0
- package/dist/types/VideoState.js +8 -0
- package/dist/utils/NetworkHostPattern.d.ts +2 -1
- package/dist/utils/NetworkHostPattern.js +3 -2
- package/dist/utils/getPluginManifestErrors.js +1 -1
- package/dist/utils/setDate.d.ts +4 -0
- package/dist/utils/setDate.js +26 -0
- package/dist/utils/timesync.d.ts +28 -0
- package/dist/utils/timesync.js +67 -0
- package/package.json +2 -2
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { MediaObjectFit } from '.';
|
|
2
|
+
import CogsConnection from './CogsConnection';
|
|
3
|
+
import MediaClipStateMessage from './types/MediaClipStateMessage';
|
|
4
|
+
import { VideoState } from './types/VideoState';
|
|
5
|
+
type EventTypes = {
|
|
6
|
+
state: VideoState;
|
|
7
|
+
videoClipState: MediaClipStateMessage;
|
|
8
|
+
};
|
|
9
|
+
export default class VideoPlayer {
|
|
10
|
+
private cogsConnection;
|
|
11
|
+
private eventTarget;
|
|
12
|
+
private globalVolume;
|
|
13
|
+
private videoClipPlayers;
|
|
14
|
+
private activeClip?;
|
|
15
|
+
private pendingClip?;
|
|
16
|
+
private parentElement;
|
|
17
|
+
private sinkId;
|
|
18
|
+
constructor(cogsConnection: CogsConnection<any>, parentElement?: HTMLElement);
|
|
19
|
+
setParentElement(parentElement: HTMLElement): void;
|
|
20
|
+
resetParentElement(): void;
|
|
21
|
+
setGlobalVolume(globalVolume: number): void;
|
|
22
|
+
playVideoClip(path: string, { playId, volume, loop, fit }: {
|
|
23
|
+
playId: string;
|
|
24
|
+
volume: number;
|
|
25
|
+
loop: boolean;
|
|
26
|
+
fit: MediaObjectFit;
|
|
27
|
+
}): void;
|
|
28
|
+
pauseVideoClip(): void;
|
|
29
|
+
stopVideoClip(): void;
|
|
30
|
+
setVideoClipVolume({ volume }: {
|
|
31
|
+
volume: number;
|
|
32
|
+
}): void;
|
|
33
|
+
setVideoClipFit({ fit }: {
|
|
34
|
+
fit: MediaObjectFit;
|
|
35
|
+
}): void;
|
|
36
|
+
private handleStoppedClip;
|
|
37
|
+
private updateVideoClipPlayer;
|
|
38
|
+
setAudioSink(sinkId: string): void;
|
|
39
|
+
private updateConfig;
|
|
40
|
+
private notifyStateListeners;
|
|
41
|
+
private notifyClipStateListeners;
|
|
42
|
+
addEventListener<EventName extends keyof EventTypes>(type: EventName, listener: (ev: CustomEvent<EventTypes[EventName]>) => void, options?: boolean | AddEventListenerOptions): void;
|
|
43
|
+
removeEventListener<EventName extends keyof EventTypes>(type: EventName, listener: (ev: CustomEvent<EventTypes[EventName]>) => void, options?: boolean | EventListenerOptions): void;
|
|
44
|
+
private dispatchEvent;
|
|
45
|
+
private createVideoElement;
|
|
46
|
+
private createClipPlayer;
|
|
47
|
+
private unloadClip;
|
|
48
|
+
}
|
|
49
|
+
export {};
|
|
@@ -0,0 +1,394 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const VideoState_1 = require("./types/VideoState");
|
|
4
|
+
const DEFAULT_PARENT_ELEMENT = document.body;
|
|
5
|
+
class VideoPlayer {
|
|
6
|
+
constructor(
|
|
7
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
8
|
+
cogsConnection, parentElement = DEFAULT_PARENT_ELEMENT) {
|
|
9
|
+
this.cogsConnection = cogsConnection;
|
|
10
|
+
this.eventTarget = new EventTarget();
|
|
11
|
+
this.globalVolume = 1;
|
|
12
|
+
this.videoClipPlayers = {};
|
|
13
|
+
this.sinkId = '';
|
|
14
|
+
this.parentElement = parentElement;
|
|
15
|
+
// Send the current status of each clip to COGS
|
|
16
|
+
this.addEventListener('videoClipState', ({ detail }) => {
|
|
17
|
+
cogsConnection.sendMediaClipState(detail);
|
|
18
|
+
});
|
|
19
|
+
// Listen for video control messages
|
|
20
|
+
cogsConnection.addEventListener('message', ({ message }) => {
|
|
21
|
+
switch (message.type) {
|
|
22
|
+
case 'media_config_update':
|
|
23
|
+
this.setGlobalVolume(message.globalVolume);
|
|
24
|
+
if (message.audioOutput !== undefined) {
|
|
25
|
+
const sinkId = cogsConnection.getAudioSinkId(message.audioOutput);
|
|
26
|
+
this.setAudioSink(sinkId !== null && sinkId !== void 0 ? sinkId : '');
|
|
27
|
+
}
|
|
28
|
+
this.updateConfig(message.files);
|
|
29
|
+
break;
|
|
30
|
+
case 'video_play':
|
|
31
|
+
this.playVideoClip(message.file, {
|
|
32
|
+
playId: message.playId,
|
|
33
|
+
volume: message.volume,
|
|
34
|
+
loop: Boolean(message.loop),
|
|
35
|
+
fit: message.fit,
|
|
36
|
+
});
|
|
37
|
+
break;
|
|
38
|
+
case 'video_pause':
|
|
39
|
+
this.pauseVideoClip();
|
|
40
|
+
break;
|
|
41
|
+
case 'video_stop':
|
|
42
|
+
this.stopVideoClip();
|
|
43
|
+
break;
|
|
44
|
+
case 'video_set_volume':
|
|
45
|
+
this.setVideoClipVolume({ volume: message.volume });
|
|
46
|
+
break;
|
|
47
|
+
case 'video_set_fit':
|
|
48
|
+
this.setVideoClipFit({ fit: message.fit });
|
|
49
|
+
break;
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
// On connection, send the current playing state of all clips
|
|
53
|
+
// (Usually empty unless websocket is reconnecting)
|
|
54
|
+
const sendInitialClipStates = () => {
|
|
55
|
+
const files = Object.entries(this.videoClipPlayers).map(([file, player]) => {
|
|
56
|
+
const status = !player.videoElement.paused
|
|
57
|
+
? 'playing'
|
|
58
|
+
: player.videoElement.currentTime === 0 || player.videoElement.currentTime === player.videoElement.duration
|
|
59
|
+
? 'paused'
|
|
60
|
+
: 'stopped';
|
|
61
|
+
return [file, status];
|
|
62
|
+
});
|
|
63
|
+
cogsConnection.sendInitialMediaClipStates({ mediaType: 'video', files });
|
|
64
|
+
};
|
|
65
|
+
cogsConnection.addEventListener('open', sendInitialClipStates);
|
|
66
|
+
sendInitialClipStates();
|
|
67
|
+
}
|
|
68
|
+
setParentElement(parentElement) {
|
|
69
|
+
this.parentElement = parentElement;
|
|
70
|
+
Object.values(this.videoClipPlayers).forEach((clipPlayer) => {
|
|
71
|
+
parentElement.appendChild(clipPlayer.videoElement);
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
resetParentElement() {
|
|
75
|
+
this.setParentElement(DEFAULT_PARENT_ELEMENT);
|
|
76
|
+
}
|
|
77
|
+
setGlobalVolume(globalVolume) {
|
|
78
|
+
Object.values(this.videoClipPlayers).forEach((clipPlayer) => {
|
|
79
|
+
setVideoElementVolume(clipPlayer.videoElement, clipPlayer.volume * globalVolume);
|
|
80
|
+
});
|
|
81
|
+
this.globalVolume = globalVolume;
|
|
82
|
+
this.notifyStateListeners();
|
|
83
|
+
}
|
|
84
|
+
playVideoClip(path, { playId, volume, loop, fit }) {
|
|
85
|
+
var _a;
|
|
86
|
+
if (!this.videoClipPlayers[path]) {
|
|
87
|
+
this.videoClipPlayers[path] = this.createClipPlayer(path, { preload: 'none', ephemeral: true, fit });
|
|
88
|
+
}
|
|
89
|
+
// Check if there's already a pending clip, which has now been superseded and abort the play operation
|
|
90
|
+
if (this.pendingClip) {
|
|
91
|
+
this.updateVideoClipPlayer(this.pendingClip.path, (clipPlayer) => {
|
|
92
|
+
clipPlayer.videoElement.load(); // Resets the media element
|
|
93
|
+
return clipPlayer;
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
// New pending clip is video being requested
|
|
97
|
+
if (((_a = this.activeClip) === null || _a === void 0 ? void 0 : _a.path) !== path) {
|
|
98
|
+
this.pendingClip = { path, playId, actionOncePlaying: 'play' };
|
|
99
|
+
}
|
|
100
|
+
// Setup and play the pending clip's player
|
|
101
|
+
this.updateVideoClipPlayer(path, (clipPlayer) => {
|
|
102
|
+
clipPlayer.volume = volume;
|
|
103
|
+
setVideoElementVolume(clipPlayer.videoElement, volume * this.globalVolume);
|
|
104
|
+
clipPlayer.videoElement.loop = loop;
|
|
105
|
+
clipPlayer.videoElement.style.objectFit = fit;
|
|
106
|
+
if (clipPlayer.videoElement.currentTime === clipPlayer.videoElement.duration) {
|
|
107
|
+
clipPlayer.videoElement.currentTime = 0;
|
|
108
|
+
}
|
|
109
|
+
clipPlayer.videoElement.play();
|
|
110
|
+
// Display right away if there's currently no active clip
|
|
111
|
+
if (!this.activeClip) {
|
|
112
|
+
clipPlayer.videoElement.style.display = 'block';
|
|
113
|
+
}
|
|
114
|
+
return clipPlayer;
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
pauseVideoClip() {
|
|
118
|
+
// Pending clip should be paused when it loads and becomes active
|
|
119
|
+
if (this.pendingClip) {
|
|
120
|
+
this.pendingClip.actionOncePlaying = 'pause';
|
|
121
|
+
}
|
|
122
|
+
// Pause the currently active clip
|
|
123
|
+
if (this.activeClip) {
|
|
124
|
+
const { playId, path } = this.activeClip;
|
|
125
|
+
this.updateVideoClipPlayer(path, (clipPlayer) => {
|
|
126
|
+
var _a;
|
|
127
|
+
(_a = clipPlayer.videoElement) === null || _a === void 0 ? void 0 : _a.pause();
|
|
128
|
+
return clipPlayer;
|
|
129
|
+
});
|
|
130
|
+
this.notifyClipStateListeners(playId, path, 'paused');
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
stopVideoClip() {
|
|
134
|
+
// Pending clip should be stopped when it loads and becomes active
|
|
135
|
+
if (this.pendingClip) {
|
|
136
|
+
this.pendingClip.actionOncePlaying = 'stop';
|
|
137
|
+
}
|
|
138
|
+
// Stop the currently active clip
|
|
139
|
+
if (this.activeClip) {
|
|
140
|
+
this.handleStoppedClip(this.activeClip.path);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
setVideoClipVolume({ volume }) {
|
|
144
|
+
var _a, _b;
|
|
145
|
+
// If there is a pending clip, this is latest to have been played so update its volume
|
|
146
|
+
const clipToUpdate = (_b = (_a = this.pendingClip) !== null && _a !== void 0 ? _a : this.activeClip) !== null && _b !== void 0 ? _b : undefined;
|
|
147
|
+
if (!clipToUpdate) {
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
if (!(volume >= 0 && volume <= 1)) {
|
|
151
|
+
console.warn('Invalid volume', volume);
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
this.updateVideoClipPlayer(clipToUpdate.path, (clipPlayer) => {
|
|
155
|
+
if (clipPlayer.videoElement) {
|
|
156
|
+
clipPlayer.volume = volume;
|
|
157
|
+
setVideoElementVolume(clipPlayer.videoElement, volume * this.globalVolume);
|
|
158
|
+
}
|
|
159
|
+
return clipPlayer;
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
setVideoClipFit({ fit }) {
|
|
163
|
+
var _a, _b;
|
|
164
|
+
// If there is a pending clip, this is latest to have been played so update its fit
|
|
165
|
+
const clipToUpdate = (_b = (_a = this.pendingClip) !== null && _a !== void 0 ? _a : this.activeClip) !== null && _b !== void 0 ? _b : undefined;
|
|
166
|
+
if (!clipToUpdate) {
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
this.updateVideoClipPlayer(clipToUpdate.path, (clipPlayer) => {
|
|
170
|
+
if (clipPlayer.videoElement) {
|
|
171
|
+
clipPlayer.videoElement.style.objectFit = fit;
|
|
172
|
+
}
|
|
173
|
+
return clipPlayer;
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
handleStoppedClip(path) {
|
|
177
|
+
var _a;
|
|
178
|
+
if (!this.activeClip || this.activeClip.path !== path) {
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
const playId = this.activeClip.playId;
|
|
182
|
+
// Once an ephemeral clip stops, cleanup and remove the player
|
|
183
|
+
if ((_a = this.videoClipPlayers[this.activeClip.path]) === null || _a === void 0 ? void 0 : _a.config.ephemeral) {
|
|
184
|
+
this.unloadClip(path);
|
|
185
|
+
}
|
|
186
|
+
this.activeClip = undefined;
|
|
187
|
+
this.updateVideoClipPlayer(path, (clipPlayer) => {
|
|
188
|
+
clipPlayer.videoElement.pause();
|
|
189
|
+
clipPlayer.videoElement.currentTime = 0;
|
|
190
|
+
clipPlayer.videoElement.style.display = 'none';
|
|
191
|
+
return clipPlayer;
|
|
192
|
+
});
|
|
193
|
+
this.notifyClipStateListeners(playId, path, 'stopped');
|
|
194
|
+
}
|
|
195
|
+
updateVideoClipPlayer(path, update) {
|
|
196
|
+
if (this.videoClipPlayers[path]) {
|
|
197
|
+
const newPlayer = update(this.videoClipPlayers[path]);
|
|
198
|
+
if (newPlayer) {
|
|
199
|
+
this.videoClipPlayers[path] = newPlayer;
|
|
200
|
+
}
|
|
201
|
+
else {
|
|
202
|
+
delete this.videoClipPlayers[path];
|
|
203
|
+
}
|
|
204
|
+
this.notifyStateListeners();
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
setAudioSink(sinkId) {
|
|
208
|
+
for (const clipPlayer of Object.values(this.videoClipPlayers)) {
|
|
209
|
+
setPlayerSinkId(clipPlayer, sinkId);
|
|
210
|
+
}
|
|
211
|
+
this.sinkId = sinkId;
|
|
212
|
+
}
|
|
213
|
+
updateConfig(newPaths) {
|
|
214
|
+
const newVideoPaths = Object.fromEntries(Object.entries(newPaths).filter(([, { type }]) => type === 'video' || !type));
|
|
215
|
+
const previousClipPlayers = this.videoClipPlayers;
|
|
216
|
+
this.videoClipPlayers = (() => {
|
|
217
|
+
const clipPlayers = { ...previousClipPlayers };
|
|
218
|
+
const removedClips = Object.keys(previousClipPlayers).filter((previousPath) => !(previousPath in newVideoPaths));
|
|
219
|
+
removedClips.forEach((path) => {
|
|
220
|
+
var _a, _b;
|
|
221
|
+
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) {
|
|
222
|
+
this.updateVideoClipPlayer(path, (player) => {
|
|
223
|
+
player.config = { ...player.config, ephemeral: true };
|
|
224
|
+
return player;
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
else {
|
|
228
|
+
this.unloadClip(path);
|
|
229
|
+
delete clipPlayers[path];
|
|
230
|
+
}
|
|
231
|
+
});
|
|
232
|
+
const addedClips = Object.entries(newVideoPaths).filter(([newFile]) => !previousClipPlayers[newFile]);
|
|
233
|
+
addedClips.forEach(([path, config]) => {
|
|
234
|
+
clipPlayers[path] = this.createClipPlayer(path, { ...config, preload: preloadString(config.preload), ephemeral: false, fit: 'contain' });
|
|
235
|
+
});
|
|
236
|
+
const updatedClips = Object.entries(previousClipPlayers).filter(([previousPath]) => previousPath in newVideoPaths);
|
|
237
|
+
updatedClips.forEach(([path, previousClipPlayer]) => {
|
|
238
|
+
if (previousClipPlayer.config.preload !== newVideoPaths[path].preload) {
|
|
239
|
+
this.updateVideoClipPlayer(path, (player) => {
|
|
240
|
+
player.config = {
|
|
241
|
+
...player.config,
|
|
242
|
+
preload: preloadString(newVideoPaths[path].preload),
|
|
243
|
+
ephemeral: false,
|
|
244
|
+
};
|
|
245
|
+
player.videoElement.preload = player.config.preload;
|
|
246
|
+
return player;
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
});
|
|
250
|
+
return clipPlayers;
|
|
251
|
+
})();
|
|
252
|
+
this.notifyStateListeners();
|
|
253
|
+
}
|
|
254
|
+
notifyStateListeners() {
|
|
255
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
256
|
+
const VideoState = {
|
|
257
|
+
globalVolume: this.globalVolume,
|
|
258
|
+
isPlaying: this.activeClip ? !((_a = this.videoClipPlayers[this.activeClip.path].videoElement) === null || _a === void 0 ? void 0 : _a.paused) : false,
|
|
259
|
+
clips: { ...this.videoClipPlayers },
|
|
260
|
+
activeClip: this.activeClip
|
|
261
|
+
? {
|
|
262
|
+
path: this.activeClip.path,
|
|
263
|
+
state: !((_b = this.videoClipPlayers[this.activeClip.path].videoElement) === null || _b === void 0 ? void 0 : _b.paused) ? VideoState_1.ActiveVideoClipState.Playing : VideoState_1.ActiveVideoClipState.Paused,
|
|
264
|
+
loop: (_d = (_c = this.videoClipPlayers[this.activeClip.path].videoElement) === null || _c === void 0 ? void 0 : _c.loop) !== null && _d !== void 0 ? _d : false,
|
|
265
|
+
volume: ((_e = this.videoClipPlayers[this.activeClip.path].videoElement) === null || _e === void 0 ? void 0 : _e.muted)
|
|
266
|
+
? 0
|
|
267
|
+
: ((_g = (_f = this.videoClipPlayers[this.activeClip.path].videoElement) === null || _f === void 0 ? void 0 : _f.volume) !== null && _g !== void 0 ? _g : 0),
|
|
268
|
+
}
|
|
269
|
+
: undefined,
|
|
270
|
+
};
|
|
271
|
+
this.dispatchEvent('state', VideoState);
|
|
272
|
+
}
|
|
273
|
+
notifyClipStateListeners(playId, file, status) {
|
|
274
|
+
this.dispatchEvent('videoClipState', { playId, mediaType: 'video', file, status });
|
|
275
|
+
}
|
|
276
|
+
// Type-safe wrapper around EventTarget
|
|
277
|
+
addEventListener(type, listener, options) {
|
|
278
|
+
this.eventTarget.addEventListener(type, listener, options);
|
|
279
|
+
}
|
|
280
|
+
removeEventListener(type, listener, options) {
|
|
281
|
+
this.eventTarget.removeEventListener(type, listener, options);
|
|
282
|
+
}
|
|
283
|
+
dispatchEvent(type, detail) {
|
|
284
|
+
this.eventTarget.dispatchEvent(new CustomEvent(type, { detail }));
|
|
285
|
+
}
|
|
286
|
+
createVideoElement(path, config, { volume }) {
|
|
287
|
+
const videoElement = document.createElement('video');
|
|
288
|
+
videoElement.playsInline = true; // Required for iOS
|
|
289
|
+
videoElement.src = this.cogsConnection.getAssetUrl(path);
|
|
290
|
+
videoElement.autoplay = false;
|
|
291
|
+
videoElement.loop = false;
|
|
292
|
+
setVideoElementVolume(videoElement, volume * this.globalVolume);
|
|
293
|
+
videoElement.preload = config.preload;
|
|
294
|
+
videoElement.addEventListener('playing', () => {
|
|
295
|
+
var _a, _b;
|
|
296
|
+
// If the clip is still the pending one when it actually start playing, then ensure it is in the correct state
|
|
297
|
+
if (((_a = this.pendingClip) === null || _a === void 0 ? void 0 : _a.path) === path) {
|
|
298
|
+
switch (this.pendingClip.actionOncePlaying) {
|
|
299
|
+
case 'play': {
|
|
300
|
+
// Continue playing, show the video element, and notify listeners
|
|
301
|
+
videoElement.style.display = 'block';
|
|
302
|
+
this.notifyClipStateListeners(this.pendingClip.playId, path, 'playing');
|
|
303
|
+
break;
|
|
304
|
+
}
|
|
305
|
+
case 'pause': {
|
|
306
|
+
// Pause playback, show the video element, and notify listeners
|
|
307
|
+
videoElement.style.display = 'block';
|
|
308
|
+
videoElement.pause();
|
|
309
|
+
this.notifyClipStateListeners(this.pendingClip.playId, path, 'paused');
|
|
310
|
+
break;
|
|
311
|
+
}
|
|
312
|
+
case 'stop': {
|
|
313
|
+
// Pause playback, leave the video element hidden, and notify listeners
|
|
314
|
+
videoElement.pause();
|
|
315
|
+
this.notifyClipStateListeners(this.pendingClip.playId, path, 'stopped');
|
|
316
|
+
break;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
// If there was a previously active clip, then stop it
|
|
320
|
+
if (this.activeClip) {
|
|
321
|
+
this.handleStoppedClip(this.activeClip.path);
|
|
322
|
+
}
|
|
323
|
+
this.activeClip = this.pendingClip;
|
|
324
|
+
this.pendingClip = undefined;
|
|
325
|
+
}
|
|
326
|
+
else if (((_b = this.activeClip) === null || _b === void 0 ? void 0 : _b.path) === path) {
|
|
327
|
+
// If we were the active clip then just notify listeners that we are now playing
|
|
328
|
+
this.notifyClipStateListeners(this.activeClip.playId, path, 'playing');
|
|
329
|
+
}
|
|
330
|
+
else {
|
|
331
|
+
// Otherwise it shouldn't be playing, like because another clip became pending before we loaded,
|
|
332
|
+
// so we pause and don't show or notify listeners
|
|
333
|
+
videoElement.pause();
|
|
334
|
+
}
|
|
335
|
+
});
|
|
336
|
+
videoElement.addEventListener('ended', () => {
|
|
337
|
+
// Ignore if there's a pending clip, as once that starts playing the active clip will be stopped
|
|
338
|
+
// Also ignore if the video is set to loop
|
|
339
|
+
if (!this.pendingClip && !videoElement.loop) {
|
|
340
|
+
this.handleStoppedClip(path);
|
|
341
|
+
}
|
|
342
|
+
});
|
|
343
|
+
videoElement.style.position = 'absolute';
|
|
344
|
+
videoElement.style.top = '0';
|
|
345
|
+
videoElement.style.left = '0';
|
|
346
|
+
videoElement.style.width = '100%';
|
|
347
|
+
videoElement.style.height = '100%';
|
|
348
|
+
videoElement.style.objectFit = config.fit;
|
|
349
|
+
videoElement.style.display = 'none';
|
|
350
|
+
this.parentElement.appendChild(videoElement);
|
|
351
|
+
return videoElement;
|
|
352
|
+
}
|
|
353
|
+
createClipPlayer(path, config) {
|
|
354
|
+
const volume = 1;
|
|
355
|
+
const player = {
|
|
356
|
+
config,
|
|
357
|
+
videoElement: this.createVideoElement(path, config, { volume }),
|
|
358
|
+
volume,
|
|
359
|
+
};
|
|
360
|
+
setPlayerSinkId(player, this.sinkId);
|
|
361
|
+
return player;
|
|
362
|
+
}
|
|
363
|
+
unloadClip(path) {
|
|
364
|
+
var _a, _b;
|
|
365
|
+
if (((_a = this.activeClip) === null || _a === void 0 ? void 0 : _a.path) === path) {
|
|
366
|
+
const playId = this.activeClip.playId;
|
|
367
|
+
this.activeClip = undefined;
|
|
368
|
+
this.notifyClipStateListeners(playId, path, 'stopped');
|
|
369
|
+
}
|
|
370
|
+
(_b = this.videoClipPlayers[path]) === null || _b === void 0 ? void 0 : _b.videoElement.remove();
|
|
371
|
+
this.updateVideoClipPlayer(path, () => null);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
exports.default = VideoPlayer;
|
|
375
|
+
function preloadString(preload) {
|
|
376
|
+
return typeof preload === 'string' ? preload : preload ? 'auto' : 'none';
|
|
377
|
+
}
|
|
378
|
+
function setPlayerSinkId(player, sinkId) {
|
|
379
|
+
if (sinkId === undefined) {
|
|
380
|
+
return;
|
|
381
|
+
}
|
|
382
|
+
if (typeof player.videoElement.setSinkId === 'function') {
|
|
383
|
+
player.videoElement.setSinkId(sinkId);
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
/**
|
|
387
|
+
* Set video volume
|
|
388
|
+
*
|
|
389
|
+
* This doesn't work on iOS (volume is read-only) so at least mute it if the volume is zero
|
|
390
|
+
*/
|
|
391
|
+
function setVideoElementVolume(videoElement, volume) {
|
|
392
|
+
videoElement.volume = volume;
|
|
393
|
+
videoElement.muted = volume === 0;
|
|
394
|
+
}
|
package/dist/browser/index.mjs
CHANGED
|
@@ -2921,7 +2921,7 @@ const Bu = (e, t) => {
|
|
|
2921
2921
|
...t.checks ?? [],
|
|
2922
2922
|
...r.map((n) => typeof n == "function" ? { _zod: { check: n, def: { check: "custom" }, onattach: [] } } : n)
|
|
2923
2923
|
]
|
|
2924
|
-
})), e.clone = (r, n) => $e(e, r, n), e.brand = () => e, e.register = (r, n) => (r.add(e, n), e), e.parse = (r, n) => Fu(e, r, n, { callee: e.parse }), e.safeParse = (r, n) => Gu(e, r, n), e.parseAsync = async (r, n) => qu(e, r, n, { callee: e.parseAsync }), e.safeParseAsync = async (r, n) => Vu(e, r, n), e.spa = e.safeParseAsync, e.encode = (r, n) => Wu(e, r, n), e.decode = (r, n) => Hu(e, r, n), e.encodeAsync = async (r, n) => Xu(e, r, n), e.decodeAsync = async (r, n) => Ku(e, r, n), e.safeEncode = (r, n) => Yu(e, r, n), e.safeDecode = (r, n) => Ju(e, r, n), e.safeEncodeAsync = async (r, n) => Qu(e, r, n), e.safeDecodeAsync = async (r, n) => ec(e, r, n), e.refine = (r, n) => e.check(Wc(r, n)), e.superRefine = (r) => e.check(Hc(r)), e.overwrite = (r) => e.check(Ze(r)), e.optional = () => Yr(e), e.nullable = () => Jr(e), e.nullish = () => Yr(Jr(e)), e.nonoptional = (r) => jc(e, r), e.array = () =>
|
|
2924
|
+
})), e.clone = (r, n) => $e(e, r, n), e.brand = () => e, e.register = (r, n) => (r.add(e, n), e), e.parse = (r, n) => Fu(e, r, n, { callee: e.parse }), e.safeParse = (r, n) => Gu(e, r, n), e.parseAsync = async (r, n) => qu(e, r, n, { callee: e.parseAsync }), e.safeParseAsync = async (r, n) => Vu(e, r, n), e.spa = e.safeParseAsync, e.encode = (r, n) => Wu(e, r, n), e.decode = (r, n) => Hu(e, r, n), e.encodeAsync = async (r, n) => Xu(e, r, n), e.decodeAsync = async (r, n) => Ku(e, r, n), e.safeEncode = (r, n) => Yu(e, r, n), e.safeDecode = (r, n) => Ju(e, r, n), e.safeEncodeAsync = async (r, n) => Qu(e, r, n), e.safeDecodeAsync = async (r, n) => ec(e, r, n), e.refine = (r, n) => e.check(Wc(r, n)), e.superRefine = (r) => e.check(Hc(r)), e.overwrite = (r) => e.check(Ze(r)), e.optional = () => Yr(e), e.nullable = () => Jr(e), e.nullish = () => Yr(Jr(e)), e.nonoptional = (r) => jc(e, r), e.array = () => ve(e), e.or = (r) => de([e, r]), e.and = (r) => Ac(e, r), e.transform = (r) => Qr(e, Cc(r)), e.default = (r) => Zc(e, r), e.prefault = (r) => xc(e, r), e.catch = (r) => Uc(e, r), e.pipe = (r) => Qr(e, r), e.readonly = () => qc(e), e.describe = (r) => {
|
|
2925
2925
|
const n = e.clone();
|
|
2926
2926
|
return Ve.add(n, { description: r }), n;
|
|
2927
2927
|
}, Object.defineProperty(e, "description", {
|
|
@@ -3028,7 +3028,7 @@ function zs(e) {
|
|
|
3028
3028
|
const Ic = /* @__PURE__ */ g("ZodArray", (e, t) => {
|
|
3029
3029
|
va.init(e, t), H.init(e, t), e.element = t.element, e.min = (r, n) => e.check(Qe(r, n)), e.nonempty = (r) => e.check(Qe(1, r)), e.max = (r, n) => e.check(ks(r, n)), e.length = (r, n) => e.check(Ps(r, n)), e.unwrap = () => e.element;
|
|
3030
3030
|
});
|
|
3031
|
-
function
|
|
3031
|
+
function ve(e, t) {
|
|
3032
3032
|
return ku(Ic, e, t);
|
|
3033
3033
|
}
|
|
3034
3034
|
const Zs = /* @__PURE__ */ g("ZodObject", (e, t) => {
|
|
@@ -4339,10 +4339,10 @@ function Hs() {
|
|
|
4339
4339
|
"64:ff9b:1::/48": "NAT64 (local-use)"
|
|
4340
4340
|
}, K.RE_BAD_CHARACTERS = /([^0-9a-f:/%])/gi, K.RE_BAD_ADDRESS = /([0-9a-f]{5,}|:{3,}|[^:]:$|^:[^:]|\/$)/gi, K.RE_SUBNET_STRING = /\/\d{1,3}(?=%|$)/, K.RE_ZONE_STRING = /%.*$/, K.RE_URL = /^\[{0,1}([0-9a-f:]+)\]{0,1}/, K.RE_URL_WITH_PORT = /\[([0-9a-f:]+)\]:([0-9]{1,5})/), K;
|
|
4341
4341
|
}
|
|
4342
|
-
var
|
|
4342
|
+
var Ee = {}, hn;
|
|
4343
4343
|
function Xs() {
|
|
4344
|
-
if (hn) return
|
|
4345
|
-
hn = 1, Object.defineProperty(
|
|
4344
|
+
if (hn) return Ee;
|
|
4345
|
+
hn = 1, Object.defineProperty(Ee, "__esModule", { value: !0 }), Ee.escapeHtml = e, Ee.spanAllZeroes = t, Ee.spanAll = r, Ee.spanLeadingZeroes = s, Ee.simpleGroup = i;
|
|
4346
4346
|
function e(o) {
|
|
4347
4347
|
return o.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
4348
4348
|
}
|
|
@@ -4361,7 +4361,7 @@ function Xs() {
|
|
|
4361
4361
|
function i(o, c = 0) {
|
|
4362
4362
|
return o.split(":").map((l, a) => /group-v4/.test(l) ? l : `<span class="hover-group group-${a + c}">${n(l)}</span>`);
|
|
4363
4363
|
}
|
|
4364
|
-
return
|
|
4364
|
+
return Ee;
|
|
4365
4365
|
}
|
|
4366
4366
|
var ne = {}, dn;
|
|
4367
4367
|
function wl() {
|
|
@@ -5326,9 +5326,10 @@ class Il {
|
|
|
5326
5326
|
}
|
|
5327
5327
|
/** Checks whether `host` and `port` are matched by this pattern. */
|
|
5328
5328
|
validate(t, r) {
|
|
5329
|
-
return this.
|
|
5329
|
+
return this.validateHostname(t) && (this.port === "*" || Number(this.port) === r);
|
|
5330
5330
|
}
|
|
5331
|
-
|
|
5331
|
+
/** Checks whether `host` is matched by the hostname part of this pattern, ignoring the port. */
|
|
5332
|
+
validateHostname(t) {
|
|
5332
5333
|
const r = this.host.toLowerCase();
|
|
5333
5334
|
return t = t.toLowerCase(), r === "*" ? !0 : r === "localhost" ? t === "localhost" || Rl(t) : r === "private" ? yl(t) : r.startsWith("*.") ? t.endsWith(r.slice(1)) : r === t;
|
|
5334
5335
|
}
|
|
@@ -6409,7 +6410,7 @@ function Jl() {
|
|
|
6409
6410
|
}, sr;
|
|
6410
6411
|
}
|
|
6411
6412
|
var Ql = Jl();
|
|
6412
|
-
const fr = /* @__PURE__ */ bl(Ql), us =
|
|
6413
|
+
const fr = /* @__PURE__ */ bl(Ql), us = ve(ee().min(1)).refine((e) => new Set(e).size === e.length, {
|
|
6413
6414
|
message: "Array items must be unique"
|
|
6414
6415
|
});
|
|
6415
6416
|
function ei(e) {
|
|
@@ -6485,12 +6486,12 @@ function ei(e) {
|
|
|
6485
6486
|
height: te().gt(0).int(),
|
|
6486
6487
|
visible: Pe().optional()
|
|
6487
6488
|
}).optional(),
|
|
6488
|
-
config:
|
|
6489
|
+
config: ve(h).optional(),
|
|
6489
6490
|
events: q({
|
|
6490
|
-
fromCogs:
|
|
6491
|
-
toCogs:
|
|
6491
|
+
fromCogs: ve(p).optional(),
|
|
6492
|
+
toCogs: ve(p).optional()
|
|
6492
6493
|
}).optional(),
|
|
6493
|
-
state:
|
|
6494
|
+
state: ve(I).optional(),
|
|
6494
6495
|
media: q({
|
|
6495
6496
|
audio: G(!0).optional(),
|
|
6496
6497
|
video: G(!0).optional(),
|
|
@@ -6506,10 +6507,10 @@ function ei(e) {
|
|
|
6506
6507
|
}).optional(),
|
|
6507
6508
|
permissions: ur({
|
|
6508
6509
|
network: ur({
|
|
6509
|
-
access:
|
|
6510
|
+
access: ve(
|
|
6510
6511
|
e({
|
|
6511
|
-
hosts:
|
|
6512
|
-
|
|
6512
|
+
hosts: ve(ee().refine(v, { error: "Invalid network host pattern" })).min(1),
|
|
6513
|
+
caCertificates: ve(ee().min(1)).optional()
|
|
6513
6514
|
})
|
|
6514
6515
|
).optional()
|
|
6515
6516
|
}).optional()
|