@give-tech/ec-player 0.0.1-beta.3 → 0.0.1-beta.31

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
@@ -118,9 +118,11 @@ class H264Decoder {
118
118
  }
119
119
  const DEFAULT_PLAYER_CONFIG = {
120
120
  wasmPath: "/wasm/decoder-simd.js",
121
- targetBufferSize: 60,
121
+ targetBufferSize: 90,
122
+ // 90 帧(约 3.6 秒 @25fps),适应高分辨率视频
122
123
  decodeBatchSize: 2,
123
- maxQueueSize: 200,
124
+ maxQueueSize: 300,
125
+ // 增加 sample 队列上限
124
126
  isLive: false
125
127
  };
126
128
  class BasePlayer {
@@ -415,6 +417,9 @@ class fMP4Demuxer {
415
417
  this.timescale = 1e3;
416
418
  this.trackId = 1;
417
419
  this.avcC = null;
420
+ this.hvcC = null;
421
+ this.isHevc = false;
422
+ this.trackHandlers = /* @__PURE__ */ new Map();
418
423
  }
419
424
  /**
420
425
  * 解析初始化段 (ftyp + moov)
@@ -433,7 +438,9 @@ class fMP4Demuxer {
433
438
  return {
434
439
  trackId: this.trackId,
435
440
  timescale: this.timescale,
436
- avcC: this.avcC || void 0
441
+ avcC: this.avcC || void 0,
442
+ hvcC: this.hvcC || void 0,
443
+ isHevc: this.isHevc
437
444
  };
438
445
  }
439
446
  /**
@@ -458,33 +465,56 @@ class fMP4Demuxer {
458
465
  parseTrak(data, trakOffset, trakSize) {
459
466
  let offset = trakOffset + 8;
460
467
  const endOffset = trakOffset + trakSize;
468
+ let currentTrackId = null;
469
+ let handlerType = null;
470
+ console.log("[fMP4Demuxer] parseTrak called, size:", trakSize);
461
471
  while (offset < endOffset - 8) {
462
472
  const boxSize = readUint32$1(data, offset);
463
473
  const boxType = readFourCC(data, offset + 4);
464
474
  if (boxSize < 8) break;
465
- if (boxType === "mdia") {
466
- this.parseMdia(data, offset, boxSize);
475
+ if (boxType === "tkhd") {
476
+ const boxDataStart = offset + 8;
477
+ const version = data[boxDataStart];
478
+ if (version === 1) {
479
+ currentTrackId = readUint32$1(data, boxDataStart + 1 + 3 + 8 + 8);
480
+ } else {
481
+ currentTrackId = readUint32$1(data, boxDataStart + 1 + 3 + 4 + 4);
482
+ }
483
+ console.log("[fMP4Demuxer] tkhd: version=", version, "trackId=", currentTrackId);
484
+ } else if (boxType === "mdia") {
485
+ handlerType = this.parseMdiaAndGetHandlerType(data, offset, boxSize);
486
+ if (handlerType && currentTrackId !== null) {
487
+ this.trackHandlers.set(currentTrackId, handlerType);
488
+ console.log("[fMP4Demuxer] Track", currentTrackId, "handler:", handlerType);
489
+ if (handlerType === "vide") {
490
+ this.trackId = currentTrackId;
491
+ }
492
+ }
467
493
  }
468
494
  offset += boxSize;
469
495
  }
470
496
  }
471
497
  /**
472
- * 解析 mdia box
498
+ * 解析 mdia box 并返回 handler type
473
499
  */
474
- parseMdia(data, mdiaOffset, mdiaSize) {
500
+ parseMdiaAndGetHandlerType(data, mdiaOffset, mdiaSize) {
475
501
  let offset = mdiaOffset + 8;
476
502
  const endOffset = mdiaOffset + mdiaSize;
503
+ let handlerType = null;
477
504
  while (offset < endOffset - 8) {
478
505
  const boxSize = readUint32$1(data, offset);
479
506
  const boxType = readFourCC(data, offset + 4);
480
507
  if (boxSize < 8) break;
481
508
  if (boxType === "mdhd") {
482
509
  this.parseMdhd(data, offset + 8);
483
- } else if (boxType === "minf") {
510
+ } else if (boxType === "hdlr") {
511
+ handlerType = readFourCC(data, offset + 8 + 8);
512
+ } else if (boxType === "minf" && handlerType === "vide") {
484
513
  this.parseMinf(data, offset, boxSize);
485
514
  }
486
515
  offset += boxSize;
487
516
  }
517
+ return handlerType;
488
518
  }
489
519
  /**
490
520
  * 解析 mdhd box 获取 timescale
@@ -530,7 +560,7 @@ class fMP4Demuxer {
530
560
  }
531
561
  }
532
562
  /**
533
- * 解析 stsd box 获取 avcC
563
+ * 解析 stsd box 获取 avcC 或 hvcC
534
564
  */
535
565
  parseStsd(data, stsdOffset, stsdSize) {
536
566
  const boxDataOffset = stsdOffset + 8;
@@ -544,6 +574,10 @@ class fMP4Demuxer {
544
574
  this.parseAvcSampleEntry(data, offset, entrySize);
545
575
  return;
546
576
  }
577
+ if (entryType === "hvc1" || entryType === "hev1") {
578
+ this.parseHevcSampleEntry(data, offset, entrySize);
579
+ return;
580
+ }
547
581
  offset += entrySize;
548
582
  }
549
583
  }
@@ -564,6 +598,25 @@ class fMP4Demuxer {
564
598
  offset += boxSize;
565
599
  }
566
600
  }
