@clockworkdog/cogs-client 1.5.0 → 1.5.2
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.js +31 -7
- package/dist/RtspStreamer.js +1 -0
- package/dist/VideoPlayer.d.ts +1 -3
- package/dist/VideoPlayer.js +98 -33
- package/dist/browser/index.js +135 -61
- package/dist/helpers/urls.js +4 -20
- package/package.json +1 -1
package/dist/AudioPlayer.js
CHANGED
|
@@ -3,6 +3,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
const howler_1 = require("howler");
|
|
4
4
|
const urls_1 = require("./helpers/urls");
|
|
5
5
|
const DEBUG = true;
|
|
6
|
+
// Check an iOS-only property (See https://developer.mozilla.org/en-US/docs/Web/API/Navigator#non-standard_properties)
|
|
7
|
+
const IS_IOS = typeof navigator.standalone !== 'undefined';
|
|
6
8
|
class AudioPlayer {
|
|
7
9
|
constructor(cogsConnection) {
|
|
8
10
|
this.eventTarget = new EventTarget();
|
|
@@ -148,12 +150,13 @@ class AudioPlayer {
|
|
|
148
150
|
if (isFadeValid(fade)) {
|
|
149
151
|
// Start fade when clip starts
|
|
150
152
|
clipPlayer.player.volume(0, soundId);
|
|
153
|
+
clipPlayer.player.mute(false, soundId);
|
|
151
154
|
clipPlayer.player.once('play', () => {
|
|
152
|
-
clipPlayer.player
|
|
155
|
+
fadeAudioPlayerVolume(clipPlayer.player, volume, fade * 1000, soundId);
|
|
153
156
|
}, soundId);
|
|
154
157
|
}
|
|
155
158
|
else {
|
|
156
|
-
clipPlayer.player
|
|
159
|
+
setAudioPlayerVolume(clipPlayer.player, volume, soundId);
|
|
157
160
|
}
|
|
158
161
|
// Track new/updated active clip
|
|
159
162
|
clipPlayer.activeClips = { ...clipPlayer.activeClips, [soundId]: activeClip };
|
|
@@ -181,7 +184,7 @@ class AudioPlayer {
|
|
|
181
184
|
this.updateActiveAudioClip(path, soundId, (clip) => ({ ...clip, state: { type: 'paused' } }));
|
|
182
185
|
this.notifyClipStateListeners(clip.playId, path, 'paused');
|
|
183
186
|
}, soundId);
|
|
184
|
-
|
|
187
|
+
fadeAudioPlayerVolume(clipPlayer.player, 0, fade * 1000, soundId);
|
|
185
188
|
clip.state = { type: 'pausing' };
|
|
186
189
|
}
|
|
187
190
|
else {
|
|
@@ -221,7 +224,7 @@ class AudioPlayer {
|
|
|
221
224
|
// Cleanup any old fade callbacks first
|
|
222
225
|
// TODO: Remove cast once https://github.com/DefinitelyTyped/DefinitelyTyped/pull/59411 is merged
|
|
223
226
|
clipPlayer.player.off('fade', soundId);
|
|
224
|
-
|
|
227
|
+
fadeAudioPlayerVolume(clipPlayer.player, 0, fade * 1000, soundId);
|
|
225
228
|
// Set callback after starting new fade, otherwise it will fire straight away as the previous fade is cancelled
|
|
226
229
|
clipPlayer.player.once('fade', (soundId) => clipPlayer.player.stop(soundId), soundId);
|
|
227
230
|
clip.state = { type: 'stopping' };
|
|
@@ -264,10 +267,10 @@ class AudioPlayer {
|
|
|
264
267
|
if (clip.state.type !== 'pausing' && clip.state.type !== 'stopping') {
|
|
265
268
|
const soundId = parseInt(soundIdStr);
|
|
266
269
|
if (isFadeValid(fade)) {
|
|
267
|
-
|
|
270
|
+
fadeAudioPlayerVolume(clipPlayer.player, volume, fade * 1000, soundId);
|
|
268
271
|
}
|
|
269
272
|
else {
|
|
270
|
-
clipPlayer.player
|
|
273
|
+
setAudioPlayerVolume(clipPlayer.player, volume, soundId);
|
|
271
274
|
}
|
|
272
275
|
return [soundIdStr, { ...clip, volume }];
|
|
273
276
|
}
|
|
@@ -401,8 +404,11 @@ function log(...data) {
|
|
|
401
404
|
console.log(...data);
|
|
402
405
|
}
|
|
403
406
|
}
|
|
407
|
+
/**
|
|
408
|
+
* @returns `true` if this is this a valid fade duration. Always returns `false` on iOS
|
|
409
|
+
*/
|
|
404
410
|
function isFadeValid(fade) {
|
|
405
|
-
return typeof fade === 'number' && !isNaN(fade) && fade > 0;
|
|
411
|
+
return !IS_IOS && typeof fade === 'number' && !isNaN(fade) && fade > 0;
|
|
406
412
|
}
|
|
407
413
|
function setPlayerSinkId(player, sinkId) {
|
|
408
414
|
var _a;
|
|
@@ -420,3 +426,21 @@ function setPlayerSinkId(player, sinkId) {
|
|
|
420
426
|
console.warn('Cannot set sink ID: web audio not supported', player);
|
|
421
427
|
}
|
|
422
428
|
}
|
|
429
|
+
/**
|
|
430
|
+
* Set audio volume
|
|
431
|
+
*
|
|
432
|
+
* This doesn't work on iOS (volume is read-only) so at least mute it if the volume is zero
|
|
433
|
+
*/
|
|
434
|
+
function setAudioPlayerVolume(howl, volume, soundId) {
|
|
435
|
+
howl.volume(volume, soundId);
|
|
436
|
+
howl.mute(volume === 0, soundId);
|
|
437
|
+
}
|
|
438
|
+
/**
|
|
439
|
+
* Fade to audio volume
|
|
440
|
+
*
|
|
441
|
+
* This doesn't work on iOS (volume is read-only) so at least mute it if the volume is zero
|
|
442
|
+
*/
|
|
443
|
+
function fadeAudioPlayerVolume(howl, volume, fade, soundId) {
|
|
444
|
+
howl.mute(false, soundId);
|
|
445
|
+
howl.fade(howl.volume(soundId), volume, fade, soundId);
|
|
446
|
+
}
|
package/dist/RtspStreamer.js
CHANGED
|
@@ -22,6 +22,7 @@ class RtspStreamer {
|
|
|
22
22
|
play(params) {
|
|
23
23
|
var _a;
|
|
24
24
|
const { uri, videoElement } = params;
|
|
25
|
+
videoElement.playsInline = true; // Required for iOS
|
|
25
26
|
let pipeline;
|
|
26
27
|
const startPipeline = () => {
|
|
27
28
|
pipeline === null || pipeline === void 0 ? void 0 : pipeline.close();
|
package/dist/VideoPlayer.d.ts
CHANGED
|
@@ -11,6 +11,7 @@ export default class VideoPlayer {
|
|
|
11
11
|
private globalVolume;
|
|
12
12
|
private videoClipPlayers;
|
|
13
13
|
private activeClip?;
|
|
14
|
+
private pendingClip?;
|
|
14
15
|
private parentElement;
|
|
15
16
|
private sinkId;
|
|
16
17
|
constructor(cogsConnection: CogsConnection, parentElement?: HTMLElement);
|
|
@@ -28,9 +29,6 @@ export default class VideoPlayer {
|
|
|
28
29
|
setVideoClipVolume({ volume }: {
|
|
29
30
|
volume: number;
|
|
30
31
|
}): void;
|
|
31
|
-
setVideoClipLoop({ loop }: {
|
|
32
|
-
loop: true | undefined;
|
|
33
|
-
}): void;
|
|
34
32
|
setVideoClipFit({ fit }: {
|
|
35
33
|
fit: MediaObjectFit;
|
|
36
34
|
}): void;
|
package/dist/VideoPlayer.js
CHANGED
|
@@ -75,35 +75,50 @@ class VideoPlayer {
|
|
|
75
75
|
}
|
|
76
76
|
setGlobalVolume(globalVolume) {
|
|
77
77
|
Object.values(this.videoClipPlayers).forEach((clipPlayer) => {
|
|
78
|
-
clipPlayer.videoElement
|
|
78
|
+
setVideoElementVolume(clipPlayer.videoElement, clipPlayer.volume * globalVolume);
|
|
79
79
|
});
|
|
80
80
|
this.globalVolume = globalVolume;
|
|
81
81
|
this.notifyStateListeners();
|
|
82
82
|
}
|
|
83
83
|
playVideoClip(path, { playId, volume, loop, fit }) {
|
|
84
|
-
|
|
85
|
-
if (this.activeClip.path !== path) {
|
|
86
|
-
this.stopVideoClip();
|
|
87
|
-
}
|
|
88
|
-
}
|
|
84
|
+
var _a;
|
|
89
85
|
if (!this.videoClipPlayers[path]) {
|
|
90
86
|
this.videoClipPlayers[path] = this.createClipPlayer(path, { preload: 'none', ephemeral: true, fit });
|
|
91
87
|
}
|
|
92
|
-
|
|
88
|
+
// Check if there's already a pending clip, which has now been superseded and abort the play operation
|
|
89
|
+
if (this.pendingClip) {
|
|
90
|
+
this.updateVideoClipPlayer(this.pendingClip.path, (clipPlayer) => {
|
|
91
|
+
clipPlayer.videoElement.load(); // Resets the media element
|
|
92
|
+
return clipPlayer;
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
// New pending clip is video being requested
|
|
96
|
+
if (((_a = this.activeClip) === null || _a === void 0 ? void 0 : _a.path) !== path) {
|
|
97
|
+
this.pendingClip = { path, playId, actionOncePlaying: 'play' };
|
|
98
|
+
}
|
|
99
|
+
// Setup and play the pending clip's player
|
|
93
100
|
this.updateVideoClipPlayer(path, (clipPlayer) => {
|
|
94
101
|
clipPlayer.volume = volume;
|
|
95
|
-
clipPlayer.videoElement
|
|
102
|
+
setVideoElementVolume(clipPlayer.videoElement, volume * this.globalVolume);
|
|
96
103
|
clipPlayer.videoElement.loop = loop;
|
|
97
104
|
clipPlayer.videoElement.style.objectFit = fit;
|
|
98
105
|
if (clipPlayer.videoElement.currentTime === clipPlayer.videoElement.duration) {
|
|
99
106
|
clipPlayer.videoElement.currentTime = 0;
|
|
100
107
|
}
|
|
101
108
|
clipPlayer.videoElement.play();
|
|
102
|
-
|
|
109
|
+
// Display right away if there's currently no active clip
|
|
110
|
+
if (!this.activeClip) {
|
|
111
|
+
clipPlayer.videoElement.style.display = 'block';
|
|
112
|
+
}
|
|
103
113
|
return clipPlayer;
|
|
104
114
|
});
|
|
105
115
|
}
|
|
106
116
|
pauseVideoClip() {
|
|
117
|
+
// Pending clip should be paused when it loads and becomes active
|
|
118
|
+
if (this.pendingClip) {
|
|
119
|
+
this.pendingClip.actionOncePlaying = 'pause';
|
|
120
|
+
}
|
|
121
|
+
// Pause the currently active clip
|
|
107
122
|
if (this.activeClip) {
|
|
108
123
|
const { playId, path } = this.activeClip;
|
|
109
124
|
this.updateVideoClipPlayer(path, (clipPlayer) => {
|
|
@@ -115,41 +130,42 @@ class VideoPlayer {
|
|
|
115
130
|
}
|
|
116
131
|
}
|
|
117
132
|
stopVideoClip() {
|
|
133
|
+
// Pending clip should be stopped when it loads and becomes active
|
|
134
|
+
if (this.pendingClip) {
|
|
135
|
+
this.pendingClip.actionOncePlaying = 'stop';
|
|
136
|
+
}
|
|
137
|
+
// Stop the currently active clip
|
|
118
138
|
if (this.activeClip) {
|
|
119
139
|
this.handleStoppedClip(this.activeClip.path);
|
|
120
140
|
}
|
|
121
141
|
}
|
|
122
142
|
setVideoClipVolume({ volume }) {
|
|
123
|
-
|
|
143
|
+
var _a, _b;
|
|
144
|
+
// If there is a pending clip, this is latest to have been played so update its volume
|
|
145
|
+
const clipToUpdate = (_b = (_a = this.pendingClip) !== null && _a !== void 0 ? _a : this.activeClip) !== null && _b !== void 0 ? _b : undefined;
|
|
146
|
+
if (!clipToUpdate) {
|
|
124
147
|
return;
|
|
125
148
|
}
|
|
126
149
|
if (!(volume >= 0 && volume <= 1)) {
|
|
127
150
|
console.warn('Invalid volume', volume);
|
|
128
151
|
return;
|
|
129
152
|
}
|
|
130
|
-
this.updateVideoClipPlayer(
|
|
153
|
+
this.updateVideoClipPlayer(clipToUpdate.path, (clipPlayer) => {
|
|
131
154
|
if (clipPlayer.videoElement) {
|
|
132
|
-
clipPlayer.
|
|
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;
|
|
155
|
+
clipPlayer.volume = volume;
|
|
156
|
+
setVideoElementVolume(clipPlayer.videoElement, volume * this.globalVolume);
|
|
144
157
|
}
|
|
145
158
|
return clipPlayer;
|
|
146
159
|
});
|
|
147
160
|
}
|
|
148
161
|
setVideoClipFit({ fit }) {
|
|
149
|
-
|
|
162
|
+
var _a, _b;
|
|
163
|
+
// If there is a pending clip, this is latest to have been played so update its fit
|
|
164
|
+
const clipToUpdate = (_b = (_a = this.pendingClip) !== null && _a !== void 0 ? _a : this.activeClip) !== null && _b !== void 0 ? _b : undefined;
|
|
165
|
+
if (!clipToUpdate) {
|
|
150
166
|
return;
|
|
151
167
|
}
|
|
152
|
-
this.updateVideoClipPlayer(
|
|
168
|
+
this.updateVideoClipPlayer(clipToUpdate.path, (clipPlayer) => {
|
|
153
169
|
if (clipPlayer.videoElement) {
|
|
154
170
|
clipPlayer.videoElement.style.objectFit = fit;
|
|
155
171
|
}
|
|
@@ -225,7 +241,7 @@ class VideoPlayer {
|
|
|
225
241
|
preload: preloadString(newVideoPaths[path].preload),
|
|
226
242
|
ephemeral: false,
|
|
227
243
|
};
|
|
228
|
-
player.videoElement.preload = player.config.preload
|
|
244
|
+
player.videoElement.preload = player.config.preload;
|
|
229
245
|
return player;
|
|
230
246
|
});
|
|
231
247
|
}
|
|
@@ -235,7 +251,7 @@ class VideoPlayer {
|
|
|
235
251
|
this.notifyStateListeners();
|
|
236
252
|
}
|
|
237
253
|
notifyStateListeners() {
|
|
238
|
-
var _a, _b, _c, _d, _e, _f;
|
|
254
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
239
255
|
const VideoState = {
|
|
240
256
|
globalVolume: this.globalVolume,
|
|
241
257
|
isPlaying: this.activeClip ? !((_a = this.videoClipPlayers[this.activeClip.path].videoElement) === null || _a === void 0 ? void 0 : _a.paused) : false,
|
|
@@ -245,7 +261,8 @@ class VideoPlayer {
|
|
|
245
261
|
path: this.activeClip.path,
|
|
246
262
|
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
263
|
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: (
|
|
264
|
+
volume: ((_e = this.videoClipPlayers[this.activeClip.path].videoElement) === null || _e === void 0 ? void 0 : _e.muted) ? 0
|
|
265
|
+
: (_g = (_f = this.videoClipPlayers[this.activeClip.path].videoElement) === null || _f === void 0 ? void 0 : _f.volume) !== null && _g !== void 0 ? _g : 0,
|
|
249
266
|
}
|
|
250
267
|
: undefined,
|
|
251
268
|
};
|
|
@@ -266,19 +283,58 @@ class VideoPlayer {
|
|
|
266
283
|
}
|
|
267
284
|
createVideoElement(path, config, { volume }) {
|
|
268
285
|
const videoElement = document.createElement('video');
|
|
286
|
+
videoElement.playsInline = true; // Required for iOS
|
|
269
287
|
videoElement.src = urls_1.assetUrl(path);
|
|
270
288
|
videoElement.autoplay = false;
|
|
271
289
|
videoElement.loop = false;
|
|
272
|
-
videoElement
|
|
290
|
+
setVideoElementVolume(videoElement, volume * this.globalVolume);
|
|
273
291
|
videoElement.preload = config.preload;
|
|
274
292
|
videoElement.addEventListener('playing', () => {
|
|
275
|
-
var _a;
|
|
276
|
-
|
|
293
|
+
var _a, _b;
|
|
294
|
+
// If the clip is still the pending one when it actually start playing, then ensure it is in the correct state
|
|
295
|
+
if (((_a = this.pendingClip) === null || _a === void 0 ? void 0 : _a.path) === path) {
|
|
296
|
+
switch (this.pendingClip.actionOncePlaying) {
|
|
297
|
+
case 'play': {
|
|
298
|
+
// Continue playing, show the video element, and notify listeners
|
|
299
|
+
videoElement.style.display = 'block';
|
|
300
|
+
this.notifyClipStateListeners(this.pendingClip.playId, path, 'playing');
|
|
301
|
+
break;
|
|
302
|
+
}
|
|
303
|
+
case 'pause': {
|
|
304
|
+
// Pause playback, show the video element, and notify listeners
|
|
305
|
+
videoElement.style.display = 'block';
|
|
306
|
+
videoElement.pause();
|
|
307
|
+
this.notifyClipStateListeners(this.pendingClip.playId, path, 'paused');
|
|
308
|
+
break;
|
|
309
|
+
}
|
|
310
|
+
case 'stop': {
|
|
311
|
+
// Pause playback, leave the video element hidden, and notify listeners
|
|
312
|
+
videoElement.pause();
|
|
313
|
+
this.notifyClipStateListeners(this.pendingClip.playId, path, 'stopped');
|
|
314
|
+
break;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
// If there was a previously active clip, then stop it
|
|
318
|
+
if (this.activeClip) {
|
|
319
|
+
this.handleStoppedClip(this.activeClip.path);
|
|
320
|
+
}
|
|
321
|
+
this.activeClip = this.pendingClip;
|
|
322
|
+
this.pendingClip = undefined;
|
|
323
|
+
}
|
|
324
|
+
else if (((_b = this.activeClip) === null || _b === void 0 ? void 0 : _b.path) === path) {
|
|
325
|
+
// If we were the active clip then just notify listeners that we are now playing
|
|
277
326
|
this.notifyClipStateListeners(this.activeClip.playId, path, 'playing');
|
|
278
327
|
}
|
|
328
|
+
else {
|
|
329
|
+
// Otherwise it shouldn't be playing, like because another clip became pending before we loaded,
|
|
330
|
+
// so we pause and don't show or notify listeners
|
|
331
|
+
videoElement.pause();
|
|
332
|
+
}
|
|
279
333
|
});
|
|
280
334
|
videoElement.addEventListener('ended', () => {
|
|
281
|
-
if
|
|
335
|
+
// Ignore if there's a pending clip, as once that starts playing the active clip will be stopped
|
|
336
|
+
// Also ignore if the video is set to loop
|
|
337
|
+
if (!this.pendingClip && !videoElement.loop) {
|
|
282
338
|
this.handleStoppedClip(path);
|
|
283
339
|
}
|
|
284
340
|
});
|
|
@@ -315,7 +371,7 @@ class VideoPlayer {
|
|
|
315
371
|
}
|
|
316
372
|
exports.default = VideoPlayer;
|
|
317
373
|
function preloadString(preload) {
|
|
318
|
-
return typeof preload === 'string' ? preload : preload ? '
|
|
374
|
+
return typeof preload === 'string' ? preload : preload ? 'auto' : 'none';
|
|
319
375
|
}
|
|
320
376
|
function setPlayerSinkId(player, sinkId) {
|
|
321
377
|
if (sinkId === undefined) {
|
|
@@ -325,3 +381,12 @@ function setPlayerSinkId(player, sinkId) {
|
|
|
325
381
|
player.videoElement.setSinkId(sinkId);
|
|
326
382
|
}
|
|
327
383
|
}
|
|
384
|
+
/**
|
|
385
|
+
* Set video volume
|
|
386
|
+
*
|
|
387
|
+
* This doesn't work on iOS (volume is read-only) so at least mute it if the volume is zero
|
|
388
|
+
*/
|
|
389
|
+
function setVideoElementVolume(videoElement, volume) {
|
|
390
|
+
videoElement.volume = volume;
|
|
391
|
+
videoElement.muted = volume === 0;
|
|
392
|
+
}
|