@clockworkdog/cogs-client 3.0.0-alpha.7 → 3.0.0-alpha.8
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 +921 -863
- package/dist/browser/index.umd.js +5 -5
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/state-based/AudioManager.d.ts +3 -1
- package/dist/state-based/AudioManager.js +6 -5
- package/dist/state-based/ClipManager.js +3 -0
- package/dist/state-based/MediaPreloader.d.ts +14 -0
- package/dist/state-based/MediaPreloader.js +86 -0
- package/dist/state-based/SurfaceManager.d.ts +3 -1
- package/dist/state-based/SurfaceManager.js +7 -4
- package/dist/state-based/VideoManager.d.ts +3 -1
- package/dist/state-based/VideoManager.js +7 -5
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -6,6 +6,7 @@ export type { default as ShowPhase } from './types/ShowPhase';
|
|
|
6
6
|
export type { default as MediaObjectFit } from './types/MediaObjectFit';
|
|
7
7
|
export * as MediaSchema from './types/MediaSchema';
|
|
8
8
|
export * from './state-based/SurfaceManager';
|
|
9
|
+
export * from './state-based/MediaPreloader';
|
|
9
10
|
export { default as CogsAudioPlayer } from './AudioPlayer';
|
|
10
11
|
export { default as CogsVideoPlayer } from './VideoPlayer';
|
|
11
12
|
export { SurfaceManager } from './state-based/SurfaceManager';
|
package/dist/index.js
CHANGED
|
@@ -2,6 +2,7 @@ export { default as CogsConnection } from './CogsConnection';
|
|
|
2
2
|
export * from './CogsConnection';
|
|
3
3
|
export * as MediaSchema from './types/MediaSchema';
|
|
4
4
|
export * from './state-based/SurfaceManager';
|
|
5
|
+
export * from './state-based/MediaPreloader';
|
|
5
6
|
export { default as CogsAudioPlayer } from './AudioPlayer';
|
|
6
7
|
export { default as CogsVideoPlayer } from './VideoPlayer';
|
|
7
8
|
export { SurfaceManager } from './state-based/SurfaceManager';
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { AudioState } from '../types/MediaSchema';
|
|
2
2
|
import { ClipManager } from './ClipManager';
|
|
3
|
+
import { MediaPreloader } from './MediaPreloader';
|
|
3
4
|
export declare class AudioManager extends ClipManager<AudioState> {
|
|
5
|
+
private mediaPreloader;
|
|
4
6
|
private audioElement?;
|
|
5
7
|
private isSeeking;
|
|
6
|
-
constructor(surfaceElement: HTMLElement, clipElement: HTMLElement, state: AudioState, constructAssetURL: (file: string) => string);
|
|
8
|
+
constructor(surfaceElement: HTMLElement, clipElement: HTMLElement, state: AudioState, constructAssetURL: (file: string) => string, mediaPreloader: MediaPreloader);
|
|
7
9
|
private updateAudioElement;
|
|
8
10
|
/**
|
|
9
11
|
* Helper function to seek to a specified time.
|
|
@@ -12,15 +12,17 @@ function playbackSmoothing(deltaTime) {
|
|
|
12
12
|
return Math.sign(deltaTime) * Math.pow(Math.abs(deltaTime) / MAX_SYNC_THRESHOLD_MS, PLAYBACK_ADJUSTMENT_SMOOTHING) * MAX_PLAYBACK_RATE_ADJUSTMENT;
|
|
13
13
|
}
|
|
14
14
|
export class AudioManager extends ClipManager {
|
|
15
|
+
mediaPreloader;
|
|
15
16
|
audioElement;
|
|
16
17
|
isSeeking = false;
|
|
17
|
-
constructor(surfaceElement, clipElement, state, constructAssetURL) {
|
|
18
|
+
constructor(surfaceElement, clipElement, state, constructAssetURL, mediaPreloader) {
|
|
18
19
|
super(surfaceElement, clipElement, state, constructAssetURL);
|
|
20
|
+
this.mediaPreloader = mediaPreloader;
|
|
19
21
|
this.clipElement = clipElement;
|
|
20
22
|
}
|
|
21
23
|
updateAudioElement() {
|
|
22
|
-
this.
|
|
23
|
-
this.audioElement =
|
|
24
|
+
const element = this.mediaPreloader.getElement(this._state.file, 'audio');
|
|
25
|
+
this.audioElement = element;
|
|
24
26
|
this.clipElement.replaceChildren(this.audioElement);
|
|
25
27
|
}
|
|
26
28
|
/**
|
|
@@ -56,7 +58,7 @@ export class AudioManager extends ClipManager {
|
|
|
56
58
|
// this.videoElement.src will be a fully qualified URL
|
|
57
59
|
const assetURL = this.constructAssetURL(this._state.file);
|
|
58
60
|
if (!this.audioElement.src.includes(assetURL)) {
|
|
59
|
-
this.
|
|
61
|
+
this.updateAudioElement();
|
|
60
62
|
}
|
|
61
63
|
if (this.audioElement.volume !== volume) {
|
|
62
64
|
this.audioElement.volume = volume;
|
|
@@ -106,7 +108,6 @@ export class AudioManager extends ClipManager {
|
|
|
106
108
|
}
|
|
107
109
|
destroy() {
|
|
108
110
|
if (this.audioElement) {
|
|
109
|
-
this.audioElement.src = '';
|
|
110
111
|
this.audioElement.remove();
|
|
111
112
|
}
|
|
112
113
|
}
|
|
@@ -38,6 +38,9 @@ export class ClipManager {
|
|
|
38
38
|
}
|
|
39
39
|
_state;
|
|
40
40
|
setState(newState) {
|
|
41
|
+
if (this._state.file !== newState.file) {
|
|
42
|
+
throw new Error(`Cannot change from ${this._state.file} to ${newState.file}. Create a new clip instead.`);
|
|
43
|
+
}
|
|
41
44
|
this._state = newState;
|
|
42
45
|
clearTimeout(this.timeout);
|
|
43
46
|
this.loop();
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { MediaClientConfig } from '../types/CogsClientMessage';
|
|
2
|
+
export declare class MediaPreloader {
|
|
3
|
+
private _state;
|
|
4
|
+
private _elements;
|
|
5
|
+
private _constructAssetURL;
|
|
6
|
+
constructor(constructAssetURL: (file: string) => string, testState?: MediaClientConfig['files']);
|
|
7
|
+
get state(): {
|
|
8
|
+
[x: string]: import("../types/CogsClientMessage").Media;
|
|
9
|
+
};
|
|
10
|
+
setState(newState: MediaClientConfig['files']): void;
|
|
11
|
+
private update;
|
|
12
|
+
getElement(file: string, type: 'audio' | 'video'): HTMLAudioElement;
|
|
13
|
+
releaseElement(resource: string | HTMLMediaElement): void;
|
|
14
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
export class MediaPreloader {
|
|
2
|
+
_state;
|
|
3
|
+
_elements = {};
|
|
4
|
+
_constructAssetURL;
|
|
5
|
+
constructor(constructAssetURL, testState = {}) {
|
|
6
|
+
this._constructAssetURL = constructAssetURL;
|
|
7
|
+
this._state = testState;
|
|
8
|
+
}
|
|
9
|
+
get state() {
|
|
10
|
+
return { ...this._state };
|
|
11
|
+
}
|
|
12
|
+
setState(newState) {
|
|
13
|
+
this._state = newState;
|
|
14
|
+
this.update();
|
|
15
|
+
}
|
|
16
|
+
update() {
|
|
17
|
+
// Clean up previous elements
|
|
18
|
+
for (const [filename, media] of Object.entries(this._elements)) {
|
|
19
|
+
if (!(filename in this._state)) {
|
|
20
|
+
media.element.src = '';
|
|
21
|
+
delete this._elements[filename];
|
|
22
|
+
}
|
|
23
|
+
media.inUse = media.element.isConnected;
|
|
24
|
+
}
|
|
25
|
+
for (const [filename, fileConfig] of Object.entries(this._state)) {
|
|
26
|
+
if (filename in this._elements) {
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
// Create new elements
|
|
30
|
+
let preloadAttr;
|
|
31
|
+
if (fileConfig.preload === true) {
|
|
32
|
+
preloadAttr = 'auto';
|
|
33
|
+
}
|
|
34
|
+
else if (fileConfig.preload === false) {
|
|
35
|
+
preloadAttr = 'none';
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
preloadAttr = fileConfig.preload;
|
|
39
|
+
}
|
|
40
|
+
switch (fileConfig.type) {
|
|
41
|
+
case 'audio': {
|
|
42
|
+
const element = document.createElement('audio');
|
|
43
|
+
element.src = this._constructAssetURL(filename);
|
|
44
|
+
element.preload = preloadAttr;
|
|
45
|
+
this._elements[filename] = { element, inUse: false, type: 'audio' };
|
|
46
|
+
break;
|
|
47
|
+
}
|
|
48
|
+
case 'video': {
|
|
49
|
+
const element = document.createElement('video');
|
|
50
|
+
element.src = this._constructAssetURL(filename);
|
|
51
|
+
element.preload = preloadAttr;
|
|
52
|
+
this._elements[filename] = { element, inUse: false, type: 'video' };
|
|
53
|
+
break;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
getElement(file, type) {
|
|
59
|
+
const media = this._elements[file];
|
|
60
|
+
if (media && media.inUse === false) {
|
|
61
|
+
media.inUse = true;
|
|
62
|
+
return media.element;
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
const element = document.createElement(type);
|
|
66
|
+
element.src = this._constructAssetURL(file);
|
|
67
|
+
this._elements[file] = { element, type, inUse: true };
|
|
68
|
+
return element;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
releaseElement(resource) {
|
|
72
|
+
if (typeof resource === 'string') {
|
|
73
|
+
const media = this._elements[resource];
|
|
74
|
+
if (media) {
|
|
75
|
+
media.inUse = false;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
Object.entries(this._elements).forEach(([file, media]) => {
|
|
80
|
+
if (media.element === resource) {
|
|
81
|
+
delete this._elements[file];
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { MediaSurfaceState } from '../types/MediaSchema';
|
|
2
|
+
import { MediaPreloader } from './MediaPreloader';
|
|
2
3
|
export declare const DATA_CLIP_ID = "data-clip-id";
|
|
3
4
|
/**
|
|
4
5
|
* The SurfaceManager will receive state updates and:
|
|
@@ -7,11 +8,12 @@ export declare const DATA_CLIP_ID = "data-clip-id";
|
|
|
7
8
|
*/
|
|
8
9
|
export declare class SurfaceManager {
|
|
9
10
|
private constructAssetUrl;
|
|
11
|
+
private mediaPreloader;
|
|
10
12
|
private _state;
|
|
11
13
|
setState(newState: MediaSurfaceState): void;
|
|
12
14
|
private _element;
|
|
13
15
|
get element(): HTMLDivElement;
|
|
14
16
|
private resources;
|
|
15
|
-
constructor(constructAssetUrl: (file: string) => string,
|
|
17
|
+
constructor(constructAssetUrl: (file: string) => string, initialState?: MediaSurfaceState, mediaPreloader?: MediaPreloader);
|
|
16
18
|
update(): void;
|
|
17
19
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { ImageManager } from './ImageManager';
|
|
2
2
|
import { VideoManager } from './VideoManager';
|
|
3
3
|
import { AudioManager } from './AudioManager';
|
|
4
|
+
import { MediaPreloader } from './MediaPreloader';
|
|
4
5
|
export const DATA_CLIP_ID = 'data-clip-id';
|
|
5
6
|
/**
|
|
6
7
|
* The SurfaceManager will receive state updates and:
|
|
@@ -9,6 +10,7 @@ export const DATA_CLIP_ID = 'data-clip-id';
|
|
|
9
10
|
*/
|
|
10
11
|
export class SurfaceManager {
|
|
11
12
|
constructAssetUrl;
|
|
13
|
+
mediaPreloader;
|
|
12
14
|
_state = {};
|
|
13
15
|
setState(newState) {
|
|
14
16
|
this._state = newState;
|
|
@@ -19,13 +21,14 @@ export class SurfaceManager {
|
|
|
19
21
|
return this._element;
|
|
20
22
|
}
|
|
21
23
|
resources = {};
|
|
22
|
-
constructor(constructAssetUrl,
|
|
24
|
+
constructor(constructAssetUrl, initialState = {}, mediaPreloader = new MediaPreloader(constructAssetUrl)) {
|
|
23
25
|
this.constructAssetUrl = constructAssetUrl;
|
|
26
|
+
this.mediaPreloader = mediaPreloader;
|
|
24
27
|
this._element = document.createElement('div');
|
|
25
28
|
this._element.className = 'surface-manager';
|
|
26
29
|
this._element.style.width = '100%';
|
|
27
30
|
this._element.style.height = '100%';
|
|
28
|
-
this._state =
|
|
31
|
+
this._state = initialState || {};
|
|
29
32
|
this.update();
|
|
30
33
|
}
|
|
31
34
|
update() {
|
|
@@ -68,10 +71,10 @@ export class SurfaceManager {
|
|
|
68
71
|
resource.manager = new ImageManager(this._element, resource.element, clip, this.constructAssetUrl);
|
|
69
72
|
break;
|
|
70
73
|
case 'audio':
|
|
71
|
-
resource.manager = new AudioManager(this._element, resource.element, clip, this.constructAssetUrl);
|
|
74
|
+
resource.manager = new AudioManager(this._element, resource.element, clip, this.constructAssetUrl, this.mediaPreloader);
|
|
72
75
|
break;
|
|
73
76
|
case 'video':
|
|
74
|
-
resource.manager = new VideoManager(this._element, resource.element, clip, this.constructAssetUrl);
|
|
77
|
+
resource.manager = new VideoManager(this._element, resource.element, clip, this.constructAssetUrl, this.mediaPreloader);
|
|
75
78
|
break;
|
|
76
79
|
}
|
|
77
80
|
}
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { VideoState } from '../types/MediaSchema';
|
|
2
2
|
import { ClipManager } from './ClipManager';
|
|
3
|
+
import { MediaPreloader } from './MediaPreloader';
|
|
3
4
|
export declare class VideoManager extends ClipManager<VideoState> {
|
|
5
|
+
private mediaPreloader;
|
|
4
6
|
private videoElement?;
|
|
5
7
|
private isSeeking;
|
|
6
8
|
private timeToIntercept;
|
|
7
|
-
constructor(surfaceElement: HTMLElement, clipElement: HTMLElement, state: VideoState, constructAssetURL: (file: string) => string);
|
|
9
|
+
constructor(surfaceElement: HTMLElement, clipElement: HTMLElement, state: VideoState, constructAssetURL: (file: string) => string, mediaPreloader: MediaPreloader);
|
|
8
10
|
private updateVideoElement;
|
|
9
11
|
private get videoDuration();
|
|
10
12
|
/**
|
|
@@ -29,18 +29,20 @@ function isLooping(state, time, duration) {
|
|
|
29
29
|
return Math.abs(timeRemaining - timeUntilKeyframe) <= LOOPING_EPSILON_MS;
|
|
30
30
|
}
|
|
31
31
|
export class VideoManager extends ClipManager {
|
|
32
|
+
mediaPreloader;
|
|
32
33
|
videoElement;
|
|
33
34
|
// We seek to another part of the video and do nothing until we get there
|
|
34
35
|
isSeeking = false;
|
|
35
36
|
// We change playbackRate to intercept the server time of the video and don't change course until we intercept
|
|
36
37
|
timeToIntercept = undefined;
|
|
37
|
-
constructor(surfaceElement, clipElement, state, constructAssetURL) {
|
|
38
|
+
constructor(surfaceElement, clipElement, state, constructAssetURL, mediaPreloader) {
|
|
38
39
|
super(surfaceElement, clipElement, state, constructAssetURL);
|
|
40
|
+
this.mediaPreloader = mediaPreloader;
|
|
39
41
|
this.clipElement = clipElement;
|
|
40
42
|
}
|
|
41
43
|
updateVideoElement() {
|
|
42
|
-
this.
|
|
43
|
-
this.videoElement =
|
|
44
|
+
const element = this.mediaPreloader.getElement(this._state.file, 'video');
|
|
45
|
+
this.videoElement = element;
|
|
44
46
|
this.clipElement.replaceChildren(this.videoElement);
|
|
45
47
|
this.videoElement.style.position = 'absolute';
|
|
46
48
|
this.videoElement.style.width = '100%';
|
|
@@ -88,7 +90,7 @@ export class VideoManager extends ClipManager {
|
|
|
88
90
|
// this.videoElement.src will be a fully qualified URL
|
|
89
91
|
const assetURL = this.constructAssetURL(this._state.file);
|
|
90
92
|
if (!this.videoElement.src.includes(assetURL)) {
|
|
91
|
-
this.
|
|
93
|
+
this.updateVideoElement();
|
|
92
94
|
}
|
|
93
95
|
if (this.videoElement.style.objectFit !== this._state.fit) {
|
|
94
96
|
this.videoElement.style.objectFit = this._state.fit;
|
|
@@ -206,7 +208,7 @@ export class VideoManager extends ClipManager {
|
|
|
206
208
|
}
|
|
207
209
|
destroy() {
|
|
208
210
|
if (this.videoElement) {
|
|
209
|
-
this.videoElement
|
|
211
|
+
this.mediaPreloader.releaseElement(this.videoElement);
|
|
210
212
|
this.videoElement.remove();
|
|
211
213
|
}
|
|
212
214
|
}
|
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.8",
|
|
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.8",
|
|
41
41
|
"howler": "clockwork-dog/howler.js#fix-looping-clips",
|
|
42
42
|
"reconnecting-websocket": "^4.4.0",
|
|
43
43
|
"zod": "^4.1.13"
|