@give-tech/ec-player 0.0.1-beta.2 → 0.0.1-beta.21
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 +21 -4
- package/dist/demuxer/fMP4Demuxer.d.ts.map +1 -1
- package/dist/index.js +438 -156
- package/dist/index.js.map +1 -1
- package/dist/player/FLVPlayer.d.ts.map +1 -1
- package/dist/player/HLSPlayer.d.ts +28 -0
- package/dist/player/HLSPlayer.d.ts.map +1 -1
- package/dist/prefetch/SegmentPrefetcher.d.ts +4 -1
- package/dist/prefetch/SegmentPrefetcher.d.ts.map +1 -1
- package/dist/types/index.d.ts +3 -0
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -415,6 +415,8 @@ class fMP4Demuxer {
|
|
|
415
415
|
this.timescale = 1e3;
|
|
416
416
|
this.trackId = 1;
|
|
417
417
|
this.avcC = null;
|
|
418
|
+
this.hvcC = null;
|
|
419
|
+
this.isHevc = false;
|
|
418
420
|
}
|
|
419
421
|
/**
|
|
420
422
|
* 解析初始化段 (ftyp + moov)
|
|
@@ -433,7 +435,9 @@ class fMP4Demuxer {
|
|
|
433
435
|
return {
|
|
434
436
|
trackId: this.trackId,
|
|
435
437
|
timescale: this.timescale,
|
|
436
|
-
avcC: this.avcC || void 0
|
|
438
|
+
avcC: this.avcC || void 0,
|
|
439
|
+
hvcC: this.hvcC || void 0,
|
|
440
|
+
isHevc: this.isHevc
|
|
437
441
|
};
|
|
438
442
|
}
|
|
439
443
|
/**
|
|
@@ -458,33 +462,54 @@ class fMP4Demuxer {
|
|
|
458
462
|
parseTrak(data, trakOffset, trakSize) {
|
|
459
463
|
let offset = trakOffset + 8;
|
|
460
464
|
const endOffset = trakOffset + trakSize;
|
|
465
|
+
let currentTrackId = null;
|
|
466
|
+
let isVideoTrack = false;
|
|
467
|
+
console.log("[fMP4Demuxer] parseTrak called, size:", trakSize);
|
|
461
468
|
while (offset < endOffset - 8) {
|
|
462
469
|
const boxSize = readUint32$1(data, offset);
|
|
463
470
|
const boxType = readFourCC(data, offset + 4);
|
|
464
471
|
if (boxSize < 8) break;
|
|
465
|
-
if (boxType === "
|
|
466
|
-
|
|
472
|
+
if (boxType === "tkhd") {
|
|
473
|
+
const boxDataStart = offset + 8;
|
|
474
|
+
const version = data[boxDataStart];
|
|
475
|
+
if (version === 1) {
|
|
476
|
+
currentTrackId = readUint32$1(data, boxDataStart + 1 + 3 + 8 + 8);
|
|
477
|
+
} else {
|
|
478
|
+
currentTrackId = readUint32$1(data, boxDataStart + 1 + 3 + 4 + 4);
|
|
479
|
+
}
|
|
480
|
+
console.log("[fMP4Demuxer] tkhd: version=", version, "trackId=", currentTrackId);
|
|
481
|
+
} else if (boxType === "mdia") {
|
|
482
|
+
isVideoTrack = this.parseMdiaAndCheckVideo(data, offset, boxSize);
|
|
483
|
+
if (isVideoTrack && currentTrackId !== null) {
|
|
484
|
+
this.trackId = currentTrackId;
|
|
485
|
+
console.log("[fMP4Demuxer] Found video track, trackId:", currentTrackId);
|
|
486
|
+
}
|
|
467
487
|
}
|
|
468
488
|
offset += boxSize;
|
|
469
489
|
}
|
|
470
490
|
}
|
|
471
491
|
/**
|
|
472
|
-
* 解析 mdia box
|
|
492
|
+
* 解析 mdia box 并检查是否为视频轨道
|
|
473
493
|
*/
|
|
474
|
-
|
|
494
|
+
parseMdiaAndCheckVideo(data, mdiaOffset, mdiaSize) {
|
|
475
495
|
let offset = mdiaOffset + 8;
|
|
476
496
|
const endOffset = mdiaOffset + mdiaSize;
|
|
497
|
+
let isVideo = false;
|
|
477
498
|
while (offset < endOffset - 8) {
|
|
478
499
|
const boxSize = readUint32$1(data, offset);
|
|
479
500
|
const boxType = readFourCC(data, offset + 4);
|
|
480
501
|
if (boxSize < 8) break;
|
|
481
502
|
if (boxType === "mdhd") {
|
|
482
503
|
this.parseMdhd(data, offset + 8);
|
|
483
|
-
} else if (boxType === "
|
|
504
|
+
} else if (boxType === "hdlr") {
|
|
505
|
+
const handlerType = readFourCC(data, offset + 8 + 8);
|
|
506
|
+
isVideo = handlerType === "vide";
|
|
507
|
+
} else if (boxType === "minf" && isVideo) {
|
|
484
508
|
this.parseMinf(data, offset, boxSize);
|
|
485
509
|
}
|
|
486
510
|
offset += boxSize;
|
|
487
511
|
}
|
|
512
|
+
return isVideo;
|
|
488
513
|
}
|
|
489
514
|
/**
|
|
490
515
|
* 解析 mdhd box 获取 timescale
|
|
@@ -530,7 +555,7 @@ class fMP4Demuxer {
|
|
|
530
555
|
}
|
|
531
556
|
}
|
|
532
557
|
/**
|
|
533
|
-
* 解析 stsd box 获取 avcC
|
|
558
|
+
* 解析 stsd box 获取 avcC 或 hvcC
|
|
534
559
|
*/
|
|
535
560
|
parseStsd(data, stsdOffset, stsdSize) {
|
|
536
561
|
const boxDataOffset = stsdOffset + 8;
|
|
@@ -544,6 +569,10 @@ class fMP4Demuxer {
|
|
|
544
569
|
this.parseAvcSampleEntry(data, offset, entrySize);
|
|
545
570
|
return;
|
|
546
571
|
}
|
|
572
|
+
if (entryType === "hvc1" || entryType === "hev1") {
|
|
573
|
+
this.parseHevcSampleEntry(data, offset, entrySize);
|
|
574
|
+
return;
|
|
575
|
+
}
|
|
547
576
|
offset += entrySize;
|
|
548
577
|
}
|
|
549
578
|
}
|
|
@@ -564,6 +593,25 @@ class fMP4Demuxer {
|
|
|
564
593
|
offset += boxSize;
|
|
565
594
|
}
|
|
566
595
|
}
|
|
596
|
+
/**
|
|
597
|
+
* 解析 HEVC Sample Entry
|
|
598
|
+
*/
|
|
599
|
+
parseHevcSampleEntry(data, entryOffset, entrySize) {
|
|
600
|
+
this.isHevc = true;
|
|
601
|
+
let offset = entryOffset + 8 + 78;
|
|
602
|
+
const endOffset = entryOffset + entrySize;
|
|
603
|
+
while (offset < endOffset - 8) {
|
|
604
|
+
const boxSize = readUint32$1(data, offset);
|
|
605
|
+
const boxType = readFourCC(data, offset + 4);
|
|
606
|
+
if (boxSize < 8) break;
|
|
607
|
+
if (boxType === "hvcC") {
|
|
608
|
+
this.hvcC = data.slice(offset, offset + boxSize);
|
|
609
|
+
console.log("[fMP4Demuxer] Found hvcC, size:", boxSize);
|
|
610
|
+
return;
|
|
611
|
+
}
|
|
612
|
+
offset += boxSize;
|
|
613
|
+
}
|
|
614
|
+
}
|
|
567
615
|
/**
|
|
568
616
|
* 解析 moof box
|
|
569
617
|
*/
|
|
@@ -714,20 +762,35 @@ class fMP4Demuxer {
|
|
|
714
762
|
}
|
|
715
763
|
/**
|
|
716
764
|
* 从 moof + mdat 提取视频样本
|
|
765
|
+
* 只提取视频轨道(trackId 匹配)的数据
|
|
717
766
|
*/
|
|
718
|
-
extractSamples(moof, mdatData, mdatOffset) {
|
|
767
|
+
extractSamples(moof, mdatData, moofOffset, mdatOffset) {
|
|
719
768
|
const samples = [];
|
|
720
769
|
for (const traf of moof.trafs) {
|
|
721
|
-
if (!traf.tfhd || !traf.tfdt)
|
|
770
|
+
if (!traf.tfhd || !traf.tfdt) {
|
|
771
|
+
continue;
|
|
772
|
+
}
|
|
773
|
+
if (traf.tfhd.trackId !== this.trackId) {
|
|
774
|
+
continue;
|
|
775
|
+
}
|
|
722
776
|
let baseDts = traf.tfdt.baseMediaDecodeTime;
|
|
777
|
+
const baseDataOffset = traf.tfhd.baseDataOffset ?? moofOffset;
|
|
723
778
|
for (const trun of traf.truns) {
|
|
724
779
|
let dataOffset = 0;
|
|
725
780
|
if (trun.dataOffset !== void 0) {
|
|
726
|
-
|
|
781
|
+
const absoluteOffset = baseDataOffset + trun.dataOffset;
|
|
782
|
+
dataOffset = absoluteOffset - mdatOffset - 8;
|
|
727
783
|
}
|
|
728
784
|
for (const sample of trun.samples) {
|
|
729
|
-
|
|
730
|
-
|
|
785
|
+
let sampleSize = sample.size;
|
|
786
|
+
if (sampleSize === void 0 || sampleSize <= 0) {
|
|
787
|
+
sampleSize = mdatData.length - dataOffset;
|
|
788
|
+
}
|
|
789
|
+
if (sampleSize <= 0) {
|
|
790
|
+
console.log(`[fMP4Demuxer] Skipping sample: no data available`);
|
|
791
|
+
continue;
|
|
792
|
+
}
|
|
793
|
+
const sampleData = mdatData.slice(dataOffset, dataOffset + sampleSize);
|
|
731
794
|
const isSync = (sample.flags ?? 0) & 16777216;
|
|
732
795
|
const cto = sample.compositionTimeOffset ?? 0;
|
|
733
796
|
const duration = sample.duration ?? 0;
|
|
@@ -738,7 +801,7 @@ class fMP4Demuxer {
|
|
|
738
801
|
duration,
|
|
739
802
|
isSync: isSync !== 0
|
|
740
803
|
});
|
|
741
|
-
dataOffset +=
|
|
804
|
+
dataOffset += sampleSize;
|
|
742
805
|
baseDts += duration;
|
|
743
806
|
}
|
|
744
807
|
}
|
|
@@ -757,6 +820,18 @@ class fMP4Demuxer {
|
|
|
757
820
|
getAvcC() {
|
|
758
821
|
return this.avcC;
|
|
759
822
|
}
|
|
823
|
+
/**
|
|
824
|
+
* 获取 hvcC 配置
|
|
825
|
+
*/
|
|
826
|
+
getHvcC() {
|
|
827
|
+
return this.hvcC;
|
|
828
|
+
}
|
|
829
|
+
/**
|
|
830
|
+
* 是否为 HEVC 编码
|
|
831
|
+
*/
|
|
832
|
+
isHevcStream() {
|
|
833
|
+
return this.isHevc;
|
|
834
|
+
}
|
|
760
835
|
}
|
|
761
836
|
class NALParser {
|
|
762
837
|
/**
|
|
@@ -804,6 +879,93 @@ class NALParser {
|
|
|
804
879
|
return units;
|
|
805
880
|
}
|
|
806
881
|
}
|
|
882
|
+
const HEVC_NAL_TYPE = {
|
|
883
|
+
// Picture Parameter Set
|
|
884
|
+
IDR_W_RADL: 19
|
|
885
|
+
};
|
|
886
|
+
class HEVCDecoder {
|
|
887
|
+
constructor(wasmLoader) {
|
|
888
|
+
this.wasmModule = null;
|
|
889
|
+
this.decoderContext = null;
|
|
890
|
+
this.wasmModule = wasmLoader.getModule();
|
|
891
|
+
}
|
|
892
|
+
async init() {
|
|
893
|
+
if (!this.wasmModule) {
|
|
894
|
+
throw new Error("WASM module not loaded");
|
|
895
|
+
}
|
|
896
|
+
this.decoderContext = this.wasmModule._create_decoder(173);
|
|
897
|
+
if (this.decoderContext === 0 || this.decoderContext === null) {
|
|
898
|
+
throw new Error("Failed to create HEVC decoder context");
|
|
899
|
+
}
|
|
900
|
+
console.log("[HEVCDecoder] Initialized with codec_id=173");
|
|
901
|
+
}
|
|
902
|
+
decode(nalUnit) {
|
|
903
|
+
if (this.decoderContext === null || !this.wasmModule) {
|
|
904
|
+
return null;
|
|
905
|
+
}
|
|
906
|
+
const dataPtr = this.wasmModule._malloc(nalUnit.size);
|
|
907
|
+
this.wasmModule.HEAPU8.set(nalUnit.data, dataPtr);
|
|
908
|
+
const result = this.wasmModule._decode_video(
|
|
909
|
+
this.decoderContext,
|
|
910
|
+
dataPtr,
|
|
911
|
+
nalUnit.size
|
|
912
|
+
);
|
|
913
|
+
this.wasmModule._free(dataPtr);
|
|
914
|
+
if (result === 1) {
|
|
915
|
+
const width = this.wasmModule._get_frame_width(this.decoderContext);
|
|
916
|
+
const height = this.wasmModule._get_frame_height(this.decoderContext);
|
|
917
|
+
if (width <= 0 || height <= 0) {
|
|
918
|
+
return null;
|
|
919
|
+
}
|
|
920
|
+
const yPtr = this.wasmModule._get_frame_data(this.decoderContext, 0);
|
|
921
|
+
const uPtr = this.wasmModule._get_frame_data(this.decoderContext, 1);
|
|
922
|
+
const vPtr = this.wasmModule._get_frame_data(this.decoderContext, 2);
|
|
923
|
+
const yLineSize = this.wasmModule._get_frame_linesize(this.decoderContext, 0);
|
|
924
|
+
const uLineSize = this.wasmModule._get_frame_linesize(this.decoderContext, 1);
|
|
925
|
+
const vLineSize = this.wasmModule._get_frame_linesize(this.decoderContext, 2);
|
|
926
|
+
const frameData = this.yuv420pToRgba(
|
|
927
|
+
yPtr,
|
|
928
|
+
uPtr,
|
|
929
|
+
vPtr,
|
|
930
|
+
width,
|
|
931
|
+
height,
|
|
932
|
+
yLineSize,
|
|
933
|
+
uLineSize,
|
|
934
|
+
vLineSize
|
|
935
|
+
);
|
|
936
|
+
return { width, height, data: frameData };
|
|
937
|
+
}
|
|
938
|
+
return null;
|
|
939
|
+
}
|
|
940
|
+
yuv420pToRgba(yPtr, uPtr, vPtr, width, height, yLineSize, uLineSize, vLineSize) {
|
|
941
|
+
const rgba = new Uint8Array(width * height * 4);
|
|
942
|
+
for (let y = 0; y < height; y++) {
|
|
943
|
+
for (let x = 0; x < width; x++) {
|
|
944
|
+
const yIndex = y * yLineSize + x;
|
|
945
|
+
const uIndex = (y >> 1) * uLineSize + (x >> 1);
|
|
946
|
+
const vIndex = (y >> 1) * vLineSize + (x >> 1);
|
|
947
|
+
const yValue = this.wasmModule.HEAPU8[yPtr + yIndex];
|
|
948
|
+
const uValue = this.wasmModule.HEAPU8[uPtr + uIndex];
|
|
949
|
+
const vValue = this.wasmModule.HEAPU8[vPtr + vIndex];
|
|
950
|
+
const c = yValue - 16;
|
|
951
|
+
const d = uValue - 128;
|
|
952
|
+
const e = vValue - 128;
|
|
953
|
+
let r = 298 * c + 409 * e + 128 >> 8;
|
|
954
|
+
let g = 298 * c - 100 * d - 208 * e + 128 >> 8;
|
|
955
|
+
let b = 298 * c + 516 * d + 128 >> 8;
|
|
956
|
+
r = r < 0 ? 0 : r > 255 ? 255 : r;
|
|
957
|
+
g = g < 0 ? 0 : g > 255 ? 255 : g;
|
|
958
|
+
b = b < 0 ? 0 : b > 255 ? 255 : b;
|
|
959
|
+
const rgbaIndex = (y * width + x) * 4;
|
|
960
|
+
rgba[rgbaIndex] = r;
|
|
961
|
+
rgba[rgbaIndex + 1] = g;
|
|
962
|
+
rgba[rgbaIndex + 2] = b;
|
|
963
|
+
rgba[rgbaIndex + 3] = 255;
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
return rgba;
|
|
967
|
+
}
|
|
968
|
+
}
|
|
807
969
|
class BasePrefetcher {
|
|
808
970
|
constructor(config, callbacks = {}) {
|
|
809
971
|
this.isRunning = false;
|
|
@@ -1077,11 +1239,16 @@ class SegmentPrefetcher extends BasePrefetcher {
|
|
|
1077
1239
|
/**
|
|
1078
1240
|
* 处理预取队列中的数据
|
|
1079
1241
|
*
|
|
1242
|
+
* @param maxSegments 最多处理的分片数量(默认处理所有)
|
|
1080
1243
|
* @returns 是否有数据被处理
|
|
1081
1244
|
*/
|
|
1082
|
-
processQueue() {
|
|
1245
|
+
processQueue(maxSegments) {
|
|
1083
1246
|
let processed = false;
|
|
1247
|
+
let processedCount = 0;
|
|
1084
1248
|
while (this.prefetchQueue.length > 0) {
|
|
1249
|
+
if (maxSegments !== void 0 && processedCount >= maxSegments) {
|
|
1250
|
+
break;
|
|
1251
|
+
}
|
|
1085
1252
|
const item = this.prefetchQueue[0];
|
|
1086
1253
|
if (item.segmentIndex !== this.currentSegmentIndex) {
|
|
1087
1254
|
break;
|
|
@@ -1097,6 +1264,7 @@ class SegmentPrefetcher extends BasePrefetcher {
|
|
|
1097
1264
|
callbacks.onSegmentParsed?.(item.segmentIndex, itemCount);
|
|
1098
1265
|
console.log(`[SegmentPrefetcher] Parsed segment #${item.segmentIndex}: ${itemCount} items, ${parseTime.toFixed(0)}ms`);
|
|
1099
1266
|
processed = true;
|
|
1267
|
+
processedCount++;
|
|
1100
1268
|
}
|
|
1101
1269
|
return processed;
|
|
1102
1270
|
}
|
|
@@ -1334,21 +1502,37 @@ const DEFAULT_HLS_CONFIG = {
|
|
|
1334
1502
|
class HLSSegmentPrefetcher extends SegmentPrefetcher {
|
|
1335
1503
|
constructor(config, callbacks, player) {
|
|
1336
1504
|
super(config, callbacks);
|
|
1505
|
+
this.currentInitSegmentUri = null;
|
|
1337
1506
|
this.player = player;
|
|
1338
1507
|
}
|
|
1339
1508
|
/**
|
|
1340
1509
|
* 获取分片数据
|
|
1341
1510
|
*/
|
|
1342
1511
|
async fetchSegment(segment, index) {
|
|
1512
|
+
console.log("[HLSSegmentPrefetcher] fetchSegment called, index=", index, "uri=", segment.uri);
|
|
1513
|
+
if (segment.initSegmentUri && segment.initSegmentUri !== this.currentInitSegmentUri) {
|
|
1514
|
+
console.log("[HLSSegmentPrefetcher] Init segment changed:", segment.initSegmentUri);
|
|
1515
|
+
await this.player.loadNewInitSegment(segment.initSegmentUri);
|
|
1516
|
+
this.currentInitSegmentUri = segment.initSegmentUri;
|
|
1517
|
+
}
|
|
1343
1518
|
const baseUrl = this.baseUrl;
|
|
1344
1519
|
const url = segment.uri.startsWith("http") ? segment.uri : baseUrl + segment.uri;
|
|
1520
|
+
console.log("[HLSSegmentPrefetcher] Fetching URL:", url);
|
|
1345
1521
|
const headers = {};
|
|
1346
1522
|
if (segment.byteRange) {
|
|
1347
1523
|
const { start, end } = segment.byteRange;
|
|
1348
1524
|
headers["Range"] = `bytes=${start}-${end}`;
|
|
1349
1525
|
}
|
|
1350
|
-
|
|
1351
|
-
|
|
1526
|
+
try {
|
|
1527
|
+
const response = await fetch(url, { headers });
|
|
1528
|
+
console.log("[HLSSegmentPrefetcher] Fetch response:", response.status, response.ok);
|
|
1529
|
+
const buffer = await response.arrayBuffer();
|
|
1530
|
+
console.log("[HLSSegmentPrefetcher] Fetched data size:", buffer.byteLength);
|
|
1531
|
+
return new Uint8Array(buffer);
|
|
1532
|
+
} catch (error) {
|
|
1533
|
+
console.error("[HLSSegmentPrefetcher] Fetch error:", error);
|
|
1534
|
+
throw error;
|
|
1535
|
+
}
|
|
1352
1536
|
}
|
|
1353
1537
|
/**
|
|
1354
1538
|
* 解析分片数据
|
|
@@ -1378,19 +1562,29 @@ class HLSPlayer extends BasePlayer {
|
|
|
1378
1562
|
this.pesExtractor = new PESExtractor();
|
|
1379
1563
|
this.nalParser = new NALParser();
|
|
1380
1564
|
this.fmp4Demuxer = new fMP4Demuxer();
|
|
1565
|
+
this.hevcDecoder = null;
|
|
1381
1566
|
this.isPrefetching = false;
|
|
1382
1567
|
this.currentSegmentIndex = 0;
|
|
1383
1568
|
this.segments = [];
|
|
1384
1569
|
this.fmp4Segments = [];
|
|
1385
1570
|
this.initSegment = null;
|
|
1386
1571
|
this._isFMP4 = false;
|
|
1572
|
+
this._isHevc = false;
|
|
1387
1573
|
this.currentPlaylistUrl = "";
|
|
1388
1574
|
this._nalQueue = [];
|
|
1389
1575
|
this._sampleQueue = [];
|
|
1390
1576
|
this.prefetcher = null;
|
|
1391
1577
|
this.lastRenderTime = 0;
|
|
1578
|
+
this.playStartTime = 0;
|
|
1579
|
+
this.accumulatedMediaTime = 0;
|
|
1580
|
+
this._lastLogTime = 0;
|
|
1392
1581
|
this.renderLoop = (timestamp = 0) => {
|
|
1393
1582
|
if (!this.isPlaying) return;
|
|
1583
|
+
const now = performance.now();
|
|
1584
|
+
if (!this._lastLogTime || now - this._lastLogTime > 1e3) {
|
|
1585
|
+
this._lastLogTime = now;
|
|
1586
|
+
console.log("[renderLoop] frameBuffer=", this.frameBuffer.length, "renderer=", !!this.renderer);
|
|
1587
|
+
}
|
|
1394
1588
|
const downloaded = this.isFMP4 ? this.sampleQueue.length : this.nalQueue.length;
|
|
1395
1589
|
const segments = this.isFMP4 ? this.fmp4Segments : this.segments;
|
|
1396
1590
|
const totalSegments = segments.length;
|
|
@@ -1402,19 +1596,35 @@ class HLSPlayer extends BasePlayer {
|
|
|
1402
1596
|
totalSegments
|
|
1403
1597
|
});
|
|
1404
1598
|
if (this.frameBuffer.length > 0 && this.renderer) {
|
|
1405
|
-
const frame = this.frameBuffer
|
|
1406
|
-
this.
|
|
1407
|
-
if (this.
|
|
1408
|
-
|
|
1409
|
-
this.
|
|
1599
|
+
const frame = this.frameBuffer[0];
|
|
1600
|
+
const timescale = this.fmp4Demuxer.getTimescale();
|
|
1601
|
+
if (this.playStartTime === 0) {
|
|
1602
|
+
this.playStartTime = now;
|
|
1603
|
+
this.accumulatedMediaTime = 0;
|
|
1604
|
+
console.log("[renderLoop] Init: timescale=", timescale);
|
|
1605
|
+
}
|
|
1606
|
+
const elapsedWallTime = now - this.playStartTime;
|
|
1607
|
+
const frameDurationMs = frame.duration ? frame.duration * 1e3 / timescale : 33.33;
|
|
1608
|
+
if (Math.floor(elapsedWallTime / 1e3) !== Math.floor((elapsedWallTime - 20) / 1e3)) {
|
|
1609
|
+
console.log("[renderLoop] elapsed=", Math.floor(elapsedWallTime), "accumulated=", Math.floor(this.accumulatedMediaTime), "frameDuration=", frame.duration, "frameDurationMs=", frameDurationMs.toFixed(2), "frameBuffer=", this.frameBuffer.length);
|
|
1610
|
+
}
|
|
1611
|
+
const bufferMs = 50;
|
|
1612
|
+
if (elapsedWallTime >= this.accumulatedMediaTime - bufferMs) {
|
|
1613
|
+
this.frameBuffer.shift();
|
|
1614
|
+
this.renderer.render(frame);
|
|
1615
|
+
this.accumulatedMediaTime += frameDurationMs;
|
|
1616
|
+
this.setCurrentTime(this.accumulatedMediaTime);
|
|
1617
|
+
this.updateState({ resolution: `${frame.width}x${frame.height}` });
|
|
1618
|
+
const fps = this.renderer.updateFps();
|
|
1619
|
+
this.updateState({ fps });
|
|
1620
|
+
this.callbacks.onFrameRender?.(frame);
|
|
1410
1621
|
}
|
|
1411
|
-
this.lastRenderTime = timestamp;
|
|
1412
|
-
this.updateState({ resolution: `${frame.width}x${frame.height}` });
|
|
1413
|
-
const fps = this.renderer.updateFps();
|
|
1414
|
-
this.updateState({ fps });
|
|
1415
|
-
this.callbacks.onFrameRender?.(frame);
|
|
1416
1622
|
} else {
|
|
1417
|
-
this.
|
|
1623
|
+
if (this.playStartTime !== 0) {
|
|
1624
|
+
const oldPlayStartTime = this.playStartTime;
|
|
1625
|
+
this.playStartTime = now - this.accumulatedMediaTime;
|
|
1626
|
+
console.log("[renderLoop] No frames, adjusting playStartTime from", oldPlayStartTime, "to", this.playStartTime, "accumulated=", this.accumulatedMediaTime);
|
|
1627
|
+
}
|
|
1418
1628
|
}
|
|
1419
1629
|
this.frameTimer = requestAnimationFrame(this.renderLoop);
|
|
1420
1630
|
};
|
|
@@ -1423,6 +1633,9 @@ class HLSPlayer extends BasePlayer {
|
|
|
1423
1633
|
get isFMP4() {
|
|
1424
1634
|
return this._isFMP4;
|
|
1425
1635
|
}
|
|
1636
|
+
get isHevc() {
|
|
1637
|
+
return this._isHevc;
|
|
1638
|
+
}
|
|
1426
1639
|
get nalQueue() {
|
|
1427
1640
|
return this._nalQueue;
|
|
1428
1641
|
}
|
|
@@ -1489,6 +1702,8 @@ class HLSPlayer extends BasePlayer {
|
|
|
1489
1702
|
this.isPlaying = true;
|
|
1490
1703
|
this.decodeLoopAbort = false;
|
|
1491
1704
|
this.lastRenderTime = 0;
|
|
1705
|
+
this.playStartTime = 0;
|
|
1706
|
+
this.accumulatedMediaTime = 0;
|
|
1492
1707
|
this.updateState({ isPlaying: true });
|
|
1493
1708
|
if (!this.prefetcher) {
|
|
1494
1709
|
this.initPrefetcher();
|
|
@@ -1521,6 +1736,8 @@ class HLSPlayer extends BasePlayer {
|
|
|
1521
1736
|
if (this.prefetcher) {
|
|
1522
1737
|
this.prefetcher.setCurrentSegmentIndex(targetIndex);
|
|
1523
1738
|
}
|
|
1739
|
+
this.playStartTime = performance.now();
|
|
1740
|
+
this.accumulatedMediaTime = time;
|
|
1524
1741
|
this.setCurrentTime(time);
|
|
1525
1742
|
console.log("[HLSPlayer] Seek to", time, "ms, segment:", targetIndex);
|
|
1526
1743
|
}
|
|
@@ -1531,10 +1748,22 @@ class HLSPlayer extends BasePlayer {
|
|
|
1531
1748
|
console.log("[DecodeLoop] START, isFMP4:", this.isFMP4);
|
|
1532
1749
|
let batchCount = 0;
|
|
1533
1750
|
while (this.isPlaying && !this.decodeLoopAbort) {
|
|
1534
|
-
this.
|
|
1751
|
+
const sampleQueueSize = this.sampleQueue.length;
|
|
1752
|
+
const maxSamplesBeforePause = this.config.maxQueueSize * 3;
|
|
1753
|
+
if (sampleQueueSize < maxSamplesBeforePause && this.prefetcher) {
|
|
1754
|
+
this.prefetcher.processQueue(1);
|
|
1755
|
+
}
|
|
1756
|
+
if (!this.config.isLive && this.frameBuffer.length >= this.config.targetBufferSize) {
|
|
1757
|
+
await this.sleep(10);
|
|
1758
|
+
continue;
|
|
1759
|
+
}
|
|
1535
1760
|
const batchSize = this.config.decodeBatchSize;
|
|
1536
1761
|
let decodedInBatch = 0;
|
|
1537
1762
|
if (this.isFMP4) {
|
|
1763
|
+
const queueSize = this.sampleQueue.length;
|
|
1764
|
+
if (queueSize > 0 || batchCount % 50 === 0) {
|
|
1765
|
+
console.log("[DecodeLoop] fMP4: sampleQueue=", queueSize, "frameBuffer=", this.frameBuffer.length, "batch=", batchCount);
|
|
1766
|
+
}
|
|
1538
1767
|
while (this.sampleQueue.length > 0 && decodedInBatch < batchSize) {
|
|
1539
1768
|
const queuedSample = this.sampleQueue.shift();
|
|
1540
1769
|
const sample = queuedSample.sample;
|
|
@@ -1542,9 +1771,11 @@ class HLSPlayer extends BasePlayer {
|
|
|
1542
1771
|
if (frame) {
|
|
1543
1772
|
this.frameBuffer.push(frame);
|
|
1544
1773
|
decodedInBatch++;
|
|
1545
|
-
|
|
1546
|
-
this.frameBuffer.
|
|
1547
|
-
|
|
1774
|
+
if (this.config.isLive) {
|
|
1775
|
+
while (this.frameBuffer.length > this.config.targetBufferSize) {
|
|
1776
|
+
this.frameBuffer.shift();
|
|
1777
|
+
this.droppedFrames++;
|
|
1778
|
+
}
|
|
1548
1779
|
}
|
|
1549
1780
|
}
|
|
1550
1781
|
}
|
|
@@ -1560,9 +1791,11 @@ class HLSPlayer extends BasePlayer {
|
|
|
1560
1791
|
if (frame) {
|
|
1561
1792
|
this.frameBuffer.push(frame);
|
|
1562
1793
|
decodedInBatch++;
|
|
1563
|
-
|
|
1564
|
-
this.frameBuffer.
|
|
1565
|
-
|
|
1794
|
+
if (this.config.isLive) {
|
|
1795
|
+
while (this.frameBuffer.length > this.config.targetBufferSize) {
|
|
1796
|
+
this.frameBuffer.shift();
|
|
1797
|
+
this.droppedFrames++;
|
|
1798
|
+
}
|
|
1566
1799
|
}
|
|
1567
1800
|
}
|
|
1568
1801
|
}
|
|
@@ -1604,7 +1837,9 @@ class HLSPlayer extends BasePlayer {
|
|
|
1604
1837
|
const segmentInfos = this.fmp4Segments.map((seg) => ({
|
|
1605
1838
|
uri: seg.uri,
|
|
1606
1839
|
duration: seg.duration,
|
|
1607
|
-
byteRange: seg.byteRange
|
|
1840
|
+
byteRange: seg.byteRange,
|
|
1841
|
+
initSegmentUri: seg.initSegmentUri
|
|
1842
|
+
// 传递初始化段 URI
|
|
1608
1843
|
}));
|
|
1609
1844
|
this.prefetcher.setSegments(segmentInfos, baseUrl);
|
|
1610
1845
|
} else {
|
|
@@ -1620,33 +1855,46 @@ class HLSPlayer extends BasePlayer {
|
|
|
1620
1855
|
* 解析 fMP4 数据
|
|
1621
1856
|
*/
|
|
1622
1857
|
parseFMP4Data(data) {
|
|
1623
|
-
let
|
|
1624
|
-
let mdatOffset = -1;
|
|
1625
|
-
let mdatSize = 0;
|
|
1858
|
+
let totalSampleCount = 0;
|
|
1626
1859
|
let offset = 0;
|
|
1627
1860
|
while (offset < data.length - 8) {
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1861
|
+
let moofOffset = -1;
|
|
1862
|
+
let mdatOffset = -1;
|
|
1863
|
+
let mdatSize = 0;
|
|
1864
|
+
while (offset < data.length - 8) {
|
|
1865
|
+
const boxSize = this.readBoxSize(data, offset);
|
|
1866
|
+
const boxType = this.readBoxType(data, offset + 4);
|
|
1867
|
+
if (boxSize < 8) break;
|
|
1868
|
+
if (boxType === "moof") {
|
|
1869
|
+
moofOffset = offset;
|
|
1870
|
+
offset += boxSize;
|
|
1871
|
+
break;
|
|
1872
|
+
}
|
|
1873
|
+
offset += boxSize;
|
|
1636
1874
|
}
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1875
|
+
if (moofOffset < 0) break;
|
|
1876
|
+
while (offset < data.length - 8) {
|
|
1877
|
+
const boxSize = this.readBoxSize(data, offset);
|
|
1878
|
+
const boxType = this.readBoxType(data, offset + 4);
|
|
1879
|
+
if (boxSize < 8) break;
|
|
1880
|
+
if (boxType === "mdat") {
|
|
1881
|
+
mdatOffset = offset;
|
|
1882
|
+
mdatSize = boxSize;
|
|
1883
|
+
offset += boxSize;
|
|
1884
|
+
break;
|
|
1885
|
+
}
|
|
1886
|
+
offset += boxSize;
|
|
1887
|
+
}
|
|
1888
|
+
if (mdatOffset < 0) break;
|
|
1641
1889
|
const moof = this.fmp4Demuxer.parseMoof(data, moofOffset);
|
|
1642
1890
|
const mdatData = data.slice(mdatOffset + 8, mdatOffset + mdatSize);
|
|
1643
|
-
const samples = this.fmp4Demuxer.extractSamples(moof, mdatData, mdatOffset);
|
|
1891
|
+
const samples = this.fmp4Demuxer.extractSamples(moof, mdatData, moofOffset, mdatOffset);
|
|
1644
1892
|
for (const sample of samples) {
|
|
1645
1893
|
this._sampleQueue.push({ sample });
|
|
1646
1894
|
}
|
|
1647
|
-
|
|
1895
|
+
totalSampleCount += samples.length;
|
|
1648
1896
|
}
|
|
1649
|
-
return
|
|
1897
|
+
return totalSampleCount;
|
|
1650
1898
|
}
|
|
1651
1899
|
/**
|
|
1652
1900
|
* 解析 TS 数据
|
|
@@ -1690,16 +1938,74 @@ class HLSPlayer extends BasePlayer {
|
|
|
1690
1938
|
const data = new Uint8Array(await response.arrayBuffer());
|
|
1691
1939
|
console.log("[fMP4] Init segment data size:", data.length, "bytes");
|
|
1692
1940
|
const initInfo = this.fmp4Demuxer.parseInitSegment(data);
|
|
1693
|
-
console.log("[fMP4] Parse result:",
|
|
1694
|
-
|
|
1941
|
+
console.log("[fMP4] Parse result:", {
|
|
1942
|
+
isHevc: initInfo?.isHevc,
|
|
1943
|
+
hasAvcC: !!initInfo?.avcC,
|
|
1944
|
+
hasHvcC: !!initInfo?.hvcC,
|
|
1945
|
+
timescale: initInfo?.timescale
|
|
1946
|
+
});
|
|
1947
|
+
this._isHevc = initInfo?.isHevc ?? false;
|
|
1948
|
+
if (initInfo?.hvcC && this._isHevc) {
|
|
1949
|
+
console.log("[fMP4] hvcC size:", initInfo.hvcC.length);
|
|
1950
|
+
await this.initHevcDecoder();
|
|
1951
|
+
this.initDecoderFromHvcC(initInfo.hvcC);
|
|
1952
|
+
console.log("[fMP4] HEVC decoder initialized, timescale:", initInfo.timescale);
|
|
1953
|
+
} else if (initInfo?.avcC) {
|
|
1695
1954
|
console.log("[fMP4] avcC size:", initInfo.avcC.length);
|
|
1696
1955
|
this.initDecoderFromAvcC(initInfo.avcC);
|
|
1697
|
-
console.log("[fMP4]
|
|
1956
|
+
console.log("[fMP4] AVC decoder initialized, timescale:", initInfo.timescale);
|
|
1957
|
+
} else {
|
|
1958
|
+
console.error("[fMP4] Failed to parse init segment or no avcC/hvcC found!");
|
|
1959
|
+
throw new Error("Failed to parse fMP4 init segment: no valid codec config found");
|
|
1960
|
+
}
|
|
1961
|
+
}
|
|
1962
|
+
/**
|
|
1963
|
+
* 加载新的初始化段(用于不连续性流)
|
|
1964
|
+
*/
|
|
1965
|
+
async loadNewInitSegment(uri) {
|
|
1966
|
+
console.log("[fMP4] Loading new init segment for discontinuity:", uri);
|
|
1967
|
+
this._sampleQueue.length = 0;
|
|
1968
|
+
this._nalQueue.length = 0;
|
|
1969
|
+
const url = uri.startsWith("http") ? uri : this.currentPlaylistUrl.substring(0, this.currentPlaylistUrl.lastIndexOf("/") + 1) + uri;
|
|
1970
|
+
const response = await fetch(url);
|
|
1971
|
+
const data = new Uint8Array(await response.arrayBuffer());
|
|
1972
|
+
console.log("[fMP4] New init segment data size:", data.length, "bytes");
|
|
1973
|
+
const initInfo = this.fmp4Demuxer.parseInitSegment(data);
|
|
1974
|
+
console.log("[fMP4] New init segment parse result:", {
|
|
1975
|
+
isHevc: initInfo?.isHevc,
|
|
1976
|
+
hasAvcC: !!initInfo?.avcC,
|
|
1977
|
+
hasHvcC: !!initInfo?.hvcC
|
|
1978
|
+
});
|
|
1979
|
+
const newIsHevc = initInfo?.isHevc ?? false;
|
|
1980
|
+
if (newIsHevc !== this._isHevc) {
|
|
1981
|
+
console.log("[fMP4] Codec type changed from", this._isHevc ? "HEVC" : "AVC", "to", newIsHevc ? "HEVC" : "AVC");
|
|
1982
|
+
this._isHevc = newIsHevc;
|
|
1983
|
+
this.decoderInitialized = false;
|
|
1984
|
+
if (newIsHevc) {
|
|
1985
|
+
await this.initHevcDecoder();
|
|
1986
|
+
}
|
|
1987
|
+
} else {
|
|
1988
|
+
this.decoderInitialized = false;
|
|
1989
|
+
}
|
|
1990
|
+
if (initInfo?.hvcC && this._isHevc) {
|
|
1991
|
+
this.initDecoderFromHvcC(initInfo.hvcC);
|
|
1992
|
+
console.log("[fMP4] HEVC decoder re-initialized for discontinuity");
|
|
1993
|
+
} else if (initInfo?.avcC) {
|
|
1994
|
+
this.initDecoderFromAvcC(initInfo.avcC);
|
|
1995
|
+
console.log("[fMP4] AVC decoder re-initialized for discontinuity");
|
|
1698
1996
|
} else {
|
|
1699
|
-
console.error("[fMP4] Failed to parse init segment
|
|
1700
|
-
throw new Error("Failed to parse fMP4 init segment");
|
|
1997
|
+
console.error("[fMP4] Failed to parse new init segment!");
|
|
1701
1998
|
}
|
|
1702
1999
|
}
|
|
2000
|
+
/**
|
|
2001
|
+
* 初始化 HEVC 解码器
|
|
2002
|
+
*/
|
|
2003
|
+
async initHevcDecoder() {
|
|
2004
|
+
if (this.hevcDecoder) return;
|
|
2005
|
+
this.hevcDecoder = new HEVCDecoder(this.wasmLoader);
|
|
2006
|
+
await this.hevcDecoder.init();
|
|
2007
|
+
console.log("[fMP4] HEVC decoder created");
|
|
2008
|
+
}
|
|
1703
2009
|
/**
|
|
1704
2010
|
* 从 avcC 初始化解码器
|
|
1705
2011
|
*/
|
|
@@ -1744,6 +2050,40 @@ class HLSPlayer extends BasePlayer {
|
|
|
1744
2050
|
this.decoderInitialized = true;
|
|
1745
2051
|
console.log("[fMP4] Decoder initialized from avcC");
|
|
1746
2052
|
}
|
|
2053
|
+
/**
|
|
2054
|
+
* 从 hvcC 初始化 HEVC 解码器
|
|
2055
|
+
*/
|
|
2056
|
+
initDecoderFromHvcC(hvcC) {
|
|
2057
|
+
if (this.decoderInitialized || !this.hevcDecoder) return;
|
|
2058
|
+
let offset = 22;
|
|
2059
|
+
const numOfArrays = hvcC[offset];
|
|
2060
|
+
offset += 1;
|
|
2061
|
+
console.log(`[fMP4] hvcC: numOfArrays=${numOfArrays}`);
|
|
2062
|
+
for (let i = 0; i < numOfArrays && offset < hvcC.length - 3; i++) {
|
|
2063
|
+
const typeCompressed = hvcC[offset];
|
|
2064
|
+
const arrayType = typeCompressed & 63;
|
|
2065
|
+
const numNalus = hvcC[offset + 1] << 8 | hvcC[offset + 2];
|
|
2066
|
+
offset += 3;
|
|
2067
|
+
console.log(`[fMP4] HEVC array: type=${arrayType}, count=${numNalus}`);
|
|
2068
|
+
for (let j = 0; j < numNalus && offset < hvcC.length - 2; j++) {
|
|
2069
|
+
const nalUnitLength = hvcC[offset] << 8 | hvcC[offset + 1];
|
|
2070
|
+
offset += 2;
|
|
2071
|
+
if (offset + nalUnitLength > hvcC.length) break;
|
|
2072
|
+
const nalUnitData = hvcC.slice(offset, offset + nalUnitLength);
|
|
2073
|
+
offset += nalUnitLength;
|
|
2074
|
+
const nalWithStartCode = new Uint8Array(4 + nalUnitLength);
|
|
2075
|
+
nalWithStartCode.set([0, 0, 0, 1], 0);
|
|
2076
|
+
nalWithStartCode.set(nalUnitData, 4);
|
|
2077
|
+
if (arrayType === 32 || arrayType === 33 || arrayType === 34) {
|
|
2078
|
+
this.hevcDecoder.decode({ type: arrayType, data: nalWithStartCode, size: nalWithStartCode.length });
|
|
2079
|
+
const typeName = arrayType === 32 ? "VPS" : arrayType === 33 ? "SPS" : "PPS";
|
|
2080
|
+
console.log(`[fMP4] HEVC ${typeName} decoded, length=${nalUnitLength}`);
|
|
2081
|
+
}
|
|
2082
|
+
}
|
|
2083
|
+
}
|
|
2084
|
+
this.decoderInitialized = true;
|
|
2085
|
+
console.log("[fMP4] HEVC decoder initialized from hvcC");
|
|
2086
|
+
}
|
|
1747
2087
|
/**
|
|
1748
2088
|
* 解析 HLS 播放列表
|
|
1749
2089
|
*/
|
|
@@ -1776,25 +2116,31 @@ class HLSPlayer extends BasePlayer {
|
|
|
1776
2116
|
}
|
|
1777
2117
|
const isFMP4 = lines.some((line) => line.includes("#EXT-X-MAP:"));
|
|
1778
2118
|
console.log("[parsePlaylist] isFMP4:", isFMP4, "url:", url);
|
|
2119
|
+
let currentInitSegment;
|
|
1779
2120
|
for (let i = 0; i < lines.length; i++) {
|
|
1780
2121
|
const line = lines[i].trim();
|
|
1781
2122
|
if (line.startsWith("#EXT-X-MAP:")) {
|
|
1782
2123
|
const mapInfo = line.substring("#EXT-X-MAP:".length);
|
|
1783
2124
|
const uriMatch = mapInfo.match(/URI="([^"]+)"/);
|
|
1784
2125
|
if (uriMatch) {
|
|
1785
|
-
|
|
2126
|
+
currentInitSegment = { uri: uriMatch[1] };
|
|
1786
2127
|
const byteRangeMatch = mapInfo.match(/BYTERANGE="(\d+)@(\d+)"/);
|
|
1787
2128
|
if (byteRangeMatch) {
|
|
1788
|
-
|
|
2129
|
+
currentInitSegment.byteRange = {
|
|
1789
2130
|
start: parseInt(byteRangeMatch[2]),
|
|
1790
2131
|
end: parseInt(byteRangeMatch[2]) + parseInt(byteRangeMatch[1]) - 1
|
|
1791
2132
|
};
|
|
1792
2133
|
}
|
|
2134
|
+
if (!initSegment) {
|
|
2135
|
+
initSegment = currentInitSegment;
|
|
2136
|
+
}
|
|
1793
2137
|
}
|
|
2138
|
+
continue;
|
|
2139
|
+
}
|
|
2140
|
+
if (line === "#EXT-X-DISCONTINUITY") {
|
|
2141
|
+
console.log("[parsePlaylist] Found EXT-X-DISCONTINUITY");
|
|
2142
|
+
continue;
|
|
1794
2143
|
}
|
|
1795
|
-
}
|
|
1796
|
-
for (let i = 0; i < lines.length; i++) {
|
|
1797
|
-
const line = lines[i].trim();
|
|
1798
2144
|
if (line.startsWith("#EXTINF:")) {
|
|
1799
2145
|
const duration = parseFloat(line.split(":")[1].split(",")[0]);
|
|
1800
2146
|
let byteRange;
|
|
@@ -1812,7 +2158,13 @@ class HLSPlayer extends BasePlayer {
|
|
|
1812
2158
|
const uri = nextLine;
|
|
1813
2159
|
if (uri && !uri.startsWith("#")) {
|
|
1814
2160
|
if (isFMP4) {
|
|
1815
|
-
fmp4Segments.push({
|
|
2161
|
+
fmp4Segments.push({
|
|
2162
|
+
uri,
|
|
2163
|
+
duration,
|
|
2164
|
+
byteRange,
|
|
2165
|
+
initSegmentUri: currentInitSegment?.uri
|
|
2166
|
+
// 关联当前初始化段
|
|
2167
|
+
});
|
|
1816
2168
|
} else {
|
|
1817
2169
|
segments.push({ uri, duration });
|
|
1818
2170
|
}
|
|
@@ -1855,7 +2207,8 @@ class HLSPlayer extends BasePlayer {
|
|
|
1855
2207
|
* 解码 fMP4 sample
|
|
1856
2208
|
*/
|
|
1857
2209
|
decodeSample(sample) {
|
|
1858
|
-
|
|
2210
|
+
const decoder = this._isHevc ? this.hevcDecoder : this.decoder;
|
|
2211
|
+
if (!decoder) {
|
|
1859
2212
|
console.warn("[fMP4] Decoder not available");
|
|
1860
2213
|
return null;
|
|
1861
2214
|
}
|
|
@@ -1866,8 +2219,9 @@ class HLSPlayer extends BasePlayer {
|
|
|
1866
2219
|
const data = sample.data;
|
|
1867
2220
|
const hasStartCode = data.length >= 4 && data[0] === 0 && data[1] === 0 && (data[2] === 1 || data[2] === 0 && data[3] === 1);
|
|
1868
2221
|
if (hasStartCode) {
|
|
1869
|
-
|
|
1870
|
-
|
|
2222
|
+
const nalType2 = this._isHevc ? sample.isSync ? HEVC_NAL_TYPE.IDR_W_RADL : 1 : sample.isSync ? 5 : 1;
|
|
2223
|
+
return decoder.decode({
|
|
2224
|
+
type: nalType2,
|
|
1871
2225
|
data,
|
|
1872
2226
|
size: data.length
|
|
1873
2227
|
});
|
|
@@ -1906,11 +2260,17 @@ class HLSPlayer extends BasePlayer {
|
|
|
1906
2260
|
writeOffset += nalLength;
|
|
1907
2261
|
offset += 4 + nalLength;
|
|
1908
2262
|
}
|
|
1909
|
-
const
|
|
1910
|
-
|
|
2263
|
+
const nalType = this._isHevc ? sample.isSync ? HEVC_NAL_TYPE.IDR_W_RADL : 1 : sample.isSync ? 5 : 1;
|
|
2264
|
+
const frame = decoder.decode({
|
|
2265
|
+
type: nalType,
|
|
1911
2266
|
data: annexBData,
|
|
1912
2267
|
size: annexBData.length
|
|
1913
2268
|
});
|
|
2269
|
+
if (frame) {
|
|
2270
|
+
frame.pts = sample.pts;
|
|
2271
|
+
frame.dts = sample.dts;
|
|
2272
|
+
frame.duration = sample.duration;
|
|
2273
|
+
}
|
|
1914
2274
|
return frame;
|
|
1915
2275
|
}
|
|
1916
2276
|
/**
|
|
@@ -2388,89 +2748,6 @@ class FLVDemuxer {
|
|
|
2388
2748
|
return tag.frameType === FRAME_TYPE_KEYFRAME;
|
|
2389
2749
|
}
|
|
2390
2750
|
}
|
|
2391
|
-
class HEVCDecoder {
|
|
2392
|
-
constructor(wasmLoader) {
|
|
2393
|
-
this.wasmModule = null;
|
|
2394
|
-
this.decoderContext = null;
|
|
2395
|
-
this.wasmModule = wasmLoader.getModule();
|
|
2396
|
-
}
|
|
2397
|
-
async init() {
|
|
2398
|
-
if (!this.wasmModule) {
|
|
2399
|
-
throw new Error("WASM module not loaded");
|
|
2400
|
-
}
|
|
2401
|
-
this.decoderContext = this.wasmModule._create_decoder(173);
|
|
2402
|
-
if (this.decoderContext === 0 || this.decoderContext === null) {
|
|
2403
|
-
throw new Error("Failed to create HEVC decoder context");
|
|
2404
|
-
}
|
|
2405
|
-
console.log("[HEVCDecoder] Initialized with codec_id=173");
|
|
2406
|
-
}
|
|
2407
|
-
decode(nalUnit) {
|
|
2408
|
-
if (this.decoderContext === null || !this.wasmModule) {
|
|
2409
|
-
return null;
|
|
2410
|
-
}
|
|
2411
|
-
const dataPtr = this.wasmModule._malloc(nalUnit.size);
|
|
2412
|
-
this.wasmModule.HEAPU8.set(nalUnit.data, dataPtr);
|
|
2413
|
-
const result = this.wasmModule._decode_video(
|
|
2414
|
-
this.decoderContext,
|
|
2415
|
-
dataPtr,
|
|
2416
|
-
nalUnit.size
|
|
2417
|
-
);
|
|
2418
|
-
this.wasmModule._free(dataPtr);
|
|
2419
|
-
if (result === 1) {
|
|
2420
|
-
const width = this.wasmModule._get_frame_width(this.decoderContext);
|
|
2421
|
-
const height = this.wasmModule._get_frame_height(this.decoderContext);
|
|
2422
|
-
if (width <= 0 || height <= 0) {
|
|
2423
|
-
return null;
|
|
2424
|
-
}
|
|
2425
|
-
const yPtr = this.wasmModule._get_frame_data(this.decoderContext, 0);
|
|
2426
|
-
const uPtr = this.wasmModule._get_frame_data(this.decoderContext, 1);
|
|
2427
|
-
const vPtr = this.wasmModule._get_frame_data(this.decoderContext, 2);
|
|
2428
|
-
const yLineSize = this.wasmModule._get_frame_linesize(this.decoderContext, 0);
|
|
2429
|
-
const uLineSize = this.wasmModule._get_frame_linesize(this.decoderContext, 1);
|
|
2430
|
-
const vLineSize = this.wasmModule._get_frame_linesize(this.decoderContext, 2);
|
|
2431
|
-
const frameData = this.yuv420pToRgba(
|
|
2432
|
-
yPtr,
|
|
2433
|
-
uPtr,
|
|
2434
|
-
vPtr,
|
|
2435
|
-
width,
|
|
2436
|
-
height,
|
|
2437
|
-
yLineSize,
|
|
2438
|
-
uLineSize,
|
|
2439
|
-
vLineSize
|
|
2440
|
-
);
|
|
2441
|
-
return { width, height, data: frameData };
|
|
2442
|
-
}
|
|
2443
|
-
return null;
|
|
2444
|
-
}
|
|
2445
|
-
yuv420pToRgba(yPtr, uPtr, vPtr, width, height, yLineSize, uLineSize, vLineSize) {
|
|
2446
|
-
const rgba = new Uint8Array(width * height * 4);
|
|
2447
|
-
for (let y = 0; y < height; y++) {
|
|
2448
|
-
for (let x = 0; x < width; x++) {
|
|
2449
|
-
const yIndex = y * yLineSize + x;
|
|
2450
|
-
const uIndex = (y >> 1) * uLineSize + (x >> 1);
|
|
2451
|
-
const vIndex = (y >> 1) * vLineSize + (x >> 1);
|
|
2452
|
-
const yValue = this.wasmModule.HEAPU8[yPtr + yIndex];
|
|
2453
|
-
const uValue = this.wasmModule.HEAPU8[uPtr + uIndex];
|
|
2454
|
-
const vValue = this.wasmModule.HEAPU8[vPtr + vIndex];
|
|
2455
|
-
const c = yValue - 16;
|
|
2456
|
-
const d = uValue - 128;
|
|
2457
|
-
const e = vValue - 128;
|
|
2458
|
-
let r = 298 * c + 409 * e + 128 >> 8;
|
|
2459
|
-
let g = 298 * c - 100 * d - 208 * e + 128 >> 8;
|
|
2460
|
-
let b = 298 * c + 516 * d + 128 >> 8;
|
|
2461
|
-
r = r < 0 ? 0 : r > 255 ? 255 : r;
|
|
2462
|
-
g = g < 0 ? 0 : g > 255 ? 255 : g;
|
|
2463
|
-
b = b < 0 ? 0 : b > 255 ? 255 : b;
|
|
2464
|
-
const rgbaIndex = (y * width + x) * 4;
|
|
2465
|
-
rgba[rgbaIndex] = r;
|
|
2466
|
-
rgba[rgbaIndex + 1] = g;
|
|
2467
|
-
rgba[rgbaIndex + 2] = b;
|
|
2468
|
-
rgba[rgbaIndex + 3] = 255;
|
|
2469
|
-
}
|
|
2470
|
-
}
|
|
2471
|
-
return rgba;
|
|
2472
|
-
}
|
|
2473
|
-
}
|
|
2474
2751
|
const CODEC_ID_AVC = 7;
|
|
2475
2752
|
const CODEC_ID_HEVC = 12;
|
|
2476
2753
|
const FLV_PREFETCHER_CONFIG = {
|
|
@@ -2734,8 +3011,8 @@ class FLVPlayer extends BasePlayer {
|
|
|
2734
3011
|
* 开始播放(覆盖基类方法)
|
|
2735
3012
|
*/
|
|
2736
3013
|
async play() {
|
|
2737
|
-
const MIN_BUFFER_SIZE = this.dynamicMinBufferSize;
|
|
2738
|
-
const MAX_WAIT_TIME = 1e4;
|
|
3014
|
+
const MIN_BUFFER_SIZE = this.config.isLive ? 3 : this.dynamicMinBufferSize;
|
|
3015
|
+
const MAX_WAIT_TIME = this.config.isLive ? 3e3 : 1e4;
|
|
2739
3016
|
const startTime = Date.now();
|
|
2740
3017
|
console.log(`[FLVPlayer] Waiting for buffer (target: ${MIN_BUFFER_SIZE} frames)...`);
|
|
2741
3018
|
let aggressiveDecodeCount = 0;
|
|
@@ -2766,6 +3043,12 @@ class FLVPlayer extends BasePlayer {
|
|
|
2766
3043
|
}
|
|
2767
3044
|
console.log("[FLVPlayer] Buffer ready, frames:", this._timedFrameBuffer.length, "queue:", this._videoTagQueue.length);
|
|
2768
3045
|
if (this._timedFrameBuffer.length > 0) {
|
|
3046
|
+
if (this.config.isLive && this._timedFrameBuffer.length > 60) {
|
|
3047
|
+
const droppedCount = this._timedFrameBuffer.length - 30;
|
|
3048
|
+
this._timedFrameBuffer = this._timedFrameBuffer.slice(-30);
|
|
3049
|
+
this.droppedFrames += droppedCount;
|
|
3050
|
+
console.log("[FLVPlayer] Live resume: dropped", droppedCount, "old frames, keeping latest 30");
|
|
3051
|
+
}
|
|
2769
3052
|
this.firstFrameDts = this._timedFrameBuffer[0].dts;
|
|
2770
3053
|
this.playStartTime = performance.now();
|
|
2771
3054
|
console.log("[FLVPlayer] Play time initialized, firstFrameDts:", this.firstFrameDts);
|
|
@@ -2788,8 +3071,7 @@ class FLVPlayer extends BasePlayer {
|
|
|
2788
3071
|
this.prefetcher.stop();
|
|
2789
3072
|
}
|
|
2790
3073
|
if (this.config.isLive) {
|
|
2791
|
-
console.log("[FLVPlayer] Pausing live stream,
|
|
2792
|
-
this.stopLiveDownload();
|
|
3074
|
+
console.log("[FLVPlayer] Pausing live stream, keeping download running");
|
|
2793
3075
|
}
|
|
2794
3076
|
}
|
|
2795
3077
|
/**
|