rx-player 4.2.0-dev.2024090600 → 4.2.0-dev.2024091600

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.
Files changed (89) hide show
  1. package/CHANGELOG.md +14 -7
  2. package/VERSION +1 -1
  3. package/dist/commonjs/__GENERATED_CODE/embedded_dash_wasm.d.ts.map +1 -1
  4. package/dist/commonjs/__GENERATED_CODE/embedded_dash_wasm.js +1 -1
  5. package/dist/commonjs/__GENERATED_CODE/embedded_worker.d.ts.map +1 -1
  6. package/dist/commonjs/__GENERATED_CODE/embedded_worker.js +1 -1
  7. package/dist/commonjs/core/cmcd/cmcd_data_builder.d.ts +12 -0
  8. package/dist/commonjs/core/cmcd/cmcd_data_builder.d.ts.map +1 -1
  9. package/dist/commonjs/core/cmcd/cmcd_data_builder.js +27 -1
  10. package/dist/commonjs/core/fetchers/segment/segment_fetcher.d.ts +24 -0
  11. package/dist/commonjs/core/fetchers/segment/segment_fetcher.d.ts.map +1 -1
  12. package/dist/commonjs/core/fetchers/segment/segment_queue.d.ts.map +1 -1
  13. package/dist/commonjs/core/fetchers/segment/segment_queue.js +8 -7
  14. package/dist/commonjs/experimental/tools/VideoThumbnailLoader/video_thumbnail_loader.d.ts.map +1 -1
  15. package/dist/commonjs/experimental/tools/VideoThumbnailLoader/video_thumbnail_loader.js +2 -2
  16. package/dist/commonjs/main_thread/api/public_api.js +2 -2
  17. package/dist/commonjs/parsers/manifest/dash/common/parse_mpd.js +2 -2
  18. package/dist/commonjs/parsers/manifest/dash/common/resolve_base_urls.js +2 -2
  19. package/dist/commonjs/parsers/manifest/dash/wasm-parser/ts/generators/AdaptationSet.d.ts.map +1 -1
  20. package/dist/commonjs/parsers/manifest/dash/wasm-parser/ts/generators/AdaptationSet.js +6 -5
  21. package/dist/commonjs/parsers/manifest/dash/wasm-parser/ts/generators/Label.d.ts +4 -0
  22. package/dist/commonjs/parsers/manifest/dash/wasm-parser/ts/generators/Label.d.ts.map +1 -0
  23. package/dist/commonjs/parsers/manifest/dash/wasm-parser/ts/generators/Label.js +12 -0
  24. package/dist/commonjs/parsers/manifest/dash/wasm-parser/ts/types.d.ts +2 -1
  25. package/dist/commonjs/parsers/manifest/dash/wasm-parser/ts/types.d.ts.map +1 -1
  26. package/dist/commonjs/parsers/manifest/metaplaylist/metaplaylist_parser.js +2 -2
  27. package/dist/commonjs/parsers/manifest/smooth/create_parser.js +2 -2
  28. package/dist/commonjs/transports/dash/construct_segment_url.js +2 -2
  29. package/dist/commonjs/transports/smooth/utils.js +2 -2
  30. package/dist/commonjs/utils/{resolve_url.d.ts → url-utils.d.ts} +15 -4
  31. package/dist/commonjs/utils/url-utils.d.ts.map +1 -0
  32. package/dist/commonjs/utils/{resolve_url.js → url-utils.js} +71 -2
  33. package/dist/es2017/__GENERATED_CODE/embedded_dash_wasm.d.ts.map +1 -1
  34. package/dist/es2017/__GENERATED_CODE/embedded_dash_wasm.js +1 -1
  35. package/dist/es2017/__GENERATED_CODE/embedded_worker.d.ts.map +1 -1
  36. package/dist/es2017/__GENERATED_CODE/embedded_worker.js +1 -1
  37. package/dist/es2017/core/cmcd/cmcd_data_builder.d.ts +12 -0
  38. package/dist/es2017/core/cmcd/cmcd_data_builder.d.ts.map +1 -1
  39. package/dist/es2017/core/cmcd/cmcd_data_builder.js +27 -1
  40. package/dist/es2017/core/fetchers/segment/segment_fetcher.d.ts +24 -0
  41. package/dist/es2017/core/fetchers/segment/segment_fetcher.d.ts.map +1 -1
  42. package/dist/es2017/core/fetchers/segment/segment_queue.d.ts.map +1 -1
  43. package/dist/es2017/core/fetchers/segment/segment_queue.js +8 -7
  44. package/dist/es2017/experimental/tools/VideoThumbnailLoader/video_thumbnail_loader.d.ts.map +1 -1
  45. package/dist/es2017/experimental/tools/VideoThumbnailLoader/video_thumbnail_loader.js +2 -2
  46. package/dist/es2017/main_thread/api/public_api.js +2 -2
  47. package/dist/es2017/parsers/manifest/dash/common/parse_mpd.js +1 -1
  48. package/dist/es2017/parsers/manifest/dash/common/resolve_base_urls.js +1 -1
  49. package/dist/es2017/parsers/manifest/dash/wasm-parser/ts/generators/AdaptationSet.d.ts.map +1 -1
  50. package/dist/es2017/parsers/manifest/dash/wasm-parser/ts/generators/AdaptationSet.js +6 -5
  51. package/dist/es2017/parsers/manifest/dash/wasm-parser/ts/generators/Label.d.ts +4 -0
  52. package/dist/es2017/parsers/manifest/dash/wasm-parser/ts/generators/Label.d.ts.map +1 -0
  53. package/dist/es2017/parsers/manifest/dash/wasm-parser/ts/generators/Label.js +9 -0
  54. package/dist/es2017/parsers/manifest/dash/wasm-parser/ts/types.d.ts +2 -1
  55. package/dist/es2017/parsers/manifest/dash/wasm-parser/ts/types.d.ts.map +1 -1
  56. package/dist/es2017/parsers/manifest/metaplaylist/metaplaylist_parser.js +1 -1
  57. package/dist/es2017/parsers/manifest/smooth/create_parser.js +1 -1
  58. package/dist/es2017/transports/dash/construct_segment_url.js +1 -1
  59. package/dist/es2017/transports/smooth/utils.js +1 -1
  60. package/dist/es2017/utils/{resolve_url.d.ts → url-utils.d.ts} +15 -4
  61. package/dist/es2017/utils/url-utils.d.ts.map +1 -0
  62. package/dist/es2017/utils/{resolve_url.js → url-utils.js} +71 -3
  63. package/dist/mpd-parser.wasm +0 -0
  64. package/dist/rx-player.js +245 -175
  65. package/dist/rx-player.min.js +15 -15
  66. package/dist/worker.js +8 -8
  67. package/package.json +5 -5
  68. package/src/__GENERATED_CODE/embedded_dash_wasm.ts +1 -1
  69. package/src/__GENERATED_CODE/embedded_worker.ts +1 -1
  70. package/src/core/cmcd/cmcd_data_builder.ts +68 -2
  71. package/src/core/fetchers/segment/segment_fetcher.ts +24 -0
  72. package/src/core/fetchers/segment/segment_queue.ts +11 -9
  73. package/src/experimental/tools/VideoThumbnailLoader/video_thumbnail_loader.ts +8 -2
  74. package/src/main_thread/api/public_api.ts +2 -2
  75. package/src/parsers/manifest/dash/common/parse_mpd.ts +1 -1
  76. package/src/parsers/manifest/dash/common/resolve_base_urls.ts +1 -1
  77. package/src/parsers/manifest/dash/wasm-parser/rs/events.rs +3 -2
  78. package/src/parsers/manifest/dash/wasm-parser/rs/processor/mod.rs +6 -2
  79. package/src/parsers/manifest/dash/wasm-parser/ts/generators/AdaptationSet.ts +10 -5
  80. package/src/parsers/manifest/dash/wasm-parser/ts/generators/Label.ts +16 -0
  81. package/src/parsers/manifest/dash/wasm-parser/ts/types.ts +3 -0
  82. package/src/parsers/manifest/metaplaylist/metaplaylist_parser.ts +1 -1
  83. package/src/parsers/manifest/smooth/create_parser.ts +1 -1
  84. package/src/transports/dash/construct_segment_url.ts +1 -1
  85. package/src/transports/smooth/utils.ts +1 -1
  86. package/src/utils/__tests__/{resolve_url.test.ts → url-utils.test.ts} +148 -1
  87. package/src/utils/{resolve_url.ts → url-utils.ts} +83 -5
  88. package/dist/commonjs/utils/resolve_url.d.ts.map +0 -1
  89. package/dist/es2017/utils/resolve_url.d.ts.map +0 -1
