@give-tech/ec-player 0.0.1-beta.48 → 0.0.1-beta.50
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/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +268 -31
- package/dist/index.js.map +1 -1
- package/dist/player/BasePlayer.d.ts +11 -0
- package/dist/player/BasePlayer.d.ts.map +1 -1
- package/dist/player/EcPlayerCore.d.ts.map +1 -1
- package/dist/player/FLVPlayer.d.ts +12 -0
- package/dist/player/FLVPlayer.d.ts.map +1 -1
- package/dist/player/HLSPlayer.d.ts +9 -0
- package/dist/player/HLSPlayer.d.ts.map +1 -1
- package/dist/prefetch/BasePrefetcher.d.ts.map +1 -1
- package/dist/prefetch/SegmentPrefetcher.d.ts +9 -4
- package/dist/prefetch/SegmentPrefetcher.d.ts.map +1 -1
- package/dist/prefetch/index.d.ts +1 -1
- package/dist/prefetch/index.d.ts.map +1 -1
- package/dist/prefetch/types.d.ts +15 -0
- package/dist/prefetch/types.d.ts.map +1 -1
- package/dist/types/index.d.ts +10 -0
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -7,6 +7,6 @@
|
|
|
7
7
|
export { EcPlayerCore, type EcPlayerCoreConfig } from './player';
|
|
8
8
|
export { EnvDetector, type EnvInfo, type EnvCapabilities } from './env';
|
|
9
9
|
export { WASMLoader } from './decoder/WASMLoader';
|
|
10
|
-
export type { PlayerState as EcPlayerState, PlayerCallbacks as EcPlayerCallbacks } from './types';
|
|
10
|
+
export type { PlayerState as EcPlayerState, PlayerCallbacks as EcPlayerCallbacks, LoadingStageInfo as EcLoadingStageInfo } from './types';
|
|
11
11
|
export { LogLevel, setLogLevel } from './utils';
|
|
12
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,OAAO,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAA;AAGjD,YAAY,EACV,WAAW,IAAI,aAAa,EAC5B,eAAe,IAAI,iBAAiB,
|
|
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,EACpC,gBAAgB,IAAI,kBAAkB,EACvC,MAAM,SAAS,CAAA;AAGhB,OAAO,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,SAAS,CAAA"}
|
package/dist/index.js
CHANGED
|
@@ -235,6 +235,8 @@ class BasePlayer {
|
|
|
235
235
|
isPlaying: false,
|
|
236
236
|
isLoaded: false,
|
|
237
237
|
isLoading: false,
|
|
238
|
+
isBuffering: false,
|
|
239
|
+
bufferingProgress: void 0,
|
|
238
240
|
fps: 0,
|
|
239
241
|
resolution: "-",
|
|
240
242
|
decoded: 0,
|
|
@@ -283,6 +285,23 @@ class BasePlayer {
|
|
|
283
285
|
this.decoder = new H264Decoder(this.wasmLoader);
|
|
284
286
|
await this.decoder.init();
|
|
285
287
|
}
|
|
288
|
+
/**
|
|
289
|
+
* 预初始化解码器(并行加载优化)
|
|
290
|
+
* 在数据下载的同时加载 WASM 模块
|
|
291
|
+
* 返回初始化结果,供 finishInitDecoder 使用
|
|
292
|
+
*/
|
|
293
|
+
async preInitDecoder() {
|
|
294
|
+
await this.wasmLoader.load(this.config.wasmPath);
|
|
295
|
+
return null;
|
|
296
|
+
}
|
|
297
|
+
/**
|
|
298
|
+
* 完成解码器初始化
|
|
299
|
+
* 使用 preInitDecoder 的结果完成初始化
|
|
300
|
+
*/
|
|
301
|
+
async finishInitDecoder(_initResult) {
|
|
302
|
+
this.decoder = new H264Decoder(this.wasmLoader);
|
|
303
|
+
await this.decoder.init();
|
|
304
|
+
}
|
|
286
305
|
/**
|
|
287
306
|
* 开始播放
|
|
288
307
|
*/
|
|
@@ -317,6 +336,8 @@ class BasePlayer {
|
|
|
317
336
|
isPlaying: false,
|
|
318
337
|
isLoaded: false,
|
|
319
338
|
isLoading: false,
|
|
339
|
+
isBuffering: false,
|
|
340
|
+
bufferingProgress: void 0,
|
|
320
341
|
fps: 0,
|
|
321
342
|
resolution: "-",
|
|
322
343
|
decoded: 0,
|
|
@@ -1202,9 +1223,9 @@ class BasePrefetcher {
|
|
|
1202
1223
|
const queueSize = this.getQueueSize();
|
|
1203
1224
|
this.updateStatus({ queueSize });
|
|
1204
1225
|
if (this.shouldPrefetch()) {
|
|
1205
|
-
this.
|
|
1206
|
-
|
|
1207
|
-
|
|
1226
|
+
this.doPrefetch().catch((err) => {
|
|
1227
|
+
console.error("[BasePrefetcher] Prefetch error:", err);
|
|
1228
|
+
});
|
|
1208
1229
|
}
|
|
1209
1230
|
const interval = this.calculateInterval(queueSize);
|
|
1210
1231
|
await this.sleep(interval);
|
|
@@ -1303,7 +1324,8 @@ class SegmentPrefetcher extends BasePrefetcher {
|
|
|
1303
1324
|
this.currentSegmentIndex = 0;
|
|
1304
1325
|
this.fetchedSegmentCount = 0;
|
|
1305
1326
|
this.baseUrl = "";
|
|
1306
|
-
this.
|
|
1327
|
+
this.activePrefetchCount = 0;
|
|
1328
|
+
this.prefetchingIndexes = /* @__PURE__ */ new Set();
|
|
1307
1329
|
}
|
|
1308
1330
|
/**
|
|
1309
1331
|
* 创建初始状态
|
|
@@ -1356,35 +1378,46 @@ class SegmentPrefetcher extends BasePrefetcher {
|
|
|
1356
1378
|
* 判断是否应该预取
|
|
1357
1379
|
*/
|
|
1358
1380
|
shouldPrefetch() {
|
|
1359
|
-
|
|
1381
|
+
const maxConcurrent = this.config.maxConcurrentPrefetch ?? 1;
|
|
1382
|
+
if (this.activePrefetchCount >= maxConcurrent) return false;
|
|
1360
1383
|
if (this.segments.length === 0) return false;
|
|
1361
1384
|
const prefetchAhead = this.config.prefetchAhead;
|
|
1362
|
-
|
|
1363
|
-
|
|
1385
|
+
const totalPending = this.prefetchQueue.length + this.activePrefetchCount;
|
|
1386
|
+
if (totalPending >= prefetchAhead) return false;
|
|
1387
|
+
const nextIndex = this.currentSegmentIndex + this.prefetchQueue.length + this.activePrefetchCount;
|
|
1364
1388
|
return nextIndex < this.segments.length;
|
|
1365
1389
|
}
|
|
1366
1390
|
/**
|
|
1367
|
-
*
|
|
1391
|
+
* 执行预取(支持并行下载)
|
|
1368
1392
|
*/
|
|
1369
1393
|
async doPrefetch() {
|
|
1370
|
-
|
|
1371
|
-
this.
|
|
1394
|
+
const maxConcurrent = this.config.maxConcurrentPrefetch ?? 1;
|
|
1395
|
+
if (this.activePrefetchCount >= maxConcurrent) return;
|
|
1396
|
+
this.activePrefetchCount++;
|
|
1372
1397
|
this.updateStatus({ isPrefetching: true });
|
|
1373
1398
|
try {
|
|
1374
|
-
const nextIndex = this.currentSegmentIndex + this.prefetchQueue.length;
|
|
1399
|
+
const nextIndex = this.currentSegmentIndex + this.prefetchQueue.length + this.prefetchingIndexes.size;
|
|
1375
1400
|
if (nextIndex >= this.segments.length) {
|
|
1401
|
+
this.activePrefetchCount--;
|
|
1402
|
+
return;
|
|
1403
|
+
}
|
|
1404
|
+
if (this.prefetchingIndexes.has(nextIndex)) {
|
|
1405
|
+
this.activePrefetchCount--;
|
|
1376
1406
|
return;
|
|
1377
1407
|
}
|
|
1408
|
+
this.prefetchingIndexes.add(nextIndex);
|
|
1378
1409
|
const segment = this.segments[nextIndex];
|
|
1379
1410
|
const fetchStart = performance.now();
|
|
1380
1411
|
const data = await this.fetchSegment(segment, nextIndex);
|
|
1381
1412
|
const fetchTime = performance.now() - fetchStart;
|
|
1413
|
+
this.prefetchingIndexes.delete(nextIndex);
|
|
1382
1414
|
this.prefetchQueue.push({
|
|
1383
1415
|
data,
|
|
1384
1416
|
segmentIndex: nextIndex,
|
|
1385
1417
|
fetchTime,
|
|
1386
1418
|
segmentDuration: segment.duration
|
|
1387
1419
|
});
|
|
1420
|
+
this.prefetchQueue.sort((a, b) => a.segmentIndex - b.segmentIndex);
|
|
1388
1421
|
this.fetchedSegmentCount++;
|
|
1389
1422
|
this.updateStatus({
|
|
1390
1423
|
prefetchQueueSize: this.prefetchQueue.length,
|
|
@@ -1396,13 +1429,15 @@ class SegmentPrefetcher extends BasePrefetcher {
|
|
|
1396
1429
|
} catch (error) {
|
|
1397
1430
|
if (error.name === "AbortError") {
|
|
1398
1431
|
console.log(`[SegmentPrefetcher] Fetch aborted`);
|
|
1432
|
+
this.prefetchingIndexes.clear();
|
|
1433
|
+
this.activePrefetchCount = 0;
|
|
1399
1434
|
return;
|
|
1400
1435
|
}
|
|
1401
1436
|
console.error(`[SegmentPrefetcher] Fetch failed:`, error.message);
|
|
1402
1437
|
this.callbacks.onError?.(error);
|
|
1403
1438
|
} finally {
|
|
1404
|
-
this.
|
|
1405
|
-
this.updateStatus({ isPrefetching:
|
|
1439
|
+
this.activePrefetchCount--;
|
|
1440
|
+
this.updateStatus({ isPrefetching: this.activePrefetchCount > 0 });
|
|
1406
1441
|
}
|
|
1407
1442
|
}
|
|
1408
1443
|
/**
|
|
@@ -1445,7 +1480,8 @@ class SegmentPrefetcher extends BasePrefetcher {
|
|
|
1445
1480
|
this.segments = [];
|
|
1446
1481
|
this.currentSegmentIndex = 0;
|
|
1447
1482
|
this.baseUrl = "";
|
|
1448
|
-
this.
|
|
1483
|
+
this.activePrefetchCount = 0;
|
|
1484
|
+
this.prefetchingIndexes.clear();
|
|
1449
1485
|
}
|
|
1450
1486
|
/**
|
|
1451
1487
|
* 获取当前分片索引
|
|
@@ -1652,7 +1688,8 @@ const DEFAULT_PREFETCHER_CONFIG = {
|
|
|
1652
1688
|
};
|
|
1653
1689
|
const DEFAULT_SEGMENT_PREFETCHER_CONFIG = {
|
|
1654
1690
|
...DEFAULT_PREFETCHER_CONFIG,
|
|
1655
|
-
prefetchAhead: 2
|
|
1691
|
+
prefetchAhead: 2,
|
|
1692
|
+
maxConcurrentPrefetch: 2
|
|
1656
1693
|
};
|
|
1657
1694
|
const DEFAULT_STREAM_PREFETCHER_CONFIG = {
|
|
1658
1695
|
...DEFAULT_PREFETCHER_CONFIG,
|
|
@@ -1668,7 +1705,9 @@ const HLS_PREFETCHER_CONFIG = {
|
|
|
1668
1705
|
prefetchAhead: 6,
|
|
1669
1706
|
// 预载 6 个分片(约 30 秒),适应高分辨率视频
|
|
1670
1707
|
prefetchInterval: 10,
|
|
1671
|
-
dynamicInterval: true
|
|
1708
|
+
dynamicInterval: true,
|
|
1709
|
+
maxConcurrentPrefetch: 2
|
|
1710
|
+
// 并行下载 2 个分片
|
|
1672
1711
|
};
|
|
1673
1712
|
const DEFAULT_HLS_CONFIG = {
|
|
1674
1713
|
...DEFAULT_PLAYER_CONFIG
|
|
@@ -1677,10 +1716,12 @@ class HLSSegmentPrefetcher extends SegmentPrefetcher {
|
|
|
1677
1716
|
constructor(config, callbacks, player) {
|
|
1678
1717
|
super(config, callbacks);
|
|
1679
1718
|
this.currentInitSegmentUri = null;
|
|
1719
|
+
this.progressDisplayIndex = -1;
|
|
1720
|
+
this.lastReportedPercent = -1;
|
|
1680
1721
|
this.player = player;
|
|
1681
1722
|
}
|
|
1682
1723
|
/**
|
|
1683
|
-
*
|
|
1724
|
+
* 获取分片数据(支持流式下载和进度报告)
|
|
1684
1725
|
*/
|
|
1685
1726
|
async fetchSegment(segment, index) {
|
|
1686
1727
|
if (segment.initSegmentUri && segment.initSegmentUri !== this.currentInitSegmentUri) {
|
|
@@ -1697,7 +1738,61 @@ class HLSSegmentPrefetcher extends SegmentPrefetcher {
|
|
|
1697
1738
|
}
|
|
1698
1739
|
const signal = this.player.getAbortSignal();
|
|
1699
1740
|
const response = await fetch(url, { headers, signal });
|
|
1700
|
-
|
|
1741
|
+
const contentLength = response.headers.get("Content-Length");
|
|
1742
|
+
const totalSize = contentLength ? parseInt(contentLength, 10) : 0;
|
|
1743
|
+
const reader = response.body?.getReader();
|
|
1744
|
+
if (!reader) {
|
|
1745
|
+
return new Uint8Array(await response.arrayBuffer());
|
|
1746
|
+
}
|
|
1747
|
+
const chunks = [];
|
|
1748
|
+
let loadedBytes = 0;
|
|
1749
|
+
let lastProgressTime = 0;
|
|
1750
|
+
let lastProgressBytes = 0;
|
|
1751
|
+
let downloadSpeed = 0;
|
|
1752
|
+
const PROGRESS_INTERVAL = 200;
|
|
1753
|
+
if (index > this.progressDisplayIndex) {
|
|
1754
|
+
this.progressDisplayIndex = index;
|
|
1755
|
+
}
|
|
1756
|
+
while (true) {
|
|
1757
|
+
const { done, value } = await reader.read();
|
|
1758
|
+
if (done) break;
|
|
1759
|
+
chunks.push(value);
|
|
1760
|
+
loadedBytes += value.length;
|
|
1761
|
+
const now = Date.now();
|
|
1762
|
+
if (now - lastProgressTime >= PROGRESS_INTERVAL) {
|
|
1763
|
+
if (lastProgressTime > 0) {
|
|
1764
|
+
const timeDiff = (now - lastProgressTime) / 1e3;
|
|
1765
|
+
const bytesDiff = loadedBytes - lastProgressBytes;
|
|
1766
|
+
downloadSpeed = bytesDiff / timeDiff;
|
|
1767
|
+
}
|
|
1768
|
+
lastProgressTime = now;
|
|
1769
|
+
lastProgressBytes = loadedBytes;
|
|
1770
|
+
if (index === this.progressDisplayIndex && totalSize > 0) {
|
|
1771
|
+
const percent = Math.min(100, Math.round(loadedBytes / totalSize * 100));
|
|
1772
|
+
if (percent !== this.lastReportedPercent) {
|
|
1773
|
+
this.lastReportedPercent = percent;
|
|
1774
|
+
const callbacks = this.callbacks;
|
|
1775
|
+
callbacks.onDownloadProgress?.({
|
|
1776
|
+
segmentIndex: index,
|
|
1777
|
+
loaded: loadedBytes,
|
|
1778
|
+
total: totalSize,
|
|
1779
|
+
speed: downloadSpeed > 0 ? downloadSpeed : void 0
|
|
1780
|
+
});
|
|
1781
|
+
}
|
|
1782
|
+
}
|
|
1783
|
+
}
|
|
1784
|
+
}
|
|
1785
|
+
if (index === this.progressDisplayIndex) {
|
|
1786
|
+
this.progressDisplayIndex = -1;
|
|
1787
|
+
this.lastReportedPercent = -1;
|
|
1788
|
+
}
|
|
1789
|
+
const result = new Uint8Array(loadedBytes);
|
|
1790
|
+
let offset = 0;
|
|
1791
|
+
for (const chunk of chunks) {
|
|
1792
|
+
result.set(chunk, offset);
|
|
1793
|
+
offset += chunk.length;
|
|
1794
|
+
}
|
|
1795
|
+
return result;
|
|
1701
1796
|
}
|
|
1702
1797
|
/**
|
|
1703
1798
|
* 解析分片数据
|
|
@@ -1785,12 +1880,20 @@ class HLSPlayer extends BasePlayer {
|
|
|
1785
1880
|
totalSegments
|
|
1786
1881
|
});
|
|
1787
1882
|
if (this.frameBuffer.length > 0 && this.renderer) {
|
|
1883
|
+
const wasBuffering = this.state.isBuffering;
|
|
1884
|
+
if (this.state.isBuffering) {
|
|
1885
|
+
this.updateState({ isBuffering: false, bufferingProgress: void 0 });
|
|
1886
|
+
}
|
|
1788
1887
|
const timescale = this.fmp4Demuxer.getTimescale();
|
|
1789
1888
|
if (this.playStartTime === 0) {
|
|
1790
1889
|
this.playStartTime = now;
|
|
1791
1890
|
this.accumulatedMediaTime = 0;
|
|
1792
1891
|
console.log("[renderLoop] Init: timescale=", timescale);
|
|
1793
1892
|
}
|
|
1893
|
+
if (wasBuffering && this.accumulatedMediaTime > 0) {
|
|
1894
|
+
this.playStartTime = now - this.accumulatedMediaTime / this._playbackRate;
|
|
1895
|
+
console.log("[renderLoop] Buffer recovered, reset playStartTime to continue from", Math.floor(this.accumulatedMediaTime), "ms");
|
|
1896
|
+
}
|
|
1794
1897
|
const elapsedWallTime = (now - this.playStartTime) * this._playbackRate;
|
|
1795
1898
|
if (Math.floor(elapsedWallTime / 1e3) !== Math.floor((elapsedWallTime - 20 * this._playbackRate) / 1e3)) {
|
|
1796
1899
|
console.log("[renderLoop] elapsed=", Math.floor(elapsedWallTime), "accumulated=", Math.floor(this.accumulatedMediaTime), "rate=", this._playbackRate, "frameBuffer=", this.frameBuffer.length);
|
|
@@ -1831,6 +1934,9 @@ class HLSPlayer extends BasePlayer {
|
|
|
1831
1934
|
this.callbacks.onFrameRender?.(frame);
|
|
1832
1935
|
}
|
|
1833
1936
|
} else {
|
|
1937
|
+
if (this.isPlaying && !this.state.isBuffering) {
|
|
1938
|
+
this.updateState({ isBuffering: true });
|
|
1939
|
+
}
|
|
1834
1940
|
if (this.playStartTime === 0) {
|
|
1835
1941
|
console.log("[renderLoop] Waiting for frames...");
|
|
1836
1942
|
}
|
|
@@ -1909,7 +2015,6 @@ class HLSPlayer extends BasePlayer {
|
|
|
1909
2015
|
totalSegments: segments.length
|
|
1910
2016
|
});
|
|
1911
2017
|
this.initPrefetcher();
|
|
1912
|
-
this.prefetcher.start();
|
|
1913
2018
|
}
|
|
1914
2019
|
/**
|
|
1915
2020
|
* 初始化解码器(覆盖基类方法,添加 fMP4 支持)
|
|
@@ -1920,6 +2025,22 @@ class HLSPlayer extends BasePlayer {
|
|
|
1920
2025
|
await this.loadInitSegment();
|
|
1921
2026
|
}
|
|
1922
2027
|
}
|
|
2028
|
+
/**
|
|
2029
|
+
* 预初始化解码器(并行加载优化)
|
|
2030
|
+
* 在数据下载的同时加载 WASM 模块
|
|
2031
|
+
*/
|
|
2032
|
+
async preInitDecoder() {
|
|
2033
|
+
return super.preInitDecoder();
|
|
2034
|
+
}
|
|
2035
|
+
/**
|
|
2036
|
+
* 完成解码器初始化(使用预加载的 WASM)
|
|
2037
|
+
*/
|
|
2038
|
+
async finishInitDecoder(initResult) {
|
|
2039
|
+
await super.finishInitDecoder(initResult);
|
|
2040
|
+
if (this.isFMP4 && this.initSegment) {
|
|
2041
|
+
await this.loadInitSegment();
|
|
2042
|
+
}
|
|
2043
|
+
}
|
|
1923
2044
|
/**
|
|
1924
2045
|
* 开始播放(覆盖基类方法,使用自定义渲染循环)
|
|
1925
2046
|
*/
|
|
@@ -2017,14 +2138,26 @@ class HLSPlayer extends BasePlayer {
|
|
|
2017
2138
|
async decodeLoop() {
|
|
2018
2139
|
console.log("[DecodeLoop] START, isFMP4:", this.isFMP4);
|
|
2019
2140
|
let batchCount = 0;
|
|
2020
|
-
const
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2141
|
+
const CHECK_INTERVAL_MS = 5e3;
|
|
2142
|
+
let checkCount = 0;
|
|
2143
|
+
const MAX_CHECK_COUNT = 24;
|
|
2144
|
+
const checkReadyTimeout = () => {
|
|
2145
|
+
if (this.readyFired || this.decodeLoopAbort) return;
|
|
2146
|
+
checkCount++;
|
|
2147
|
+
if (checkCount > MAX_CHECK_COUNT) {
|
|
2148
|
+
console.warn("[HLSPlayer] Max ready timeout reached, triggering onReady");
|
|
2024
2149
|
this.readyFired = true;
|
|
2025
2150
|
this.callbacks.onReady?.();
|
|
2151
|
+
return;
|
|
2026
2152
|
}
|
|
2027
|
-
|
|
2153
|
+
if (this.frameBuffer.length > 0) {
|
|
2154
|
+
this.readyFired = true;
|
|
2155
|
+
this.callbacks.onReady?.();
|
|
2156
|
+
return;
|
|
2157
|
+
}
|
|
2158
|
+
setTimeout(checkReadyTimeout, CHECK_INTERVAL_MS);
|
|
2159
|
+
};
|
|
2160
|
+
setTimeout(checkReadyTimeout, CHECK_INTERVAL_MS);
|
|
2028
2161
|
while (this.isPlaying && !this.decodeLoopAbort) {
|
|
2029
2162
|
const sampleQueueSize = this.sampleQueue.length;
|
|
2030
2163
|
const maxSamplesBeforePause = this.config.maxQueueSize * 3;
|
|
@@ -2131,7 +2264,6 @@ class HLSPlayer extends BasePlayer {
|
|
|
2131
2264
|
await this.sleep(2);
|
|
2132
2265
|
}
|
|
2133
2266
|
}
|
|
2134
|
-
clearTimeout(readyTimeoutId);
|
|
2135
2267
|
console.log("[DecodeLoop] END, batches:", batchCount);
|
|
2136
2268
|
}
|
|
2137
2269
|
/**
|
|
@@ -2152,6 +2284,18 @@ class HLSPlayer extends BasePlayer {
|
|
|
2152
2284
|
onSegmentParsed: (segmentIndex, itemCount) => {
|
|
2153
2285
|
this.currentSegmentIndex = segmentIndex + 1;
|
|
2154
2286
|
this.updateState({ segmentIndex: this.currentSegmentIndex });
|
|
2287
|
+
},
|
|
2288
|
+
onDownloadProgress: (progress) => {
|
|
2289
|
+
if (progress.total && progress.total > 0) {
|
|
2290
|
+
const percent = Math.round(progress.loaded / progress.total * 100);
|
|
2291
|
+
this.callbacks.onLoadingStageChange?.({
|
|
2292
|
+
stage: "loading",
|
|
2293
|
+
detail: `下载中 ${percent}%`
|
|
2294
|
+
});
|
|
2295
|
+
if (this.state.isBuffering) {
|
|
2296
|
+
this.updateState({ bufferingProgress: percent });
|
|
2297
|
+
}
|
|
2298
|
+
}
|
|
2155
2299
|
}
|
|
2156
2300
|
},
|
|
2157
2301
|
this
|
|
@@ -2354,6 +2498,9 @@ class HLSPlayer extends BasePlayer {
|
|
|
2354
2498
|
} else {
|
|
2355
2499
|
console.error("[fMP4] Failed to parse new init segment!");
|
|
2356
2500
|
}
|
|
2501
|
+
if (initInfo?.avcC || initInfo?.hvcC) {
|
|
2502
|
+
this.decoderInitialized = true;
|
|
2503
|
+
}
|
|
2357
2504
|
}
|
|
2358
2505
|
/**
|
|
2359
2506
|
* 初始化 HEVC 解码器
|
|
@@ -3206,6 +3353,9 @@ class FLVPlayer extends BasePlayer {
|
|
|
3206
3353
|
this.abortController = null;
|
|
3207
3354
|
this.readyFired = false;
|
|
3208
3355
|
this._currentDownloadSpeed = 0;
|
|
3356
|
+
this._decoderInitContext = null;
|
|
3357
|
+
this._totalFileSize = 0;
|
|
3358
|
+
this._downloadedBytes = 0;
|
|
3209
3359
|
this.renderedFrames = 0;
|
|
3210
3360
|
this.lastRenderLogTime = 0;
|
|
3211
3361
|
this.consecutiveEmptyBuffer = 0;
|
|
@@ -3244,6 +3394,9 @@ class FLVPlayer extends BasePlayer {
|
|
|
3244
3394
|
}
|
|
3245
3395
|
this._lastRafTime = now;
|
|
3246
3396
|
if (this._timedFrameBuffer.length > 0 && this.renderer) {
|
|
3397
|
+
if (this.state.isBuffering) {
|
|
3398
|
+
this.updateState({ isBuffering: false, bufferingProgress: void 0 });
|
|
3399
|
+
}
|
|
3247
3400
|
this.consecutiveEmptyBuffer = 0;
|
|
3248
3401
|
if (this.playStartTime <= 0) {
|
|
3249
3402
|
this.firstFrameDts = this._timedFrameBuffer[0].dts;
|
|
@@ -3309,6 +3462,9 @@ class FLVPlayer extends BasePlayer {
|
|
|
3309
3462
|
this.lastRenderLogTime = now;
|
|
3310
3463
|
}
|
|
3311
3464
|
} else {
|
|
3465
|
+
if (this.isPlaying && !this.state.isBuffering) {
|
|
3466
|
+
this.updateState({ isBuffering: true });
|
|
3467
|
+
}
|
|
3312
3468
|
if (this.consecutiveEmptyBuffer === 0 && this.lastRenderedDts >= 0) {
|
|
3313
3469
|
this.bufferEmptyStartTime = now;
|
|
3314
3470
|
}
|
|
@@ -3410,6 +3566,62 @@ class FLVPlayer extends BasePlayer {
|
|
|
3410
3566
|
console.warn("[FLVPlayer] No config found, will try to init from first frames");
|
|
3411
3567
|
}
|
|
3412
3568
|
}
|
|
3569
|
+
/**
|
|
3570
|
+
* 预初始化解码器(并行加载优化)
|
|
3571
|
+
* 在数据下载的同时加载 WASM 模块
|
|
3572
|
+
*/
|
|
3573
|
+
async preInitDecoder() {
|
|
3574
|
+
await this.wasmLoader.load(this.config.wasmPath);
|
|
3575
|
+
this._decoderInitContext = {
|
|
3576
|
+
wasmLoaded: true,
|
|
3577
|
+
codecId: this._currentCodecId
|
|
3578
|
+
};
|
|
3579
|
+
console.log("[FLVPlayer] WASM pre-loaded for parallel init");
|
|
3580
|
+
return this._decoderInitContext;
|
|
3581
|
+
}
|
|
3582
|
+
/**
|
|
3583
|
+
* 完成解码器初始化(使用预加载的 WASM)
|
|
3584
|
+
*/
|
|
3585
|
+
async finishInitDecoder(_initResult) {
|
|
3586
|
+
if (!this._decoderInitContext?.wasmLoaded) {
|
|
3587
|
+
return this.initDecoder();
|
|
3588
|
+
}
|
|
3589
|
+
const avcConfig = this.flvDemuxer.getAvcConfig();
|
|
3590
|
+
const hevcConfig = this.flvDemuxer.getHevcConfig();
|
|
3591
|
+
this._currentCodecId = this.flvDemuxer.getCodecId();
|
|
3592
|
+
if (hevcConfig) {
|
|
3593
|
+
console.log("[FLVPlayer] Finishing HEVC decoder init...");
|
|
3594
|
+
this.hevcDecoder = new HEVCDecoder(this.wasmLoader);
|
|
3595
|
+
await this.hevcDecoder.init();
|
|
3596
|
+
if (hevcConfig.vpsList.length > 0 || hevcConfig.spsList.length > 0) {
|
|
3597
|
+
this.initDecoderFromHEVCConfig(hevcConfig);
|
|
3598
|
+
this.decoderInitialized = true;
|
|
3599
|
+
} else {
|
|
3600
|
+
console.log("[FLVPlayer] HEVC config has no VPS/SPS/PPS, will init from first NALU");
|
|
3601
|
+
if (this._videoTagQueue.length > 0) {
|
|
3602
|
+
const firstTag = this._videoTagQueue[0];
|
|
3603
|
+
this.tryInitFromData(firstTag.annexBData);
|
|
3604
|
+
}
|
|
3605
|
+
}
|
|
3606
|
+
} else if (avcConfig) {
|
|
3607
|
+
console.log("[FLVPlayer] Finishing AVC decoder init...");
|
|
3608
|
+
this.h264Decoder = new H264Decoder(this.wasmLoader);
|
|
3609
|
+
await this.h264Decoder.init();
|
|
3610
|
+
if (avcConfig.spsList.length > 0) {
|
|
3611
|
+
this.initDecoderFromAVCConfig(avcConfig);
|
|
3612
|
+
this.decoderInitialized = true;
|
|
3613
|
+
} else {
|
|
3614
|
+
console.log("[FLVPlayer] AVC config has no SPS/PPS, will init from first NALU");
|
|
3615
|
+
if (this._videoTagQueue.length > 0) {
|
|
3616
|
+
const firstTag = this._videoTagQueue[0];
|
|
3617
|
+
this.tryInitFromData(firstTag.annexBData);
|
|
3618
|
+
}
|
|
3619
|
+
}
|
|
3620
|
+
} else {
|
|
3621
|
+
console.warn("[FLVPlayer] No config found, will try to init from first frames");
|
|
3622
|
+
}
|
|
3623
|
+
this._decoderInitContext = null;
|
|
3624
|
+
}
|
|
3413
3625
|
/**
|
|
3414
3626
|
* 根据视频分辨率动态调整缓冲参数
|
|
3415
3627
|
*/
|
|
@@ -3687,10 +3899,6 @@ class FLVPlayer extends BasePlayer {
|
|
|
3687
3899
|
}
|
|
3688
3900
|
console.log("[FLVPlayer] DecodeLoop END, decoded:", this._timedFrameBuffer.length + this.droppedFrames, "failed:", this.decodeFailCount, "dropped:", this.droppedFrames);
|
|
3689
3901
|
}
|
|
3690
|
-
// ==================== 私有方法 ====================
|
|
3691
|
-
/**
|
|
3692
|
-
* 流式加载 FLV 文件
|
|
3693
|
-
*/
|
|
3694
3902
|
async loadFLV(url) {
|
|
3695
3903
|
console.log("[FLVPlayer] Fetching FLV...");
|
|
3696
3904
|
const signal = this.abortController?.signal;
|
|
@@ -3708,6 +3916,10 @@ class FLVPlayer extends BasePlayer {
|
|
|
3708
3916
|
throw new Error(`Failed to fetch FLV: ${response.status}`);
|
|
3709
3917
|
}
|
|
3710
3918
|
console.log("[FLVPlayer] Fetch response OK, status:", response.status);
|
|
3919
|
+
const contentLength = response.headers.get("Content-Length");
|
|
3920
|
+
this._totalFileSize = contentLength ? parseInt(contentLength, 10) : 0;
|
|
3921
|
+
this._downloadedBytes = 0;
|
|
3922
|
+
console.log("[FLVPlayer] Content-Length:", this._totalFileSize, "bytes");
|
|
3711
3923
|
this.isPrefetching = true;
|
|
3712
3924
|
this.updateState({ isPrefetching: true });
|
|
3713
3925
|
const isLive = this.config.isLive || false;
|
|
@@ -3724,12 +3936,25 @@ class FLVPlayer extends BasePlayer {
|
|
|
3724
3936
|
const startTime = Date.now();
|
|
3725
3937
|
let started = false;
|
|
3726
3938
|
let lastLoggedTags = 0;
|
|
3939
|
+
let lastProgressUpdate = 0;
|
|
3727
3940
|
this.liveReader = reader;
|
|
3728
3941
|
while (true) {
|
|
3729
3942
|
const { done, value } = await reader.read();
|
|
3730
3943
|
if (value) {
|
|
3731
3944
|
chunks.push(value);
|
|
3732
3945
|
totalLength += value.length;
|
|
3946
|
+
this._downloadedBytes = totalLength;
|
|
3947
|
+
const now = Date.now();
|
|
3948
|
+
if (this._totalFileSize > 0 && now - lastProgressUpdate > 200) {
|
|
3949
|
+
lastProgressUpdate = now;
|
|
3950
|
+
const percent = Math.round(totalLength / this._totalFileSize * 100);
|
|
3951
|
+
const downloadedMB = (totalLength / 1024 / 1024).toFixed(1);
|
|
3952
|
+
const totalMB = (this._totalFileSize / 1024 / 1024).toFixed(1);
|
|
3953
|
+
this.callbacks.onLoadingStageChange?.({
|
|
3954
|
+
stage: "loading",
|
|
3955
|
+
detail: `${percent}% (${downloadedMB}/${totalMB} MB)`
|
|
3956
|
+
});
|
|
3957
|
+
}
|
|
3733
3958
|
}
|
|
3734
3959
|
const shouldStart = totalLength >= MIN_DATA_SIZE || Date.now() - startTime > TIMEOUT_MS;
|
|
3735
3960
|
if (!started && shouldStart && totalLength > 0) {
|
|
@@ -4207,9 +4432,11 @@ class EcPlayerCore {
|
|
|
4207
4432
|
*/
|
|
4208
4433
|
async load(url, isLive) {
|
|
4209
4434
|
this.callbacks.onStateChange?.({ isLoading: true, isLoaded: false });
|
|
4435
|
+
this.callbacks.onLoadingStageChange?.({ stage: "connecting" });
|
|
4210
4436
|
try {
|
|
4211
4437
|
this.detectedFormat = FormatDetector.detectFromUrl(url);
|
|
4212
4438
|
console.log("[EcPlayerCore] Detected format:", this.detectedFormat, "from URL:", url, "isLive:", isLive);
|
|
4439
|
+
this.callbacks.onLoadingStageChange?.({ stage: "detected", detail: this.detectedFormat });
|
|
4213
4440
|
if (isLive !== void 0) {
|
|
4214
4441
|
this.config.isLive = isLive;
|
|
4215
4442
|
}
|
|
@@ -4218,13 +4445,22 @@ class EcPlayerCore {
|
|
|
4218
4445
|
this.player.setRenderer(this.pendingRenderer);
|
|
4219
4446
|
this.pendingRenderer = null;
|
|
4220
4447
|
}
|
|
4221
|
-
|
|
4448
|
+
this.callbacks.onLoadingStageChange?.({ stage: "loading" });
|
|
4449
|
+
const [_, initResult] = await Promise.all([
|
|
4450
|
+
this.player.load(url),
|
|
4451
|
+
this.player.preInitDecoder()
|
|
4452
|
+
]);
|
|
4222
4453
|
if (this.player instanceof HLSPlayer) {
|
|
4223
4454
|
this.detectedFormat = this.player.isFMP4 ? "hls-fmp4" : "hls-ts";
|
|
4224
4455
|
console.log("[EcPlayerCore] Updated format after playlist parse:", this.detectedFormat);
|
|
4225
4456
|
}
|
|
4226
|
-
|
|
4457
|
+
this.callbacks.onLoadingStageChange?.({ stage: "initializing" });
|
|
4458
|
+
await this.player.finishInitDecoder(initResult);
|
|
4227
4459
|
this.callbacks.onStateChange?.({ isLoaded: true });
|
|
4460
|
+
} catch (error) {
|
|
4461
|
+
console.error("[EcPlayerCore] Load failed:", error);
|
|
4462
|
+
this.callbacks.onError?.(error);
|
|
4463
|
+
throw error;
|
|
4228
4464
|
} finally {
|
|
4229
4465
|
this.callbacks.onStateChange?.({ isLoading: false });
|
|
4230
4466
|
}
|
|
@@ -4300,6 +4536,7 @@ class EcPlayerCore {
|
|
|
4300
4536
|
isPlaying: false,
|
|
4301
4537
|
isLoaded: false,
|
|
4302
4538
|
isLoading: false,
|
|
4539
|
+
isBuffering: false,
|
|
4303
4540
|
fps: 0,
|
|
4304
4541
|
resolution: "-",
|
|
4305
4542
|
decoded: 0,
|