@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/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 === "mdia") {
464
- this.parseMdia(data, offset, boxSize);
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
- parseMdia(data, mdiaOffset, mdiaSize) {
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 === "minf") {
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.updateStatus({ prefetchQueueSize: this.prefetchQueue.length });
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.renderLoop = () => {
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.initPrefetcher();
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 moofOffset = -1;
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
- const boxSize = this.readBoxSize(data, offset);
1611
- const boxType = this.readBoxType(data, offset + 4);
1612
- if (boxType === "moof") {
1613
- moofOffset = offset;
1614
- } else if (boxType === "mdat") {
1615
- mdatOffset = offset;
1616
- mdatSize = boxSize;
1617
- break;
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
- offset += boxSize;
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
- sampleCount = samples.length;
1824
+ totalSampleCount += samples.length;
1630
1825
  }
1631
- return sampleCount;
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:", initInfo);
1676
- if (initInfo?.avcC) {
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] Init segment loaded, timescale:", initInfo.timescale);
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 or no avcC found!");
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
- initSegment = { uri: uriMatch[1] };
2056
+ currentInitSegment = { uri: uriMatch[1] };
1768
2057
  const byteRangeMatch = mapInfo.match(/BYTERANGE="(\d+)@(\d+)"/);
1769
2058
  if (byteRangeMatch) {
1770
- initSegment.byteRange = {
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({ uri, duration, byteRange });
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
- if (!this.decoder) {
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
- return this.decoder.decode({
1852
- type: sample.isSync ? 5 : 1,
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 frame = this.decoder.decode({
1892
- type: sample.isSync ? 5 : 1,
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.firstFrameDts < 0) {
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, stopping download");
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
- if (tag.timestamp > this._lastQueuedTimestamp) {
2796
- let annexBData = this.flvDemuxer.videoTagToAnnexB(tag, lengthSize);
2797
- if (!annexBData) {
2798
- for (const trySize of [4, 2, 1]) {
2799
- if (trySize !== lengthSize) {
2800
- annexBData = this.flvDemuxer.videoTagToAnnexB(tag, trySize);
2801
- if (annexBData) {
2802
- lengthSize = trySize;
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
- if (annexBData) {
2809
- this._videoTagQueue.push({ tag, annexBData });
2810
- this._lastQueuedTimestamp = tag.timestamp;
2811
- newCount++;
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,