@@ -16,6 +16,7 @@ import createUuid from "../../utils/create_uuid";
16
16
  import isNullOrUndefined from "../../utils/is_null_or_undefined";
17
17
  import type { IRange } from "../../utils/ranges";
18
18
  import TaskCanceller from "../../utils/task_canceller";
19
+ import { getRelativeUrl } from "../../utils/url-utils";
19
20
 
20
21
  /**
21
22
  * `rtp`, for "REQUESTED_MAXIMUM_THROUGHPUT", indicates the maximum throughput
@@ -44,6 +45,18 @@ export interface ICmcdSegmentInfo {
44
45
  representation: IRepresentation;
45
46
  /** Segment metadata linked to the wanted segment. */
46
47
  segment: ISegment;
48
+ /**
49
+ * Optional next segment that may be requested after this one.
50
+ * Should only be set (to something else than `undefined`) if that following
51
+ * segment is part of the same `Representation`.
52
+ *
53
+ * This information is used to produce the "next object request" and "next
54
+ * range request" part of the CMCD payload, used for segment prefetching.
55
+ *
56
+ * If `null` no segment will be requested next for now.
57
+ * If `undefined` we do not know which next segment will be requested.
58
+ */
59
+ nextSegment: ISegment | null | undefined;
47
60
  }
