shaka-player 4.2.5 → 4.2.7

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 (45) hide show
  1. package/.github/workflows/build-and-test.yaml +13 -0
  2. package/AUTHORS +1 -0
  3. package/CHANGELOG.md +31 -0
  4. package/CONTRIBUTORS +1 -0
  5. package/dist/deps.js +1 -1
  6. package/dist/locales.js +31 -31
  7. package/dist/shaka-player.compiled.d.ts +10 -0
  8. package/dist/shaka-player.compiled.debug.d.ts +10 -0
  9. package/dist/shaka-player.compiled.debug.externs.js +12 -0
  10. package/dist/shaka-player.compiled.debug.js +252 -246
  11. package/dist/shaka-player.compiled.debug.map +3 -3
  12. package/dist/shaka-player.compiled.externs.js +12 -0
  13. package/dist/shaka-player.compiled.js +713 -708
  14. package/dist/shaka-player.compiled.map +3 -3
  15. package/dist/shaka-player.ui.d.ts +10 -0
  16. package/dist/shaka-player.ui.debug.d.ts +10 -0
  17. package/dist/shaka-player.ui.debug.externs.js +12 -0
  18. package/dist/shaka-player.ui.debug.js +266 -260
  19. package/dist/shaka-player.ui.debug.map +3 -3
  20. package/dist/shaka-player.ui.externs.js +12 -0
  21. package/dist/shaka-player.ui.js +844 -840
  22. package/dist/shaka-player.ui.map +3 -3
  23. package/lib/ads/ad_manager.js +2 -2
  24. package/lib/hls/hls_parser.js +63 -18
  25. package/lib/media/media_source_engine.js +49 -3
  26. package/lib/media/segment_index.js +5 -10
  27. package/lib/media/segment_reference.js +42 -0
  28. package/lib/media/streaming_engine.js +47 -12
  29. package/lib/player.js +12 -2
  30. package/lib/text/ui_text_displayer.js +24 -4
  31. package/lib/text/web_vtt_generator.js +21 -9
  32. package/lib/util/dom_utils.js +3 -1
  33. package/lib/util/stream_utils.js +6 -0
  34. package/lib/util/timer.js +17 -0
  35. package/package.json +1 -1
  36. package/test/hls/hls_live_unit.js +8 -30
  37. package/test/hls/hls_parser_unit.js +30 -0
  38. package/test/media/segment_index_unit.js +28 -0
  39. package/test/player_integration.js +37 -3
  40. package/test/player_unit.js +4 -2
  41. package/test/test/boot.js +29 -11
  42. package/test/test/util/manifest_parser_util.js +4 -3
  43. package/test/test/util/test_scheme.js +2 -2
  44. package/test/text/ui_text_displayer_unit.js +70 -0
  45. package/ui/controls.js +5 -1
