shaka-player 3.2.2 → 3.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/ISSUE_TEMPLATE/bug_report.md +1 -1
- package/.github/ISSUE_TEMPLATE/feature_request.md +1 -1
- package/.github/ISSUE_TEMPLATE/question.md +1 -1
- package/.github/workflows/build_and_test.yaml +73 -0
- package/.github/workflows/release.yaml +53 -0
- package/AUTHORS +6 -5
- package/CHANGELOG.md +314 -1
- package/CONTRIBUTORS +4 -2
- package/build/checkversion.py +1 -1
- package/build/conformance.textproto +183 -169
- package/build/test.py +6 -0
- package/build/types/core +3 -0
- package/build/types/offline +1 -0
- package/build/types/polyfill +1 -0
- package/build/types/ui +2 -0
- package/demo/common/assets.js +18 -0
- package/demo/common/message_ids.js +13 -1
- package/demo/config.js +31 -1
- package/demo/index.html +1 -1
- package/demo/locales/en.json +12 -0
- package/demo/locales/source.json +48 -0
- package/dist/controls.css +26 -2
- package/dist/controls.css.map +1 -1
- package/dist/demo.compiled.debug.js +19 -16
- package/dist/demo.compiled.debug.map +3 -3
- package/dist/demo.compiled.js +19 -16
- package/dist/demo.compiled.map +3 -3
- package/dist/deps.js +21 -14
- package/dist/locales.js +31 -29
- package/dist/receiver.compiled.debug.js +11 -9
- package/dist/receiver.compiled.debug.map +3 -3
- package/dist/receiver.compiled.js +11 -9
- package/dist/receiver.compiled.map +3 -3
- package/dist/shaka-player.compiled.d.ts +232 -18
- package/dist/shaka-player.compiled.debug.d.ts +232 -18
- package/dist/shaka-player.compiled.debug.externs.js +293 -17
- package/dist/shaka-player.compiled.debug.js +481 -422
- package/dist/shaka-player.compiled.debug.map +4 -4
- package/dist/shaka-player.compiled.externs.js +293 -17
- package/dist/shaka-player.compiled.js +846 -817
- package/dist/shaka-player.compiled.map +4 -4
- package/dist/shaka-player.ui.d.ts +250 -30
- package/dist/shaka-player.ui.debug.d.ts +250 -30
- package/dist/shaka-player.ui.debug.externs.js +322 -28
- package/dist/shaka-player.ui.debug.js +630 -553
- package/dist/shaka-player.ui.debug.map +4 -4
- package/dist/shaka-player.ui.externs.js +322 -28
- package/dist/shaka-player.ui.js +1048 -1006
- package/dist/shaka-player.ui.map +4 -4
- package/docs/design/bg-fetch-after.gv +29 -0
- package/docs/design/bg-fetch-after.gv.png +0 -0
- package/docs/design/bg-fetch-before.gv +18 -0
- package/docs/design/bg-fetch-before.gv.png +0 -0
- package/docs/design/bg-fetch.md +134 -0
- package/docs/tutorials/blob-url.md +15 -0
- package/docs/tutorials/index.json +1 -0
- package/docs/tutorials/ui-customization.md +66 -0
- package/docs/tutorials/ui.md +5 -5
- package/externs/cmcd.js +186 -0
- package/externs/ima.js +3 -0
- package/externs/shaka/ads.js +5 -0
- package/externs/shaka/manifest.js +12 -1
- package/externs/shaka/manifest_parser.js +10 -1
- package/externs/shaka/net.js +20 -4
- package/externs/shaka/offline.js +31 -2
- package/externs/shaka/player.js +138 -5
- package/externs/shaka/text.js +24 -0
- package/externs/webcrypto.js +18 -0
- package/karma.conf.js +19 -15
- package/lib/abr/ewma.js +14 -0
- package/lib/abr/ewma_bandwidth_estimator.js +16 -2
- package/lib/abr/simple_abr_manager.js +6 -0
- package/lib/ads/ad_manager.js +1 -0
- package/lib/ads/client_side_ad.js +8 -0
- package/lib/ads/client_side_ad_manager.js +8 -4
- package/lib/ads/server_side_ad.js +7 -0
- package/lib/cast/cast_proxy.js +3 -0
- package/lib/cast/cast_receiver.js +3 -0
- package/lib/cast/cast_utils.js +1 -0
- package/lib/dash/dash_parser.js +79 -33
- package/lib/dash/segment_base.js +25 -1
- package/lib/dash/segment_list.js +18 -9
- package/lib/dash/segment_template.js +23 -10
- package/lib/hls/hls_parser.js +116 -23
- package/lib/hls/manifest_text_parser.js +1 -0
- package/lib/media/quality_observer.js +274 -0
- package/lib/media/region_observer.js +39 -33
- package/lib/media/region_timeline.js +17 -19
- package/lib/media/segment_index.js +8 -1
- package/lib/media/segment_reference.js +37 -4
- package/lib/media/streaming_engine.js +47 -6
- package/lib/net/http_fetch_plugin.js +31 -11
- package/lib/net/http_plugin_utils.js +1 -0
- package/lib/net/http_xhr_plugin.js +40 -14
- package/lib/net/networking_engine.js +85 -18
- package/lib/offline/download_info.js +68 -0
- package/lib/offline/download_manager.js +13 -5
- package/lib/offline/indexeddb/base_storage_cell.js +35 -0
- package/lib/offline/indexeddb/storage_mechanism.js +1 -1
- package/lib/offline/indexeddb/v5_storage_cell.js +5 -0
- package/lib/offline/storage.js +401 -293
- package/lib/offline/stored_content_utils.js +2 -0
- package/lib/player.js +254 -27
- package/lib/polyfill/media_capabilities.js +30 -15
- package/lib/polyfill/random_uuid.js +52 -0
- package/lib/text/cue.js +18 -0
- package/lib/text/ttml_text_parser.js +109 -60
- package/lib/text/ui_text_displayer.js +19 -13
- package/lib/util/cmcd_manager.js +672 -0
- package/lib/util/error.js +6 -0
- package/lib/util/fake_event_target.js +21 -1
- package/lib/util/language_utils.js +12 -0
- package/lib/util/mutex.js +52 -0
- package/lib/util/periods.js +98 -64
- package/lib/util/player_configuration.js +18 -0
- package/lib/util/stream_utils.js +86 -10
- package/lib/util/xml_utils.js +19 -0
- package/package.json +3 -6
- package/shaka-player.uncompiled.js +3 -0
- package/test/dash/dash_parser_content_protection_unit.js +2 -0
- package/test/dash/dash_parser_live_unit.js +144 -1
- package/test/dash/dash_parser_manifest_unit.js +37 -2
- package/test/dash/dash_parser_segment_base_unit.js +2 -0
- package/test/dash/dash_parser_segment_list_unit.js +2 -1
- package/test/dash/dash_parser_segment_template_unit.js +2 -1
- package/test/hls/hls_live_unit.js +2 -0
- package/test/hls/hls_parser_unit.js +76 -0
- package/test/media/drm_engine_unit.js +3 -3
- package/test/media/quality_observer_unit.js +142 -0
- package/test/media/region_observer_unit.js +40 -5
- package/test/media/region_timeline_unit.js +40 -22
- package/test/media/segment_reference_unit.js +16 -1
- package/test/media/streaming_engine_integration.js +4 -2
- package/test/media/streaming_engine_unit.js +147 -0
- package/test/net/http_plugin_unit.js +20 -15
- package/test/offline/storage_integration.js +108 -4
- package/test/player_integration.js +140 -8
- package/test/player_src_equals_integration.js +41 -8
- package/test/player_unit.js +19 -9
- package/test/test/assets/chapters.vtt +2 -2
- package/test/test/assets/chapters2.vtt +10 -0
- package/test/test/util/dash_parser_util.js +4 -0
- package/test/test/util/fake_ad.js +7 -0
- package/test/test/util/fake_drm_engine.js +9 -2
- package/test/test/util/manifest_generator.js +3 -2
- package/test/test/util/test_scheme.js +14 -1
- package/test/test/util/ttml_utils.js +81 -0
- package/test/test/util/util.js +10 -7
- package/test/text/mp4_ttml_parser_unit.js +11 -18
- package/test/text/ttml_text_parser_unit.js +544 -228
- package/test/ui/text_displayer_layout_unit.js +8 -7
- package/test/ui/ui_unit.js +250 -0
- package/test/util/cmcd_manager_unit.js +291 -0
- package/test/util/stream_utils_unit.js +36 -30
- package/third_party/closure-uri/uri.js +21 -0
- package/ui/airplay_button.js +1 -0
- package/ui/audio_language_selection.js +7 -0
- package/ui/cast_button.js +1 -0
- package/ui/context_menu.js +109 -0
- package/ui/controls.js +44 -7
- package/ui/controls.less +1 -0
- package/ui/enums.js +4 -0
- package/ui/externs/ui.js +28 -1
- package/ui/fast_forward_button.js +14 -5
- package/ui/fullscreen_button.js +1 -0
- package/ui/less/buttons.less +0 -4
- package/ui/less/containers.less +74 -0
- package/ui/less/overflow_menu.less +0 -10
- package/ui/less/tooltip.less +97 -0
- package/ui/locales/en.json +1 -0
- package/ui/locales/source.json +4 -0
- package/ui/localization.js +12 -36
- package/ui/loop_button.js +5 -0
- package/ui/mute_button.js +2 -1
- package/ui/overflow_menu.js +5 -25
- package/ui/pip_button.js +5 -0
- package/ui/play_button.js +2 -2
- package/ui/playback_rate_selection.js +8 -9
- package/ui/resolution_selection.js +7 -0
- package/ui/rewind_button.js +15 -6
- package/ui/seek_bar.js +4 -2
- package/ui/settings_menu.js +36 -4
- package/ui/small_play_button.js +1 -0
- package/ui/statistics_button.js +253 -0
- package/ui/text_selection.js +12 -4
- package/ui/ui.js +30 -0
- package/ui/volume_bar.js +1 -1
- package/externs/media_capabilities.js +0 -16
package/lib/hls/hls_parser.js
CHANGED
|
@@ -25,6 +25,7 @@ goog.require('shaka.net.DataUriPlugin');
|
|
|
25
25
|
goog.require('shaka.net.NetworkingEngine');
|
|
26
26
|
goog.require('shaka.util.ArrayUtils');
|
|
27
27
|
goog.require('shaka.util.BufferUtils');
|
|
28
|
+
goog.require('shaka.util.CmcdManager');
|
|
28
29
|
goog.require('shaka.util.DataViewReader');
|
|
29
30
|
goog.require('shaka.util.Error');
|
|
30
31
|
goog.require('shaka.util.FakeEvent');
|
|
@@ -332,7 +333,8 @@ shaka.hls.HlsParser = class {
|
|
|
332
333
|
const segments = await this.createSegments_(
|
|
333
334
|
streamInfo.verbatimMediaPlaylistUri, playlist, stream.type,
|
|
334
335
|
stream.mimeType, streamInfo.mediaSequenceToStartTime, mediaVariables,
|
|
335
|
-
streamInfo.discontinuityToMediaSequence
|
|
336
|
+
streamInfo.discontinuityToMediaSequence, stream.codecs,
|
|
337
|
+
stream.bandwidth);
|
|
336
338
|
|
|
337
339
|
stream.segmentIndex.mergeAndEvict(
|
|
338
340
|
segments, this.presentationTimeline_.getSegmentAvailabilityStart());
|
|
@@ -763,7 +765,7 @@ shaka.hls.HlsParser = class {
|
|
|
763
765
|
const videoRange = tag.getAttributeValue('VIDEO-RANGE');
|
|
764
766
|
|
|
765
767
|
const streamInfos = await this.createStreamInfosForVariantTag_(tag,
|
|
766
|
-
resolution, frameRate);
|
|
768
|
+
resolution, frameRate, bandwidth);
|
|
767
769
|
|
|
768
770
|
if (streamInfos) {
|
|
769
771
|
goog.asserts.assert(streamInfos.audio.length ||
|
|
@@ -797,10 +799,11 @@ shaka.hls.HlsParser = class {
|
|
|
797
799
|
* @param {!shaka.hls.Tag} tag
|
|
798
800
|
* @param {?string} resolution
|
|
799
801
|
* @param {?string} frameRate
|
|
802
|
+
* @param {number} bandwidth
|
|
800
803
|
* @return {!Promise.<?shaka.hls.HlsParser.StreamInfos>}
|
|
801
804
|
* @private
|
|
802
805
|
*/
|
|
803
|
-
async createStreamInfosForVariantTag_(tag, resolution, frameRate) {
|
|
806
|
+
async createStreamInfosForVariantTag_(tag, resolution, frameRate, bandwidth) {
|
|
804
807
|
const ContentType = shaka.util.ManifestParserUtils.ContentType;
|
|
805
808
|
/** @type {!Array.<string>} */
|
|
806
809
|
let allCodecs = this.getCodecsForVariantTag_(tag);
|
|
@@ -870,7 +873,8 @@ shaka.hls.HlsParser = class {
|
|
|
870
873
|
let streamInfo;
|
|
871
874
|
if (!ignoreStream) {
|
|
872
875
|
streamInfo =
|
|
873
|
-
await this.createStreamInfoFromVariantTag_(tag, allCodecs, type
|
|
876
|
+
await this.createStreamInfoFromVariantTag_(tag, allCodecs, type,
|
|
877
|
+
bandwidth);
|
|
874
878
|
}
|
|
875
879
|
if (streamInfo) {
|
|
876
880
|
res[streamInfo.stream.type] = [streamInfo];
|
|
@@ -893,15 +897,12 @@ shaka.hls.HlsParser = class {
|
|
|
893
897
|
*/
|
|
894
898
|
getCodecsForVariantTag_(tag) {
|
|
895
899
|
// These are the default codecs to assume if none are specified.
|
|
896
|
-
// The video codec is H.264, with baseline profile and level 3.0.
|
|
897
|
-
// http://blog.pearce.org.nz/2013/11/what-does-h264avc1-codecs-parameters.html
|
|
898
|
-
// The audio codec is "low-complexity" AAC.
|
|
899
900
|
const defaultCodecsArray = [];
|
|
900
901
|
if (!this.config_.disableVideo) {
|
|
901
|
-
defaultCodecsArray.push(
|
|
902
|
+
defaultCodecsArray.push(this.config_.hls.defaultVideoCodec);
|
|
902
903
|
}
|
|
903
904
|
if (!this.config_.disableAudio) {
|
|
904
|
-
defaultCodecsArray.push(
|
|
905
|
+
defaultCodecsArray.push(this.config_.hls.defaultAudioCodec);
|
|
905
906
|
}
|
|
906
907
|
const defaultCodecs = defaultCodecsArray.join(',');
|
|
907
908
|
|
|
@@ -1280,6 +1281,30 @@ shaka.hls.HlsParser = class {
|
|
|
1280
1281
|
if (this.uriToStreamInfosMap_.has(verbatimImagePlaylistUri)) {
|
|
1281
1282
|
return this.uriToStreamInfosMap_.get(verbatimImagePlaylistUri);
|
|
1282
1283
|
}
|
|
1284
|
+
|
|
1285
|
+
// Parse misc attributes.
|
|
1286
|
+
const resolution = tag.getAttributeValue('RESOLUTION');
|
|
1287
|
+
if (resolution) {
|
|
1288
|
+
// The RESOLUTION tag represents the resolution of a single thumbnail, not
|
|
1289
|
+
// of the entire sheet at once (like we expect in the output).
|
|
1290
|
+
// So multiply by the layout size.
|
|
1291
|
+
|
|
1292
|
+
const reference = streamInfo.stream.segmentIndex.get(0);
|
|
1293
|
+
const layout = reference.getTilesLayout();
|
|
1294
|
+
if (layout) {
|
|
1295
|
+
streamInfo.stream.width =
|
|
1296
|
+
Number(resolution.split('x')[0]) * Number(layout.split('x')[0]);
|
|
1297
|
+
streamInfo.stream.height =
|
|
1298
|
+
Number(resolution.split('x')[1]) * Number(layout.split('x')[1]);
|
|
1299
|
+
// TODO: What happens if there are multiple grids, with different
|
|
1300
|
+
// layout sizes, inside this image stream?
|
|
1301
|
+
}
|
|
1302
|
+
}
|
|
1303
|
+
const bandwidth = tag.getAttributeValue('BANDWIDTH');
|
|
1304
|
+
if (bandwidth) {
|
|
1305
|
+
streamInfo.stream.bandwidth = Number(bandwidth);
|
|
1306
|
+
}
|
|
1307
|
+
|
|
1283
1308
|
this.uriToStreamInfosMap_.set(verbatimImagePlaylistUri, streamInfo);
|
|
1284
1309
|
return streamInfo;
|
|
1285
1310
|
}
|
|
@@ -1290,10 +1315,11 @@ shaka.hls.HlsParser = class {
|
|
|
1290
1315
|
* @param {!shaka.hls.Tag} tag
|
|
1291
1316
|
* @param {!Array.<string>} allCodecs
|
|
1292
1317
|
* @param {string} type
|
|
1318
|
+
* @param {number} bandwidth
|
|
1293
1319
|
* @return {!Promise.<?shaka.hls.HlsParser.StreamInfo>}
|
|
1294
1320
|
* @private
|
|
1295
1321
|
*/
|
|
1296
|
-
async createStreamInfoFromVariantTag_(tag, allCodecs, type) {
|
|
1322
|
+
async createStreamInfoFromVariantTag_(tag, allCodecs, type, bandwidth) {
|
|
1297
1323
|
goog.asserts.assert(tag.name == 'EXT-X-STREAM-INF',
|
|
1298
1324
|
'Should only be called on variant tags!');
|
|
1299
1325
|
const verbatimMediaPlaylistUri = this.variableSubstitution_(
|
|
@@ -1309,7 +1335,7 @@ shaka.hls.HlsParser = class {
|
|
|
1309
1335
|
codecs, type, /* language= */ 'und', /* primary= */ false,
|
|
1310
1336
|
/* name= */ null, /* channelcount= */ null, closedCaptions,
|
|
1311
1337
|
/* characteristics= */ null, /* forced= */ false,
|
|
1312
|
-
/* spatialAudio= */ false);
|
|
1338
|
+
/* spatialAudio= */ false, bandwidth);
|
|
1313
1339
|
if (streamInfo == null) {
|
|
1314
1340
|
return null;
|
|
1315
1341
|
}
|
|
@@ -1336,12 +1362,13 @@ shaka.hls.HlsParser = class {
|
|
|
1336
1362
|
* @param {?string} characteristics
|
|
1337
1363
|
* @param {boolean} forced
|
|
1338
1364
|
* @param {boolean} spatialAudio
|
|
1365
|
+
* @param {(number|undefined)} bandwidth
|
|
1339
1366
|
* @return {!Promise.<?shaka.hls.HlsParser.StreamInfo>}
|
|
1340
1367
|
* @private
|
|
1341
1368
|
*/
|
|
1342
1369
|
async createStreamInfo_(verbatimMediaPlaylistUri, codecs, type, language,
|
|
1343
1370
|
primary, name, channelsCount, closedCaptions, characteristics, forced,
|
|
1344
|
-
spatialAudio) {
|
|
1371
|
+
spatialAudio, bandwidth = undefined) {
|
|
1345
1372
|
// TODO: Refactor, too many parameters
|
|
1346
1373
|
let absoluteMediaPlaylistUri = shaka.hls.Utils.constructAbsoluteUri(
|
|
1347
1374
|
this.masterPlaylistUri_, verbatimMediaPlaylistUri);
|
|
@@ -1459,7 +1486,7 @@ shaka.hls.HlsParser = class {
|
|
|
1459
1486
|
try {
|
|
1460
1487
|
segments = await this.createSegments_(verbatimMediaPlaylistUri,
|
|
1461
1488
|
playlist, type, mimeType, mediaSequenceToStartTime, mediaVariables,
|
|
1462
|
-
discontinuityToMediaSequence);
|
|
1489
|
+
discontinuityToMediaSequence, codecs, bandwidth);
|
|
1463
1490
|
} catch (error) {
|
|
1464
1491
|
if (error.code == shaka.util.Error.Code.HLS_INTERNAL_SKIP_STREAM) {
|
|
1465
1492
|
shaka.log.alwaysWarn('Skipping unsupported HLS stream',
|
|
@@ -1811,6 +1838,7 @@ shaka.hls.HlsParser = class {
|
|
|
1811
1838
|
}
|
|
1812
1839
|
|
|
1813
1840
|
let tilesLayout = '';
|
|
1841
|
+
let tileDuration = null;
|
|
1814
1842
|
if (type == shaka.util.ManifestParserUtils.ContentType.IMAGE) {
|
|
1815
1843
|
// By default in HLS the tilesLayout is 1x1
|
|
1816
1844
|
tilesLayout = '1x1';
|
|
@@ -1818,6 +1846,10 @@ shaka.hls.HlsParser = class {
|
|
|
1818
1846
|
shaka.hls.Utils.getFirstTagWithName(tags, 'EXT-X-TILES');
|
|
1819
1847
|
if (tilesTag) {
|
|
1820
1848
|
tilesLayout = tilesTag.getRequiredAttrValue('LAYOUT');
|
|
1849
|
+
const duration = tilesTag.getAttributeValue('DURATION');
|
|
1850
|
+
if (duration) {
|
|
1851
|
+
tileDuration = Number(duration);
|
|
1852
|
+
}
|
|
1821
1853
|
}
|
|
1822
1854
|
}
|
|
1823
1855
|
|
|
@@ -1833,6 +1865,7 @@ shaka.hls.HlsParser = class {
|
|
|
1833
1865
|
/* appendWindowEnd= */ Infinity,
|
|
1834
1866
|
partialSegmentRefs,
|
|
1835
1867
|
tilesLayout,
|
|
1868
|
+
tileDuration,
|
|
1836
1869
|
);
|
|
1837
1870
|
}
|
|
1838
1871
|
|
|
@@ -1887,11 +1920,14 @@ shaka.hls.HlsParser = class {
|
|
|
1887
1920
|
* @param {!Map.<number, number>} mediaSequenceToStartTime
|
|
1888
1921
|
* @param {!Map.<string, string>} variables
|
|
1889
1922
|
* @param {!Map.<number, number>} discontinuityToMediaSequence
|
|
1923
|
+
* @param {string} codecs
|
|
1924
|
+
* @param {(number|undefined)} bandwidth
|
|
1890
1925
|
* @return {!Promise<!Array.<!shaka.media.SegmentReference>>}
|
|
1891
1926
|
* @private
|
|
1892
1927
|
*/
|
|
1893
1928
|
async createSegments_(verbatimMediaPlaylistUri, playlist, type, mimeType,
|
|
1894
|
-
mediaSequenceToStartTime, variables, discontinuityToMediaSequence
|
|
1929
|
+
mediaSequenceToStartTime, variables, discontinuityToMediaSequence,
|
|
1930
|
+
codecs, bandwidth) {
|
|
1895
1931
|
/** @type {Array.<!shaka.hls.Segment>} */
|
|
1896
1932
|
const hlsSegments = playlist.segments;
|
|
1897
1933
|
goog.asserts.assert(hlsSegments.length, 'Playlist should have segments!');
|
|
@@ -1934,7 +1970,7 @@ shaka.hls.HlsParser = class {
|
|
|
1934
1970
|
this.playlistStartTime_ = await this.getStartTime_(
|
|
1935
1971
|
verbatimMediaPlaylistUri, initSegmentRef, mimeType,
|
|
1936
1972
|
position, /* isDiscontinuity= */ false,
|
|
1937
|
-
hlsSegments[0], variables, type);
|
|
1973
|
+
hlsSegments[0], variables, type, codecs, bandwidth);
|
|
1938
1974
|
}
|
|
1939
1975
|
firstStartTime = this.playlistStartTime_;
|
|
1940
1976
|
}
|
|
@@ -1987,7 +2023,8 @@ shaka.hls.HlsParser = class {
|
|
|
1987
2023
|
// eslint-disable-next-line no-await-in-loop
|
|
1988
2024
|
timestampOffset = await this.getTimestampOffset_(
|
|
1989
2025
|
discontintuitySequenceNum, verbatimMediaPlaylistUri, initSegmentRef,
|
|
1990
|
-
mimeType, position, item, variables, startTime, type
|
|
2026
|
+
mimeType, position, item, variables, startTime, type, codecs,
|
|
2027
|
+
bandwidth);
|
|
1991
2028
|
}
|
|
1992
2029
|
|
|
1993
2030
|
// If the stream is low latency and the user has not configured the
|
|
@@ -2041,13 +2078,16 @@ shaka.hls.HlsParser = class {
|
|
|
2041
2078
|
* @param {!Map.<string, string>} variables
|
|
2042
2079
|
* @param {number} startTime
|
|
2043
2080
|
* @param {string} type
|
|
2081
|
+
* @param {string} codecs
|
|
2082
|
+
* @param {number|undefined} bandwidth
|
|
2044
2083
|
* @return {!Promise.<number>}
|
|
2045
2084
|
* @throws {shaka.util.Error}
|
|
2046
2085
|
* @private
|
|
2047
2086
|
*/
|
|
2048
2087
|
async getTimestampOffset_(discontintuitySequenceNum,
|
|
2049
2088
|
verbatimMediaPlaylistUri, initSegmentRef,
|
|
2050
|
-
mimeType, mediaSequenceNumber, segment, variables, startTime, type
|
|
2089
|
+
mimeType, mediaSequenceNumber, segment, variables, startTime, type,
|
|
2090
|
+
codecs, bandwidth) {
|
|
2051
2091
|
let timestampOffset = 0;
|
|
2052
2092
|
if (this.discontinuityToTso_.has(discontintuitySequenceNum)) {
|
|
2053
2093
|
timestampOffset =
|
|
@@ -2056,7 +2096,7 @@ shaka.hls.HlsParser = class {
|
|
|
2056
2096
|
const mediaStartTime = await this.getStartTime_(
|
|
2057
2097
|
verbatimMediaPlaylistUri, initSegmentRef, mimeType,
|
|
2058
2098
|
mediaSequenceNumber, /* isDiscontinuity= */ true, segment,
|
|
2059
|
-
variables, type);
|
|
2099
|
+
variables, type, codecs, bandwidth);
|
|
2060
2100
|
timestampOffset = startTime - mediaStartTime;
|
|
2061
2101
|
shaka.log.v1('Segment timestampOffset =', timestampOffset);
|
|
2062
2102
|
this.discontinuityToTso_.set(
|
|
@@ -2070,10 +2110,14 @@ shaka.hls.HlsParser = class {
|
|
|
2070
2110
|
* segment if we have to.
|
|
2071
2111
|
*
|
|
2072
2112
|
* @param {!shaka.media.AnySegmentReference} reference
|
|
2113
|
+
* @param {string} type
|
|
2114
|
+
* @param {string} mimeType
|
|
2115
|
+
* @param {string} codecs
|
|
2116
|
+
* @param {number|undefined} bandwidth
|
|
2073
2117
|
* @return {!Promise.<shaka.extern.Response>}
|
|
2074
2118
|
* @private
|
|
2075
2119
|
*/
|
|
2076
|
-
async fetchStartOfSegment_(reference) {
|
|
2120
|
+
async fetchStartOfSegment_(reference, type, mimeType, codecs, bandwidth) {
|
|
2077
2121
|
const requestType = shaka.net.NetworkingEngine.RequestType.SEGMENT;
|
|
2078
2122
|
|
|
2079
2123
|
// Create two requests:
|
|
@@ -2087,6 +2131,20 @@ shaka.hls.HlsParser = class {
|
|
|
2087
2131
|
reference.endByte,
|
|
2088
2132
|
this.config_.retryParameters);
|
|
2089
2133
|
|
|
2134
|
+
// We can only add partial CMCD data here because the stream
|
|
2135
|
+
// and manifest objects are still being created
|
|
2136
|
+
this.playerInterface_.modifySegmentRequest(
|
|
2137
|
+
fullRequest,
|
|
2138
|
+
{
|
|
2139
|
+
type: type,
|
|
2140
|
+
init: reference instanceof shaka.media.InitSegmentReference,
|
|
2141
|
+
duration: reference.endTime - reference.startTime,
|
|
2142
|
+
mimeType: mimeType,
|
|
2143
|
+
codecs: codecs,
|
|
2144
|
+
bandwidth: bandwidth,
|
|
2145
|
+
},
|
|
2146
|
+
);
|
|
2147
|
+
|
|
2090
2148
|
if (this.config_.hls.useFullSegmentsForStartTime) {
|
|
2091
2149
|
return this.makeNetworkRequest_(fullRequest, requestType);
|
|
2092
2150
|
}
|
|
@@ -2097,6 +2155,18 @@ shaka.hls.HlsParser = class {
|
|
|
2097
2155
|
reference.startByte + shaka.hls.HlsParser.START_OF_SEGMENT_SIZE_ - 1,
|
|
2098
2156
|
this.config_.retryParameters);
|
|
2099
2157
|
|
|
2158
|
+
this.playerInterface_.modifySegmentRequest(
|
|
2159
|
+
partialRequest,
|
|
2160
|
+
{
|
|
2161
|
+
type: type,
|
|
2162
|
+
init: reference instanceof shaka.media.InitSegmentReference,
|
|
2163
|
+
duration: reference.endTime - reference.startTime,
|
|
2164
|
+
mimeType: mimeType,
|
|
2165
|
+
codecs: codecs,
|
|
2166
|
+
bandwidth: bandwidth,
|
|
2167
|
+
},
|
|
2168
|
+
);
|
|
2169
|
+
|
|
2100
2170
|
// TODO(vaage): The need to do fall back requests is not likely to be unique
|
|
2101
2171
|
// to here. It would be nice if the fallback(s) could be included into
|
|
2102
2172
|
// the same abortable operation as the original request.
|
|
@@ -2144,12 +2214,14 @@ shaka.hls.HlsParser = class {
|
|
|
2144
2214
|
* @param {!shaka.hls.Segment} segment
|
|
2145
2215
|
* @param {!Map.<string, string>} variables
|
|
2146
2216
|
* @param {string} type
|
|
2217
|
+
* @param {string} codecs
|
|
2218
|
+
* @param {number|undefined} bandwidth
|
|
2147
2219
|
* @return {!Promise.<number>}
|
|
2148
2220
|
* @private
|
|
2149
2221
|
*/
|
|
2150
2222
|
async getStartTime_(
|
|
2151
2223
|
verbatimMediaPlaylistUri, initSegmentRef, mimeType, mediaSequenceNumber,
|
|
2152
|
-
isDiscontinuity, segment, variables, type) {
|
|
2224
|
+
isDiscontinuity, segment, variables, type, codecs, bandwidth) {
|
|
2153
2225
|
const segmentRef = this.createSegmentReference_(
|
|
2154
2226
|
initSegmentRef,
|
|
2155
2227
|
/* previousReference= */ null,
|
|
@@ -2217,10 +2289,22 @@ shaka.hls.HlsParser = class {
|
|
|
2217
2289
|
if (mimeType == 'video/mp4' || mimeType == 'audio/mp4') {
|
|
2218
2290
|
// We also need the init segment to get the correct timescale. But if the
|
|
2219
2291
|
// stream is self-initializing, use the same response for both.
|
|
2220
|
-
const fetches = [this.fetchStartOfSegment_(
|
|
2292
|
+
const fetches = [this.fetchStartOfSegment_(
|
|
2293
|
+
segmentRef,
|
|
2294
|
+
type,
|
|
2295
|
+
mimeType,
|
|
2296
|
+
codecs,
|
|
2297
|
+
bandwidth,
|
|
2298
|
+
)];
|
|
2221
2299
|
|
|
2222
2300
|
if (initSegmentRef) {
|
|
2223
|
-
fetches.push(this.fetchStartOfSegment_(
|
|
2301
|
+
fetches.push(this.fetchStartOfSegment_(
|
|
2302
|
+
initSegmentRef,
|
|
2303
|
+
type,
|
|
2304
|
+
mimeType,
|
|
2305
|
+
codecs,
|
|
2306
|
+
bandwidth,
|
|
2307
|
+
));
|
|
2224
2308
|
}
|
|
2225
2309
|
|
|
2226
2310
|
const responses = await Promise.all(fetches);
|
|
@@ -2236,7 +2320,13 @@ shaka.hls.HlsParser = class {
|
|
|
2236
2320
|
}
|
|
2237
2321
|
|
|
2238
2322
|
if (mimeType == 'video/mp2t') {
|
|
2239
|
-
const response = await this.fetchStartOfSegment_(
|
|
2323
|
+
const response = await this.fetchStartOfSegment_(
|
|
2324
|
+
segmentRef,
|
|
2325
|
+
type,
|
|
2326
|
+
mimeType,
|
|
2327
|
+
codecs,
|
|
2328
|
+
bandwidth,
|
|
2329
|
+
);
|
|
2240
2330
|
goog.asserts.assert(response.data, 'Should have a response body!');
|
|
2241
2331
|
return this.getStartTimeFromTsSegment_(
|
|
2242
2332
|
verbatimMediaPlaylistUri, response.uri, response.data);
|
|
@@ -2614,6 +2704,9 @@ shaka.hls.HlsParser = class {
|
|
|
2614
2704
|
const request = shaka.net.NetworkingEngine.makeRequest(
|
|
2615
2705
|
[absoluteUri], this.config_.retryParameters);
|
|
2616
2706
|
|
|
2707
|
+
const format = shaka.util.CmcdManager.StreamingFormat.HLS;
|
|
2708
|
+
this.playerInterface_.modifyManifestRequest(request, {format: format});
|
|
2709
|
+
|
|
2617
2710
|
return this.makeNetworkRequest_(request, requestType);
|
|
2618
2711
|
}
|
|
2619
2712
|
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
/*! @license
|
|
2
|
+
* Shaka Player
|
|
3
|
+
* Copyright 2016 Google LLC
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
goog.provide('shaka.media.QualityObserver');
|
|
8
|
+
|
|
9
|
+
goog.require('shaka.media.IPlayheadObserver');
|
|
10
|
+
goog.require('shaka.log');
|
|
11
|
+
goog.require('shaka.util.FakeEvent');
|
|
12
|
+
goog.require('shaka.util.FakeEventTarget');
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Monitors the quality of content being appended to the source buffers and
|
|
16
|
+
* fires 'qualitychange' events when the media quality at the playhead changes.
|
|
17
|
+
*
|
|
18
|
+
* @implements {shaka.media.IPlayheadObserver}
|
|
19
|
+
* @final
|
|
20
|
+
*/
|
|
21
|
+
shaka.media.QualityObserver = class extends shaka.util.FakeEventTarget {
|
|
22
|
+
/**
|
|
23
|
+
* Creates a new QualityObserver.
|
|
24
|
+
*
|
|
25
|
+
* @param {!function():!shaka.extern.BufferedInfo} getBufferedInfo
|
|
26
|
+
* Buffered info is needed to purge QualityChanges that are no
|
|
27
|
+
* longer relevant.
|
|
28
|
+
*/
|
|
29
|
+
constructor(getBufferedInfo) {
|
|
30
|
+
super();
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* @private {!Map.<string,!shaka.media.QualityObserver.ContentTypeState>}
|
|
34
|
+
*/
|
|
35
|
+
this.contentTypeStates_ = new Map();
|
|
36
|
+
|
|
37
|
+
/** @private function():!shaka.extern.BufferedInfo */
|
|
38
|
+
this.getBufferedInfo_ = getBufferedInfo;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** @override */
|
|
42
|
+
release() {
|
|
43
|
+
this.contentTypeStates_.clear();
|
|
44
|
+
super.release();
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Get the ContenTypeState for a contentType, creating a new
|
|
49
|
+
* one if necessary.
|
|
50
|
+
*
|
|
51
|
+
* @param {!string} contentType
|
|
52
|
+
* The contend type e.g. "video" or "audio".
|
|
53
|
+
* @return {!shaka.media.QualityObserver.ContentTypeState}
|
|
54
|
+
* @private
|
|
55
|
+
*/
|
|
56
|
+
getContentTypeState_(contentType) {
|
|
57
|
+
let contentTypeState = this.contentTypeStates_.get(contentType);
|
|
58
|
+
if (!contentTypeState) {
|
|
59
|
+
contentTypeState = {
|
|
60
|
+
qualityChangePositions: [],
|
|
61
|
+
currentQuality: null,
|
|
62
|
+
contentType: contentType,
|
|
63
|
+
};
|
|
64
|
+
this.contentTypeStates_.set(contentType, contentTypeState);
|
|
65
|
+
}
|
|
66
|
+
return contentTypeState;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Adds a QualityChangePosition for the contentType identified by
|
|
71
|
+
* the mediaQuality.contentType.
|
|
72
|
+
*
|
|
73
|
+
* @param {!shaka.extern.MediaQualityInfo} mediaQuality
|
|
74
|
+
* @param {!number} position
|
|
75
|
+
* Position in seconds of the quality change.
|
|
76
|
+
*/
|
|
77
|
+
addMediaQualityChange(mediaQuality, position) {
|
|
78
|
+
const contentTypeState =
|
|
79
|
+
this.getContentTypeState_(mediaQuality.contentType);
|
|
80
|
+
|
|
81
|
+
// Remove unneeded QualityChangePosition(s) before adding the new one
|
|
82
|
+
this.purgeQualityChangePositions_(contentTypeState);
|
|
83
|
+
|
|
84
|
+
const newChangePosition = {
|
|
85
|
+
mediaQuality: mediaQuality,
|
|
86
|
+
position: position,
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
const changePositions = contentTypeState.qualityChangePositions;
|
|
90
|
+
const insertBeforeIndex = changePositions.findIndex(
|
|
91
|
+
(qualityChange) => (qualityChange.position >= position));
|
|
92
|
+
|
|
93
|
+
if (insertBeforeIndex >= 0) {
|
|
94
|
+
const duplicatePositions =
|
|
95
|
+
(changePositions[insertBeforeIndex].position == position) ? 1 : 0;
|
|
96
|
+
changePositions.splice(
|
|
97
|
+
insertBeforeIndex, duplicatePositions, newChangePosition);
|
|
98
|
+
} else {
|
|
99
|
+
changePositions.push(newChangePosition);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Determines the media quality at a specific position in the source buffer.
|
|
105
|
+
*
|
|
106
|
+
* @param {!number} position
|
|
107
|
+
* Position in seconds
|
|
108
|
+
* @param {!shaka.media.QualityObserver.ContentTypeState} contentTypeState
|
|
109
|
+
* @return {?shaka.extern.MediaQualityInfo}
|
|
110
|
+
* @private
|
|
111
|
+
*/
|
|
112
|
+
static getMediaQualityAtPosition_(position, contentTypeState) {
|
|
113
|
+
// The qualityChangePositions must be ordered by position ascending
|
|
114
|
+
// Find the last QualityChangePosition prior to the position
|
|
115
|
+
const changePositions = contentTypeState.qualityChangePositions;
|
|
116
|
+
for (let i = changePositions.length - 1; i >= 0; i--) {
|
|
117
|
+
const qualityChange = changePositions[i];
|
|
118
|
+
if (qualityChange.position <= position) {
|
|
119
|
+
return qualityChange.mediaQuality;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return null;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Determines if two MediaQualityInfo objects are the same or not.
|
|
127
|
+
*
|
|
128
|
+
* @param {?shaka.extern.MediaQualityInfo} mq1
|
|
129
|
+
* @param {?shaka.extern.MediaQualityInfo} mq2
|
|
130
|
+
* @return {boolean}
|
|
131
|
+
* @private
|
|
132
|
+
*/
|
|
133
|
+
static mediaQualitiesAreTheSame_(mq1, mq2) {
|
|
134
|
+
if (mq1 === mq2) {
|
|
135
|
+
return true;
|
|
136
|
+
}
|
|
137
|
+
if (!mq1 || !mq2) {
|
|
138
|
+
return false;
|
|
139
|
+
}
|
|
140
|
+
return (mq1.bandwidth == mq2.bandwidth) &&
|
|
141
|
+
(mq1.audioSamplingRate == mq2.audioSamplingRate) &&
|
|
142
|
+
(mq1.codecs == mq2.codecs) &&
|
|
143
|
+
(mq1.contentType == mq2.contentType) &&
|
|
144
|
+
(mq1.frameRate == mq2.frameRate) &&
|
|
145
|
+
(mq1.height == mq2.height) &&
|
|
146
|
+
(mq1.mimeType == mq2.mimeType) &&
|
|
147
|
+
(mq1.channelsCount == mq2.channelsCount) &&
|
|
148
|
+
(mq1.pixelAspectRatio == mq2.pixelAspectRatio) &&
|
|
149
|
+
(mq1.width == mq2.width);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/** @override */
|
|
153
|
+
poll(positionInSeconds, wasSeeking) {
|
|
154
|
+
for (const contentTypeState of this.contentTypeStates_.values()) {
|
|
155
|
+
const qualityAtPosition =
|
|
156
|
+
shaka.media.QualityObserver.getMediaQualityAtPosition_(
|
|
157
|
+
positionInSeconds, contentTypeState);
|
|
158
|
+
if (qualityAtPosition &&
|
|
159
|
+
!shaka.media.QualityObserver.mediaQualitiesAreTheSame_(
|
|
160
|
+
contentTypeState.currentQuality, qualityAtPosition)) {
|
|
161
|
+
if (this.positionIsBuffered_(
|
|
162
|
+
positionInSeconds, qualityAtPosition.contentType)) {
|
|
163
|
+
contentTypeState.currentQuality = qualityAtPosition;
|
|
164
|
+
|
|
165
|
+
shaka.log.debug('Media quality changed at position ' +
|
|
166
|
+
positionInSeconds + ' ' + JSON.stringify(qualityAtPosition));
|
|
167
|
+
|
|
168
|
+
const event = new shaka.util.FakeEvent('qualitychange', new Map([
|
|
169
|
+
['quality', qualityAtPosition],
|
|
170
|
+
['position', positionInSeconds],
|
|
171
|
+
]));
|
|
172
|
+
this.dispatchEvent(event);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Determine if a position is buffered for a given content type.
|
|
180
|
+
*
|
|
181
|
+
* @param {!number} position
|
|
182
|
+
* @param {!string} contentType
|
|
183
|
+
* @private
|
|
184
|
+
*/
|
|
185
|
+
positionIsBuffered_(position, contentType) {
|
|
186
|
+
const bufferedInfo = this.getBufferedInfo_();
|
|
187
|
+
const bufferedRanges = bufferedInfo[contentType];
|
|
188
|
+
if (bufferedRanges && bufferedRanges.length > 0) {
|
|
189
|
+
const bufferStart = bufferedRanges[0].start;
|
|
190
|
+
const bufferEnd = bufferedRanges[bufferedRanges.length - 1].end;
|
|
191
|
+
if (position >= bufferStart && position < bufferEnd) {
|
|
192
|
+
return true;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
return false;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Removes the QualityChangePosition(s) that are not relevant to the buffered
|
|
200
|
+
* content of the specified contentType. Note that this function is
|
|
201
|
+
* invoked just before adding the quality change info associated with
|
|
202
|
+
* the next media segment to be appended.
|
|
203
|
+
*
|
|
204
|
+
* @param {!shaka.media.QualityObserver.ContentTypeState} contentTypeState
|
|
205
|
+
* @private
|
|
206
|
+
*/
|
|
207
|
+
purgeQualityChangePositions_(contentTypeState) {
|
|
208
|
+
const bufferedInfo = this.getBufferedInfo_();
|
|
209
|
+
const bufferedRanges = bufferedInfo[contentTypeState.contentType];
|
|
210
|
+
|
|
211
|
+
if (bufferedRanges && bufferedRanges.length > 0) {
|
|
212
|
+
const bufferStart = bufferedRanges[0].start;
|
|
213
|
+
const bufferEnd = bufferedRanges[bufferedRanges.length - 1].end;
|
|
214
|
+
const oldChangePositions = contentTypeState.qualityChangePositions;
|
|
215
|
+
contentTypeState.qualityChangePositions =
|
|
216
|
+
oldChangePositions.filter(
|
|
217
|
+
(qualityChange, index) => {
|
|
218
|
+
// Remove all but last quality change before bufferStart.
|
|
219
|
+
if ((qualityChange.position <= bufferStart) &&
|
|
220
|
+
(index + 1 < oldChangePositions.length) &&
|
|
221
|
+
(oldChangePositions[index + 1].position <= bufferStart)) {
|
|
222
|
+
return false;
|
|
223
|
+
}
|
|
224
|
+
// Remove all quality changes after bufferEnd.
|
|
225
|
+
if (qualityChange.position >= bufferEnd) {
|
|
226
|
+
return false;
|
|
227
|
+
}
|
|
228
|
+
return true;
|
|
229
|
+
});
|
|
230
|
+
} else {
|
|
231
|
+
// Nothing is buffered; so remove all quality changes.
|
|
232
|
+
contentTypeState.qualityChangePositions = [];
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* @typedef {{
|
|
239
|
+
* mediaQuality: !shaka.extern.MediaQualityInfo,
|
|
240
|
+
* position: !number
|
|
241
|
+
* }}
|
|
242
|
+
*
|
|
243
|
+
* @description
|
|
244
|
+
* Identifies the position of a media quality change in the
|
|
245
|
+
* source buffer.
|
|
246
|
+
*
|
|
247
|
+
* @property {shaka.extern.MediaQualityInfo} !mediaQuality
|
|
248
|
+
* The new media quality for content after position in the source buffer.
|
|
249
|
+
* @property {number} !position
|
|
250
|
+
* A position in seconds in the source buffer
|
|
251
|
+
*/
|
|
252
|
+
shaka.media.QualityObserver.QualityChangePosition;
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* @typedef {{
|
|
256
|
+
* qualityChangePositions:
|
|
257
|
+
* !Array.<shaka.media.QualityObserver.QualityChangePosition>,
|
|
258
|
+
* currentQuality: ?shaka.extern.MediaQualityInfo,
|
|
259
|
+
* contentType: !string
|
|
260
|
+
* }}
|
|
261
|
+
*
|
|
262
|
+
* @description
|
|
263
|
+
* Contains media quality information for a specific content type
|
|
264
|
+
* e.g video or audio.
|
|
265
|
+
*
|
|
266
|
+
* @property {!Array.<shaka.media.QualityObserver.QualityChangePosition>}
|
|
267
|
+
* qualityChangePositions
|
|
268
|
+
* Quality changes ordered by position ascending.
|
|
269
|
+
* @property {?shaka.media.MediaQualityInfo} currentMediaQuality
|
|
270
|
+
* The media quality at the playhead position.
|
|
271
|
+
* @property {string} contentType
|
|
272
|
+
* The contentType e.g. 'video' or 'audio'
|
|
273
|
+
*/
|
|
274
|
+
shaka.media.QualityObserver.ContentTypeState;
|