@clockworkdog/cogs-client 3.0.0-alpha.10 → 3.0.0-alpha.13

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.
@@ -8,8 +8,9 @@ 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;
@@ -7,11 +7,13 @@ export class MediaClipManager {
7
7
  surfaceElement;
8
8
  clipElement;
9
9
  constructAssetURL;
10
+ getAudioOutput;
10
11
  mediaPreloader;
11
- constructor(surfaceElement, clipElement, state, constructAssetURL, mediaPreloader) {
12
+ constructor(surfaceElement, clipElement, state, constructAssetURL, getAudioOutput, mediaPreloader) {
12
13
  this.surfaceElement = surfaceElement;
13
14
  this.clipElement = clipElement;
14
15
  this.constructAssetURL = constructAssetURL;
16
+ this.getAudioOutput = getAudioOutput;
15
17
  this.mediaPreloader = mediaPreloader;
16
18
  this._state = state;
17
19
  // Allow the class to be constructed, then call the loop
@@ -36,8 +38,6 @@ export class MediaClipManager {
36
38
  _state;
37
39
  setState(newState) {
38
40
  this._state = newState;
39
- clearTimeout(this.timeout);
40
- this.loop();
41
41
  }
42
42
  timeout;
43
43
  loop = async () => {
@@ -52,24 +52,36 @@ export class MediaClipManager {
52
52
  }
53
53
  export function assertElement(mediaElement, parentElement, clip, constructAssetURL, preloader) {
54
54
  let element;
55
+ const assetURL = constructAssetURL(clip.file);
55
56
  switch (clip.type) {
56
57
  case 'image':
57
58
  {
58
59
  element = mediaElement instanceof HTMLImageElement ? mediaElement : document.createElement('img');
59
- const assetURL = constructAssetURL(clip.file);
60
60
  if (!element.src.includes(assetURL)) {
61
61
  element.src = assetURL;
62
62
  }
63
63
  }
64
64
  break;
65
65
  case 'audio':
66
- element = mediaElement instanceof HTMLAudioElement ? mediaElement : preloader.getElement(clip.file, clip.type);
66
+ if (mediaElement instanceof HTMLAudioElement && mediaElement.src.includes(assetURL)) {
67
+ element = mediaElement;
68
+ }
69
+ else {
70
+ element = preloader.getElement(clip.file, clip.type);
71
+ }
67
72
  break;
68
73
  case 'video':
69
- element = mediaElement instanceof HTMLVideoElement ? mediaElement : preloader.getElement(clip.file, clip.type);
74
+ if (mediaElement instanceof HTMLVideoElement && mediaElement.src.includes(assetURL)) {
75
+ element = mediaElement;
76
+ }
77
+ else {
78
+ element = preloader.getElement(clip.file, clip.type);
79
+ }
70
80
  break;
71
81
  }
72
- parentElement.replaceChildren(element);
82
+ if (parentElement.children.length !== 1 || parentElement.childNodes[0] !== element) {
83
+ parentElement.replaceChildren(element);
84
+ }
73
85
  element.style.position = 'absolute';
74
86
  element.style.width = '100%';
75
87
  element.style.height = '100%';
@@ -93,9 +105,15 @@ export function assertAudialProperties(mediaElement, properties, sinkId) {
93
105
  mediaElement.volume = properties.volume;
94
106
  }
95
107
  if (mediaElement.sinkId !== sinkId) {
96
- mediaElement.setSinkId(sinkId).catch(() => {
108
+ try {
109
+ mediaElement.setSinkId(sinkId).catch(() => {
110
+ /* Do nothing, will be tried in next loop */
111
+ });
112
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
113
+ }
114
+ catch (_) {
97
115
  /* Do nothing, will be tried in next loop */
98
- });
116
+ }
99
117
  }
100
118
  }
101
119
  const OUTER_TARGET_SYNC_THRESHOLD_MS = 50; // When outside of this range we attempt to sync playback
@@ -217,7 +235,8 @@ export class AudioManager extends MediaClipManager {
217
235
  }
218
236
  if (!currentState || !this.audioElement)
219
237
  return;
220
- assertAudialProperties(this.audioElement, currentState, this._state.audioOutput);
238
+ const sinkId = this.getAudioOutput(this._state.audioOutput);
239
+ assertAudialProperties(this.audioElement, currentState, sinkId);
221
240
  const nextSyncState = assertTemporalProperties(this.audioElement, currentState, this._state.keyframes, this.syncState);
222
241
  if (this.syncState.state !== 'seeking' && nextSyncState.state === 'seeking') {
223
242
  this.audioElement.addEventListener('seeked', () => {
@@ -244,8 +263,9 @@ export class VideoManager extends MediaClipManager {
244
263
  }
245
264
  if (!currentState || !this.videoElement)
246
265
  return;
266
+ const sinkId = this.getAudioOutput(this._state.audioOutput);
247
267
  assertVisualProperties(this.videoElement, currentState, this._state.fit);
248
- assertAudialProperties(this.videoElement, currentState, this._state.audioOutput);
268
+ assertAudialProperties(this.videoElement, currentState, sinkId);
249
269
  const nextSyncState = assertTemporalProperties(this.videoElement, currentState, this._state.keyframes, this.syncState);
250
270
  if (this.syncState.state !== 'seeking' && nextSyncState.state === 'seeking') {
251
271
  this.videoElement.addEventListener('seeked', () => {
@@ -17,7 +17,12 @@ export class MediaPreloader {
17
17
  // Clean up previous elements
18
18
  for (const [filename, media] of Object.entries(this._elements)) {
19
19
  if (!(filename in this._state)) {
20
+ if (media.inUse) {
21
+ console.warn(`Failed to clean up element ${media.element.src}`);
22
+ continue;
23
+ }
20
24
  media.element.src = '';
25
+ media.element.load();
21
26
  delete this._elements[filename];
22
27
  }
23
28
  media.inUse = media.element.isConnected;
@@ -64,7 +69,9 @@ export class MediaPreloader {
64
69
  else {
65
70
  const element = document.createElement(type);
66
71
  element.src = this._constructAssetURL(file);
67
- this._elements[file] = { element, type, inUse: true };
72
+ if (type === 'video') {
73
+ this._elements[file] = { element, type, inUse: true };
74
+ }
68
75
  return element;
69
76
  }
70
77
  }
@@ -8,12 +8,13 @@ export declare const DATA_CLIP_ID = "data-clip-id";
8
8
  */
9
9
  export declare class SurfaceManager {
10
10
  private constructAssetUrl;
11
+ private getAudioOutput;
11
12
  private mediaPreloader;
12
13
  private _state;
13
14
  setState(newState: MediaSurfaceState): void;
14
15
  private _element;
15
16
  get element(): HTMLDivElement;
16
17
  private resources;
17
- constructor(constructAssetUrl: (file: string) => string, testState?: MediaSurfaceState, mediaPreloader?: MediaPreloader);
18
+ constructor(constructAssetUrl: (file: string) => string, getAudioOutput: (outputLabel: string) => string, testState?: MediaSurfaceState, mediaPreloader?: MediaPreloader);
18
19
  update(): void;
19
20
  }
@@ -8,6 +8,7 @@ export const DATA_CLIP_ID = 'data-clip-id';
8
8
  */
9
9
  export class SurfaceManager {
10
10
  constructAssetUrl;
11
+ getAudioOutput;
11
12
  mediaPreloader;
12
13
  _state = {};
13
14
  setState(newState) {
@@ -19,8 +20,9 @@ export class SurfaceManager {
19
20
  return this._element;
20
21
  }
21
22
  resources = {};
22
- constructor(constructAssetUrl, testState, mediaPreloader = new MediaPreloader(constructAssetUrl)) {
23
+ constructor(constructAssetUrl, getAudioOutput, testState, mediaPreloader = new MediaPreloader(constructAssetUrl)) {
23
24
  this.constructAssetUrl = constructAssetUrl;
25
+ this.getAudioOutput = getAudioOutput;
24
26
  this.mediaPreloader = mediaPreloader;
25
27
  this._element = document.createElement('div');
26
28
  this._element.className = 'surface-manager';
@@ -66,13 +68,13 @@ export class SurfaceManager {
66
68
  if (!resource.manager) {
67
69
  switch (clip.type) {
68
70
  case 'image':
69
- resource.manager = new ImageManager(this._element, resource.element, clip, this.constructAssetUrl, this.mediaPreloader);
71
+ resource.manager = new ImageManager(this._element, resource.element, clip, this.constructAssetUrl, this.getAudioOutput, this.mediaPreloader);
70
72
  break;
71
73
  case 'audio':
72
- resource.manager = new AudioManager(this._element, resource.element, clip, this.constructAssetUrl, this.mediaPreloader);
74
+ resource.manager = new AudioManager(this._element, resource.element, clip, this.constructAssetUrl, this.getAudioOutput, this.mediaPreloader);
73
75
  break;
74
76
  case 'video':
75
- resource.manager = new VideoManager(this._element, resource.element, clip, this.constructAssetUrl, this.mediaPreloader);
77
+ resource.manager = new VideoManager(this._element, resource.element, clip, this.constructAssetUrl, this.getAudioOutput, this.mediaPreloader);
76
78
  break;
77
79
  }
78
80
  }
@@ -1,5 +1,10 @@
1
1
  import { defaultAudioOptions, defaultImageOptions, defaultVideoOptions } from '../types/MediaSchema';
2
2
  export function getStateAtTime(state, time) {
3
+ //If there are any null keyframes the clip has been terminated
4
+ const nullKeyframes = state.keyframes.filter((kf) => kf[1] === null);
5
+ if (nullKeyframes.some((kf) => kf[0] <= time)) {
6
+ return undefined;
7
+ }
3
8
  switch (state.type) {
4
9
  case 'image': {
5
10
  const firstTimestamp = state.keyframes[0][0];
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.10",
6
+ "version": "3.0.0-alpha.13",
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.10",
40
+ "@clockworkdog/timesync": "^3.0.0-alpha.13",
41
41
  "howler": "clockwork-dog/howler.js#fix-looping-clips",
42
42
  "reconnecting-websocket": "^4.4.0",
43
43
  "zod": "^4.1.13"