@clockworkdog/cogs-client 3.0.0-alpha.11 → 3.0.0-alpha.14
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/browser/index.mjs +740 -674
- package/dist/browser/index.umd.js +9 -9
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/state-based/MediaClipManager.d.ts +27 -5
- package/dist/state-based/MediaClipManager.js +90 -29
- package/dist/state-based/MediaPreloader.d.ts +1 -1
- package/dist/state-based/MediaPreloader.js +2 -2
- package/dist/state-based/SurfaceManager.d.ts +3 -0
- package/dist/state-based/SurfaceManager.js +26 -5
- package/dist/types/MediaSchema.d.ts +2 -0
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -8,6 +8,7 @@ export * as MediaSchema from './types/MediaSchema';
|
|
|
8
8
|
export { default as CogsAudioPlayer } from './AudioPlayer';
|
|
9
9
|
export { default as CogsVideoPlayer } from './VideoPlayer';
|
|
10
10
|
export { SurfaceManager } from './state-based/SurfaceManager';
|
|
11
|
+
export { MediaPreloader } from './state-based/MediaPreloader';
|
|
11
12
|
export * from './types/AudioState';
|
|
12
13
|
export { assetUrl, preloadUrl } from './utils/urls';
|
|
13
14
|
export { getStateAtTime } from './utils/getStateAtTime';
|
package/dist/index.js
CHANGED
|
@@ -4,6 +4,7 @@ export * as MediaSchema from './types/MediaSchema';
|
|
|
4
4
|
export { default as CogsAudioPlayer } from './AudioPlayer';
|
|
5
5
|
export { default as CogsVideoPlayer } from './VideoPlayer';
|
|
6
6
|
export { SurfaceManager } from './state-based/SurfaceManager';
|
|
7
|
+
export { MediaPreloader } from './state-based/MediaPreloader';
|
|
7
8
|
export * from './types/AudioState';
|
|
8
9
|
export { assetUrl, preloadUrl } from './utils/urls';
|
|
9
10
|
export { getStateAtTime } from './utils/getStateAtTime';
|
|
@@ -8,23 +8,43 @@ export declare abstract class MediaClipManager<T extends MediaClipState> {
|
|
|
8
8
|
private surfaceElement;
|
|
9
9
|
protected clipElement: HTMLElement;
|
|
10
10
|
protected constructAssetURL: (file: string) => string;
|
|
11
|
+
protected getAudioOutput: (outputLabel: string) => string;
|
|
11
12
|
protected mediaPreloader: MediaPreloader;
|
|
12
|
-
constructor(surfaceElement: HTMLElement, clipElement: HTMLElement, state: T, constructAssetURL: (file: string) => string, mediaPreloader: MediaPreloader);
|
|
13
|
+
constructor(surfaceElement: HTMLElement, clipElement: HTMLElement, state: T, constructAssetURL: (file: string) => string, getAudioOutput: (outputLabel: string) => string, mediaPreloader: MediaPreloader);
|
|
13
14
|
protected abstract update(): void;
|
|
14
15
|
abstract destroy(): void;
|
|
15
16
|
isConnected(element?: HTMLElement): boolean;
|
|
16
17
|
protected _state: T;
|
|
17
18
|
setState(newState: T): void;
|
|
18
19
|
private timeout;
|
|
19
|
-
|
|
20
|
+
loop: () => Promise<void>;
|
|
20
21
|
}
|
|
21
|
-
|
|
22
|
+
/**
|
|
23
|
+
* Makes sure that the child media element exists and is of the correct type
|
|
24
|
+
* - If it isn't or doesn't exist we'll get a new one
|
|
25
|
+
* - If it is audio or video we'll try and get a preloaded media element
|
|
26
|
+
* - Otherwise we'll directly create and set the src
|
|
27
|
+
*/
|
|
28
|
+
export declare function assertElement(mediaElement: HTMLMediaElement | HTMLImageElement | undefined, parentElement: HTMLElement, clip: MediaClipState, constructAssetURL: (file: string) => string, preloader: MediaPreloader): HTMLElement;
|
|
29
|
+
/**
|
|
30
|
+
* Makes sure that the element looks correct.
|
|
31
|
+
* - If the opacity, zIndex or fit are incorrect, we'll set again
|
|
32
|
+
*/
|
|
22
33
|
export declare function assertVisualProperties(mediaElement: HTMLMediaElement | HTMLImageElement, properties: VisualProperties, objectFit: ImageMetadata['fit']): void;
|
|
23
|
-
|
|
34
|
+
/**
|
|
35
|
+
* Makes sure that the element sounds correct.
|
|
36
|
+
* - It should have the right volume, and play out the correct speaker.
|
|
37
|
+
*/
|
|
38
|
+
export declare function assertAudialProperties(mediaElement: HTMLMediaElement, properties: AudialProperties, sinkId: string, surfaceVolume: number): void;
|
|
24
39
|
interface TemporalSyncState {
|
|
25
40
|
state: 'idle' | 'seeking' | 'intercepting';
|
|
26
41
|
}
|
|
27
|
-
|
|
42
|
+
/**
|
|
43
|
+
* Makes sure the media is at the correct time and speed.
|
|
44
|
+
* - If we fall slightly behind, we will lightly adjust the speed to catch up.
|
|
45
|
+
* - If we are too far away to smoothly realign, we will seek to the correct time.
|
|
46
|
+
*/
|
|
47
|
+
export declare function assertTemporalProperties(mediaElement: HTMLMediaElement, properties: TemporalProperties, keyframes: VideoState['keyframes'], syncState: TemporalSyncState, disablePlaybackRateAdjustment?: boolean): TemporalSyncState;
|
|
28
48
|
export declare class ImageManager extends MediaClipManager<ImageState> {
|
|
29
49
|
private imageElement;
|
|
30
50
|
protected update(): void;
|
|
@@ -33,12 +53,14 @@ export declare class ImageManager extends MediaClipManager<ImageState> {
|
|
|
33
53
|
export declare class AudioManager extends MediaClipManager<AudioState> {
|
|
34
54
|
private syncState;
|
|
35
55
|
private audioElement;
|
|
56
|
+
volume: number;
|
|
36
57
|
protected update(): void;
|
|
37
58
|
destroy(): void;
|
|
38
59
|
}
|
|
39
60
|
export declare class VideoManager extends MediaClipManager<VideoState> {
|
|
40
61
|
private syncState;
|
|
41
62
|
private videoElement?;
|
|
63
|
+
volume: number;
|
|
42
64
|
protected update(): void;
|
|
43
65
|
destroy(): void;
|
|
44
66
|
}
|
|
@@ -1,4 +1,14 @@
|
|
|
1
1
|
import { getStateAtTime } from '../utils/getStateAtTime';
|
|
2
|
+
const getPath = (url) => {
|
|
3
|
+
try {
|
|
4
|
+
const { pathname } = new URL(url, window.location.href);
|
|
5
|
+
return pathname;
|
|
6
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
7
|
+
}
|
|
8
|
+
catch (_) {
|
|
9
|
+
return undefined;
|
|
10
|
+
}
|
|
11
|
+
};
|
|
2
12
|
/**
|
|
3
13
|
* Each instance of a MediaClipManager is responsible for displaying
|
|
4
14
|
* an image/audio/video clip in the correct state.
|
|
@@ -7,15 +17,15 @@ export class MediaClipManager {
|
|
|
7
17
|
surfaceElement;
|
|
8
18
|
clipElement;
|
|
9
19
|
constructAssetURL;
|
|
20
|
+
getAudioOutput;
|
|
10
21
|
mediaPreloader;
|
|
11
|
-
constructor(surfaceElement, clipElement, state, constructAssetURL, mediaPreloader) {
|
|
22
|
+
constructor(surfaceElement, clipElement, state, constructAssetURL, getAudioOutput, mediaPreloader) {
|
|
12
23
|
this.surfaceElement = surfaceElement;
|
|
13
24
|
this.clipElement = clipElement;
|
|
14
25
|
this.constructAssetURL = constructAssetURL;
|
|
26
|
+
this.getAudioOutput = getAudioOutput;
|
|
15
27
|
this.mediaPreloader = mediaPreloader;
|
|
16
28
|
this._state = state;
|
|
17
|
-
// Allow the class to be constructed, then call the loop
|
|
18
|
-
setTimeout(this.loop);
|
|
19
29
|
}
|
|
20
30
|
isConnected(element) {
|
|
21
31
|
if (!this.surfaceElement) {
|
|
@@ -39,50 +49,62 @@ export class MediaClipManager {
|
|
|
39
49
|
}
|
|
40
50
|
timeout;
|
|
41
51
|
loop = async () => {
|
|
52
|
+
clearTimeout(this.timeout);
|
|
42
53
|
if (this.isConnected()) {
|
|
43
54
|
this.update();
|
|
44
|
-
this.timeout = setTimeout(this.loop,
|
|
55
|
+
this.timeout = setTimeout(this.loop, INNER_TARGET_SYNC_THRESHOLD_MS);
|
|
45
56
|
}
|
|
46
57
|
else {
|
|
47
58
|
this.destroy();
|
|
48
59
|
}
|
|
49
60
|
};
|
|
50
61
|
}
|
|
62
|
+
/**
|
|
63
|
+
* Makes sure that the child media element exists and is of the correct type
|
|
64
|
+
* - If it isn't or doesn't exist we'll get a new one
|
|
65
|
+
* - If it is audio or video we'll try and get a preloaded media element
|
|
66
|
+
* - Otherwise we'll directly create and set the src
|
|
67
|
+
*/
|
|
51
68
|
export function assertElement(mediaElement, parentElement, clip, constructAssetURL, preloader) {
|
|
52
|
-
let element;
|
|
69
|
+
let element = undefined;
|
|
53
70
|
const assetURL = constructAssetURL(clip.file);
|
|
71
|
+
const assetPath = getPath(assetURL);
|
|
54
72
|
switch (clip.type) {
|
|
55
73
|
case 'image':
|
|
56
74
|
{
|
|
57
75
|
element = mediaElement instanceof HTMLImageElement ? mediaElement : document.createElement('img');
|
|
58
|
-
|
|
76
|
+
const elementPath = getPath(element.src);
|
|
77
|
+
if (elementPath !== assetPath) {
|
|
59
78
|
element.src = assetURL;
|
|
60
79
|
}
|
|
61
80
|
}
|
|
62
81
|
break;
|
|
63
82
|
case 'audio':
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
break;
|
|
71
|
-
case 'video':
|
|
72
|
-
if (mediaElement instanceof HTMLVideoElement && mediaElement.src.includes(assetURL)) {
|
|
73
|
-
element = mediaElement;
|
|
83
|
+
case 'video': {
|
|
84
|
+
if (mediaElement !== undefined) {
|
|
85
|
+
const path = getPath(mediaElement.src);
|
|
86
|
+
if (mediaElement.tagName.toLowerCase() === clip.type && path !== undefined && path === assetPath) {
|
|
87
|
+
element = mediaElement;
|
|
88
|
+
}
|
|
74
89
|
}
|
|
75
|
-
|
|
90
|
+
if (!element) {
|
|
76
91
|
element = preloader.getElement(clip.file, clip.type);
|
|
77
92
|
}
|
|
78
93
|
break;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
if (parentElement.children.length !== 1 || parentElement.childNodes[0] !== element) {
|
|
97
|
+
parentElement.replaceChildren(element);
|
|
79
98
|
}
|
|
80
|
-
parentElement.replaceChildren(element);
|
|
81
99
|
element.style.position = 'absolute';
|
|
82
100
|
element.style.width = '100%';
|
|
83
101
|
element.style.height = '100%';
|
|
84
102
|
return element;
|
|
85
103
|
}
|
|
104
|
+
/**
|
|
105
|
+
* Makes sure that the element looks correct.
|
|
106
|
+
* - If the opacity, zIndex or fit are incorrect, we'll set again
|
|
107
|
+
*/
|
|
86
108
|
export function assertVisualProperties(mediaElement, properties, objectFit) {
|
|
87
109
|
const opacityString = String(properties.opacity);
|
|
88
110
|
if (mediaElement.style.opacity !== opacityString) {
|
|
@@ -96,9 +118,14 @@ export function assertVisualProperties(mediaElement, properties, objectFit) {
|
|
|
96
118
|
mediaElement.style.objectFit = objectFit;
|
|
97
119
|
}
|
|
98
120
|
}
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
121
|
+
/**
|
|
122
|
+
* Makes sure that the element sounds correct.
|
|
123
|
+
* - It should have the right volume, and play out the correct speaker.
|
|
124
|
+
*/
|
|
125
|
+
export function assertAudialProperties(mediaElement, properties, sinkId, surfaceVolume) {
|
|
126
|
+
const clipVolume = properties.volume * surfaceVolume;
|
|
127
|
+
if (mediaElement.volume !== clipVolume) {
|
|
128
|
+
mediaElement.volume = clipVolume;
|
|
102
129
|
}
|
|
103
130
|
if (mediaElement.sinkId !== sinkId) {
|
|
104
131
|
try {
|
|
@@ -113,6 +140,7 @@ export function assertAudialProperties(mediaElement, properties, sinkId) {
|
|
|
113
140
|
}
|
|
114
141
|
}
|
|
115
142
|
const OUTER_TARGET_SYNC_THRESHOLD_MS = 50; // When outside of this range we attempt to sync playback
|
|
143
|
+
const OUTER_TARGET_SYNC_NO_PLAYBACK_RATE_ADJUSTMENT_THRESHOLD_MS = 500; // When outside of this range we attempt to sync playback (when playback rate adjustment not allowed)
|
|
116
144
|
const INNER_TARGET_SYNC_THRESHOLD_MS = 5; // When attempting to sync playback, we aim for this accuracy
|
|
117
145
|
const MAX_SYNC_THRESHOLD_MS = 1_000; // If we are further than this, we will seek instead
|
|
118
146
|
const SEEK_LOOKAHEAD_MS = 5; // If it takes time to seek, we should seek ahead a little
|
|
@@ -122,7 +150,12 @@ const MAX_PLAYBACK_RATE_ADJUSTMENT = 0.1;
|
|
|
122
150
|
function playbackSmoothing(deltaTime) {
|
|
123
151
|
return Math.sign(deltaTime) * Math.pow(Math.abs(deltaTime) / MAX_SYNC_THRESHOLD_MS, PLAYBACK_ADJUSTMENT_SMOOTHING) * MAX_PLAYBACK_RATE_ADJUSTMENT;
|
|
124
152
|
}
|
|
125
|
-
|
|
153
|
+
/**
|
|
154
|
+
* Makes sure the media is at the correct time and speed.
|
|
155
|
+
* - If we fall slightly behind, we will lightly adjust the speed to catch up.
|
|
156
|
+
* - If we are too far away to smoothly realign, we will seek to the correct time.
|
|
157
|
+
*/
|
|
158
|
+
export function assertTemporalProperties(mediaElement, properties, keyframes, syncState, disablePlaybackRateAdjustment) {
|
|
126
159
|
if (mediaElement.paused && properties.rate > 0) {
|
|
127
160
|
mediaElement.play().catch(() => {
|
|
128
161
|
/* Do nothing, will be tried in next loop */
|
|
@@ -154,6 +187,19 @@ export function assertTemporalProperties(mediaElement, properties, keyframes, sy
|
|
|
154
187
|
}
|
|
155
188
|
return { state: 'idle' };
|
|
156
189
|
case syncState.state === 'idle' &&
|
|
190
|
+
properties.rate > 0 &&
|
|
191
|
+
disablePlaybackRateAdjustment === true &&
|
|
192
|
+
deltaTimeAbs <= OUTER_TARGET_SYNC_NO_PLAYBACK_RATE_ADJUSTMENT_THRESHOLD_MS:
|
|
193
|
+
// If we aren't able to adjust playback rate, we are more forgiving
|
|
194
|
+
// in our "synced" check to avoid the clip being seeked forward
|
|
195
|
+
// in normal playback where we expect to be a little out of sync
|
|
196
|
+
// due to network and startup latency
|
|
197
|
+
if (mediaElement.playbackRate !== properties.rate) {
|
|
198
|
+
mediaElement.playbackRate = properties.rate;
|
|
199
|
+
}
|
|
200
|
+
return { state: 'idle' };
|
|
201
|
+
case syncState.state === 'idle' &&
|
|
202
|
+
disablePlaybackRateAdjustment !== true && // Never adjust playback rate if disabled for this clip
|
|
157
203
|
properties.rate > 0 &&
|
|
158
204
|
deltaTimeAbs > OUTER_TARGET_SYNC_THRESHOLD_MS &&
|
|
159
205
|
deltaTimeAbs <= MAX_SYNC_THRESHOLD_MS:
|
|
@@ -177,7 +223,6 @@ export function assertTemporalProperties(mediaElement, properties, keyframes, sy
|
|
|
177
223
|
}
|
|
178
224
|
case syncState.state === 'intercepting' && Math.sign(deltaTime) === Math.sign(mediaElement.playbackRate - properties.rate): {
|
|
179
225
|
// We have missed our interception. Go back to idle to try again.
|
|
180
|
-
console.warn(deltaTime, 'missed intercept');
|
|
181
226
|
return { state: 'idle' };
|
|
182
227
|
}
|
|
183
228
|
case syncState.state === 'intercepting':
|
|
@@ -221,6 +266,7 @@ export class ImageManager extends MediaClipManager {
|
|
|
221
266
|
export class AudioManager extends MediaClipManager {
|
|
222
267
|
syncState = { state: 'idle' };
|
|
223
268
|
audioElement;
|
|
269
|
+
volume = 1;
|
|
224
270
|
update() {
|
|
225
271
|
const currentState = getStateAtTime(this._state, Date.now());
|
|
226
272
|
if (currentState) {
|
|
@@ -231,8 +277,9 @@ export class AudioManager extends MediaClipManager {
|
|
|
231
277
|
}
|
|
232
278
|
if (!currentState || !this.audioElement)
|
|
233
279
|
return;
|
|
234
|
-
|
|
235
|
-
|
|
280
|
+
const sinkId = this.getAudioOutput(this._state.audioOutput);
|
|
281
|
+
assertAudialProperties(this.audioElement, currentState, sinkId, this.volume);
|
|
282
|
+
const nextSyncState = assertTemporalProperties(this.audioElement, currentState, this._state.keyframes, this.syncState, this._state.disablePlaybackRateAdjustment);
|
|
236
283
|
if (this.syncState.state !== 'seeking' && nextSyncState.state === 'seeking') {
|
|
237
284
|
this.audioElement.addEventListener('seeked', () => {
|
|
238
285
|
this.syncState = { state: 'idle' };
|
|
@@ -241,13 +288,20 @@ export class AudioManager extends MediaClipManager {
|
|
|
241
288
|
this.syncState = nextSyncState;
|
|
242
289
|
}
|
|
243
290
|
destroy() {
|
|
244
|
-
this.audioElement
|
|
291
|
+
if (this.audioElement) {
|
|
292
|
+
this.audioElement.pause();
|
|
293
|
+
this.audioElement.remove();
|
|
294
|
+
this.audioElement.volume = 0;
|
|
295
|
+
this.audioElement.currentTime = 0;
|
|
296
|
+
this.mediaPreloader.releaseElement(this.audioElement);
|
|
297
|
+
}
|
|
245
298
|
this.audioElement = undefined;
|
|
246
299
|
}
|
|
247
300
|
}
|
|
248
301
|
export class VideoManager extends MediaClipManager {
|
|
249
302
|
syncState = { state: 'idle' };
|
|
250
303
|
videoElement;
|
|
304
|
+
volume = 1;
|
|
251
305
|
update() {
|
|
252
306
|
const currentState = getStateAtTime(this._state, Date.now());
|
|
253
307
|
if (currentState) {
|
|
@@ -258,9 +312,10 @@ export class VideoManager extends MediaClipManager {
|
|
|
258
312
|
}
|
|
259
313
|
if (!currentState || !this.videoElement)
|
|
260
314
|
return;
|
|
315
|
+
const sinkId = this.getAudioOutput(this._state.audioOutput);
|
|
261
316
|
assertVisualProperties(this.videoElement, currentState, this._state.fit);
|
|
262
|
-
assertAudialProperties(this.videoElement, currentState, this.
|
|
263
|
-
const nextSyncState = assertTemporalProperties(this.videoElement, currentState, this._state.keyframes, this.syncState);
|
|
317
|
+
assertAudialProperties(this.videoElement, currentState, sinkId, this.volume);
|
|
318
|
+
const nextSyncState = assertTemporalProperties(this.videoElement, currentState, this._state.keyframes, this.syncState, this._state.disablePlaybackRateAdjustment);
|
|
264
319
|
if (this.syncState.state !== 'seeking' && nextSyncState.state === 'seeking') {
|
|
265
320
|
this.videoElement.addEventListener('seeked', () => {
|
|
266
321
|
this.syncState = { state: 'idle' };
|
|
@@ -269,7 +324,13 @@ export class VideoManager extends MediaClipManager {
|
|
|
269
324
|
this.syncState = nextSyncState;
|
|
270
325
|
}
|
|
271
326
|
destroy() {
|
|
272
|
-
this.videoElement
|
|
327
|
+
if (this.videoElement) {
|
|
328
|
+
this.videoElement.pause();
|
|
329
|
+
this.videoElement.remove();
|
|
330
|
+
this.videoElement.volume = 0;
|
|
331
|
+
this.videoElement.currentTime = 0;
|
|
332
|
+
this.mediaPreloader.releaseElement(this.videoElement);
|
|
333
|
+
}
|
|
273
334
|
this.videoElement = undefined;
|
|
274
335
|
}
|
|
275
336
|
}
|
|
@@ -10,5 +10,5 @@ export declare class MediaPreloader {
|
|
|
10
10
|
setState(newState: MediaClientConfig['files']): void;
|
|
11
11
|
private update;
|
|
12
12
|
getElement(file: string, type: 'audio' | 'video'): HTMLAudioElement;
|
|
13
|
-
releaseElement(resource: string |
|
|
13
|
+
releaseElement(resource: string | HTMLElement): void;
|
|
14
14
|
}
|
|
@@ -83,9 +83,9 @@ export class MediaPreloader {
|
|
|
83
83
|
}
|
|
84
84
|
}
|
|
85
85
|
else {
|
|
86
|
-
Object.
|
|
86
|
+
Object.values(this._elements).forEach((media) => {
|
|
87
87
|
if (media.element === resource) {
|
|
88
|
-
|
|
88
|
+
media.inUse = false;
|
|
89
89
|
}
|
|
90
90
|
});
|
|
91
91
|
}
|
|
@@ -12,6 +12,9 @@ export declare class SurfaceManager {
|
|
|
12
12
|
private mediaPreloader;
|
|
13
13
|
private _state;
|
|
14
14
|
setState(newState: MediaSurfaceState): void;
|
|
15
|
+
private _volume;
|
|
16
|
+
get volume(): number;
|
|
17
|
+
set volume(newVolume: number);
|
|
15
18
|
private _element;
|
|
16
19
|
get element(): HTMLDivElement;
|
|
17
20
|
private resources;
|
|
@@ -15,6 +15,18 @@ export class SurfaceManager {
|
|
|
15
15
|
this._state = newState;
|
|
16
16
|
this.update();
|
|
17
17
|
}
|
|
18
|
+
_volume = 1;
|
|
19
|
+
get volume() {
|
|
20
|
+
return this._volume;
|
|
21
|
+
}
|
|
22
|
+
set volume(newVolume) {
|
|
23
|
+
this._volume = newVolume;
|
|
24
|
+
Object.values(this.resources).forEach(({ manager }) => {
|
|
25
|
+
if (manager instanceof AudioManager || manager instanceof VideoManager) {
|
|
26
|
+
manager.volume = newVolume;
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
}
|
|
18
30
|
_element;
|
|
19
31
|
get element() {
|
|
20
32
|
return this._element;
|
|
@@ -68,14 +80,23 @@ export class SurfaceManager {
|
|
|
68
80
|
if (!resource.manager) {
|
|
69
81
|
switch (clip.type) {
|
|
70
82
|
case 'image':
|
|
71
|
-
resource.manager = new ImageManager(this._element, resource.element, clip, this.constructAssetUrl, this.mediaPreloader);
|
|
83
|
+
resource.manager = new ImageManager(this._element, resource.element, clip, this.constructAssetUrl, this.getAudioOutput, this.mediaPreloader);
|
|
84
|
+
resource.manager.loop();
|
|
72
85
|
break;
|
|
73
|
-
case 'audio':
|
|
74
|
-
|
|
86
|
+
case 'audio': {
|
|
87
|
+
const audioManager = new AudioManager(this._element, resource.element, clip, this.constructAssetUrl, this.getAudioOutput, this.mediaPreloader);
|
|
88
|
+
resource.manager = audioManager;
|
|
89
|
+
audioManager.volume = this._volume;
|
|
90
|
+
audioManager.loop();
|
|
75
91
|
break;
|
|
76
|
-
|
|
77
|
-
|
|
92
|
+
}
|
|
93
|
+
case 'video': {
|
|
94
|
+
const videoManager = new VideoManager(this._element, resource.element, clip, this.constructAssetUrl, this.getAudioOutput, this.mediaPreloader);
|
|
95
|
+
resource.manager = videoManager;
|
|
96
|
+
videoManager.volume = this._volume;
|
|
97
|
+
videoManager.loop();
|
|
78
98
|
break;
|
|
99
|
+
}
|
|
79
100
|
}
|
|
80
101
|
}
|
|
81
102
|
else {
|
|
@@ -194,6 +194,7 @@ export type AudioState = {
|
|
|
194
194
|
type: 'audio';
|
|
195
195
|
file: string;
|
|
196
196
|
audioOutput: string;
|
|
197
|
+
disablePlaybackRateAdjustment?: boolean;
|
|
197
198
|
keyframes: [InitialAudioKeyframe, ...Array<AudioKeyframe | NullKeyframe>];
|
|
198
199
|
};
|
|
199
200
|
export type VideoState = {
|
|
@@ -201,6 +202,7 @@ export type VideoState = {
|
|
|
201
202
|
file: string;
|
|
202
203
|
fit: 'cover' | 'contain' | 'none';
|
|
203
204
|
audioOutput: string;
|
|
205
|
+
disablePlaybackRateAdjustment?: boolean;
|
|
204
206
|
keyframes: [InitialVideoKeyframe, ...Array<VideoKeyframe | NullKeyframe>];
|
|
205
207
|
};
|
|
206
208
|
export type MediaClipState = ImageState | AudioState | VideoState;
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"description": "Connect to COGS to build a custom Media Master",
|
|
4
4
|
"author": "Clockwork Dog <info@clockwork.dog>",
|
|
5
5
|
"homepage": "https://github.com/clockwork-dog/cogs-sdk/tree/main/packages/javascript",
|
|
6
|
-
"version": "3.0.0-alpha.
|
|
6
|
+
"version": "3.0.0-alpha.14",
|
|
7
7
|
"keywords": [],
|
|
8
8
|
"license": "MIT",
|
|
9
9
|
"repository": {
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
"cy:generate": "cypress run --e2e"
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
|
-
"@clockworkdog/timesync": "^3.0.0-alpha.
|
|
40
|
+
"@clockworkdog/timesync": "^3.0.0-alpha.14",
|
|
41
41
|
"howler": "clockwork-dog/howler.js#fix-looping-clips",
|
|
42
42
|
"reconnecting-websocket": "^4.4.0",
|
|
43
43
|
"zod": "^4.1.13"
|