shaka-player 4.3.1 → 4.3.2
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/.github/workflows/build-and-test.yaml +13 -0
- package/AUTHORS +1 -0
- package/CHANGELOG.md +11 -0
- package/CONTRIBUTORS +1 -0
- package/README.md +1 -1
- package/dist/locales.js +31 -31
- package/dist/shaka-player.compiled.debug.js +171 -170
- package/dist/shaka-player.compiled.debug.map +3 -3
- package/dist/shaka-player.compiled.js +753 -753
- package/dist/shaka-player.compiled.map +2 -2
- package/dist/shaka-player.ui.debug.js +208 -206
- package/dist/shaka-player.ui.debug.map +3 -3
- package/dist/shaka-player.ui.js +812 -811
- package/dist/shaka-player.ui.map +3 -3
- package/lib/ads/ad_manager.js +2 -2
- package/lib/hls/hls_parser.js +12 -4
- package/lib/media/media_source_engine.js +16 -0
- package/lib/media/segment_reference.js +3 -0
- package/lib/media/streaming_engine.js +33 -10
- package/lib/player.js +6 -2
- package/package.json +1 -1
- package/test/player_integration.js +10 -3
- package/test/test/boot.js +29 -11
- package/test/test/util/manifest_parser_util.js +4 -3
- package/test/test/util/test_scheme.js +2 -2
- package/ui/controls.js +5 -1
package/lib/ads/ad_manager.js
CHANGED
|
@@ -19,8 +19,8 @@ goog.require('shaka.util.IReleasable');
|
|
|
19
19
|
|
|
20
20
|
|
|
21
21
|
/**
|
|
22
|
-
* @event shaka.ads.AdManager.
|
|
23
|
-
* @description Fired when
|
|
22
|
+
* @event shaka.ads.AdManager.AdsLoadedEvent
|
|
23
|
+
* @description Fired when a sequence of ads has been loaded.
|
|
24
24
|
* @property {string} type
|
|
25
25
|
* 'ads-loaded'
|
|
26
26
|
* @property {number} loadTime
|
package/lib/hls/hls_parser.js
CHANGED
|
@@ -2592,12 +2592,12 @@ shaka.hls.HlsParser = class {
|
|
|
2592
2592
|
/** @type {shaka.extern.HlsAes128Key|undefined} */
|
|
2593
2593
|
let hlsAes128Key = undefined;
|
|
2594
2594
|
|
|
2595
|
-
|
|
2596
|
-
|
|
2595
|
+
let discontinuitySequence = shaka.hls.Utils.getFirstTagWithNameAsNumber(
|
|
2596
|
+
playlist.tags, 'EXT-X-DISCONTINUITY-SEQUENCE', 0);
|
|
2597
2597
|
const mediaSequenceNumber = shaka.hls.Utils.getFirstTagWithNameAsNumber(
|
|
2598
2598
|
playlist.tags, 'EXT-X-MEDIA-SEQUENCE', 0);
|
|
2599
|
-
const skipTag = shaka.hls.Utils.getFirstTagWithName(
|
|
2600
|
-
'EXT-X-SKIP');
|
|
2599
|
+
const skipTag = shaka.hls.Utils.getFirstTagWithName(
|
|
2600
|
+
playlist.tags, 'EXT-X-SKIP');
|
|
2601
2601
|
const skippedSegments =
|
|
2602
2602
|
skipTag ? Number(skipTag.getAttributeValue('SKIPPED-SEGMENTS')) : 0;
|
|
2603
2603
|
let position = mediaSequenceNumber + skippedSegments;
|
|
@@ -2620,6 +2620,12 @@ shaka.hls.HlsParser = class {
|
|
|
2620
2620
|
(i == 0) ? firstStartTime : previousReference.endTime;
|
|
2621
2621
|
position = mediaSequenceNumber + skippedSegments + i;
|
|
2622
2622
|
|
|
2623
|
+
const discontinuityTag = shaka.hls.Utils.getFirstTagWithName(
|
|
2624
|
+
playlist.tags, 'EXT-X-DISCONTINUITY');
|
|
2625
|
+
if (discontinuityTag) {
|
|
2626
|
+
discontinuitySequence++;
|
|
2627
|
+
}
|
|
2628
|
+
|
|
2623
2629
|
// Apply new AES-128 tags as you see them, keeping a running total.
|
|
2624
2630
|
for (const drmTag of item.tags) {
|
|
2625
2631
|
if (drmTag.name == 'EXT-X-KEY' &&
|
|
@@ -2657,6 +2663,8 @@ shaka.hls.HlsParser = class {
|
|
|
2657
2663
|
previousReference = reference;
|
|
2658
2664
|
|
|
2659
2665
|
if (reference) {
|
|
2666
|
+
reference.discontinuitySequence = discontinuitySequence;
|
|
2667
|
+
|
|
2660
2668
|
if (this.config_.hls.ignoreManifestProgramDateTime &&
|
|
2661
2669
|
this.minSequenceNumber_ != null &&
|
|
2662
2670
|
position < this.minSequenceNumber_) {
|
|
@@ -924,6 +924,22 @@ shaka.media.MediaSourceEngine = class {
|
|
|
924
924
|
]);
|
|
925
925
|
}
|
|
926
926
|
|
|
927
|
+
/**
|
|
928
|
+
* Adjust timestamp offset to maintain AV sync across discontinuities.
|
|
929
|
+
* Only used in sequence mode.
|
|
930
|
+
*
|
|
931
|
+
* @param {shaka.util.ManifestParserUtils.ContentType} contentType
|
|
932
|
+
* @param {number} timestampOffset
|
|
933
|
+
* @return {!Promise}
|
|
934
|
+
*/
|
|
935
|
+
async resync(contentType, timestampOffset) {
|
|
936
|
+
goog.asserts.assert(this.sequenceMode_,
|
|
937
|
+
'resyncAudio only used with sequence mode!');
|
|
938
|
+
await this.enqueueOperation_(
|
|
939
|
+
contentType,
|
|
940
|
+
() => this.setTimestampOffset_(contentType, timestampOffset));
|
|
941
|
+
}
|
|
942
|
+
|
|
927
943
|
/**
|
|
928
944
|
* @param {string=} reason Valid reasons are 'network' and 'decode'.
|
|
929
945
|
* @return {!Promise}
|
|
@@ -622,6 +622,11 @@ shaka.media.StreamingEngine = class {
|
|
|
622
622
|
* within the presentation timeline.
|
|
623
623
|
*/
|
|
624
624
|
seeked() {
|
|
625
|
+
if (!this.playerInterface_) {
|
|
626
|
+
// Already destroyed.
|
|
627
|
+
return;
|
|
628
|
+
}
|
|
629
|
+
|
|
625
630
|
const presentationTime = this.playerInterface_.getPresentationTime();
|
|
626
631
|
const ContentType = shaka.util.ManifestParserUtils.ContentType;
|
|
627
632
|
const newTimeIsBuffered = (type) => {
|
|
@@ -679,7 +684,7 @@ shaka.media.StreamingEngine = class {
|
|
|
679
684
|
|
|
680
685
|
/**
|
|
681
686
|
* Clear the buffer for a given stream. Unlike clearBuffer_, this will handle
|
|
682
|
-
* cases where a MediaState is performing an update. After this runs,
|
|
687
|
+
* cases where a MediaState is performing an update. After this runs, the
|
|
683
688
|
* MediaState will have a pending update.
|
|
684
689
|
* @param {!shaka.media.StreamingEngine.MediaState_} mediaState
|
|
685
690
|
* @private
|
|
@@ -795,9 +800,9 @@ shaka.media.StreamingEngine = class {
|
|
|
795
800
|
for (const type of streamsByType.keys()) {
|
|
796
801
|
const stream = streamsByType.get(type);
|
|
797
802
|
if (!this.mediaStates_.has(type)) {
|
|
798
|
-
const
|
|
799
|
-
this.mediaStates_.set(type,
|
|
800
|
-
this.scheduleUpdate_(
|
|
803
|
+
const mediaState = this.createMediaState_(stream);
|
|
804
|
+
this.mediaStates_.set(type, mediaState);
|
|
805
|
+
this.scheduleUpdate_(mediaState, 0);
|
|
801
806
|
}
|
|
802
807
|
}
|
|
803
808
|
}
|
|
@@ -909,7 +914,7 @@ shaka.media.StreamingEngine = class {
|
|
|
909
914
|
'mediastate.stream should not have segmentIndex yet.');
|
|
910
915
|
thisStream.closeSegmentIndex();
|
|
911
916
|
}
|
|
912
|
-
if (mediaState.
|
|
917
|
+
if (!mediaState.performingUpdate && !mediaState.updateTimer) {
|
|
913
918
|
this.scheduleUpdate_(mediaState, 0);
|
|
914
919
|
}
|
|
915
920
|
return;
|
|
@@ -1394,7 +1399,7 @@ shaka.media.StreamingEngine = class {
|
|
|
1394
1399
|
// If the network slows down, abort the current fetch request and start
|
|
1395
1400
|
// a new one, and ignore the error message.
|
|
1396
1401
|
mediaState.performingUpdate = false;
|
|
1397
|
-
mediaState
|
|
1402
|
+
this.cancelUpdate_(mediaState);
|
|
1398
1403
|
this.scheduleUpdate_(mediaState, 0);
|
|
1399
1404
|
} else if (mediaState.type == ContentType.TEXT &&
|
|
1400
1405
|
this.config_.ignoreTextStreamFailures) {
|
|
@@ -1467,7 +1472,10 @@ shaka.media.StreamingEngine = class {
|
|
|
1467
1472
|
|
|
1468
1473
|
for (const mediaState of this.mediaStates_.values()) {
|
|
1469
1474
|
const logPrefix = shaka.media.StreamingEngine.logPrefix_(mediaState);
|
|
1470
|
-
if
|
|
1475
|
+
// Only schedule an update if it has an error, but it's not mid-update
|
|
1476
|
+
// and there is not already an update scheduled.
|
|
1477
|
+
if (mediaState.hasError && !mediaState.performingUpdate &&
|
|
1478
|
+
!mediaState.updateTimer) {
|
|
1471
1479
|
shaka.log.info(logPrefix, 'Retrying after failure...');
|
|
1472
1480
|
mediaState.hasError = false;
|
|
1473
1481
|
this.scheduleUpdate_(mediaState, delaySeconds);
|
|
@@ -1661,6 +1669,19 @@ shaka.media.StreamingEngine = class {
|
|
|
1661
1669
|
}
|
|
1662
1670
|
}
|
|
1663
1671
|
|
|
1672
|
+
if (this.manifest_.sequenceMode) {
|
|
1673
|
+
// Across discontinuity bounds, we should resync timestamps for
|
|
1674
|
+
// sequence mode playbacks. The next segment appended should
|
|
1675
|
+
// land at its theoretical timestamp from the segment index.
|
|
1676
|
+
const lastDiscontinuitySequence =
|
|
1677
|
+
mediaState.lastSegmentReference ?
|
|
1678
|
+
mediaState.lastSegmentReference.discontinuitySequence : null;
|
|
1679
|
+
if (reference.discontinuitySequence != lastDiscontinuitySequence) {
|
|
1680
|
+
operations.push(this.playerInterface_.mediaSourceEngine.resync(
|
|
1681
|
+
mediaState.type, reference.startTime));
|
|
1682
|
+
}
|
|
1683
|
+
}
|
|
1684
|
+
|
|
1664
1685
|
await Promise.all(operations);
|
|
1665
1686
|
}
|
|
1666
1687
|
|
|
@@ -1715,8 +1736,6 @@ shaka.media.StreamingEngine = class {
|
|
|
1715
1736
|
|
|
1716
1737
|
await this.evict_(mediaState, presentationTime);
|
|
1717
1738
|
this.destroyer_.ensureNotDestroyed();
|
|
1718
|
-
shaka.log.v1(logPrefix, 'appending media segment at',
|
|
1719
|
-
(reference.syncTime == null ? 'unknown' : reference.syncTime));
|
|
1720
1739
|
|
|
1721
1740
|
// 'seeked' or 'adaptation' triggered logic applies only to this
|
|
1722
1741
|
// appendBuffer() call.
|
|
@@ -2079,7 +2098,11 @@ shaka.media.StreamingEngine = class {
|
|
|
2079
2098
|
shaka.log.debug(logPrefix, 'cleared buffer');
|
|
2080
2099
|
mediaState.clearingBuffer = false;
|
|
2081
2100
|
mediaState.endOfStream = false;
|
|
2082
|
-
|
|
2101
|
+
// Since the clear operation was async, check to make sure we're not doing
|
|
2102
|
+
// another update and we don't have one scheduled yet.
|
|
2103
|
+
if (!mediaState.performingUpdate && !mediaState.updateTimer) {
|
|
2104
|
+
this.scheduleUpdate_(mediaState, 0);
|
|
2105
|
+
}
|
|
2083
2106
|
}
|
|
2084
2107
|
|
|
2085
2108
|
|
package/lib/player.js
CHANGED
|
@@ -4330,6 +4330,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
4330
4330
|
return [];
|
|
4331
4331
|
}
|
|
4332
4332
|
const chapters = [];
|
|
4333
|
+
const uniqueChapters = new Set();
|
|
4333
4334
|
for (const chaptersTrack of chaptersTracksWithLanguage) {
|
|
4334
4335
|
if (chaptersTrack && chaptersTrack.cues) {
|
|
4335
4336
|
for (const cue of chaptersTrack.cues) {
|
|
@@ -4344,7 +4345,10 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
4344
4345
|
startTime: cue.startTime,
|
|
4345
4346
|
endTime: cue.endTime,
|
|
4346
4347
|
};
|
|
4347
|
-
|
|
4348
|
+
if (!uniqueChapters.has(id)) {
|
|
4349
|
+
chapters.push(chapter);
|
|
4350
|
+
uniqueChapters.add(id);
|
|
4351
|
+
}
|
|
4348
4352
|
}
|
|
4349
4353
|
}
|
|
4350
4354
|
}
|
|
@@ -6974,7 +6978,7 @@ shaka.Player.TYPICAL_BUFFERING_THRESHOLD_ = 0.5;
|
|
|
6974
6978
|
* @define {string} A version number taken from git at compile time.
|
|
6975
6979
|
* @export
|
|
6976
6980
|
*/
|
|
6977
|
-
shaka.Player.version = 'v4.3.
|
|
6981
|
+
shaka.Player.version = 'v4.3.2-uncompiled';
|
|
6978
6982
|
|
|
6979
6983
|
// Initialize the deprecation system using the version string we just set
|
|
6980
6984
|
// on the player.
|
package/package.json
CHANGED
|
@@ -970,7 +970,7 @@ describe('Player', () => {
|
|
|
970
970
|
expect(getBufferedBehind()).toBe(20); // Buffered to start still.
|
|
971
971
|
video.currentTime = 50;
|
|
972
972
|
await waitUntilBuffered(30);
|
|
973
|
-
expect(getBufferedBehind()).toBeLessThan(
|
|
973
|
+
expect(getBufferedBehind()).toBeLessThan(40); // 30 + segment_size
|
|
974
974
|
|
|
975
975
|
player.configure('streaming.bufferBehind', 10);
|
|
976
976
|
// We only evict content when we append a segment, so increase the
|
|
@@ -997,7 +997,7 @@ describe('Player', () => {
|
|
|
997
997
|
}
|
|
998
998
|
|
|
999
999
|
async function waitUntilBuffered(amount) {
|
|
1000
|
-
for (let i = 0; i <
|
|
1000
|
+
for (let i = 0; i < 50; i++) {
|
|
1001
1001
|
// We buffer from an internal segment, so this shouldn't take long to
|
|
1002
1002
|
// buffer.
|
|
1003
1003
|
await Util.delay(0.1); // eslint-disable-line no-await-in-loop
|
|
@@ -1005,7 +1005,14 @@ describe('Player', () => {
|
|
|
1005
1005
|
return;
|
|
1006
1006
|
}
|
|
1007
1007
|
}
|
|
1008
|
-
|
|
1008
|
+
|
|
1009
|
+
const ranges =
|
|
1010
|
+
shaka.media.TimeRangesUtils.getBufferedInfo(video.buffered);
|
|
1011
|
+
const currentTime = video.currentTime;
|
|
1012
|
+
const target = currentTime + amount;
|
|
1013
|
+
|
|
1014
|
+
throw new Error('Timeout waiting to buffer! ' +
|
|
1015
|
+
JSON.stringify({ranges, currentTime, target}));
|
|
1009
1016
|
}
|
|
1010
1017
|
}); // describe('buffering')
|
|
1011
1018
|
|
package/test/test/boot.js
CHANGED
|
@@ -66,22 +66,40 @@ function failTestsOnNamespacedElementOrAttributeNames() {
|
|
|
66
66
|
}
|
|
67
67
|
|
|
68
68
|
/**
|
|
69
|
-
*
|
|
70
|
-
*
|
|
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
|
|
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
|
-
|
|
94
|
+
failOnError('Unhandled rejection in Promise', error);
|
|
95
|
+
});
|
|
78
96
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
};
|
|
@@ -413,7 +413,7 @@ const sintelAudioSegment = {
|
|
|
413
413
|
mdhdOffset: 0x1b6,
|
|
414
414
|
segmentUri: '/base/test/test/assets/sintel-audio-segment.mp4',
|
|
415
415
|
tfdtOffset: 0x3c,
|
|
416
|
-
segmentDuration: 10
|
|
416
|
+
segmentDuration: 10,
|
|
417
417
|
mimeType: 'audio/mp4',
|
|
418
418
|
codecs: 'mp4a.40.2',
|
|
419
419
|
};
|
|
@@ -439,7 +439,7 @@ const sintelEncryptedAudio = {
|
|
|
439
439
|
mdhdOffset: 0x1b6,
|
|
440
440
|
segmentUri: '/base/test/test/assets/encrypted-sintel-audio-segment.mp4',
|
|
441
441
|
tfdtOffset: 0x3c,
|
|
442
|
-
segmentDuration: 10
|
|
442
|
+
segmentDuration: 10,
|
|
443
443
|
mimeType: 'audio/mp4',
|
|
444
444
|
codecs: 'mp4a.40.2',
|
|
445
445
|
initData:
|
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
|
-
|
|
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_);
|