shaka-player 4.6.16 → 4.6.18
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 +17 -4
- package/.github/workflows/release-please.yaml +2 -2
- package/AUTHORS +1 -0
- package/CHANGELOG.md +38 -0
- package/CONTRIBUTORS +1 -0
- package/dist/locales.js +34 -34
- package/dist/shaka-player.compiled.d.ts +1 -0
- package/dist/shaka-player.compiled.debug.d.ts +1 -0
- package/dist/shaka-player.compiled.debug.externs.js +4 -0
- package/dist/shaka-player.compiled.debug.js +206 -203
- package/dist/shaka-player.compiled.debug.map +3 -3
- package/dist/shaka-player.compiled.externs.js +4 -0
- package/dist/shaka-player.compiled.js +1070 -1066
- package/dist/shaka-player.compiled.map +3 -3
- package/dist/shaka-player.ui.d.ts +1 -0
- package/dist/shaka-player.ui.debug.d.ts +1 -0
- package/dist/shaka-player.ui.debug.externs.js +4 -0
- package/dist/shaka-player.ui.debug.js +222 -219
- package/dist/shaka-player.ui.debug.map +3 -3
- package/dist/shaka-player.ui.externs.js +4 -0
- package/dist/shaka-player.ui.js +1217 -1213
- package/dist/shaka-player.ui.map +3 -3
- package/docs/tutorials/faq.md +13 -0
- package/docs/tutorials/offline.md +3 -0
- package/docs/tutorials/ui.md +4 -4
- package/karma.conf.js +1 -0
- package/lib/ads/client_side_ad_manager.js +4 -2
- package/lib/dash/segment_template.js +24 -1
- package/lib/media/drm_engine.js +17 -3
- package/lib/media/media_source_engine.js +13 -3
- package/lib/media/segment_index.js +10 -0
- package/lib/media/segment_reference.js +12 -0
- package/lib/media/streaming_engine.js +9 -27
- package/lib/player.js +48 -17
- package/lib/text/ui_text_displayer.js +10 -1
- package/lib/util/platform.js +31 -9
- package/lib/util/stream_utils.js +8 -5
- package/maintained-branches.md +5 -0
- package/package.json +1 -1
- package/roadmap.md +22 -5
- package/test/codec_switching/codec_switching_integration.js +183 -13
- package/test/media/media_source_engine_unit.js +105 -148
- package/test/player_unit.js +1 -1
- package/test/test/assets/dash-multi-codec-ec3/ChID_voices_6ch_256kbps_ddp.mp4 +0 -0
- package/test/test/assets/dash-multi-codec-ec3/ChID_voices_6ch_640kbps_aac.mp4 +0 -0
- package/test/test/assets/dash-multi-codec-ec3/dash.mpd +32 -0
- package/test/util/platform_unit.js +54 -0
- package/ui/resolution_selection.js +1 -0
package/docs/tutorials/faq.md
CHANGED
|
@@ -245,6 +245,19 @@ the following configuration:
|
|
|
245
245
|
player.configure('manifest.hls.liveSegmentsDelay', 1);
|
|
246
246
|
```
|
|
247
247
|
|
|
248
|
+
<hr>
|
|
249
|
+
|
|
250
|
+
**Q:** Why can I not find the new version of Shaka Player on
|
|
251
|
+
ajax.googleapis.com?
|
|
252
|
+
|
|
253
|
+
**A:** When a new version of Shaka Player is released on GitHub, it has to
|
|
254
|
+
undergo a brief manual review process before it can be uploaded to Google's
|
|
255
|
+
servers. After approval, the upload process then usually takes several hours.
|
|
256
|
+
Therefore it is not unusual for a day or two to pass before a new version of
|
|
257
|
+
Shaka Player is hosted on Google's ajax.googleapis.com website. We ask for your
|
|
258
|
+
patience in these matters.
|
|
259
|
+
|
|
260
|
+
|
|
248
261
|
[386]: https://github.com/shaka-project/shaka-player/issues/386#issuecomment-227898001
|
|
249
262
|
[489]: https://github.com/shaka-project/shaka-player/issues/489#issuecomment-240466224
|
|
250
263
|
[743]: https://github.com/shaka-project/shaka-player/issues/743
|
|
@@ -340,6 +340,9 @@ you want.
|
|
|
340
340
|
At this point, the content is now stored offline and it's ready to be played.
|
|
341
341
|
Next we will add functionality to play offline content.
|
|
342
342
|
|
|
343
|
+
Note: If you call `storage.store` twice with the same manifestUri as input,
|
|
344
|
+
you'll download the same manifestUri twice.
|
|
345
|
+
|
|
343
346
|
## Playing Offline Content
|
|
344
347
|
|
|
345
348
|
Now that we have stored some content, we want to play it. To do this, resolve
|
package/docs/tutorials/ui.md
CHANGED
|
@@ -16,9 +16,9 @@ Set up controls with HTML data attributes:
|
|
|
16
16
|
<!DOCTYPE html>
|
|
17
17
|
<html>
|
|
18
18
|
<head>
|
|
19
|
-
<!-- Shaka Player
|
|
19
|
+
<!-- Shaka Player UI compiled library: -->
|
|
20
20
|
<script src="dist/shaka-player.ui.js"></script>
|
|
21
|
-
<!-- Shaka Player
|
|
21
|
+
<!-- Shaka Player UI compiled library default CSS: -->
|
|
22
22
|
<link rel="stylesheet" type="text/css" href="dist/controls.css">
|
|
23
23
|
<!-- Chromecast SDK (if you want Chromecast support for your app): -->
|
|
24
24
|
<script defer src="https://www.gstatic.com/cv/js/sender/v1/cast_sender.js"></script>
|
|
@@ -52,7 +52,7 @@ async function init() {
|
|
|
52
52
|
const controls = ui.getControls();
|
|
53
53
|
const player = controls.getPlayer();
|
|
54
54
|
|
|
55
|
-
// Attach player and
|
|
55
|
+
// Attach player and UI to the window to make it easy to access in the JS console.
|
|
56
56
|
window.player = player;
|
|
57
57
|
window.ui = ui;
|
|
58
58
|
|
|
@@ -145,7 +145,7 @@ casting to an Android receiver app.
|
|
|
145
145
|
<video autoplay data-shaka-player id="video" style="width:100%;height:100%"
|
|
146
146
|
src="https://storage.googleapis.com/shaka-demo-assets/angel-one/dash.mpd"></video>
|
|
147
147
|
</div
|
|
148
|
-
|
|
148
|
+
```
|
|
149
149
|
|
|
150
150
|
#### Providing source(s) for auto load.
|
|
151
151
|
|
package/karma.conf.js
CHANGED
|
@@ -234,6 +234,7 @@ module.exports = (config) => {
|
|
|
234
234
|
{pattern: 'test/**/*.js', included: false},
|
|
235
235
|
{pattern: 'test/test/assets/*', included: false},
|
|
236
236
|
{pattern: 'test/test/assets/dash-multi-codec/*', included: false},
|
|
237
|
+
{pattern: 'test/test/assets/dash-multi-codec-ec3/*', included: false},
|
|
237
238
|
{pattern: 'test/test/assets/3675/*', included: false},
|
|
238
239
|
{pattern: 'test/test/assets/6339/*', included: false},
|
|
239
240
|
{pattern: 'test/test/assets/dash-aes-128/*', included: false},
|
|
@@ -533,7 +533,9 @@ shaka.ads.ClientSideAdManager = class {
|
|
|
533
533
|
shaka.ads.AdManager.AD_STARTED, data));
|
|
534
534
|
if (this.ad_.isLinear()) {
|
|
535
535
|
this.adContainer_.setAttribute('ad-active', 'true');
|
|
536
|
-
this.
|
|
536
|
+
if (!this.config_.customPlayheadTracker) {
|
|
537
|
+
this.video_.pause();
|
|
538
|
+
}
|
|
537
539
|
if (this.video_.muted) {
|
|
538
540
|
this.ad_.setInitialMuted(this.video_.volume);
|
|
539
541
|
} else {
|
|
@@ -551,7 +553,7 @@ shaka.ads.ClientSideAdManager = class {
|
|
|
551
553
|
(new Map()).set('originalEvent', e)));
|
|
552
554
|
if (this.ad_ && this.ad_.isLinear()) {
|
|
553
555
|
this.adContainer_.removeAttribute('ad-active');
|
|
554
|
-
if (!this.video_.ended) {
|
|
556
|
+
if (!this.config_.customPlayheadTracker && !this.video_.ended) {
|
|
555
557
|
this.video_.play();
|
|
556
558
|
}
|
|
557
559
|
}
|
|
@@ -392,6 +392,11 @@ shaka.dash.SegmentTemplate = class {
|
|
|
392
392
|
Math.ceil(availablePeriodTimes[1] / segmentDuration) - 1,
|
|
393
393
|
];
|
|
394
394
|
|
|
395
|
+
// For Low Latency we can request the partial current position.
|
|
396
|
+
if (context.representation.availabilityTimeOffset) {
|
|
397
|
+
availablePeriodPositions[1]++;
|
|
398
|
+
}
|
|
399
|
+
|
|
395
400
|
// In absolute positions.
|
|
396
401
|
const availablePresentationPositions =
|
|
397
402
|
availablePeriodPositions.map((x) => x + startNumber);
|
|
@@ -689,7 +694,7 @@ shaka.dash.TimelineSegmentIndex = class extends shaka.media.SegmentIndex {
|
|
|
689
694
|
*/
|
|
690
695
|
appendTemplateInfo(info, periodStart, periodEnd, shouldFit,
|
|
691
696
|
initSegmentReference) {
|
|
692
|
-
this.
|
|
697
|
+
this.updateInitSegmentReference(initSegmentReference);
|
|
693
698
|
if (!this.templateInfo_) {
|
|
694
699
|
this.templateInfo_ = info;
|
|
695
700
|
this.periodStart_ = periodStart;
|
|
@@ -720,6 +725,24 @@ shaka.dash.TimelineSegmentIndex = class extends shaka.media.SegmentIndex {
|
|
|
720
725
|
}
|
|
721
726
|
}
|
|
722
727
|
|
|
728
|
+
/**
|
|
729
|
+
* Updates the init segment reference and propagates the update to all
|
|
730
|
+
* references.
|
|
731
|
+
* @param {shaka.media.InitSegmentReference} initSegmentReference
|
|
732
|
+
*/
|
|
733
|
+
updateInitSegmentReference(initSegmentReference) {
|
|
734
|
+
if (this.initSegmentReference_ === initSegmentReference) {
|
|
735
|
+
return;
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
this.initSegmentReference_ = initSegmentReference;
|
|
739
|
+
for (const reference of this.references) {
|
|
740
|
+
if (reference) {
|
|
741
|
+
reference.updateInitSegmentReference(initSegmentReference);
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
|
|
723
746
|
/**
|
|
724
747
|
*
|
|
725
748
|
* @param {number} time
|
package/lib/media/drm_engine.js
CHANGED
|
@@ -1551,6 +1551,9 @@ shaka.media.DrmEngine = class {
|
|
|
1551
1551
|
const req = this.playerInterface_.netEngine.request(requestType, request);
|
|
1552
1552
|
response = await req.promise;
|
|
1553
1553
|
} catch (error) {
|
|
1554
|
+
if (this.destroyer_.destroyed()) {
|
|
1555
|
+
return;
|
|
1556
|
+
}
|
|
1554
1557
|
// Request failed!
|
|
1555
1558
|
goog.asserts.assert(error instanceof shaka.util.Error,
|
|
1556
1559
|
'Wrong NetworkingEngine error type!');
|
|
@@ -1559,9 +1562,20 @@ shaka.media.DrmEngine = class {
|
|
|
1559
1562
|
shaka.util.Error.Category.DRM,
|
|
1560
1563
|
shaka.util.Error.Code.LICENSE_REQUEST_FAILED,
|
|
1561
1564
|
error);
|
|
1562
|
-
this.
|
|
1563
|
-
|
|
1564
|
-
metadata.updatePromise
|
|
1565
|
+
if (this.activeSessions_.size == 1) {
|
|
1566
|
+
this.onError_(shakaErr);
|
|
1567
|
+
if (metadata && metadata.updatePromise) {
|
|
1568
|
+
metadata.updatePromise.reject(shakaErr);
|
|
1569
|
+
}
|
|
1570
|
+
} else {
|
|
1571
|
+
if (metadata && metadata.updatePromise) {
|
|
1572
|
+
metadata.updatePromise.reject(shakaErr);
|
|
1573
|
+
}
|
|
1574
|
+
this.activeSessions_.delete(session);
|
|
1575
|
+
if (this.areAllSessionsLoaded_()) {
|
|
1576
|
+
this.allSessionsLoaded_.resolve();
|
|
1577
|
+
this.keyStatusTimer_.tickAfter(/* seconds= */ 0.1);
|
|
1578
|
+
}
|
|
1565
1579
|
}
|
|
1566
1580
|
return;
|
|
1567
1581
|
}
|
|
@@ -1784,12 +1784,17 @@ shaka.media.MediaSourceEngine = class {
|
|
|
1784
1784
|
|
|
1785
1785
|
const totalOfBuffers = streamsByType.size;
|
|
1786
1786
|
let numberOfSourceBufferAdded = 0;
|
|
1787
|
-
|
|
1787
|
+
const onSourceBufferAdded = () => {
|
|
1788
1788
|
numberOfSourceBufferAdded++;
|
|
1789
1789
|
if (numberOfSourceBufferAdded === totalOfBuffers) {
|
|
1790
1790
|
sourceBufferAdded.resolve();
|
|
1791
|
+
this.eventManager_.unlisten(sourceBuffers, 'addsourcebuffer',
|
|
1792
|
+
onSourceBufferAdded);
|
|
1791
1793
|
}
|
|
1792
|
-
}
|
|
1794
|
+
};
|
|
1795
|
+
|
|
1796
|
+
this.eventManager_.listen(sourceBuffers, 'addsourcebuffer',
|
|
1797
|
+
onSourceBufferAdded);
|
|
1793
1798
|
|
|
1794
1799
|
for (const contentType of streamsByType.keys()) {
|
|
1795
1800
|
const stream = streamsByType.get(contentType);
|
|
@@ -1812,7 +1817,12 @@ shaka.media.MediaSourceEngine = class {
|
|
|
1812
1817
|
this.destroyer_.ensureNotDestroyed();
|
|
1813
1818
|
|
|
1814
1819
|
this.eventManager_.listenOnce(this.video_, 'canplaythrough', () => {
|
|
1815
|
-
|
|
1820
|
+
// Don't use ensureNotDestroyed() from this event listener, because
|
|
1821
|
+
// that results in an uncaught exception. Instead, just check the
|
|
1822
|
+
// flag.
|
|
1823
|
+
if (this.destroyer_.destroyed()) {
|
|
1824
|
+
return;
|
|
1825
|
+
}
|
|
1816
1826
|
|
|
1817
1827
|
this.video_.autoplay = previousAutoPlayState;
|
|
1818
1828
|
if (!previousPausedState) {
|
|
@@ -693,6 +693,16 @@ shaka.media.MetaSegmentIndex = class extends shaka.media.SegmentIndex {
|
|
|
693
693
|
this.indexes_ = [];
|
|
694
694
|
}
|
|
695
695
|
|
|
696
|
+
/**
|
|
697
|
+
* @override
|
|
698
|
+
* @export
|
|
699
|
+
*/
|
|
700
|
+
forEachTopLevelReference(fn) {
|
|
701
|
+
for (const index of this.indexes_) {
|
|
702
|
+
index.forEachTopLevelReference(fn);
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
|
|
696
706
|
/**
|
|
697
707
|
* @override
|
|
698
708
|
* @export
|
|
@@ -586,6 +586,18 @@ shaka.media.SegmentReference = class {
|
|
|
586
586
|
this.offset(offset);
|
|
587
587
|
}
|
|
588
588
|
}
|
|
589
|
+
|
|
590
|
+
/**
|
|
591
|
+
* Updates the init segment reference and propagates the update to all partial
|
|
592
|
+
* references.
|
|
593
|
+
* @param {shaka.media.InitSegmentReference} initSegmentReference
|
|
594
|
+
*/
|
|
595
|
+
updateInitSegmentReference(initSegmentReference) {
|
|
596
|
+
this.initSegmentReference = initSegmentReference;
|
|
597
|
+
for (const partialReference of this.partialReferences) {
|
|
598
|
+
partialReference.updateInitSegmentReference(initSegmentReference);
|
|
599
|
+
}
|
|
600
|
+
}
|
|
589
601
|
};
|
|
590
602
|
|
|
591
603
|
|
|
@@ -878,7 +878,6 @@ shaka.media.StreamingEngine = class {
|
|
|
878
878
|
// The playhead might be seeking on startup, if a start time is set, so
|
|
879
879
|
// start "seeked" as true.
|
|
880
880
|
seeked: true,
|
|
881
|
-
needsResync: false,
|
|
882
881
|
recovering: false,
|
|
883
882
|
hasError: false,
|
|
884
883
|
operation: null,
|
|
@@ -1245,11 +1244,6 @@ shaka.media.StreamingEngine = class {
|
|
|
1245
1244
|
shaka.log.v1(
|
|
1246
1245
|
logPrefix, 'looking up segment from new stream endTime:', time);
|
|
1247
1246
|
|
|
1248
|
-
// Using a new iterator means we need to resync the stream in sequence
|
|
1249
|
-
// mode. The buffered range might not align perfectly with the last
|
|
1250
|
-
// segment end time, so we may end up repeating a segment. Resyncing
|
|
1251
|
-
// makes this safe to do.
|
|
1252
|
-
mediaState.needsResync = true;
|
|
1253
1247
|
mediaState.segmentIterator =
|
|
1254
1248
|
mediaState.stream.segmentIndex.getIteratorForTime(time);
|
|
1255
1249
|
const ref = mediaState.segmentIterator &&
|
|
@@ -1730,9 +1724,16 @@ shaka.media.StreamingEngine = class {
|
|
|
1730
1724
|
otherState = this.mediaStates_.get(ContentType.VIDEO);
|
|
1731
1725
|
}
|
|
1732
1726
|
if (otherState) {
|
|
1727
|
+
// First, abort all operations in progress on the other stream.
|
|
1728
|
+
await this.abortOperations_(otherState).catch(() => {});
|
|
1729
|
+
// Then clear our cache of the last init segment, since MSE will be
|
|
1730
|
+
// reloaded and no init segment will be there post-reload.
|
|
1733
1731
|
otherState.lastInitSegmentReference = null;
|
|
1732
|
+
// Now force the existing buffer to be cleared. It is not necessary
|
|
1733
|
+
// to perform the MSE clear operation, but this has the side-effect
|
|
1734
|
+
// that our state for that stream will then match MSE's post-reload
|
|
1735
|
+
// state.
|
|
1734
1736
|
this.forceClearBuffer_(otherState);
|
|
1735
|
-
this.abortOperations_(otherState).catch(() => {});
|
|
1736
1737
|
}
|
|
1737
1738
|
}
|
|
1738
1739
|
|
|
@@ -1762,19 +1763,6 @@ shaka.media.StreamingEngine = class {
|
|
|
1762
1763
|
mediaState.type, timestampOffset, appendWindowStart,
|
|
1763
1764
|
appendWindowEnd, ignoreTimestampOffset,
|
|
1764
1765
|
mediaState.stream, streamsByType);
|
|
1765
|
-
if (isResetMediaSourceNecessary) {
|
|
1766
|
-
let otherState = null;
|
|
1767
|
-
if (mediaState.type === ContentType.VIDEO) {
|
|
1768
|
-
otherState = this.mediaStates_.get(ContentType.AUDIO);
|
|
1769
|
-
} else if (mediaState.type === ContentType.AUDIO) {
|
|
1770
|
-
otherState = this.mediaStates_.get(ContentType.VIDEO);
|
|
1771
|
-
}
|
|
1772
|
-
if (otherState) {
|
|
1773
|
-
otherState.waitingToClearBuffer = false;
|
|
1774
|
-
otherState.performingUpdate = false;
|
|
1775
|
-
this.forceClearBuffer_(otherState);
|
|
1776
|
-
}
|
|
1777
|
-
}
|
|
1778
1766
|
} catch (error) {
|
|
1779
1767
|
mediaState.lastAppendWindowStart = null;
|
|
1780
1768
|
mediaState.lastAppendWindowEnd = null;
|
|
@@ -1869,9 +1857,7 @@ shaka.media.StreamingEngine = class {
|
|
|
1869
1857
|
// Across discontinuity bounds, we should resync timestamps for
|
|
1870
1858
|
// sequence mode playbacks. The next segment appended should
|
|
1871
1859
|
// land at its theoretical timestamp from the segment index.
|
|
1872
|
-
if (reference.discontinuitySequence != lastDiscontinuitySequence
|
|
1873
|
-
mediaState.needsResync) {
|
|
1874
|
-
mediaState.needsResync = false;
|
|
1860
|
+
if (reference.discontinuitySequence != lastDiscontinuitySequence) {
|
|
1875
1861
|
operations.push(this.playerInterface_.mediaSourceEngine.resync(
|
|
1876
1862
|
mediaState.type, reference.startTime));
|
|
1877
1863
|
}
|
|
@@ -2541,7 +2527,6 @@ shaka.media.StreamingEngine.PlayerInterface;
|
|
|
2541
2527
|
* clearBufferSafeMargin: number,
|
|
2542
2528
|
* clearingBuffer: boolean,
|
|
2543
2529
|
* seeked: boolean,
|
|
2544
|
-
* needsResync: boolean,
|
|
2545
2530
|
* adaptation: boolean,
|
|
2546
2531
|
* recovering: boolean,
|
|
2547
2532
|
* hasError: boolean,
|
|
@@ -2596,9 +2581,6 @@ shaka.media.StreamingEngine.PlayerInterface;
|
|
|
2596
2581
|
* True indicates that the presentation just seeked.
|
|
2597
2582
|
* @property {boolean} adaptation
|
|
2598
2583
|
* True indicates that the presentation just automatically switched variants.
|
|
2599
|
-
* @property {boolean} needsResync
|
|
2600
|
-
* True indicates that the stream needs to be resynced in sequence mode,
|
|
2601
|
-
* regardless of discontinuity sequence.
|
|
2602
2584
|
* @property {boolean} recovering
|
|
2603
2585
|
* True indicates that the last segment was not appended because it could not
|
|
2604
2586
|
* fit in the buffer.
|
package/lib/player.js
CHANGED
|
@@ -1946,31 +1946,49 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
1946
1946
|
this.config_.playRangeStart,
|
|
1947
1947
|
this.config_.playRangeEnd);
|
|
1948
1948
|
|
|
1949
|
-
this.playhead_ = this.createPlayhead(this.startTime_);
|
|
1950
|
-
this.playheadObservers_ =
|
|
1951
|
-
this.createPlayheadObserversForMSE_(startTimeOfLoad);
|
|
1952
1949
|
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1950
|
+
const setupPlayhead = (startTime) => {
|
|
1951
|
+
this.playhead_ = this.createPlayhead(startTime);
|
|
1952
|
+
this.playheadObservers_ =
|
|
1953
|
+
this.createPlayheadObserversForMSE_(startTimeOfLoad);
|
|
1954
|
+
|
|
1955
|
+
// We need to start the buffer management code near the end because it
|
|
1956
|
+
// will set the initial buffering state and that depends on other
|
|
1957
|
+
// components being initialized.
|
|
1958
|
+
const rebufferThreshold = Math.max(
|
|
1959
|
+
this.manifest_.minBufferTime,
|
|
1960
|
+
this.config_.streaming.rebufferingGoal);
|
|
1961
|
+
this.startBufferManagement_(mediaElement, rebufferThreshold);
|
|
1962
|
+
};
|
|
1963
|
+
|
|
1964
|
+
if (!this.config_.streaming.startAtSegmentBoundary) {
|
|
1965
|
+
setupPlayhead(this.startTime_);
|
|
1966
|
+
}
|
|
1959
1967
|
|
|
1960
1968
|
// Now we can switch to the initial variant.
|
|
1961
1969
|
if (!activeVariant) {
|
|
1962
1970
|
goog.asserts.assert(initialVariant,
|
|
1963
1971
|
'Must have choosen an initial variant!');
|
|
1964
1972
|
|
|
1965
|
-
this.switchVariant_(initialVariant, /* fromAdaptation= */ true,
|
|
1966
|
-
/* clearBuffer= */ false, /* safeMargin= */ 0);
|
|
1967
|
-
|
|
1968
1973
|
// Now that we have initial streams, we may adjust the start time to
|
|
1969
1974
|
// align to a segment boundary.
|
|
1970
1975
|
if (this.config_.streaming.startAtSegmentBoundary) {
|
|
1971
|
-
this.
|
|
1972
|
-
|
|
1976
|
+
const timeline = this.manifest_.presentationTimeline;
|
|
1977
|
+
let initialTime = this.startTime_ || this.video_.currentTime;
|
|
1978
|
+
const seekRangeStart = timeline.getSeekRangeStart();
|
|
1979
|
+
const seekRangeEnd = timeline.getSeekRangeEnd();
|
|
1980
|
+
if (initialTime < seekRangeStart) {
|
|
1981
|
+
initialTime = seekRangeStart;
|
|
1982
|
+
} else if (initialTime > seekRangeEnd) {
|
|
1983
|
+
initialTime = seekRangeEnd;
|
|
1984
|
+
}
|
|
1985
|
+
const startTime = await this.adjustStartTime_(
|
|
1986
|
+
initialVariant, initialTime);
|
|
1987
|
+
setupPlayhead(startTime);
|
|
1973
1988
|
}
|
|
1989
|
+
|
|
1990
|
+
this.switchVariant_(initialVariant, /* fromAdaptation= */ true,
|
|
1991
|
+
/* clearBuffer= */ false, /* safeMargin= */ 0);
|
|
1974
1992
|
}
|
|
1975
1993
|
|
|
1976
1994
|
this.playhead_.ready();
|
|
@@ -2282,6 +2300,15 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
2282
2300
|
mediaElement.load();
|
|
2283
2301
|
}
|
|
2284
2302
|
|
|
2303
|
+
// In Safari using HLS won't load anything unless you call load()
|
|
2304
|
+
// explicitly, no matter the value of the preload attribute.
|
|
2305
|
+
// Note: this only happens when there are not autoplay.
|
|
2306
|
+
if (mediaElement.preload != 'none' && !mediaElement.autoplay &&
|
|
2307
|
+
shaka.util.MimeUtils.isHlsType(mimeType) &&
|
|
2308
|
+
shaka.util.Platform.safariVersion()) {
|
|
2309
|
+
mediaElement.load();
|
|
2310
|
+
}
|
|
2311
|
+
|
|
2285
2312
|
// Set the load mode last so that we know that all our components are
|
|
2286
2313
|
// initialized.
|
|
2287
2314
|
this.loadMode_ = shaka.Player.LoadMode.SRC_EQUALS;
|
|
@@ -3544,12 +3571,16 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
3544
3571
|
const currentTime = this.video_.currentTime;
|
|
3545
3572
|
const seekRange = this.seekRange();
|
|
3546
3573
|
const safeSeekOffset = this.config_.streaming.safeSeekOffset;
|
|
3574
|
+
|
|
3575
|
+
// Cancel trick play if we hit the beginning or end of the seekable
|
|
3576
|
+
// (Sub-second accuracy not required here)
|
|
3547
3577
|
if (rate > 0) {
|
|
3548
|
-
if (currentTime >= seekRange.end) {
|
|
3578
|
+
if (Math.floor(currentTime) >= Math.floor(seekRange.end)) {
|
|
3549
3579
|
this.cancelTrickPlay();
|
|
3550
3580
|
}
|
|
3551
3581
|
} else {
|
|
3552
|
-
if (currentTime <=
|
|
3582
|
+
if (Math.floor(currentTime) <=
|
|
3583
|
+
Math.floor(seekRange.start + safeSeekOffset)) {
|
|
3553
3584
|
this.cancelTrickPlay();
|
|
3554
3585
|
}
|
|
3555
3586
|
}
|
|
@@ -6850,7 +6881,7 @@ shaka.Player.TYPICAL_BUFFERING_THRESHOLD_ = 0.5;
|
|
|
6850
6881
|
* @export
|
|
6851
6882
|
*/
|
|
6852
6883
|
// eslint-disable-next-line no-useless-concat
|
|
6853
|
-
shaka.Player.version = 'v4.6.
|
|
6884
|
+
shaka.Player.version = 'v4.6.18' + '-uncompiled'; // x-release-please-version
|
|
6854
6885
|
|
|
6855
6886
|
// Initialize the deprecation system using the version string we just set
|
|
6856
6887
|
// on the player.
|
|
@@ -72,7 +72,9 @@ shaka.text.UITextDisplayer = class {
|
|
|
72
72
|
|
|
73
73
|
/** @private {shaka.util.Timer} */
|
|
74
74
|
this.captionsTimer_ = new shaka.util.Timer(() => {
|
|
75
|
-
this.
|
|
75
|
+
if (!this.video_.paused) {
|
|
76
|
+
this.updateCaptions_();
|
|
77
|
+
}
|
|
76
78
|
}).tickEvery(updatePeriod);
|
|
77
79
|
|
|
78
80
|
/**
|
|
@@ -93,6 +95,10 @@ shaka.text.UITextDisplayer = class {
|
|
|
93
95
|
this.updateCaptions_(/* forceUpdate= */ true);
|
|
94
96
|
});
|
|
95
97
|
|
|
98
|
+
this.eventManager_.listen(this.video_, 'seeking', () => {
|
|
99
|
+
this.updateCaptions_(/* forceUpdate= */ true);
|
|
100
|
+
});
|
|
101
|
+
|
|
96
102
|
// From: https://html.spec.whatwg.org/multipage/media.html#dom-video-videowidth
|
|
97
103
|
// Whenever the natural width or natural height of the video changes
|
|
98
104
|
// (including, for example, because the selected video track was changed),
|
|
@@ -339,6 +345,9 @@ shaka.text.UITextDisplayer = class {
|
|
|
339
345
|
const cueRegistry = this.currentCuesMap_.get(cue);
|
|
340
346
|
goog.asserts.assert(cueRegistry, 'cueRegistry should exist.');
|
|
341
347
|
if (cueRegistry.regionElement) {
|
|
348
|
+
if (cueRegistry.regionElement.contains(container)) {
|
|
349
|
+
cueRegistry.regionElement.removeChild(container);
|
|
350
|
+
}
|
|
342
351
|
container.appendChild(cueRegistry.regionElement);
|
|
343
352
|
cueRegistry.regionElement.appendChild(cueRegistry.cueElement);
|
|
344
353
|
} else {
|
package/lib/util/platform.js
CHANGED
|
@@ -149,10 +149,9 @@ shaka.util.Platform = class {
|
|
|
149
149
|
* @return {boolean}
|
|
150
150
|
*/
|
|
151
151
|
static isWebOS3() {
|
|
152
|
-
// See:
|
|
153
|
-
return shaka.util.Platform.
|
|
154
|
-
shaka.util.Platform.
|
|
155
|
-
'Chrome/38.0.2125.122 Safari/537.36');
|
|
152
|
+
// See: https://webostv.developer.lge.com/develop/specifications/web-api-and-web-engine#useragent-string
|
|
153
|
+
return shaka.util.Platform.isWebOS() &&
|
|
154
|
+
shaka.util.Platform.chromeVersion() === 38;
|
|
156
155
|
}
|
|
157
156
|
|
|
158
157
|
/**
|
|
@@ -161,18 +160,20 @@ shaka.util.Platform = class {
|
|
|
161
160
|
* @return {boolean}
|
|
162
161
|
*/
|
|
163
162
|
static isWebOS4() {
|
|
164
|
-
// See:
|
|
165
|
-
return
|
|
163
|
+
// See: https://webostv.developer.lge.com/develop/specifications/web-api-and-web-engine#useragent-string
|
|
164
|
+
return shaka.util.Platform.isWebOS() &&
|
|
165
|
+
shaka.util.Platform.chromeVersion() === 53;
|
|
166
166
|
}
|
|
167
167
|
|
|
168
168
|
/**
|
|
169
|
-
* Check if the current platform is a WebOS
|
|
169
|
+
* Check if the current platform is a WebOS 5.
|
|
170
170
|
*
|
|
171
171
|
* @return {boolean}
|
|
172
172
|
*/
|
|
173
173
|
static isWebOS5() {
|
|
174
|
-
// See:
|
|
175
|
-
return
|
|
174
|
+
// See: https://webostv.developer.lge.com/develop/specifications/web-api-and-web-engine#useragent-string
|
|
175
|
+
return shaka.util.Platform.isWebOS() &&
|
|
176
|
+
shaka.util.Platform.chromeVersion() === 68;
|
|
176
177
|
}
|
|
177
178
|
|
|
178
179
|
/**
|
|
@@ -184,6 +185,17 @@ shaka.util.Platform = class {
|
|
|
184
185
|
return shaka.util.Platform.userAgentContains_('CrKey');
|
|
185
186
|
}
|
|
186
187
|
|
|
188
|
+
/**
|
|
189
|
+
* Check if the current platform is a Google Chromecast with Android
|
|
190
|
+
* (i.e. Chromecast with GoogleTV).
|
|
191
|
+
*
|
|
192
|
+
* @return {boolean}
|
|
193
|
+
*/
|
|
194
|
+
static isAndroidCastDevice() {
|
|
195
|
+
const Platform = shaka.util.Platform;
|
|
196
|
+
return Platform.isChromecast() && Platform.isAndroid();
|
|
197
|
+
}
|
|
198
|
+
|
|
187
199
|
/**
|
|
188
200
|
* Returns a major version number for Chrome, or Chromium-based browsers.
|
|
189
201
|
*
|
|
@@ -414,6 +426,9 @@ shaka.util.Platform = class {
|
|
|
414
426
|
return navigator.userAgentData.platform.toLowerCase() == 'macos';
|
|
415
427
|
}
|
|
416
428
|
// Fall back to the old API, with less strict matching.
|
|
429
|
+
if (!navigator.platform) {
|
|
430
|
+
return false;
|
|
431
|
+
}
|
|
417
432
|
return navigator.platform.toLowerCase().includes('mac');
|
|
418
433
|
}
|
|
419
434
|
|
|
@@ -428,6 +443,9 @@ shaka.util.Platform = class {
|
|
|
428
443
|
return navigator.userAgentData.platform.toLowerCase() == 'windows';
|
|
429
444
|
}
|
|
430
445
|
// Fall back to the old API, with less strict matching.
|
|
446
|
+
if (!navigator.platform) {
|
|
447
|
+
return false;
|
|
448
|
+
}
|
|
431
449
|
return navigator.platform.toLowerCase().includes('windows');
|
|
432
450
|
}
|
|
433
451
|
|
|
@@ -535,6 +553,10 @@ shaka.util.Platform = class {
|
|
|
535
553
|
Platform.isWebOS4() || Platform.isWebOS5()) {
|
|
536
554
|
return false;
|
|
537
555
|
}
|
|
556
|
+
// Older chromecasts without GoogleTV seem to not support SMOOTH properly.
|
|
557
|
+
if (Platform.isChromecast() && !Platform.isAndroidCastDevice()) {
|
|
558
|
+
return false;
|
|
559
|
+
}
|
|
538
560
|
// See: https://chromium-review.googlesource.com/c/chromium/src/+/4577759
|
|
539
561
|
if (Platform.isWindows() && Platform.isEdge()) {
|
|
540
562
|
return false;
|
package/lib/util/stream_utils.js
CHANGED
|
@@ -839,8 +839,12 @@ shaka.util.StreamUtils = class {
|
|
|
839
839
|
// currently don't support 'fLaC', while 'flac' is supported by most
|
|
840
840
|
// major browsers.
|
|
841
841
|
// See https://bugs.chromium.org/p/chromium/issues/detail?id=1422728
|
|
842
|
-
if (codecs
|
|
843
|
-
|
|
842
|
+
if (codecs.toLowerCase() == 'flac') {
|
|
843
|
+
if (!shaka.util.Platform.isSafari()) {
|
|
844
|
+
return 'flac';
|
|
845
|
+
} else {
|
|
846
|
+
return 'fLaC';
|
|
847
|
+
}
|
|
844
848
|
}
|
|
845
849
|
|
|
846
850
|
// The same is true for 'Opus'.
|
|
@@ -1321,8 +1325,6 @@ shaka.util.StreamUtils = class {
|
|
|
1321
1325
|
* @return {shaka.extern.Track}
|
|
1322
1326
|
*/
|
|
1323
1327
|
static html5TextTrackToTrack(textTrack) {
|
|
1324
|
-
const CLOSED_CAPTION_MIMETYPE =
|
|
1325
|
-
shaka.util.MimeUtils.CEA608_CLOSED_CAPTION_MIMETYPE;
|
|
1326
1328
|
const StreamUtils = shaka.util.StreamUtils;
|
|
1327
1329
|
|
|
1328
1330
|
/** @type {shaka.extern.Track} */
|
|
@@ -1331,7 +1333,8 @@ shaka.util.StreamUtils = class {
|
|
|
1331
1333
|
track.type = 'text';
|
|
1332
1334
|
track.originalTextId = textTrack.id;
|
|
1333
1335
|
if (textTrack.kind == 'captions') {
|
|
1334
|
-
|
|
1336
|
+
// See: https://github.com/shaka-project/shaka-player/issues/6233
|
|
1337
|
+
track.mimeType = 'unknown';
|
|
1335
1338
|
}
|
|
1336
1339
|
if (textTrack.kind == 'subtitles') {
|
|
1337
1340
|
track.mimeType = 'text/vtt';
|
package/maintained-branches.md
CHANGED
|
@@ -2,8 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
We are currently maintaining the following branches of Shaka Player:
|
|
4
4
|
|
|
5
|
+
<<<<<<< HEAD
|
|
5
6
|
- v4.6 (latest)
|
|
6
7
|
- v4.5 (previous)
|
|
8
|
+
=======
|
|
9
|
+
- v4.8 (latest)
|
|
10
|
+
- v4.7 (previous)
|
|
11
|
+
>>>>>>> 99204977e (chore: Update maintained branches doc (#6504))
|
|
7
12
|
- v4.3 (in use by the Cast Application Framework)
|
|
8
13
|
- No active LTS branches at this time
|
|
9
14
|
|
package/package.json
CHANGED
package/roadmap.md
CHANGED
|
@@ -9,8 +9,6 @@ The goals of future milestones are fluid until we begin that development cycle,
|
|
|
9
9
|
so the exact milestone for future features is not pre-determined.
|
|
10
10
|
|
|
11
11
|
Candidate features for future release cycles:
|
|
12
|
-
- Official TypeScript defs, generated from source
|
|
13
|
-
https://github.com/shaka-project/shaka-player/issues/1030
|
|
14
12
|
- Background fetch for offline storage
|
|
15
13
|
https://github.com/shaka-project/shaka-player/issues/879
|
|
16
14
|
- A method to stitch together clips
|
|
@@ -19,13 +17,32 @@ Candidate features for future release cycles:
|
|
|
19
17
|
https://github.com/shaka-project/shaka-player/issues/898
|
|
20
18
|
- Smaller, more modular binary
|
|
21
19
|
|
|
22
|
-
v5.0 - 2024
|
|
20
|
+
v5.0 - 2024 Q4
|
|
21
|
+
- Conversion to Typescript
|
|
22
|
+
|
|
23
|
+
v4.9 - 2024 Q3
|
|
24
|
+
- DASH patch manifests
|
|
25
|
+
https://github.com/shaka-project/shaka-player/issues/2228
|
|
26
|
+
|
|
27
|
+
=====
|
|
28
|
+
|
|
29
|
+
v4.8 - 2024 Q2
|
|
23
30
|
- Preload API
|
|
24
31
|
https://github.com/shaka-project/shaka-player/issues/880
|
|
32
|
+
- AES-256 and AES-256-CTR (HLS)
|
|
33
|
+
https://github.com/shaka-project/shaka-player/issues/6001
|
|
34
|
+
- Detect maximum HW resolution automatically on some platforms
|
|
35
|
+
|
|
36
|
+
v4.7 - 2023 Q4
|
|
25
37
|
- Common Media Server Data (CMSD)
|
|
26
38
|
https://github.com/shaka-project/shaka-player/issues/5890
|
|
27
|
-
|
|
28
|
-
|
|
39
|
+
- DASH: Handle mixed-codec variants
|
|
40
|
+
https://github.com/shaka-project/shaka-player/issues/5961
|
|
41
|
+
- Allow Media Source Recoveries
|
|
42
|
+
- UI: Add double tap to forward/rewind in the video
|
|
43
|
+
https://github.com/shaka-project/shaka-player/issues/3357
|
|
44
|
+
- Improve npm package size
|
|
45
|
+
https://github.com/shaka-project/shaka-player/issues/2172
|
|
29
46
|
|
|
30
47
|
v4.6 - 2023 Q4
|
|
31
48
|
- Content Steering
|