@give-tech/ec-player 0.0.1-beta.31 → 0.0.1-beta.33

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.
@@ -6,9 +6,14 @@ import { WASMLoader } from './WASMLoader';
6
6
  export declare class H264Decoder {
7
7
  private wasmModule;
8
8
  private decoderContext;
9
+ private destroyed;
9
10
  constructor(wasmLoader: WASMLoader);
10
11
  init(): Promise<void>;
11
12
  decode(nalUnit: NALUnit): VideoFrame | null;
13
+ /**
14
+ * 销毁解码器,释放 WASM 资源
15
+ */
16
+ destroy(): void;
12
17
  private yuv420pToRgba;
13
18
  }
14
19
  //# sourceMappingURL=H264Decoder.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"H264Decoder.d.ts","sourceRoot":"","sources":["../../src/decoder/H264Decoder.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAA8B,UAAU,EAAE,OAAO,EAAE,MAAM,UAAU,CAAA;AAC/E,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AAEzC,qBAAa,WAAW;IACtB,OAAO,CAAC,UAAU,CAA0B;IAC5C,OAAO,CAAC,cAAc,CAA8B;gBAExC,UAAU,EAAE,UAAU;IAI5B,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAY3B,MAAM,CAAC,OAAO,EAAE,OAAO,GAAG,UAAU,GAAG,IAAI;IA4C3C,OAAO,CAAC,aAAa;CAuCtB"}