48
61
 
49
62
  /**
@@ -221,7 +234,6 @@ export default class CmcdDataBuilder {
221
234
  const props = this._getCommonCmcdData(this._lastThroughput[content.adaptation.type]);
222
235
  props.br = Math.round(content.representation.bitrate / 1000);
223
236
  props.d = Math.round(content.segment.duration * 1000);
224
- // TODO nor (next object request) and nrr (next range request)
225
237
 
226
238
  switch (content.adaptation.type) {
227
239
  case "video":
@@ -238,6 +250,33 @@ export default class CmcdDataBuilder {
238
250
  props.ot = "i";
239
251
  }
240
252
 
253
+ if (
254
+ !isNullOrUndefined(content.nextSegment) &&
255
+ content.segment.url !== null &&
256
+ content.nextSegment.url !== null
257
+ ) {
258
+ // We add a special case for some initialization segment which need
259
+ // multiple byte-ranges to fully request, as the `CmcdDataBuilder`
260
+ // is not supposed to keep track of how the requesting part of the
261
+ // RxPlayer actually perform its multi-byte-range requests
262
+ if (!content.nextSegment.isInit || content.nextSegment.indexRange === undefined) {
263
+ const currSegmentUrl = content.segment.url;
264
+ const nextSegmentUrl = content.nextSegment.url;
265
+ const relativeUrl = getRelativeUrl(currSegmentUrl, nextSegmentUrl);
266
+ if (relativeUrl !== null) {
267
+ if (relativeUrl !== ".") {
268
+ props.nor = encodeURIComponent(relativeUrl);
269
+ }
270
+ if (content.nextSegment.range !== undefined) {
271
+ props.nrr = String(content.nextSegment.range[0]) + "-";
272
+ if (isFinite(content.nextSegment.range[1])) {
273
+ props.nrr += String(content.nextSegment.range[1]);
274
+ }
275
+ }
276
+ }
277
+ }
278
+ }
279
+
241
280
  let precizeBufferLengthMs;
242
281
  if (
243
282
  lastObservation !== undefined &&
@@ -355,7 +394,7 @@ export default class CmcdDataBuilder {
355
394
  };
356
395
 
357
396
  const addStringProperty = (
358
- prop: "cid" | "sid",
397
+ prop: "cid" | "sid" | "nor" | "nrr",
359
398
  headerName: keyof typeof headers,
360
399
  ): void => {
361
400
  const val = props[prop];
@@ -384,6 +423,8 @@ export default class CmcdDataBuilder {
384
423
  addNumberProperty("d", "object");
385
424
  addNumberProperty("dl", "request");
386
425
  addNumberProperty("mtp", "request");
426
+ addStringProperty("nor", "request");
427
+ addStringProperty("nrr", "request");
387
428
  addTokenProperty("ot", "object");
388
429
  addNumberProperty("pr", "session");
389
430
  addNumberProperty("rtp", "status");
@@ -518,6 +559,31 @@ interface ICmcdProperties {
518
559
  * In kbps.
519
560
  */
520
561
  mtp?: number | undefined;
