rx-player 3.27.1-dev.2022041500 → 3.27.1-dev.2022051100

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 (27) hide show
  1. package/CHANGELOG.md +11 -5
  2. package/VERSION +1 -1
  3. package/dist/_esm5.processed/core/api/public_api.js +2 -2
  4. package/dist/_esm5.processed/core/decrypt/content_decryptor.js +12 -10
  5. package/dist/_esm5.processed/core/decrypt/utils/key_session_record.js +2 -1
  6. package/dist/_esm5.processed/core/init/throw_on_media_error.js +19 -10
  7. package/dist/_esm5.processed/manifest/representation.js +4 -4
  8. package/dist/_esm5.processed/parsers/manifest/dash/common/flatten_overlapping_periods.js +11 -1
  9. package/dist/_esm5.processed/parsers/manifest/dash/common/indexes/timeline/timeline_representation_index.d.ts +2 -0
  10. package/dist/_esm5.processed/parsers/manifest/dash/common/indexes/timeline/timeline_representation_index.js +9 -2
  11. package/dist/_esm5.processed/parsers/manifest/dash/common/parse_representations.js +10 -3
  12. package/dist/_esm5.processed/parsers/manifest/types.d.ts +7 -2
  13. package/dist/_esm5.processed/parsers/texttracks/ttml/html/generate_css_test_outline.js +14 -4
  14. package/dist/rx-player.js +85 -33
  15. package/dist/rx-player.min.js +1 -1
  16. package/package.json +1 -1
  17. package/sonar-project.properties +1 -1
  18. package/src/core/api/public_api.ts +2 -2
  19. package/src/core/decrypt/content_decryptor.ts +12 -10
  20. package/src/core/decrypt/utils/key_session_record.ts +2 -1
  21. package/src/core/init/throw_on_media_error.ts +25 -15
  22. package/src/manifest/representation.ts +2 -2
  23. package/src/parsers/manifest/dash/common/flatten_overlapping_periods.ts +10 -1
  24. package/src/parsers/manifest/dash/common/indexes/timeline/timeline_representation_index.ts +12 -2
  25. package/src/parsers/manifest/dash/common/parse_representations.ts +9 -3
  26. package/src/parsers/manifest/types.ts +7 -2
  27. package/src/parsers/texttracks/ttml/html/generate_css_test_outline.ts +15 -4
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "rx-player",
3
3
  "author": "Canal+",
4
- "version": "3.27.1-dev.2022041500",
4
+ "version": "3.27.1-dev.2022051100",
5
5
  "description": "Canal+ HTML5 Video Player",
6
6
  "main": "./dist/rx-player.js",
