@give-tech/ec-player 0.0.1-beta.5 → 0.0.1-beta.7
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 +17 -1
- package/dist/demuxer/fMP4Demuxer.d.ts.map +1 -1
- package/dist/index.js +266 -102
- package/dist/index.js.map +1 -1
- package/dist/player/HLSPlayer.d.ts +16 -0
- package/dist/player/HLSPlayer.d.ts.map +1 -1
- package/dist/prefetch/SegmentPrefetcher.d.ts +2 -0
- package/dist/prefetch/SegmentPrefetcher.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -50,11 +50,15 @@ export interface InitSegment {
|
|
|
50
50
|
trackId: number;
|
|
51
51
|
timescale: number;
|
|
52
52
|
avcC?: Uint8Array;
|
|
53
|
+
hvcC?: Uint8Array;
|
|
54
|
+
isHevc?: boolean;
|
|
53
55
|
}
|
|
54
56
|
export declare class fMP4Demuxer {
|
|
55
57
|
private timescale;
|
|
56
58
|
private trackId;
|
|
57
59
|
private avcC;
|
|
60
|
+
private hvcC;
|
|
61
|
+
private isHevc;
|
|
58
62
|
/**
|
|
59
63
|
* 解析初始化段 (ftyp + moov)
|
|
60
64
|
*/
|
|
@@ -84,13 +88,17 @@ export declare class fMP4Demuxer {
|
|
|
84
88
|
*/
|
|
85
89
|
private parseStbl;
|
|
86
90
|
/**
|
|
87
|
-
* 解析 stsd box 获取 avcC
|
|
91
|
+
* 解析 stsd box 获取 avcC 或 hvcC
|
|
88
92
|
*/
|
|
89
93
|
private parseStsd;
|
|
90
94
|
/**
|
|
91
95
|
* 解析 AVC Sample Entry
|
|
92
96
|
*/
|
|
93
97
|
private parseAvcSampleEntry;
|
|
98
|
+
/**
|
|
99
|
+
* 解析 HEVC Sample Entry
|
|
100
|
+
*/
|
|
101
|
+
private parseHevcSampleEntry;
|
|
94
102
|
/**
|
|
95
103
|
* 解析 moof box
|
|
96
104
|
*/
|
|
@@ -123,6 +131,14 @@ export declare class fMP4Demuxer {
|
|
|
123
131
|
* 获取 avcC 配置
|
|
124
132
|
*/
|
|
125
133
|
getAvcC(): Uint8Array | null;
|
|
134
|
+
/**
|
|
135
|
+
* 获取 hvcC 配置
|
|
136
|
+
*/
|
|
137
|
+
getHvcC(): Uint8Array | null;
|
|
138
|
+
/**
|
|
139
|
+
* 是否为 HEVC 编码
|
|
140
|
+
*/
|
|
141
|
+
isHevcStream(): boolean;
|
|
126
142
|
}
|
|
127
143
|
/**
|
|
128
144
|
* 检测是否为 fMP4 格式
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fMP4Demuxer.d.ts","sourceRoot":"","sources":["../../src/demuxer/fMP4Demuxer.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AA8CH,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,qBAAqB,CAAC,EAAE,MAAM,CAAA;CAC/B;AAED,MAAM,WAAW,OAAO;IACtB,OAAO,EAAE,MAAM,CAAA;IACf,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,sBAAsB,CAAC,EAAE,MAAM,CAAA;IAC/B,qBAAqB,CAAC,EAAE,MAAM,CAAA;IAC9B,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B,kBAAkB,CAAC,EAAE,MAAM,CAAA;CAC5B;AAED,MAAM,WAAW,OAAO;IACtB,mBAAmB,EAAE,MAAM,CAAA;CAC5B;AAED,MAAM,WAAW,OAAO;IACtB,WAAW,EAAE,MAAM,CAAA;IACnB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,OAAO,EAAE,UAAU,EAAE,CAAA;CACtB;AAED,MAAM,WAAW,OAAO;IACtB,IAAI,CAAC,EAAE,OAAO,CAAA;IACd,IAAI,CAAC,EAAE,OAAO,CAAA;IACd,KAAK,EAAE,OAAO,EAAE,CAAA;CACjB;AAED,MAAM,WAAW,OAAO;IACtB,IAAI,CAAC,EAAE;QAAE,cAAc,EAAE,MAAM,CAAA;KAAE,CAAA;IACjC,KAAK,EAAE,OAAO,EAAE,CAAA;IAChB,UAAU,EAAE,MAAM,CAAA;IAClB,IAAI,EAAE,MAAM,CAAA;CACb;AAED,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,UAAU,CAAA;IAChB,GAAG,EAAE,MAAM,CAAA;IACX,GAAG,EAAE,MAAM,CAAA;IACX,QAAQ,EAAE,MAAM,CAAA;IAChB,MAAM,EAAE,OAAO,CAAA;CAChB;AAED,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,MAAM,CAAA;IACf,SAAS,EAAE,MAAM,CAAA;IACjB,IAAI,CAAC,EAAE,UAAU,CAAA;
|
|
1
|
+
{"version":3,"file":"fMP4Demuxer.d.ts","sourceRoot":"","sources":["../../src/demuxer/fMP4Demuxer.ts"],"names":[],"mappings":"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"}
|
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
|
/**
|
|
@@ -530,7 +534,7 @@ class fMP4Demuxer {
|
|
|
530
534
|
}
|
|
531
535
|
}
|
|
532
536
|
/**
|
|
533
|
-
* 解析 stsd box 获取 avcC
|
|
537
|
+
* 解析 stsd box 获取 avcC 或 hvcC
|
|
534
538
|
*/
|
|
535
539
|
parseStsd(data, stsdOffset, stsdSize) {
|
|
536
540
|
const boxDataOffset = stsdOffset + 8;
|
|
@@ -544,6 +548,10 @@ class fMP4Demuxer {
|
|
|
544
548
|
this.parseAvcSampleEntry(data, offset, entrySize);
|
|
545
549
|
return;
|
|
546
550
|
}
|
|
551
|
+
if (entryType === "hvc1" || entryType === "hev1") {
|
|
552
|
+
this.parseHevcSampleEntry(data, offset, entrySize);
|
|
553
|
+
return;
|
|
554
|
+
}
|
|
547
555
|
offset += entrySize;
|
|
548
556
|
}
|
|
549
557
|
}
|
|
@@ -564,6 +572,25 @@ class fMP4Demuxer {
|
|
|
564
572
|
offset += boxSize;
|
|
565
573
|
}
|
|
566
574
|
}
|
|
575
|
+
/**
|
|
576
|
+
* 解析 HEVC Sample Entry
|
|
577
|
+
*/
|
|
578
|
+
parseHevcSampleEntry(data, entryOffset, entrySize) {
|
|
579
|
+
this.isHevc = true;
|
|
580
|
+
let offset = entryOffset + 8 + 78;
|
|
581
|
+
const endOffset = entryOffset + entrySize;
|
|
582
|
+
while (offset < endOffset - 8) {
|
|
583
|
+
const boxSize = readUint32$1(data, offset);
|
|
584
|
+
const boxType = readFourCC(data, offset + 4);
|
|
585
|
+
if (boxSize < 8) break;
|
|
586
|
+
if (boxType === "hvcC") {
|
|
587
|
+
this.hvcC = data.slice(offset, offset + boxSize);
|
|
588
|
+
console.log("[fMP4Demuxer] Found hvcC, size:", boxSize);
|
|
589
|
+
return;
|
|
590
|
+
}
|
|
591
|
+
offset += boxSize;
|
|
592
|
+
}
|
|
593
|
+
}
|
|
567
594
|
/**
|
|
568
595
|
* 解析 moof box
|
|
569
596
|
*/
|
|
@@ -757,6 +784,18 @@ class fMP4Demuxer {
|
|
|
757
784
|
getAvcC() {
|
|
758
785
|
return this.avcC;
|
|
759
786
|
}
|
|
787
|
+
/**
|
|
788
|
+
* 获取 hvcC 配置
|
|
789
|
+
*/
|
|
790
|
+
getHvcC() {
|
|
791
|
+
return this.hvcC;
|
|
792
|
+
}
|
|
793
|
+
/**
|
|
794
|
+
* 是否为 HEVC 编码
|
|
795
|
+
*/
|
|
796
|
+
isHevcStream() {
|
|
797
|
+
return this.isHevc;
|
|
798
|
+
}
|
|
760
799
|
}
|
|
761
800
|
class NALParser {
|
|
762
801
|
/**
|
|
@@ -804,6 +843,93 @@ class NALParser {
|
|
|
804
843
|
return units;
|
|
805
844
|
}
|
|
806
845
|
}
|
|
846
|
+
const HEVC_NAL_TYPE = {
|
|
847
|
+
// Picture Parameter Set
|
|
848
|
+
IDR_W_RADL: 19
|
|
849
|
+
};
|
|
850
|
+
class HEVCDecoder {
|
|
851
|
+
constructor(wasmLoader) {
|
|
852
|
+
this.wasmModule = null;
|
|
853
|
+
this.decoderContext = null;
|
|
854
|
+
this.wasmModule = wasmLoader.getModule();
|
|
855
|
+
}
|
|
856
|
+
async init() {
|
|
857
|
+
if (!this.wasmModule) {
|
|
858
|
+
throw new Error("WASM module not loaded");
|
|
859
|
+
}
|
|
860
|
+
this.decoderContext = this.wasmModule._create_decoder(173);
|
|
861
|
+
if (this.decoderContext === 0 || this.decoderContext === null) {
|
|
862
|
+
throw new Error("Failed to create HEVC decoder context");
|
|
863
|
+
}
|
|
864
|
+
console.log("[HEVCDecoder] Initialized with codec_id=173");
|
|
865
|
+
}
|
|
866
|
+
decode(nalUnit) {
|
|
867
|
+
if (this.decoderContext === null || !this.wasmModule) {
|
|
868
|
+
return null;
|
|
869
|
+
}
|
|
870
|
+
const dataPtr = this.wasmModule._malloc(nalUnit.size);
|
|
871
|
+
this.wasmModule.HEAPU8.set(nalUnit.data, dataPtr);
|
|
872
|
+
const result = this.wasmModule._decode_video(
|
|
873
|
+
this.decoderContext,
|
|
874
|
+
dataPtr,
|
|
875
|
+
nalUnit.size
|
|
876
|
+
);
|
|
877
|
+
this.wasmModule._free(dataPtr);
|
|
878
|
+
if (result === 1) {
|
|
879
|
+
const width = this.wasmModule._get_frame_width(this.decoderContext);
|
|
880
|
+
const height = this.wasmModule._get_frame_height(this.decoderContext);
|
|
881
|
+
if (width <= 0 || height <= 0) {
|
|
882
|
+
return null;
|
|
883
|
+
}
|
|
884
|
+
const yPtr = this.wasmModule._get_frame_data(this.decoderContext, 0);
|
|
885
|
+
const uPtr = this.wasmModule._get_frame_data(this.decoderContext, 1);
|
|
886
|
+
const vPtr = this.wasmModule._get_frame_data(this.decoderContext, 2);
|
|
887
|
+
const yLineSize = this.wasmModule._get_frame_linesize(this.decoderContext, 0);
|
|
888
|
+
const uLineSize = this.wasmModule._get_frame_linesize(this.decoderContext, 1);
|
|
889
|
+
const vLineSize = this.wasmModule._get_frame_linesize(this.decoderContext, 2);
|
|
890
|
+
const frameData = this.yuv420pToRgba(
|
|
891
|
+
yPtr,
|
|
892
|
+
uPtr,
|
|
893
|
+
vPtr,
|
|
894
|
+
width,
|
|
895
|
+
height,
|
|
896
|
+
yLineSize,
|
|
897
|
+
uLineSize,
|
|
898
|
+
vLineSize
|
|
899
|
+
);
|
|
900
|
+
return { width, height, data: frameData };
|
|
901
|
+
}
|
|
902
|
+
return null;
|
|
903
|
+
}
|
|
904
|
+
yuv420pToRgba(yPtr, uPtr, vPtr, width, height, yLineSize, uLineSize, vLineSize) {
|
|
905
|
+
const rgba = new Uint8Array(width * height * 4);
|
|
906
|
+
for (let y = 0; y < height; y++) {
|
|
907
|
+
for (let x = 0; x < width; x++) {
|
|
908
|
+
const yIndex = y * yLineSize + x;
|
|
909
|
+
const uIndex = (y >> 1) * uLineSize + (x >> 1);
|
|
910
|
+
const vIndex = (y >> 1) * vLineSize + (x >> 1);
|
|
911
|
+
const yValue = this.wasmModule.HEAPU8[yPtr + yIndex];
|
|
912
|
+
const uValue = this.wasmModule.HEAPU8[uPtr + uIndex];
|
|
913
|
+
const vValue = this.wasmModule.HEAPU8[vPtr + vIndex];
|
|
914
|
+
const c = yValue - 16;
|
|
915
|
+
const d = uValue - 128;
|
|
916
|
+
const e = vValue - 128;
|
|
917
|
+
let r = 298 * c + 409 * e + 128 >> 8;
|
|
918
|
+
let g = 298 * c - 100 * d - 208 * e + 128 >> 8;
|
|
919
|
+
let b = 298 * c + 516 * d + 128 >> 8;
|
|
920
|
+
r = r < 0 ? 0 : r > 255 ? 255 : r;
|
|
921
|
+
g = g < 0 ? 0 : g > 255 ? 255 : g;
|
|
922
|
+
b = b < 0 ? 0 : b > 255 ? 255 : b;
|
|
923
|
+
const rgbaIndex = (y * width + x) * 4;
|
|
924
|
+
rgba[rgbaIndex] = r;
|
|
925
|
+
rgba[rgbaIndex + 1] = g;
|
|
926
|
+
rgba[rgbaIndex + 2] = b;
|
|
927
|
+
rgba[rgbaIndex + 3] = 255;
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
return rgba;
|
|
931
|
+
}
|
|
932
|
+
}
|
|
807
933
|
class BasePrefetcher {
|
|
808
934
|
constructor(config, callbacks = {}) {
|
|
809
935
|
this.isRunning = false;
|
|
@@ -1334,12 +1460,18 @@ const DEFAULT_HLS_CONFIG = {
|
|
|
1334
1460
|
class HLSSegmentPrefetcher extends SegmentPrefetcher {
|
|
1335
1461
|
constructor(config, callbacks, player) {
|
|
1336
1462
|
super(config, callbacks);
|
|
1463
|
+
this.currentInitSegmentUri = null;
|
|
1337
1464
|
this.player = player;
|
|
1338
1465
|
}
|
|
1339
1466
|
/**
|
|
1340
1467
|
* 获取分片数据
|
|
1341
1468
|
*/
|
|
1342
1469
|
async fetchSegment(segment, index) {
|
|
1470
|
+
if (segment.initSegmentUri && segment.initSegmentUri !== this.currentInitSegmentUri) {
|
|
1471
|
+
console.log("[HLSSegmentPrefetcher] Init segment changed:", segment.initSegmentUri);
|
|
1472
|
+
await this.player.loadNewInitSegment(segment.initSegmentUri);
|
|
1473
|
+
this.currentInitSegmentUri = segment.initSegmentUri;
|
|
1474
|
+
}
|
|
1343
1475
|
const baseUrl = this.baseUrl;
|
|
1344
1476
|
const url = segment.uri.startsWith("http") ? segment.uri : baseUrl + segment.uri;
|
|
1345
1477
|
const headers = {};
|
|
@@ -1378,12 +1510,14 @@ class HLSPlayer extends BasePlayer {
|
|
|
1378
1510
|
this.pesExtractor = new PESExtractor();
|
|
1379
1511
|
this.nalParser = new NALParser();
|
|
1380
1512
|
this.fmp4Demuxer = new fMP4Demuxer();
|
|
1513
|
+
this.hevcDecoder = null;
|
|
1381
1514
|
this.isPrefetching = false;
|
|
1382
1515
|
this.currentSegmentIndex = 0;
|
|
1383
1516
|
this.segments = [];
|
|
1384
1517
|
this.fmp4Segments = [];
|
|
1385
1518
|
this.initSegment = null;
|
|
1386
1519
|
this._isFMP4 = false;
|
|
1520
|
+
this._isHevc = false;
|
|
1387
1521
|
this.currentPlaylistUrl = "";
|
|
1388
1522
|
this._nalQueue = [];
|
|
1389
1523
|
this._sampleQueue = [];
|
|
@@ -1423,6 +1557,9 @@ class HLSPlayer extends BasePlayer {
|
|
|
1423
1557
|
get isFMP4() {
|
|
1424
1558
|
return this._isFMP4;
|
|
1425
1559
|
}
|
|
1560
|
+
get isHevc() {
|
|
1561
|
+
return this._isHevc;
|
|
1562
|
+
}
|
|
1426
1563
|
get nalQueue() {
|
|
1427
1564
|
return this._nalQueue;
|
|
1428
1565
|
}
|
|
@@ -1604,7 +1741,9 @@ class HLSPlayer extends BasePlayer {
|
|
|
1604
1741
|
const segmentInfos = this.fmp4Segments.map((seg) => ({
|
|
1605
1742
|
uri: seg.uri,
|
|
1606
1743
|
duration: seg.duration,
|
|
1607
|
-
byteRange: seg.byteRange
|
|
1744
|
+
byteRange: seg.byteRange,
|
|
1745
|
+
initSegmentUri: seg.initSegmentUri
|
|
1746
|
+
// 传递初始化段 URI
|
|
1608
1747
|
}));
|
|
1609
1748
|
this.prefetcher.setSegments(segmentInfos, baseUrl);
|
|
1610
1749
|
} else {
|
|
@@ -1690,16 +1829,75 @@ class HLSPlayer extends BasePlayer {
|
|
|
1690
1829
|
const data = new Uint8Array(await response.arrayBuffer());
|
|
1691
1830
|
console.log("[fMP4] Init segment data size:", data.length, "bytes");
|
|
1692
1831
|
const initInfo = this.fmp4Demuxer.parseInitSegment(data);
|
|
1693
|
-
console.log("[fMP4] Parse result:",
|
|
1694
|
-
|
|
1832
|
+
console.log("[fMP4] Parse result:", {
|
|
1833
|
+
isHevc: initInfo?.isHevc,
|
|
1834
|
+
hasAvcC: !!initInfo?.avcC,
|
|
1835
|
+
hasHvcC: !!initInfo?.hvcC,
|
|
1836
|
+
timescale: initInfo?.timescale
|
|
1837
|
+
});
|
|
1838
|
+
this._isHevc = initInfo?.isHevc ?? false;
|
|
1839
|
+
if (initInfo?.hvcC && this._isHevc) {
|
|
1840
|
+
console.log("[fMP4] hvcC size:", initInfo.hvcC.length);
|
|
1841
|
+
await this.initHevcDecoder();
|
|
1842
|
+
this.initDecoderFromHvcC(initInfo.hvcC);
|
|
1843
|
+
console.log("[fMP4] HEVC decoder initialized, timescale:", initInfo.timescale);
|
|
1844
|
+
} else if (initInfo?.avcC) {
|
|
1695
1845
|
console.log("[fMP4] avcC size:", initInfo.avcC.length);
|
|
1696
1846
|
this.initDecoderFromAvcC(initInfo.avcC);
|
|
1697
|
-
console.log("[fMP4]
|
|
1847
|
+
console.log("[fMP4] AVC decoder initialized, timescale:", initInfo.timescale);
|
|
1848
|
+
} else {
|
|
1849
|
+
console.error("[fMP4] Failed to parse init segment or no avcC/hvcC found!");
|
|
1850
|
+
throw new Error("Failed to parse fMP4 init segment: no valid codec config found");
|
|
1851
|
+
}
|
|
1852
|
+
}
|
|
1853
|
+
/**
|
|
1854
|
+
* 加载新的初始化段(用于不连续性流)
|
|
1855
|
+
*/
|
|
1856
|
+
async loadNewInitSegment(uri) {
|
|
1857
|
+
console.log("[fMP4] Loading new init segment for discontinuity:", uri);
|
|
1858
|
+
this._sampleQueue.length = 0;
|
|
1859
|
+
this._nalQueue.length = 0;
|
|
1860
|
+
this.frameBuffer = [];
|
|
1861
|
+
const url = uri.startsWith("http") ? uri : this.currentPlaylistUrl.substring(0, this.currentPlaylistUrl.lastIndexOf("/") + 1) + uri;
|
|
1862
|
+
const response = await fetch(url);
|
|
1863
|
+
const data = new Uint8Array(await response.arrayBuffer());
|
|
1864
|
+
console.log("[fMP4] New init segment data size:", data.length, "bytes");
|
|
1865
|
+
const initInfo = this.fmp4Demuxer.parseInitSegment(data);
|
|
1866
|
+
console.log("[fMP4] New init segment parse result:", {
|
|
1867
|
+
isHevc: initInfo?.isHevc,
|
|
1868
|
+
hasAvcC: !!initInfo?.avcC,
|
|
1869
|
+
hasHvcC: !!initInfo?.hvcC
|
|
1870
|
+
});
|
|
1871
|
+
const newIsHevc = initInfo?.isHevc ?? false;
|
|
1872
|
+
if (newIsHevc !== this._isHevc) {
|
|
1873
|
+
console.log("[fMP4] Codec type changed from", this._isHevc ? "HEVC" : "AVC", "to", newIsHevc ? "HEVC" : "AVC");
|
|
1874
|
+
this._isHevc = newIsHevc;
|
|
1875
|
+
this.decoderInitialized = false;
|
|
1876
|
+
if (newIsHevc) {
|
|
1877
|
+
await this.initHevcDecoder();
|
|
1878
|
+
}
|
|
1879
|
+
} else {
|
|
1880
|
+
this.decoderInitialized = false;
|
|
1881
|
+
}
|
|
1882
|
+
if (initInfo?.hvcC && this._isHevc) {
|
|
1883
|
+
this.initDecoderFromHvcC(initInfo.hvcC);
|
|
1884
|
+
console.log("[fMP4] HEVC decoder re-initialized for discontinuity");
|
|
1885
|
+
} else if (initInfo?.avcC) {
|
|
1886
|
+
this.initDecoderFromAvcC(initInfo.avcC);
|
|
1887
|
+
console.log("[fMP4] AVC decoder re-initialized for discontinuity");
|
|
1698
1888
|
} else {
|
|
1699
|
-
console.error("[fMP4] Failed to parse init segment
|
|
1700
|
-
throw new Error("Failed to parse fMP4 init segment");
|
|
1889
|
+
console.error("[fMP4] Failed to parse new init segment!");
|
|
1701
1890
|
}
|
|
1702
1891
|
}
|
|
1892
|
+
/**
|
|
1893
|
+
* 初始化 HEVC 解码器
|
|
1894
|
+
*/
|
|
1895
|
+
async initHevcDecoder() {
|
|
1896
|
+
if (this.hevcDecoder) return;
|
|
1897
|
+
this.hevcDecoder = new HEVCDecoder(this.wasmLoader);
|
|
1898
|
+
await this.hevcDecoder.init();
|
|
1899
|
+
console.log("[fMP4] HEVC decoder created");
|
|
1900
|
+
}
|
|
1703
1901
|
/**
|
|
1704
1902
|
* 从 avcC 初始化解码器
|
|
1705
1903
|
*/
|
|
@@ -1744,6 +1942,40 @@ class HLSPlayer extends BasePlayer {
|
|
|
1744
1942
|
this.decoderInitialized = true;
|
|
1745
1943
|
console.log("[fMP4] Decoder initialized from avcC");
|
|
1746
1944
|
}
|
|
1945
|
+
/**
|
|
1946
|
+
* 从 hvcC 初始化 HEVC 解码器
|
|
1947
|
+
*/
|
|
1948
|
+
initDecoderFromHvcC(hvcC) {
|
|
1949
|
+
if (this.decoderInitialized || !this.hevcDecoder) return;
|
|
1950
|
+
let offset = 22;
|
|
1951
|
+
const numOfArrays = hvcC[offset];
|
|
1952
|
+
offset += 1;
|
|
1953
|
+
console.log(`[fMP4] hvcC: numOfArrays=${numOfArrays}`);
|
|
1954
|
+
for (let i = 0; i < numOfArrays && offset < hvcC.length - 3; i++) {
|
|
1955
|
+
const typeCompressed = hvcC[offset];
|
|
1956
|
+
const arrayType = typeCompressed & 63;
|
|
1957
|
+
const numNalus = hvcC[offset + 1] << 8 | hvcC[offset + 2];
|
|
1958
|
+
offset += 3;
|
|
1959
|
+
console.log(`[fMP4] HEVC array: type=${arrayType}, count=${numNalus}`);
|
|
1960
|
+
for (let j = 0; j < numNalus && offset < hvcC.length - 2; j++) {
|
|
1961
|
+
const nalUnitLength = hvcC[offset] << 8 | hvcC[offset + 1];
|
|
1962
|
+
offset += 2;
|
|
1963
|
+
if (offset + nalUnitLength > hvcC.length) break;
|
|
1964
|
+
const nalUnitData = hvcC.slice(offset, offset + nalUnitLength);
|
|
1965
|
+
offset += nalUnitLength;
|
|
1966
|
+
const nalWithStartCode = new Uint8Array(4 + nalUnitLength);
|
|
1967
|
+
nalWithStartCode.set([0, 0, 0, 1], 0);
|
|
1968
|
+
nalWithStartCode.set(nalUnitData, 4);
|
|
1969
|
+
if (arrayType === 32 || arrayType === 33 || arrayType === 34) {
|
|
1970
|
+
this.hevcDecoder.decode({ type: arrayType, data: nalWithStartCode, size: nalWithStartCode.length });
|
|
1971
|
+
const typeName = arrayType === 32 ? "VPS" : arrayType === 33 ? "SPS" : "PPS";
|
|
1972
|
+
console.log(`[fMP4] HEVC ${typeName} decoded, length=${nalUnitLength}`);
|
|
1973
|
+
}
|
|
1974
|
+
}
|
|
1975
|
+
}
|
|
1976
|
+
this.decoderInitialized = true;
|
|
1977
|
+
console.log("[fMP4] HEVC decoder initialized from hvcC");
|
|
1978
|
+
}
|
|
1747
1979
|
/**
|
|
1748
1980
|
* 解析 HLS 播放列表
|
|
1749
1981
|
*/
|
|
@@ -1776,25 +2008,31 @@ class HLSPlayer extends BasePlayer {
|
|
|
1776
2008
|
}
|
|
1777
2009
|
const isFMP4 = lines.some((line) => line.includes("#EXT-X-MAP:"));
|
|
1778
2010
|
console.log("[parsePlaylist] isFMP4:", isFMP4, "url:", url);
|
|
2011
|
+
let currentInitSegment;
|
|
1779
2012
|
for (let i = 0; i < lines.length; i++) {
|
|
1780
2013
|
const line = lines[i].trim();
|
|
1781
2014
|
if (line.startsWith("#EXT-X-MAP:")) {
|
|
1782
2015
|
const mapInfo = line.substring("#EXT-X-MAP:".length);
|
|
1783
2016
|
const uriMatch = mapInfo.match(/URI="([^"]+)"/);
|
|
1784
2017
|
if (uriMatch) {
|
|
1785
|
-
|
|
2018
|
+
currentInitSegment = { uri: uriMatch[1] };
|
|
1786
2019
|
const byteRangeMatch = mapInfo.match(/BYTERANGE="(\d+)@(\d+)"/);
|
|
1787
2020
|
if (byteRangeMatch) {
|
|
1788
|
-
|
|
2021
|
+
currentInitSegment.byteRange = {
|
|
1789
2022
|
start: parseInt(byteRangeMatch[2]),
|
|
1790
2023
|
end: parseInt(byteRangeMatch[2]) + parseInt(byteRangeMatch[1]) - 1
|
|
1791
2024
|
};
|
|
1792
2025
|
}
|
|
2026
|
+
if (!initSegment) {
|
|
2027
|
+
initSegment = currentInitSegment;
|
|
2028
|
+
}
|
|
1793
2029
|
}
|
|
2030
|
+
continue;
|
|
2031
|
+
}
|
|
2032
|
+
if (line === "#EXT-X-DISCONTINUITY") {
|
|
2033
|
+
console.log("[parsePlaylist] Found EXT-X-DISCONTINUITY");
|
|
2034
|
+
continue;
|
|
1794
2035
|
}
|
|
1795
|
-
}
|
|
1796
|
-
for (let i = 0; i < lines.length; i++) {
|
|
1797
|
-
const line = lines[i].trim();
|
|
1798
2036
|
if (line.startsWith("#EXTINF:")) {
|
|
1799
2037
|
const duration = parseFloat(line.split(":")[1].split(",")[0]);
|
|
1800
2038
|
let byteRange;
|
|
@@ -1812,7 +2050,13 @@ class HLSPlayer extends BasePlayer {
|
|
|
1812
2050
|
const uri = nextLine;
|
|
1813
2051
|
if (uri && !uri.startsWith("#")) {
|
|
1814
2052
|
if (isFMP4) {
|
|
1815
|
-
fmp4Segments.push({
|
|
2053
|
+
fmp4Segments.push({
|
|
2054
|
+
uri,
|
|
2055
|
+
duration,
|
|
2056
|
+
byteRange,
|
|
2057
|
+
initSegmentUri: currentInitSegment?.uri
|
|
2058
|
+
// 关联当前初始化段
|
|
2059
|
+
});
|
|
1816
2060
|
} else {
|
|
1817
2061
|
segments.push({ uri, duration });
|
|
1818
2062
|
}
|
|
@@ -1855,7 +2099,8 @@ class HLSPlayer extends BasePlayer {
|
|
|
1855
2099
|
* 解码 fMP4 sample
|
|
1856
2100
|
*/
|
|
1857
2101
|
decodeSample(sample) {
|
|
1858
|
-
|
|
2102
|
+
const decoder = this._isHevc ? this.hevcDecoder : this.decoder;
|
|
2103
|
+
if (!decoder) {
|
|
1859
2104
|
console.warn("[fMP4] Decoder not available");
|
|
1860
2105
|
return null;
|
|
1861
2106
|
}
|
|
@@ -1866,8 +2111,9 @@ class HLSPlayer extends BasePlayer {
|
|
|
1866
2111
|
const data = sample.data;
|
|
1867
2112
|
const hasStartCode = data.length >= 4 && data[0] === 0 && data[1] === 0 && (data[2] === 1 || data[2] === 0 && data[3] === 1);
|
|
1868
2113
|
if (hasStartCode) {
|
|
1869
|
-
|
|
1870
|
-
|
|
2114
|
+
const nalType2 = this._isHevc ? sample.isSync ? HEVC_NAL_TYPE.IDR_W_RADL : 1 : sample.isSync ? 5 : 1;
|
|
2115
|
+
return decoder.decode({
|
|
2116
|
+
type: nalType2,
|
|
1871
2117
|
data,
|
|
1872
2118
|
size: data.length
|
|
1873
2119
|
});
|
|
@@ -1906,8 +2152,9 @@ class HLSPlayer extends BasePlayer {
|
|
|
1906
2152
|
writeOffset += nalLength;
|
|
1907
2153
|
offset += 4 + nalLength;
|
|
1908
2154
|
}
|
|
1909
|
-
const
|
|
1910
|
-
|
|
2155
|
+
const nalType = this._isHevc ? sample.isSync ? HEVC_NAL_TYPE.IDR_W_RADL : 1 : sample.isSync ? 5 : 1;
|
|
2156
|
+
const frame = decoder.decode({
|
|
2157
|
+
type: nalType,
|
|
1911
2158
|
data: annexBData,
|
|
1912
2159
|
size: annexBData.length
|
|
1913
2160
|
});
|
|
@@ -2388,89 +2635,6 @@ class FLVDemuxer {
|
|
|
2388
2635
|
return tag.frameType === FRAME_TYPE_KEYFRAME;
|
|
2389
2636
|
}
|
|
2390
2637
|
}
|
|
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
2638
|
const CODEC_ID_AVC = 7;
|
|
2475
2639
|
const CODEC_ID_HEVC = 12;
|
|
2476
2640
|
const FLV_PREFETCHER_CONFIG = {
|