562
+ /**
563
+ * Next Object Request (nor)
564
+ * Relative path of the next object to be requested.
565
+ * This can be used to trigger pre-fetching by the CDN.
566
+ * This MUST be a path relative to the current request.
567
+ * This string MUST be URLEncoded.
568
+ * The client SHOULD NOT depend upon any pre-fetch action being taken - it is
569
+ * merely a request for such a pre-fetch to take place.
570
+ */
571
+ nor?: string | undefined;
572
+ /**
573
+ * Next Range Request (nrr)
574
+ * If the next request will be a partial object request, then this string
575
+ * denotes the byte range to be requested. If the ‘nor’ field is not set, then
576
+ * the object is assumed to match the object currently being requested.
577
+ * The client SHOULD NOT depend upon any pre-fetch action being taken – it is
578
+ * merely a request for such a pre-fetch to take place. Formatting is similar
579
+ * to the HTTP Range header, except that the unit MUST be ‘byte’, the ‘Range:’
580
+ * prefix is NOT required and specifying multiple ranges is NOT allowed.
581
+ * Valid combinations are:
582
+ * "<range-start>-"
583
+ * "<range-start>-<range-end>"
584
+ * "-<suffix-length>"
585
+ */
586
+ nrr?: string | undefined;
521
587
  /**
522
588
  * Object type (ot)
523
589
  * The media type of the current object being requested:
@@ -427,11 +427,35 @@ export interface ISegmentFetcherCallbacks<TSegmentDataType> {
427
427
 
428
428
  /** Content used by the segment loader as a context to load a new segment. */
429
429
  export interface ISegmentLoaderContent {
430
+ /** Manifest metadata linked to the wanted segment. */
430
431
  manifest: IManifest;
432
+ /** Period metadata linked to the wanted segment. */
431
433
  period: IPeriod;
434
+ /** Adaptation metadata linked to the wanted segment. */
432
435
  adaptation: IAdaptation;
436
+ /** Representation metadata linked to the wanted segment. */
433
437
  representation: IRepresentation;
438
+ /** Segment metadata linked to the wanted segment. */
434
439
  segment: ISegment;
440
+ /**
441
+ * Optional next segment that may be requested after this one.
442
+ * Should only be set (to something else than `undefined`) if that following
443
+ * segment is part of the same `Representation`.
444
+ *
445
+ * This is only used as an hint, finally requesting another segment after this
446
+ * one due to unexpected changes (e.g. bandwidth update, track change etc.) is
447
+ * OK.
448
+ *
449
+ * This information is then used mostly for matters related yet not required
450
+ * by requests, such as CMCD reporting. In scenarios when it's not
451
+ * straightforward to guess which segment will be requested after this one,
452
+ * this property can be ignored (set to `undefined`).
453
+ *
454
+ * If `null` no segment will be requested next for now.
455
+ *
456
+ * If `undefined` we do not know which next segment will be requested.
457
+ */
458
+ nextSegment: ISegment | null | undefined;
435
459
  }
436
460
 
437
461
  /**
@@ -255,11 +255,10 @@ export default class SegmentQueue<T> extends EventEmitter<ISegmentQueueEvent<T>>
255
255
  }
256
256
 
257
257
  const { downloadQueue, content, initSegmentInfoRef, currentCanceller } = contentInfo;
258
- const { segmentQueue } = downloadQueue.getValue();
259
- const currentNeededSegment = segmentQueue[0];
260
- const recursivelyRequestSegments = (
261
- startingSegment: IQueuedSegment | undefined,
262
- ): void => {
258
+
259
+ const recursivelyRequestSegments = (): void => {
260
+ const { segmentQueue } = downloadQueue.getValue();
261
+ const startingSegment = segmentQueue[0];
263
262
  if (currentCanceller !== null && currentCanceller.isUsed()) {
264
263
  contentInfo.mediaSegmentRequest = null;
265
264
  return;
@@ -276,7 +275,10 @@ export default class SegmentQueue<T> extends EventEmitter<ISegmentQueueEvent<T>>
276
275
  : canceller.linkToSignal(currentCanceller.signal);
277
276
 
278
277
  const { segment, priority } = startingSegment;
279
- const context = objectAssign({ segment }, content);
278
+ const context = objectAssign(
279
+ { segment, nextSegment: segmentQueue[1]?.segment },
280
+ content,
281
+ );
280
282
 
281
283
  /**
282
284
  * If `true` , the current task has either errored, finished, or was
@@ -318,7 +320,7 @@ export default class SegmentQueue<T> extends EventEmitter<ISegmentQueueEvent<T>>
318
320
  lastQueue.shift();
319
321
  }
320
322
  isComplete = true;
321
- recursivelyRequestSegments(lastQueue[0]);
323
+ recursivelyRequestSegments();
322
324
  };
323
325
 
324
326
  /** Scheduled actual segment request. */
@@ -422,7 +424,7 @@ export default class SegmentQueue<T> extends EventEmitter<ISegmentQueueEvent<T>>
422
424
 
423
425
  contentInfo.mediaSegmentRequest = { segment, priority, request, canceller };
424
426
  };
425
- recursivelyRequestSegments(currentNeededSegment);
427
+ recursivelyRequestSegments();
426
428
  }
