@besovideo/webrtc-player 0.10.43 → 0.10.44

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.
@@ -2,7 +2,7 @@ import {
2
2
  __async,
3
3
  init_define_processenv,
4
4
  init_jsx_shim
5
- } from "./chunk-7BKM3LDB.js";
5
+ } from "./chunk-7SHCWKP3.js";
6
6
 
7
7
  // src/lib/bvmedia/index.ts
8
8
  init_define_processenv();
@@ -1028,7 +1028,21 @@ var BVMedia = class {
1028
1028
  this.webcodecPlayer.decodeAudio(packet.data, packet.pts);
1029
1029
  }
1030
1030
  }
1031
+ // Recording subscribes independently of playback volume and onaudioData.
1032
+ setDecodedAudioCallback(callback) {
1033
+ this.onDecodedAudioCallback = callback;
1034
+ return Promise.resolve();
1035
+ }
1031
1036
  _OnAudioData(frame) {
1037
+ var _a;
1038
+ (_a = this.onDecodedAudioCallback) == null ? void 0 : _a.call(this, {
1039
+ pts: frame.pts / 1e3,
1040
+ // decoded PCM uses milliseconds; recording uses seconds
1041
+ data: frame.data,
1042
+ channels: this.audioInfo.channels,
1043
+ sampleRate: this.audioInfo.sampleRate,
1044
+ format: this.audioInfo.encoding
1045
+ });
1032
1046
  if (this.audioPlayer) {
1033
1047
  let numberOfFrames = frame.len / this.audioInfo.channels / this.audioPlayer.getNumofSamples(this.audioInfo.encoding);
1034
1048
  let ar = new Uint8Array(frame.data);
@@ -119,7 +119,7 @@ var __async = (__this, __arguments, generator) => {
119
119
  var define_processenv_default;
120
120
  var init_define_processenv = __esm({
121
121
  "<define:processenv>"() {
122
- define_processenv_default = { NODE_ENV: "production", SH_LIB_NAME: "@besovideo/webrtc-player", SH_LIB_VERSION: "0.10.43", PROJECT_NAMESPACE: "bvplayer", SH_LIB_BUILD_TIME: "2026-09-04 14:49:37" };
122
+ define_processenv_default = { NODE_ENV: "production", SH_LIB_NAME: "@besovideo/webrtc-player", SH_LIB_VERSION: "0.10.44", PROJECT_NAMESPACE: "bvplayer", SH_LIB_BUILD_TIME: "2026-09-10 18:05:58" };
123
123
  }
124
124
  });
125
125
 
@@ -125,7 +125,7 @@ var bvPlayerCore = (() => {
125
125
  var define_processenv_default;
126
126
  var init_define_processenv = __esm({
127
127
  "<define:processenv>"() {
128
- define_processenv_default = { NODE_ENV: "production", SH_LIB_NAME: "@besovideo/webrtc-player", SH_LIB_VERSION: "0.10.43", PROJECT_NAMESPACE: "bvplayer", SH_LIB_BUILD_TIME: "2026-09-04 14:49:37" };
128
+ define_processenv_default = { NODE_ENV: "production", SH_LIB_NAME: "@besovideo/webrtc-player", SH_LIB_VERSION: "0.10.44", PROJECT_NAMESPACE: "bvplayer", SH_LIB_BUILD_TIME: "2026-09-10 18:05:58" };
129
129
  }
130
130
  });
131
131
 
@@ -14840,7 +14840,21 @@ CustomVideoTrackGeneratorWorker();`;
14840
14840
  this.webcodecPlayer.decodeAudio(packet.data, packet.pts);
14841
14841
  }
14842
14842
  }
14843
+ // Recording subscribes independently of playback volume and onaudioData.
14844
+ setDecodedAudioCallback(callback) {
14845
+ this.onDecodedAudioCallback = callback;
14846
+ return Promise.resolve();
14847
+ }
14843
14848
  _OnAudioData(frame) {
14849
+ var _a;
14850
+ (_a = this.onDecodedAudioCallback) == null ? void 0 : _a.call(this, {
14851
+ pts: frame.pts / 1e3,
14852
+ // decoded PCM uses milliseconds; recording uses seconds
14853
+ data: frame.data,
14854
+ channels: this.audioInfo.channels,
14855
+ sampleRate: this.audioInfo.sampleRate,
14856
+ format: this.audioInfo.encoding
14857
+ });
14844
14858
  if (this.audioPlayer) {
14845
14859
  let numberOfFrames = frame.len / this.audioInfo.channels / this.audioPlayer.getNumofSamples(this.audioInfo.encoding);
14846
14860
  let ar = new Uint8Array(frame.data);
@@ -37325,6 +37339,182 @@ ${event.candidate ? event.candidate.candidate : "(null)"}`
37325
37339
  // src/utils/bvrtcRecord.ts
37326
37340
  init_define_processenv();
37327
37341
  init_jsx_shim();
37342
+
37343
+ // src/utils/hevcRecord.ts
37344
+ init_define_processenv();
37345
+ init_jsx_shim();
37346
+ function hevcDecoderConfig(vps, sps, pps) {
37347
+ const rbsp = [];
37348
+ for (let i3 = 2; i3 < sps.length; i3++) {
37349
+ if (i3 >= 4 && sps[i3] === 3 && sps[i3 - 1] === 0 && sps[i3 - 2] === 0)
37350
+ continue;
37351
+ rbsp.push(sps[i3]);
37352
+ }
37353
+ let offset2 = 0;
37354
+ const read2 = (count) => {
37355
+ if (offset2 + count > rbsp.length * 8)
37356
+ throw new Error("Invalid HEVC SPS in BVRTC callback");
37357
+ let value = 0;
37358
+ for (let i3 = 0; i3 < count; i3++, offset2++)
37359
+ value = value * 2 + (rbsp[offset2 >> 3] >> 7 - (offset2 & 7) & 1);
37360
+ return value;
37361
+ };
37362
+ const ue = () => {
37363
+ let zeros = 0;
37364
+ while (read2(1) === 0) {
37365
+ if (++zeros > 31)
37366
+ throw new Error("Invalid HEVC SPS Exp-Golomb value");
37367
+ }
37368
+ return __pow(2, zeros) - 1 + read2(zeros);
37369
+ };
37370
+ read2(4);
37371
+ const subLayers = read2(3);
37372
+ const temporalNested = read2(1);
37373
+ const profileTierLevel = Uint8Array.from({ length: 12 }, () => read2(8));
37374
+ const subProfiles = [];
37375
+ const subLevels = [];
37376
+ for (let i3 = 0; i3 < subLayers; i3++) {
37377
+ subProfiles.push(read2(1));
37378
+ subLevels.push(read2(1));
37379
+ }
37380
+ if (subLayers)
37381
+ read2((8 - subLayers) * 2);
37382
+ for (let i3 = 0; i3 < subLayers; i3++) {
37383
+ if (subProfiles[i3]) {
37384
+ read2(32);
37385
+ read2(32);
37386
+ read2(24);
37387
+ }
37388
+ if (subLevels[i3])
37389
+ read2(8);
37390
+ }
37391
+ ue();
37392
+ const chromaFormat = ue();
37393
+ if (chromaFormat === 3)
37394
+ read2(1);
37395
+ ue();
37396
+ ue();
37397
+ if (read2(1)) {
37398
+ ue();
37399
+ ue();
37400
+ ue();
37401
+ ue();
37402
+ }
37403
+ const bitDepthLuma = ue();
37404
+ const bitDepthChroma = ue();
37405
+ if (chromaFormat > 3 || bitDepthLuma > 7 || bitDepthChroma > 7)
37406
+ throw new Error("Invalid HEVC SPS format");
37407
+ const sets2 = [vps, sps, pps];
37408
+ if (sets2.some((set2) => set2.length < 2 || set2.length > 65535))
37409
+ throw new Error("Invalid HEVC parameter set length");
37410
+ const result = new Uint8Array(23 + sets2.reduce((size, set2) => size + 5 + set2.length, 0));
37411
+ result[0] = 1;
37412
+ result.set(profileTierLevel, 1);
37413
+ result.set([
37414
+ 240,
37415
+ 0,
37416
+ 252,
37417
+ 252 | chromaFormat,
37418
+ 248 | bitDepthLuma,
37419
+ 248 | bitDepthChroma,
37420
+ 0,
37421
+ 0,
37422
+ subLayers + 1 << 3 | temporalNested << 2 | 3,
37423
+ 3
37424
+ ], 13);
37425
+ let position = 23;
37426
+ sets2.forEach((set2, index) => {
37427
+ result[position++] = 128 | 32 + index;
37428
+ new DataView(result.buffer).setUint16(position, 1);
37429
+ position += 2;
37430
+ new DataView(result.buffer).setUint16(position, set2.length);
37431
+ position += 2;
37432
+ result.set(set2, position);
37433
+ position += set2.length;
37434
+ });
37435
+ return result;
37436
+ }
37437
+
37438
+ // src/utils/pcmResampler.ts
37439
+ init_define_processenv();
37440
+ init_jsx_shim();
37441
+ var PcmResampler = class {
37442
+ constructor(inputRate, outputRate, channels) {
37443
+ this.inputRate = inputRate;
37444
+ this.outputRate = outputRate;
37445
+ this.channels = channels;
37446
+ this.radius = 32;
37447
+ this.buffer = new Int16Array(0);
37448
+ this.bufferStart = 0;
37449
+ this.inputFrames = 0;
37450
+ this.outputFrames = 0;
37451
+ this.phases = [];
37452
+ if (!Number.isInteger(inputRate) || inputRate <= 0 || !Number.isInteger(outputRate) || outputRate <= 0 || !Number.isInteger(channels) || channels <= 0)
37453
+ throw new Error("Invalid PCM resampler format");
37454
+ const gcd = (a, b2) => b2 ? gcd(b2, a % b2) : a;
37455
+ const count = outputRate / gcd(inputRate, outputRate);
37456
+ const cutoff = Math.min(1, outputRate / inputRate);
37457
+ for (let phase = 0; phase < count; phase++) {
37458
+ const fraction = phase * inputRate % outputRate / outputRate;
37459
+ const weights = [];
37460
+ for (let tap = -this.radius + 1; tap <= this.radius; tap++) {
37461
+ const distance = fraction - tap;
37462
+ const x2 = Math.PI * distance * cutoff;
37463
+ const sinc = Math.abs(x2) < 1e-12 ? 1 : Math.sin(x2) / x2;
37464
+ const window2 = Math.abs(distance) >= this.radius ? 0 : 0.42 + 0.5 * Math.cos(Math.PI * distance / this.radius) + 0.08 * Math.cos(2 * Math.PI * distance / this.radius);
37465
+ weights.push(cutoff * sinc * window2);
37466
+ }
37467
+ const sum = weights.reduce((a, b2) => a + b2, 0);
37468
+ this.phases.push(weights.map((weight) => weight / sum));
37469
+ }
37470
+ }
37471
+ get pendingSeconds() {
37472
+ return this.inputFrames / this.inputRate - this.outputFrames / this.outputRate;
37473
+ }
37474
+ process(input) {
37475
+ if (input.length % this.channels)
37476
+ throw new Error("Incomplete interleaved PCM frame");
37477
+ if (this.inputRate === this.outputRate)
37478
+ return input;
37479
+ const merged = new Int16Array(this.buffer.length + input.length);
37480
+ merged.set(this.buffer);
37481
+ merged.set(input, this.buffer.length);
37482
+ this.buffer = merged;
37483
+ this.inputFrames += input.length / this.channels;
37484
+ return this.render(false);
37485
+ }
37486
+ flush() {
37487
+ return this.inputRate === this.outputRate ? new Int16Array(0) : this.render(true);
37488
+ }
37489
+ render(final) {
37490
+ const limit = Math.round(this.inputFrames * this.outputRate / this.inputRate);
37491
+ const result = [];
37492
+ while (this.outputFrames < limit) {
37493
+ const center = Math.floor(this.outputFrames * this.inputRate / this.outputRate);
37494
+ if (!final && center + this.radius >= this.inputFrames)
37495
+ break;
37496
+ const weights = this.phases[this.outputFrames % this.phases.length];
37497
+ for (let channel = 0; channel < this.channels; channel++) {
37498
+ let value = 0;
37499
+ for (let i3 = 0; i3 < weights.length; i3++) {
37500
+ const frame = center - this.radius + 1 + i3;
37501
+ if (frame >= 0 && frame < this.inputFrames)
37502
+ value += this.buffer[(frame - this.bufferStart) * this.channels + channel] * weights[i3];
37503
+ }
37504
+ result.push(Math.max(-32768, Math.min(32767, Math.round(value))));
37505
+ }
37506
+ this.outputFrames++;
37507
+ }
37508
+ const keepFrom = Math.max(0, Math.floor(this.outputFrames * this.inputRate / this.outputRate) - this.radius);
37509
+ if (keepFrom > this.bufferStart) {
37510
+ this.buffer = this.buffer.slice((keepFrom - this.bufferStart) * this.channels);
37511
+ this.bufferStart = keepFrom;
37512
+ }
37513
+ return Int16Array.from(result);
37514
+ }
37515
+ };
37516
+
37517
+ // src/utils/bvrtcRecord.ts
37328
37518
  var startCodeLength = (data, offset2) => data[offset2] === 0 && data[offset2 + 1] === 0 && (data[offset2 + 2] === 1 ? 3 : data[offset2 + 2] === 0 && data[offset2 + 3] === 1 ? 4 : 0);
37329
37519
  function splitAnnexB(data) {
37330
37520
  const units = [];
@@ -37385,32 +37575,21 @@ ${event.candidate ? event.candidate.candidate : "(null)"}`
37385
37575
  return value & 128 ? pcm : -pcm;
37386
37576
  };
37387
37577
  var AAC_SAMPLE_RATE = 48e3;
37388
- function resampleS16(input, channels, inputRate) {
37389
- if (inputRate === AAC_SAMPLE_RATE)
37390
- return input;
37391
- const inputFrames = Math.floor(input.length / channels);
37392
- if (!inputFrames)
37393
- return input;
37394
- const outputFrames = Math.round(inputFrames * AAC_SAMPLE_RATE / inputRate);
37395
- const output2 = new Int16Array(outputFrames * channels);
37396
- for (let frame = 0; frame < outputFrames; frame++) {
37397
- const sourcePosition = frame * inputRate / AAC_SAMPLE_RATE;
37398
- const lower = Math.floor(sourcePosition);
37399
- const upper = Math.min(lower + 1, inputFrames - 1);
37400
- const mix = sourcePosition - lower;
37401
- for (let channel = 0; channel < channels; channel++) {
37402
- output2[frame * channels + channel] = input[lower * channels + channel] * (1 - mix) + input[upper * channels + channel] * mix;
37403
- }
37404
- }
37405
- return output2;
37406
- }
37407
37578
  var BvrtcRecorder = class {
37408
37579
  constructor(player, fileName) {
37409
37580
  this.player = player;
37410
37581
  this.fileName = fileName;
37411
37582
  this.startedAt = 0;
37583
+ this.hevc = false;
37584
+ this.videoStarted = false;
37412
37585
  this.pendingAudio = [];
37413
37586
  this.transcodeAlaw = false;
37587
+ this.transcodePcm = false;
37588
+ this.pendingPcmAudio = [];
37589
+ this.pcmChannels = 1;
37590
+ this.recordingClockStart = 0;
37591
+ this.videoClockOffset = 0;
37592
+ this.pcmClockOffset = 0;
37414
37593
  this.stopped = false;
37415
37594
  this.onVideo = (packet) => {
37416
37595
  if (this.stopped)
@@ -37418,6 +37597,45 @@ ${event.candidate ? event.candidate.candidate : "(null)"}`
37418
37597
  const units = splitAnnexB(new Uint8Array(packet.data));
37419
37598
  if (!units.length)
37420
37599
  return;
37600
+ const hasPicture = units.some((unit) => this.hevc ? (unit[0] >> 1 & 63) < 32 : (unit[0] & 31) >= 1 && (unit[0] & 31) <= 5);
37601
+ if (this.transcodePcm && this.firstPts === void 0 && hasPicture) {
37602
+ this.videoClockOffset = (performance.now() - this.recordingClockStart) * 1e3;
37603
+ this.normalizePts(packet.pts);
37604
+ }
37605
+ if (this.hevc) {
37606
+ for (const unit of units) {
37607
+ const type = unit[0] >> 1 & 63;
37608
+ if (type === 32)
37609
+ this.vps = unit;
37610
+ if (type === 33)
37611
+ this.sps = unit;
37612
+ if (type === 34)
37613
+ this.pps = unit;
37614
+ }
37615
+ const sampleUnits = units.filter((unit) => {
37616
+ const type = unit[0] >> 1 & 63;
37617
+ return type < 32 || type > 34;
37618
+ });
37619
+ if (!sampleUnits.some((unit) => (unit[0] >> 1 & 63) < 32))
37620
+ return;
37621
+ const key2 = sampleUnits.some((unit) => {
37622
+ const type = unit[0] >> 1 & 63;
37623
+ return type >= 16 && type <= 21;
37624
+ });
37625
+ if (!this.videoStarted && !key2)
37626
+ return;
37627
+ this.createMuxer();
37628
+ if (!this.muxer)
37629
+ return;
37630
+ this.videoStarted = true;
37631
+ const pts2 = this.normalizePts(packet.pts);
37632
+ const duration2 = this.lastVideoPts === void 0 ? 0 : Math.max(0, pts2 - this.lastVideoPts);
37633
+ this.lastVideoPts = pts2;
37634
+ this.muxer.addVideoChunkRaw(avcSample(sampleUnits), key2 ? "key" : "delta", pts2, duration2, {
37635
+ decoderConfig: { codec: "hvc1", description: hevcDecoderConfig(this.vps, this.sps, this.pps) }
37636
+ });
37637
+ return;
37638
+ }
37421
37639
  for (const unit of units) {
37422
37640
  const type = unit[0] & 31;
37423
37641
  if (type === 7)
@@ -37425,13 +37643,19 @@ ${event.candidate ? event.candidate.candidate : "(null)"}`
37425
37643
  if (type === 8)
37426
37644
  this.pps = unit;
37427
37645
  }
37646
+ if (!units.some((unit) => (unit[0] & 31) >= 1 && (unit[0] & 31) <= 5))
37647
+ return;
37648
+ const key = units.some((unit) => (unit[0] & 31) === 5);
37649
+ if (!this.videoStarted && !key)
37650
+ return;
37428
37651
  this.createMuxer();
37429
37652
  if (!this.muxer)
37430
37653
  return;
37654
+ this.videoStarted = true;
37431
37655
  const pts = this.normalizePts(packet.pts);
37432
37656
  const duration = this.lastVideoPts === void 0 ? 0 : Math.max(0, pts - this.lastVideoPts);
37433
37657
  this.lastVideoPts = pts;
37434
- this.muxer.addVideoChunkRaw(avcSample(units), packet.flags & 1 ? "key" : "delta", pts, duration, {
37658
+ this.muxer.addVideoChunkRaw(avcSample(units), key ? "key" : "delta", pts, duration, {
37435
37659
  decoderConfig: { codec: "avc1", description: avcDecoderConfig(this.sps, this.pps) }
37436
37660
  });
37437
37661
  };
@@ -37447,77 +37671,115 @@ ${event.candidate ? event.candidate.candidate : "(null)"}`
37447
37671
  }
37448
37672
  this.addAudio(packet);
37449
37673
  };
37674
+ this.onPcmAudio = (packet) => {
37675
+ if (this.stopped)
37676
+ return;
37677
+ if (this.pcmClockStart === void 0) {
37678
+ this.pcmClockStart = packet.pts;
37679
+ this.pcmClockOffset = (performance.now() - this.recordingClockStart) * 1e3;
37680
+ }
37681
+ if (!this.muxer) {
37682
+ this.pendingPcmAudio.push(packet);
37683
+ return;
37684
+ }
37685
+ this.addPcmAudio(packet);
37686
+ };
37450
37687
  }
37451
37688
  start() {
37452
37689
  return __async(this, null, function* () {
37453
- var _a, _b;
37690
+ var _a, _b, _c;
37454
37691
  const videoCodec = (_a = this.player.getVideoInfo().codec) == null ? void 0 : _a.toLowerCase();
37455
37692
  const audioCodec = (_b = this.player.getAudioInfo().codec) == null ? void 0 : _b.toLowerCase();
37456
- if (videoCodec && videoCodec !== "none" && !/(h264|avc)/.test(videoCodec))
37693
+ this.hevc = /^(hevc|h265|h\.265|hvc1|hev1)$/.test(videoCodec || "");
37694
+ if (videoCodec && videoCodec !== "none" && !/(h264|avc)/.test(videoCodec) && !this.hevc)
37457
37695
  throw new Error(`BVRTC passthrough recording does not support video codec: ${videoCodec}`);
37458
- if (audioCodec && audioCodec !== "aac" && audioCodec !== "pcm_alaw")
37696
+ this.transcodePcm = /^(adpcm_)?g726(le)?$/.test(audioCodec || "");
37697
+ if (audioCodec && audioCodec !== "aac" && audioCodec !== "pcm_alaw" && !this.transcodePcm)
37459
37698
  throw new Error(`BVRTC passthrough recording does not support audio codec: ${audioCodec}`);
37460
37699
  this.transcodeAlaw = audioCodec === "pcm_alaw";
37461
- if (this.transcodeAlaw && !globalThis.AudioEncoder) {
37462
- throw new Error("This browser does not support WebCodecs AAC audio encoding required for pcm_alaw MP4 recording");
37700
+ if (this.transcodeAlaw || this.transcodePcm) {
37701
+ if (!(yield this.createAudioEncoder())) {
37702
+ this.transcodeAlaw = false;
37703
+ this.transcodePcm = false;
37704
+ }
37705
+ }
37706
+ if (this.transcodePcm && !this.player.setDecodedAudioCallback) {
37707
+ (_c = this.audioEncoder) == null ? void 0 : _c.close();
37708
+ throw new Error("BVRTC G.726 recording requires decoded PCM audio callbacks");
37463
37709
  }
37464
37710
  this.startedAt = Date.now();
37711
+ this.recordingClockStart = performance.now();
37465
37712
  if (videoCodec === "none")
37466
37713
  this.createMuxer();
37467
37714
  yield Promise.all([
37468
37715
  this.player.setVideoCallback(this.onVideo),
37469
- this.player.setAudioCallback(this.onAudio)
37716
+ this.transcodePcm ? this.player.setDecodedAudioCallback(this.onPcmAudio) : this.player.setAudioCallback(this.onAudio)
37470
37717
  ]);
37471
37718
  });
37472
37719
  }
37473
37720
  createMuxer() {
37474
37721
  var _a, _b;
37475
37722
  const hasVideo = ((_a = this.player.getVideoInfo().codec) == null ? void 0 : _a.toLowerCase()) !== "none";
37476
- if (this.muxer || hasVideo && (!this.sps || !this.pps))
37723
+ if (this.muxer || hasVideo && (!this.sps || !this.pps || this.hevc && !this.vps))
37477
37724
  return;
37478
37725
  const video = this.player.getVideoInfo();
37479
37726
  const audio = this.player.getAudioInfo();
37480
37727
  this.target = new ArrayBufferTarget2();
37481
37728
  this.muxer = new Muxer2({
37482
37729
  target: this.target,
37483
- video: hasVideo ? { codec: "avc", width: video.width || 1, height: video.height || 1 } : void 0,
37484
- audio: ((_b = audio.codec) == null ? void 0 : _b.toLowerCase()) === "aac" || this.transcodeAlaw ? { codec: "aac", numberOfChannels: audio.channels || 1, sampleRate: this.transcodeAlaw ? AAC_SAMPLE_RATE : audio.sampleRate || 8e3 } : void 0,
37730
+ video: hasVideo ? { codec: this.hevc ? "hevc" : "avc", width: video.width || 1, height: video.height || 1 } : void 0,
37731
+ audio: ((_b = audio.codec) == null ? void 0 : _b.toLowerCase()) === "aac" || this.transcodeAlaw || this.transcodePcm ? { codec: "aac", numberOfChannels: audio.channels || 1, sampleRate: this.transcodeAlaw || this.transcodePcm ? AAC_SAMPLE_RATE : audio.sampleRate || 8e3 } : void 0,
37485
37732
  fastStart: "in-memory",
37486
37733
  firstTimestampBehavior: "cross-track-offset"
37487
37734
  });
37488
- if (this.transcodeAlaw)
37489
- this.createAlawEncoder();
37490
37735
  for (const packet of this.pendingAudio.splice(0))
37491
37736
  this.addAudio(packet);
37737
+ for (const packet of this.pendingPcmAudio.splice(0))
37738
+ this.addPcmAudio(packet);
37492
37739
  }
37493
- createAlawEncoder() {
37494
- if (this.audioEncoder || !this.muxer)
37495
- return;
37496
- const audio = this.player.getAudioInfo();
37497
- const Encoder3 = globalThis.AudioEncoder;
37498
- this.audioEncoder = new Encoder3({
37499
- output: (chunk, meta) => {
37500
- var _a;
37501
- return (_a = this.muxer) == null ? void 0 : _a.addAudioChunk(chunk, meta);
37502
- },
37503
- error: (error2) => console.error("BVRTC AAC encoder error", error2)
37504
- });
37505
- this.audioEncoder.configure({
37506
- codec: "mp4a.40.2",
37507
- sampleRate: AAC_SAMPLE_RATE,
37508
- numberOfChannels: audio.channels || 1,
37509
- // Chromium's AAC WebCodecs implementation currently accepts 96 kbps+
37510
- // only (even for 8 kHz G.711 input).
37511
- bitrate: 96e3
37740
+ createAudioEncoder() {
37741
+ return __async(this, null, function* () {
37742
+ var _a, _b;
37743
+ const audio = this.player.getAudioInfo();
37744
+ const Encoder3 = globalThis.AudioEncoder;
37745
+ if (!Encoder3 || !globalThis.AudioData)
37746
+ return false;
37747
+ const config = {
37748
+ codec: "mp4a.40.2",
37749
+ sampleRate: AAC_SAMPLE_RATE,
37750
+ numberOfChannels: audio.channels || 1,
37751
+ // Chromium's AAC WebCodecs implementation currently accepts 96 kbps+
37752
+ // only (even for 8 kHz G.711 input).
37753
+ bitrate: 96e3
37754
+ };
37755
+ try {
37756
+ if (!(yield Encoder3.isConfigSupported(config)).supported)
37757
+ return false;
37758
+ this.audioEncoder = new Encoder3({
37759
+ output: (chunk, meta) => {
37760
+ var _a2;
37761
+ return (_a2 = this.muxer) == null ? void 0 : _a2.addAudioChunk(chunk, meta);
37762
+ },
37763
+ error: (error2) => console.error("BVRTC AAC encoder error", error2)
37764
+ });
37765
+ this.audioEncoder.configure(config);
37766
+ return true;
37767
+ } catch (e) {
37768
+ if (((_a = this.audioEncoder) == null ? void 0 : _a.state) !== "closed")
37769
+ (_b = this.audioEncoder) == null ? void 0 : _b.close();
37770
+ this.audioEncoder = void 0;
37771
+ return false;
37772
+ }
37512
37773
  });
37513
37774
  }
37514
37775
  normalizePts(pts) {
37515
37776
  var _a;
37777
+ pts = pts >= 1e11 ? pts / 1e3 : pts;
37516
37778
  (_a = this.firstPts) != null ? _a : this.firstPts = pts;
37517
- return Math.max(0, (pts - this.firstPts) * 1e3);
37779
+ return Math.max(0, Math.round((pts - this.firstPts) * 1e6 + this.videoClockOffset));
37518
37780
  }
37519
37781
  addAudio(packet) {
37520
- var _a, _b;
37782
+ var _a;
37521
37783
  if (!this.muxer)
37522
37784
  return;
37523
37785
  if (this.transcodeAlaw) {
@@ -37526,37 +37788,69 @@ ${event.candidate ? event.candidate.candidate : "(null)"}`
37526
37788
  const decoded = new Int16Array(encoded.byteLength);
37527
37789
  for (let i3 = 0; i3 < encoded.byteLength; i3++)
37528
37790
  decoded[i3] = alawToS16(encoded[i3]);
37529
- const channels = audio.channels || 1;
37530
- const pcm = resampleS16(decoded, channels, audio.sampleRate || 8e3);
37531
- const AudioDataCtor = globalThis.AudioData;
37532
- const frame = new AudioDataCtor({ format: "s16", sampleRate: AAC_SAMPLE_RATE, numberOfFrames: pcm.length / channels, numberOfChannels: channels, timestamp: this.normalizePts(packet.pts), data: pcm });
37533
- (_a = this.audioEncoder) == null ? void 0 : _a.encode(frame);
37534
- frame.close();
37791
+ this.encodePcm(decoded, audio.channels || 1, audio.sampleRate || 8e3, packet.pts);
37535
37792
  return;
37536
37793
  }
37537
- if (((_b = this.player.getAudioInfo().codec) == null ? void 0 : _b.toLowerCase()) !== "aac")
37794
+ if (((_a = this.player.getAudioInfo().codec) == null ? void 0 : _a.toLowerCase()) !== "aac")
37538
37795
  return;
37539
- const pts = this.normalizePts(packet.pts);
37540
- const duration = this.lastAudioPts === void 0 ? 0 : Math.max(0, pts - this.lastAudioPts);
37796
+ const duration = 1024 * 1e6 / (this.player.getAudioInfo().sampleRate || 8e3);
37797
+ const sourcePts = this.normalizePts(packet.pts);
37798
+ const pts = this.lastAudioPts === void 0 ? sourcePts : Math.max(sourcePts, this.lastAudioPts + duration);
37541
37799
  this.lastAudioPts = pts;
37542
37800
  this.muxer.addAudioChunkRaw(stripAdts(new Uint8Array(packet.data)), "key", pts, duration);
37543
37801
  }
37802
+ encodePcm(decoded, channels, sampleRate, sourcePts, normalizedPts = this.normalizePts(sourcePts)) {
37803
+ var _a;
37804
+ (_a = this.pcmResampler) != null ? _a : this.pcmResampler = new PcmResampler(sampleRate, AAC_SAMPLE_RATE, channels);
37805
+ this.pcmChannels = channels;
37806
+ const pendingSeconds = this.pcmResampler.pendingSeconds;
37807
+ const pcm = this.pcmResampler.process(decoded);
37808
+ this.writePcm(pcm, channels, normalizedPts - pendingSeconds * 1e6);
37809
+ }
37810
+ writePcm(pcm, channels, normalizedPts) {
37811
+ var _a;
37812
+ const numberOfFrames = pcm.length / channels;
37813
+ if (!numberOfFrames)
37814
+ return;
37815
+ const timestamp = Math.max(normalizedPts, (_a = this.nextPcmPts) != null ? _a : 0);
37816
+ this.nextPcmPts = timestamp + numberOfFrames * 1e6 / AAC_SAMPLE_RATE;
37817
+ const AudioDataCtor = globalThis.AudioData;
37818
+ const frame = new AudioDataCtor({ format: "s16", sampleRate: AAC_SAMPLE_RATE, numberOfFrames, numberOfChannels: channels, timestamp: Math.round(timestamp), data: pcm });
37819
+ try {
37820
+ this.audioEncoder.encode(frame);
37821
+ } finally {
37822
+ frame.close();
37823
+ }
37824
+ }
37825
+ addPcmAudio(packet) {
37826
+ if (packet.format !== "s16")
37827
+ throw new Error(`Unsupported BVRTC decoded PCM format: ${packet.format}`);
37828
+ const timestamp = (packet.pts - this.pcmClockStart) * 1e6 + this.pcmClockOffset;
37829
+ this.encodePcm(new Int16Array(packet.data), packet.channels, packet.sampleRate, packet.pts, timestamp);
37830
+ }
37544
37831
  stop(onRecordInfo) {
37545
37832
  return __async(this, null, function* () {
37546
- var _a, _b, _c;
37833
+ var _a, _b, _c, _d, _e;
37547
37834
  if (this.stopped)
37548
37835
  return;
37549
37836
  this.stopped = true;
37550
37837
  const disableCallbacks = Promise.allSettled([
37551
37838
  this.player.setVideoCallback(null),
37552
- this.player.setAudioCallback(null)
37839
+ this.transcodePcm ? this.player.setDecodedAudioCallback(null) : this.player.setAudioCallback(null)
37553
37840
  ]);
37554
37841
  const endTime = Date.now();
37555
- if (!this.muxer || !this.target)
37842
+ this.pendingAudio = [];
37843
+ this.pendingPcmAudio = [];
37844
+ if (!this.muxer || !this.target) {
37845
+ (_a = this.audioEncoder) == null ? void 0 : _a.close();
37846
+ yield disableCallbacks;
37556
37847
  return;
37557
- yield (_a = this.audioEncoder) == null ? void 0 : _a.flush();
37558
- (_b = this.audioEncoder) == null ? void 0 : _b.close();
37559
- (_c = this.muxer) == null ? void 0 : _c.finalize();
37848
+ }
37849
+ if (this.pcmResampler)
37850
+ this.writePcm(this.pcmResampler.flush(), this.pcmChannels, (_b = this.nextPcmPts) != null ? _b : 0);
37851
+ yield (_c = this.audioEncoder) == null ? void 0 : _c.flush();
37852
+ (_d = this.audioEncoder) == null ? void 0 : _d.close();
37853
+ (_e = this.muxer) == null ? void 0 : _e.finalize();
37560
37854
  const blob = new Blob([this.target.buffer], { type: "video/mp4" });
37561
37855
  const suffix = "mp4";
37562
37856
  const download2 = (name) => file_default.downLoadBlob(blob, name || this.fileName, suffix);
@@ -38656,7 +38950,8 @@ ${event.candidate ? event.candidate.candidate : "(null)"}`
38656
38950
  processHandle.reg(() => __async(this, null, function* () {
38657
38951
  yield callApis.avCall(
38658
38952
  __spreadProps(__spreadValues({}, callInfo), {
38659
- id: remoteUserId,
38953
+ // 接通前尚未返回远端 ID,取消呼叫时保留原始设备目标。
38954
+ id: remoteUserId || callInfo.id,
38660
38955
  status: 6 /* END */
38661
38956
  }),
38662
38957
  token
package/dist/main.es.js CHANGED
@@ -27,7 +27,7 @@ import {
27
27
  logger_default,
28
28
  t,
29
29
  util_default
30
- } from "./chunk-7BKM3LDB.js";
30
+ } from "./chunk-7SHCWKP3.js";
31
31
 
32
32
  // node_modules/.pnpm/ts-ebml@3.0.2/node_modules/ts-ebml/dist/EBML.min.js
33
33
  var require_EBML_min = __commonJS({
@@ -29833,7 +29833,7 @@ init_define_processenv();
29833
29833
  init_jsx_shim();
29834
29834
  var bvMediaPromise;
29835
29835
  function loadBvMedia() {
29836
- bvMediaPromise != null ? bvMediaPromise : bvMediaPromise = import("./bvmedia-7YQE7SZM.js").then(
29836
+ bvMediaPromise != null ? bvMediaPromise : bvMediaPromise = import("./bvmedia-7JPROY4X.js").then(
29837
29837
  (module) => module.default
29838
29838
  );
29839
29839
  return bvMediaPromise;
@@ -31858,6 +31858,182 @@ var DialogManager = class {
31858
31858
  // src/utils/bvrtcRecord.ts
31859
31859
  init_define_processenv();
31860
31860
  init_jsx_shim();
31861
+
31862
+ // src/utils/hevcRecord.ts
31863
+ init_define_processenv();
31864
+ init_jsx_shim();
31865
+ function hevcDecoderConfig(vps, sps, pps) {
31866
+ const rbsp = [];
31867
+ for (let i2 = 2; i2 < sps.length; i2++) {
31868
+ if (i2 >= 4 && sps[i2] === 3 && sps[i2 - 1] === 0 && sps[i2 - 2] === 0)
31869
+ continue;
31870
+ rbsp.push(sps[i2]);
31871
+ }
31872
+ let offset2 = 0;
31873
+ const read2 = (count) => {
31874
+ if (offset2 + count > rbsp.length * 8)
31875
+ throw new Error("Invalid HEVC SPS in BVRTC callback");
31876
+ let value = 0;
31877
+ for (let i2 = 0; i2 < count; i2++, offset2++)
31878
+ value = value * 2 + (rbsp[offset2 >> 3] >> 7 - (offset2 & 7) & 1);
31879
+ return value;
31880
+ };
31881
+ const ue = () => {
31882
+ let zeros = 0;
31883
+ while (read2(1) === 0) {
31884
+ if (++zeros > 31)
31885
+ throw new Error("Invalid HEVC SPS Exp-Golomb value");
31886
+ }
31887
+ return __pow(2, zeros) - 1 + read2(zeros);
31888
+ };
31889
+ read2(4);
31890
+ const subLayers = read2(3);
31891
+ const temporalNested = read2(1);
31892
+ const profileTierLevel = Uint8Array.from({ length: 12 }, () => read2(8));
31893
+ const subProfiles = [];
31894
+ const subLevels = [];
31895
+ for (let i2 = 0; i2 < subLayers; i2++) {
31896
+ subProfiles.push(read2(1));
31897
+ subLevels.push(read2(1));
31898
+ }
31899
+ if (subLayers)
31900
+ read2((8 - subLayers) * 2);
31901
+ for (let i2 = 0; i2 < subLayers; i2++) {
31902
+ if (subProfiles[i2]) {
31903
+ read2(32);
31904
+ read2(32);
31905
+ read2(24);
31906
+ }
31907
+ if (subLevels[i2])
31908
+ read2(8);
31909
+ }
31910
+ ue();
31911
+ const chromaFormat = ue();
31912
+ if (chromaFormat === 3)
31913
+ read2(1);
31914
+ ue();
31915
+ ue();
31916
+ if (read2(1)) {
31917
+ ue();
31918
+ ue();
31919
+ ue();
31920
+ ue();
31921
+ }
31922
+ const bitDepthLuma = ue();
31923
+ const bitDepthChroma = ue();
31924
+ if (chromaFormat > 3 || bitDepthLuma > 7 || bitDepthChroma > 7)
31925
+ throw new Error("Invalid HEVC SPS format");
31926
+ const sets = [vps, sps, pps];
31927
+ if (sets.some((set2) => set2.length < 2 || set2.length > 65535))
31928
+ throw new Error("Invalid HEVC parameter set length");
31929
+ const result = new Uint8Array(23 + sets.reduce((size, set2) => size + 5 + set2.length, 0));
31930
+ result[0] = 1;
31931
+ result.set(profileTierLevel, 1);
31932
+ result.set([
31933
+ 240,
31934
+ 0,
31935
+ 252,
31936
+ 252 | chromaFormat,
31937
+ 248 | bitDepthLuma,
31938
+ 248 | bitDepthChroma,
31939
+ 0,
31940
+ 0,
31941
+ subLayers + 1 << 3 | temporalNested << 2 | 3,
31942
+ 3
31943
+ ], 13);
31944
+ let position = 23;
31945
+ sets.forEach((set2, index) => {
31946
+ result[position++] = 128 | 32 + index;
31947
+ new DataView(result.buffer).setUint16(position, 1);
31948
+ position += 2;
31949
+ new DataView(result.buffer).setUint16(position, set2.length);
31950
+ position += 2;
31951
+ result.set(set2, position);
31952
+ position += set2.length;
31953
+ });
31954
+ return result;
31955
+ }
31956
+
31957
+ // src/utils/pcmResampler.ts
31958
+ init_define_processenv();
31959
+ init_jsx_shim();
31960
+ var PcmResampler = class {
31961
+ constructor(inputRate, outputRate, channels) {
31962
+ this.inputRate = inputRate;
31963
+ this.outputRate = outputRate;
31964
+ this.channels = channels;
31965
+ this.radius = 32;
31966
+ this.buffer = new Int16Array(0);
31967
+ this.bufferStart = 0;
31968
+ this.inputFrames = 0;
31969
+ this.outputFrames = 0;
31970
+ this.phases = [];
31971
+ if (!Number.isInteger(inputRate) || inputRate <= 0 || !Number.isInteger(outputRate) || outputRate <= 0 || !Number.isInteger(channels) || channels <= 0)
31972
+ throw new Error("Invalid PCM resampler format");
31973
+ const gcd = (a, b) => b ? gcd(b, a % b) : a;
31974
+ const count = outputRate / gcd(inputRate, outputRate);
31975
+ const cutoff = Math.min(1, outputRate / inputRate);
31976
+ for (let phase = 0; phase < count; phase++) {
31977
+ const fraction = phase * inputRate % outputRate / outputRate;
31978
+ const weights = [];
31979
+ for (let tap = -this.radius + 1; tap <= this.radius; tap++) {
31980
+ const distance = fraction - tap;
31981
+ const x = Math.PI * distance * cutoff;
31982
+ const sinc = Math.abs(x) < 1e-12 ? 1 : Math.sin(x) / x;
31983
+ const window2 = Math.abs(distance) >= this.radius ? 0 : 0.42 + 0.5 * Math.cos(Math.PI * distance / this.radius) + 0.08 * Math.cos(2 * Math.PI * distance / this.radius);
31984
+ weights.push(cutoff * sinc * window2);
31985
+ }
31986
+ const sum = weights.reduce((a, b) => a + b, 0);
31987
+ this.phases.push(weights.map((weight) => weight / sum));
31988
+ }
31989
+ }
31990
+ get pendingSeconds() {
31991
+ return this.inputFrames / this.inputRate - this.outputFrames / this.outputRate;
31992
+ }
31993
+ process(input) {
31994
+ if (input.length % this.channels)
31995
+ throw new Error("Incomplete interleaved PCM frame");
31996
+ if (this.inputRate === this.outputRate)
31997
+ return input;
31998
+ const merged = new Int16Array(this.buffer.length + input.length);
31999
+ merged.set(this.buffer);
32000
+ merged.set(input, this.buffer.length);
32001
+ this.buffer = merged;
32002
+ this.inputFrames += input.length / this.channels;
32003
+ return this.render(false);
32004
+ }
32005
+ flush() {
32006
+ return this.inputRate === this.outputRate ? new Int16Array(0) : this.render(true);
32007
+ }
32008
+ render(final) {
32009
+ const limit = Math.round(this.inputFrames * this.outputRate / this.inputRate);
32010
+ const result = [];
32011
+ while (this.outputFrames < limit) {
32012
+ const center = Math.floor(this.outputFrames * this.inputRate / this.outputRate);
32013
+ if (!final && center + this.radius >= this.inputFrames)
32014
+ break;
32015
+ const weights = this.phases[this.outputFrames % this.phases.length];
32016
+ for (let channel = 0; channel < this.channels; channel++) {
32017
+ let value = 0;
32018
+ for (let i2 = 0; i2 < weights.length; i2++) {
32019
+ const frame = center - this.radius + 1 + i2;
32020
+ if (frame >= 0 && frame < this.inputFrames)
32021
+ value += this.buffer[(frame - this.bufferStart) * this.channels + channel] * weights[i2];
32022
+ }
32023
+ result.push(Math.max(-32768, Math.min(32767, Math.round(value))));
32024
+ }
32025
+ this.outputFrames++;
32026
+ }
32027
+ const keepFrom = Math.max(0, Math.floor(this.outputFrames * this.inputRate / this.outputRate) - this.radius);
32028
+ if (keepFrom > this.bufferStart) {
32029
+ this.buffer = this.buffer.slice((keepFrom - this.bufferStart) * this.channels);
32030
+ this.bufferStart = keepFrom;
32031
+ }
32032
+ return Int16Array.from(result);
32033
+ }
32034
+ };
32035
+
32036
+ // src/utils/bvrtcRecord.ts
31861
32037
  var startCodeLength = (data, offset2) => data[offset2] === 0 && data[offset2 + 1] === 0 && (data[offset2 + 2] === 1 ? 3 : data[offset2 + 2] === 0 && data[offset2 + 3] === 1 ? 4 : 0);
31862
32038
  function splitAnnexB(data) {
31863
32039
  const units = [];
@@ -31918,32 +32094,21 @@ var alawToS16 = (sample) => {
31918
32094
  return value & 128 ? pcm : -pcm;
31919
32095
  };
31920
32096
  var AAC_SAMPLE_RATE = 48e3;
31921
- function resampleS16(input, channels, inputRate) {
31922
- if (inputRate === AAC_SAMPLE_RATE)
31923
- return input;
31924
- const inputFrames = Math.floor(input.length / channels);
31925
- if (!inputFrames)
31926
- return input;
31927
- const outputFrames = Math.round(inputFrames * AAC_SAMPLE_RATE / inputRate);
31928
- const output = new Int16Array(outputFrames * channels);
31929
- for (let frame = 0; frame < outputFrames; frame++) {
31930
- const sourcePosition = frame * inputRate / AAC_SAMPLE_RATE;
31931
- const lower = Math.floor(sourcePosition);
31932
- const upper = Math.min(lower + 1, inputFrames - 1);
31933
- const mix = sourcePosition - lower;
31934
- for (let channel = 0; channel < channels; channel++) {
31935
- output[frame * channels + channel] = input[lower * channels + channel] * (1 - mix) + input[upper * channels + channel] * mix;
31936
- }
31937
- }
31938
- return output;
31939
- }
31940
32097
  var BvrtcRecorder = class {
31941
32098
  constructor(player, fileName) {
31942
32099
  this.player = player;
31943
32100
  this.fileName = fileName;
31944
32101
  this.startedAt = 0;
32102
+ this.hevc = false;
32103
+ this.videoStarted = false;
31945
32104
  this.pendingAudio = [];
31946
32105
  this.transcodeAlaw = false;
32106
+ this.transcodePcm = false;
32107
+ this.pendingPcmAudio = [];
32108
+ this.pcmChannels = 1;
32109
+ this.recordingClockStart = 0;
32110
+ this.videoClockOffset = 0;
32111
+ this.pcmClockOffset = 0;
31947
32112
  this.stopped = false;
31948
32113
  this.onVideo = (packet) => {
31949
32114
  if (this.stopped)
@@ -31951,6 +32116,45 @@ var BvrtcRecorder = class {
31951
32116
  const units = splitAnnexB(new Uint8Array(packet.data));
31952
32117
  if (!units.length)
31953
32118
  return;
32119
+ const hasPicture = units.some((unit) => this.hevc ? (unit[0] >> 1 & 63) < 32 : (unit[0] & 31) >= 1 && (unit[0] & 31) <= 5);
32120
+ if (this.transcodePcm && this.firstPts === void 0 && hasPicture) {
32121
+ this.videoClockOffset = (performance.now() - this.recordingClockStart) * 1e3;
32122
+ this.normalizePts(packet.pts);
32123
+ }
32124
+ if (this.hevc) {
32125
+ for (const unit of units) {
32126
+ const type = unit[0] >> 1 & 63;
32127
+ if (type === 32)
32128
+ this.vps = unit;
32129
+ if (type === 33)
32130
+ this.sps = unit;
32131
+ if (type === 34)
32132
+ this.pps = unit;
32133
+ }
32134
+ const sampleUnits = units.filter((unit) => {
32135
+ const type = unit[0] >> 1 & 63;
32136
+ return type < 32 || type > 34;
32137
+ });
32138
+ if (!sampleUnits.some((unit) => (unit[0] >> 1 & 63) < 32))
32139
+ return;
32140
+ const key2 = sampleUnits.some((unit) => {
32141
+ const type = unit[0] >> 1 & 63;
32142
+ return type >= 16 && type <= 21;
32143
+ });
32144
+ if (!this.videoStarted && !key2)
32145
+ return;
32146
+ this.createMuxer();
32147
+ if (!this.muxer)
32148
+ return;
32149
+ this.videoStarted = true;
32150
+ const pts2 = this.normalizePts(packet.pts);
32151
+ const duration2 = this.lastVideoPts === void 0 ? 0 : Math.max(0, pts2 - this.lastVideoPts);
32152
+ this.lastVideoPts = pts2;
32153
+ this.muxer.addVideoChunkRaw(avcSample(sampleUnits), key2 ? "key" : "delta", pts2, duration2, {
32154
+ decoderConfig: { codec: "hvc1", description: hevcDecoderConfig(this.vps, this.sps, this.pps) }
32155
+ });
32156
+ return;
32157
+ }
31954
32158
  for (const unit of units) {
31955
32159
  const type = unit[0] & 31;
31956
32160
  if (type === 7)
@@ -31958,13 +32162,19 @@ var BvrtcRecorder = class {
31958
32162
  if (type === 8)
31959
32163
  this.pps = unit;
31960
32164
  }
32165
+ if (!units.some((unit) => (unit[0] & 31) >= 1 && (unit[0] & 31) <= 5))
32166
+ return;
32167
+ const key = units.some((unit) => (unit[0] & 31) === 5);
32168
+ if (!this.videoStarted && !key)
32169
+ return;
31961
32170
  this.createMuxer();
31962
32171
  if (!this.muxer)
31963
32172
  return;
32173
+ this.videoStarted = true;
31964
32174
  const pts = this.normalizePts(packet.pts);
31965
32175
  const duration = this.lastVideoPts === void 0 ? 0 : Math.max(0, pts - this.lastVideoPts);
31966
32176
  this.lastVideoPts = pts;
31967
- this.muxer.addVideoChunkRaw(avcSample(units), packet.flags & 1 ? "key" : "delta", pts, duration, {
32177
+ this.muxer.addVideoChunkRaw(avcSample(units), key ? "key" : "delta", pts, duration, {
31968
32178
  decoderConfig: { codec: "avc1", description: avcDecoderConfig(this.sps, this.pps) }
31969
32179
  });
31970
32180
  };
@@ -31980,77 +32190,115 @@ var BvrtcRecorder = class {
31980
32190
  }
31981
32191
  this.addAudio(packet);
31982
32192
  };
32193
+ this.onPcmAudio = (packet) => {
32194
+ if (this.stopped)
32195
+ return;
32196
+ if (this.pcmClockStart === void 0) {
32197
+ this.pcmClockStart = packet.pts;
32198
+ this.pcmClockOffset = (performance.now() - this.recordingClockStart) * 1e3;
32199
+ }
32200
+ if (!this.muxer) {
32201
+ this.pendingPcmAudio.push(packet);
32202
+ return;
32203
+ }
32204
+ this.addPcmAudio(packet);
32205
+ };
31983
32206
  }
31984
32207
  start() {
31985
32208
  return __async(this, null, function* () {
31986
- var _a, _b;
32209
+ var _a, _b, _c;
31987
32210
  const videoCodec = (_a = this.player.getVideoInfo().codec) == null ? void 0 : _a.toLowerCase();
31988
32211
  const audioCodec = (_b = this.player.getAudioInfo().codec) == null ? void 0 : _b.toLowerCase();
31989
- if (videoCodec && videoCodec !== "none" && !/(h264|avc)/.test(videoCodec))
32212
+ this.hevc = /^(hevc|h265|h\.265|hvc1|hev1)$/.test(videoCodec || "");
32213
+ if (videoCodec && videoCodec !== "none" && !/(h264|avc)/.test(videoCodec) && !this.hevc)
31990
32214
  throw new Error(`BVRTC passthrough recording does not support video codec: ${videoCodec}`);
31991
- if (audioCodec && audioCodec !== "aac" && audioCodec !== "pcm_alaw")
32215
+ this.transcodePcm = /^(adpcm_)?g726(le)?$/.test(audioCodec || "");
32216
+ if (audioCodec && audioCodec !== "aac" && audioCodec !== "pcm_alaw" && !this.transcodePcm)
31992
32217
  throw new Error(`BVRTC passthrough recording does not support audio codec: ${audioCodec}`);
31993
32218
  this.transcodeAlaw = audioCodec === "pcm_alaw";
31994
- if (this.transcodeAlaw && !globalThis.AudioEncoder) {
31995
- throw new Error("This browser does not support WebCodecs AAC audio encoding required for pcm_alaw MP4 recording");
32219
+ if (this.transcodeAlaw || this.transcodePcm) {
32220
+ if (!(yield this.createAudioEncoder())) {
32221
+ this.transcodeAlaw = false;
32222
+ this.transcodePcm = false;
32223
+ }
32224
+ }
32225
+ if (this.transcodePcm && !this.player.setDecodedAudioCallback) {
32226
+ (_c = this.audioEncoder) == null ? void 0 : _c.close();
32227
+ throw new Error("BVRTC G.726 recording requires decoded PCM audio callbacks");
31996
32228
  }
31997
32229
  this.startedAt = Date.now();
32230
+ this.recordingClockStart = performance.now();
31998
32231
  if (videoCodec === "none")
31999
32232
  this.createMuxer();
32000
32233
  yield Promise.all([
32001
32234
  this.player.setVideoCallback(this.onVideo),
32002
- this.player.setAudioCallback(this.onAudio)
32235
+ this.transcodePcm ? this.player.setDecodedAudioCallback(this.onPcmAudio) : this.player.setAudioCallback(this.onAudio)
32003
32236
  ]);
32004
32237
  });
32005
32238
  }
32006
32239
  createMuxer() {
32007
32240
  var _a, _b;
32008
32241
  const hasVideo = ((_a = this.player.getVideoInfo().codec) == null ? void 0 : _a.toLowerCase()) !== "none";
32009
- if (this.muxer || hasVideo && (!this.sps || !this.pps))
32242
+ if (this.muxer || hasVideo && (!this.sps || !this.pps || this.hevc && !this.vps))
32010
32243
  return;
32011
32244
  const video = this.player.getVideoInfo();
32012
32245
  const audio = this.player.getAudioInfo();
32013
32246
  this.target = new ArrayBufferTarget2();
32014
32247
  this.muxer = new Muxer2({
32015
32248
  target: this.target,
32016
- video: hasVideo ? { codec: "avc", width: video.width || 1, height: video.height || 1 } : void 0,
32017
- audio: ((_b = audio.codec) == null ? void 0 : _b.toLowerCase()) === "aac" || this.transcodeAlaw ? { codec: "aac", numberOfChannels: audio.channels || 1, sampleRate: this.transcodeAlaw ? AAC_SAMPLE_RATE : audio.sampleRate || 8e3 } : void 0,
32249
+ video: hasVideo ? { codec: this.hevc ? "hevc" : "avc", width: video.width || 1, height: video.height || 1 } : void 0,
32250
+ audio: ((_b = audio.codec) == null ? void 0 : _b.toLowerCase()) === "aac" || this.transcodeAlaw || this.transcodePcm ? { codec: "aac", numberOfChannels: audio.channels || 1, sampleRate: this.transcodeAlaw || this.transcodePcm ? AAC_SAMPLE_RATE : audio.sampleRate || 8e3 } : void 0,
32018
32251
  fastStart: "in-memory",
32019
32252
  firstTimestampBehavior: "cross-track-offset"
32020
32253
  });
32021
- if (this.transcodeAlaw)
32022
- this.createAlawEncoder();
32023
32254
  for (const packet of this.pendingAudio.splice(0))
32024
32255
  this.addAudio(packet);
32256
+ for (const packet of this.pendingPcmAudio.splice(0))
32257
+ this.addPcmAudio(packet);
32025
32258
  }
32026
- createAlawEncoder() {
32027
- if (this.audioEncoder || !this.muxer)
32028
- return;
32029
- const audio = this.player.getAudioInfo();
32030
- const Encoder3 = globalThis.AudioEncoder;
32031
- this.audioEncoder = new Encoder3({
32032
- output: (chunk, meta) => {
32033
- var _a;
32034
- return (_a = this.muxer) == null ? void 0 : _a.addAudioChunk(chunk, meta);
32035
- },
32036
- error: (error) => console.error("BVRTC AAC encoder error", error)
32037
- });
32038
- this.audioEncoder.configure({
32039
- codec: "mp4a.40.2",
32040
- sampleRate: AAC_SAMPLE_RATE,
32041
- numberOfChannels: audio.channels || 1,
32042
- // Chromium's AAC WebCodecs implementation currently accepts 96 kbps+
32043
- // only (even for 8 kHz G.711 input).
32044
- bitrate: 96e3
32259
+ createAudioEncoder() {
32260
+ return __async(this, null, function* () {
32261
+ var _a, _b;
32262
+ const audio = this.player.getAudioInfo();
32263
+ const Encoder3 = globalThis.AudioEncoder;
32264
+ if (!Encoder3 || !globalThis.AudioData)
32265
+ return false;
32266
+ const config = {
32267
+ codec: "mp4a.40.2",
32268
+ sampleRate: AAC_SAMPLE_RATE,
32269
+ numberOfChannels: audio.channels || 1,
32270
+ // Chromium's AAC WebCodecs implementation currently accepts 96 kbps+
32271
+ // only (even for 8 kHz G.711 input).
32272
+ bitrate: 96e3
32273
+ };
32274
+ try {
32275
+ if (!(yield Encoder3.isConfigSupported(config)).supported)
32276
+ return false;
32277
+ this.audioEncoder = new Encoder3({
32278
+ output: (chunk, meta) => {
32279
+ var _a2;
32280
+ return (_a2 = this.muxer) == null ? void 0 : _a2.addAudioChunk(chunk, meta);
32281
+ },
32282
+ error: (error) => console.error("BVRTC AAC encoder error", error)
32283
+ });
32284
+ this.audioEncoder.configure(config);
32285
+ return true;
32286
+ } catch (e) {
32287
+ if (((_a = this.audioEncoder) == null ? void 0 : _a.state) !== "closed")
32288
+ (_b = this.audioEncoder) == null ? void 0 : _b.close();
32289
+ this.audioEncoder = void 0;
32290
+ return false;
32291
+ }
32045
32292
  });
32046
32293
  }
32047
32294
  normalizePts(pts) {
32048
32295
  var _a;
32296
+ pts = pts >= 1e11 ? pts / 1e3 : pts;
32049
32297
  (_a = this.firstPts) != null ? _a : this.firstPts = pts;
32050
- return Math.max(0, (pts - this.firstPts) * 1e3);
32298
+ return Math.max(0, Math.round((pts - this.firstPts) * 1e6 + this.videoClockOffset));
32051
32299
  }
32052
32300
  addAudio(packet) {
32053
- var _a, _b;
32301
+ var _a;
32054
32302
  if (!this.muxer)
32055
32303
  return;
32056
32304
  if (this.transcodeAlaw) {
@@ -32059,37 +32307,69 @@ var BvrtcRecorder = class {
32059
32307
  const decoded = new Int16Array(encoded.byteLength);
32060
32308
  for (let i2 = 0; i2 < encoded.byteLength; i2++)
32061
32309
  decoded[i2] = alawToS16(encoded[i2]);
32062
- const channels = audio.channels || 1;
32063
- const pcm = resampleS16(decoded, channels, audio.sampleRate || 8e3);
32064
- const AudioDataCtor = globalThis.AudioData;
32065
- const frame = new AudioDataCtor({ format: "s16", sampleRate: AAC_SAMPLE_RATE, numberOfFrames: pcm.length / channels, numberOfChannels: channels, timestamp: this.normalizePts(packet.pts), data: pcm });
32066
- (_a = this.audioEncoder) == null ? void 0 : _a.encode(frame);
32067
- frame.close();
32310
+ this.encodePcm(decoded, audio.channels || 1, audio.sampleRate || 8e3, packet.pts);
32068
32311
  return;
32069
32312
  }
32070
- if (((_b = this.player.getAudioInfo().codec) == null ? void 0 : _b.toLowerCase()) !== "aac")
32313
+ if (((_a = this.player.getAudioInfo().codec) == null ? void 0 : _a.toLowerCase()) !== "aac")
32071
32314
  return;
32072
- const pts = this.normalizePts(packet.pts);
32073
- const duration = this.lastAudioPts === void 0 ? 0 : Math.max(0, pts - this.lastAudioPts);
32315
+ const duration = 1024 * 1e6 / (this.player.getAudioInfo().sampleRate || 8e3);
32316
+ const sourcePts = this.normalizePts(packet.pts);
32317
+ const pts = this.lastAudioPts === void 0 ? sourcePts : Math.max(sourcePts, this.lastAudioPts + duration);
32074
32318
  this.lastAudioPts = pts;
32075
32319
  this.muxer.addAudioChunkRaw(stripAdts(new Uint8Array(packet.data)), "key", pts, duration);
32076
32320
  }
32321
+ encodePcm(decoded, channels, sampleRate, sourcePts, normalizedPts = this.normalizePts(sourcePts)) {
32322
+ var _a;
32323
+ (_a = this.pcmResampler) != null ? _a : this.pcmResampler = new PcmResampler(sampleRate, AAC_SAMPLE_RATE, channels);
32324
+ this.pcmChannels = channels;
32325
+ const pendingSeconds = this.pcmResampler.pendingSeconds;
32326
+ const pcm = this.pcmResampler.process(decoded);
32327
+ this.writePcm(pcm, channels, normalizedPts - pendingSeconds * 1e6);
32328
+ }
32329
+ writePcm(pcm, channels, normalizedPts) {
32330
+ var _a;
32331
+ const numberOfFrames = pcm.length / channels;
32332
+ if (!numberOfFrames)
32333
+ return;
32334
+ const timestamp = Math.max(normalizedPts, (_a = this.nextPcmPts) != null ? _a : 0);
32335
+ this.nextPcmPts = timestamp + numberOfFrames * 1e6 / AAC_SAMPLE_RATE;
32336
+ const AudioDataCtor = globalThis.AudioData;
32337
+ const frame = new AudioDataCtor({ format: "s16", sampleRate: AAC_SAMPLE_RATE, numberOfFrames, numberOfChannels: channels, timestamp: Math.round(timestamp), data: pcm });
32338
+ try {
32339
+ this.audioEncoder.encode(frame);
32340
+ } finally {
32341
+ frame.close();
32342
+ }
32343
+ }
32344
+ addPcmAudio(packet) {
32345
+ if (packet.format !== "s16")
32346
+ throw new Error(`Unsupported BVRTC decoded PCM format: ${packet.format}`);
32347
+ const timestamp = (packet.pts - this.pcmClockStart) * 1e6 + this.pcmClockOffset;
32348
+ this.encodePcm(new Int16Array(packet.data), packet.channels, packet.sampleRate, packet.pts, timestamp);
32349
+ }
32077
32350
  stop(onRecordInfo) {
32078
32351
  return __async(this, null, function* () {
32079
- var _a, _b, _c;
32352
+ var _a, _b, _c, _d, _e;
32080
32353
  if (this.stopped)
32081
32354
  return;
32082
32355
  this.stopped = true;
32083
32356
  const disableCallbacks = Promise.allSettled([
32084
32357
  this.player.setVideoCallback(null),
32085
- this.player.setAudioCallback(null)
32358
+ this.transcodePcm ? this.player.setDecodedAudioCallback(null) : this.player.setAudioCallback(null)
32086
32359
  ]);
32087
32360
  const endTime = Date.now();
32088
- if (!this.muxer || !this.target)
32361
+ this.pendingAudio = [];
32362
+ this.pendingPcmAudio = [];
32363
+ if (!this.muxer || !this.target) {
32364
+ (_a = this.audioEncoder) == null ? void 0 : _a.close();
32365
+ yield disableCallbacks;
32089
32366
  return;
32090
- yield (_a = this.audioEncoder) == null ? void 0 : _a.flush();
32091
- (_b = this.audioEncoder) == null ? void 0 : _b.close();
32092
- (_c = this.muxer) == null ? void 0 : _c.finalize();
32367
+ }
32368
+ if (this.pcmResampler)
32369
+ this.writePcm(this.pcmResampler.flush(), this.pcmChannels, (_b = this.nextPcmPts) != null ? _b : 0);
32370
+ yield (_c = this.audioEncoder) == null ? void 0 : _c.flush();
32371
+ (_d = this.audioEncoder) == null ? void 0 : _d.close();
32372
+ (_e = this.muxer) == null ? void 0 : _e.finalize();
32093
32373
  const blob = new Blob([this.target.buffer], { type: "video/mp4" });
32094
32374
  const suffix = "mp4";
32095
32375
  const download2 = (name) => file_default.downLoadBlob(blob, name || this.fileName, suffix);
@@ -33189,7 +33469,8 @@ function CreateCallConnectionManager(params) {
33189
33469
  processHandle.reg(() => __async(this, null, function* () {
33190
33470
  yield callApis.avCall(
33191
33471
  __spreadProps(__spreadValues({}, callInfo), {
33192
- id: remoteUserId,
33472
+ // 接通前尚未返回远端 ID,取消呼叫时保留原始设备目标。
33473
+ id: remoteUserId || callInfo.id,
33193
33474
  status: 6 /* END */
33194
33475
  }),
33195
33476
  token
@@ -4,6 +4,13 @@ export interface IBvrtcPacket {
4
4
  flags: number;
5
5
  data: ArrayBuffer;
6
6
  }
7
+ export interface IBvrtcPcmPacket {
8
+ pts: number;
9
+ data: ArrayBuffer;
10
+ channels: number;
11
+ sampleRate: number;
12
+ format: string;
13
+ }
7
14
  interface IBvrtcPlayer {
8
15
  getVideoInfo(): {
9
16
  codec?: string;
@@ -17,6 +24,7 @@ interface IBvrtcPlayer {
17
24
  };
18
25
  setVideoCallback(callback: ((packet: IBvrtcPacket) => void) | null): Promise<void>;
19
26
  setAudioCallback(callback: ((packet: IBvrtcPacket) => void) | null): Promise<void>;
27
+ setDecodedAudioCallback?(callback: ((packet: IBvrtcPcmPacket) => void) | null): Promise<void>;
20
28
  }
21
29
  export declare class BvrtcRecorder {
22
30
  private player;
@@ -29,18 +37,34 @@ export declare class BvrtcRecorder {
29
37
  private lastAudioPts?;
30
38
  private sps?;
31
39
  private pps?;
40
+ private vps?;
41
+ private hevc;
42
+ private videoStarted;
32
43
  private pendingAudio;
33
44
  private transcodeAlaw;
45
+ private transcodePcm;
46
+ private pendingPcmAudio;
47
+ private nextPcmPts?;
48
+ private pcmResampler?;
49
+ private pcmChannels;
50
+ private recordingClockStart;
51
+ private videoClockOffset;
52
+ private pcmClockStart?;
53
+ private pcmClockOffset;
34
54
  private audioEncoder?;
35
55
  private stopped;
36
56
  constructor(player: IBvrtcPlayer, fileName: string);
37
57
  start(): Promise<void>;
38
58
  private createMuxer;
39
- private createAlawEncoder;
59
+ private createAudioEncoder;
40
60
  private normalizePts;
41
61
  private onVideo;
42
62
  private addAudio;
43
63
  private onAudio;
64
+ private encodePcm;
65
+ private writePcm;
66
+ private addPcmAudio;
67
+ private onPcmAudio;
44
68
  stop(onRecordInfo?: IOnMediaRecordHandle): Promise<void>;
45
69
  }
46
70
  export {};
@@ -0,0 +1 @@
1
+ export declare function hevcDecoderConfig(vps: Uint8Array, sps: Uint8Array, pps: Uint8Array): Uint8Array;
@@ -0,0 +1,16 @@
1
+ export declare class PcmResampler {
2
+ private inputRate;
3
+ private outputRate;
4
+ private channels;
5
+ private readonly radius;
6
+ private buffer;
7
+ private bufferStart;
8
+ private inputFrames;
9
+ private outputFrames;
10
+ private readonly phases;
11
+ constructor(inputRate: number, outputRate: number, channels: number);
12
+ get pendingSeconds(): number;
13
+ process(input: Int16Array): Int16Array;
14
+ flush(): Int16Array;
15
+ private render;
16
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@besovideo/webrtc-player",
3
- "version": "0.10.43",
3
+ "version": "0.10.44",
4
4
  "description": "@besovideo/webrtc-player desc",
5
5
  "type": "module",
6
6
  "types": "./dist/types/main.d.ts",