@give-tech/ec-player 0.0.1-beta.0 → 0.0.1-beta.10
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/demuxer/fMP4Demuxer.d.ts +20 -3
- package/dist/demuxer/fMP4Demuxer.d.ts.map +1 -1
- package/dist/index.js +380 -156
- package/dist/index.js.map +1 -1
- package/dist/player/BasePlayer.d.ts.map +1 -1
- package/dist/player/EcPlayerCore.d.ts.map +1 -1
- package/dist/player/FLVPlayer.d.ts.map +1 -1
- package/dist/player/HLSPlayer.d.ts +18 -1
- package/dist/player/HLSPlayer.d.ts.map +1 -1
- package/dist/prefetch/SegmentPrefetcher.d.ts +8 -2
- package/dist/prefetch/SegmentPrefetcher.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 -1
package/dist/index.js
CHANGED
|
@@ -147,6 +147,7 @@ class BasePlayer {
|
|
|
147
147
|
droppedFrames: 0,
|
|
148
148
|
isPrefetching: false,
|
|
149
149
|
segmentIndex: 0,
|
|
150
|
+
fetchedSegmentCount: 0,
|
|
150
151
|
totalSegments: 0,
|
|
151
152
|
downloadSpeed: 0,
|
|
152
153
|
currentTime: 0,
|
|
@@ -227,6 +228,7 @@ class BasePlayer {
|
|
|
227
228
|
droppedFrames: 0,
|
|
228
229
|
isPrefetching: false,
|
|
229
230
|
segmentIndex: 0,
|
|
231
|
+
fetchedSegmentCount: 0,
|
|
230
232
|
totalSegments: 0,
|
|
231
233
|
downloadSpeed: 0,
|
|
232
234
|
currentTime: 0,
|
|
@@ -413,6 +415,8 @@ class fMP4Demuxer {
|
|
|
413
415
|
this.timescale = 1e3;
|
|
414
416
|
this.trackId = 1;
|
|
415
417
|
this.avcC = null;
|
|
418
|
+
this.hvcC = null;
|
|
419
|
+
this.isHevc = false;
|
|
416
420
|
}
|
|
417
421
|
/**
|
|
418
422
|
* 解析初始化段 (ftyp + moov)
|
|
@@ -431,7 +435,9 @@ class fMP4Demuxer {
|
|
|
431
435
|
return {
|
|
432
436
|
trackId: this.trackId,
|
|
433
437
|
timescale: this.timescale,
|
|
434
|
-
avcC: this.avcC || void 0
|
|
438
|
+
avcC: this.avcC || void 0,
|
|
439
|
+
hvcC: this.hvcC || void 0,
|
|
440
|
+
isHevc: this.isHevc
|
|
435
441
|
};
|
|
436
442
|
}
|
|
437
443
|
/**
|
|
@@ -456,33 +462,53 @@ class fMP4Demuxer {
|
|
|
456
462
|
parseTrak(data, trakOffset, trakSize) {
|
|
457
463
|
let offset = trakOffset + 8;
|
|
458
464
|
const endOffset = trakOffset + trakSize;
|
|
465
|
+
let currentTrackId = null;
|
|
466
|
+
let isVideoTrack = false;
|
|
459
467
|
while (offset < endOffset - 8) {
|
|
460
468
|
const boxSize = readUint32$1(data, offset);
|
|
461
469
|
const boxType = readFourCC(data, offset + 4);
|
|
462
470
|
if (boxSize < 8) break;
|
|
463
|
-
if (boxType === "
|
|
464
|
-
|
|
471
|
+
if (boxType === "tkhd") {
|
|
472
|
+
const boxDataStart = offset + 8;
|
|
473
|
+
const version = data[boxDataStart];
|
|
474
|
+
if (version === 1) {
|
|
475
|
+
currentTrackId = readUint32$1(data, boxDataStart + 1 + 3 + 8 + 8);
|
|
476
|
+
} else {
|
|
477
|
+
currentTrackId = readUint32$1(data, boxDataStart + 1 + 3 + 4 + 4);
|
|
478
|
+
}
|
|
479
|
+
console.log("[fMP4Demuxer] tkhd: version=", version, "trackId=", currentTrackId);
|
|
480
|
+
} else if (boxType === "mdia") {
|
|
481
|
+
isVideoTrack = this.parseMdiaAndCheckVideo(data, offset, boxSize);
|
|
482
|
+
if (isVideoTrack && currentTrackId !== null) {
|
|
483
|
+
this.trackId = currentTrackId;
|
|
484
|
+
console.log("[fMP4Demuxer] Found video track, trackId:", currentTrackId);
|
|
485
|
+
}
|
|
465
486
|
}
|
|
466
487
|
offset += boxSize;
|
|
467
488
|
}
|
|
468
489
|
}
|
|
469
490
|
/**
|
|
470
|
-
* 解析 mdia box
|
|
491
|
+
* 解析 mdia box 并检查是否为视频轨道
|
|
471
492
|
*/
|
|
472
|
-
|
|
493
|
+
parseMdiaAndCheckVideo(data, mdiaOffset, mdiaSize) {
|
|
473
494
|
let offset = mdiaOffset + 8;
|
|
474
495
|
const endOffset = mdiaOffset + mdiaSize;
|
|
496
|
+
let isVideo = false;
|
|
475
497
|
while (offset < endOffset - 8) {
|
|
476
498
|
const boxSize = readUint32$1(data, offset);
|
|
477
499
|
const boxType = readFourCC(data, offset + 4);
|
|
478
500
|
if (boxSize < 8) break;
|
|
479
501
|
if (boxType === "mdhd") {
|
|
480
502
|
this.parseMdhd(data, offset + 8);
|
|
481
|
-
} else if (boxType === "
|
|
503
|
+
} else if (boxType === "hdlr") {
|
|
504
|
+
const handlerType = readFourCC(data, offset + 8 + 8);
|
|
505
|
+
isVideo = handlerType === "vide";
|
|
506
|
+
} else if (boxType === "minf" && isVideo) {
|
|
482
507
|
this.parseMinf(data, offset, boxSize);
|
|
483
508
|
}
|
|
484
509
|
offset += boxSize;
|
|
485
510
|
}
|
|
511
|
+
return isVideo;
|
|
486
512
|
}
|
|
487
513
|
/**
|
|
488
514
|
* 解析 mdhd box 获取 timescale
|
|
@@ -528,7 +554,7 @@ class fMP4Demuxer {
|
|
|
528
554
|
}
|
|
529
555
|
}
|
|
530
556
|
/**
|
|
531
|
-
* 解析 stsd box 获取 avcC
|
|
557
|
+
* 解析 stsd box 获取 avcC 或 hvcC
|
|
532
558
|
*/
|
|
533
559
|
parseStsd(data, stsdOffset, stsdSize) {
|
|
534
560
|
const boxDataOffset = stsdOffset + 8;
|
|
@@ -542,6 +568,10 @@ class fMP4Demuxer {
|
|
|
542
568
|
this.parseAvcSampleEntry(data, offset, entrySize);
|
|
543
569
|
return;
|
|
544
570
|
}
|
|
571
|
+
if (entryType === "hvc1" || entryType === "hev1") {
|
|
572
|
+
this.parseHevcSampleEntry(data, offset, entrySize);
|
|
573
|
+
return;
|
|
574
|
+
}
|
|
545
575
|
offset += entrySize;
|
|
546
576
|
}
|
|
547
577
|
}
|
|
@@ -562,6 +592,25 @@ class fMP4Demuxer {
|
|
|
562
592
|
offset += boxSize;
|
|
563
593
|
}
|
|
564
594
|
}
|
|
595
|
+
/**
|
|
596
|
+
* 解析 HEVC Sample Entry
|
|
597
|
+
*/
|
|
598
|
+
parseHevcSampleEntry(data, entryOffset, entrySize) {
|
|
599
|
+
this.isHevc = true;
|
|
600
|
+
let offset = entryOffset + 8 + 78;
|
|
601
|
+
const endOffset = entryOffset + entrySize;
|
|
602
|
+
while (offset < endOffset - 8) {
|
|
603
|
+
const boxSize = readUint32$1(data, offset);
|
|
604
|
+
const boxType = readFourCC(data, offset + 4);
|
|
605
|
+
if (boxSize < 8) break;
|
|
606
|
+
if (boxType === "hvcC") {
|
|
607
|
+
this.hvcC = data.slice(offset, offset + boxSize);
|
|
608
|
+
console.log("[fMP4Demuxer] Found hvcC, size:", boxSize);
|
|
609
|
+
return;
|
|
610
|
+
}
|
|
611
|
+
offset += boxSize;
|
|
612
|
+
}
|
|
613
|
+
}
|
|
565
614
|
/**
|
|
566
615
|
* 解析 moof box
|
|
567
616
|
*/
|
|
@@ -712,11 +761,16 @@ class fMP4Demuxer {
|
|
|
712
761
|
}
|
|
713
762
|
/**
|
|
714
763
|
* 从 moof + mdat 提取视频样本
|
|
764
|
+
* 只提取视频轨道(trackId 匹配)的数据
|
|
715
765
|
*/
|
|
716
766
|
extractSamples(moof, mdatData, mdatOffset) {
|
|
717
767
|
const samples = [];
|
|
718
768
|
for (const traf of moof.trafs) {
|
|
719
769
|
if (!traf.tfhd || !traf.tfdt) continue;
|
|
770
|
+
if (traf.tfhd.trackId !== this.trackId) {
|
|
771
|
+
console.log(`[fMP4Demuxer] Skipping track ${traf.tfhd.trackId} (expected video track ${this.trackId})`);
|
|
772
|
+
continue;
|
|
773
|
+
}
|
|
720
774
|
let baseDts = traf.tfdt.baseMediaDecodeTime;
|
|
721
775
|
for (const trun of traf.truns) {
|
|
722
776
|
let dataOffset = 0;
|
|
@@ -755,6 +809,18 @@ class fMP4Demuxer {
|
|
|
755
809
|
getAvcC() {
|
|
756
810
|
return this.avcC;
|
|
757
811
|
}
|
|
812
|
+
/**
|
|
813
|
+
* 获取 hvcC 配置
|
|
814
|
+
*/
|
|
815
|
+
getHvcC() {
|
|
816
|
+
return this.hvcC;
|
|
817
|
+
}
|
|
818
|
+
/**
|
|
819
|
+
* 是否为 HEVC 编码
|
|
820
|
+
*/
|
|
821
|
+
isHevcStream() {
|
|
822
|
+
return this.isHevc;
|
|
823
|
+
}
|
|
758
824
|
}
|
|
759
825
|
class NALParser {
|
|
760
826
|
/**
|
|
@@ -802,6 +868,93 @@ class NALParser {
|
|
|
802
868
|
return units;
|
|
803
869
|
}
|
|
804
870
|
}
|
|
871
|
+
const HEVC_NAL_TYPE = {
|
|
872
|
+
// Picture Parameter Set
|
|
873
|
+
IDR_W_RADL: 19
|
|
874
|
+
};
|
|
875
|
+
class HEVCDecoder {
|
|
876
|
+
constructor(wasmLoader) {
|
|
877
|
+
this.wasmModule = null;
|
|
878
|
+
this.decoderContext = null;
|
|
879
|
+
this.wasmModule = wasmLoader.getModule();
|
|
880
|
+
}
|
|
881
|
+
async init() {
|
|
882
|
+
if (!this.wasmModule) {
|
|
883
|
+
throw new Error("WASM module not loaded");
|
|
884
|
+
}
|
|
885
|
+
this.decoderContext = this.wasmModule._create_decoder(173);
|
|
886
|
+
if (this.decoderContext === 0 || this.decoderContext === null) {
|
|
887
|
+
throw new Error("Failed to create HEVC decoder context");
|
|
888
|
+
}
|
|
889
|
+
console.log("[HEVCDecoder] Initialized with codec_id=173");
|
|
890
|
+
}
|
|
891
|
+
decode(nalUnit) {
|
|
892
|
+
if (this.decoderContext === null || !this.wasmModule) {
|
|
893
|
+
return null;
|
|
894
|
+
}
|
|
895
|
+
const dataPtr = this.wasmModule._malloc(nalUnit.size);
|
|
896
|
+
this.wasmModule.HEAPU8.set(nalUnit.data, dataPtr);
|
|
897
|
+
const result = this.wasmModule._decode_video(
|
|
898
|
+
this.decoderContext,
|
|
899
|
+
dataPtr,
|
|
900
|
+
nalUnit.size
|
|
901
|
+
);
|
|
902
|
+
this.wasmModule._free(dataPtr);
|
|
903
|
+
if (result === 1) {
|
|
904
|
+
const width = this.wasmModule._get_frame_width(this.decoderContext);
|
|
905
|
+
const height = this.wasmModule._get_frame_height(this.decoderContext);
|
|
906
|
+
if (width <= 0 || height <= 0) {
|
|
907
|
+
return null;
|
|
908
|
+
}
|
|
909
|
+
const yPtr = this.wasmModule._get_frame_data(this.decoderContext, 0);
|
|
910
|
+
const uPtr = this.wasmModule._get_frame_data(this.decoderContext, 1);
|
|
911
|
+
const vPtr = this.wasmModule._get_frame_data(this.decoderContext, 2);
|
|
912
|
+
const yLineSize = this.wasmModule._get_frame_linesize(this.decoderContext, 0);
|
|
913
|
+
const uLineSize = this.wasmModule._get_frame_linesize(this.decoderContext, 1);
|
|
914
|
+
const vLineSize = this.wasmModule._get_frame_linesize(this.decoderContext, 2);
|
|
915
|
+
const frameData = this.yuv420pToRgba(
|
|
916
|
+
yPtr,
|
|
917
|
+
uPtr,
|
|
918
|
+
vPtr,
|
|
919
|
+
width,
|
|
920
|
+
height,
|
|
921
|
+
yLineSize,
|
|
922
|
+
uLineSize,
|
|
923
|
+
vLineSize
|
|
924
|
+
);
|
|
925
|
+
return { width, height, data: frameData };
|
|
926
|
+
}
|
|
927
|
+
return null;
|
|
928
|
+
}
|
|
929
|
+
yuv420pToRgba(yPtr, uPtr, vPtr, width, height, yLineSize, uLineSize, vLineSize) {
|
|
930
|
+
const rgba = new Uint8Array(width * height * 4);
|
|
931
|
+
for (let y = 0; y < height; y++) {
|
|
932
|
+
for (let x = 0; x < width; x++) {
|
|
933
|
+
const yIndex = y * yLineSize + x;
|
|
934
|
+
const uIndex = (y >> 1) * uLineSize + (x >> 1);
|
|
935
|
+
const vIndex = (y >> 1) * vLineSize + (x >> 1);
|
|
936
|
+
const yValue = this.wasmModule.HEAPU8[yPtr + yIndex];
|
|
937
|
+
const uValue = this.wasmModule.HEAPU8[uPtr + uIndex];
|
|
938
|
+
const vValue = this.wasmModule.HEAPU8[vPtr + vIndex];
|
|
939
|
+
const c = yValue - 16;
|
|
940
|
+
const d = uValue - 128;
|
|
941
|
+
const e = vValue - 128;
|
|
942
|
+
let r = 298 * c + 409 * e + 128 >> 8;
|
|
943
|
+
let g = 298 * c - 100 * d - 208 * e + 128 >> 8;
|
|
944
|
+
let b = 298 * c + 516 * d + 128 >> 8;
|
|
945
|
+
r = r < 0 ? 0 : r > 255 ? 255 : r;
|
|
946
|
+
g = g < 0 ? 0 : g > 255 ? 255 : g;
|
|
947
|
+
b = b < 0 ? 0 : b > 255 ? 255 : b;
|
|
948
|
+
const rgbaIndex = (y * width + x) * 4;
|
|
949
|
+
rgba[rgbaIndex] = r;
|
|
950
|
+
rgba[rgbaIndex + 1] = g;
|
|
951
|
+
rgba[rgbaIndex + 2] = b;
|
|
952
|
+
rgba[rgbaIndex + 3] = 255;
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
return rgba;
|
|
956
|
+
}
|
|
957
|
+
}
|
|
805
958
|
class BasePrefetcher {
|
|
806
959
|
constructor(config, callbacks = {}) {
|
|
807
960
|
this.isRunning = false;
|
|
@@ -973,6 +1126,7 @@ class SegmentPrefetcher extends BasePrefetcher {
|
|
|
973
1126
|
this.prefetchQueue = [];
|
|
974
1127
|
this.segments = [];
|
|
975
1128
|
this.currentSegmentIndex = 0;
|
|
1129
|
+
this.fetchedSegmentCount = 0;
|
|
976
1130
|
this.baseUrl = "";
|
|
977
1131
|
this.isPrefetchingSegment = false;
|
|
978
1132
|
}
|
|
@@ -984,7 +1138,8 @@ class SegmentPrefetcher extends BasePrefetcher {
|
|
|
984
1138
|
...super.createInitialState(),
|
|
985
1139
|
currentSegmentIndex: 0,
|
|
986
1140
|
totalSegments: 0,
|
|
987
|
-
prefetchQueueSize: 0
|
|
1141
|
+
prefetchQueueSize: 0,
|
|
1142
|
+
fetchedSegmentCount: 0
|
|
988
1143
|
};
|
|
989
1144
|
}
|
|
990
1145
|
/**
|
|
@@ -998,7 +1153,8 @@ class SegmentPrefetcher extends BasePrefetcher {
|
|
|
998
1153
|
this.updateStatus({
|
|
999
1154
|
totalSegments: segments.length,
|
|
1000
1155
|
currentSegmentIndex: 0,
|
|
1001
|
-
prefetchQueueSize: 0
|
|
1156
|
+
prefetchQueueSize: 0,
|
|
1157
|
+
fetchedSegmentCount: 0
|
|
1002
1158
|
});
|
|
1003
1159
|
console.log(`[SegmentPrefetcher] Set ${segments.length} segments, baseUrl: ${baseUrl}`);
|
|
1004
1160
|
}
|
|
@@ -1010,7 +1166,8 @@ class SegmentPrefetcher extends BasePrefetcher {
|
|
|
1010
1166
|
this.prefetchQueue = [];
|
|
1011
1167
|
this.updateStatus({
|
|
1012
1168
|
currentSegmentIndex: index,
|
|
1013
|
-
prefetchQueueSize: 0
|
|
1169
|
+
prefetchQueueSize: 0,
|
|
1170
|
+
fetchedSegmentCount: 0
|
|
1014
1171
|
});
|
|
1015
1172
|
console.log(`[SegmentPrefetcher] Current segment index set to ${index}`);
|
|
1016
1173
|
}
|
|
@@ -1052,7 +1209,11 @@ class SegmentPrefetcher extends BasePrefetcher {
|
|
|
1052
1209
|
segmentIndex: nextIndex,
|
|
1053
1210
|
fetchTime
|
|
1054
1211
|
});
|
|
1055
|
-
this.
|
|
1212
|
+
this.fetchedSegmentCount++;
|
|
1213
|
+
this.updateStatus({
|
|
1214
|
+
prefetchQueueSize: this.prefetchQueue.length,
|
|
1215
|
+
fetchedSegmentCount: this.fetchedSegmentCount
|
|
1216
|
+
});
|
|
1056
1217
|
const callbacks = this.callbacks;
|
|
1057
1218
|
callbacks.onSegmentFetched?.(nextIndex, data);
|
|
1058
1219
|
console.log(`[SegmentPrefetcher] Fetched segment #${nextIndex}: ${data.length} bytes, ${fetchTime.toFixed(0)}ms`);
|
|
@@ -1324,12 +1485,18 @@ const DEFAULT_HLS_CONFIG = {
|
|
|
1324
1485
|
class HLSSegmentPrefetcher extends SegmentPrefetcher {
|
|
1325
1486
|
constructor(config, callbacks, player) {
|
|
1326
1487
|
super(config, callbacks);
|
|
1488
|
+
this.currentInitSegmentUri = null;
|
|
1327
1489
|
this.player = player;
|
|
1328
1490
|
}
|
|
1329
1491
|
/**
|
|
1330
1492
|
* 获取分片数据
|
|
1331
1493
|
*/
|
|
1332
1494
|
async fetchSegment(segment, index) {
|
|
1495
|
+
if (segment.initSegmentUri && segment.initSegmentUri !== this.currentInitSegmentUri) {
|
|
1496
|
+
console.log("[HLSSegmentPrefetcher] Init segment changed:", segment.initSegmentUri);
|
|
1497
|
+
await this.player.loadNewInitSegment(segment.initSegmentUri);
|
|
1498
|
+
this.currentInitSegmentUri = segment.initSegmentUri;
|
|
1499
|
+
}
|
|
1333
1500
|
const baseUrl = this.baseUrl;
|
|
1334
1501
|
const url = segment.uri.startsWith("http") ? segment.uri : baseUrl + segment.uri;
|
|
1335
1502
|
const headers = {};
|
|
@@ -1368,26 +1535,24 @@ class HLSPlayer extends BasePlayer {
|
|
|
1368
1535
|
this.pesExtractor = new PESExtractor();
|
|
1369
1536
|
this.nalParser = new NALParser();
|
|
1370
1537
|
this.fmp4Demuxer = new fMP4Demuxer();
|
|
1538
|
+
this.hevcDecoder = null;
|
|
1371
1539
|
this.isPrefetching = false;
|
|
1372
1540
|
this.currentSegmentIndex = 0;
|
|
1373
1541
|
this.segments = [];
|
|
1374
1542
|
this.fmp4Segments = [];
|
|
1375
1543
|
this.initSegment = null;
|
|
1376
1544
|
this._isFMP4 = false;
|
|
1545
|
+
this._isHevc = false;
|
|
1377
1546
|
this.currentPlaylistUrl = "";
|
|
1378
1547
|
this._nalQueue = [];
|
|
1379
1548
|
this._sampleQueue = [];
|
|
1380
1549
|
this.prefetcher = null;
|
|
1381
|
-
this.
|
|
1550
|
+
this.lastRenderTime = 0;
|
|
1551
|
+
this.renderLoop = (timestamp = 0) => {
|
|
1382
1552
|
if (!this.isPlaying) return;
|
|
1383
1553
|
const downloaded = this.isFMP4 ? this.sampleQueue.length : this.nalQueue.length;
|
|
1384
1554
|
const segments = this.isFMP4 ? this.fmp4Segments : this.segments;
|
|
1385
1555
|
const totalSegments = segments.length;
|
|
1386
|
-
let currentTime = 0;
|
|
1387
|
-
for (let i = 0; i < this.currentSegmentIndex && i < segments.length; i++) {
|
|
1388
|
-
currentTime += segments[i].duration * 1e3;
|
|
1389
|
-
}
|
|
1390
|
-
this.setCurrentTime(currentTime);
|
|
1391
1556
|
this.updateState({
|
|
1392
1557
|
decoded: this.frameBuffer.length,
|
|
1393
1558
|
downloaded,
|
|
@@ -1398,10 +1563,17 @@ class HLSPlayer extends BasePlayer {
|
|
|
1398
1563
|
if (this.frameBuffer.length > 0 && this.renderer) {
|
|
1399
1564
|
const frame = this.frameBuffer.shift();
|
|
1400
1565
|
this.renderer.render(frame);
|
|
1566
|
+
if (this.lastRenderTime > 0) {
|
|
1567
|
+
const elapsed = timestamp - this.lastRenderTime;
|
|
1568
|
+
this.setCurrentTime(this._currentTime + elapsed);
|
|
1569
|
+
}
|
|
1570
|
+
this.lastRenderTime = timestamp;
|
|
1401
1571
|
this.updateState({ resolution: `${frame.width}x${frame.height}` });
|
|
1402
1572
|
const fps = this.renderer.updateFps();
|
|
1403
1573
|
this.updateState({ fps });
|
|
1404
1574
|
this.callbacks.onFrameRender?.(frame);
|
|
1575
|
+
} else {
|
|
1576
|
+
this.lastRenderTime = 0;
|
|
1405
1577
|
}
|
|
1406
1578
|
this.frameTimer = requestAnimationFrame(this.renderLoop);
|
|
1407
1579
|
};
|
|
@@ -1410,6 +1582,9 @@ class HLSPlayer extends BasePlayer {
|
|
|
1410
1582
|
get isFMP4() {
|
|
1411
1583
|
return this._isFMP4;
|
|
1412
1584
|
}
|
|
1585
|
+
get isHevc() {
|
|
1586
|
+
return this._isHevc;
|
|
1587
|
+
}
|
|
1413
1588
|
get nalQueue() {
|
|
1414
1589
|
return this._nalQueue;
|
|
1415
1590
|
}
|
|
@@ -1475,8 +1650,11 @@ class HLSPlayer extends BasePlayer {
|
|
|
1475
1650
|
async play() {
|
|
1476
1651
|
this.isPlaying = true;
|
|
1477
1652
|
this.decodeLoopAbort = false;
|
|
1653
|
+
this.lastRenderTime = 0;
|
|
1478
1654
|
this.updateState({ isPlaying: true });
|
|
1479
|
-
this.
|
|
1655
|
+
if (!this.prefetcher) {
|
|
1656
|
+
this.initPrefetcher();
|
|
1657
|
+
}
|
|
1480
1658
|
this.prefetcher?.start();
|
|
1481
1659
|
this.decodeLoop();
|
|
1482
1660
|
this.frameTimer = requestAnimationFrame(this.renderLoop);
|
|
@@ -1569,9 +1747,11 @@ class HLSPlayer extends BasePlayer {
|
|
|
1569
1747
|
HLS_PREFETCHER_CONFIG,
|
|
1570
1748
|
{
|
|
1571
1749
|
onStatusChange: (status) => {
|
|
1750
|
+
const segStatus = status;
|
|
1572
1751
|
this.updateState({
|
|
1573
1752
|
isPrefetching: status.isPrefetching,
|
|
1574
|
-
downloadSpeed: status.downloadSpeed
|
|
1753
|
+
downloadSpeed: status.downloadSpeed,
|
|
1754
|
+
fetchedSegmentCount: segStatus.fetchedSegmentCount ?? 0
|
|
1575
1755
|
});
|
|
1576
1756
|
},
|
|
1577
1757
|
onSegmentParsed: (segmentIndex, itemCount) => {
|
|
@@ -1586,7 +1766,9 @@ class HLSPlayer extends BasePlayer {
|
|
|
1586
1766
|
const segmentInfos = this.fmp4Segments.map((seg) => ({
|
|
1587
1767
|
uri: seg.uri,
|
|
1588
1768
|
duration: seg.duration,
|
|
1589
|
-
byteRange: seg.byteRange
|
|
1769
|
+
byteRange: seg.byteRange,
|
|
1770
|
+
initSegmentUri: seg.initSegmentUri
|
|
1771
|
+
// 传递初始化段 URI
|
|
1590
1772
|
}));
|
|
1591
1773
|
this.prefetcher.setSegments(segmentInfos, baseUrl);
|
|
1592
1774
|
} else {
|
|
@@ -1602,33 +1784,46 @@ class HLSPlayer extends BasePlayer {
|
|
|
1602
1784
|
* 解析 fMP4 数据
|
|
1603
1785
|
*/
|
|
1604
1786
|
parseFMP4Data(data) {
|
|
1605
|
-
let
|
|
1606
|
-
let mdatOffset = -1;
|
|
1607
|
-
let mdatSize = 0;
|
|
1787
|
+
let totalSampleCount = 0;
|
|
1608
1788
|
let offset = 0;
|
|
1609
1789
|
while (offset < data.length - 8) {
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1790
|
+
let moofOffset = -1;
|
|
1791
|
+
let mdatOffset = -1;
|
|
1792
|
+
let mdatSize = 0;
|
|
1793
|
+
while (offset < data.length - 8) {
|
|
1794
|
+
const boxSize = this.readBoxSize(data, offset);
|
|
1795
|
+
const boxType = this.readBoxType(data, offset + 4);
|
|
1796
|
+
if (boxSize < 8) break;
|
|
1797
|
+
if (boxType === "moof") {
|
|
1798
|
+
moofOffset = offset;
|
|
1799
|
+
offset += boxSize;
|
|
1800
|
+
break;
|
|
1801
|
+
}
|
|
1802
|
+
offset += boxSize;
|
|
1803
|
+
}
|
|
1804
|
+
if (moofOffset < 0) break;
|
|
1805
|
+
while (offset < data.length - 8) {
|
|
1806
|
+
const boxSize = this.readBoxSize(data, offset);
|
|
1807
|
+
const boxType = this.readBoxType(data, offset + 4);
|
|
1808
|
+
if (boxSize < 8) break;
|
|
1809
|
+
if (boxType === "mdat") {
|
|
1810
|
+
mdatOffset = offset;
|
|
1811
|
+
mdatSize = boxSize;
|
|
1812
|
+
offset += boxSize;
|
|
1813
|
+
break;
|
|
1814
|
+
}
|
|
1815
|
+
offset += boxSize;
|
|
1618
1816
|
}
|
|
1619
|
-
|
|
1620
|
-
}
|
|
1621
|
-
let sampleCount = 0;
|
|
1622
|
-
if (moofOffset >= 0 && mdatOffset >= 0) {
|
|
1817
|
+
if (mdatOffset < 0) break;
|
|
1623
1818
|
const moof = this.fmp4Demuxer.parseMoof(data, moofOffset);
|
|
1624
1819
|
const mdatData = data.slice(mdatOffset + 8, mdatOffset + mdatSize);
|
|
1625
1820
|
const samples = this.fmp4Demuxer.extractSamples(moof, mdatData, mdatOffset);
|
|
1626
1821
|
for (const sample of samples) {
|
|
1627
1822
|
this._sampleQueue.push({ sample });
|
|
1628
1823
|
}
|
|
1629
|
-
|
|
1824
|
+
totalSampleCount += samples.length;
|
|
1630
1825
|
}
|
|
1631
|
-
return
|
|
1826
|
+
return totalSampleCount;
|
|
1632
1827
|
}
|
|
1633
1828
|
/**
|
|
1634
1829
|
* 解析 TS 数据
|
|
@@ -1672,16 +1867,75 @@ class HLSPlayer extends BasePlayer {
|
|
|
1672
1867
|
const data = new Uint8Array(await response.arrayBuffer());
|
|
1673
1868
|
console.log("[fMP4] Init segment data size:", data.length, "bytes");
|
|
1674
1869
|
const initInfo = this.fmp4Demuxer.parseInitSegment(data);
|
|
1675
|
-
console.log("[fMP4] Parse result:",
|
|
1676
|
-
|
|
1870
|
+
console.log("[fMP4] Parse result:", {
|
|
1871
|
+
isHevc: initInfo?.isHevc,
|
|
1872
|
+
hasAvcC: !!initInfo?.avcC,
|
|
1873
|
+
hasHvcC: !!initInfo?.hvcC,
|
|
1874
|
+
timescale: initInfo?.timescale
|
|
1875
|
+
});
|
|
1876
|
+
this._isHevc = initInfo?.isHevc ?? false;
|
|
1877
|
+
if (initInfo?.hvcC && this._isHevc) {
|
|
1878
|
+
console.log("[fMP4] hvcC size:", initInfo.hvcC.length);
|
|
1879
|
+
await this.initHevcDecoder();
|
|
1880
|
+
this.initDecoderFromHvcC(initInfo.hvcC);
|
|
1881
|
+
console.log("[fMP4] HEVC decoder initialized, timescale:", initInfo.timescale);
|
|
1882
|
+
} else if (initInfo?.avcC) {
|
|
1677
1883
|
console.log("[fMP4] avcC size:", initInfo.avcC.length);
|
|
1678
1884
|
this.initDecoderFromAvcC(initInfo.avcC);
|
|
1679
|
-
console.log("[fMP4]
|
|
1885
|
+
console.log("[fMP4] AVC decoder initialized, timescale:", initInfo.timescale);
|
|
1886
|
+
} else {
|
|
1887
|
+
console.error("[fMP4] Failed to parse init segment or no avcC/hvcC found!");
|
|
1888
|
+
throw new Error("Failed to parse fMP4 init segment: no valid codec config found");
|
|
1889
|
+
}
|
|
1890
|
+
}
|
|
1891
|
+
/**
|
|
1892
|
+
* 加载新的初始化段(用于不连续性流)
|
|
1893
|
+
*/
|
|
1894
|
+
async loadNewInitSegment(uri) {
|
|
1895
|
+
console.log("[fMP4] Loading new init segment for discontinuity:", uri);
|
|
1896
|
+
this._sampleQueue.length = 0;
|
|
1897
|
+
this._nalQueue.length = 0;
|
|
1898
|
+
this.frameBuffer = [];
|
|
1899
|
+
const url = uri.startsWith("http") ? uri : this.currentPlaylistUrl.substring(0, this.currentPlaylistUrl.lastIndexOf("/") + 1) + uri;
|
|
1900
|
+
const response = await fetch(url);
|
|
1901
|
+
const data = new Uint8Array(await response.arrayBuffer());
|
|
1902
|
+
console.log("[fMP4] New init segment data size:", data.length, "bytes");
|
|
1903
|
+
const initInfo = this.fmp4Demuxer.parseInitSegment(data);
|
|
1904
|
+
console.log("[fMP4] New init segment parse result:", {
|
|
1905
|
+
isHevc: initInfo?.isHevc,
|
|
1906
|
+
hasAvcC: !!initInfo?.avcC,
|
|
1907
|
+
hasHvcC: !!initInfo?.hvcC
|
|
1908
|
+
});
|
|
1909
|
+
const newIsHevc = initInfo?.isHevc ?? false;
|
|
1910
|
+
if (newIsHevc !== this._isHevc) {
|
|
1911
|
+
console.log("[fMP4] Codec type changed from", this._isHevc ? "HEVC" : "AVC", "to", newIsHevc ? "HEVC" : "AVC");
|
|
1912
|
+
this._isHevc = newIsHevc;
|
|
1913
|
+
this.decoderInitialized = false;
|
|
1914
|
+
if (newIsHevc) {
|
|
1915
|
+
await this.initHevcDecoder();
|
|
1916
|
+
}
|
|
1917
|
+
} else {
|
|
1918
|
+
this.decoderInitialized = false;
|
|
1919
|
+
}
|
|
1920
|
+
if (initInfo?.hvcC && this._isHevc) {
|
|
1921
|
+
this.initDecoderFromHvcC(initInfo.hvcC);
|
|
1922
|
+
console.log("[fMP4] HEVC decoder re-initialized for discontinuity");
|
|
1923
|
+
} else if (initInfo?.avcC) {
|
|
1924
|
+
this.initDecoderFromAvcC(initInfo.avcC);
|
|
1925
|
+
console.log("[fMP4] AVC decoder re-initialized for discontinuity");
|
|
1680
1926
|
} else {
|
|
1681
|
-
console.error("[fMP4] Failed to parse init segment
|
|
1682
|
-
throw new Error("Failed to parse fMP4 init segment");
|
|
1927
|
+
console.error("[fMP4] Failed to parse new init segment!");
|
|
1683
1928
|
}
|
|
1684
1929
|
}
|
|
1930
|
+
/**
|
|
1931
|
+
* 初始化 HEVC 解码器
|
|
1932
|
+
*/
|
|
1933
|
+
async initHevcDecoder() {
|
|
1934
|
+
if (this.hevcDecoder) return;
|
|
1935
|
+
this.hevcDecoder = new HEVCDecoder(this.wasmLoader);
|
|
1936
|
+
await this.hevcDecoder.init();
|
|
1937
|
+
console.log("[fMP4] HEVC decoder created");
|
|
1938
|
+
}
|
|
1685
1939
|
/**
|
|
1686
1940
|
* 从 avcC 初始化解码器
|
|
1687
1941
|
*/
|
|
@@ -1726,6 +1980,40 @@ class HLSPlayer extends BasePlayer {
|
|
|
1726
1980
|
this.decoderInitialized = true;
|
|
1727
1981
|
console.log("[fMP4] Decoder initialized from avcC");
|
|
1728
1982
|
}
|
|
1983
|
+
/**
|
|
1984
|
+
* 从 hvcC 初始化 HEVC 解码器
|
|
1985
|
+
*/
|
|
1986
|
+
initDecoderFromHvcC(hvcC) {
|
|
1987
|
+
if (this.decoderInitialized || !this.hevcDecoder) return;
|
|
1988
|
+
let offset = 22;
|
|
1989
|
+
const numOfArrays = hvcC[offset];
|
|
1990
|
+
offset += 1;
|
|
1991
|
+
console.log(`[fMP4] hvcC: numOfArrays=${numOfArrays}`);
|
|
1992
|
+
for (let i = 0; i < numOfArrays && offset < hvcC.length - 3; i++) {
|
|
1993
|
+
const typeCompressed = hvcC[offset];
|
|
1994
|
+
const arrayType = typeCompressed & 63;
|
|
1995
|
+
const numNalus = hvcC[offset + 1] << 8 | hvcC[offset + 2];
|
|
1996
|
+
offset += 3;
|
|
1997
|
+
console.log(`[fMP4] HEVC array: type=${arrayType}, count=${numNalus}`);
|
|
1998
|
+
for (let j = 0; j < numNalus && offset < hvcC.length - 2; j++) {
|
|
1999
|
+
const nalUnitLength = hvcC[offset] << 8 | hvcC[offset + 1];
|
|
2000
|
+
offset += 2;
|
|
2001
|
+
if (offset + nalUnitLength > hvcC.length) break;
|
|
2002
|
+
const nalUnitData = hvcC.slice(offset, offset + nalUnitLength);
|
|
2003
|
+
offset += nalUnitLength;
|
|
2004
|
+
const nalWithStartCode = new Uint8Array(4 + nalUnitLength);
|
|
2005
|
+
nalWithStartCode.set([0, 0, 0, 1], 0);
|
|
2006
|
+
nalWithStartCode.set(nalUnitData, 4);
|
|
2007
|
+
if (arrayType === 32 || arrayType === 33 || arrayType === 34) {
|
|
2008
|
+
this.hevcDecoder.decode({ type: arrayType, data: nalWithStartCode, size: nalWithStartCode.length });
|
|
2009
|
+
const typeName = arrayType === 32 ? "VPS" : arrayType === 33 ? "SPS" : "PPS";
|
|
2010
|
+
console.log(`[fMP4] HEVC ${typeName} decoded, length=${nalUnitLength}`);
|
|
2011
|
+
}
|
|
2012
|
+
}
|
|
2013
|
+
}
|
|
2014
|
+
this.decoderInitialized = true;
|
|
2015
|
+
console.log("[fMP4] HEVC decoder initialized from hvcC");
|
|
2016
|
+
}
|
|
1729
2017
|
/**
|
|
1730
2018
|
* 解析 HLS 播放列表
|
|
1731
2019
|
*/
|
|
@@ -1758,25 +2046,31 @@ class HLSPlayer extends BasePlayer {
|
|
|
1758
2046
|
}
|
|
1759
2047
|
const isFMP4 = lines.some((line) => line.includes("#EXT-X-MAP:"));
|
|
1760
2048
|
console.log("[parsePlaylist] isFMP4:", isFMP4, "url:", url);
|
|
2049
|
+
let currentInitSegment;
|
|
1761
2050
|
for (let i = 0; i < lines.length; i++) {
|
|
1762
2051
|
const line = lines[i].trim();
|
|
1763
2052
|
if (line.startsWith("#EXT-X-MAP:")) {
|
|
1764
2053
|
const mapInfo = line.substring("#EXT-X-MAP:".length);
|
|
1765
2054
|
const uriMatch = mapInfo.match(/URI="([^"]+)"/);
|
|
1766
2055
|
if (uriMatch) {
|
|
1767
|
-
|
|
2056
|
+
currentInitSegment = { uri: uriMatch[1] };
|
|
1768
2057
|
const byteRangeMatch = mapInfo.match(/BYTERANGE="(\d+)@(\d+)"/);
|
|
1769
2058
|
if (byteRangeMatch) {
|
|
1770
|
-
|
|
2059
|
+
currentInitSegment.byteRange = {
|
|
1771
2060
|
start: parseInt(byteRangeMatch[2]),
|
|
1772
2061
|
end: parseInt(byteRangeMatch[2]) + parseInt(byteRangeMatch[1]) - 1
|
|
1773
2062
|
};
|
|
1774
2063
|
}
|
|
2064
|
+
if (!initSegment) {
|
|
2065
|
+
initSegment = currentInitSegment;
|
|
2066
|
+
}
|
|
1775
2067
|
}
|
|
2068
|
+
continue;
|
|
2069
|
+
}
|
|
2070
|
+
if (line === "#EXT-X-DISCONTINUITY") {
|
|
2071
|
+
console.log("[parsePlaylist] Found EXT-X-DISCONTINUITY");
|
|
2072
|
+
continue;
|
|
1776
2073
|
}
|
|
1777
|
-
}
|
|
1778
|
-
for (let i = 0; i < lines.length; i++) {
|
|
1779
|
-
const line = lines[i].trim();
|
|
1780
2074
|
if (line.startsWith("#EXTINF:")) {
|
|
1781
2075
|
const duration = parseFloat(line.split(":")[1].split(",")[0]);
|
|
1782
2076
|
let byteRange;
|
|
@@ -1794,7 +2088,13 @@ class HLSPlayer extends BasePlayer {
|
|
|
1794
2088
|
const uri = nextLine;
|
|
1795
2089
|
if (uri && !uri.startsWith("#")) {
|
|
1796
2090
|
if (isFMP4) {
|
|
1797
|
-
fmp4Segments.push({
|
|
2091
|
+
fmp4Segments.push({
|
|
2092
|
+
uri,
|
|
2093
|
+
duration,
|
|
2094
|
+
byteRange,
|
|
2095
|
+
initSegmentUri: currentInitSegment?.uri
|
|
2096
|
+
// 关联当前初始化段
|
|
2097
|
+
});
|
|
1798
2098
|
} else {
|
|
1799
2099
|
segments.push({ uri, duration });
|
|
1800
2100
|
}
|
|
@@ -1837,7 +2137,8 @@ class HLSPlayer extends BasePlayer {
|
|
|
1837
2137
|
* 解码 fMP4 sample
|
|
1838
2138
|
*/
|
|
1839
2139
|
decodeSample(sample) {
|
|
1840
|
-
|
|
2140
|
+
const decoder = this._isHevc ? this.hevcDecoder : this.decoder;
|
|
2141
|
+
if (!decoder) {
|
|
1841
2142
|
console.warn("[fMP4] Decoder not available");
|
|
1842
2143
|
return null;
|
|
1843
2144
|
}
|
|
@@ -1848,8 +2149,9 @@ class HLSPlayer extends BasePlayer {
|
|
|
1848
2149
|
const data = sample.data;
|
|
1849
2150
|
const hasStartCode = data.length >= 4 && data[0] === 0 && data[1] === 0 && (data[2] === 1 || data[2] === 0 && data[3] === 1);
|
|
1850
2151
|
if (hasStartCode) {
|
|
1851
|
-
|
|
1852
|
-
|
|
2152
|
+
const nalType2 = this._isHevc ? sample.isSync ? HEVC_NAL_TYPE.IDR_W_RADL : 1 : sample.isSync ? 5 : 1;
|
|
2153
|
+
return decoder.decode({
|
|
2154
|
+
type: nalType2,
|
|
1853
2155
|
data,
|
|
1854
2156
|
size: data.length
|
|
1855
2157
|
});
|
|
@@ -1888,8 +2190,9 @@ class HLSPlayer extends BasePlayer {
|
|
|
1888
2190
|
writeOffset += nalLength;
|
|
1889
2191
|
offset += 4 + nalLength;
|
|
1890
2192
|
}
|
|
1891
|
-
const
|
|
1892
|
-
|
|
2193
|
+
const nalType = this._isHevc ? sample.isSync ? HEVC_NAL_TYPE.IDR_W_RADL : 1 : sample.isSync ? 5 : 1;
|
|
2194
|
+
const frame = decoder.decode({
|
|
2195
|
+
type: nalType,
|
|
1893
2196
|
data: annexBData,
|
|
1894
2197
|
size: annexBData.length
|
|
1895
2198
|
});
|
|
@@ -2370,89 +2673,6 @@ class FLVDemuxer {
|
|
|
2370
2673
|
return tag.frameType === FRAME_TYPE_KEYFRAME;
|
|
2371
2674
|
}
|
|
2372
2675
|
}
|
|
2373
|
-
class HEVCDecoder {
|
|
2374
|
-
constructor(wasmLoader) {
|
|
2375
|
-
this.wasmModule = null;
|
|
2376
|
-
this.decoderContext = null;
|
|
2377
|
-
this.wasmModule = wasmLoader.getModule();
|
|
2378
|
-
}
|
|
2379
|
-
async init() {
|
|
2380
|
-
if (!this.wasmModule) {
|
|
2381
|
-
throw new Error("WASM module not loaded");
|
|
2382
|
-
}
|
|
2383
|
-
this.decoderContext = this.wasmModule._create_decoder(173);
|
|
2384
|
-
if (this.decoderContext === 0 || this.decoderContext === null) {
|
|
2385
|
-
throw new Error("Failed to create HEVC decoder context");
|
|
2386
|
-
}
|
|
2387
|
-
console.log("[HEVCDecoder] Initialized with codec_id=173");
|
|
2388
|
-
}
|
|
2389
|
-
decode(nalUnit) {
|
|
2390
|
-
if (this.decoderContext === null || !this.wasmModule) {
|
|
2391
|
-
return null;
|
|
2392
|
-
}
|
|
2393
|
-
const dataPtr = this.wasmModule._malloc(nalUnit.size);
|
|
2394
|
-
this.wasmModule.HEAPU8.set(nalUnit.data, dataPtr);
|
|
2395
|
-
const result = this.wasmModule._decode_video(
|
|
2396
|
-
this.decoderContext,
|
|
2397
|
-
dataPtr,
|
|
2398
|
-
nalUnit.size
|
|
2399
|
-
);
|
|
2400
|
-
this.wasmModule._free(dataPtr);
|
|
2401
|
-
if (result === 1) {
|
|
2402
|
-
const width = this.wasmModule._get_frame_width(this.decoderContext);
|
|
2403
|
-
const height = this.wasmModule._get_frame_height(this.decoderContext);
|
|
2404
|
-
if (width <= 0 || height <= 0) {
|
|
2405
|
-
return null;
|
|
2406
|
-
}
|
|
2407
|
-
const yPtr = this.wasmModule._get_frame_data(this.decoderContext, 0);
|
|
2408
|
-
const uPtr = this.wasmModule._get_frame_data(this.decoderContext, 1);
|
|
2409
|
-
const vPtr = this.wasmModule._get_frame_data(this.decoderContext, 2);
|
|
2410
|
-
const yLineSize = this.wasmModule._get_frame_linesize(this.decoderContext, 0);
|
|
2411
|
-
const uLineSize = this.wasmModule._get_frame_linesize(this.decoderContext, 1);
|
|
2412
|
-
const vLineSize = this.wasmModule._get_frame_linesize(this.decoderContext, 2);
|
|
2413
|
-
const frameData = this.yuv420pToRgba(
|
|
2414
|
-
yPtr,
|
|
2415
|
-
uPtr,
|
|
2416
|
-
vPtr,
|
|
2417
|
-
width,
|
|
2418
|
-
height,
|
|
2419
|
-
yLineSize,
|
|
2420
|
-
uLineSize,
|
|
2421
|
-
vLineSize
|
|
2422
|
-
);
|
|
2423
|
-
return { width, height, data: frameData };
|
|
2424
|
-
}
|
|
2425
|
-
return null;
|
|
2426
|
-
}
|
|
2427
|
-
yuv420pToRgba(yPtr, uPtr, vPtr, width, height, yLineSize, uLineSize, vLineSize) {
|
|
2428
|
-
const rgba = new Uint8Array(width * height * 4);
|
|
2429
|
-
for (let y = 0; y < height; y++) {
|
|
2430
|
-
for (let x = 0; x < width; x++) {
|
|
2431
|
-
const yIndex = y * yLineSize + x;
|
|
2432
|
-
const uIndex = (y >> 1) * uLineSize + (x >> 1);
|
|
2433
|
-
const vIndex = (y >> 1) * vLineSize + (x >> 1);
|
|
2434
|
-
const yValue = this.wasmModule.HEAPU8[yPtr + yIndex];
|
|
2435
|
-
const uValue = this.wasmModule.HEAPU8[uPtr + uIndex];
|
|
2436
|
-
const vValue = this.wasmModule.HEAPU8[vPtr + vIndex];
|
|
2437
|
-
const c = yValue - 16;
|
|
2438
|
-
const d = uValue - 128;
|
|
2439
|
-
const e = vValue - 128;
|
|
2440
|
-
let r = 298 * c + 409 * e + 128 >> 8;
|
|
2441
|
-
let g = 298 * c - 100 * d - 208 * e + 128 >> 8;
|
|
2442
|
-
let b = 298 * c + 516 * d + 128 >> 8;
|
|
2443
|
-
r = r < 0 ? 0 : r > 255 ? 255 : r;
|
|
2444
|
-
g = g < 0 ? 0 : g > 255 ? 255 : g;
|
|
2445
|
-
b = b < 0 ? 0 : b > 255 ? 255 : b;
|
|
2446
|
-
const rgbaIndex = (y * width + x) * 4;
|
|
2447
|
-
rgba[rgbaIndex] = r;
|
|
2448
|
-
rgba[rgbaIndex + 1] = g;
|
|
2449
|
-
rgba[rgbaIndex + 2] = b;
|
|
2450
|
-
rgba[rgbaIndex + 3] = 255;
|
|
2451
|
-
}
|
|
2452
|
-
}
|
|
2453
|
-
return rgba;
|
|
2454
|
-
}
|
|
2455
|
-
}
|
|
2456
2676
|
const CODEC_ID_AVC = 7;
|
|
2457
2677
|
const CODEC_ID_HEVC = 12;
|
|
2458
2678
|
const FLV_PREFETCHER_CONFIG = {
|
|
@@ -2537,7 +2757,7 @@ class FLVPlayer extends BasePlayer {
|
|
|
2537
2757
|
const isLive = this.config.isLive;
|
|
2538
2758
|
if (this._timedFrameBuffer.length > 0 && this.renderer) {
|
|
2539
2759
|
this.consecutiveEmptyBuffer = 0;
|
|
2540
|
-
if (this.
|
|
2760
|
+
if (this.playStartTime <= 0) {
|
|
2541
2761
|
this.firstFrameDts = this._timedFrameBuffer[0].dts;
|
|
2542
2762
|
this.playStartTime = now;
|
|
2543
2763
|
this.playStartTimeOffset = 0;
|
|
@@ -2716,8 +2936,8 @@ class FLVPlayer extends BasePlayer {
|
|
|
2716
2936
|
* 开始播放(覆盖基类方法)
|
|
2717
2937
|
*/
|
|
2718
2938
|
async play() {
|
|
2719
|
-
const MIN_BUFFER_SIZE = this.dynamicMinBufferSize;
|
|
2720
|
-
const MAX_WAIT_TIME = 1e4;
|
|
2939
|
+
const MIN_BUFFER_SIZE = this.config.isLive ? 3 : this.dynamicMinBufferSize;
|
|
2940
|
+
const MAX_WAIT_TIME = this.config.isLive ? 3e3 : 1e4;
|
|
2721
2941
|
const startTime = Date.now();
|
|
2722
2942
|
console.log(`[FLVPlayer] Waiting for buffer (target: ${MIN_BUFFER_SIZE} frames)...`);
|
|
2723
2943
|
let aggressiveDecodeCount = 0;
|
|
@@ -2748,6 +2968,12 @@ class FLVPlayer extends BasePlayer {
|
|
|
2748
2968
|
}
|
|
2749
2969
|
console.log("[FLVPlayer] Buffer ready, frames:", this._timedFrameBuffer.length, "queue:", this._videoTagQueue.length);
|
|
2750
2970
|
if (this._timedFrameBuffer.length > 0) {
|
|
2971
|
+
if (this.config.isLive && this._timedFrameBuffer.length > 60) {
|
|
2972
|
+
const droppedCount = this._timedFrameBuffer.length - 30;
|
|
2973
|
+
this._timedFrameBuffer = this._timedFrameBuffer.slice(-30);
|
|
2974
|
+
this.droppedFrames += droppedCount;
|
|
2975
|
+
console.log("[FLVPlayer] Live resume: dropped", droppedCount, "old frames, keeping latest 30");
|
|
2976
|
+
}
|
|
2751
2977
|
this.firstFrameDts = this._timedFrameBuffer[0].dts;
|
|
2752
2978
|
this.playStartTime = performance.now();
|
|
2753
2979
|
console.log("[FLVPlayer] Play time initialized, firstFrameDts:", this.firstFrameDts);
|
|
@@ -2770,8 +2996,7 @@ class FLVPlayer extends BasePlayer {
|
|
|
2770
2996
|
this.prefetcher.stop();
|
|
2771
2997
|
}
|
|
2772
2998
|
if (this.config.isLive) {
|
|
2773
|
-
console.log("[FLVPlayer] Pausing live stream,
|
|
2774
|
-
this.stopLiveDownload();
|
|
2999
|
+
console.log("[FLVPlayer] Pausing live stream, keeping download running");
|
|
2775
3000
|
}
|
|
2776
3001
|
}
|
|
2777
3002
|
/**
|
|
@@ -2792,24 +3017,22 @@ class FLVPlayer extends BasePlayer {
|
|
|
2792
3017
|
}
|
|
2793
3018
|
let newCount = 0;
|
|
2794
3019
|
for (const tag of result.videoTags) {
|
|
2795
|
-
|
|
2796
|
-
|
|
2797
|
-
|
|
2798
|
-
|
|
2799
|
-
|
|
2800
|
-
|
|
2801
|
-
|
|
2802
|
-
|
|
2803
|
-
break;
|
|
2804
|
-
}
|
|
3020
|
+
let annexBData = this.flvDemuxer.videoTagToAnnexB(tag, lengthSize);
|
|
3021
|
+
if (!annexBData) {
|
|
3022
|
+
for (const trySize of [4, 2, 1]) {
|
|
3023
|
+
if (trySize !== lengthSize) {
|
|
3024
|
+
annexBData = this.flvDemuxer.videoTagToAnnexB(tag, trySize);
|
|
3025
|
+
if (annexBData) {
|
|
3026
|
+
lengthSize = trySize;
|
|
3027
|
+
break;
|
|
2805
3028
|
}
|
|
2806
3029
|
}
|
|
2807
3030
|
}
|
|
2808
|
-
|
|
2809
|
-
|
|
2810
|
-
|
|
2811
|
-
|
|
2812
|
-
|
|
3031
|
+
}
|
|
3032
|
+
if (annexBData) {
|
|
3033
|
+
this._videoTagQueue.push({ tag, annexBData });
|
|
3034
|
+
this._lastQueuedTimestamp = tag.timestamp;
|
|
3035
|
+
newCount++;
|
|
2813
3036
|
}
|
|
2814
3037
|
}
|
|
2815
3038
|
if (newCount > 0) {
|
|
@@ -3476,6 +3699,7 @@ class EcPlayerCore {
|
|
|
3476
3699
|
droppedFrames: 0,
|
|
3477
3700
|
isPrefetching: false,
|
|
3478
3701
|
segmentIndex: 0,
|
|
3702
|
+
fetchedSegmentCount: 0,
|
|
3479
3703
|
totalSegments: 0,
|
|
3480
3704
|
downloadSpeed: 0,
|
|
3481
3705
|
currentTime: 0,
|