427
429
 
428
430
  /**
@@ -449,7 +451,7 @@ export default class SegmentQueue<T> extends EventEmitter<ISegmentQueueEvent<T>>
449
451
  ? noop
450
452
  : canceller.linkToSignal(contentInfo.currentCanceller.signal);
451
453
  const { segment, priority } = queuedInitSegment;
452
- const context = objectAssign({ segment }, content);
454
+ const context = objectAssign({ segment, nextSegment: undefined }, content);
453
455
 
454
456
  /**
455
457
  * If `true` , the current task has either errored, finished, or was
@@ -207,7 +207,10 @@ export default class VideoThumbnailLoader {
207
207
  lastRepInfo.initSegmentUniqueId = null;
208
208
  return sourceBufferInterface;
209
209
  }
210
- const segmentInfo = objectAssign({ segment: initSegment }, content);
210
+ const segmentInfo = objectAssign(
211
+ { segment: initSegment, nextSegment: undefined },
212
+ content,
213
+ );
211
214
  await loadAndPushSegment(
212
215
  segmentInfo,
213
216
  sourceBufferInterface,
@@ -272,7 +275,10 @@ export default class VideoThumbnailLoader {
272
275
  const unlinkSignal = requestCanceller.linkToSignal(
273
276
  lastRepInfo.cleaner.signal,
274
277
  );
275
- const segmentInfo = objectAssign({ segment }, content);
278
+ const segmentInfo = objectAssign(
279
+ { segment, nextSegment: undefined },
280
+ content,
281
+ );
276
282
  const prom = loadAndPushSegment(
277
283
  segmentInfo,
278
284
  sourceBufferInterface,
@@ -409,7 +409,7 @@ class Player extends EventEmitter<IPublicAPIEvent> {
409
409
  // See: https://bugzilla.mozilla.org/show_bug.cgi?id=1194624
410
410
  videoElement.preload = "auto";
411
411
 
412
- this.version = /* PLAYER_VERSION */ "4.2.0-dev.2024090600";
412
+ this.version = /* PLAYER_VERSION */ "4.2.0-dev.2024091600";
413
413
  this.log = log;
414
414
  this.state = "STOPPED";
415
415
  this.videoElement = videoElement;
@@ -3313,7 +3313,7 @@ class Player extends EventEmitter<IPublicAPIEvent> {
3313
3313
  }
3314
3314
  }
3315
3315
  }
3316
- Player.version = /* PLAYER_VERSION */ "4.2.0-dev.2024090600";
3316
+ Player.version = /* PLAYER_VERSION */ "4.2.0-dev.2024091600";
3317
3317
 
3318
3318
  /** Every events sent by the RxPlayer's public API. */
