@clockworkdog/cogs-client 3.0.0-alpha.13 → 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 +712 -669
- 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 +25 -4
- package/dist/state-based/MediaClipManager.js +82 -27
- 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 +25 -4
- 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';
|
|
@@ -17,15 +17,34 @@ export declare abstract class MediaClipManager<T extends MediaClipState> {
|
|
|
17
17
|
protected _state: T;
|
|
18
18
|
setState(newState: T): void;
|
|
19
19
|
private timeout;
|
|
20
|
-
|
|
20
|
+
loop: () => Promise<void>;
|
|
21
21
|
}
|
|
22
|
-
|
|
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
|
+
*/
|
|
23
33
|
export declare function assertVisualProperties(mediaElement: HTMLMediaElement | HTMLImageElement, properties: VisualProperties, objectFit: ImageMetadata['fit']): void;
|
|
24
|
-
|
|
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;
|
|
25
39
|
interface TemporalSyncState {
|
|
26
40
|
state: 'idle' | 'seeking' | 'intercepting';
|
|
27
41
|
}
|
|
28
|
-
|
|
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;
|
|
29
48
|
export declare class ImageManager extends MediaClipManager<ImageState> {
|
|
30
49
|
private imageElement;
|
|
31
50
|
protected update(): void;
|
|
@@ -34,12 +53,14 @@ export declare class ImageManager extends MediaClipManager<ImageState> {
|
|
|
34
53
|
export declare class AudioManager extends MediaClipManager<AudioState> {
|
|
35
54
|
private syncState;
|
|
36
55
|
private audioElement;
|
|
56
|
+
volume: number;
|
|
37
57
|
protected update(): void;
|
|
38
58
|
destroy(): void;
|
|
39
59
|
}
|
|
40
60
|
export declare class VideoManager extends MediaClipManager<VideoState> {
|
|
41
61
|
private syncState;
|
|
42
62
|
private videoElement?;
|
|
63
|
+
volume: number;
|
|
43
64
|
protected update(): void;
|
|
44
65
|
destroy(): void;
|
|
45
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.
|
|
@@ -16,8 +26,6 @@ export class MediaClipManager {
|
|
|
16
26
|
this.getAudioOutput = getAudioOutput;
|
|
17
27
|
this.mediaPreloader = mediaPreloader;
|
|
18
28
|
this._state = state;
|
|
19
|
-
// Allow the class to be constructed, then call the loop
|
|
20
|
-
setTimeout(this.loop);
|
|
21
29
|
}
|
|
22
30
|
isConnected(element) {
|
|
23
31
|
if (!this.surfaceElement) {
|
|
@@ -41,43 +49,49 @@ export class MediaClipManager {
|
|
|
41
49
|
}
|
|
42
50
|
timeout;
|
|
43
51
|
loop = async () => {
|
|
52
|
+
clearTimeout(this.timeout);
|
|
44
53
|
if (this.isConnected()) {
|
|
45
54
|
this.update();
|
|
46
|
-
this.timeout = setTimeout(this.loop,
|
|
55
|
+
this.timeout = setTimeout(this.loop, INNER_TARGET_SYNC_THRESHOLD_MS);
|
|
47
56
|
}
|
|
48
57
|
else {
|
|
49
58
|
this.destroy();
|
|
50
59
|
}
|
|
51
60
|
};
|
|
52
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
|
+
*/
|
|
53
68
|
export function assertElement(mediaElement, parentElement, clip, constructAssetURL, preloader) {
|
|
54
|
-
let element;
|
|
69
|
+
let element = undefined;
|
|
55
70
|
const assetURL = constructAssetURL(clip.file);
|
|
71
|
+
const assetPath = getPath(assetURL);
|
|
56
72
|
switch (clip.type) {
|
|
57
73
|
case 'image':
|
|
58
74
|
{
|
|
59
75
|
element = mediaElement instanceof HTMLImageElement ? mediaElement : document.createElement('img');
|
|
60
|
-
|
|
76
|
+
const elementPath = getPath(element.src);
|
|
77
|
+
if (elementPath !== assetPath) {
|
|
61
78
|
element.src = assetURL;
|
|
62
79
|
}
|
|
63
80
|
}
|
|
64
81
|
break;
|
|
65
82
|
case 'audio':
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
break;
|
|
73
|
-
case 'video':
|
|
74
|
-
if (mediaElement instanceof HTMLVideoElement && mediaElement.src.includes(assetURL)) {
|
|
75
|
-
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
|
+
}
|
|
76
89
|
}
|
|
77
|
-
|
|
90
|
+
if (!element) {
|
|
78
91
|
element = preloader.getElement(clip.file, clip.type);
|
|
79
92
|
}
|
|
80
93
|
break;
|
|
94
|
+
}
|
|
81
95
|
}
|
|
82
96
|
if (parentElement.children.length !== 1 || parentElement.childNodes[0] !== element) {
|
|
83
97
|
parentElement.replaceChildren(element);
|
|
@@ -87,6 +101,10 @@ export function assertElement(mediaElement, parentElement, clip, constructAssetU
|
|
|
87
101
|
element.style.height = '100%';
|
|
88
102
|
return element;
|
|
89
103
|
}
|
|
104
|
+
/**
|
|
105
|
+
* Makes sure that the element looks correct.
|
|
106
|
+
* - If the opacity, zIndex or fit are incorrect, we'll set again
|
|
107
|
+
*/
|
|
90
108
|
export function assertVisualProperties(mediaElement, properties, objectFit) {
|
|
91
109
|
const opacityString = String(properties.opacity);
|
|
92
110
|
if (mediaElement.style.opacity !== opacityString) {
|
|
@@ -100,9 +118,14 @@ export function assertVisualProperties(mediaElement, properties, objectFit) {
|
|
|
100
118
|
mediaElement.style.objectFit = objectFit;
|
|
101
119
|
}
|
|
102
120
|
}
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
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;
|
|
106
129
|
}
|
|
107
130
|
if (mediaElement.sinkId !== sinkId) {
|
|
108
131
|
try {
|
|
@@ -117,6 +140,7 @@ export function assertAudialProperties(mediaElement, properties, sinkId) {
|
|
|
117
140
|
}
|
|
118
141
|
}
|
|
119
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)
|
|
120
144
|
const INNER_TARGET_SYNC_THRESHOLD_MS = 5; // When attempting to sync playback, we aim for this accuracy
|
|
121
145
|
const MAX_SYNC_THRESHOLD_MS = 1_000; // If we are further than this, we will seek instead
|
|
122
146
|
const SEEK_LOOKAHEAD_MS = 5; // If it takes time to seek, we should seek ahead a little
|
|
@@ -126,7 +150,12 @@ const MAX_PLAYBACK_RATE_ADJUSTMENT = 0.1;
|
|
|
126
150
|
function playbackSmoothing(deltaTime) {
|
|
127
151
|
return Math.sign(deltaTime) * Math.pow(Math.abs(deltaTime) / MAX_SYNC_THRESHOLD_MS, PLAYBACK_ADJUSTMENT_SMOOTHING) * MAX_PLAYBACK_RATE_ADJUSTMENT;
|
|
128
152
|
}
|
|
129
|
-
|
|
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) {
|
|
130
159
|
if (mediaElement.paused && properties.rate > 0) {
|
|
131
160
|
mediaElement.play().catch(() => {
|
|
132
161
|
/* Do nothing, will be tried in next loop */
|
|
@@ -158,6 +187,19 @@ export function assertTemporalProperties(mediaElement, properties, keyframes, sy
|
|
|
158
187
|
}
|
|
159
188
|
return { state: 'idle' };
|
|
160
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
|
|
161
203
|
properties.rate > 0 &&
|
|
162
204
|
deltaTimeAbs > OUTER_TARGET_SYNC_THRESHOLD_MS &&
|
|
163
205
|
deltaTimeAbs <= MAX_SYNC_THRESHOLD_MS:
|
|
@@ -181,7 +223,6 @@ export function assertTemporalProperties(mediaElement, properties, keyframes, sy
|
|
|
181
223
|
}
|
|
182
224
|
case syncState.state === 'intercepting' && Math.sign(deltaTime) === Math.sign(mediaElement.playbackRate - properties.rate): {
|
|
183
225
|
// We have missed our interception. Go back to idle to try again.
|
|
184
|
-
console.warn(deltaTime, 'missed intercept');
|
|
185
226
|
return { state: 'idle' };
|
|
186
227
|
}
|
|
187
228
|
case syncState.state === 'intercepting':
|
|
@@ -225,6 +266,7 @@ export class ImageManager extends MediaClipManager {
|
|
|
225
266
|
export class AudioManager extends MediaClipManager {
|
|
226
267
|
syncState = { state: 'idle' };
|
|
227
268
|
audioElement;
|
|
269
|
+
volume = 1;
|
|
228
270
|
update() {
|
|
229
271
|
const currentState = getStateAtTime(this._state, Date.now());
|
|
230
272
|
if (currentState) {
|
|
@@ -236,8 +278,8 @@ export class AudioManager extends MediaClipManager {
|
|
|
236
278
|
if (!currentState || !this.audioElement)
|
|
237
279
|
return;
|
|
238
280
|
const sinkId = this.getAudioOutput(this._state.audioOutput);
|
|
239
|
-
assertAudialProperties(this.audioElement, currentState, sinkId);
|
|
240
|
-
const nextSyncState = assertTemporalProperties(this.audioElement, currentState, this._state.keyframes, this.syncState);
|
|
281
|
+
assertAudialProperties(this.audioElement, currentState, sinkId, this.volume);
|
|
282
|
+
const nextSyncState = assertTemporalProperties(this.audioElement, currentState, this._state.keyframes, this.syncState, this._state.disablePlaybackRateAdjustment);
|
|
241
283
|
if (this.syncState.state !== 'seeking' && nextSyncState.state === 'seeking') {
|
|
242
284
|
this.audioElement.addEventListener('seeked', () => {
|
|
243
285
|
this.syncState = { state: 'idle' };
|
|
@@ -246,13 +288,20 @@ export class AudioManager extends MediaClipManager {
|
|
|
246
288
|
this.syncState = nextSyncState;
|
|
247
289
|
}
|
|
248
290
|
destroy() {
|
|
249
|
-
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
|
+
}
|
|
250
298
|
this.audioElement = undefined;
|
|
251
299
|
}
|
|
252
300
|
}
|
|
253
301
|
export class VideoManager extends MediaClipManager {
|
|
254
302
|
syncState = { state: 'idle' };
|
|
255
303
|
videoElement;
|
|
304
|
+
volume = 1;
|
|
256
305
|
update() {
|
|
257
306
|
const currentState = getStateAtTime(this._state, Date.now());
|
|
258
307
|
if (currentState) {
|
|
@@ -265,8 +314,8 @@ export class VideoManager extends MediaClipManager {
|
|
|
265
314
|
return;
|
|
266
315
|
const sinkId = this.getAudioOutput(this._state.audioOutput);
|
|
267
316
|
assertVisualProperties(this.videoElement, currentState, this._state.fit);
|
|
268
|
-
assertAudialProperties(this.videoElement, currentState, sinkId);
|
|
269
|
-
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);
|
|
270
319
|
if (this.syncState.state !== 'seeking' && nextSyncState.state === 'seeking') {
|
|
271
320
|
this.videoElement.addEventListener('seeked', () => {
|
|
272
321
|
this.syncState = { state: 'idle' };
|
|
@@ -275,7 +324,13 @@ export class VideoManager extends MediaClipManager {
|
|
|
275
324
|
this.syncState = nextSyncState;
|
|
276
325
|
}
|
|
277
326
|
destroy() {
|
|
278
|
-
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
|
+
}
|
|
279
334
|
this.videoElement = undefined;
|
|
280
335
|
}
|
|
281
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;
|
|
@@ -69,13 +81,22 @@ export class SurfaceManager {
|
|
|
69
81
|
switch (clip.type) {
|
|
70
82
|
case 'image':
|
|
71
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"
|