7
7
  "keywords": [
@@ -1,7 +1,7 @@
1
1
  sonar.projectKey=rx-player
2
2
  sonar.organization=rx-player
3
3
  sonar.projectName=rx-player
4
- sonar.projectVersion=3.27.1-dev.2022041500
4
+ sonar.projectVersion=3.27.1-dev.2022051100
5
5
  sonar.sources=./src,./demo,./tests
6
6
  sonar.exclusions=demo/full/bundle.js,demo/standalone/lib.js,demo/bundle.js
7
7
  sonar.host.url=https://sonarcloud.io
@@ -428,7 +428,7 @@ class Player extends EventEmitter<IPublicAPIEvent> {
428
428
  // See: https://bugzilla.mozilla.org/show_bug.cgi?id=1194624
429
429
  videoElement.preload = "auto";
430
430
 
431
- this.version = /* PLAYER_VERSION */"3.27.1-dev.2022041500";
431
+ this.version = /* PLAYER_VERSION */"3.27.1-dev.2022051100";
432
432
  this.log = log;
433
433
  this.state = "STOPPED";
434
434
  this.videoElement = videoElement;
@@ -2940,7 +2940,7 @@ class Player extends EventEmitter<IPublicAPIEvent> {
2940
2940
  return activeRepresentations[currentPeriod.id];
2941
2941
  }
2942
2942
  }
2943
- Player.version = /* PLAYER_VERSION */"3.27.1-dev.2022041500";
2943
+ Player.version = /* PLAYER_VERSION */"3.27.1-dev.2022051100";
2944
2944
 
2945
2945
  /** Payload emitted with a `positionUpdate` event. */
2946
2946
  export interface IPositionUpdateItem {
@@ -780,16 +780,18 @@ function updateDecipherability(
780
780
  return representation.decipherable;
781
781
  }
782
782
  const contentKIDs = representation.contentProtections.keyIds;
783
- for (let i = 0; i < contentKIDs.length; i++) {
784
- const elt = contentKIDs[i];
785
- for (let j = 0; j < blacklistedKeyIDs.length; j++) {
786
- if (areKeyIdsEqual(blacklistedKeyIDs[j], elt.keyId)) {
787
- return false;
783
+ if (contentKIDs !== undefined) {
784
+ for (let i = 0; i < contentKIDs.length; i++) {
785
+ const elt = contentKIDs[i];
786
+ for (let j = 0; j < blacklistedKeyIDs.length; j++) {
787
+ if (areKeyIdsEqual(blacklistedKeyIDs[j], elt.keyId)) {
788
+ return false;
789
+ }
788
790
  }
789
- }
790
- for (let j = 0; j < whitelistedKeyIds.length; j++) {
791
- if (areKeyIdsEqual(whitelistedKeyIds[j], elt.keyId)) {
792
- return true;
791
+ for (let j = 0; j < whitelistedKeyIds.length; j++) {
792
+ if (areKeyIdsEqual(whitelistedKeyIds[j], elt.keyId)) {
793
+ return true;
794
+ }
793
795
  }
794
796
  }
795
797
  }
@@ -1157,7 +1159,7 @@ function addKeyIdsFromPeriod(
1157
1159
  for (const adaptation of period.getAdaptations()) {
1158
1160
  for (const representation of adaptation.representations) {
1159
1161
  if (representation.contentProtections !== undefined &&
1160
- representation.contentProtections.keyIds.length >= - 1)
1162
+ representation.contentProtections.keyIds !== undefined)
1161
1163
  {
1162
1164
  for (const kidInf of representation.contentProtections.keyIds) {
1163
1165
  set.add(kidInf.keyId);
@@ -144,7 +144,7 @@ export default class KeySessionRecord {
144
144
  initializationData : IProcessedProtectionData
145
145
  ) : boolean {
146
146
  const { keyIds } = initializationData;
147
- if (keyIds !== undefined) {
147
+ if (keyIds !== undefined && keyIds.length > 0) {
148
148
  if (this._keyIds !== null && areAllKeyIdsContainedIn(keyIds, this._keyIds)) {
149
149
  return true;
150
150
  }
@@ -159,6 +159,7 @@ export default class KeySessionRecord {
159
159
  initializationData : IProcessedProtectionData
160
160
  ) : boolean {
161
161
  if (initializationData.keyIds !== undefined &&
162
+ initializationData.keyIds.length > 0 &&
162
163
  this._initializationData.keyIds !== undefined)
163
164
  {
164
165
  return areAllKeyIdsContainedIn(initializationData.keyIds,
@@ -20,6 +20,7 @@ import {
20
20
  Observable,
21
21
  } from "rxjs";
22
22
  import { MediaError } from "../../errors";
23
+ import isNullOrUndefined from "../../utils/is_null_or_undefined";
23
24
 
24
25
  /**
25
26
  * Returns an observable which throws the right MediaError as soon an "error"
@@ -32,27 +33,36 @@ export default function throwOnMediaError(
32
33
  ) : Observable<never> {
33
34
  return observableFromEvent(mediaElement, "error")
34
35
  .pipe(mergeMap(() => {
35
- const errorCode = mediaElement.error == null ? 0 :
36
- mediaElement.error.code;
36
+ const mediaError = mediaElement.error;
37
+ let errorCode : number | undefined;
38
+ let errorMessage : string | undefined;
39
+ if (!isNullOrUndefined(mediaError)) {
40
+ errorCode = mediaError.code;
41
+ errorMessage = mediaError.message;
42
+ }
43
+
37
44
  switch (errorCode) {
38
45
  case 1:
39
- throw new MediaError("MEDIA_ERR_ABORTED",
40
- "The fetching of the associated resource was aborted " +
41
- "by the user's request.");
46
+ errorMessage = errorMessage ??
47
+ "The fetching of the associated resource was aborted by the user's request.";
48
+ throw new MediaError("MEDIA_ERR_ABORTED", errorMessage);
42
49
  case 2:
43
- throw new MediaError("MEDIA_ERR_NETWORK",
44
- "A network error occurred which prevented the media " +
45
- "from being successfully fetched");
50
+ errorMessage = errorMessage ??
51
+ "A network error occurred which prevented the media from being " +
52
+ "successfully fetched";
53
+ throw new MediaError("MEDIA_ERR_NETWORK", errorMessage);
46
54
  case 3:
47
- throw new MediaError("MEDIA_ERR_DECODE",
48
- "An error occurred while trying to decode the media " +
49
- "resource");
55
+ errorMessage = errorMessage ??
56
+ "An error occurred while trying to decode the media resource";
57
+ throw new MediaError("MEDIA_ERR_DECODE", errorMessage);
50
58
  case 4:
51
- throw new MediaError("MEDIA_ERR_SRC_NOT_SUPPORTED",
52
- "The media resource has been found to be unsuitable.");
59
+ errorMessage = errorMessage ??
60
+ "The media resource has been found to be unsuitable.";
61
+ throw new MediaError("MEDIA_ERR_SRC_NOT_SUPPORTED", errorMessage);
53
62
  default:
54
- throw new MediaError("MEDIA_ERR_UNKNOWN",
55
- "The HTMLMediaElement errored due to an unknown reason.");
63
+ errorMessage = errorMessage ??
64
+ "The HTMLMediaElement errored due to an unknown reason.";
65
+ throw new MediaError("MEDIA_ERR_UNKNOWN", errorMessage);
56
66
  }
57
67
  }));
58
68
  }
@@ -174,7 +174,7 @@ class Representation {
174
174
  for (let j = 0; j < initData.values.length; j++) {
175
175
  if (initData.values[j].systemId.toLowerCase() === drmSystemId.toLowerCase()) {
176
176
  if (!createdObjForType) {
177
- const keyIds = this.contentProtections?.keyIds.map(val => val.keyId);
177
+ const keyIds = this.contentProtections?.keyIds?.map(val => val.keyId);
178
178
  filtered.push({ type: initData.type,
179
179
  keyIds,
180
180
  values: [initData.values[j]] });
@@ -221,7 +221,7 @@ class Representation {
221
221
  {
222
222
  return [];
223
223
  }
224
- const keyIds = this.contentProtections?.keyIds.map(val => val.keyId);
224
+ const keyIds = this.contentProtections?.keyIds?.map(val => val.keyId);
225
225
  return this.contentProtections.initData.map((x) => {
226
226
  return { type: x.type,
227
227
  keyIds,
@@ -62,7 +62,16 @@ export default function flattenOverlappingPeriods(
62
62
  parsedPeriod.start);
63
63
  lastFlattenedPeriod.duration = parsedPeriod.start - lastFlattenedPeriod.start;
64
64
  lastFlattenedPeriod.end = parsedPeriod.start;
65
- if (lastFlattenedPeriod.duration <= 0) {
65
+ if (lastFlattenedPeriod.duration > 0) {
66
+ // Note: Calling `break` to quit the while loop should theoritically be
67
+ // unnecessary as the previous operations should ensure we do not re-enter
68
+ // the loop's condition.
69
+ // Yet we dit encounter infinite loops without it because of float-related
70
+ // rounding errors.
71
+ break;
72
+ } else {
73
+ // `lastFlattenedPeriod` has now a negative or `0` duration.
74
+ // Remove it, consider the next Period in its place, and re-start the loop.
66
75
  flattenedPeriods.pop();
67
76
  lastFlattenedPeriod = flattenedPeriods[flattenedPeriods.length - 1];
68
77
  }
@@ -201,6 +201,9 @@ export default class TimelineRepresentationIndex implements IRepresentationIndex
201
201
  /** Absolute start of the period, timescaled and converted to index time. */
202
202
  private _scaledPeriodStart : number;
203
203
 
204
+ /** Actual un-scaled start of the Period as indicated in the MPD. */
205
+ private _periodStart : number;
206
+
204
207
  /** Absolute end of the period, timescaled and converted to index time. */
205
208
  private _scaledPeriodEnd : number | undefined;
206
209
 
@@ -305,6 +308,7 @@ export default class TimelineRepresentationIndex implements IRepresentationIndex
305
308
  startNumber: index.startNumber,
306
309
  timeline: index.timeline ?? null,
307
310
  timescale };
311
+ this._periodStart = periodStart;
308
312
  this._scaledPeriodStart = toIndexTime(periodStart, this._index);
309
313
  this._scaledPeriodEnd = periodEnd === undefined ? undefined :
310
314
  toIndexTime(periodEnd, this._index);
@@ -461,6 +465,7 @@ export default class TimelineRepresentationIndex implements IRepresentationIndex
461
465
  this._index = newIndex._index;
462
466
  this._isDynamic = newIndex._isDynamic;
463
467
  this._scaledPeriodStart = newIndex._scaledPeriodStart;
468
+ this._periodStart = newIndex._periodStart;
464
469
  this._scaledPeriodEnd = newIndex._scaledPeriodEnd;
465
470
  this._lastUpdate = newIndex._lastUpdate;
466
471
  this._manifestBoundsCalculator = newIndex._manifestBoundsCalculator;
@@ -486,6 +491,7 @@ export default class TimelineRepresentationIndex implements IRepresentationIndex
486
491
  }
487
492
  this._isDynamic = newIndex._isDynamic;
488
493
  this._scaledPeriodStart = newIndex._scaledPeriodStart;
494
+ this._periodStart = newIndex._periodStart;
489
495
  this._scaledPeriodEnd = newIndex._scaledPeriodEnd;
490
496
  this._lastUpdate = newIndex._lastUpdate;
491
497
  this._isLastPeriod = newIndex._isLastPeriod;
@@ -548,6 +554,9 @@ export default class TimelineRepresentationIndex implements IRepresentationIndex
548
554
  if (this._index.timeline === null) {
549
555
  this._index.timeline = this._getTimeline();
550
556
  }
557
+ if (!this._isDynamic) {
558
+ return;
559
+ }
551
560
  const firstPosition = this._manifestBoundsCalculator.estimateMinimumBound();
552
561
  if (firstPosition == null) {
553
562
  return; // we don't know yet
@@ -605,12 +614,13 @@ export default class TimelineRepresentationIndex implements IRepresentationIndex
605
614
  const newElements = this._parseTimeline();
606
615
  this._parseTimeline = null; // Free memory
607
616
 
617
+ const actualPeriodStart = this._periodStart * this._index.timescale;
608
618
  const { MIN_DASH_S_ELEMENTS_TO_PARSE_UNSAFELY } = config.getCurrent();
609
619
  if (this._unsafelyBaseOnPreviousIndex === null ||
610
620
  newElements.length < MIN_DASH_S_ELEMENTS_TO_PARSE_UNSAFELY)
611
621
  {
612
622
  // Just completely parse the current timeline
613
- return constructTimelineFromElements(newElements, this._scaledPeriodStart);
623
+ return constructTimelineFromElements(newElements, actualPeriodStart);
614
624
  }
615
625
 
616
626
  // Construct previously parsed timeline if not already done
@@ -625,7 +635,7 @@ export default class TimelineRepresentationIndex implements IRepresentationIndex
625
635
 
626
636
  return constructTimelineFromPreviousTimeline(newElements,
627
637
  prevTimeline,
628
- this._scaledPeriodStart);
638
+ actualPeriodStart);
629
639
 
630
640
  }
631
641
  }
@@ -225,7 +225,12 @@ export default function parseRepresentations(
225
225
  .toLowerCase();
226
226
  }
227
227
  if (cp.attributes.keyId !== undefined && cp.attributes.keyId.length > 0) {
228
- acc.keyIds.push({ keyId: cp.attributes.keyId, systemId });
228
+ const kidObj = { keyId: cp.attributes.keyId, systemId };
229
+ if (acc.keyIds === undefined) {
230
+ acc.keyIds = [kidObj];
231
+ } else {
232
+ acc.keyIds.push(kidObj);
233
+ }
229
234
  }
230
235
  if (systemId !== undefined) {
231
236
  const { cencPssh } = cp.children;
@@ -244,9 +249,10 @@ export default function parseRepresentations(
244
249
  }
245
250
  }
246
251
  return acc;
247
- }, { keyIds: [], initData: [] });
252
+ }, { keyIds: undefined, initData: [] });
248
253
  if (Object.keys(contentProtections.initData).length > 0 ||
249
- contentProtections.keyIds.length > 0)
254
+ (contentProtections.keyIds !== undefined &&
255
+ contentProtections.keyIds.length > 0))
250
256
  {
251
257
  parsedRepresentation.contentProtections = contentProtections;
252
258
  }
@@ -69,8 +69,13 @@ export interface IContentProtectionInitData {
69
69
  */
70
70
  /** Describes every encryption protection parsed for a given media. */
71
71
  export interface IContentProtections {
72
- /** The different encryption key IDs associated with that content. */
73
- keyIds : IContentProtectionKID[];
72
+ /**
73
+ * The different encryption key IDs associated with that content.
74
+ *
75
+ * `undefined` if the key id(s) associated with that content may exist but are
76
+ * not known.
77
+ */
78
+ keyIds : IContentProtectionKID[] | undefined;
74
79
  /** The different encryption initialization data associated with that content. */
75
80
  initData : IContentProtectionInitData[];
76
81
  }
@@ -14,6 +14,8 @@
14
14
  * limitations under the License.
15
15
  */
16
16
 
17
+ import isNonEmptyString from "../../../../utils/is_non_empty_string";
18
+
17
19
  /**
18
20
  * Try to replicate the textOutline TTML style property into CSS.
19
21
  *
@@ -28,8 +30,17 @@ export default function generateCSSTextOutline(
28
30
  color : string,
29
31
  thickness : string|number
30
32
  ) : string {
31
- return `-1px -1px ${thickness} ${color},` +
32
- `1px -1px ${thickness} ${color},` +
33
- `-1px 1px ${thickness} ${color},` +
34
- `1px 1px ${thickness} ${color}`;
33
+ let thick = thickness;
34
+ if (isNonEmptyString(thickness) && thickness.trim().endsWith("%")) {
35
+ // As em and % are basically equivalent in CSS
36
+ // (they both are relative to the font-size
37
+ // of the current element)
38
+ // We convert the non supported % into the supported em
39
+ thick = thickness.trim().slice(0, -1);
40
+ thick = (parseInt(thick, 10) / 100).toString() + "em";
41
+ }
42
+ return `-1px -1px ${thick} ${color},` +
43
+ `1px -1px ${thick} ${color},` +
44
+ `-1px 1px ${thick} ${color},` +
45
+ `1px 1px ${thick} ${color}`;
35
46
  }