3319
3319
  interface IPublicAPIEvent {
@@ -20,7 +20,7 @@ import type { IManifest } from "../../../../manifest";
20
20
  import arrayFind from "../../../../utils/array_find";
21
21
  import isNullOrUndefined from "../../../../utils/is_null_or_undefined";
22
22
  import getMonotonicTimeStamp from "../../../../utils/monotonic_timestamp";
23
- import { getFilenameIndexInUrl } from "../../../../utils/resolve_url";
23
+ import { getFilenameIndexInUrl } from "../../../../utils/url-utils";
24
24
  import type { IParsedManifest } from "../../types";
25
25
  import type {
26
26
  IMPDIntermediateRepresentation,
@@ -14,7 +14,7 @@
14
14
  * limitations under the License.
15
15
  */
16
16
 
17
- import resolveURL from "../../../../utils/resolve_url";
17
+ import { resolveURL } from "../../../../utils/url-utils";
18
18
  import type { IBaseUrlIntermediateRepresentation } from "../node_parser_types";
19
19
 
20
20
  export interface IResolvedBaseUrl {
@@ -86,6 +86,9 @@ pub enum TagName {
86
86
  // -- Inside a <SegmentList> --
87
87
  /// Indicate a <SegmentURL> node
88
88
  SegmentUrl = 20,
89
+
90
+ /// Indicate a <Label> node
91
+ Label = 21,
89
92
  }
90
93
 
91
94
  #[derive(PartialEq, Clone, Copy)]
@@ -276,8 +279,6 @@ pub enum AttributeName {
276
279
  /// namespaces that will appear in it.
277
280
  Namespace = 70,
278
281
 
279
- Label = 71, // String
280
-
281
282
  ServiceLocation = 72, // String
282
283
 
283
284
  // SegmentTemplate
@@ -114,7 +114,10 @@ impl MPDProcessor {
114
114
  }
115
115
  b"cenc:pssh" => self.process_cenc_element(),
116
116
  b"Location" => self.process_location_element(),
117
- b"Label" => self.process_label_element(),
117
+ b"Label" => {
118
+ TagName::Label.report_tag_open();
119
+ self.process_label_element();
120
+ }
118
121
  b"SegmentTimeline" => self.process_segment_timeline_element(),
119
122
 
120
123
  b"EventStream" => {
@@ -274,7 +277,7 @@ impl MPDProcessor {
274
277
  Ok(Event::Text(t)) => {
275
278
  if t.len() > 0 {
276
279
  match t.unescape() {
277
- Ok(unescaped) => AttributeName::Label.report(unescaped),
280
+ Ok(unescaped) => AttributeName::Text.report(unescaped),
278
281
  Err(err) => ParsingError::from(err).report_err(),
279
282
  }
280
283
  }
@@ -284,6 +287,7 @@ impl MPDProcessor {
284
287
  if inner_tag > 0 {
285
288
  inner_tag -= 1;
286
289
  } else {
290
+ TagName::Label.report_tag_close();
287
291
  break;
288
292
  }
289
293
  }
@@ -27,6 +27,7 @@ import { parseFloatOrBool, parseString } from "../utils";
27
27
  import { generateBaseUrlAttrParser } from "./BaseURL";
28
28
  import { generateContentComponentAttrParser } from "./ContentComponent";
29
29
  import { generateContentProtectionAttrParser } from "./ContentProtection";
30
+ import { generateLabelElementParser } from "./Label";
30
31
  import {
31
32
  generateRepresentationAttrParser,
32
33
  generateRepresentationChildrenParser,
@@ -209,6 +210,15 @@ export function generateAdaptationSetChildrenParser(
209
210
  break;
210
211
  }
211
212
 
213
+ case TagName.Label: {
214
+ parsersStack.pushParsers(
215
+ nodeId,
216
+ noop, // Label as treated like an attribute
217
+ generateLabelElementParser(adaptationSetChildren, linearMemory),
218
+ );
219
+ break;
220
+ }
221
+
212
222
  default:
213
223
  // Allows to make sure we're not mistakenly closing a re-opened
214
224
  // tag.
@@ -363,11 +373,6 @@ export function generateAdaptationSetAttrParser(
363
373
  case AttributeName.AvailabilityTimeComplete:
364
374
  adaptationAttrs.availabilityTimeComplete = dataView.getUint8(0) === 0;
365
375
  break;
366
- case AttributeName.Label: {
367
- const label = parseString(textDecoder, linearMemory.buffer, ptr, len);
368
- adaptationAttrs.label = label;
369
- break;
370
- }
371
376
 
372
377
  // TODO
373
378
  // case AttributeName.StartsWithSap:
@@ -0,0 +1,16 @@
1
+ import type { IAdaptationSetChildren } from "../../../node_parser_types";
2
+ import type { IAttributeParser } from "../parsers_stack";
3
+ import { AttributeName } from "../types";
4
+ import { parseString } from "../utils";
5
+
6
+ export function generateLabelElementParser(
7
+ adaptationSet: IAdaptationSetChildren,
8
+ linearMemory: WebAssembly.Memory,
9
+ ): IAttributeParser {
10
+ const textDecoder = new TextDecoder();
11
+ return function onMPDAttribute(attr: AttributeName, ptr: number, len: number) {
12
+ if (attr === AttributeName.Text) {
13
+ adaptationSet.label = parseString(textDecoder, linearMemory.buffer, ptr, len);
14
+ }
15
+ };
16
+ }
@@ -108,6 +108,9 @@ export const enum TagName {
108
108
 
109
109
  /// Indicate a <SegmentURL> node
110
110
  SegmentUrl = 20,
111
+
112
+ /// Indicate a <Label> node
113
+ Label = 21,
111
114
  }
112
115
 
113
116
  /**
@@ -22,7 +22,7 @@ import type { ITrackType } from "../../../public_types";
22
22
  import idGenerator from "../../../utils/id_generator";
23
23
  import isNullOrUndefined from "../../../utils/is_null_or_undefined";
24
24
  import getMonotonicTimeStamp from "../../../utils/monotonic_timestamp";
25
- import { getFilenameIndexInUrl } from "../../../utils/resolve_url";
25
+ import { getFilenameIndexInUrl } from "../../../utils/url-utils";
26
26
  import type {
27
27
  IParsedAdaptation,
28
28
  IParsedAdaptations,
@@ -23,8 +23,8 @@ import isNonEmptyString from "../../../utils/is_non_empty_string";
23
23
  import isNullOrUndefined from "../../../utils/is_null_or_undefined";
24
24
  import getMonotonicTimeStamp from "../../../utils/monotonic_timestamp";
25
25
  import objectAssign from "../../../utils/object_assign";
26
- import { getFilenameIndexInUrl } from "../../../utils/resolve_url";
27
26
  import { hexToBytes } from "../../../utils/string_parsing";
27
+ import { getFilenameIndexInUrl } from "../../../utils/url-utils";
28
28
  import { createBox } from "../../containers/isobmff";
29
29
  import type {
30
30
  IParsedAdaptation,
@@ -16,7 +16,7 @@
16
16
 
17
17
  import type { ISegment } from "../../manifest";
18
18
  import type { ICdnMetadata } from "../../parsers/manifest";
19
- import resolveURL from "../../utils/resolve_url";
19
+ import { resolveURL } from "../../utils/url-utils";
20
20
 
21
21
  export default function constructSegmentUrl(
22
22
  wantedCdn: ICdnMetadata | null,
@@ -16,7 +16,7 @@
16
16
 
17
17
  import type { ISegment, IRepresentation } from "../../manifest";
18
18
  import type { ICdnMetadata } from "../../parsers/manifest";
19
- import resolveURL from "../../utils/resolve_url";
19
+ import { resolveURL } from "../../utils/url-utils";
20
20
 
21
21
  /**
22
22
  * Returns `true` if the given Representation refers to segments in an MP4
@@ -1,5 +1,5 @@
1
1
  import { describe, it, expect } from "vitest";
2
- import resolveURL, { getFilenameIndexInUrl } from "../resolve_url";
2
+ import { getFilenameIndexInUrl, getRelativeUrl, resolveURL } from "../url-utils";
3
3
 
4
4
  describe(`utils - resolveURL ${resolveURL.name}`, () => {
5
5
  it("should return an empty string if no argument is given", () => {
@@ -155,3 +155,150 @@ describe("utils - getFilenameIndexInUrl", () => {
155
155
  expect(getFilenameIndexInUrl("https://ww/rr/d/?test/toto/efewf/ffe/")).toEqual(16);
156
156
  });
157
157
  });
158
+
159
+ describe("utils - getRelativeUrl", () => {
160
+ it("should succeed on two absolute URLs on the same domain", () => {
161
+ expect(
162
+ getRelativeUrl("https://foo.com/foo/bar/cil/", "https://foo.com/foo/bar/cil/diub"),
163
+ ).toEqual("diub");
164
+ expect(
165
+ getRelativeUrl(
166
+ "https://foo.com/foo/bar/cil/",
167
+ "https://foo.com/foo/bar/cil/diub/emp",
168
+ ),
169
+ ).toEqual("diub/emp");
170
+ expect(
171
+ getRelativeUrl(
172
+ "https://foo.com/foo/bar/cil/diub/emp/",
173
+ "https://foo.com/foo/bar/cil/f/g/h/i",
174
+ ),
175
+ ).toEqual("../../f/g/h/i");
176
+
177
+ expect(
178
+ getRelativeUrl(
179
+ "https://foo.com/foo/bar/cil/emp",
180
+ "https://foo.com/foo/bar/cil/emp",
181
+ ),
182
+ ).toEqual("");
183
+ });
184
+
185
+ it("should handle it right when one of the input is just a domain name without a path", () => {
186
+ expect(
187
+ getRelativeUrl(
188
+ "http://github.com" /** Without a starting slash */,
189
+ "http://github.com/rx-player",
190
+ ),
191
+ ).toBe("/rx-player");
192
+ expect(
193
+ getRelativeUrl(
194
+ "http://github.com/rx-player/",
195
+ "http://github.com" /** Without a starting slash */,
196
+ ),
197
+ ).toBe("..");
198
+ });
199
+
200
+ it("should handle it right when one of the input has a path of length 0", () => {
201
+ expect(getRelativeUrl("http://github.com/", "http://github.com/rx-player")).toBe(
202
+ "rx-player",
203
+ );
204
+ expect(
205
+ getRelativeUrl(
206
+ "http://github.com/rx-player/",
207
+ "http://github.com" /** Without a starting slash */,
208
+ ),
209
+ ).toBe("..");
210
+ });
211
+
212
+ it("should fail if scheme is different", () => {
213
+ expect(
214
+ getRelativeUrl("http://foo.com/foo/bar/cil/emp", "https://foo.com/foo/bar/cil/emp"),
215
+ ).toEqual(null);
216
+
217
+ expect(
218
+ getRelativeUrl("https://foo.com/foo/bar/cil/emp", "http://foo.com/foo/bar/cil/emp"),
219
+ ).toEqual(null);
220
+ });
221
+
222
+ it("should fail if domain is different", () => {
223
+ expect(
224
+ getRelativeUrl(
225
+ "https://bar.com/foo/bar/cil/emp",
226
+ "https://foo.com/foo/bar/cil/emp",
227
+ ),
228
+ ).toEqual(null);
229
+ });
230
+
231
+ it("should fail if one anounce a domain but not the other", () => {
232
+ expect(getRelativeUrl("https://foo.com/foo/bar/cil/diub/emp", "/a")).toEqual(null);
233
+
234
+ expect(getRelativeUrl("../url", "https://foo.com")).toEqual(null);
235
+ });
236
+
237
+ it('should output "." if the URL points to the same file', () => {
238
+ expect(getRelativeUrl("../url", "../url")).toEqual("");
239
+ expect(
240
+ getRelativeUrl(
241
+ "https://foo.com/foo/bar/cil/diub/emp",
242
+ "https://foo.com/foo/bar/cil/diub/emp",
243
+ ),
244
+ ).toEqual("");
245
+ expect(
246
+ getRelativeUrl(
247
+ "https://foo.com/foo/bar/cil/diub/emp",
248
+ "https://foo.com/foo/bar/../bar/cil/diub/emp",
249
+ ),
250
+ ).toEqual("");
251
+ });
252
+
253
+ it("should succeed on two relative URLs", () => {
254
+ expect(getRelativeUrl("../url/a", "../url/a/b")).toEqual("a/b");
255
+ expect(getRelativeUrl("../url/a", "../url/b/c")).toEqual("b/c");
256
+ });
257
+
258
+ it("should fail if one seems to be relative to the domain's root, yet we don't know it", () => {
259
+ expect(getRelativeUrl("/url/a", "../../../url/b/c")).toEqual(null);
260
+ expect(getRelativeUrl("./url/a", "/url/b/c")).toEqual(null);
261
+ expect(getRelativeUrl("http://foo.com/url/a", "../url/b/c")).toEqual(null);
262
+ });
263
+
264
+ it("should succeed if both seem to be relative to the domain's root, even if we don't know it", () => {
265
+ expect(getRelativeUrl("/url/a", "/url/b/c")).toEqual("b/c");
266
+ });
267
+
268
+ const normalExamples = [
269
+ { input: "http://a/b/c/g", output: "g" },
270
+ { input: "http://a/b/c/g/", output: "g/" },
271
+ { input: "http://a/g", output: "../../g" },
272
+ { input: "http://a/b/c/d;p?y", output: "?y" },
273
+ { input: "http://a/b/c/g?y", output: "g?y" },
274
+ { input: "http://a/b/c/d;p?q#s", output: "#s" },
275
+ { input: "http://a/b/c/g#s", output: "g#s" },
276
+ { input: "http://a/b/c/g?y#s", output: "g?y#s" },
277
+ { input: "http://a/b/c/;x", output: ";x" },
278
+ { input: "http://a/b/c/g;x", output: "g;x" },
279
+ { input: "http://a/b/c/g;x?y#s", output: "g;x?y#s" },
280
+ { input: "http://a/b/c/d;p?q", output: "" },
281
+ { input: "http://a/b/c/", output: "." },
282
+ { input: "http://a/b/", output: ".." },
283
+ { input: "http://a/b/g", output: "../g" },
284
+ { input: "http://a/", output: "../.." },
285
+ { input: "http://a/g", output: "../../g" },
286
+ ];
287
+ normalExamples.forEach((example) => {
288
+ it("should conform to RFC 3986 normal examples - case: " + example.input, () => {
289
+ const baseURL: string = "http://a/b/c/d;p?q";
290
+ // https://datatracker.ietf.org/doc/html/rfc3986#section-5.4.1
291
+ expect(getRelativeUrl(baseURL, example.input)).toBe(example.output);
292
+ });
293
+ });
294
+
295
+ it("should succeed on two relative URLs", () => {
296
+ expect(getRelativeUrl("../url/a", "../url/a/b")).toEqual("a/b");
297
+ expect(getRelativeUrl("../url/a", "../url/b/c")).toEqual("b/c");
298
+ });
299
+
300
+ it("should return null if the relation between the two URLs is unclear", () => {
301
+ expect(getRelativeUrl("/url/a", "url/a/b")).toEqual(null);
302
+ expect(getRelativeUrl("url/a", "/url/a/b")).toEqual(null);
303
+ });
304
+ });