@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.
- package/dist/decoder/H264Decoder.d.ts +5 -0
- package/dist/decoder/H264Decoder.d.ts.map +1 -1
- package/dist/decoder/HEVCDecoder.d.ts +5 -0
- package/dist/decoder/HEVCDecoder.d.ts.map +1 -1
- package/dist/decoder/WASMLoader.d.ts +31 -2
- package/dist/decoder/WASMLoader.d.ts.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +208 -17
- package/dist/index.js.map +1 -1
- package/dist/player/FLVPlayer.d.ts +5 -0
- package/dist/player/FLVPlayer.d.ts.map +1 -1
- package/dist/player/HLSPlayer.d.ts +5 -1
- package/dist/player/HLSPlayer.d.ts.map +1 -1
- package/dist/prefetch/SegmentPrefetcher.d.ts +2 -1
- package/dist/prefetch/SegmentPrefetcher.d.ts.map +1 -1
- package/dist/prefetch/types.d.ts +2 -0
- package/dist/prefetch/types.d.ts.map +1 -1
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +1 -4
- package/wasm/decoder.js +19 -0
- package/wasm/decoder.wasm +0 -0
|
@@ -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;
|
|
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;
|
|
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
|
|
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
|
|
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
|
package/dist/index.d.ts.map
CHANGED
|
@@ -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.
|
|
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
|
-
|
|
18
|
-
if (typeof
|
|
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
|
-
|
|
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.
|
|
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({
|
|
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
|
|
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
|
-
|
|
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
|