libpag 4.5.42 → 4.5.47

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/lib/libpag.wasm CHANGED
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "libpag",
3
- "version": "4.5.42",
3
+ "version": "4.5.47",
4
4
  "description": "Portable Animated Graphics",
5
5
  "main": "lib/libpag.cjs.js",
6
6
  "module": "lib/libpag.esm.js",
@@ -1 +1 @@
1
- 0e2eb62437d23295e4275f5d02d62815
1
+ 94f0d9f82511857dbdad5cdd092e89ae
package/src/.pag.wasm.md5 CHANGED
@@ -1 +1 @@
1
- 0b03781fb8f26e18996b06316db244fd
1
+ 39a2addcc9a57c9119f9f44de6d33114
@@ -18,8 +18,8 @@ export class GlobalCanvas {
18
18
  this._canvas.width = this.width;
19
19
  this._canvas.height = this.height;
20
20
 
21
- const gl = this._canvas.getContext('webgl', WEBGL_CONTEXT_ATTRIBUTES) as WebGLRenderingContext;
22
- if (!gl) throw new Error('Canvas context is not WebGL!');
21
+ const gl = this._canvas.getContext('webgl2', WEBGL_CONTEXT_ATTRIBUTES) as WebGL2RenderingContext;
22
+ if (!gl) throw new Error('Canvas context is not WebGL2!');
23
23
  this._glContext = BackendContext.from(gl);
24
24
  }
25
25
  this.retainCount += 1;
