hls.js 1.6.0-beta.3.0.canary.10979 → 1.6.0-beta.3.0.canary.10981

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/package.json CHANGED
@@ -134,5 +134,5 @@
134
134
  "url-toolkit": "2.2.5",
135
135
  "wrangler": "3.109.2"
136
136
  },
137
- "version": "1.6.0-beta.3.0.canary.10979"
137
+ "version": "1.6.0-beta.3.0.canary.10981"
138
138
  }
package/src/config.ts CHANGED
@@ -15,6 +15,7 @@ import { TimelineController } from './controller/timeline-controller';
15
15
  import Cues from './utils/cues';
16
16
  import FetchLoader, { fetchSupported } from './utils/fetch-loader';
17
17
  import { requestMediaKeySystemAccess } from './utils/mediakeys-helper';
18
+ import { stringify } from './utils/safe-json-stringify';
18
19
  import XhrLoader from './utils/xhr-loader';
19
20
  import type { MediaKeySessionContext } from './controller/eme-controller';
20
21
  import type Hls from './hls';
@@ -688,7 +689,7 @@ export function mergeConfig(
688
689
  logger.warn(
689
690
  `hls.js config: "${report.join(
690
691
  '", "',
691
- )}" setting(s) are deprecated, use "${policyName}": ${JSON.stringify(
692
+ )}" setting(s) are deprecated, use "${policyName}": ${stringify(
692
693
  userConfig[policyName],
693
694
  )}`,
694
695
  );
@@ -8,6 +8,7 @@ import {
8
8
  requiresMediaCapabilitiesDecodingInfo,
9
9
  SUPPORTED_INFO_DEFAULT,
10
10
  } from '../utils/mediacapabilities-helper';
11
+ import { isHEVC } from '../utils/mp4-tools';
11
12
  import {
12
13
  type AudioTracksByGroup,
13
14
  type CodecSetTier,
@@ -15,6 +16,7 @@ import {
15
16
  getCodecTiers,
16
17
  getStartCodecTier,
17
18
  } from '../utils/rendition-helper';
19
+ import { stringify } from '../utils/safe-json-stringify';
18
20
  import type Hls from '../hls';
19
21
  import type { Fragment } from '../loader/fragment';
20
22
  import type { Part } from '../loader/fragment';
@@ -764,7 +766,7 @@ class AbrController extends Logger implements AbrComponentAPI {
764
766
  : videoRanges[0];
765
767
  currentFrameRate = minFramerate;
766
768
  currentBw = Math.max(currentBw, minBitrate);
767
- this.log(`picked start tier ${JSON.stringify(startTier)}`);
769
+ this.log(`picked start tier ${stringify(startTier)}`);
768
770
  } else {
769
771
  currentCodecSet = level?.codecSet;
770
772
  currentVideoRange = level?.videoRange;
@@ -803,7 +805,7 @@ class AbrController extends Logger implements AbrComponentAPI {
803
805
  currentBw,
804
806
  audioPreference,
805
807
  ) ||
806
- levelInfo.videoCodec?.substring(0, 4) === 'hvc1') // Force media capabilities check for HEVC to avoid failure on Windows
808
+ isHEVC(levelInfo.videoCodec)) // Force media capabilities check for HEVC to avoid failure on Windows
807
809
  ) {
808
810
  levelInfo.supportedPromise = getMediaDecodingInfoPromise(
809
811
  levelInfo,
@@ -821,11 +823,11 @@ class AbrController extends Logger implements AbrComponentAPI {
821
823
  this.warn(
822
824
  `MediaCapabilities decodingInfo error: "${
823
825
  decodingInfo.error
824
- }" for level ${index} ${JSON.stringify(decodingInfo)}`,
826
+ }" for level ${index} ${stringify(decodingInfo)}`,
825
827
  );
826
828
  } else if (!decodingInfo.supported) {
827
829
  this.warn(
828
- `Unsupported MediaCapabilities decodingInfo result for level ${index} ${JSON.stringify(
830
+ `Unsupported MediaCapabilities decodingInfo result for level ${index} ${stringify(
829
831
  decodingInfo,
830
832
  )}`,
831
833
  );
@@ -797,13 +797,15 @@ class AudioStreamController
797
797
  this.state = State.IDLE;
798
798
  }
799
799
  break;
800
+ case ErrorDetails.BUFFER_ADD_CODEC_ERROR:
800
801
  case ErrorDetails.BUFFER_APPEND_ERROR:
801
- case ErrorDetails.BUFFER_FULL_ERROR:
802
- if (!data.parent || data.parent !== 'audio') {
802
+ if (data.parent !== 'audio') {
803
803
  return;
804
804
  }
805
- if (data.details === ErrorDetails.BUFFER_APPEND_ERROR) {
806
- this.resetLoadingState();
805
+ this.resetLoadingState();
806
+ break;
807
+ case ErrorDetails.BUFFER_FULL_ERROR:
808
+ if (data.parent !== 'audio') {
807
809
  return;
808
810
  }
809
811
  if (this.reduceLengthAndFlushBuffer(data)) {
@@ -954,7 +956,7 @@ class AudioStreamController
954
956
  }
955
957
  const track = tracks.audio;
956
958
 
957
- track.id = 'audio';
959
+ track.id = PlaylistLevelType.AUDIO;
958
960
 
959
961
  const variantAudioCodecs = currentLevel.audioCodec;
960
962
  this.log(
@@ -15,6 +15,7 @@ import {
15
15
  isCompatibleTrackChange,
16
16
  isManagedMediaSource,
17
17
  } from '../utils/mediasource-helper';
18
+ import { stringify } from '../utils/safe-json-stringify';
18
19
  import type { FragmentTracker } from './fragment-tracker';
19
20
  import type { HlsConfig } from '../config';
20
21
  import type Hls from '../hls';
@@ -353,8 +354,8 @@ export default class BufferController extends Logger implements ComponentAPI {
353
354
  }
354
355
  this
355
356
  .log(`attachTransferred: (bufferCodecEventsTotal ${this.bufferCodecEventsTotal})
356
- required tracks: ${JSON.stringify(requiredTracks, (key, value) => (key === 'initSegment' ? undefined : value))};
357
- transfer tracks: ${JSON.stringify(transferredTracks, (key, value) => (key === 'initSegment' ? undefined : value))}}`);
357
+ required tracks: ${stringify(requiredTracks, (key, value) => (key === 'initSegment' ? undefined : value))};
358
+ transfer tracks: ${stringify(transferredTracks, (key, value) => (key === 'initSegment' ? undefined : value))}}`);
358
359
  if (!isCompatibleTrackChange(transferredTracks, requiredTracks)) {
359
360
  // destroy attaching media source
360
361
  data.mediaSource = null;
@@ -1322,7 +1323,7 @@ transfer tracks: ${JSON.stringify(transferredTracks, (key, value) => (key === 'i
1322
1323
  private checkPendingTracks() {
1323
1324
  const { bufferCodecEventsTotal, pendingTrackCount, tracks } = this;
1324
1325
  this.log(
1325
- `checkPendingTracks (pending: ${pendingTrackCount} codec events expected: ${bufferCodecEventsTotal}) ${JSON.stringify(tracks)}`,
1326
+ `checkPendingTracks (pending: ${pendingTrackCount} codec events expected: ${bufferCodecEventsTotal}) ${stringify(tracks)}`,
1326
1327
  );
1327
1328
  // Check if we've received all of the expected bufferCodec events. When none remain, create all the sourceBuffers at once.
1328
1329
  // This is important because the MSE spec allows implementations to throw QuotaExceededErrors if creating new sourceBuffers after
@@ -1335,7 +1336,6 @@ transfer tracks: ${JSON.stringify(transferredTracks, (key, value) => (key === 'i
1335
1336
  } else {
1336
1337
  // ok, let's create them now !
1337
1338
  this.createSourceBuffers();
1338
- this.bufferCreated();
1339
1339
  }
1340
1340
  }
1341
1341
  }
@@ -1391,7 +1391,7 @@ transfer tracks: ${JSON.stringify(transferredTracks, (key, value) => (key === 'i
1391
1391
  const mimeType = `${track.container};codecs=${codec}`;
1392
1392
  track.codec = codec;
1393
1393
  this.log(
1394
- `creating sourceBuffer(${mimeType})${this.currentOp(type) ? ' Queued' : ''} ${JSON.stringify(track)}`,
1394
+ `creating sourceBuffer(${mimeType})${this.currentOp(type) ? ' Queued' : ''} ${stringify(track)}`,
1395
1395
  );
1396
1396
  try {
1397
1397
  const sb = mediaSource.addSourceBuffer(
@@ -1408,6 +1408,10 @@ transfer tracks: ${JSON.stringify(transferredTracks, (key, value) => (key === 'i
1408
1408
  this.error(
1409
1409
  `error while trying to add sourceBuffer: ${error.message}`,
1410
1410
  );
1411
+ // remove init segment from queue and delete track info
1412
+ this.shiftAndExecuteNext(type);
1413
+ this.operationQueue?.removeBlockers();
1414
+ delete this.tracks[type];
1411
1415
  this.hls.trigger(Events.ERROR, {
1412
1416
  type: ErrorTypes.MEDIA_ERROR,
1413
1417
  details: ErrorDetails.BUFFER_ADD_CODEC_ERROR,
@@ -1415,12 +1419,14 @@ transfer tracks: ${JSON.stringify(transferredTracks, (key, value) => (key === 'i
1415
1419
  error,
1416
1420
  sourceBufferName: type,
1417
1421
  mimeType: mimeType,
1422
+ parent: track.id as PlaylistLevelType,
1418
1423
  });
1419
- break;
1424
+ return;
1420
1425
  }
1421
1426
  this.trackSourceBuffer(type, track);
1422
1427
  }
1423
1428
  }
1429
+ this.bufferCreated();
1424
1430
  }
1425
1431
 
1426
1432
  private getTrackCodec(track: BaseTrack, trackName: SourceBufferName): string {
@@ -1449,7 +1455,7 @@ transfer tracks: ${JSON.stringify(transferredTracks, (key, value) => (key === 'i
1449
1455
  id: track.id,
1450
1456
  listeners: [],
1451
1457
  };
1452
-
1458
+ this.removeBufferListeners(type);
1453
1459
  this.addBufferListener(type, 'updatestart', this.onSBUpdateStart);
1454
1460
  this.addBufferListener(type, 'updateend', this.onSBUpdateEnd);
1455
1461
  this.addBufferListener(type, 'error', this.onSBUpdateError);
@@ -13,6 +13,7 @@ import {
13
13
  import { AttrList } from '../utils/attr-list';
14
14
  import { reassignFragmentLevelIndexes } from '../utils/level-helper';
15
15
  import { Logger } from '../utils/logger';
16
+ import { stringify } from '../utils/safe-json-stringify';
16
17
  import type { RetryConfig } from '../config';
17
18
  import type Hls from '../hls';
18
19
  import type { NetworkComponentAPI } from '../types/component-api';
@@ -221,9 +222,9 @@ export default class ContentSteeringController
221
222
  data.error.message
222
223
  }") with content-steering for Pathway: ${errorPathway} levels: ${
223
224
  levels ? levels.length : levels
224
- } priorities: ${JSON.stringify(
225
+ } priorities: ${stringify(
225
226
  pathwayPriority,
226
- )} penalized: ${JSON.stringify(this.penalizedPathways)}`,
227
+ )} penalized: ${stringify(this.penalizedPathways)}`,
227
228
  );
228
229
  }
229
230
  }
@@ -27,6 +27,7 @@ import {
27
27
  type PsshInvalidResult,
28
28
  } from '../utils/mp4-tools';
29
29
  import { base64Decode } from '../utils/numeric-encoding-utils';
30
+ import { stringify } from '../utils/safe-json-stringify';
30
31
  import { strToUtf8array } from '../utils/utf8-utils';
31
32
  import type { EMEControllerConfig, HlsConfig, LoadPolicy } from '../config';
32
33
  import type Hls from '../hls';
@@ -254,7 +255,7 @@ class EMEController extends Logger implements ComponentAPI {
254
255
  let keySystemAccess = keySystemAccessPromises?.keySystemAccess;
255
256
  if (!keySystemAccess) {
256
257
  this.log(
257
- `Requesting encrypted media "${keySystem}" key-system access with config: ${JSON.stringify(
258
+ `Requesting encrypted media "${keySystem}" key-system access with config: ${stringify(
258
259
  mediaKeySystemConfigs,
259
260
  )}`,
260
261
  );
@@ -519,7 +520,7 @@ class EMEController extends Logger implements ComponentAPI {
519
520
  details: ErrorDetails.KEY_SYSTEM_NO_CONFIGURED_LICENSE,
520
521
  fatal: true,
521
522
  },
522
- `Missing key-system license configuration options ${JSON.stringify({
523
+ `Missing key-system license configuration options ${stringify({
523
524
  drmSystems: this.config.drmSystems,
524
525
  })}`,
525
526
  );
@@ -3,6 +3,7 @@ import { ErrorDetails, ErrorTypes } from '../errors';
3
3
  import { Events } from '../events';
4
4
  import { HdcpLevels } from '../types/level';
5
5
  import { PlaylistContextType, PlaylistLevelType } from '../types/loader';
6
+ import { getCodecsForMimeType } from '../utils/codecs';
6
7
  import {
7
8
  getRetryConfig,
8
9
  isTimeoutError,
@@ -506,6 +507,19 @@ export default class ErrorController
506
507
  data.errorAction.resolved = true;
507
508
  // Stream controller is responsible for this but won't switch on false start
508
509
  this.hls.nextLoadLevel = this.hls.nextAutoLevel;
510
+ if (
511
+ data.details === ErrorDetails.BUFFER_ADD_CODEC_ERROR &&
512
+ data.mimeType &&
513
+ data.sourceBufferName !== 'audiovideo'
514
+ ) {
515
+ const codec = getCodecsForMimeType(data.mimeType);
516
+ const levels = this.hls.levels;
517
+ for (let i = levels.length; i--; ) {
518
+ if (levels[i][`${data.sourceBufferName}Codec`] === codec) {
519
+ this.hls.removeLevel(i);
520
+ }
521
+ }
522
+ }
509
523
  }
510
524
  }
511
525
  }
@@ -8,6 +8,7 @@ import {
8
8
  addEventListener,
9
9
  removeEventListener,
10
10
  } from '../utils/event-listener-helper';
11
+ import { stringify } from '../utils/safe-json-stringify';
11
12
  import type { InFlightData } from './base-stream-controller';
12
13
  import type { InFlightFragments } from '../hls';
13
14
  import type Hls from '../hls';
@@ -486,7 +487,7 @@ export default class GapController extends TaskLoop {
486
487
  const error = new Error(
487
488
  `Playback stalling at @${
488
489
  media.currentTime
489
- } due to low buffer (${JSON.stringify(bufferInfo)})`,
490
+ } due to low buffer (${stringify(bufferInfo)})`,
490
491
  );
491
492
  this.warn(error.message);
492
493
  hls.trigger(Events.ERROR, {
@@ -6,6 +6,7 @@ import {
6
6
  isSCTE35Attribute,
7
7
  } from '../loader/date-range';
8
8
  import { MetadataSchema } from '../types/demuxer';
9
+ import { stringify } from '../utils/safe-json-stringify';
9
10
  import {
10
11
  clearCurrentCues,
11
12
  removeCuesInRange,
@@ -54,7 +55,7 @@ function createCueWithDataFields(
54
55
  cue = new Cue(
55
56
  startTime,
56
57
  endTime,
57
- JSON.stringify(type ? { type, ...data } : data),
58
+ stringify(type ? { type, ...data } : data),
58
59
  );
59
60
  }
60
61
  return cue;
@@ -12,6 +12,7 @@ import {
12
12
  videoCodecPreferenceValue,
13
13
  } from '../utils/codecs';
14
14
  import { reassignFragmentLevelIndexes } from '../utils/level-helper';
15
+ import { stringify } from '../utils/safe-json-stringify';
15
16
  import type ContentSteeringController from './content-steering-controller';
16
17
  import type Hls from '../hls';
17
18
  import type {
@@ -248,7 +249,7 @@ export default class LevelController extends BasePlaylistController {
248
249
  if (this.hls) {
249
250
  if (data.levels.length) {
250
251
  this.warn(
251
- `One or more CODECS in variant not supported: ${JSON.stringify(
252
+ `One or more CODECS in variant not supported: ${stringify(
252
253
  data.levels[0].attrs,
253
254
  )}`,
254
255
  );
@@ -1015,13 +1015,15 @@ export default class StreamController
1015
1015
  this.state = State.IDLE;
1016
1016
  }
1017
1017
  break;
1018
+ case ErrorDetails.BUFFER_ADD_CODEC_ERROR:
1018
1019
  case ErrorDetails.BUFFER_APPEND_ERROR:
1019
- case ErrorDetails.BUFFER_FULL_ERROR:
1020
- if (!data.parent || data.parent !== 'main') {
1020
+ if (data.parent !== 'main') {
1021
1021
  return;
1022
1022
  }
1023
- if (data.details === ErrorDetails.BUFFER_APPEND_ERROR) {
1024
- this.resetLoadingState();
1023
+ this.resetLoadingState();
1024
+ break;
1025
+ case ErrorDetails.BUFFER_FULL_ERROR:
1026
+ if (data.parent !== 'main') {
1025
1027
  return;
1026
1028
  }
1027
1029
  if (this.reduceLengthAndFlushBuffer(data)) {
@@ -1416,7 +1418,7 @@ export default class StreamController
1416
1418
  );
1417
1419
  }
1418
1420
  audio.levelCodec = audioCodec;
1419
- audio.id = 'main';
1421
+ audio.id = PlaylistLevelType.MAIN;
1420
1422
  this.log(
1421
1423
  `Init audio buffer, container:${
1422
1424
  audio.container
@@ -1428,7 +1430,7 @@ export default class StreamController
1428
1430
  }
1429
1431
  if (video) {
1430
1432
  video.levelCodec = currentLevel.videoCodec;
1431
- video.id = 'main';
1433
+ video.id = PlaylistLevelType.MAIN;
1432
1434
  const parsedVideoCodec = video.codec;
1433
1435
  if (parsedVideoCodec?.length === 4) {
1434
1436
  // Make up for passthrough-remuxer not being able to parse full codec
@@ -14,6 +14,7 @@ import { ErrorDetails, ErrorTypes } from '../errors';
14
14
  import { Events } from '../events';
15
15
  import { PlaylistLevelType } from '../types/loader';
16
16
  import { getM2TSSupportedAudioTypes } from '../utils/codecs';
17
+ import { stringify } from '../utils/safe-json-stringify';
17
18
  import type { WorkerContext } from './inject-worker';
18
19
  import type { HlsEventEmitter, HlsListeners } from '../events';
19
20
  import type Hls from '../hls';
@@ -95,7 +96,7 @@ export default class TransmuxerInterface {
95
96
  cmd: 'init',
96
97
  typeSupported: m2tsTypeSupported,
97
98
  id,
98
- config: JSON.stringify(config),
99
+ config: stringify(config),
99
100
  });
100
101
  } catch (err) {
101
102
  logger.warn(
@@ -143,7 +144,7 @@ export default class TransmuxerInterface {
143
144
  resetNo: instanceNo,
144
145
  typeSupported: m2tsTypeSupported,
145
146
  id: this.id,
146
- config: JSON.stringify(config),
147
+ config: stringify(config),
147
148
  });
148
149
  }
149
150
  }
@@ -1,3 +1,4 @@
1
+ import { stringify } from './safe-json-stringify';
1
2
  import { logger } from '../utils/logger';
2
3
  import type OutputFilter from './output-filter';
3
4
 
@@ -593,10 +594,7 @@ export class CaptionScreen {
593
594
  }
594
595
 
595
596
  setPAC(pacData: PACData) {
596
- this.logger.log(
597
- VerboseLevel.INFO,
598
- () => 'pacData = ' + JSON.stringify(pacData),
599
- );
597
+ this.logger.log(VerboseLevel.INFO, () => 'pacData = ' + stringify(pacData));
600
598
  let newRow = pacData.row - 1;
601
599
  if (this.nrRollUpRows && newRow < this.nrRollUpRows - 1) {
602
600
  newRow = this.nrRollUpRows - 1;
@@ -650,10 +648,7 @@ export class CaptionScreen {
650
648
  * Set background/extra foreground, but first do back_space, and then insert space (backwards compatibility).
651
649
  */
652
650
  setBkgData(bkgData: Partial<PenStyles>) {
653
- this.logger.log(
654
- VerboseLevel.INFO,
655
- () => 'bkgData = ' + JSON.stringify(bkgData),
656
- );
651
+ this.logger.log(VerboseLevel.INFO, () => 'bkgData = ' + stringify(bkgData));
657
652
  this.backSpace();
658
653
  this.setPen(bkgData);
659
654
  this.insertChar(0x20); // Space
@@ -951,7 +946,7 @@ class Cea608Channel {
951
946
  } else {
952
947
  styles.foreground = 'white';
953
948
  }
954
- this.logger.log(VerboseLevel.INFO, 'MIDROW: ' + JSON.stringify(styles));
949
+ this.logger.log(VerboseLevel.INFO, 'MIDROW: ' + stringify(styles));
955
950
  this.writeScreen.setPen(styles);
956
951
  }
957
952
 
@@ -1,4 +1,9 @@
1
1
  import { getMediaSource } from './mediasource-helper';
2
+ import { isHEVC } from './mp4-tools';
3
+
4
+ export const userAgentHevcSupportIsInaccurate = () => {
5
+ return /\(Windows.+Firefox\//i.test(navigator.userAgent);
6
+ };
2
7
 
3
8
  // from http://mp4ra.org/codecs.html
4
9
  // values indicate codec selection preference (lower is higher priority)
@@ -121,8 +126,12 @@ export function videoCodecPreferenceValue(
121
126
  }
122
127
 
123
128
  export function codecsSetSelectionPreferenceValue(codecSet: string): number {
129
+ const limitedHevcSupport = userAgentHevcSupportIsInaccurate();
124
130
  return codecSet.split(',').reduce((num, fourCC) => {
125
- const preferenceValue = sampleEntryCodesISO.video[fourCC];
131
+ const lowerPriority = limitedHevcSupport && isHEVC(fourCC);
132
+ const preferenceValue = lowerPriority
133
+ ? 9
134
+ : sampleEntryCodesISO.video[fourCC];
126
135
  if (preferenceValue) {
127
136
  return (preferenceValue * 2 + num) / (num ? 3 : 2);
128
137
  }
@@ -272,3 +281,7 @@ export function getM2TSSupportedAudioTypes(
272
281
  : false,
273
282
  };
274
283
  }
284
+
285
+ export function getCodecsForMimeType(mimeType: string): string {
286
+ return mimeType.replace(/^.+codecs=["']?([^"']+).*$/, '$1');
287
+ }
@@ -3,6 +3,7 @@
3
3
  */
4
4
 
5
5
  import { logger } from './logger';
6
+ import { stringify } from './safe-json-stringify';
6
7
  import { DateRange } from '../loader/date-range';
7
8
  import { assignProgramDateTime, mapDateRanges } from '../loader/m3u8-parser';
8
9
  import type { Fragment, MediaFragment, Part } from '../loader/fragment';
@@ -344,7 +345,7 @@ function mergeDateRanges(
344
345
  }
345
346
  } else {
346
347
  logger.warn(
347
- `Ignoring invalid Playlist Delta Update DATERANGE tag: "${JSON.stringify(
348
+ `Ignoring invalid Playlist Delta Update DATERANGE tag: "${stringify(
348
349
  deltaDateRanges[id].attr,
349
350
  )}"`,
350
351
  );
@@ -1,4 +1,10 @@
1
- import { fillInMissingAV01Params, mimeTypeForCodec } from './codecs';
1
+ import {
2
+ fillInMissingAV01Params,
3
+ getCodecsForMimeType,
4
+ mimeTypeForCodec,
5
+ userAgentHevcSupportIsInaccurate,
6
+ } from './codecs';
7
+ import { isHEVC } from './mp4-tools';
2
8
  import type { AudioTracksByGroup } from './rendition-helper';
3
9
  import type { Level, VideoRange } from '../types/level';
4
10
  import type { AudioSelectionOption } from '../types/media-playlist';
@@ -115,10 +121,31 @@ export function getMediaDecodingInfoPromise(
115
121
  baseVideoConfiguration.transferFunction =
116
122
  videoRange.toLowerCase() as TransferFunction;
117
123
  }
118
-
124
+ const videoCodecsArray = videoCodecs.split(',');
125
+ // Override Windows Firefox HEVC MediaCapabilities result (https://github.com/video-dev/hls.js/issues/7046)
126
+ const ua = navigator.userAgent;
127
+ if (
128
+ videoCodecsArray.some((videoCodec) => isHEVC(videoCodec)) &&
129
+ userAgentHevcSupportIsInaccurate()
130
+ ) {
131
+ return Promise.resolve({
132
+ supported: false,
133
+ configurations,
134
+ decodingInfoResults: [
135
+ {
136
+ supported: false,
137
+ smooth: false,
138
+ powerEfficient: false,
139
+ },
140
+ ],
141
+ error: new Error(
142
+ `Overriding Windows Firefox HEVC MediaCapabilities result based on user-agent sting: (${ua})`,
143
+ ),
144
+ });
145
+ }
119
146
  configurations.push.apply(
120
147
  configurations,
121
- videoCodecs.split(',').map((videoCodec) => ({
148
+ videoCodecsArray.map((videoCodec) => ({
122
149
  type: 'media-source',
123
150
  video: {
124
151
  ...baseVideoConfiguration,
@@ -187,7 +214,7 @@ function getMediaDecodingInfoKey(config: MediaDecodingConfiguration): string {
187
214
  const { audio, video } = config;
188
215
  const mediaConfig = video || audio;
189
216
  if (mediaConfig) {
190
- const codec = mediaConfig.contentType.split('"')[1];
217
+ const codec = getCodecsForMimeType(mediaConfig.contentType);
191
218
  if (video) {
192
219
  return `r${video.height}x${video.width}f${Math.ceil(video.framerate)}${
193
220
  video.transferFunction || 'sd'
@@ -1012,12 +1012,11 @@ export function parseSamples(
1012
1012
  return seiSamples;
1013
1013
  }
1014
1014
 
1015
- function isHEVC(codec: string) {
1015
+ export function isHEVC(codec: string | undefined) {
1016
1016
  if (!codec) {
1017
1017
  return false;
1018
1018
  }
1019
- const delimit = codec.indexOf('.');
1020
- const baseCodec = delimit < 0 ? codec : codec.substring(0, delimit);
1019
+ const baseCodec = codec.substring(0, 4);
1021
1020
  return (
1022
1021
  baseCodec === 'hvc1' ||
1023
1022
  baseCodec === 'hev1' ||
@@ -1,6 +1,7 @@
1
1
  import { codecsSetSelectionPreferenceValue } from './codecs';
2
2
  import { getVideoSelectionOptions } from './hdr';
3
3
  import { logger } from './logger';
4
+ import { stringify } from './safe-json-stringify';
4
5
  import type Hls from '../hls';
5
6
  import type { Level, VideoRange } from '../types/level';
6
7
  import type {
@@ -165,9 +166,7 @@ export function getStartCodecTier(
165
166
  ) {
166
167
  logStartCodecCandidateIgnored(
167
168
  candidate,
168
- `no variants with VIDEO-RANGE of ${JSON.stringify(
169
- videoRanges,
170
- )} found`,
169
+ `no variants with VIDEO-RANGE of ${stringify(videoRanges)} found`,
171
170
  );
172
171
  return selected;
173
172
  }
@@ -1,6 +1,11 @@
1
- const replacer = () => {
1
+ const omitCircularRefsReplacer = (
2
+ replacer: ((this: any, key: string, value: any) => any) | undefined,
3
+ ) => {
2
4
  const known = new WeakSet();
3
5
  return (_, value) => {
6
+ if (replacer) {
7
+ value = replacer(_, value);
8
+ }
4
9
  if (typeof value === 'object' && value !== null) {
5
10
  if (known.has(value)) {
6
11
  return;
@@ -11,5 +16,7 @@ const replacer = () => {
11
16
  };
12
17
  };
13
18
 
14
- export const stringify = <T>(object: T): string =>
15
- JSON.stringify(object, replacer());
19
+ export const stringify = <T>(
20
+ object: T,
21
+ replacer?: (this: any, key: string, value: any) => any,
22
+ ): string => JSON.stringify(object, omitCircularRefsReplacer(replacer));