@clockworkdog/cogs-client 1.5.1 → 1.5.3
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/README.md +0 -8
- package/dist/AudioPlayer.js +35 -10
- package/dist/VideoPlayer.d.ts +1 -3
- package/dist/VideoPlayer.js +97 -33
- package/dist/browser/index.js +137 -64
- package/dist/helpers/urls.js +4 -20
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -20,14 +20,6 @@ Include the script in your HTML page:
|
|
|
20
20
|
|
|
21
21
|
### NPM / Yarn
|
|
22
22
|
|
|
23
|
-
If you haven't yet created a web project we recommend using Vite with Typescript:
|
|
24
|
-
|
|
25
|
-
```shell
|
|
26
|
-
yarn create vite my-custom-content --template vanilla-ts
|
|
27
|
-
cd my-custom-content
|
|
28
|
-
yarn
|
|
29
|
-
```
|
|
30
|
-
|
|
31
23
|
Then add `cogs-client` with NPM or Yarn:
|
|
32
24
|
|
|
33
25
|
```shell
|
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();
|
|
@@ -131,12 +133,13 @@ class AudioPlayer {
|
|
|
131
133
|
// Once clip starts, check if it should actually be paused or stopped
|
|
132
134
|
// If not, then update state to 'playing'
|
|
133
135
|
clipPlayer.player.once('play', () => {
|
|
134
|
-
|
|
135
|
-
|
|
136
|
+
var _a;
|
|
137
|
+
const clipState = (_a = clipPlayer.activeClips[soundId]) === null || _a === void 0 ? void 0 : _a.state;
|
|
138
|
+
if ((clipState === null || clipState === void 0 ? void 0 : clipState.type) === 'pause_requested') {
|
|
136
139
|
log('Clip started playing but should be paused', { path, soundId });
|
|
137
140
|
this.pauseAudioClip(path, { fade: clipState.fade }, soundId, true);
|
|
138
141
|
}
|
|
139
|
-
else if (clipState.type === 'stop_requested') {
|
|
142
|
+
else if ((clipState === null || clipState === void 0 ? void 0 : clipState.type) === 'stop_requested') {
|
|
140
143
|
log('Clip started playing but should be stopped', { path, soundId });
|
|
141
144
|
this.stopAudioClip(path, { fade: clipState.fade }, soundId, true);
|
|
142
145
|
}
|
|
@@ -148,12 +151,13 @@ class AudioPlayer {
|
|
|
148
151
|
if (isFadeValid(fade)) {
|
|
149
152
|
// Start fade when clip starts
|
|
150
153
|
clipPlayer.player.volume(0, soundId);
|
|
154
|
+
clipPlayer.player.mute(false, soundId);
|
|
151
155
|
clipPlayer.player.once('play', () => {
|
|
152
|
-
clipPlayer.player
|
|
156
|
+
fadeAudioPlayerVolume(clipPlayer.player, volume, fade * 1000, soundId);
|
|
153
157
|
}, soundId);
|
|
154
158
|
}
|
|
155
159
|
else {
|
|
156
|
-
clipPlayer.player
|
|
160
|
+
setAudioPlayerVolume(clipPlayer.player, volume, soundId);
|
|
157
161
|
}
|
|
158
162
|
// Track new/updated active clip
|
|
159
163
|
clipPlayer.activeClips = { ...clipPlayer.activeClips, [soundId]: activeClip };
|
|
@@ -181,7 +185,7 @@ class AudioPlayer {
|
|
|
181
185
|
this.updateActiveAudioClip(path, soundId, (clip) => ({ ...clip, state: { type: 'paused' } }));
|
|
182
186
|
this.notifyClipStateListeners(clip.playId, path, 'paused');
|
|
183
187
|
}, soundId);
|
|
184
|
-
|
|
188
|
+
fadeAudioPlayerVolume(clipPlayer.player, 0, fade * 1000, soundId);
|
|
185
189
|
clip.state = { type: 'pausing' };
|
|
186
190
|
}
|
|
187
191
|
else {
|
|
@@ -221,7 +225,7 @@ class AudioPlayer {
|
|
|
221
225
|
// Cleanup any old fade callbacks first
|
|
222
226
|
// TODO: Remove cast once https://github.com/DefinitelyTyped/DefinitelyTyped/pull/59411 is merged
|
|
223
227
|
clipPlayer.player.off('fade', soundId);
|
|
224
|
-
|
|
228
|
+
fadeAudioPlayerVolume(clipPlayer.player, 0, fade * 1000, soundId);
|
|
225
229
|
// Set callback after starting new fade, otherwise it will fire straight away as the previous fade is cancelled
|
|
226
230
|
clipPlayer.player.once('fade', (soundId) => clipPlayer.player.stop(soundId), soundId);
|
|
227
231
|
clip.state = { type: 'stopping' };
|
|
@@ -264,10 +268,10 @@ class AudioPlayer {
|
|
|
264
268
|
if (clip.state.type !== 'pausing' && clip.state.type !== 'stopping') {
|
|
265
269
|
const soundId = parseInt(soundIdStr);
|
|
266
270
|
if (isFadeValid(fade)) {
|
|
267
|
-
|
|
271
|
+
fadeAudioPlayerVolume(clipPlayer.player, volume, fade * 1000, soundId);
|
|
268
272
|
}
|
|
269
273
|
else {
|
|
270
|
-
clipPlayer.player
|
|
274
|
+
setAudioPlayerVolume(clipPlayer.player, volume, soundId);
|
|
271
275
|
}
|
|
272
276
|
return [soundIdStr, { ...clip, volume }];
|
|
273
277
|
}
|
|
@@ -401,8 +405,11 @@ function log(...data) {
|
|
|
401
405
|
console.log(...data);
|
|
402
406
|
}
|
|
403
407
|
}
|
|
408
|
+
/**
|
|
409
|
+
* @returns `true` if this is this a valid fade duration. Always returns `false` on iOS
|
|
410
|
+
*/
|
|
404
411
|
function isFadeValid(fade) {
|
|
405
|
-
return typeof fade === 'number' && !isNaN(fade) && fade > 0;
|
|
412
|
+
return !IS_IOS && typeof fade === 'number' && !isNaN(fade) && fade > 0;
|
|
406
413
|
}
|
|
407
414
|
function setPlayerSinkId(player, sinkId) {
|
|
408
415
|
var _a;
|
|
@@ -420,3 +427,21 @@ function setPlayerSinkId(player, sinkId) {
|
|
|
420
427
|
console.warn('Cannot set sink ID: web audio not supported', player);
|
|
421
428
|
}
|
|
422
429
|
}
|
|
430
|
+
/**
|
|
431
|
+
* Set audio volume
|
|
432
|
+
*
|
|
433
|
+
* This doesn't work on iOS (volume is read-only) so at least mute it if the volume is zero
|
|
434
|
+
*/
|
|
435
|
+
function setAudioPlayerVolume(howl, volume, soundId) {
|
|
436
|
+
howl.volume(volume, soundId);
|
|
437
|
+
howl.mute(volume === 0, soundId);
|
|
438
|
+
}
|
|
439
|
+
/**
|
|
440
|
+
* Fade to audio volume
|
|
441
|
+
*
|
|
442
|
+
* This doesn't work on iOS (volume is read-only) so at least mute it if the volume is zero
|
|
443
|
+
*/
|
|
444
|
+
function fadeAudioPlayerVolume(howl, volume, fade, soundId) {
|
|
445
|
+
howl.mute(false, soundId);
|
|
446
|
+
howl.fade(howl.volume(soundId), volume, fade, soundId);
|
|
447
|
+
}
|
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
|
};
|
|
@@ -270,16 +287,54 @@ class VideoPlayer {
|
|
|
270
287
|
videoElement.src = urls_1.assetUrl(path);
|
|
271
288
|
videoElement.autoplay = false;
|
|
272
289
|
videoElement.loop = false;
|
|
273
|
-
videoElement
|
|
290
|
+
setVideoElementVolume(videoElement, volume * this.globalVolume);
|
|
274
291
|
videoElement.preload = config.preload;
|
|
275
292
|
videoElement.addEventListener('playing', () => {
|
|
276
|
-
var _a;
|
|
277
|
-
|
|
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
|
|
278
326
|
this.notifyClipStateListeners(this.activeClip.playId, path, 'playing');
|
|
279
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
|
+
}
|
|
280
333
|
});
|
|
281
334
|
videoElement.addEventListener('ended', () => {
|
|
282
|
-
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) {
|
|
283
338
|
this.handleStoppedClip(path);
|
|
284
339
|
}
|
|
285
340
|
});
|
|
@@ -316,7 +371,7 @@ class VideoPlayer {
|
|
|
316
371
|
}
|
|
317
372
|
exports.default = VideoPlayer;
|
|
318
373
|
function preloadString(preload) {
|
|
319
|
-
return typeof preload === 'string' ? preload : preload ? '
|
|
374
|
+
return typeof preload === 'string' ? preload : preload ? 'auto' : 'none';
|
|
320
375
|
}
|
|
321
376
|
function setPlayerSinkId(player, sinkId) {
|
|
322
377
|
if (sinkId === undefined) {
|
|
@@ -326,3 +381,12 @@ function setPlayerSinkId(player, sinkId) {
|
|
|
326
381
|
player.videoElement.setSinkId(sinkId);
|
|
327
382
|
}
|
|
328
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
|
+
}
|