601
+ /**
602
+ * 解析 HEVC Sample Entry
603
+ */
604
+ parseHevcSampleEntry(data, entryOffset, entrySize) {
605
+ this.isHevc = true;
606
+ let offset = entryOffset + 8 + 78;
607
+ const endOffset = entryOffset + entrySize;
608
+ while (offset < endOffset - 8) {
609
+ const boxSize = readUint32$1(data, offset);
610
+ const boxType = readFourCC(data, offset + 4);
611
+ if (boxSize < 8) break;
612
+ if (boxType === "hvcC") {
613
+ this.hvcC = data.slice(offset, offset + boxSize);
614
+ console.log("[fMP4Demuxer] Found hvcC, size:", boxSize);
615
+ return;
616
+ }
617
+ offset += boxSize;
618
+ }
619
+ }
567
620
  /**
568
621
  * 解析 moof box
569
622
  */
@@ -714,21 +767,51 @@ class fMP4Demuxer {
714
767
  }
715
768
  /**
716
769
  * 从 moof + mdat 提取视频样本
770
+ * 只提取视频轨道(trackId 匹配)的数据
717
771
  */
718
- extractSamples(moof, mdatData, mdatOffset) {
772
+ extractSamples(moof, mdatData, moofOffset, mdatOffset) {
719
773
  const samples = [];
774
+ console.log(`[fMP4Demuxer] extractSamples: moof.trafs=${moof.trafs.length}, trackHandlers=${JSON.stringify(Object.fromEntries(this.trackHandlers))}`);
720
775
  for (const traf of moof.trafs) {
721
- if (!traf.tfhd || !traf.tfdt) continue;
776
+ if (!traf.tfhd || !traf.tfdt) {
777
+ console.log(`[fMP4Demuxer] traf missing tfhd or tfdt`);
778
+ continue;
779
+ }
780
+ const trackId = traf.tfhd.trackId;
781
+ const handlerType = this.trackHandlers.get(trackId);
782
+ if (handlerType && handlerType !== "vide") {
783
+ console.log(`[fMP4Demuxer] Skipping non-video track: trackId=${trackId}, handler=${handlerType}`);
784
+ continue;
785
+ }
786
+ if (!handlerType) {
787
+ console.log(`[fMP4Demuxer] Unknown trackId=${trackId}, will filter by sample size`);
788
+ } else {
789
+ console.log(`[fMP4Demuxer] Processing video track: trackId=${trackId}, truns=${traf.truns.length}`);
790
+ }
722
791
  let baseDts = traf.tfdt.baseMediaDecodeTime;
792
+ const baseDataOffset = traf.tfhd.baseDataOffset ?? moofOffset;
723
793
  for (const trun of traf.truns) {
724
794
  let dataOffset = 0;
725
795
  if (trun.dataOffset !== void 0) {
726
- dataOffset = trun.dataOffset - mdatOffset - 8;
796
+ const absoluteOffset = baseDataOffset + trun.dataOffset;
797
+ dataOffset = absoluteOffset - mdatOffset - 8;
727
798
  }
728
799
  for (const sample of trun.samples) {
729
- if (sample.size === void 0 || sample.size <= 0) continue;
730
- const sampleData = mdatData.slice(dataOffset, dataOffset + sample.size);
731
- const isSync = (sample.flags ?? 0) & 16777216;
800
+ let sampleSize = sample.size;
801
+ if (sampleSize === void 0 || sampleSize <= 0) {
802
+ sampleSize = mdatData.length - dataOffset;
803
+ }
804
+ if (sampleSize <= 0) {
805
+ console.log(`[fMP4Demuxer] Skipping sample: no data available`);
806
+ continue;
807
+ }
808
+ if (!handlerType && sampleSize < 1e3) {
809
+ dataOffset += sampleSize;
810
+ baseDts += sample.duration ?? 0;
811
+ continue;
812
+ }
813
+ const sampleData = mdatData.slice(dataOffset, dataOffset + sampleSize);
814
+ const isSync = !((sample.flags ?? 0) & 16777216);
732
815
  const cto = sample.compositionTimeOffset ?? 0;
733
816
  const duration = sample.duration ?? 0;
734
817
  samples.push({
@@ -736,9 +819,9 @@ class fMP4Demuxer {
736
819
  dts: baseDts,
737
820
  pts: baseDts + cto,
738
821
  duration,
739
- isSync: isSync !== 0
822
+ isSync
740
823
  });
741
- dataOffset += sample.size;
824
+ dataOffset += sampleSize;
742
825
  baseDts += duration;
743
826
  }
744
827
  }
@@ -757,6 +840,18 @@ class fMP4Demuxer {
757
840
  getAvcC() {
758
841
  return this.avcC;
759
842
  }
843
+ /**
844
+ * 获取 hvcC 配置
845
+ */
846
+ getHvcC() {
847
+ return this.hvcC;
848
+ }
849
+ /**
850
+ * 是否为 HEVC 编码
851
+ */
852
+ isHevcStream() {
853
+ return this.isHevc;
854
+ }
760
855
  }
761
856
  class NALParser {
762
857
  /**
@@ -804,6 +899,93 @@ class NALParser {
804
899
  return units;
805
900
  }
806
901
  }
902
+ const HEVC_NAL_TYPE = {
903
+ // Picture Parameter Set
904
+ IDR_W_RADL: 19
905
+ };
906
+ class HEVCDecoder {
907
+ constructor(wasmLoader) {
908
+ this.wasmModule = null;
909
+ this.decoderContext = null;
910
+ this.wasmModule = wasmLoader.getModule();
911
+ }
912
+ async init() {
913
+ if (!this.wasmModule) {
914
+ throw new Error("WASM module not loaded");
915
+ }
916
+ this.decoderContext = this.wasmModule._create_decoder(173);
917
+ if (this.decoderContext === 0 || this.decoderContext === null) {
918
+ throw new Error("Failed to create HEVC decoder context");
919
+ }
920
+ console.log("[HEVCDecoder] Initialized with codec_id=173");
921
+ }
922
+ decode(nalUnit) {
923
+ if (this.decoderContext === null || !this.wasmModule) {
924
+ return null;
925
+ }
926
+ const dataPtr = this.wasmModule._malloc(nalUnit.size);
927
+ this.wasmModule.HEAPU8.set(nalUnit.data, dataPtr);
928
+ const result = this.wasmModule._decode_video(
929
+ this.decoderContext,
930
+ dataPtr,
931
+ nalUnit.size
932
+ );
933
+ this.wasmModule._free(dataPtr);
934
+ if (result === 1) {
935
+ const width = this.wasmModule._get_frame_width(this.decoderContext);
936
+ const height = this.wasmModule._get_frame_height(this.decoderContext);
937
+ if (width <= 0 || height <= 0) {
938
+ return null;
939
+ }
940
+ const yPtr = this.wasmModule._get_frame_data(this.decoderContext, 0);
941
+ const uPtr = this.wasmModule._get_frame_data(this.decoderContext, 1);
942
+ const vPtr = this.wasmModule._get_frame_data(this.decoderContext, 2);
943
+ const yLineSize = this.wasmModule._get_frame_linesize(this.decoderContext, 0);
944
+ const uLineSize = this.wasmModule._get_frame_linesize(this.decoderContext, 1);
945
+ const vLineSize = this.wasmModule._get_frame_linesize(this.decoderContext, 2);
946
+ const frameData = this.yuv420pToRgba(
947
+ yPtr,
948
+ uPtr,
949
+ vPtr,
950
+ width,
951
+ height,
952
+ yLineSize,
953
+ uLineSize,
954
+ vLineSize
955
+ );
956
+ return { width, height, data: frameData };
957
+ }
958
+ return null;
959
+ }
960
+ yuv420pToRgba(yPtr, uPtr, vPtr, width, height, yLineSize, uLineSize, vLineSize) {
961
+ const rgba = new Uint8Array(width * height * 4);
962
+ for (let y = 0; y < height; y++) {
963
+ for (let x = 0; x < width; x++) {
964
+ const yIndex = y * yLineSize + x;
965
+ const uIndex = (y >> 1) * uLineSize + (x >> 1);
966
+ const vIndex = (y >> 1) * vLineSize + (x >> 1);
967
+ const yValue = this.wasmModule.HEAPU8[yPtr + yIndex];
968
+ const uValue = this.wasmModule.HEAPU8[uPtr + uIndex];
969
+ const vValue = this.wasmModule.HEAPU8[vPtr + vIndex];
970
+ const c = yValue - 16;
971
+ const d = uValue - 128;
972
+ const e = vValue - 128;
973
+ let r = 298 * c + 409 * e + 128 >> 8;
974
+ let g = 298 * c - 100 * d - 208 * e + 128 >> 8;
975
+ let b = 298 * c + 516 * d + 128 >> 8;
976
+ r = r < 0 ? 0 : r > 255 ? 255 : r;
977
+ g = g < 0 ? 0 : g > 255 ? 255 : g;
978
+ b = b < 0 ? 0 : b > 255 ? 255 : b;
979
+ const rgbaIndex = (y * width + x) * 4;
980
+ rgba[rgbaIndex] = r;
981
+ rgba[rgbaIndex + 1] = g;
982
+ rgba[rgbaIndex + 2] = b;
983
+ rgba[rgbaIndex + 3] = 255;
984
+ }
985
+ }
986
+ return rgba;
987
+ }
988
+ }
807
989
  class BasePrefetcher {
808
990
  constructor(config, callbacks = {}) {
809
991
  this.isRunning = false;
@@ -1077,11 +1259,16 @@ class SegmentPrefetcher extends BasePrefetcher {
1077
1259
  /**
1078
1260
  * 处理预取队列中的数据
1079
1261
  *
1262
+ * @param maxSegments 最多处理的分片数量(默认处理所有)
1080
1263
  * @returns 是否有数据被处理
1081
1264
  */
1082
- processQueue() {
1265
+ processQueue(maxSegments) {
1083
1266
  let processed = false;
1267
+ let processedCount = 0;
1084
1268
  while (this.prefetchQueue.length > 0) {
1269
+ if (maxSegments !== void 0 && processedCount >= maxSegments) {
1270
+ break;
1271
+ }
1085
1272
  const item = this.prefetchQueue[0];
1086
1273
  if (item.segmentIndex !== this.currentSegmentIndex) {
1087
1274
  break;
@@ -1097,6 +1284,7 @@ class SegmentPrefetcher extends BasePrefetcher {
1097
1284
  callbacks.onSegmentParsed?.(item.segmentIndex, itemCount);
1098
1285
  console.log(`[SegmentPrefetcher] Parsed segment #${item.segmentIndex}: ${itemCount} items, ${parseTime.toFixed(0)}ms`);
1099
1286
  processed = true;
1287
+ processedCount++;
1100
1288
  }
1101
1289
  return processed;
1102
1290
  }
@@ -1324,7 +1512,8 @@ const HLS_PREFETCHER_CONFIG = {
1324
1512
  ...DEFAULT_SEGMENT_PREFETCHER_CONFIG,
1325
1513
  lowWaterMark: 30,
1326
1514
  highWaterMark: 100,
1327
- prefetchAhead: 2,
1515
+ prefetchAhead: 6,
1516
+ // 预载 6 个分片(约 30 秒),适应高分辨率视频
1328
1517
  prefetchInterval: 10,
1329
1518
  dynamicInterval: true
1330
1519
  };
@@ -1334,12 +1523,18 @@ const DEFAULT_HLS_CONFIG = {
1334
1523
  class HLSSegmentPrefetcher extends SegmentPrefetcher {
1335
1524
  constructor(config, callbacks, player) {
1336
1525
  super(config, callbacks);
1526
+ this.currentInitSegmentUri = null;
1337
1527
  this.player = player;
1338
1528
  }
1339
1529
  /**
1340
1530
  * 获取分片数据
1341
1531
  */
1342
1532
  async fetchSegment(segment, index) {
1533
+ if (segment.initSegmentUri && segment.initSegmentUri !== this.currentInitSegmentUri) {
1534
+ console.log("[HLSSegmentPrefetcher] Init segment changed:", segment.initSegmentUri);
1535
+ await this.player.loadNewInitSegment(segment.initSegmentUri);
1536
+ this.currentInitSegmentUri = segment.initSegmentUri;
1537
+ }
1343
1538
  const baseUrl = this.baseUrl;
1344
1539
  const url = segment.uri.startsWith("http") ? segment.uri : baseUrl + segment.uri;
1345
1540
  const headers = {};
@@ -1378,19 +1573,29 @@ class HLSPlayer extends BasePlayer {
1378
1573
  this.pesExtractor = new PESExtractor();
1379
1574
  this.nalParser = new NALParser();
1380
1575
  this.fmp4Demuxer = new fMP4Demuxer();
1576
+ this.hevcDecoder = null;
1381
1577
  this.isPrefetching = false;
1382
1578
  this.currentSegmentIndex = 0;
1383
1579
  this.segments = [];
1384
1580
  this.fmp4Segments = [];
1385
1581
  this.initSegment = null;
1386
1582
  this._isFMP4 = false;
1583
+ this._isHevc = false;
1387
1584
  this.currentPlaylistUrl = "";
1388
1585
  this._nalQueue = [];
1389
1586
  this._sampleQueue = [];
1390
1587
  this.prefetcher = null;
1391
1588
  this.lastRenderTime = 0;
1589
+ this.playStartTime = 0;
1590
+ this.accumulatedMediaTime = 0;
1591
+ this._lastLogTime = 0;
1392
1592
  this.renderLoop = (timestamp = 0) => {
1393
1593
  if (!this.isPlaying) return;
1594
+ const now = performance.now();
1595
+ if (!this._lastLogTime || now - this._lastLogTime > 1e3) {
1596
+ this._lastLogTime = now;
1597
+ console.log("[renderLoop] frameBuffer=", this.frameBuffer.length, "renderer=", !!this.renderer);
1598
+ }
1394
1599
  const downloaded = this.isFMP4 ? this.sampleQueue.length : this.nalQueue.length;
1395
1600
  const segments = this.isFMP4 ? this.fmp4Segments : this.segments;
1396
1601
  const totalSegments = segments.length;
@@ -1402,19 +1607,35 @@ class HLSPlayer extends BasePlayer {
1402
1607
  totalSegments
1403
1608
  });
1404
1609
  if (this.frameBuffer.length > 0 && this.renderer) {
1405
- const frame = this.frameBuffer.shift();
1406
- this.renderer.render(frame);
1407
- if (this.lastRenderTime > 0) {
1408
- const elapsed = timestamp - this.lastRenderTime;
1409
- this.setCurrentTime(this._currentTime + elapsed);
1610
+ const frame = this.frameBuffer[0];
1611
+ const timescale = this.fmp4Demuxer.getTimescale();
1612
+ if (this.playStartTime === 0) {
1613
+ this.playStartTime = now;
1614
+ this.accumulatedMediaTime = 0;
1615
+ console.log("[renderLoop] Init: timescale=", timescale);
1616
+ }
1617
+ const elapsedWallTime = now - this.playStartTime;
1618
+ const frameDurationMs = frame.duration ? frame.duration * 1e3 / timescale : 33.33;
1619
+ if (Math.floor(elapsedWallTime / 1e3) !== Math.floor((elapsedWallTime - 20) / 1e3)) {
1620
+ console.log("[renderLoop] elapsed=", Math.floor(elapsedWallTime), "accumulated=", Math.floor(this.accumulatedMediaTime), "frameDuration=", frame.duration, "frameDurationMs=", frameDurationMs.toFixed(2), "frameBuffer=", this.frameBuffer.length);
1621
+ }
1622
+ const bufferMs = 50;
1623
+ if (elapsedWallTime >= this.accumulatedMediaTime - bufferMs) {
1624
+ this.frameBuffer.shift();
1625
+ this.renderer.render(frame);
1626
+ this.accumulatedMediaTime += frameDurationMs;
1627
+ this.setCurrentTime(this.accumulatedMediaTime / 1e3);
1628
+ this.updateState({ resolution: `${frame.width}x${frame.height}` });
1629
+ const fps = this.renderer.updateFps();
1630
+ this.updateState({ fps });
1631
+ this.callbacks.onFrameRender?.(frame);
1410
1632
  }
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
1633
  } else {
1417
- this.lastRenderTime = 0;
1634
+ if (this.playStartTime !== 0) {
1635
+ const oldPlayStartTime = this.playStartTime;
1636
+ this.playStartTime = now - this.accumulatedMediaTime;
1637
+ console.log("[renderLoop] No frames, adjusting playStartTime from", oldPlayStartTime, "to", this.playStartTime, "accumulated=", this.accumulatedMediaTime);
1638
+ }
1418
1639
  }
1419
1640
  this.frameTimer = requestAnimationFrame(this.renderLoop);
1420
1641
  };
@@ -1423,6 +1644,9 @@ class HLSPlayer extends BasePlayer {
1423
1644
  get isFMP4() {
1424
1645
  return this._isFMP4;
1425
1646
  }
1647
+ get isHevc() {
1648
+ return this._isHevc;
1649
+ }
1426
1650
  get nalQueue() {
1427
1651
  return this._nalQueue;
1428
1652
  }
@@ -1489,6 +1713,8 @@ class HLSPlayer extends BasePlayer {
1489
1713
  this.isPlaying = true;
1490
1714
  this.decodeLoopAbort = false;
1491
1715
  this.lastRenderTime = 0;
1716
+ this.playStartTime = 0;
1717
+ this.accumulatedMediaTime = 0;
1492
1718
  this.updateState({ isPlaying: true });
1493
1719
  if (!this.prefetcher) {
1494
1720
  this.initPrefetcher();
@@ -1531,10 +1757,22 @@ class HLSPlayer extends BasePlayer {
1531
1757
  console.log("[DecodeLoop] START, isFMP4:", this.isFMP4);
1532
1758
  let batchCount = 0;
1533
1759
  while (this.isPlaying && !this.decodeLoopAbort) {
1534
- this.prefetcher?.processQueue();
1760
+ const sampleQueueSize = this.sampleQueue.length;
1761
+ const maxSamplesBeforePause = this.config.maxQueueSize * 3;
1762
+ if (sampleQueueSize < maxSamplesBeforePause && this.prefetcher) {
1763
+ this.prefetcher.processQueue(1);
1764
+ }
1765
+ if (!this.config.isLive && this.frameBuffer.length >= this.config.targetBufferSize) {
1766
+ await this.sleep(10);
1767
+ continue;
1768
+ }
1535
1769
  const batchSize = this.config.decodeBatchSize;
1536
1770
  let decodedInBatch = 0;
1537
1771
  if (this.isFMP4) {
1772
+ const queueSize = this.sampleQueue.length;
1773
+ if (queueSize > 0 || batchCount % 50 === 0) {
1774
+ console.log("[DecodeLoop] fMP4: sampleQueue=", queueSize, "frameBuffer=", this.frameBuffer.length, "batch=", batchCount);
1775
+ }
1538
1776
  while (this.sampleQueue.length > 0 && decodedInBatch < batchSize) {
1539
1777
  const queuedSample = this.sampleQueue.shift();
1540
1778
  const sample = queuedSample.sample;
@@ -1542,9 +1780,11 @@ class HLSPlayer extends BasePlayer {
1542
1780
  if (frame) {
1543
1781
  this.frameBuffer.push(frame);
1544
1782
  decodedInBatch++;
1545
- while (this.frameBuffer.length > this.config.targetBufferSize) {
1546
- this.frameBuffer.shift();
1547
- this.droppedFrames++;
1783
+ if (this.config.isLive) {
1784
+ while (this.frameBuffer.length > this.config.targetBufferSize) {
1785
+ this.frameBuffer.shift();
1786
+ this.droppedFrames++;
1787
+ }
1548
1788
  }
1549
1789
  }
1550
1790
  }
@@ -1560,9 +1800,11 @@ class HLSPlayer extends BasePlayer {
1560
1800
  if (frame) {
1561
1801
  this.frameBuffer.push(frame);
1562
1802
  decodedInBatch++;
1563
- while (this.frameBuffer.length > this.config.targetBufferSize) {
1564
- this.frameBuffer.shift();
1565
- this.droppedFrames++;
1803
+ if (this.config.isLive) {
1804
+ while (this.frameBuffer.length > this.config.targetBufferSize) {
1805
+ this.frameBuffer.shift();
1806
+ this.droppedFrames++;
1807
+ }
1566
1808
  }
1567
1809
  }
1568
1810
  }
@@ -1604,7 +1846,9 @@ class HLSPlayer extends BasePlayer {
1604
1846
  const segmentInfos = this.fmp4Segments.map((seg) => ({
1605
1847
  uri: seg.uri,
1606
1848
  duration: seg.duration,
1607
- byteRange: seg.byteRange
1849
+ byteRange: seg.byteRange,
1850
+ initSegmentUri: seg.initSegmentUri
1851
+ // 传递初始化段 URI
1608
1852
  }));
1609
1853
  this.prefetcher.setSegments(segmentInfos, baseUrl);
1610
1854
  } else {
@@ -1620,33 +1864,48 @@ class HLSPlayer extends BasePlayer {
1620
1864
  * 解析 fMP4 数据
1621
1865
  */
1622
1866
  parseFMP4Data(data) {
1623
- let moofOffset = -1;
1624
- let mdatOffset = -1;
1625
- let mdatSize = 0;
1867
+ let totalSampleCount = 0;
1626
1868
  let offset = 0;
1627
1869
  while (offset < data.length - 8) {
1628
- const boxSize = this.readBoxSize(data, offset);
1629
- const boxType = this.readBoxType(data, offset + 4);
1630
- if (boxType === "moof") {
1631
- moofOffset = offset;
1632
- } else if (boxType === "mdat") {
1633
- mdatOffset = offset;
1634
- mdatSize = boxSize;
1635
- break;
1870
+ let moofOffset = -1;
1871
+ let mdatOffset = -1;
1872
+ let mdatSize = 0;
1873
+ while (offset < data.length - 8) {
1874
+ const boxSize = this.readBoxSize(data, offset);
1875
+ const boxType = this.readBoxType(data, offset + 4);
1876
+ if (boxSize < 8) break;
1877
+ if (boxType === "moof") {
1878
+ moofOffset = offset;
1879
+ offset += boxSize;
1880
+ break;
1881
+ }
1882
+ offset += boxSize;
1636
1883
  }
1637
- offset += boxSize;
1638
- }
1639
- let sampleCount = 0;
1640
- if (moofOffset >= 0 && mdatOffset >= 0) {
1884
+ if (moofOffset < 0) break;
1885
+ while (offset < data.length - 8) {
1886
+ const boxSize = this.readBoxSize(data, offset);
1887
+ const boxType = this.readBoxType(data, offset + 4);
1888
+ if (boxSize < 8) break;
1889
+ if (boxType === "mdat") {
1890
+ mdatOffset = offset;
1891
+ mdatSize = boxSize;
1892
+ offset += boxSize;
1893
+ break;
1894
+ }
1895
+ offset += boxSize;
1896
+ }
1897
+ if (mdatOffset < 0) break;
1641
1898
  const moof = this.fmp4Demuxer.parseMoof(data, moofOffset);
1642
1899
  const mdatData = data.slice(mdatOffset + 8, mdatOffset + mdatSize);
1643
- const samples = this.fmp4Demuxer.extractSamples(moof, mdatData, mdatOffset);
1900
+ const samples = this.fmp4Demuxer.extractSamples(moof, mdatData, moofOffset, mdatOffset);
1644
1901
  for (const sample of samples) {
1645
1902
  this._sampleQueue.push({ sample });
1646
1903
  }
1647
- sampleCount = samples.length;
1904
+ totalSampleCount += samples.length;
1905
+ console.log("[parseFMP4Data] Pushed", samples.length, "samples, totalQueue=", this._sampleQueue.length);
1648
1906
  }
1649
- return sampleCount;
1907
+ console.log("[parseFMP4Data] Total samples parsed:", totalSampleCount);
1908
+ return totalSampleCount;
1650
1909
  }
1651
1910
  /**
1652
1911
  * 解析 TS 数据
@@ -1690,16 +1949,74 @@ class HLSPlayer extends BasePlayer {
1690
1949
  const data = new Uint8Array(await response.arrayBuffer());
1691
1950
  console.log("[fMP4] Init segment data size:", data.length, "bytes");
1692
1951
  const initInfo = this.fmp4Demuxer.parseInitSegment(data);
1693
- console.log("[fMP4] Parse result:", initInfo);
1694
- if (initInfo?.avcC) {
1952
+ console.log("[fMP4] Parse result:", {
1953
+ isHevc: initInfo?.isHevc,
1954
+ hasAvcC: !!initInfo?.avcC,
1955
+ hasHvcC: !!initInfo?.hvcC,
1956
+ timescale: initInfo?.timescale
1957
+ });
1958
+ this._isHevc = initInfo?.isHevc ?? false;
1959
+ if (initInfo?.hvcC && this._isHevc) {
1960
+ console.log("[fMP4] hvcC size:", initInfo.hvcC.length);
1961
+ await this.initHevcDecoder();
1962
+ this.initDecoderFromHvcC(initInfo.hvcC);
1963
+ console.log("[fMP4] HEVC decoder initialized, timescale:", initInfo.timescale);
1964
+ } else if (initInfo?.avcC) {
1695
1965
  console.log("[fMP4] avcC size:", initInfo.avcC.length);
1696
1966
  this.initDecoderFromAvcC(initInfo.avcC);
1697
- console.log("[fMP4] Init segment loaded, timescale:", initInfo.timescale);
1967
+ console.log("[fMP4] AVC decoder initialized, timescale:", initInfo.timescale);
1968
+ } else {
1969
+ console.error("[fMP4] Failed to parse init segment or no avcC/hvcC found!");
1970
+ throw new Error("Failed to parse fMP4 init segment: no valid codec config found");
1971
+ }
1972
+ }
1973
+ /**
1974
+ * 加载新的初始化段(用于不连续性流)
1975
+ */
1976
+ async loadNewInitSegment(uri) {
1977
+ console.log("[fMP4] Loading new init segment for discontinuity:", uri);
1978
+ this._sampleQueue.length = 0;
1979
+ this._nalQueue.length = 0;
1980
+ const url = uri.startsWith("http") ? uri : this.currentPlaylistUrl.substring(0, this.currentPlaylistUrl.lastIndexOf("/") + 1) + uri;
1981
+ const response = await fetch(url);
1982
+ const data = new Uint8Array(await response.arrayBuffer());
1983
+ console.log("[fMP4] New init segment data size:", data.length, "bytes");
1984
+ const initInfo = this.fmp4Demuxer.parseInitSegment(data);
1985
+ console.log("[fMP4] New init segment parse result:", {
1986
+ isHevc: initInfo?.isHevc,
1987
+ hasAvcC: !!initInfo?.avcC,
1988
+ hasHvcC: !!initInfo?.hvcC
1989
+ });
1990
+ const newIsHevc = initInfo?.isHevc ?? false;
1991
+ if (newIsHevc !== this._isHevc) {
1992
+ console.log("[fMP4] Codec type changed from", this._isHevc ? "HEVC" : "AVC", "to", newIsHevc ? "HEVC" : "AVC");
1993
+ this._isHevc = newIsHevc;
1994
+ this.decoderInitialized = false;
1995
+ if (newIsHevc) {
1996
+ await this.initHevcDecoder();
1997
+ }
1998
+ } else {
1999
+ this.decoderInitialized = false;
2000
+ }
2001
+ if (initInfo?.hvcC && this._isHevc) {
2002
+ this.initDecoderFromHvcC(initInfo.hvcC);
2003
+ console.log("[fMP4] HEVC decoder re-initialized for discontinuity");
2004
+ } else if (initInfo?.avcC) {
2005
+ this.initDecoderFromAvcC(initInfo.avcC);
2006
+ console.log("[fMP4] AVC decoder re-initialized for discontinuity");
1698
2007
  } else {
1699
- console.error("[fMP4] Failed to parse init segment or no avcC found!");
1700
- throw new Error("Failed to parse fMP4 init segment");
2008
+ console.error("[fMP4] Failed to parse new init segment!");
1701
2009
  }
1702
2010
  }
2011
+ /**
2012
+ * 初始化 HEVC 解码器
2013
+ */
2014
+ async initHevcDecoder() {
2015
+ if (this.hevcDecoder) return;
2016
+ this.hevcDecoder = new HEVCDecoder(this.wasmLoader);
2017
+ await this.hevcDecoder.init();
2018
+ console.log("[fMP4] HEVC decoder created");
2019
+ }
1703
2020
  /**
1704
2021
  * 从 avcC 初始化解码器
1705
2022
  */
@@ -1744,6 +2061,43 @@ class HLSPlayer extends BasePlayer {
1744
2061
  this.decoderInitialized = true;
1745
2062
  console.log("[fMP4] Decoder initialized from avcC");
1746
2063
  }
2064
+ /**
2065
+ * 从 hvcC 初始化 HEVC 解码器
2066
+ */
2067
+ initDecoderFromHvcC(hvcC) {
2068
+ if (this.decoderInitialized || !this.hevcDecoder) return;
2069
+ console.log("[fMP4] hvcC length:", hvcC.length);
2070
+ console.log("[fMP4] hvcC first 8 bytes:", Array.from(hvcC.slice(0, 8)).map((b) => b.toString(16).padStart(2, "0")).join(" "));
2071
+ console.log("[fMP4] hvcC bytes 8-24:", Array.from(hvcC.slice(8, 24)).map((b) => b.toString(16).padStart(2, "0")).join(" "));
2072
+ let offset = 8 + 22;
2073
+ const numOfArrays = hvcC[offset];
2074
+ console.log("[fMP4] hvcC[30] (numOfArrays):", numOfArrays, "0x" + numOfArrays.toString(16));
2075
+ offset += 1;
2076
+ for (let i = 0; i < numOfArrays && offset < hvcC.length - 3; i++) {
2077
+ const typeCompressed = hvcC[offset];
2078
+ const arrayType = typeCompressed & 63;
2079
+ const numNalus = hvcC[offset + 1] << 8 | hvcC[offset + 2];
2080
+ offset += 3;
2081
+ console.log(`[fMP4] HEVC array: type=${arrayType}, count=${numNalus}`);
2082
+ for (let j = 0; j < numNalus && offset < hvcC.length - 2; j++) {
2083
+ const nalUnitLength = hvcC[offset] << 8 | hvcC[offset + 1];
2084
+ offset += 2;
2085
+ if (offset + nalUnitLength > hvcC.length) break;
2086
+ const nalUnitData = hvcC.slice(offset, offset + nalUnitLength);
2087
+ offset += nalUnitLength;
2088
+ const nalWithStartCode = new Uint8Array(4 + nalUnitLength);
2089
+ nalWithStartCode.set([0, 0, 0, 1], 0);
2090
+ nalWithStartCode.set(nalUnitData, 4);
2091
+ if (arrayType === 32 || arrayType === 33 || arrayType === 34) {
2092
+ this.hevcDecoder.decode({ type: arrayType, data: nalWithStartCode, size: nalWithStartCode.length });
2093
+ const typeName = arrayType === 32 ? "VPS" : arrayType === 33 ? "SPS" : "PPS";
2094
+ console.log(`[fMP4] HEVC ${typeName} decoded, length=${nalUnitLength}`);
2095
+ }
2096
+ }
2097
+ }
2098
+ this.decoderInitialized = true;
2099
+ console.log("[fMP4] HEVC decoder initialized from hvcC");
2100
+ }
1747
2101
  /**
1748
2102
  * 解析 HLS 播放列表
1749
2103
  */
@@ -1776,25 +2130,31 @@ class HLSPlayer extends BasePlayer {
1776
2130
  }
1777
2131
  const isFMP4 = lines.some((line) => line.includes("#EXT-X-MAP:"));
1778
2132
  console.log("[parsePlaylist] isFMP4:", isFMP4, "url:", url);
2133
+ let currentInitSegment;
1779
2134
  for (let i = 0; i < lines.length; i++) {
1780
2135
  const line = lines[i].trim();
1781
2136
  if (line.startsWith("#EXT-X-MAP:")) {
1782
2137
  const mapInfo = line.substring("#EXT-X-MAP:".length);
1783
2138
  const uriMatch = mapInfo.match(/URI="([^"]+)"/);
1784
2139
  if (uriMatch) {
1785
- initSegment = { uri: uriMatch[1] };
2140
+ currentInitSegment = { uri: uriMatch[1] };
1786
2141
  const byteRangeMatch = mapInfo.match(/BYTERANGE="(\d+)@(\d+)"/);
1787
2142
  if (byteRangeMatch) {
1788
- initSegment.byteRange = {
2143
+ currentInitSegment.byteRange = {
1789
2144
  start: parseInt(byteRangeMatch[2]),
1790
2145
  end: parseInt(byteRangeMatch[2]) + parseInt(byteRangeMatch[1]) - 1
1791
2146
  };
1792
2147
  }
2148
+ if (!initSegment) {
2149
+ initSegment = currentInitSegment;
2150
+ }
1793
2151
  }
2152
+ continue;
2153
+ }
2154
+ if (line === "#EXT-X-DISCONTINUITY") {
2155
+ console.log("[parsePlaylist] Found EXT-X-DISCONTINUITY");
2156
+ continue;
1794
2157
  }
1795
- }
1796
- for (let i = 0; i < lines.length; i++) {
1797
- const line = lines[i].trim();
1798
2158
  if (line.startsWith("#EXTINF:")) {
1799
2159
  const duration = parseFloat(line.split(":")[1].split(",")[0]);
1800
2160
  let byteRange;
@@ -1812,7 +2172,13 @@ class HLSPlayer extends BasePlayer {
1812
2172
  const uri = nextLine;
1813
2173
  if (uri && !uri.startsWith("#")) {
1814
2174
  if (isFMP4) {
1815
- fmp4Segments.push({ uri, duration, byteRange });
2175
+ fmp4Segments.push({
2176
+ uri,
2177
+ duration,
2178
+ byteRange,
2179
+ initSegmentUri: currentInitSegment?.uri
2180
+ // 关联当前初始化段
2181
+ });
1816
2182
  } else {
1817
2183
  segments.push({ uri, duration });
1818
2184
  }
@@ -1855,7 +2221,8 @@ class HLSPlayer extends BasePlayer {
1855
2221
  * 解码 fMP4 sample
1856
2222
  */
1857
2223
  decodeSample(sample) {
1858
- if (!this.decoder) {
2224
+ const decoder = this._isHevc ? this.hevcDecoder : this.decoder;
2225
+ if (!decoder) {
1859
2226
  console.warn("[fMP4] Decoder not available");
1860
2227
  return null;
1861
2228
  }
@@ -1864,10 +2231,18 @@ class HLSPlayer extends BasePlayer {
1864
2231
  return null;
1865
2232
  }
1866
2233
  const data = sample.data;
1867
- const hasStartCode = data.length >= 4 && data[0] === 0 && data[1] === 0 && (data[2] === 1 || data[2] === 0 && data[3] === 1);
2234
+ const looksLikeStartCode = data.length >= 4 && data[0] === 0 && data[1] === 0 && (data[2] === 1 || data[2] === 0 && data[3] === 1);
2235
+ let hasStartCode = looksLikeStartCode;
2236
+ if (looksLikeStartCode) {
2237
+ const possibleAvccLength = data[0] << 24 | data[1] << 16 | data[2] << 8 | data[3];
2238
+ if (possibleAvccLength > 0 && possibleAvccLength + 4 === data.length) {
2239
+ hasStartCode = false;
2240
+ }
2241
+ }
1868
2242
  if (hasStartCode) {
1869
- return this.decoder.decode({
1870
- type: sample.isSync ? 5 : 1,
2243
+ const nalType2 = this._isHevc ? sample.isSync ? HEVC_NAL_TYPE.IDR_W_RADL : 1 : sample.isSync ? 5 : 1;
2244
+ return decoder.decode({
2245
+ type: nalType2,
1871
2246
  data,
1872
2247
  size: data.length
1873
2248
  });
@@ -1906,11 +2281,17 @@ class HLSPlayer extends BasePlayer {
1906
2281
  writeOffset += nalLength;
1907
2282
  offset += 4 + nalLength;
1908
2283
  }
1909
- const frame = this.decoder.decode({
1910
- type: sample.isSync ? 5 : 1,
2284
+ const nalType = this._isHevc ? sample.isSync ? HEVC_NAL_TYPE.IDR_W_RADL : 1 : sample.isSync ? 5 : 1;
2285
+ const frame = decoder.decode({
2286
+ type: nalType,
1911
2287
  data: annexBData,
1912
2288
  size: annexBData.length
1913
2289
  });
2290
+ if (frame) {
2291
+ frame.pts = sample.pts;
2292
+ frame.dts = sample.dts;
2293
+ frame.duration = sample.duration;
2294
+ }
1914
2295
  return frame;
1915
2296
  }
1916
2297
  /**
@@ -2388,89 +2769,6 @@ class FLVDemuxer {
2388
2769
  return tag.frameType === FRAME_TYPE_KEYFRAME;
2389
2770
  }
2390
2771
  }
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
2772
  const CODEC_ID_AVC = 7;
2475
2773
  const CODEC_ID_HEVC = 12;
2476
2774
  const FLV_PREFETCHER_CONFIG = {
@@ -2734,8 +3032,8 @@ class FLVPlayer extends BasePlayer {
2734
3032
  * 开始播放(覆盖基类方法)
2735
3033
  */
2736
3034
  async play() {
2737
- const MIN_BUFFER_SIZE = this.dynamicMinBufferSize;
2738
- const MAX_WAIT_TIME = 1e4;
3035
+ const MIN_BUFFER_SIZE = this.config.isLive ? 3 : this.dynamicMinBufferSize;
3036
+ const MAX_WAIT_TIME = this.config.isLive ? 3e3 : 1e4;
2739
3037
  const startTime = Date.now();
2740
3038
  console.log(`[FLVPlayer] Waiting for buffer (target: ${MIN_BUFFER_SIZE} frames)...`);
2741
3039
  let aggressiveDecodeCount = 0;
@@ -2766,11 +3064,11 @@ class FLVPlayer extends BasePlayer {
2766
3064
  }
2767
3065
  console.log("[FLVPlayer] Buffer ready, frames:", this._timedFrameBuffer.length, "queue:", this._videoTagQueue.length);
2768
3066
  if (this._timedFrameBuffer.length > 0) {
2769
- if (this.config.isLive && this._timedFrameBuffer.length > 15) {
2770
- const droppedCount = this._timedFrameBuffer.length - 5;
2771
- this._timedFrameBuffer = this._timedFrameBuffer.slice(-5);
3067
+ if (this.config.isLive && this._timedFrameBuffer.length > 60) {
3068
+ const droppedCount = this._timedFrameBuffer.length - 30;
3069
+ this._timedFrameBuffer = this._timedFrameBuffer.slice(-30);
2772
3070
  this.droppedFrames += droppedCount;
2773
- console.log("[FLVPlayer] Live resume: dropped", droppedCount, "old frames, keeping latest 5");
3071
+ console.log("[FLVPlayer] Live resume: dropped", droppedCount, "old frames, keeping latest 30");
2774
3072
  }
2775
3073
  this.firstFrameDts = this._timedFrameBuffer[0].dts;
2776
3074
  this.playStartTime = performance.now();
@@ -2794,8 +3092,7 @@ class FLVPlayer extends BasePlayer {
2794
3092
  this.prefetcher.stop();
2795
3093
  }
2796
3094
  if (this.config.isLive) {
2797
- console.log("[FLVPlayer] Pausing live stream, stopping download");
2798
- this.stopLiveDownload();
3095
+ console.log("[FLVPlayer] Pausing live stream, keeping download running");
2799
3096
  }
2800
3097
  }
2801
3098
  /**
@@ -3427,6 +3724,10 @@ class EcPlayerCore {
3427
3724
  this.pendingRenderer = null;
3428
3725
  }
3429
3726
  await this.player.load(url);
3727
+ if (this.player instanceof HLSPlayer) {
3728
+ this.detectedFormat = this.player.isFMP4 ? "hls-fmp4" : "hls-ts";
3729
+ console.log("[EcPlayerCore] Updated format after playlist parse:", this.detectedFormat);
3730
+ }
3430
3731
  await this.player.initDecoder();
3431
3732
  this.callbacks.onStateChange?.({ isLoaded: true });
3432
3733
  } finally {