1
+ {"version":3,"file":"H264Decoder.d.ts","sourceRoot":"","sources":["../../src/decoder/H264Decoder.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAA8B,UAAU,EAAE,OAAO,EAAE,MAAM,UAAU,CAAA;AAC/E,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AAEzC,qBAAa,WAAW;IACtB,OAAO,CAAC,UAAU,CAA0B;IAC5C,OAAO,CAAC,cAAc,CAA8B;IACpD,OAAO,CAAC,SAAS,CAAQ;gBAEb,UAAU,EAAE,UAAU;IAI5B,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAY3B,MAAM,CAAC,OAAO,EAAE,OAAO,GAAG,UAAU,GAAG,IAAI;IA4C3C;;OAEG;IACH,OAAO,IAAI,IAAI;IAaf,OAAO,CAAC,aAAa;CAuCtB"}
@@ -13,9 +13,14 @@ export declare const HEVC_NAL_TYPE: {
13
13
  export declare class HEVCDecoder {
14
14
  private wasmModule;
15
15
  private decoderContext;
16
+ private destroyed;
16
17
  constructor(wasmLoader: WASMLoader);
17
18
  init(): Promise<void>;
18
19
  decode(nalUnit: NALUnit): VideoFrame | null;
20
+ /**
21
+ * 销毁解码器,释放 WASM 资源
22
+ */
23
+ destroy(): void;
19
24
  private yuv420pToRgba;
20
25
  }
21
26
  //# sourceMappingURL=HEVCDecoder.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"HEVCDecoder.d.ts","sourceRoot":"","sources":["../../src/decoder/HEVCDecoder.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAA8B,UAAU,EAAE,OAAO,EAAE,MAAM,UAAU,CAAA;AAC/E,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AAGzC,eAAO,MAAM,aAAa;;;;;;CAMhB,CAAA;AAEV,qBAAa,WAAW;IACtB,OAAO,CAAC,UAAU,CAA0B;IAC5C,OAAO,CAAC,cAAc,CAA8B;gBAExC,UAAU,EAAE,UAAU;IAI5B,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAe3B,MAAM,CAAC,OAAO,EAAE,OAAO,GAAG,UAAU,GAAG,IAAI;IA4C3C,OAAO,CAAC,aAAa;CAuCtB"}
1
+ {"version":3,"file":"HEVCDecoder.d.ts","sourceRoot":"","sources":["../../src/decoder/HEVCDecoder.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAA8B,UAAU,EAAE,OAAO,EAAE,MAAM,UAAU,CAAA;AAC/E,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AAGzC,eAAO,MAAM,aAAa;;;;;;CAMhB,CAAA;AAEV,qBAAa,WAAW;IACtB,OAAO,CAAC,UAAU,CAA0B;IAC5C,OAAO,CAAC,cAAc,CAA8B;IACpD,OAAO,CAAC,SAAS,CAAQ;gBAEb,UAAU,EAAE,UAAU;IAI5B,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAe3B,MAAM,CAAC,OAAO,EAAE,OAAO,GAAG,UAAU,GAAG,IAAI;IA4C3C;;OAEG;IACH,OAAO,IAAI,IAAI;IAaf,OAAO,CAAC,aAAa;CAuCtB"}
@@ -1,10 +1,39 @@
1
1
  /**
2
- * WASM 加载器
2
+ * WASM 加载器(多模块缓存)
3
+ *
4
+ * 支持同时缓存多个 WASM 模块(如 SIMD 和非 SIMD 版本)
5
+ * 使用懒加载,按需加载所需模块
6
+ * 解决快速切换源时的竞态条件问题
3
7
  */
4
8
  import type { WASMModule } from '../types';
5
9
  export declare class WASMLoader {
6
- private module;
10
+ private localModule;
11
+ private localPath;
12
+ /**
13
+ * 加载 WASM 模块
14
+ */
7
15
  load(wasmPath: string): Promise<WASMModule>;
16
+ private doLoad;
8
17
  getModule(): WASMModule | null;
18
+ /**
19
+ * 预加载 WASM 模块(后台加载,不阻塞)
20
+ */
21
+ static preload(wasmPath: string): void;
22
+ /**
23
+ * 批量预加载
24
+ */
25
+ static preloadAll(wasmPaths: string[]): void;
26
+ /**
27
+ * 检查是否已加载
28
+ */
29
+ static isLoaded(wasmPath?: string): boolean;
30
+ /**
31
+ * 获取已缓存的路径
32
+ */
33
+ static getLoadedPaths(): string[];
34
+ /**
35
+ * 重置(仅用于测试)
36
+ */
37
+ static reset(): void;
9
38
  }
10
39
  //# sourceMappingURL=WASMLoader.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"WASMLoader.d.ts","sourceRoot":"","sources":["../../src/decoder/WASMLoader.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,UAAU,CAAA;AAE1C,qBAAa,UAAU;IACrB,OAAO,CAAC,MAAM,CAA0B;IAElC,IAAI,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC;IAmCjD,SAAS,IAAI,UAAU,GAAG,IAAI;CAG/B"}
1
+ {"version":3,"file":"WASMLoader.d.ts","sourceRoot":"","sources":["../../src/decoder/WASMLoader.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,UAAU,CAAA;AAQ1C,qBAAa,UAAU;IACrB,OAAO,CAAC,WAAW,CAA0B;IAC7C,OAAO,CAAC,SAAS,CAAsB;IAEvC;;OAEG;IACG,IAAI,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC;YAsCnC,MAAM;IAuCpB,SAAS,IAAI,UAAU,GAAG,IAAI;IAI9B;;OAEG;IACH,MAAM,CAAC,OAAO,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI;IAWtC;;OAEG;IACH,MAAM,CAAC,UAAU,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,IAAI;IAI5C;;OAEG;IACH,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO;IAO3C;;OAEG;IACH,MAAM,CAAC,cAAc,IAAI,MAAM,EAAE;IAIjC;;OAEG;IACH,MAAM,CAAC,KAAK,IAAI,IAAI;CAIrB"}
package/dist/index.d.ts CHANGED
@@ -6,6 +6,7 @@
6
6
  */
7
7
  export { EcPlayerCore, type EcPlayerCoreConfig } from './player';
8
8
  export { EnvDetector, type EnvInfo, type EnvCapabilities } from './env';
9
+ export { WASMLoader } from './decoder/WASMLoader';
9
10
  export type { PlayerState as EcPlayerState, PlayerCallbacks as EcPlayerCallbacks } from './types';
10
11
  export { LogLevel, setLogLevel } from './utils';
11
12
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,EAAE,YAAY,EAAE,KAAK,kBAAkB,EAAE,MAAM,UAAU,CAAA;AAGhE,OAAO,EACL,WAAW,EACX,KAAK,OAAO,EACZ,KAAK,eAAe,EACrB,MAAM,OAAO,CAAA;AAGd,YAAY,EACV,WAAW,IAAI,aAAa,EAC5B,eAAe,IAAI,iBAAiB,EACrC,MAAM,SAAS,CAAA;AAGhB,OAAO,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,SAAS,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,EAAE,YAAY,EAAE,KAAK,kBAAkB,EAAE,MAAM,UAAU,CAAA;AAGhE,OAAO,EACL,WAAW,EACX,KAAK,OAAO,EACZ,KAAK,eAAe,EACrB,MAAM,OAAO,CAAA;AAGd,OAAO,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAA;AAGjD,YAAY,EACV,WAAW,IAAI,aAAa,EAC5B,eAAe,IAAI,iBAAiB,EACrC,MAAM,SAAS,CAAA;AAGhB,OAAO,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,SAAS,CAAA"}
package/dist/index.js CHANGED
@@ -1,8 +1,43 @@
1
+ const moduleCache = /* @__PURE__ */ new Map();
2
+ const loadingPromises = /* @__PURE__ */ new Map();
1
3
  class WASMLoader {
2
4
  constructor() {
3
- this.module = null;
5
+ this.localModule = null;
6
+ this.localPath = null;
4
7
  }
8
+ /**
9
+ * 加载 WASM 模块
10
+ */
5
11
  async load(wasmPath) {
12
+ if (this.localModule && this.localPath === wasmPath) {
13
+ return this.localModule;
14
+ }
15
+ const cachedModule = moduleCache.get(wasmPath);
16
+ if (cachedModule) {
17
+ this.localModule = cachedModule;
18
+ this.localPath = wasmPath;
19
+ return cachedModule;
20
+ }
21
+ const loadingPromise = loadingPromises.get(wasmPath);
22
+ if (loadingPromise) {
23
+ const module = await loadingPromise;
24
+ this.localModule = module;
25
+ this.localPath = wasmPath;
26
+ return module;
27
+ }
28
+ const loadPromise = this.doLoad(wasmPath);
29
+ loadingPromises.set(wasmPath, loadPromise);
30
+ try {
31
+ const module = await loadPromise;
32
+ this.localModule = module;
33
+ this.localPath = wasmPath;
34
+ return module;
35
+ } catch (error) {
36
+ loadingPromises.delete(wasmPath);
37
+ throw error;
38
+ }
39
+ }
40
+ async doLoad(wasmPath) {
6
41
  return new Promise((resolve, reject) => {
7
42
  const script = document.createElement("script");
8
43
  script.src = wasmPath;
@@ -14,30 +49,75 @@ class WASMLoader {
14
49
  reject(new Error("createFFmpegDecoder not found"));
15
50
  return;
16
51
  }
17
- this.module = await createFFmpegDecoder();
18
- if (typeof this.module?._create_decoder !== "function") {
52
+ const module = await createFFmpegDecoder();
53
+ if (typeof module?._create_decoder !== "function") {
19
54
  reject(new Error("Module initialization failed"));
20
55
  return;
21
56
  }
22
- resolve(this.module);
57
+ moduleCache.set(wasmPath, module);
58
+ loadingPromises.delete(wasmPath);
59
+ resolve(module);
23
60
  } catch (e) {
61
+ loadingPromises.delete(wasmPath);
24
62
  reject(new Error(`Failed to initialize decoder: ${e.message}`));
25
63
  }
26
64
  };
27
65
  script.onerror = () => {
66
+ loadingPromises.delete(wasmPath);
28
67
  reject(new Error(`Failed to load script: ${wasmPath}`));
29
68
  };
30
69
  document.head.appendChild(script);
31
70
  });
32
71
  }
33
72
  getModule() {
34
- return this.module;
73
+ return this.localModule;
74
+ }
75
+ /**
76
+ * 预加载 WASM 模块(后台加载,不阻塞)
77
+ */
78
+ static preload(wasmPath) {
79
+ if (moduleCache.has(wasmPath) || loadingPromises.has(wasmPath)) {
80
+ return;
81
+ }
82
+ const loader = new WASMLoader();
83
+ loader.load(wasmPath).catch((err) => {
84
+ console.warn(`[WASMLoader] Preload failed for ${wasmPath}:`, err.message);
85
+ });
86
+ }
87
+ /**
88
+ * 批量预加载
89
+ */
90
+ static preloadAll(wasmPaths) {
91
+ wasmPaths.forEach((path) => this.preload(path));
92
+ }
93
+ /**
94
+ * 检查是否已加载
95
+ */
96
+ static isLoaded(wasmPath) {
97
+ if (wasmPath) {
98
+ return moduleCache.has(wasmPath);
99
+ }
100
+ return moduleCache.size > 0;
101
+ }
102
+ /**
103
+ * 获取已缓存的路径
104
+ */
105
+ static getLoadedPaths() {
106
+ return Array.from(moduleCache.keys());
107
+ }
108
+ /**
109
+ * 重置(仅用于测试)
110
+ */
111
+ static reset() {
112
+ moduleCache.clear();
113
+ loadingPromises.clear();
35
114
  }
36
115
  }
37
116
  class H264Decoder {
38
117
  constructor(wasmLoader) {
39
118
  this.wasmModule = null;
40
119
  this.decoderContext = null;
120
+ this.destroyed = false;
41
121
  this.wasmModule = wasmLoader.getModule();
42
122
  }
43
123
  async init() {
@@ -50,7 +130,7 @@ class H264Decoder {
50
130
  }
51
131
  }
52
132
  decode(nalUnit) {
53
- if (this.decoderContext === null || !this.wasmModule) {
133
+ if (this.destroyed || this.decoderContext === null || !this.wasmModule) {
54
134
  return null;
55
135
  }
56
136
  const dataPtr = this.wasmModule._malloc(nalUnit.size);
@@ -87,6 +167,18 @@ class H264Decoder {
87
167
  }
88
168
  return null;
89
169
  }
170
+ /**
171
+ * 销毁解码器,释放 WASM 资源
172
+ */
173
+ destroy() {
174
+ if (this.destroyed) return;
175
+ this.destroyed = true;
176
+ if (this.decoderContext !== null && this.wasmModule?._destroy_decoder) {
177
+ this.wasmModule._destroy_decoder(this.decoderContext);
178
+ }
179
+ this.decoderContext = null;
180
+ this.wasmModule = null;
181
+ }
90
182
  yuv420pToRgba(yPtr, uPtr, vPtr, width, height, yLineSize, uLineSize, vLineSize) {
91
183
  const rgba = new Uint8Array(width * height * 4);
92
184
  for (let y = 0; y < height; y++) {
@@ -907,6 +999,7 @@ class HEVCDecoder {
907
999
  constructor(wasmLoader) {
908
1000
  this.wasmModule = null;
909
1001
  this.decoderContext = null;
1002
+ this.destroyed = false;
910
1003
  this.wasmModule = wasmLoader.getModule();
911
1004
  }
912
1005
  async init() {
@@ -920,7 +1013,7 @@ class HEVCDecoder {
920
1013
  console.log("[HEVCDecoder] Initialized with codec_id=173");
921
1014
  }
922
1015
  decode(nalUnit) {
923
- if (this.decoderContext === null || !this.wasmModule) {
1016
+ if (this.destroyed || this.decoderContext === null || !this.wasmModule) {
924
1017
  return null;
925
1018
  }
926
1019
  const dataPtr = this.wasmModule._malloc(nalUnit.size);
@@ -957,6 +1050,18 @@ class HEVCDecoder {
957
1050
  }
958
1051
  return null;
959
1052
  }
1053
+ /**
1054
+ * 销毁解码器,释放 WASM 资源
1055
+ */
1056
+ destroy() {
1057
+ if (this.destroyed) return;
1058
+ this.destroyed = true;
1059
+ if (this.decoderContext !== null && this.wasmModule?._destroy_decoder) {
1060
+ this.wasmModule._destroy_decoder(this.decoderContext);
1061
+ }
1062
+ this.decoderContext = null;
1063
+ this.wasmModule = null;
1064
+ }
960
1065
  yuv420pToRgba(yPtr, uPtr, vPtr, width, height, yLineSize, uLineSize, vLineSize) {
961
1066
  const rgba = new Uint8Array(width * height * 4);
962
1067
  for (let y = 0; y < height; y++) {
@@ -1238,7 +1343,8 @@ class SegmentPrefetcher extends BasePrefetcher {
1238
1343
  this.prefetchQueue.push({
1239
1344
  data,
1240
1345
  segmentIndex: nextIndex,
1241
- fetchTime
1346
+ fetchTime,
1347
+ segmentDuration: segment.duration
1242
1348
  });
1243
1349
  this.fetchedSegmentCount++;
1244
1350
  this.updateStatus({
@@ -1276,7 +1382,7 @@ class SegmentPrefetcher extends BasePrefetcher {
1276
1382
  this.prefetchQueue.shift();
1277
1383
  this.updateStatus({ prefetchQueueSize: this.prefetchQueue.length });
1278
1384
  const parseStart = performance.now();
1279
- const itemCount = this.parseSegment(item.data, item.segmentIndex);
1385
+ const itemCount = this.parseSegment(item.data, item.segmentIndex, item.segmentDuration);
1280
1386
  const parseTime = performance.now() - parseStart;
1281
1387
  this.currentSegmentIndex++;
1282
1388
  this.updateStatus({ currentSegmentIndex: this.currentSegmentIndex });
@@ -1548,11 +1654,11 @@ class HLSSegmentPrefetcher extends SegmentPrefetcher {
1548
1654
  /**
1549
1655
  * 解析分片数据
1550
1656
  */
1551
- parseSegment(data, index) {
1657
+ parseSegment(data, index, segmentDuration) {
1552
1658
  if (this.player.isFMP4) {
1553
1659
  return this.player.parseFMP4Data(data);
1554
1660
  } else {
1555
- return this.player.parseTSData(data);
1661
+ return this.player.parseTSData(data, segmentDuration);
1556
1662
  }
1557
1663
  }
1558
1664
  /**
@@ -1584,14 +1690,36 @@ class HLSPlayer extends BasePlayer {
1584
1690
  this.currentPlaylistUrl = "";
1585
1691
  this._nalQueue = [];
1586
1692
  this._sampleQueue = [];
1693
+ this._currentSegmentDuration = 0;
1587
1694
  this.prefetcher = null;
1695
+ this._nalCountInCurrentSegment = 0;
1588
1696
  this.lastRenderTime = 0;
1589
1697
  this.playStartTime = 0;
1590
1698
  this.accumulatedMediaTime = 0;
1591
1699
  this._lastLogTime = 0;
1700
+ this._lastRafTime = 0;
1592
1701
  this.renderLoop = (timestamp = 0) => {
1593
1702
  if (!this.isPlaying) return;
1594
1703
  const now = performance.now();
1704
+ const BACKGROUND_PAUSE_THRESHOLD = 500;
1705
+ if (this._lastRafTime > 0 && now - this._lastRafTime > BACKGROUND_PAUSE_THRESHOLD) {
1706
+ const pauseDuration = now - this._lastRafTime;
1707
+ console.log("[renderLoop] Background pause detected, duration:", pauseDuration.toFixed(0), "ms");
1708
+ if (this.config.isLive) {
1709
+ const KEEP_FRAMES = 5;
1710
+ if (this.frameBuffer.length > KEEP_FRAMES) {
1711
+ const droppedCount = this.frameBuffer.length - KEEP_FRAMES;
1712
+ this.frameBuffer = this.frameBuffer.slice(-KEEP_FRAMES);
1713
+ this.droppedFrames += droppedCount;
1714
+ console.log("[renderLoop] Live resume: dropped", droppedCount, "old frames, keeping latest", KEEP_FRAMES);
1715
+ }
1716
+ this.playStartTime = now;
1717
+ this.accumulatedMediaTime = 0;
1718
+ } else {
1719
+ this.playStartTime = now - this.accumulatedMediaTime;
1720
+ }
1721
+ }
1722
+ this._lastRafTime = now;
1595
1723
  if (!this._lastLogTime || now - this._lastLogTime > 1e3) {
1596
1724
  this._lastLogTime = now;
1597
1725
  console.log("[renderLoop] frameBuffer=", this.frameBuffer.length, "renderer=", !!this.renderer);
@@ -1696,6 +1824,8 @@ class HLSPlayer extends BasePlayer {
1696
1824
  isLoaded: true,
1697
1825
  totalSegments: segments.length
1698
1826
  });
1827
+ this.initPrefetcher();
1828
+ this.prefetcher.start();
1699
1829
  }
1700
1830
  /**
1701
1831
  * 初始化解码器(覆盖基类方法,添加 fMP4 支持)
@@ -1792,11 +1922,12 @@ class HLSPlayer extends BasePlayer {
1792
1922
  while (this.nalQueue.length > 0 && decodedInBatch < batchSize) {
1793
1923
  const queuedNal = this.nalQueue.shift();
1794
1924
  const nalUnit = queuedNal.nalUnit;
1925
+ const segmentDuration = queuedNal.segmentDuration;
1795
1926
  if (!this.decoderInitialized && (nalUnit.type === 7 || nalUnit.type === 8)) {
1796
1927
  this.initDecoderParamsSync([nalUnit]);
1797
1928
  }
1798
1929
  if (nalUnit.type === 5 || nalUnit.type === 1) {
1799
- const frame = this.decodeNAL(nalUnit);
1930
+ const frame = this.decodeNAL(nalUnit, segmentDuration);
1800
1931
  if (frame) {
1801
1932
  this.frameBuffer.push(frame);
1802
1933
  decodedInBatch++;
@@ -1910,7 +2041,7 @@ class HLSPlayer extends BasePlayer {
1910
2041
  /**
1911
2042
  * 解析 TS 数据
1912
2043
  */
1913
- parseTSData(tsData) {
2044
+ parseTSData(tsData, segmentDuration) {
1914
2045
  const packets = this.tsDemuxer.parse(tsData);
1915
2046
  let nalCount = 0;
1916
2047
  if (packets.video.length > 0) {
@@ -1920,8 +2051,15 @@ class HLSPlayer extends BasePlayer {
1920
2051
  if (!this.decoderInitialized) {
1921
2052
  this.initDecoderParamsSync(nalUnits);
1922
2053
  }
2054
+ if (segmentDuration !== void 0) {
2055
+ this._currentSegmentDuration = segmentDuration;
2056
+ }
1923
2057
  for (const nalUnit of nalUnits) {
1924
- this._nalQueue.push({ nalUnit, pts: 0 });
2058
+ this._nalQueue.push({
2059
+ nalUnit,
2060
+ pts: 0,
2061
+ segmentDuration: this._currentSegmentDuration
2062
+ });
1925
2063
  }
1926
2064
  nalCount = nalUnits.length;
1927
2065
  }
@@ -1978,7 +2116,13 @@ class HLSPlayer extends BasePlayer {
1978
2116
  this._sampleQueue.length = 0;
1979
2117
  this._nalQueue.length = 0;
1980
2118
  const url = uri.startsWith("http") ? uri : this.currentPlaylistUrl.substring(0, this.currentPlaylistUrl.lastIndexOf("/") + 1) + uri;
1981
- const response = await fetch(url);
2119
+ const headers = {};
2120
+ if (this.initSegment?.uri === uri && this.initSegment.byteRange) {
2121
+ const { start, end } = this.initSegment.byteRange;
2122
+ headers["Range"] = `bytes=${start}-${end}`;
2123
+ console.log("[fMP4] Using byte range for init segment:", { start, end });
2124
+ }
2125
+ const response = await fetch(url, { headers });
1982
2126
  const data = new Uint8Array(await response.arrayBuffer());
1983
2127
  console.log("[fMP4] New init segment data size:", data.length, "bytes");
1984
2128
  const initInfo = this.fmp4Demuxer.parseInitSegment(data);
@@ -2297,16 +2441,22 @@ class HLSPlayer extends BasePlayer {
2297
2441
  /**
2298
2442
  * 解码单个 NAL 单元
2299
2443
  */
2300
- decodeNAL(nalUnit) {
2444
+ decodeNAL(nalUnit, segmentDuration) {
2301
2445
  if (!this.decoder) return null;
2302
2446
  const nalWithStartCode = new Uint8Array(nalUnit.size + 4);
2303
2447
  nalWithStartCode.set([0, 0, 0, 1], 0);
2304
2448
  nalWithStartCode.set(nalUnit.data, 4);
2305
- return this.decoder.decode({
2449
+ const frame = this.decoder.decode({
2306
2450
  type: nalUnit.type,
2307
2451
  data: nalWithStartCode,
2308
2452
  size: nalWithStartCode.length
2309
2453
  });
2454
+ if (frame && segmentDuration && segmentDuration > 0) {
2455
+ const timescale = 1e3;
2456
+ const frameDuration = timescale / 60;
2457
+ frame.duration = frameDuration;
2458
+ }
2459
+ return frame;
2310
2460
  }
2311
2461
  }
2312
2462
  const FLV_SIGNATURE = [70, 76, 86];
@@ -2842,6 +2992,7 @@ class FLVPlayer extends BasePlayer {
2842
2992
  this.bufferEmptyStartTime = 0;
2843
2993
  this.playStartTimeOffset = 0;
2844
2994
  this.resyncCount = 0;
2995
+ this._lastRafTime = 0;
2845
2996
  this.renderLoop = () => {
2846
2997
  if (!this.isPlaying) return;
2847
2998
  this.updateState({
@@ -2851,12 +3002,32 @@ class FLVPlayer extends BasePlayer {
2851
3002
  });
2852
3003
  const now = performance.now();
2853
3004
  const isLive = this.config.isLive;
3005
+ const BACKGROUND_PAUSE_THRESHOLD = 500;
3006
+ if (this._lastRafTime > 0 && now - this._lastRafTime > BACKGROUND_PAUSE_THRESHOLD) {
3007
+ const pauseDuration = now - this._lastRafTime;
3008
+ console.log("[FLVPlayer] Background pause detected, duration:", pauseDuration.toFixed(0), "ms, isLive:", isLive, "queue:", this._videoTagQueue.length, "buffer:", this._timedFrameBuffer.length);
3009
+ if (isLive) {
3010
+ const droppedDecoded = this._timedFrameBuffer.length;
3011
+ const droppedQueue = this._videoTagQueue.length;
3012
+ this._timedFrameBuffer = [];
3013
+ this._videoTagQueue = [];
3014
+ this.droppedFrames += droppedDecoded;
3015
+ this.playStartTime = 0;
3016
+ this.firstFrameDts = -1;
3017
+ this.pausedTime = 0;
3018
+ console.log("[FLVPlayer] Live resume: cleared all buffers (decoded:", droppedDecoded, ", queue:", droppedQueue, "), waiting for new data");
3019
+ } else {
3020
+ this.playStartTimeOffset += pauseDuration;
3021
+ }
3022
+ }
3023
+ this._lastRafTime = now;
2854
3024
  if (this._timedFrameBuffer.length > 0 && this.renderer) {
2855
3025
  this.consecutiveEmptyBuffer = 0;
2856
3026
  if (this.playStartTime <= 0) {
2857
3027
  this.firstFrameDts = this._timedFrameBuffer[0].dts;
2858
3028
  this.playStartTime = now;
2859
3029
  this.playStartTimeOffset = 0;
3030
+ this.pausedTime = 0;
2860
3031
  console.log("[FLVPlayer] RenderLoop initialized, firstFrameDts:", this.firstFrameDts, "isLive:", isLive);
2861
3032
  }
2862
3033
  if (isLive) {
@@ -3095,6 +3266,25 @@ class FLVPlayer extends BasePlayer {
3095
3266
  console.log("[FLVPlayer] Pausing live stream, keeping download running");
3096
3267
  }
3097
3268
  }
3269
+ /**
3270
+ * 销毁播放器,释放所有资源
3271
+ */
3272
+ destroy() {
3273
+ this.stopLiveDownload();
3274
+ if (this.h264Decoder) {
3275
+ this.h264Decoder.destroy();
3276
+ this.h264Decoder = null;
3277
+ }
3278
+ if (this.hevcDecoder) {
3279
+ this.hevcDecoder.destroy();
3280
+ this.hevcDecoder = null;
3281
+ }
3282
+ if (this.prefetcher) {
3283
+ this.prefetcher.reset();
3284
+ this.prefetcher = null;
3285
+ }
3286
+ super.destroy();
3287
+ }
3098
3288
  /**
3099
3289
  * 处理预取缓冲区数据(供预取器调用)
3100
3290
  */
@@ -4188,6 +4378,7 @@ export {
4188
4378
  EcPlayerCore,
4189
4379
  EnvDetector,
4190
4380
  LogLevel,
4381
+ WASMLoader,
4191
4382
  setLogLevel
4192
4383
  };
4193
4384
  //# sourceMappingURL=index.js.map