@@ -18,11 +18,11 @@ export class RenderCanvas {
18
18
 
19
19
  public constructor(canvas: HTMLCanvasElement | OffscreenCanvas, contextAttributes?: WebGLContextAttributes) {
20
20
  this._canvas = canvas;
21
- const gl = canvas.getContext('webgl', {
21
+ const gl = canvas.getContext('webgl2', {
22
22
  ...WEBGL_CONTEXT_ATTRIBUTES,
23
23
  ...contextAttributes,
24
- }) as WebGLRenderingContext;
25
- if (!gl) throw new Error('Canvas context is not WebGL!');
24
+ }) as WebGL2RenderingContext;
25
+ if (!gl) throw new Error('Canvas context is not WebGL2!');
26
26
  this._glContext = BackendContext.from(gl);
27
27
  }
28
28
 
@@ -77,6 +77,7 @@ export class VideoReader {
77
77
  private bitmapCtx: OffscreenCanvasRenderingContext2D | null = null;
78
78
  private currentFrame = -1;
79
79
  private targetFrame = -1;
80
+ private visibilityHandle: (() => void) | null = null;
80
81
 
81
82
  public constructor(
82
83
  source: Uint8Array | HTMLVideoElement,
@@ -118,10 +119,10 @@ export class VideoReader {
118
119
  }
119
120
 
120
121
  public async prepare(targetFrame: number, playbackRate: number): Promise<void> {
121
- if (targetFrame === this.currentFrame) {
122
+ if (this.isDestroyed || targetFrame === this.currentFrame) {
122
123
  return;
123
124
  }
124
- const promise = new Promise<void>(async (resolve, reject) => {
125
+ const promise = new Promise<void>(async (resolve) => {
125
126
  this.setError(null); // reset error
126
127
  this.isSought = false; // reset seek status
127
128
  const {currentTime} = this.videoEl!;
@@ -137,12 +138,14 @@ export class VideoReader {
137
138
  } catch (e) {
138
139
  this.setError(e);
139
140
  this.currentFrame = targetFrame;
140
- reject(e);
141
+ resolve();
141
142
  return;
142
143
  }
143
144
  await new Promise<void>((resolveInner) => {
144
145
  requestAnimationFrame(() => {
145
- this.pause();
146
+ if (!this.isDestroyed) {
147
+ this.pause();
148
+ }
146
149
  resolveInner();
147
150
  });
148
151
  });
@@ -154,7 +157,7 @@ export class VideoReader {
154
157
  // Static frame
155
158
  await this.seek(targetTime, false);
156
159
  this.currentFrame = targetFrame;
157
- resolve(); // Ensure promise resolves
160
+ resolve();
158
161
  return;
159
162
  } else if ((Math.abs(currentTime - targetTime) < (1 / this.frameRate) * VIDEO_DECODE_WAIT_FRAME) && !this.videoEl!.paused) {
160
163
  // Within tolerable frame rate deviation
@@ -168,18 +171,22 @@ export class VideoReader {
168
171
  }
169
172
  }
170
173
 
174
+ if (this.isDestroyed || !this.videoEl) {
175
+ resolve();
176
+ return;
177
+ }
171
178
  const targetPlaybackRate = Math.min(Math.max(playbackRate, VIDEO_PLAYBACK_RATE_MIN), VIDEO_PLAYBACK_RATE_MAX);
172
- if (!this.disablePlaybackRate && this.videoEl!.playbackRate !== targetPlaybackRate) {
173
- this.videoEl!.playbackRate = targetPlaybackRate;
179
+ if (!this.disablePlaybackRate && this.videoEl.playbackRate !== targetPlaybackRate) {
180
+ this.videoEl.playbackRate = targetPlaybackRate;
174
181
  }
175
182
 
176
- if (this.isPlaying && this.videoEl!.paused) {
183
+ if (this.isPlaying && this.videoEl.paused) {
177
184
  try {
178
185
  await this.play();
179
186
  } catch (e) {
180
187
  this.setError(e);
181
188
  this.currentFrame = targetFrame;
182
- reject(e);
189
+ resolve();
183
190
  return;
184
191
  }
185
192
  }
@@ -203,13 +210,19 @@ export class VideoReader {
203
210
  await getWechatNetwork();
204
211
  }
205
212
  if (document.visibilityState !== 'visible') {
206
- const visibilityHandle = () => {
213
+ // Page is hidden, defer video play until visible
214
+ this.clearVisibilityListener();
215
+ this.visibilityHandle = () => {
216
+ if (this.isDestroyed) {
217
+ this.clearVisibilityListener();
218
+ return;
219
+ }
207
220
  if (document.visibilityState === 'visible') {
208
- if (this.videoEl) this.videoEl.play();
209
- window.removeEventListener('visibilitychange', visibilityHandle);
221
+ if (this.videoEl) this.videoEl.play().catch((e) => { this.setError(e); });
222
+ this.clearVisibilityListener();
210
223
  }
211
224
  };
212
- window.addEventListener('visibilitychange', visibilityHandle);
225
+ window.addEventListener('visibilitychange', this.visibilityHandle);
213
226
  throw new Error('The play() request was interrupted because the document was hidden!');
214
227
  }
215
228
  await this.videoEl?.play();
@@ -233,16 +246,19 @@ export class VideoReader {
233
246
  }
234
247
 
235
248
  public onDestroy() {
249
+ this.isDestroyed = true;
236
250
  if (this.player) {
237
251
  this.player.unlinkVideoReader(this);
238
252
  this.player = null;
239
253
  }
254
+ this.clearVisibilityListener();
240
255
  removeAllListeners(this.videoEl!, 'playing');
241
256
  removeAllListeners(this.videoEl!, 'timeupdate');
257
+ removeAllListeners(this.videoEl!, 'seeked');
258
+ removeAllListeners(this.videoEl!, 'canplay');
242
259
  this.videoEl = null;
243
260
  this.bitmapCanvas = null;
244
261
  this.bitmapCtx = null;
245
- this.isDestroyed = true;
246
262
  }
247
263
 
248
264
  private seek(targetTime: number, play = true) {
@@ -253,34 +269,47 @@ export class VideoReader {
253
269
  }
254
270
 
255
271
  const onSeeked = () => {
256
- removeListener(this.videoEl!, 'seeked', onSeeked);
272
+ if (!this.videoEl || this.isDestroyed) {
273
+ clearTimeout(seekTimeout);
274
+ resolve();
275
+ return;
276
+ }
277
+ removeListener(this.videoEl, 'seeked', onSeeked);
257
278
  clearTimeout(seekTimeout);
258
279
  if (play) {
259
280
  // After seeking, the video might still be in 'ended' state
260
281
  // Reset it by setting currentTime to itself to clear the ended flag
261
- if (this.videoEl!.ended) {
262
- this.videoEl!.currentTime = this.videoEl!.currentTime;
282
+ if (this.videoEl.ended) {
283
+ this.videoEl.currentTime = this.videoEl.currentTime;
263
284
  }
264
- this.videoEl?.play().catch((e) => {
285
+ this.videoEl.play().catch((e) => {
265
286
  this.setError(e);
266
287
  });
267
- } else if (!play && !this.videoEl!.paused) {
268
- this.videoEl?.pause();
288
+ } else if (!play && !this.videoEl.paused) {
289
+ this.videoEl.pause();
269
290
  }
270
291
  resolve();
271
292
  };
272
293
 
273
294
  const onCanPlay = () => {
274
- removeListener(this.videoEl!, 'canplay', onCanPlay);
295
+ if (!this.videoEl || this.isDestroyed) {
296
+ clearTimeout(seekTimeout);
297
+ resolve();
298
+ return;
299
+ }
300
+ removeListener(this.videoEl, 'canplay', onCanPlay);
275
301
  // Now that we have enough data, perform the seek.
276
- this.videoEl!.currentTime = targetTime;
277
- addListener(this.videoEl!, 'seeked', onSeeked);
302
+ this.videoEl.currentTime = targetTime;
303
+ addListener(this.videoEl, 'seeked', onSeeked);
278
304
  };
279
305
 
280
306
  const seekTimeout = setTimeout(() => {
281
- removeListener(this.videoEl!, 'canplay', onCanPlay);
282
- removeListener(this.videoEl!, 'seeked', onSeeked);
283
- reject(new Error('Seek operation timed out.'));
307
+ if (this.videoEl) {
308
+ removeListener(this.videoEl, 'canplay', onCanPlay);
309
+ removeListener(this.videoEl, 'seeked', onSeeked);
310
+ }
311
+ this.setError('Seek operation timed out.');
312
+ resolve();
284
313
  }, (1000 / this.frameRate) * VIDEO_DECODE_SEEK_TIMEOUT_FRAME);
285
314
 
286
315
  // Check if we need to wait for 'canplay' event before seeking.
@@ -298,6 +327,13 @@ export class VideoReader {
298
327
  this.error = e;
299
328
  }
300
329
 
330
+ private clearVisibilityListener() {
331
+ if (this.visibilityHandle) {
332
+ window.removeEventListener('visibilitychange', this.visibilityHandle);
333
+ this.visibilityHandle = null;
334
+ }
335
+ }
336
+
301
337
  private linkPlayer(player: PAGPlayer | null) {
302
338
  this.player = player;
303
339
  if (player) {
package/src/pag-player.ts CHANGED
@@ -64,11 +64,11 @@ export class PAGPlayer {
64
64
  callback(res);
65
65
  return res;
66
66
  }, this.wasmIns);
67
- // Check if any video reader has error.
67
+ // Log video reader errors but don't throw - video decoding failures shouldn't stop playback
68
68
  for (const videoReader of this.videoReaders) {
69
69
  const error = await videoReader.getError();
70
70
  if (error !== null) {
71
- throw error;
71
+ console.warn('[PAGPlayer] VideoReader error:', error);
72
72
  }
73
73
  }
74
74
  return res;
package/src/pag-view.ts CHANGED
@@ -425,7 +425,7 @@ export class PAGView {
425
425
  }
426
426
 
427
427
  protected async flushLoop(force = false) {
428
- if (!this.isPlaying) {
428
+ if (!this.isPlaying || this.isDestroyed) {
429
429
  return;
430
430
  }
431
431
  this.setTimer();
package/src/types.ts CHANGED
@@ -568,4 +568,4 @@ export class VecArray extends Array {
568
568
  throw new Error('This VecArray instance has been deleted.');
569
569
  }
570
570
  }
571
- }
571
+ }
@@ -104,6 +104,7 @@ export class VideoReaderManager {
104
104
  */
105
105
  public async prepareTargetFrame() {
106
106
  for (const id of this.videoIDs) {
107
+ if (this.isDestroyed) return;
107
108
  // Get the target frame index from WASM
108
109
  const targetFrame = this.wasmIns._getTargetFrameByID(id) as number;
109
110
  if (targetFrame < 0) {
@@ -137,4 +138,4 @@ export class VideoReaderManager {
137
138
  this.isDestroyed = true;
138
139
  }
139
140
 
140
- }
141
+ }