shaka-player 4.2.5 → 4.2.6
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/dist/locales.js +28 -28
- package/dist/shaka-player.compiled.debug.js +151 -150
- package/dist/shaka-player.compiled.debug.map +3 -3
- package/dist/shaka-player.compiled.js +644 -642
- package/dist/shaka-player.compiled.map +3 -3
- package/dist/shaka-player.ui.debug.js +203 -201
- package/dist/shaka-player.ui.debug.map +3 -3
- package/dist/shaka-player.ui.js +794 -792
- 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
|
@@ -2243,12 +2243,12 @@ shaka.hls.HlsParser = class {
|
|
|
2243
2243
|
/** @type {shaka.extern.HlsAes128Key|undefined} */
|
|
2244
2244
|
let hlsAes128Key = undefined;
|
|
2245
2245
|
|
|
2246
|
-
|
|
2247
|
-
|
|
2246
|
+
let discontinuitySequence = shaka.hls.Utils.getFirstTagWithNameAsNumber(
|
|
2247
|
+
playlist.tags, 'EXT-X-DISCONTINUITY-SEQUENCE', 0);
|
|
2248
2248
|
const mediaSequenceNumber = shaka.hls.Utils.getFirstTagWithNameAsNumber(
|
|
2249
2249
|
playlist.tags, 'EXT-X-MEDIA-SEQUENCE', 0);
|
|
2250
|
-
const skipTag = shaka.hls.Utils.getFirstTagWithName(
|
|
2251
|
-
'EXT-X-SKIP');
|
|
2250
|
+
const skipTag = shaka.hls.Utils.getFirstTagWithName(
|
|
2251
|
+
playlist.tags, 'EXT-X-SKIP');
|
|
2252
2252
|
const skippedSegments =
|
|
2253
2253
|
skipTag ? Number(skipTag.getAttributeValue('SKIPPED-SEGMENTS')) : 0;
|
|
2254
2254
|
let position = mediaSequenceNumber + skippedSegments;
|
|
@@ -2271,6 +2271,12 @@ shaka.hls.HlsParser = class {
|
|
|
2271
2271
|
(i == 0) ? firstStartTime : previousReference.endTime;
|
|
2272
2272
|
position = mediaSequenceNumber + skippedSegments + i;
|
|
2273
2273
|
|
|
2274
|
+
const discontinuityTag = shaka.hls.Utils.getFirstTagWithName(
|
|
2275
|
+
playlist.tags, 'EXT-X-DISCONTINUITY');
|
|
2276
|
+
if (discontinuityTag) {
|
|
2277
|
+
discontinuitySequence++;
|
|
2278
|
+
}
|
|
2279
|
+
|
|
2274
2280
|
// Apply new AES-128 tags as you see them, keeping a running total.
|
|
2275
2281
|
for (const drmTag of item.tags) {
|
|
2276
2282
|
if (drmTag.name == 'EXT-X-KEY' &&
|
|
@@ -2308,6 +2314,8 @@ shaka.hls.HlsParser = class {
|
|
|
2308
2314
|
previousReference = reference;
|
|
2309
2315
|
|
|
2310
2316
|
if (reference) {
|
|
2317
|
+
reference.discontinuitySequence = discontinuitySequence;
|
|
2318
|
+
|
|
2311
2319
|
if (this.config_.hls.ignoreManifestProgramDateTime &&
|
|
2312
2320
|
this.minSequenceNumber_ != null &&
|
|
2313
2321
|
position < this.minSequenceNumber_) {
|
|
@@ -856,6 +856,22 @@ shaka.media.MediaSourceEngine = class {
|
|
|
856
856
|
]);
|
|
857
857
|
}
|
|
858
858
|
|
|
859
|
+
/**
|
|
860
|
+
* Adjust timestamp offset to maintain AV sync across discontinuities.
|
|
861
|
+
* Only used in sequence mode.
|
|
862
|
+
*
|
|
863
|
+
* @param {shaka.util.ManifestParserUtils.ContentType} contentType
|
|
864
|
+
* @param {number} timestampOffset
|
|
865
|
+
* @return {!Promise}
|
|
866
|
+
*/
|
|
867
|
+
async resync(contentType, timestampOffset) {
|
|
868
|
+
goog.asserts.assert(this.sequenceMode_,
|
|
869
|
+
'resyncAudio only used with sequence mode!');
|
|
870
|
+
await this.enqueueOperation_(
|
|
871
|
+
contentType,
|
|
872
|
+
() => this.setTimestampOffset_(contentType, timestampOffset));
|
|
873
|
+
}
|
|
874
|
+
|
|
859
875
|
/**
|
|
860
876
|
* @param {string=} reason Valid reasons are 'network' and 'decode'.
|
|
861
877
|
* @return {!Promise}
|
|
@@ -618,6 +618,11 @@ shaka.media.StreamingEngine = class {
|
|
|
618
618
|
* within the presentation timeline.
|
|
619
619
|
*/
|
|
620
620
|
seeked() {
|
|
621
|
+
if (!this.playerInterface_) {
|
|
622
|
+
// Already destroyed.
|
|
623
|
+
return;
|
|
624
|
+
}
|
|
625
|
+
|
|
621
626
|
const presentationTime = this.playerInterface_.getPresentationTime();
|
|
622
627
|
const ContentType = shaka.util.ManifestParserUtils.ContentType;
|
|
623
628
|
const newTimeIsBuffered = (type) => {
|
|
@@ -675,7 +680,7 @@ shaka.media.StreamingEngine = class {
|
|
|
675
680
|
|
|
676
681
|
/**
|
|
677
682
|
* Clear the buffer for a given stream. Unlike clearBuffer_, this will handle
|
|
678
|
-
* cases where a MediaState is performing an update. After this runs,
|
|
683
|
+
* cases where a MediaState is performing an update. After this runs, the
|
|
679
684
|
* MediaState will have a pending update.
|
|
680
685
|
* @param {!shaka.media.StreamingEngine.MediaState_} mediaState
|
|
681
686
|
* @private
|
|
@@ -791,9 +796,9 @@ shaka.media.StreamingEngine = class {
|
|
|
791
796
|
for (const type of streamsByType.keys()) {
|
|
792
797
|
const stream = streamsByType.get(type);
|
|
793
798
|
if (!this.mediaStates_.has(type)) {
|
|
794
|
-
const
|
|
795
|
-
this.mediaStates_.set(type,
|
|
796
|
-
this.scheduleUpdate_(
|
|
799
|
+
const mediaState = this.createMediaState_(stream);
|
|
800
|
+
this.mediaStates_.set(type, mediaState);
|
|
801
|
+
this.scheduleUpdate_(mediaState, 0);
|
|
797
802
|
}
|
|
798
803
|
}
|
|
799
804
|
}
|
|
@@ -905,7 +910,7 @@ shaka.media.StreamingEngine = class {
|
|
|
905
910
|
'mediastate.stream should not have segmentIndex yet.');
|
|
906
911
|
thisStream.closeSegmentIndex();
|
|
907
912
|
}
|
|
908
|
-
if (mediaState.
|
|
913
|
+
if (!mediaState.performingUpdate && !mediaState.updateTimer) {
|
|
909
914
|
this.scheduleUpdate_(mediaState, 0);
|
|
910
915
|
}
|
|
911
916
|
return;
|
|
@@ -1389,7 +1394,7 @@ shaka.media.StreamingEngine = class {
|
|
|
1389
1394
|
// If the network slows down, abort the current fetch request and start
|
|
1390
1395
|
// a new one, and ignore the error message.
|
|
1391
1396
|
mediaState.performingUpdate = false;
|
|
1392
|
-
mediaState
|
|
1397
|
+
this.cancelUpdate_(mediaState);
|
|
1393
1398
|
this.scheduleUpdate_(mediaState, 0);
|
|
1394
1399
|
} else if (mediaState.type == ContentType.TEXT &&
|
|
1395
1400
|
this.config_.ignoreTextStreamFailures) {
|
|
@@ -1462,7 +1467,10 @@ shaka.media.StreamingEngine = class {
|
|
|
1462
1467
|
|
|
1463
1468
|
for (const mediaState of this.mediaStates_.values()) {
|
|
1464
1469
|
const logPrefix = shaka.media.StreamingEngine.logPrefix_(mediaState);
|
|
1465
|
-
if
|
|
1470
|
+
// Only schedule an update if it has an error, but it's not mid-update
|
|
1471
|
+
// and there is not already an update scheduled.
|
|
1472
|
+
if (mediaState.hasError && !mediaState.performingUpdate &&
|
|
1473
|
+
!mediaState.updateTimer) {
|
|
1466
1474
|
shaka.log.info(logPrefix, 'Retrying after failure...');
|
|
1467
1475
|
mediaState.hasError = false;
|
|
1468
1476
|
this.scheduleUpdate_(mediaState, delaySeconds);
|
|
@@ -1645,6 +1653,19 @@ shaka.media.StreamingEngine = class {
|
|
|
1645
1653
|
}
|
|
1646
1654
|
}
|
|
1647
1655
|
|
|
1656
|
+
if (this.manifest_.sequenceMode) {
|
|
1657
|
+
// Across discontinuity bounds, we should resync timestamps for
|
|
1658
|
+
// sequence mode playbacks. The next segment appended should
|
|
1659
|
+
// land at its theoretical timestamp from the segment index.
|
|
1660
|
+
const lastDiscontinuitySequence =
|
|
1661
|
+
mediaState.lastSegmentReference ?
|
|
1662
|
+
mediaState.lastSegmentReference.discontinuitySequence : null;
|
|
1663
|
+
if (reference.discontinuitySequence != lastDiscontinuitySequence) {
|
|
1664
|
+
operations.push(this.playerInterface_.mediaSourceEngine.resync(
|
|
1665
|
+
mediaState.type, reference.startTime));
|
|
1666
|
+
}
|
|
1667
|
+
}
|
|
1668
|
+
|
|
1648
1669
|
await Promise.all(operations);
|
|
1649
1670
|
}
|
|
1650
1671
|
|
|
@@ -1678,8 +1699,6 @@ shaka.media.StreamingEngine = class {
|
|
|
1678
1699
|
|
|
1679
1700
|
await this.evict_(mediaState, presentationTime);
|
|
1680
1701
|
this.destroyer_.ensureNotDestroyed();
|
|
1681
|
-
shaka.log.v1(logPrefix, 'appending media segment at',
|
|
1682
|
-
(reference.syncTime == null ? 'unknown' : reference.syncTime));
|
|
1683
1702
|
|
|
1684
1703
|
// 'seeked' or 'adaptation' triggered logic applies only to this
|
|
1685
1704
|
// appendBuffer() call.
|
|
@@ -1956,7 +1975,11 @@ shaka.media.StreamingEngine = class {
|
|
|
1956
1975
|
shaka.log.debug(logPrefix, 'cleared buffer');
|
|
1957
1976
|
mediaState.clearingBuffer = false;
|
|
1958
1977
|
mediaState.endOfStream = false;
|
|
1959
|
-
|
|
1978
|
+
// Since the clear operation was async, check to make sure we're not doing
|
|
1979
|
+
// another update and we don't have one scheduled yet.
|
|
1980
|
+
if (!mediaState.performingUpdate && !mediaState.updateTimer) {
|
|
1981
|
+
this.scheduleUpdate_(mediaState, 0);
|
|
1982
|
+
}
|
|
1960
1983
|
}
|
|
1961
1984
|
|
|
1962
1985
|
|
package/lib/player.js
CHANGED
|
@@ -4140,6 +4140,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
4140
4140
|
return [];
|
|
4141
4141
|
}
|
|
4142
4142
|
const chapters = [];
|
|
4143
|
+
const uniqueChapters = new Set();
|
|
4143
4144
|
for (const chaptersTrack of chaptersTracksWithLanguage) {
|
|
4144
4145
|
if (chaptersTrack && chaptersTrack.cues) {
|
|
4145
4146
|
for (const cue of chaptersTrack.cues) {
|
|
@@ -4154,7 +4155,10 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
4154
4155
|
startTime: cue.startTime,
|
|
4155
4156
|
endTime: cue.endTime,
|
|
4156
4157
|
};
|
|
4157
|
-
|
|
4158
|
+
if (!uniqueChapters.has(id)) {
|
|
4159
|
+
chapters.push(chapter);
|
|
4160
|
+
uniqueChapters.add(id);
|
|
4161
|
+
}
|
|
4158
4162
|
}
|
|
4159
4163
|
}
|
|
4160
4164
|
}
|
|
@@ -6595,7 +6599,7 @@ shaka.Player.TYPICAL_BUFFERING_THRESHOLD_ = 0.5;
|
|
|
6595
6599
|
* @define {string} A version number taken from git at compile time.
|
|
6596
6600
|
* @export
|
|
6597
6601
|
*/
|
|
6598
|
-
shaka.Player.version = 'v4.2.
|
|
6602
|
+
shaka.Player.version = 'v4.2.6-uncompiled';
|
|
6599
6603
|
|
|
6600
6604
|
// Initialize the deprecation system using the version string we just set
|
|
6601
6605
|
// on the player.
|
package/package.json
CHANGED
|
@@ -898,7 +898,7 @@ describe('Player', () => {
|
|
|
898
898
|
expect(getBufferedBehind()).toBe(20); // Buffered to start still.
|
|
899
899
|
video.currentTime = 50;
|
|
900
900
|
await waitUntilBuffered(30);
|
|
901
|
-
expect(getBufferedBehind()).toBeLessThan(
|
|
901
|
+
expect(getBufferedBehind()).toBeLessThan(40); // 30 + segment_size
|
|
902
902
|
|
|
903
903
|
player.configure('streaming.bufferBehind', 10);
|
|
904
904
|
// We only evict content when we append a segment, so increase the
|
|
@@ -925,7 +925,7 @@ describe('Player', () => {
|
|
|
925
925
|
}
|
|
926
926
|
|
|
927
927
|
async function waitUntilBuffered(amount) {
|
|
928
|
-
for (let i = 0; i <
|
|
928
|
+
for (let i = 0; i < 50; i++) {
|
|
929
929
|
// We buffer from an internal segment, so this shouldn't take long to
|
|
930
930
|
// buffer.
|
|
931
931
|
await Util.delay(0.1); // eslint-disable-line no-await-in-loop
|
|
@@ -933,7 +933,14 @@ describe('Player', () => {
|
|
|
933
933
|
return;
|
|
934
934
|
}
|
|
935
935
|
}
|
|
936
|
-
|
|
936
|
+
|
|
937
|
+
const ranges =
|
|
938
|
+
shaka.media.TimeRangesUtils.getBufferedInfo(video.buffered);
|
|
939
|
+
const currentTime = video.currentTime;
|
|
940
|
+
const target = currentTime + amount;
|
|
941
|
+
|
|
942
|
+
throw new Error('Timeout waiting to buffer! ' +
|
|
943
|
+
JSON.stringify({ranges, currentTime, target}));
|
|
937
944
|
}
|
|
938
945
|
}); // describe('buffering')
|
|
939
946
|
|
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
|
};
|
|
@@ -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
|
|
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
|
|
434
|
+
segmentDuration: 10,
|
|
435
435
|
mimeType: 'audio/mp4',
|
|
436
436
|
codecs: 'mp4a.40.2',
|
|
437
437
|
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_);
|