shaka-player 4.10.13 → 4.11.0
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/README.md +5 -1
- package/dist/controls.css +3 -3
- package/dist/controls.css.map +1 -1
- package/dist/deps.js +11 -11
- package/dist/locales.js +38 -34
- package/dist/shaka-player.compiled.d.ts +68 -33
- package/dist/shaka-player.compiled.debug.d.ts +68 -33
- package/dist/shaka-player.compiled.debug.externs.js +584 -544
- package/dist/shaka-player.compiled.debug.js +715 -676
- package/dist/shaka-player.compiled.debug.map +4 -4
- package/dist/shaka-player.compiled.externs.js +584 -544
- package/dist/shaka-player.compiled.js +1394 -1371
- package/dist/shaka-player.compiled.map +4 -4
- package/dist/shaka-player.ui.d.ts +76 -34
- package/dist/shaka-player.ui.debug.d.ts +76 -34
- package/dist/shaka-player.ui.debug.externs.js +77 -26
- package/dist/shaka-player.ui.debug.js +774 -720
- package/dist/shaka-player.ui.debug.map +4 -4
- package/dist/shaka-player.ui.externs.js +77 -26
- package/dist/shaka-player.ui.js +1649 -1617
- package/dist/shaka-player.ui.map +4 -4
- package/externs/mediasession.js +10 -1
- package/externs/shaka/ads.js +79 -2
- package/externs/shaka/manifest.js +6 -1
- package/externs/shaka/manifest_parser.js +6 -2
- package/externs/shaka/net.js +23 -2
- package/externs/shaka/player.js +414 -103
- package/externs/shaka/text.js +2 -1
- package/lib/abr/simple_abr_manager.js +5 -0
- package/lib/ads/ad_manager.js +45 -1
- package/lib/ads/ad_utils.js +124 -1
- package/lib/ads/interstitial_ad.js +9 -2
- package/lib/ads/interstitial_ad_manager.js +216 -101
- package/lib/ads/media_tailor_ad.js +1 -39
- package/lib/ads/media_tailor_ad_manager.js +32 -16
- package/lib/cea/cea608_data_channel.js +40 -8
- package/lib/cea/cea608_memory.js +28 -0
- package/lib/dash/dash_parser.js +162 -25
- package/lib/dash/segment_base.js +3 -2
- package/lib/dash/segment_list.js +6 -3
- package/lib/dash/segment_template.js +104 -20
- package/lib/hls/hls_parser.js +319 -215
- package/lib/media/drm_engine.js +11 -3
- package/lib/media/media_source_engine.js +0 -8
- package/lib/media/preload_manager.js +16 -4
- package/lib/media/segment_reference.js +3 -0
- package/lib/media/segment_utils.js +8 -1
- package/lib/media/streaming_engine.js +10 -6
- package/lib/mss/mss_parser.js +14 -2
- package/lib/net/http_fetch_plugin.js +12 -5
- package/lib/net/http_xhr_plugin.js +7 -3
- package/lib/net/networking_engine.js +21 -1
- package/lib/offline/manifest_converter.js +1 -0
- package/lib/offline/storage.js +1 -0
- package/lib/player.js +336 -121
- package/lib/text/cue.js +373 -0
- package/lib/text/mp4_ttml_parser.js +51 -1
- package/lib/text/mp4_vtt_parser.js +2 -5
- package/lib/text/sbv_text_parser.js +2 -31
- package/lib/text/srt_text_parser.js +1 -1
- package/lib/text/text_engine.js +1 -1
- package/lib/text/text_utils.js +1 -0
- package/lib/text/ttml_text_parser.js +14 -4
- package/lib/text/ui_text_displayer.js +0 -4
- package/lib/text/vtt_text_parser.js +10 -415
- package/lib/util/abortable_operation.js +8 -0
- package/lib/util/cmcd_manager.js +38 -0
- package/lib/util/error.js +16 -0
- package/lib/util/fake_event.js +1 -0
- package/lib/util/manifest_parser_utils.js +12 -3
- package/lib/util/player_configuration.js +4 -2
- package/lib/util/stream_utils.js +57 -2
- package/lib/util/tXml.js +2 -1
- package/lib/util/text_parser.js +46 -0
- package/lib/util/ts_parser.js +32 -3
- package/package.json +1 -1
- package/shaka-player.uncompiled.js +1 -1
- package/ui/chapter_selection.js +179 -0
- package/ui/controls.js +151 -0
- package/ui/enums.js +1 -0
- package/ui/externs/ui.js +7 -1
- package/ui/less/containers.less +3 -4
- package/ui/less/overflow_menu.less +5 -0
- package/ui/less/tooltip.less +1 -1
- package/ui/locales/en.json +1 -0
- package/ui/locales/es.json +1 -0
- package/ui/locales/fr.json +1 -0
- package/ui/locales/source.json +4 -0
- package/ui/seek_bar.js +3 -2
- package/ui/ui.js +2 -0
- package/lib/polyfill/abort_controller.js +0 -158
|
@@ -32,6 +32,7 @@ shaka.util.PlayerConfiguration = class {
|
|
|
32
32
|
// This is a relatively safe default in the absence of clues from the
|
|
33
33
|
// browser. For slower connections, the default estimate may be too high.
|
|
34
34
|
const bandwidthEstimate = 1e6; // 1Mbps
|
|
35
|
+
const minBytes = 16e3;
|
|
35
36
|
|
|
36
37
|
let abrMaxHeight = Infinity;
|
|
37
38
|
|
|
@@ -162,7 +163,6 @@ shaka.util.PlayerConfiguration = class {
|
|
|
162
163
|
ignoreManifestProgramDateTimeForTypes: [],
|
|
163
164
|
mediaPlaylistFullMimeType:
|
|
164
165
|
'video/mp2t; codecs="avc1.42E01E, mp4a.40.2"',
|
|
165
|
-
useSafariBehaviorForLive: true,
|
|
166
166
|
liveSegmentsDelay: 3,
|
|
167
167
|
sequenceMode: shaka.util.Platform.supportsSequenceMode(),
|
|
168
168
|
ignoreManifestTimestampsInSegmentsMode: false,
|
|
@@ -225,6 +225,7 @@ shaka.util.PlayerConfiguration = class {
|
|
|
225
225
|
autoLowLatencyMode: false,
|
|
226
226
|
forceHTTP: false,
|
|
227
227
|
forceHTTPS: false,
|
|
228
|
+
minBytesForProgressEvents: minBytes,
|
|
228
229
|
preferNativeHls: false,
|
|
229
230
|
updateIntervalSeconds: 1,
|
|
230
231
|
dispatchAllEmsgBoxes: false,
|
|
@@ -341,7 +342,7 @@ shaka.util.PlayerConfiguration = class {
|
|
|
341
342
|
},
|
|
342
343
|
advanced: {
|
|
343
344
|
minTotalBytes: 128e3,
|
|
344
|
-
minBytes
|
|
345
|
+
minBytes,
|
|
345
346
|
fastHalfLife: 2,
|
|
346
347
|
slowHalfLife: 5,
|
|
347
348
|
},
|
|
@@ -352,6 +353,7 @@ shaka.util.PlayerConfiguration = class {
|
|
|
352
353
|
safeMarginSwitch: 0,
|
|
353
354
|
cacheLoadThreshold: 20,
|
|
354
355
|
minTimeToSwitch: shaka.util.Platform.isApple() ? 0.5 : 0,
|
|
356
|
+
preferNetworkInformationBandwidth: false,
|
|
355
357
|
};
|
|
356
358
|
|
|
357
359
|
const cmcd = {
|
package/lib/util/stream_utils.js
CHANGED
|
@@ -118,9 +118,33 @@ shaka.util.StreamUtils = class {
|
|
|
118
118
|
validAudioStreamsMap.set(groupId, validAudioStreams);
|
|
119
119
|
}
|
|
120
120
|
|
|
121
|
+
// Keys based in MimeUtils.getNormalizedCodec. Lower is better
|
|
122
|
+
const videoCodecPreference = {
|
|
123
|
+
'vp8': 1,
|
|
124
|
+
'avc': 1,
|
|
125
|
+
'dovi-avc': 0.95,
|
|
126
|
+
'vp9': 0.9,
|
|
127
|
+
'vp09': 0.9,
|
|
128
|
+
'hevc': 0.85,
|
|
129
|
+
'dovi-hevc': 0.8,
|
|
130
|
+
'av01': 0.75,
|
|
131
|
+
'dovi-av1': 0.7,
|
|
132
|
+
'vvc': 0.65,
|
|
133
|
+
};
|
|
134
|
+
|
|
121
135
|
const videoStreams = Array.from(videoStreamsSet)
|
|
122
136
|
.sort((v1, v2) => {
|
|
123
|
-
if (!v1.bandwidth || !v2.bandwidth) {
|
|
137
|
+
if (!v1.bandwidth || !v2.bandwidth || v1.bandwidth == v2.bandwidth) {
|
|
138
|
+
if (v1.codecs && v2.codecs && v1.codecs != v2.codecs &&
|
|
139
|
+
v1.width == v2.width) {
|
|
140
|
+
const v1Codecs = MimeUtils.getNormalizedCodec(v1.codecs);
|
|
141
|
+
const v2Codecs = MimeUtils.getNormalizedCodec(v2.codecs);
|
|
142
|
+
if (v1Codecs != v2Codecs) {
|
|
143
|
+
const indexV1 = videoCodecPreference[v1Codecs] || 1;
|
|
144
|
+
const indexV2 = videoCodecPreference[v2Codecs] || 1;
|
|
145
|
+
return indexV1 - indexV2;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
124
148
|
return v1.width - v2.width;
|
|
125
149
|
}
|
|
126
150
|
return v1.bandwidth - v2.bandwidth;
|
|
@@ -1480,7 +1504,7 @@ shaka.util.StreamUtils = class {
|
|
|
1480
1504
|
// in DASH this information comes at the stream level and not at the
|
|
1481
1505
|
// segment level.
|
|
1482
1506
|
if (stream.segmentIndex) {
|
|
1483
|
-
reference = stream.segmentIndex.
|
|
1507
|
+
reference = stream.segmentIndex.earliestReference();
|
|
1484
1508
|
}
|
|
1485
1509
|
let layout = stream.tilesLayout;
|
|
1486
1510
|
if (reference) {
|
|
@@ -1860,6 +1884,37 @@ shaka.util.StreamUtils = class {
|
|
|
1860
1884
|
}
|
|
1861
1885
|
|
|
1862
1886
|
|
|
1887
|
+
/**
|
|
1888
|
+
* Set the best iframe stream to the original stream.
|
|
1889
|
+
*
|
|
1890
|
+
* @param {!shaka.extern.Stream} stream
|
|
1891
|
+
* @param {!Array.<!shaka.extern.Stream>} iFrameStreams
|
|
1892
|
+
*/
|
|
1893
|
+
static setBetterIFrameStream(stream, iFrameStreams) {
|
|
1894
|
+
if (!iFrameStreams.length) {
|
|
1895
|
+
return;
|
|
1896
|
+
}
|
|
1897
|
+
const validStreams = iFrameStreams.filter((iFrameStream) =>
|
|
1898
|
+
shaka.util.MimeUtils.getNormalizedCodec(stream.codecs) ==
|
|
1899
|
+
shaka.util.MimeUtils.getNormalizedCodec(iFrameStream.codecs))
|
|
1900
|
+
.sort((a, b) => {
|
|
1901
|
+
if (!a.bandwidth || !b.bandwidth || a.bandwidth == b.bandwidth) {
|
|
1902
|
+
return (a.width || 0) - (b.width || 0);
|
|
1903
|
+
}
|
|
1904
|
+
return a.bandwidth - b.bandwidth;
|
|
1905
|
+
});
|
|
1906
|
+
stream.trickModeVideo = validStreams[0];
|
|
1907
|
+
if (validStreams.length > 1) {
|
|
1908
|
+
const sameResolutionStream = validStreams.find((iFrameStream) =>
|
|
1909
|
+
stream.width == iFrameStream.width &&
|
|
1910
|
+
stream.height == iFrameStream.height);
|
|
1911
|
+
if (sameResolutionStream) {
|
|
1912
|
+
stream.trickModeVideo = sameResolutionStream;
|
|
1913
|
+
}
|
|
1914
|
+
}
|
|
1915
|
+
}
|
|
1916
|
+
|
|
1917
|
+
|
|
1863
1918
|
/**
|
|
1864
1919
|
* Returns a string of a variant, with the attribute values of its audio
|
|
1865
1920
|
* and/or video streams for log printing.
|
package/lib/util/tXml.js
CHANGED
|
@@ -62,7 +62,8 @@ shaka.util.TXml = class {
|
|
|
62
62
|
if (!expectedRootElemName && result.length) {
|
|
63
63
|
return result[0];
|
|
64
64
|
}
|
|
65
|
-
const rootNode = result.find((n) =>
|
|
65
|
+
const rootNode = result.find((n) =>
|
|
66
|
+
expectedRootElemName.split(',').includes(n.tagName));
|
|
66
67
|
if (rootNode) {
|
|
67
68
|
return rootNode;
|
|
68
69
|
}
|
package/lib/util/text_parser.js
CHANGED
|
@@ -131,4 +131,50 @@ shaka.util.TextParser = class {
|
|
|
131
131
|
};
|
|
132
132
|
}
|
|
133
133
|
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Parses a time.
|
|
137
|
+
*
|
|
138
|
+
* @return {?number}
|
|
139
|
+
*/
|
|
140
|
+
parseTime() {
|
|
141
|
+
const results = this.readRegex(shaka.util.TextParser.timeFormat_);
|
|
142
|
+
if (results == null) {
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
// This capture is optional, but will still be in the array as undefined,
|
|
146
|
+
// in which case it is 0.
|
|
147
|
+
const hours = Number(results[1]) || 0;
|
|
148
|
+
const minutes = Number(results[2]);
|
|
149
|
+
const seconds = Number(results[3]);
|
|
150
|
+
const milliseconds = Number(results[6]) || 0;
|
|
151
|
+
if (minutes > 59 || seconds > 59) {
|
|
152
|
+
return null;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return (milliseconds / 1000) + seconds + (minutes * 60) + (hours * 3600);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Parses a time from string.
|
|
160
|
+
*
|
|
161
|
+
* @param {?string} time
|
|
162
|
+
* @return {?number}
|
|
163
|
+
*/
|
|
164
|
+
static parseTime(time) {
|
|
165
|
+
if (!time) {
|
|
166
|
+
return null;
|
|
167
|
+
}
|
|
168
|
+
const parser = new shaka.util.TextParser(time);
|
|
169
|
+
return parser.parseTime();
|
|
170
|
+
}
|
|
134
171
|
};
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* @const
|
|
175
|
+
* @private {!RegExp}
|
|
176
|
+
* @example 00:00.000 or 00:00:00.000 or 0:00:00.000 or
|
|
177
|
+
* 00:00.00 or 00:00:00.00 or 0:00:00.00 or 00:00:00
|
|
178
|
+
*/
|
|
179
|
+
shaka.util.TextParser.timeFormat_ =
|
|
180
|
+
/(?:(\d{1,}):)?(\d{2}):(\d{2})((\.(\d{1,3})))?/g;
|
package/lib/util/ts_parser.js
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
goog.provide('shaka.util.TsParser');
|
|
8
8
|
|
|
9
|
+
goog.require('goog.asserts');
|
|
9
10
|
goog.require('shaka.Deprecate');
|
|
10
11
|
goog.require('shaka.log');
|
|
11
12
|
goog.require('shaka.util.ExpGolomb');
|
|
@@ -862,7 +863,8 @@ shaka.util.TsParser = class {
|
|
|
862
863
|
/**
|
|
863
864
|
* Return the video information
|
|
864
865
|
*
|
|
865
|
-
* @return {{height: ?string, width: ?string, codec: ?string
|
|
866
|
+
* @return {{height: ?string, width: ?string, codec: ?string,
|
|
867
|
+
* frameRate: ?string}}
|
|
866
868
|
* @export
|
|
867
869
|
*/
|
|
868
870
|
getVideoInfo() {
|
|
@@ -872,10 +874,32 @@ shaka.util.TsParser = class {
|
|
|
872
874
|
return this.getAvcInfo_();
|
|
873
875
|
}
|
|
874
876
|
|
|
877
|
+
/**
|
|
878
|
+
* @return {?string}
|
|
879
|
+
* @private
|
|
880
|
+
*/
|
|
881
|
+
getFrameRate_() {
|
|
882
|
+
const timescale = shaka.util.TsParser.Timescale;
|
|
883
|
+
const videoData = this.getVideoData();
|
|
884
|
+
if (videoData.length > 1) {
|
|
885
|
+
const firstPts = videoData[0].pts;
|
|
886
|
+
goog.asserts.assert(typeof(firstPts) == 'number',
|
|
887
|
+
'Should be an number!');
|
|
888
|
+
const secondPts = videoData[1].pts;
|
|
889
|
+
goog.asserts.assert(typeof(secondPts) == 'number',
|
|
890
|
+
'Should be an number!');
|
|
891
|
+
if (!isNaN(secondPts - firstPts)) {
|
|
892
|
+
return String(1 / (secondPts - firstPts) * timescale);
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
return null;
|
|
896
|
+
}
|
|
897
|
+
|
|
875
898
|
/**
|
|
876
899
|
* Return the video information for AVC
|
|
877
900
|
*
|
|
878
|
-
* @return {{height: ?string, width: ?string, codec: ?string
|
|
901
|
+
* @return {{height: ?string, width: ?string, codec: ?string,
|
|
902
|
+
* frameRate: ?string}}
|
|
879
903
|
* @private
|
|
880
904
|
*/
|
|
881
905
|
getAvcInfo_() {
|
|
@@ -884,6 +908,7 @@ shaka.util.TsParser = class {
|
|
|
884
908
|
height: null,
|
|
885
909
|
width: null,
|
|
886
910
|
codec: null,
|
|
911
|
+
frameRate: null,
|
|
887
912
|
};
|
|
888
913
|
const videoNalus = this.getVideoNalus();
|
|
889
914
|
if (!videoNalus.length) {
|
|
@@ -995,6 +1020,7 @@ shaka.util.TsParser = class {
|
|
|
995
1020
|
frameCropLeftOffset * 2 - frameCropRightOffset * 2);
|
|
996
1021
|
videoInfo.codec = 'avc1.' + this.byteToHex_(profileIdc) +
|
|
997
1022
|
this.byteToHex_(profileCompatibility) + this.byteToHex_(levelIdc);
|
|
1023
|
+
videoInfo.frameRate = this.getFrameRate_();
|
|
998
1024
|
|
|
999
1025
|
return videoInfo;
|
|
1000
1026
|
}
|
|
@@ -1002,7 +1028,8 @@ shaka.util.TsParser = class {
|
|
|
1002
1028
|
/**
|
|
1003
1029
|
* Return the video information for HVC
|
|
1004
1030
|
*
|
|
1005
|
-
* @return {{height: ?string, width: ?string, codec: ?string
|
|
1031
|
+
* @return {{height: ?string, width: ?string, codec: ?string,
|
|
1032
|
+
* frameRate: ?string}}
|
|
1006
1033
|
* @private
|
|
1007
1034
|
*/
|
|
1008
1035
|
getHvcInfo_() {
|
|
@@ -1011,6 +1038,7 @@ shaka.util.TsParser = class {
|
|
|
1011
1038
|
height: null,
|
|
1012
1039
|
width: null,
|
|
1013
1040
|
codec: null,
|
|
1041
|
+
frameRate: null,
|
|
1014
1042
|
};
|
|
1015
1043
|
const videoNalus = this.getVideoNalus();
|
|
1016
1044
|
if (!videoNalus.length) {
|
|
@@ -1135,6 +1163,7 @@ shaka.util.TsParser = class {
|
|
|
1135
1163
|
generalConstraintIndicatorFlags1.toString(16).toUpperCase();
|
|
1136
1164
|
}
|
|
1137
1165
|
videoInfo.codec = codec;
|
|
1166
|
+
videoInfo.frameRate = this.getFrameRate_();
|
|
1138
1167
|
|
|
1139
1168
|
return videoInfo;
|
|
1140
1169
|
}
|
package/package.json
CHANGED
|
@@ -33,7 +33,6 @@ goog.require('shaka.offline.OfflineManifestParser');
|
|
|
33
33
|
goog.require('shaka.offline.OfflineScheme');
|
|
34
34
|
goog.require('shaka.offline.Storage');
|
|
35
35
|
goog.require('shaka.offline.indexeddb.StorageMechanism');
|
|
36
|
-
goog.require('shaka.polyfill.AbortController');
|
|
37
36
|
goog.require('shaka.polyfill.Aria');
|
|
38
37
|
goog.require('shaka.polyfill.EncryptionScheme');
|
|
39
38
|
goog.require('shaka.polyfill.Fullscreen');
|
|
@@ -90,6 +89,7 @@ goog.require('shaka.ui.AdStatisticsButton');
|
|
|
90
89
|
goog.require('shaka.ui.AirPlayButton');
|
|
91
90
|
goog.require('shaka.ui.BigPlayButton');
|
|
92
91
|
goog.require('shaka.ui.CastButton');
|
|
92
|
+
goog.require('shaka.ui.ChapterSelection');
|
|
93
93
|
goog.require('shaka.ui.ContextMenu');
|
|
94
94
|
goog.require('shaka.ui.Element');
|
|
95
95
|
goog.require('shaka.ui.FastForwardButton');
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
/*! @license
|
|
2
|
+
* Shaka Player
|
|
3
|
+
* Copyright 2016 Google LLC
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
goog.provide('shaka.ui.ChapterSelection');
|
|
9
|
+
|
|
10
|
+
goog.require('shaka.ui.Controls');
|
|
11
|
+
goog.require('shaka.ui.Enums');
|
|
12
|
+
goog.require('shaka.ui.Locales');
|
|
13
|
+
goog.require('shaka.ui.Localization');
|
|
14
|
+
goog.require('shaka.ui.OverflowMenu');
|
|
15
|
+
goog.require('shaka.ui.SettingsMenu');
|
|
16
|
+
goog.require('shaka.ui.Utils');
|
|
17
|
+
goog.require('shaka.util.Dom');
|
|
18
|
+
goog.requireType('shaka.ui.Controls');
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* @extends {shaka.ui.SettingsMenu}
|
|
22
|
+
* @final
|
|
23
|
+
* @export
|
|
24
|
+
*/
|
|
25
|
+
shaka.ui.ChapterSelection = class extends shaka.ui.SettingsMenu {
|
|
26
|
+
/**
|
|
27
|
+
* @param {!HTMLElement} parent
|
|
28
|
+
* @param {!shaka.ui.Controls} controls
|
|
29
|
+
*/
|
|
30
|
+
constructor(parent, controls) {
|
|
31
|
+
super(parent, controls, shaka.ui.Enums.MaterialDesignIcons.CHAPTER);
|
|
32
|
+
|
|
33
|
+
this.button.classList.add('shaka-chapter-button');
|
|
34
|
+
this.menu.classList.add('shaka-chapters');
|
|
35
|
+
this.button.classList.add('shaka-tooltip-status');
|
|
36
|
+
|
|
37
|
+
/** @type {!Array<shaka.extern.Chapter>} */
|
|
38
|
+
this.chapters_ = [];
|
|
39
|
+
|
|
40
|
+
this.chaptersLanguage_ = 'und';
|
|
41
|
+
|
|
42
|
+
this.eventManager.listen(
|
|
43
|
+
this.localization, shaka.ui.Localization.LOCALE_UPDATED, () => {
|
|
44
|
+
this.updateLocalizedStrings_();
|
|
45
|
+
this.updateChapters_();
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
this.eventManager.listen(
|
|
49
|
+
this.localization, shaka.ui.Localization.LOCALE_CHANGED, () => {
|
|
50
|
+
this.updateLocalizedStrings_();
|
|
51
|
+
this.updateChapters_();
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
this.eventManager.listen(this.player, 'unloading', () => {
|
|
55
|
+
this.deletePreviousChapters_();
|
|
56
|
+
this.chaptersLanguage_ = 'und';
|
|
57
|
+
this.chapters_ = [];
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
this.eventManager.listen(this.player, 'trackschanged', () => {
|
|
61
|
+
this.updateChapters_();
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
// Set up all the strings in the user's preferred language.
|
|
65
|
+
this.updateLocalizedStrings_();
|
|
66
|
+
|
|
67
|
+
this.updateChapters_();
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* @private
|
|
72
|
+
*/
|
|
73
|
+
updateLocalizedStrings_() {
|
|
74
|
+
const LocIds = shaka.ui.Locales.Ids;
|
|
75
|
+
|
|
76
|
+
this.backButton.ariaLabel = this.localization.resolve(LocIds.BACK);
|
|
77
|
+
this.button.ariaLabel = this.localization.resolve(LocIds.CHAPTERS);
|
|
78
|
+
this.nameSpan.textContent = this.localization.resolve(LocIds.CHAPTERS);
|
|
79
|
+
this.backSpan.textContent = this.localization.resolve(LocIds.CHAPTERS);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* @private
|
|
84
|
+
*/
|
|
85
|
+
deletePreviousChapters_() {
|
|
86
|
+
// 1. Save the back to menu button
|
|
87
|
+
const backButton = shaka.ui.Utils.getFirstDescendantWithClassName(
|
|
88
|
+
this.menu, 'shaka-back-to-overflow-button');
|
|
89
|
+
|
|
90
|
+
// 2. Remove everything
|
|
91
|
+
shaka.util.Dom.removeAllChildren(this.menu);
|
|
92
|
+
|
|
93
|
+
// 3. Add the backTo Menu button back
|
|
94
|
+
this.menu.appendChild(backButton);
|
|
95
|
+
|
|
96
|
+
// 4. Hidden button
|
|
97
|
+
shaka.ui.Utils.setDisplay(this.button, false);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* @private
|
|
102
|
+
*/
|
|
103
|
+
updateChapters_() {
|
|
104
|
+
/**
|
|
105
|
+
* Does a value compare on chapters.
|
|
106
|
+
* @param {shaka.extern.Chapter} a
|
|
107
|
+
* @param {shaka.extern.Chapter} b
|
|
108
|
+
* @return {boolean}
|
|
109
|
+
*/
|
|
110
|
+
const chaptersEqual = (a, b) => {
|
|
111
|
+
return (!a && !b) || (a.id === b.id && a.title === b.title &&
|
|
112
|
+
a.startTime === b.startTime && a.endTime === b.endTime);
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
let nextLanguage = 'und';
|
|
116
|
+
/** @type {!Array<shaka.extern.Chapter>} */
|
|
117
|
+
let nextChapters = [];
|
|
118
|
+
|
|
119
|
+
const currentLocales = this.localization.getCurrentLocales();
|
|
120
|
+
for (const locale of Array.from(currentLocales)) {
|
|
121
|
+
nextLanguage = locale;
|
|
122
|
+
nextChapters = this.player.getChapters(nextLanguage);
|
|
123
|
+
if (nextChapters.length) {
|
|
124
|
+
break;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
if (!nextChapters.length) {
|
|
128
|
+
nextLanguage = 'und';
|
|
129
|
+
nextChapters = this.player.getChapters(nextLanguage);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const languageChanged = nextLanguage !== this.chaptersLanguage_;
|
|
133
|
+
const chaptersChanged = this.chapters_.length !== nextChapters.length ||
|
|
134
|
+
!this.chapters_.some((c, idx) => {
|
|
135
|
+
const n = nextChapters.at(idx);
|
|
136
|
+
return chaptersEqual(c, n) ||
|
|
137
|
+
nextChapters.some((n) => chaptersEqual(c, n));
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
this.chaptersLanguage_ = nextLanguage;
|
|
141
|
+
this.chapters_ = nextChapters;
|
|
142
|
+
if (!nextChapters.length) {
|
|
143
|
+
this.deletePreviousChapters_();
|
|
144
|
+
} else if (languageChanged || chaptersChanged) {
|
|
145
|
+
for (const chapter of this.chapters_) {
|
|
146
|
+
const button = shaka.util.Dom.createButton();
|
|
147
|
+
const span = shaka.util.Dom.createHTMLElement('span');
|
|
148
|
+
span.classList.add('shaka-chapter');
|
|
149
|
+
span.textContent = chapter.title;
|
|
150
|
+
button.appendChild(span);
|
|
151
|
+
|
|
152
|
+
this.eventManager.listen(button, 'click', () => {
|
|
153
|
+
this.video.currentTime = chapter.startTime;
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
this.menu.appendChild(button);
|
|
157
|
+
}
|
|
158
|
+
shaka.ui.Utils.setDisplay(this.button, true);
|
|
159
|
+
shaka.ui.Utils.focusOnTheChosenItem(this.menu);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* @implements {shaka.extern.IUIElement.Factory}
|
|
166
|
+
* @final
|
|
167
|
+
*/
|
|
168
|
+
shaka.ui.ChapterSelection.Factory = class {
|
|
169
|
+
/** @override */
|
|
170
|
+
create(rootElement, controls) {
|
|
171
|
+
return new shaka.ui.ChapterSelection(rootElement, controls);
|
|
172
|
+
}
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
shaka.ui.OverflowMenu.registerElement(
|
|
176
|
+
'chapter', new shaka.ui.ChapterSelection.Factory());
|
|
177
|
+
|
|
178
|
+
shaka.ui.Controls.registerElement(
|
|
179
|
+
'chapter', new shaka.ui.ChapterSelection.Factory());
|
package/ui/controls.js
CHANGED
|
@@ -181,6 +181,7 @@ shaka.ui.Controls = class extends shaka.util.FakeEventTarget {
|
|
|
181
181
|
// Configure and create the layout of the controls
|
|
182
182
|
this.configure(this.config_);
|
|
183
183
|
this.addEventListeners_();
|
|
184
|
+
this.setupMediaSession_();
|
|
184
185
|
|
|
185
186
|
/**
|
|
186
187
|
* The pressed keys set is used to record which keys are currently pressed
|
|
@@ -274,6 +275,8 @@ shaka.ui.Controls = class extends shaka.util.FakeEventTarget {
|
|
|
274
275
|
this.localization_ = null;
|
|
275
276
|
this.pressedKeys_.clear();
|
|
276
277
|
|
|
278
|
+
this.removeMediaSession_();
|
|
279
|
+
|
|
277
280
|
// FakeEventTarget implements IReleasable
|
|
278
281
|
super.release();
|
|
279
282
|
}
|
|
@@ -1235,6 +1238,154 @@ shaka.ui.Controls = class extends shaka.util.FakeEventTarget {
|
|
|
1235
1238
|
}
|
|
1236
1239
|
|
|
1237
1240
|
|
|
1241
|
+
/**
|
|
1242
|
+
* @private
|
|
1243
|
+
*/
|
|
1244
|
+
setupMediaSession_() {
|
|
1245
|
+
if (!this.config_.setupMediaSession || !navigator.mediaSession) {
|
|
1246
|
+
return;
|
|
1247
|
+
}
|
|
1248
|
+
const addMediaSessionHandler = (type, callback) => {
|
|
1249
|
+
try {
|
|
1250
|
+
navigator.mediaSession.setActionHandler(type, (details) => {
|
|
1251
|
+
callback(details);
|
|
1252
|
+
});
|
|
1253
|
+
} catch (error) {
|
|
1254
|
+
shaka.log.warning(
|
|
1255
|
+
`The "${type}" media session action is not supported.`);
|
|
1256
|
+
}
|
|
1257
|
+
};
|
|
1258
|
+
const updatePositionState = () => {
|
|
1259
|
+
const seekRange = this.player_.seekRange();
|
|
1260
|
+
let duration = seekRange.end - seekRange.start;
|
|
1261
|
+
const position = parseFloat(
|
|
1262
|
+
(this.video_.currentTime - seekRange.start).toFixed(2));
|
|
1263
|
+
if (this.player_.isLive() && Math.abs(duration - position) < 1) {
|
|
1264
|
+
// Positive infinity indicates media without a defined end, such as a
|
|
1265
|
+
// live stream.
|
|
1266
|
+
duration = Infinity;
|
|
1267
|
+
}
|
|
1268
|
+
try {
|
|
1269
|
+
if ((this.ad_ && this.ad_.isLinear())) {
|
|
1270
|
+
navigator.mediaSession.setPositionState();
|
|
1271
|
+
} else {
|
|
1272
|
+
navigator.mediaSession.setPositionState({
|
|
1273
|
+
duration: Math.max(0, duration),
|
|
1274
|
+
playbackRate: this.video_.playbackRate,
|
|
1275
|
+
position: Math.max(0, position),
|
|
1276
|
+
});
|
|
1277
|
+
}
|
|
1278
|
+
} catch (error) {
|
|
1279
|
+
shaka.log.warning(
|
|
1280
|
+
'setPositionState in media session is not supported.');
|
|
1281
|
+
}
|
|
1282
|
+
};
|
|
1283
|
+
const commonHandler = (details) => {
|
|
1284
|
+
const keyboardSeekDistance = this.config_.keyboardSeekDistance;
|
|
1285
|
+
const seekRange = this.player_.seekRange();
|
|
1286
|
+
switch (details.action) {
|
|
1287
|
+
case 'pause':
|
|
1288
|
+
this.onPlayPauseClick_();
|
|
1289
|
+
break;
|
|
1290
|
+
case 'play':
|
|
1291
|
+
this.onPlayPauseClick_();
|
|
1292
|
+
break;
|
|
1293
|
+
case 'seekbackward':
|
|
1294
|
+
if (!this.ad_ || !this.ad_.isLinear()) {
|
|
1295
|
+
this.seek_(this.seekBar_.getValue() -
|
|
1296
|
+
(details.seekOffset || keyboardSeekDistance));
|
|
1297
|
+
}
|
|
1298
|
+
break;
|
|
1299
|
+
case 'seekforward':
|
|
1300
|
+
if (!this.ad_ || !this.ad_.isLinear()) {
|
|
1301
|
+
this.seek_(this.seekBar_.getValue() +
|
|
1302
|
+
(details.seekOffset || keyboardSeekDistance));
|
|
1303
|
+
}
|
|
1304
|
+
break;
|
|
1305
|
+
case 'seekto':
|
|
1306
|
+
if (!this.ad_ || !this.ad_.isLinear()) {
|
|
1307
|
+
this.seek_(seekRange.start + details.seekTime);
|
|
1308
|
+
}
|
|
1309
|
+
break;
|
|
1310
|
+
case 'stop':
|
|
1311
|
+
this.player_.unload();
|
|
1312
|
+
break;
|
|
1313
|
+
case 'enterpictureinpicture':
|
|
1314
|
+
if (!this.ad_ || !this.ad_.isLinear()) {
|
|
1315
|
+
this.togglePiP();
|
|
1316
|
+
}
|
|
1317
|
+
break;
|
|
1318
|
+
}
|
|
1319
|
+
};
|
|
1320
|
+
|
|
1321
|
+
addMediaSessionHandler('pause', commonHandler);
|
|
1322
|
+
addMediaSessionHandler('play', commonHandler);
|
|
1323
|
+
addMediaSessionHandler('seekbackward', commonHandler);
|
|
1324
|
+
addMediaSessionHandler('seekforward', commonHandler);
|
|
1325
|
+
addMediaSessionHandler('seekto', commonHandler);
|
|
1326
|
+
addMediaSessionHandler('stop', commonHandler);
|
|
1327
|
+
addMediaSessionHandler('enterpictureinpicture', commonHandler);
|
|
1328
|
+
|
|
1329
|
+
this.eventManager_.listen(this.video_, 'timeupdate', () => {
|
|
1330
|
+
updatePositionState();
|
|
1331
|
+
});
|
|
1332
|
+
|
|
1333
|
+
this.eventManager_.listen(this.player_, 'metadata', (event) => {
|
|
1334
|
+
const payload = event['payload'];
|
|
1335
|
+
if (!payload) {
|
|
1336
|
+
return;
|
|
1337
|
+
}
|
|
1338
|
+
let title;
|
|
1339
|
+
if (payload['key'] == 'TIT2' && payload['data']) {
|
|
1340
|
+
title = payload['data'];
|
|
1341
|
+
}
|
|
1342
|
+
let imageUrl;
|
|
1343
|
+
if (payload['key'] == 'APIC' && payload['mimeType'] == '-->') {
|
|
1344
|
+
imageUrl = payload['data'];
|
|
1345
|
+
}
|
|
1346
|
+
if (navigator.mediaSession.metadata && title) {
|
|
1347
|
+
navigator.mediaSession.metadata.title = title;
|
|
1348
|
+
}
|
|
1349
|
+
if (imageUrl) {
|
|
1350
|
+
const video = /** @type {HTMLVideoElement} */ (this.localVideo_);
|
|
1351
|
+
if (imageUrl != video.poster) {
|
|
1352
|
+
video.poster = imageUrl;
|
|
1353
|
+
}
|
|
1354
|
+
if (navigator.mediaSession.metadata) {
|
|
1355
|
+
navigator.mediaSession.metadata.artwork = [{src: imageUrl}];
|
|
1356
|
+
}
|
|
1357
|
+
}
|
|
1358
|
+
});
|
|
1359
|
+
}
|
|
1360
|
+
|
|
1361
|
+
|
|
1362
|
+
/**
|
|
1363
|
+
* @private
|
|
1364
|
+
*/
|
|
1365
|
+
removeMediaSession_() {
|
|
1366
|
+
if (!this.config_.setupMediaSession || !navigator.mediaSession) {
|
|
1367
|
+
return;
|
|
1368
|
+
}
|
|
1369
|
+
try {
|
|
1370
|
+
navigator.mediaSession.setPositionState();
|
|
1371
|
+
} catch (error) {}
|
|
1372
|
+
|
|
1373
|
+
const disableMediaSessionHandler = (type) => {
|
|
1374
|
+
try {
|
|
1375
|
+
navigator.mediaSession.setActionHandler(type, null);
|
|
1376
|
+
} catch (error) {}
|
|
1377
|
+
};
|
|
1378
|
+
|
|
1379
|
+
disableMediaSessionHandler('pause');
|
|
1380
|
+
disableMediaSessionHandler('play');
|
|
1381
|
+
disableMediaSessionHandler('seekbackward');
|
|
1382
|
+
disableMediaSessionHandler('seekforward');
|
|
1383
|
+
disableMediaSessionHandler('seekto');
|
|
1384
|
+
disableMediaSessionHandler('stop');
|
|
1385
|
+
disableMediaSessionHandler('enterpictureinpicture');
|
|
1386
|
+
}
|
|
1387
|
+
|
|
1388
|
+
|
|
1238
1389
|
/**
|
|
1239
1390
|
* When a mobile device is rotated to landscape layout, and the video is
|
|
1240
1391
|
* loaded, make the demo app go into fullscreen.
|
package/ui/enums.js
CHANGED
package/ui/externs/ui.js
CHANGED
|
@@ -96,7 +96,8 @@ shaka.extern.UIVolumeBarColors;
|
|
|
96
96
|
* tapSeekDistance: number,
|
|
97
97
|
* refreshTickInSeconds: number,
|
|
98
98
|
* displayInVrMode: boolean,
|
|
99
|
-
* defaultVrProjectionMode: string
|
|
99
|
+
* defaultVrProjectionMode: string,
|
|
100
|
+
* setupMediaSession: boolean
|
|
100
101
|
* }}
|
|
101
102
|
*
|
|
102
103
|
* @property {!Array.<string>} controlPanelElements
|
|
@@ -247,6 +248,11 @@ shaka.extern.UIVolumeBarColors;
|
|
|
247
248
|
* Indicate the default VR projection mode.
|
|
248
249
|
* Possible values: <code>'equirectangular'</code> or <code>'cubemap'</code>.
|
|
249
250
|
* Defaults to <code>'equirectangular'</code>.
|
|
251
|
+
* @property {boolean} setupMediaSession
|
|
252
|
+
* If true, MediaSession controls will be managed by the UI. It will also use
|
|
253
|
+
* the ID3 APIC and TIT2 as image and title in Media Session, and ID3 APIC
|
|
254
|
+
* will also be used to change video poster.
|
|
255
|
+
* Defaults to true.
|
|
250
256
|
* @exportDoc
|
|
251
257
|
*/
|
|
252
258
|
shaka.extern.UIConfiguration;
|
package/ui/less/containers.less
CHANGED
|
@@ -22,10 +22,9 @@
|
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
/* Set the fonts for all other content. */
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
}
|
|
25
|
+
font-family: Roboto, sans-serif, TengwarTelcontar;
|
|
26
|
+
font-weight: normal;
|
|
27
|
+
-webkit-font-smoothing: antialiased;
|
|
29
28
|
}
|
|
30
29
|
|
|
31
30
|
/* Each browser has a different prefixed pseudo-class for fullscreened elements.
|