package/lib/util/timer.js CHANGED
@@ -78,6 +78,11 @@ shaka.util.Timer = class {
78
78
  tickEvery(seconds) {
79
79
  this.stop();
80
80
 
81
+ if (goog.DEBUG) {
82
+ // Capture the stack trace by making a fake error.
83
+ const stackTrace = Error('Timer created').stack;
84
+ shaka.util.Timer.activeTimers.set(this, stackTrace);
85
+ }
81
86
  this.ticker_ = new shaka.util.DelayedTick(() => {
82
87
  // Schedule the timer again first. |onTick_| could cancel the timer and
83
88
  // rescheduling first simplifies the implementation.
@@ -99,5 +104,17 @@ shaka.util.Timer = class {
99
104
  this.ticker_.stop();
100
105
  this.ticker_ = null;
101
106
  }
107
+ if (goog.DEBUG) {
108
+ shaka.util.Timer.activeTimers.delete(this);
109
+ }
102
110
  }
103
111
  };
112
+
113
+ if (goog.DEBUG) {
114
+ /**
115
+ * Tracks all active timer instances, along with the stack trace that created
116
+ * that timer.
117
+ * @type {!Map.<!shaka.util.Timer, string>}
118
+ */
119
+ shaka.util.Timer.activeTimers = new Map();
120
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "shaka-player",
3
3
  "description": "DASH/EME video player library",
4
- "version": "4.2.5",
4
+ "version": "4.2.7",
5
5
  "homepage": "https://github.com/shaka-project/shaka-player",
6
6
  "author": "Google",
7
7
  "maintainers": [
@@ -520,20 +520,24 @@ describe('HlsParser live', () => {
520
520
  });
521
521
  });
522
522
 
523
- it('sets timestamp offset for segments with discontinuity', async () => {
523
+ it('sets discontinuity sequence numbers', async () => {
524
524
  const ref1 = makeReference(
525
525
  'test:/main.mp4', 0, 2, /* syncTime= */ null,
526
526
  /* baseUri= */ '', /* startByte= */ 0, /* endByte= */ null,
527
527
  /* timestampOffset= */ 0);
528
+ ref1.discontinuitySequence = 30;
528
529
 
529
- // Expect the timestamp offset to be set for the segment after the
530
- // EXT-X-DISCONTINUITY tag.
531
530
  const ref2 = makeReference(
532
531
  'test:/main2.mp4', 2, 4, /* syncTime= */ null,
533
532
  /* baseUri= */ '', /* startByte= */ 0, /* endByte= */ null,
534
533
  /* timestampOffset= */ 0);
534
+ ref2.discontinuitySequence = 31;
535
535
 
536
- await testInitialManifest(master, mediaWithDiscontinuity, [ref1, ref2]);
536
+ const manifest = await testInitialManifest(
537
+ master, mediaWithDiscontinuity, [ref1, ref2]);
538
+
539
+ await testUpdate(
540
+ manifest, mediaWithUpdatedDiscontinuitySegment, [ref2]);
537
541
  });
538
542
 
539
543
  // Test for https://github.com/shaka-project/shaka-player/issues/4223
@@ -725,32 +729,6 @@ describe('HlsParser live', () => {
725
729
  shaka.net.NetworkingEngine.RequestType.MANIFEST);
726
730
  });
727
731
 
728
- it('reuses cached timestamp offset for segments with discontinuity',
729
- async () => {
730
- const ref1 = makeReference(
731
- 'test:/main.mp4', 0, 2, /* syncTime= */ null);
732
- const ref2 = makeReference(
733
- 'test:/main2.mp4', 2, 4, /* syncTime= */ null);
734
-
735
- const manifest = await testInitialManifest(
736
- master, mediaWithDiscontinuity, [ref1, ref2]);
737
-
738
- fakeNetEngine.request.calls.reset();
739
- await testUpdate(
740
- manifest, mediaWithUpdatedDiscontinuitySegment, [ref2]);
741
-
742
- // Only one request should be made, and it's for the playlist.
743
- // Expect to use the cached timestamp offset for the main2.mp4
744
- // segment, without fetching the start time again.
745
- expect(fakeNetEngine.request).toHaveBeenCalledTimes(1);
746
- fakeNetEngine.expectRequest(
747
- 'test:/video',
748
- shaka.net.NetworkingEngine.RequestType.MANIFEST);
749
- fakeNetEngine.expectNoRequest(
750
- 'test:/main.mp4',
751
- shaka.net.NetworkingEngine.RequestType.SEGMENT);
752
- });
753
-
754
732
  it('request playlist delta updates to skip segments', async () => {
755
733
  const mediaWithDeltaUpdates = [
756
734
  '#EXTM3U\n',
@@ -2524,6 +2524,9 @@ describe('HlsParser', () => {
2524
2524
  '#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
2525
2525
  '#EXTINF:5,\n',
2526
2526
  '#EXT-X-BYTERANGE:121090@616\n',
2527
+ 'main.mp4\n',
2528
+ '#EXTINF:5,\n',
2529
+ '#EXT-X-BYTERANGE:121090@616\n',
2527
2530
  'main.mp4',
2528
2531
  ].join('');
2529
2532
 
@@ -2535,6 +2538,10 @@ describe('HlsParser', () => {
2535
2538
  '#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
2536
2539
  '#EXTINF:5,\n',
2537
2540
  '#EXT-X-BYTERANGE:121090@616\n',
2541
+ 'main.mp4\n',
2542
+ '#EXTINF:5,\n',
2543
+ '#EXT-X-BYTERANGE:121090@616\n',
2544
+ '#EXT-X-KEY:METHOD=NONE\n',
2538
2545
  'main.mp4',
2539
2546
  ].join('');
2540
2547
 
@@ -2544,6 +2551,9 @@ describe('HlsParser', () => {
2544
2551
  '#EXT-X-KEY:METHOD=AES-128,',
2545
2552
  'URI="800k.key"\n',
2546
2553
  '#EXTINF:5,\n',
2554
+ 'main.ts\n',
2555
+ '#EXTINF:5,\n',
2556
+ '#EXT-X-KEY:METHOD=NONE\n',
2547
2557
  'main.ts',
2548
2558
  ].join('');
2549
2559
 
@@ -2596,6 +2606,26 @@ describe('HlsParser', () => {
2596
2606
 
2597
2607
  const actual = await parser.start('test:/master', playerInterface);
2598
2608
  expect(actual).toEqual(manifest);
2609
+
2610
+ const mp4AesEncryptionVideo = actual.variants[1].video;
2611
+ await mp4AesEncryptionVideo.createSegmentIndex();
2612
+ goog.asserts.assert(mp4AesEncryptionVideo.segmentIndex != null,
2613
+ 'Null segmentIndex!');
2614
+
2615
+ const firstMp4Segment = mp4AesEncryptionVideo.segmentIndex.get(0);
2616
+ expect(firstMp4Segment.hlsAes128Key).toBeDefined();
2617
+ const secondMp4Segment = mp4AesEncryptionVideo.segmentIndex.get(1);
2618
+ expect(secondMp4Segment.hlsAes128Key).toBeNull();
2619
+
2620
+ const tsAesEncryptionVideo = actual.variants[2].video;
2621
+ await tsAesEncryptionVideo.createSegmentIndex();
2622
+ goog.asserts.assert(tsAesEncryptionVideo.segmentIndex != null,
2623
+ 'Null segmentIndex!');
2624
+
2625
+ const firstTsSegment = tsAesEncryptionVideo.segmentIndex.get(0);
2626
+ expect(firstTsSegment.hlsAes128Key).toBeDefined();
2627
+ const secondTsSegment = tsAesEncryptionVideo.segmentIndex.get(1);
2628
+ expect(secondTsSegment.hlsAes128Key).toBeNull();
2599
2629
  });
2600
2630
 
2601
2631
  it('fails on AES-128 if WebCrypto APIs are not available', async () => {
@@ -532,6 +532,34 @@ describe('SegmentIndex', /** @suppress {accessControls} */ () => {
532
532
  goog.asserts.assert(position1 != null, 'Position should not be null!');
533
533
  expect(index1.get(position1)).toBe(references2[1]);
534
534
  });
535
+
536
+ it('does not duplicate references with rounding errors', () => {
537
+ /** @type {!Array.<!shaka.media.SegmentReference>} */
538
+ const references1 = [
539
+ makeReference(uri(10), 10, 20),
540
+ makeReference(uri(20), 20, 30),
541
+ ];
542
+ const index1 = new shaka.media.SegmentIndex(references1);
543
+
544
+ // 0.24 microseconds: an insignificant rounding error.
545
+ const tinyError = 0.24e-6;
546
+
547
+ /** @type {!Array.<!shaka.media.SegmentReference>} */
548
+ const references2 = [
549
+ makeReference(uri(10), 10, 20),
550
+ // The difference between this and the equivalent old reference is an
551
+ // insignificant rounding error.
552
+ makeReference(uri(20), 20 + tinyError, 30 + tinyError),
553
+ makeReference(uri(30), 30 + tinyError, 40),
554
+ ];
555
+
556
+ index1.merge(references2);
557
+ expect(index1.references.length).toBe(3);
558
+ expect(index1.references[0]).toEqual(references1[0]);
559
+ // The new references replaced the old one.
560
+ expect(index1.references[1]).toEqual(references2[1]);
561
+ expect(index1.references[2]).toEqual(references2[2]);
562
+ });
535
563
  });
536
564
 
537
565
  describe('evict', () => {
@@ -72,6 +72,33 @@ describe('Player', () => {
72
72
  });
73
73
  }); // describe('attach')
74
74
 
75
+ describe('destroy', () => {
76
+ // Regression test for:
77
+ // https://github.com/shaka-project/shaka-player/issues/4850
78
+ it('does not leave any lingering timers', async () => {
79
+ shaka.util.Timer.activeTimers.clear();
80
+
81
+ // Unlike the other tests in this file, this uses an uncompiled build of
82
+ // Shaka, so that we don't need to expose shaka.util.Timer.activeTimers.
83
+ player = new shaka.Player(video);
84
+ waiter.setPlayer(player);
85
+
86
+ // Play the video for a little while.
87
+ await player.load('test:sintel');
88
+ video.play();
89
+ await waiter.waitUntilPlayheadReachesOrFailOnTimeout(video, 1, 10);
90
+
91
+ // Destroy the player.
92
+ await player.destroy();
93
+
94
+ // Are there any timers left?
95
+ for (const timer of shaka.util.Timer.activeTimers.keys()) {
96
+ const stackTrace = shaka.util.Timer.activeTimers.get(timer);
97
+ fail('Lingering timer exists! Stack trace of creation: ' + stackTrace);
98
+ }
99
+ });
100
+ });
101
+
75
102
  describe('updateStartTime() in manifestparsed event handler', () => {
76
103
  it('does not get segments prior to startTime', async () => {
77
104
  player.addEventListener('manifestparsed', () => {
@@ -898,7 +925,7 @@ describe('Player', () => {
898
925
  expect(getBufferedBehind()).toBe(20); // Buffered to start still.
899
926
  video.currentTime = 50;
900
927
  await waitUntilBuffered(30);
901
- expect(getBufferedBehind()).toBeLessThan(30);
928
+ expect(getBufferedBehind()).toBeLessThan(40); // 30 + segment_size
902
929
 
903
930
  player.configure('streaming.bufferBehind', 10);
904
931
  // We only evict content when we append a segment, so increase the
@@ -925,7 +952,7 @@ describe('Player', () => {
925
952
  }
926
953
 
927
954
  async function waitUntilBuffered(amount) {
928
- for (let i = 0; i < 25; i++) {
955
+ for (let i = 0; i < 50; i++) {
929
956
  // We buffer from an internal segment, so this shouldn't take long to
930
957
  // buffer.
931
958
  await Util.delay(0.1); // eslint-disable-line no-await-in-loop
@@ -933,7 +960,14 @@ describe('Player', () => {
933
960
  return;
934
961
  }
935
962
  }
936
- throw new Error('Timeout waiting to buffer');
963
+
964
+ const ranges =
965
+ shaka.media.TimeRangesUtils.getBufferedInfo(video.buffered);
966
+ const currentTime = video.currentTime;
967
+ const target = currentTime + amount;
968
+
969
+ throw new Error('Timeout waiting to buffer! ' +
970
+ JSON.stringify({ranges, currentTime, target}));
937
971
  }
938
972
  }); // describe('buffering')
939
973
 
@@ -3390,7 +3390,9 @@ describe('Player', () => {
3390
3390
  variant.addAudio(0, (stream) => {
3391
3391
  stream.channelsCount = 6;
3392
3392
  stream.audioSamplingRate = 48000;
3393
- stream.codecs = 'ac-3';
3393
+ // ac-3 is rewritten as ec-3 on Tizen, so for the stability of this
3394
+ // test case, use ec-3.
3395
+ stream.codecs = 'ec-3';
3394
3396
  });
3395
3397
  });
3396
3398
 
@@ -3415,7 +3417,7 @@ describe('Player', () => {
3415
3417
  expect(abrManager.variants.length).toBe(1);
3416
3418
  // It should be the 6-channel variant, based on our preference.
3417
3419
  expect(abrManager.variants[0].audio.channelsCount).toBe(6);
3418
- expect(abrManager.variants[0].audio.codecs).toBe('ac-3');
3420
+ expect(abrManager.variants[0].audio.codecs).toBe('ec-3');
3419
3421
  });
3420
3422
  });
3421
3423
 
package/test/test/boot.js CHANGED
@@ -66,22 +66,40 @@ function failTestsOnNamespacedElementOrAttributeNames() {
66
66
  }
67
67
 
68
68
  /**
69
- * Listen for unhandled Promise rejections (which may occur after a test) and
70
- * convert them into test failures.
69
+ * Fail the current test on a given error, constructed with a given header and
70
+ * the full stack trace of the error.
71
+ *
72
+ * @param {string} messageHeader
73
+ * @param {!Error} error
71
74
  */
72
- function failTestsOnUnhandledRejections() {
75
+ function failOnError(messageHeader, error) {
76
+ let message = `${messageHeader}: ${error}`;
77
+ // Shaka errors have the stack trace in their toString() already, so don't
78
+ // add it again. For native errors, we need to see where it came from.
79
+ if (error && error.stack && !(error instanceof shaka.util.Error)) {
80
+ message += '\n' + error.stack;
81
+ }
82
+ fail(message);
83
+ }
84
+
85
+ /**
86
+ * Listen for unhandled errors and Promise rejections (which may occur after a
87
+ * test) and convert them into test failures.
88
+ */
89
+ function failTestsOnUnhandledErrors() {
73
90
  // https://developer.mozilla.org/en-US/docs/Web/Events/unhandledrejection
74
91
  window.addEventListener('unhandledrejection', (event) => {
75
92
  /** @type {?} */
76
93
  const error = event.reason;
77
- let message = 'Unhandled rejection in Promise: ' + error;
94
+ failOnError('Unhandled rejection in Promise', error);
95
+ });
78
96
 
79
- // Shaka errors have the stack trace in their toString() already, so don't
80
- // add it again. For native errors, we need to see where it came from.
81
- if (error && error.stack && !(error instanceof shaka.util.Error)) {
82
- message += '\n' + error.stack;
83
- }
84
- fail(message);
97
+ // https://developer.mozilla.org/en-US/docs/Web/API/Window/error_event
98
+ // https://developer.mozilla.org/en-US/docs/Web/API/ErrorEvent
99
+ window.addEventListener('error', (event) => {
100
+ /** @type {?} */
101
+ const error = event['error'];
102
+ failOnError('Unhandled error', error);
85
103
  });
86
104
  }
87
105
 
@@ -387,7 +405,7 @@ function configureJasmineEnvironment() {
387
405
  function setupTestEnvironment() {
388
406
  failTestsOnFailedAssertions();
389
407
  failTestsOnNamespacedElementOrAttributeNames();
390
- failTestsOnUnhandledRejections();
408
+ failTestsOnUnhandledErrors();
391
409
  disableScrollbars();
392
410
  workAroundLegacyEdgePromiseIssues();
393
411
 
@@ -73,7 +73,7 @@ shaka.test.ManifestParser = class {
73
73
  const appendWindowStart = /** @type {?} */(jasmine.any(Number));
74
74
  const appendWindowEnd = /** @type {?} */(jasmine.any(Number));
75
75
 
76
- return new shaka.media.SegmentReference(
76
+ const ref = new shaka.media.SegmentReference(
77
77
  start, end, getUris, startByte, endByte,
78
78
  initSegmentReference,
79
79
  timestampOffset,
@@ -82,7 +82,8 @@ shaka.test.ManifestParser = class {
82
82
  partialReferences,
83
83
  tilesLayout,
84
84
  /* tileDuration= */ undefined,
85
- syncTime,
86
- );
85
+ syncTime);
86
+ ref.discontinuitySequence = /** @type {?} */(jasmine.any(Number));
87
+ return ref;
87
88
  }
88
89
  };
@@ -405,7 +405,7 @@ const sintelAudioSegment = {
405
405
  mdhdOffset: 0x1b6,
406
406
  segmentUri: '/base/test/test/assets/sintel-audio-segment.mp4',
407
407
  tfdtOffset: 0x3c,
408
- segmentDuration: 10.005,
408
+ segmentDuration: 10,
409
409
  mimeType: 'audio/mp4',
410
410
  codecs: 'mp4a.40.2',
411
411
  };
@@ -431,7 +431,7 @@ const sintelEncryptedAudio = {
431
431
  mdhdOffset: 0x1b6,
432
432
  segmentUri: '/base/test/test/assets/encrypted-sintel-audio-segment.mp4',
433
433
  tfdtOffset: 0x3c,
434
- segmentDuration: 10.005,
434
+ segmentDuration: 10,
435
435
  mimeType: 'audio/mp4',
436
436
  codecs: 'mp4a.40.2',
437
437
  initData:
@@ -537,4 +537,74 @@ describe('UITextDisplayer', () => {
537
537
  '.shaka-text-region');
538
538
  expect(allRegionElements.length).toBe(1);
539
539
  });
540
+
541
+ it('creates separate regions when dimensions differ but id same', () => {
542
+ const identicalRegionId = 'regionId';
543
+
544
+ const cueRegion1 = new shaka.text.CueRegion();
545
+ const cueRegion2 = new shaka.text.CueRegion();
546
+ cueRegion1.id = identicalRegionId;
547
+ cueRegion2.id = identicalRegionId;
548
+
549
+ cueRegion1.height = 80;
550
+ cueRegion1.heightUnits = shaka.text.CueRegion.units.PERCENTAGE;
551
+ cueRegion1.width = 80;
552
+ cueRegion1.widthUnits = shaka.text.CueRegion.units.PERCENTAGE;
553
+
554
+ cueRegion2.height = 160; // the only difference!
555
+ cueRegion2.heightUnits = shaka.text.CueRegion.units.PERCENTAGE;
556
+ cueRegion2.width = 80;
557
+ cueRegion2.widthUnits = shaka.text.CueRegion.units.PERCENTAGE;
558
+
559
+ cueRegion1.viewportAnchorX = 10;
560
+ cueRegion1.viewportAnchorY = 10;
561
+ cueRegion1.viewportAnchorUnits = shaka.text.CueRegion.units.PERCENTAGE;
562
+
563
+ cueRegion2.viewportAnchorX = 10;
564
+ cueRegion2.viewportAnchorY = 10;
565
+ cueRegion2.viewportAnchorUnits = shaka.text.CueRegion.units.PERCENTAGE;
566
+
567
+ // These all attach to the same region, but only one region element should
568
+ // be created.
569
+ const firstBatchOfCues = [
570
+ new shaka.text.Cue(0, 100, ''),
571
+ new shaka.text.Cue(0, 100, ''),
572
+ new shaka.text.Cue(0, 100, ''),
573
+ ];
574
+ for (const cue of firstBatchOfCues) {
575
+ cue.displayAlign = shaka.text.Cue.displayAlign.CENTER;
576
+ cue.region = cueRegion1;
577
+ }
578
+
579
+ // Another batch for the other region
580
+ const secondBatchOfCues = [
581
+ new shaka.text.Cue(0, 100, ''),
582
+ new shaka.text.Cue(0, 100, ''),
583
+ new shaka.text.Cue(0, 100, ''),
584
+ ];
585
+ for (const cue of secondBatchOfCues) {
586
+ cue.displayAlign = shaka.text.Cue.displayAlign.CENTER;
587
+ cue.region = cueRegion2;
588
+ }
589
+
590
+ textDisplayer.setTextVisibility(true);
591
+ textDisplayer.append(firstBatchOfCues);
592
+ textDisplayer.append(secondBatchOfCues);
593
+ updateCaptions();
594
+
595
+ const textContainer = videoContainer.querySelector('.shaka-text-container');
596
+ const allRegionElements = textContainer.querySelectorAll(
597
+ '.shaka-text-region');
598
+
599
+ // Verify that the nested cues are attached to respective region element.
600
+ expect(allRegionElements.length).toBe(2);
601
+
602
+ const childrenOfOne = Array.from(allRegionElements[0].childNodes).filter(
603
+ (e) => e.nodeType == Node.ELEMENT_NODE);
604
+ expect(childrenOfOne.length).toBe(3);
605
+
606
+ const childrenOfTwo = Array.from(allRegionElements[1].childNodes).filter(
607
+ (e) => e.nodeType == Node.ELEMENT_NODE);
608
+ expect(childrenOfTwo.length).toBe(3);
609
+ });
540
610
  });
package/ui/controls.js CHANGED
@@ -599,7 +599,11 @@ shaka.ui.Controls = class extends shaka.util.FakeEventTarget {
599
599
  if (this.config_.forceLandscapeOnFullscreen && screen.orientation) {
600
600
  // Locking to 'landscape' should let it be either
601
601
  // 'landscape-primary' or 'landscape-secondary' as appropriate.
602
- await screen.orientation.lock('landscape');
602
+ // We ignore errors from this specific call, since it creates noise
603
+ // on desktop otherwise.
604
+ try {
605
+ await screen.orientation.lock('landscape');
606
+ } catch (error) {}
603
607
  }
604
608
  } else {
605
609
  const video = /** @type {HTMLVideoElement} */(this.localVideo_);