visualfries 0.1.5 → 0.1.7

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.
@@ -399,9 +399,10 @@ export class SceneBuilder {
399
399
  this.appManager.destroy();
400
400
  this.domManager.destroy();
401
401
  this.stateManager.destroy();
402
- this.mediaManager.destroy();
403
402
  this.timelineManager.destroy();
404
403
  this.componentsManager.destroy();
404
+ // media manages should be destroyed last
405
+ this.mediaManager.destroy();
405
406
  // Remove the container from the DI container cache
406
407
  removeContainer(this.sceneData.id);
407
408
  }
@@ -50,6 +50,9 @@ export class MediaHook {
50
50
  }
51
51
  }
52
52
  async #autoSeek() {
53
+ if (!this.#mediaElement) {
54
+ return;
55
+ }
53
56
  const target = this.#context.currentComponentTime;
54
57
  const frameDuration = 1 / (this.state.data.settings.fps || 30);
55
58
  if (this.#lastTargetTime !== null &&
@@ -60,6 +63,9 @@ export class MediaHook {
60
63
  await this.#seek(target);
61
64
  }
62
65
  async #seek(time) {
66
+ if (!this.#mediaElement) {
67
+ return;
68
+ }
63
69
  const element = this.#mediaElement;
64
70
  const frameDuration = 1 / (this.state.data.settings.fps || 30);
65
71
  // Use fastSeek for larger jumps when supported
@@ -96,6 +102,9 @@ export class MediaHook {
96
102
  });
97
103
  }
98
104
  #isOutOfSync() {
105
+ if (!this.#mediaElement) {
106
+ return false;
107
+ }
99
108
  // run only once per MAX_LAG_TIME
100
109
  const now = performance.now() / 1000; // convert to seconds
101
110
  if (now - this.#lastSyncCheck < this.#MAX_LAG_TIME) {
@@ -106,11 +115,17 @@ export class MediaHook {
106
115
  return lagTime >= this.#MAX_LAG_TIME;
107
116
  }
108
117
  async #pause(reason) {
118
+ if (!this.#mediaElement) {
119
+ return;
120
+ }
109
121
  if (!this.#mediaElement.paused && !this.#playRequested) {
110
122
  this.#mediaElement.pause();
111
123
  }
112
124
  }
113
125
  async #play() {
126
+ if (!this.#mediaElement) {
127
+ return;
128
+ }
114
129
  if (this.#mediaElement.paused) {
115
130
  this.#playRequested = true;
116
131
  try {
@@ -184,7 +199,7 @@ export class MediaHook {
184
199
  }
185
200
  // Check if component is loading using the StateManager
186
201
  if (this.state.isLoadingComponent(this.#context.contextData.id)) {
187
- if (this.#mediaElement.readyState < 2) {
202
+ if (this.#mediaElement && this.#mediaElement.readyState < 2) {
188
203
  await this.#pause('readyState < 2');
189
204
  return;
190
205
  }
@@ -193,10 +208,10 @@ export class MediaHook {
193
208
  }
194
209
  }
195
210
  // Ensure the media element matches the component's mute and volume state
196
- if (this.#mediaElement.muted != isMuted) {
211
+ if (this.#mediaElement && this.#mediaElement.muted != isMuted) {
197
212
  this.#mediaElement.muted = isMuted;
198
213
  }
199
- if (this.#mediaElement.volume != componentVolume) {
214
+ if (this.#mediaElement && this.#mediaElement.volume != componentVolume) {
200
215
  this.#mediaElement.volume = componentVolume;
201
216
  }
202
217
  const isScenePlaying = this.#context.sceneState.state === 'playing' || this.#context.sceneState.state === 'loading';
@@ -208,7 +223,7 @@ export class MediaHook {
208
223
  }
209
224
  else {
210
225
  // When scene is playing and media is paused, play media
211
- if (this.#mediaElement.paused) {
226
+ if (this.#mediaElement && this.#mediaElement.paused) {
212
227
  await this.#play();
213
228
  }
214
229
  }
@@ -229,6 +244,16 @@ export class MediaHook {
229
244
  async #handleDestroy() {
230
245
  this.#destroyed = true;
231
246
  this.#lastTargetTime = null;
247
+ // Release the media element back to the MediaManager
248
+ if (this.#mediaElement) {
249
+ const mediaType = this.#context.type === 'VIDEO' ? 'video' : 'audio';
250
+ const source = this.#context.contextData.source;
251
+ if (source && source.url) {
252
+ this.mediaManager.releaseMediaElement(source.url, mediaType);
253
+ }
254
+ this.#context.removeResource(mediaType === 'video' ? 'videoElement' : 'audioElement');
255
+ }
256
+ this.#mediaElement = undefined;
232
257
  }
233
258
  async handle(type, context) {
234
259
  this.#context = context;
@@ -91,7 +91,9 @@ export class MediaSeekingHook {
91
91
  };
92
92
  // Add error event handling
93
93
  media.onerror = () => {
94
- console.error('Media error:', media.error);
94
+ if (media.error && media.error.code !== 4) {
95
+ console.error('Media error:', media.src, media.error);
96
+ }
95
97
  };
96
98
  }
97
99
  }
@@ -100,6 +102,7 @@ export class MediaSeekingHook {
100
102
  await this.#handleSetup();
101
103
  }
102
104
  async #handleDestroy() {
105
+ // Clear media element reference - MediaHook will handle releaseMediaElement
103
106
  this.#mediaElement = undefined;
104
107
  }
105
108
  async #handleUpdate() {
@@ -181,6 +181,7 @@ export class MediaManager {
181
181
  if (type === 'video') {
182
182
  const videoElement = this.videoElements.get(mediaPath);
183
183
  if (videoElement) {
184
+ // https://html.spec.whatwg.org/multipage/media.html#best-practices-for-authors-using-media-elements
184
185
  videoElement.src = '';
185
186
  videoElement.load(); // This frees up memory
186
187
  this.videoElements.delete(mediaPath);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "visualfries",
3
- "version": "0.1.5",
3
+ "version": "0.1.7",
4
4
  "license": "MIT",
5
5
  "author": "ContentFries",
6
6
  "repository": {