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
|
-
|
|
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);
|