shaka-player 3.2.1 → 3.3.1
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/AUTHORS +9 -5
- package/CHANGELOG.md +358 -0
- package/CONTRIBUTORS +7 -2
- package/build/conformance.textproto +183 -169
- package/build/generateExterns.js +1 -1
- 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 +19 -1
- 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 +20 -17
- package/dist/demo.compiled.debug.map +3 -3
- package/dist/demo.compiled.js +20 -17
- package/dist/demo.compiled.map +3 -3
- package/dist/deps.js +19 -12
- package/dist/locales.js +39 -37
- package/dist/receiver.compiled.debug.js +12 -10
- package/dist/receiver.compiled.debug.map +3 -3
- package/dist/receiver.compiled.js +12 -10
- package/dist/receiver.compiled.map +3 -3
- package/dist/shaka-player.compiled.d.ts +231 -18
- package/dist/shaka-player.compiled.debug.d.ts +231 -18
- package/dist/shaka-player.compiled.debug.externs.js +279 -8
- package/dist/shaka-player.compiled.debug.js +512 -449
- package/dist/shaka-player.compiled.debug.map +4 -4
- package/dist/shaka-player.compiled.externs.js +279 -8
- package/dist/shaka-player.compiled.js +854 -821
- package/dist/shaka-player.compiled.map +4 -4
- package/dist/shaka-player.ui.d.ts +247 -19
- package/dist/shaka-player.ui.debug.d.ts +247 -19
- package/dist/shaka-player.ui.debug.externs.js +307 -9
- package/dist/shaka-player.ui.debug.js +662 -580
- package/dist/shaka-player.ui.debug.map +4 -4
- package/dist/shaka-player.ui.externs.js +307 -9
- package/dist/shaka-player.ui.js +1047 -1000
- 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/fairplay.md +1 -1
- 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 +1 -0
- 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/client_side_ad.js +8 -0
- package/lib/ads/client_side_ad_manager.js +54 -90
- package/lib/ads/server_side_ad.js +7 -0
- package/lib/ads/server_side_ad_manager.js +7 -10
- package/lib/cast/cast_proxy.js +3 -2
- package/lib/cast/cast_receiver.js +2 -1
- package/lib/cast/cast_sender.js +2 -2
- package/lib/cast/cast_utils.js +1 -0
- package/lib/dash/dash_parser.js +141 -16
- package/lib/dash/segment_base.js +25 -1
- package/lib/dash/segment_list.js +19 -10
- package/lib/dash/segment_template.js +24 -11
- package/lib/hls/hls_parser.js +146 -40
- package/lib/media/drm_engine.js +7 -0
- package/lib/media/gap_jumping_controller.js +4 -2
- package/lib/media/media_source_engine.js +1 -2
- package/lib/media/quality_observer.js +291 -0
- package/lib/media/segment_index.js +8 -1
- package/lib/media/segment_reference.js +37 -4
- package/lib/media/streaming_engine.js +49 -7
- 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 +83 -19
- 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 +393 -284
- package/lib/offline/stored_content_utils.js +2 -0
- package/lib/player.js +249 -43
- package/lib/polyfill/media_capabilities.js +36 -15
- package/lib/polyfill/mediasource.js +6 -0
- package/lib/polyfill/orientation.js +1 -1
- package/lib/polyfill/patchedmediakeys_apple.js +4 -4
- package/lib/polyfill/patchedmediakeys_ms.js +4 -4
- package/lib/polyfill/patchedmediakeys_webkit.js +4 -4
- package/lib/polyfill/random_uuid.js +52 -0
- package/lib/text/cue.js +18 -0
- package/lib/text/mp4_ttml_parser.js +3 -0
- package/lib/text/text_engine.js +14 -2
- package/lib/text/ttml_text_parser.js +114 -60
- package/lib/text/ui_text_displayer.js +95 -41
- package/lib/util/cmcd_manager.js +672 -0
- package/lib/util/error.js +6 -0
- package/lib/util/fake_event.js +53 -7
- package/lib/util/language_utils.js +12 -0
- package/lib/util/mutex.js +52 -0
- package/lib/util/periods.js +102 -65
- package/lib/util/platform.js +13 -1
- package/lib/util/player_configuration.js +18 -0
- package/lib/util/stream_utils.js +99 -10
- package/lib/util/xml_utils.js +19 -0
- package/package.json +2 -2
- package/shaka-player.uncompiled.js +3 -0
- package/test/cast/cast_proxy_unit.js +12 -6
- package/test/cast/cast_utils_unit.js +1 -1
- package/test/dash/dash_parser_content_protection_unit.js +2 -0
- package/test/dash/dash_parser_live_unit.js +101 -1
- package/test/dash/dash_parser_manifest_unit.js +222 -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 +7 -1
- package/test/media/closed_caption_parser_unit.js +6 -1
- package/test/media/drm_engine_unit.js +47 -3
- package/test/media/quality_observer_unit.js +139 -0
- 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 +178 -8
- package/test/player_src_equals_integration.js +41 -8
- package/test/player_unit.js +19 -9
- package/test/test/assets/3675/chunk-stream0-00001.m4s +0 -0
- package/test/test/assets/3675/chunk-stream0-00002.m4s +0 -0
- package/test/test/assets/3675/chunk-stream0-00003.m4s +0 -0
- package/test/test/assets/3675/chunk-stream0-00004.m4s +0 -0
- package/test/test/assets/3675/chunk-stream0-00005.m4s +0 -0
- package/test/test/assets/3675/chunk-stream0-00006.m4s +0 -0
- package/test/test/assets/3675/chunk-stream0-00007.m4s +0 -0
- package/test/test/assets/3675/dash_0.mpd +30 -0
- package/test/test/assets/3675/dash_1.mpd +30 -0
- package/test/test/assets/3675/dash_2.mpd +30 -0
- package/test/test/assets/3675/dash_3.mpd +30 -0
- package/test/test/assets/3675/dash_4.mpd +30 -0
- package/test/test/assets/3675/dash_5.mpd +26 -0
- package/test/test/assets/3675/init-stream0.m4s +0 -0
- 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_ad_manager.js +1 -1
- 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/text/mp4_ttml_parser_unit.js +11 -18
- package/test/text/ttml_text_parser_unit.js +552 -227
- package/test/text/ui_text_displayer_unit.js +29 -17
- package/test/ui/localization_unit.js +15 -14
- package/test/ui/text_displayer_layout_unit.js +13 -1
- package/test/ui/ui_unit.js +250 -0
- package/test/util/cmcd_manager_unit.js +291 -0
- package/test/util/stream_utils_unit.js +45 -4
- 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 +3 -3
- package/ui/context_menu.js +109 -0
- package/ui/controls.js +45 -13
- 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 +77 -7
- 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/ru.json +6 -6
- package/ui/locales/source.json +4 -0
- package/ui/localization.js +28 -51
- 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 +7 -3
- 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
|
@@ -0,0 +1,291 @@
|
|
|
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
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Monitors the quality of content being appended to the source
|
|
14
|
+
* buffers and fires onQualityChange events when the media quality
|
|
15
|
+
* at the playhead changes.
|
|
16
|
+
*
|
|
17
|
+
* @implements {shaka.media.IPlayheadObserver}
|
|
18
|
+
* @final
|
|
19
|
+
*/
|
|
20
|
+
shaka.media.QualityObserver = class {
|
|
21
|
+
/**
|
|
22
|
+
* Creates a new QualityObserver.
|
|
23
|
+
*
|
|
24
|
+
* @param {!function():!shaka.extern.BufferedInfo} getBufferedInfo
|
|
25
|
+
* Buffered info is needed to purge QualityChanges that are no
|
|
26
|
+
* longer relevant.
|
|
27
|
+
*/
|
|
28
|
+
constructor(getBufferedInfo) {
|
|
29
|
+
/**
|
|
30
|
+
* @private {!Map.<string,!shaka.media.QualityObserver.ContentTypeState>}
|
|
31
|
+
*/
|
|
32
|
+
this.contentTypeStates_ = new Map();
|
|
33
|
+
|
|
34
|
+
/** @private {shaka.media.QualityObserver.EventListener} */
|
|
35
|
+
this.onQualityChange_ = (mediaQuality, position) => {};
|
|
36
|
+
|
|
37
|
+
/** @private function():!shaka.extern.BufferedInfo */
|
|
38
|
+
this.getBufferedInfo_ = getBufferedInfo;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Set all the listeners. This overrides any previous calls to |setListeners|.
|
|
43
|
+
*
|
|
44
|
+
* @param {shaka.media.QualityObserver.EventListener} onQualityChange
|
|
45
|
+
*/
|
|
46
|
+
setListeners(onQualityChange) {
|
|
47
|
+
this.onQualityChange_ = onQualityChange;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** @override */
|
|
51
|
+
release() {
|
|
52
|
+
this.contentTypeStates_.clear();
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Get the ContenTypeState for a contentType, creating a new
|
|
57
|
+
* one if necessary.
|
|
58
|
+
*
|
|
59
|
+
* @param {!string} contentType
|
|
60
|
+
* The contend type e.g. "video" or "audio".
|
|
61
|
+
* @return {!shaka.media.QualityObserver.ContentTypeState}
|
|
62
|
+
* @private
|
|
63
|
+
*/
|
|
64
|
+
getContentTypeState_(contentType) {
|
|
65
|
+
let contentTypeState = this.contentTypeStates_.get(contentType);
|
|
66
|
+
if (!contentTypeState) {
|
|
67
|
+
contentTypeState = {
|
|
68
|
+
qualityChangePositions: [],
|
|
69
|
+
currentQuality: null,
|
|
70
|
+
contentType: contentType,
|
|
71
|
+
};
|
|
72
|
+
this.contentTypeStates_.set(contentType, contentTypeState);
|
|
73
|
+
}
|
|
74
|
+
return contentTypeState;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Adds a QualityChangePosition for the contentType identified by
|
|
79
|
+
* the mediaQuality.contentType.
|
|
80
|
+
*
|
|
81
|
+
* @param {!shaka.extern.MediaQualityInfo} mediaQuality
|
|
82
|
+
* @param {!number} position
|
|
83
|
+
* Position in seconds of the quality change.
|
|
84
|
+
*/
|
|
85
|
+
addMediaQualityChange(mediaQuality, position) {
|
|
86
|
+
const contentTypeState =
|
|
87
|
+
this.getContentTypeState_(mediaQuality.contentType);
|
|
88
|
+
|
|
89
|
+
// Remove unneeded QualityChangePosition(s) before adding the new one
|
|
90
|
+
this.purgeQualityChangePositions_(contentTypeState);
|
|
91
|
+
|
|
92
|
+
const newChangePosition = {
|
|
93
|
+
mediaQuality: mediaQuality,
|
|
94
|
+
position: position,
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
const changePositions = contentTypeState.qualityChangePositions;
|
|
98
|
+
const insertBeforeIndex = changePositions.findIndex(
|
|
99
|
+
(qualityChange) => (qualityChange.position >= position));
|
|
100
|
+
|
|
101
|
+
if (insertBeforeIndex >= 0) {
|
|
102
|
+
const duplicatePositions =
|
|
103
|
+
(changePositions[insertBeforeIndex].position == position) ? 1 : 0;
|
|
104
|
+
changePositions.splice(
|
|
105
|
+
insertBeforeIndex, duplicatePositions, newChangePosition);
|
|
106
|
+
} else {
|
|
107
|
+
changePositions.push(newChangePosition);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Determines the media quality at a specific position in the source buffer.
|
|
113
|
+
*
|
|
114
|
+
* @param {!number} position
|
|
115
|
+
* Position in seconds
|
|
116
|
+
* @param {!shaka.media.QualityObserver.ContentTypeState} contentTypeState
|
|
117
|
+
* @return {?shaka.extern.MediaQualityInfo}
|
|
118
|
+
* @private
|
|
119
|
+
*/
|
|
120
|
+
static getMediaQualityAtPosition_(position, contentTypeState) {
|
|
121
|
+
// The qualityChangePositions must be ordered by position ascending
|
|
122
|
+
// Find the last QualityChangePosition prior to the position
|
|
123
|
+
const changePositions = contentTypeState.qualityChangePositions;
|
|
124
|
+
for (let i = changePositions.length - 1; i >= 0; i--) {
|
|
125
|
+
const qualityChange = changePositions[i];
|
|
126
|
+
if (qualityChange.position <= position) {
|
|
127
|
+
return qualityChange.mediaQuality;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Determines if two MediaQualityInfo objects are the same or not.
|
|
135
|
+
*
|
|
136
|
+
* @param {?shaka.extern.MediaQualityInfo} mq1
|
|
137
|
+
* @param {?shaka.extern.MediaQualityInfo} mq2
|
|
138
|
+
* @return {boolean}
|
|
139
|
+
* @private
|
|
140
|
+
*/
|
|
141
|
+
static mediaQualitiesAreTheSame_(mq1, mq2) {
|
|
142
|
+
if (mq1 === mq2) {
|
|
143
|
+
return true;
|
|
144
|
+
}
|
|
145
|
+
if (!mq1 || !mq2) {
|
|
146
|
+
return false;
|
|
147
|
+
}
|
|
148
|
+
return (mq1.bandwidth == mq2.bandwidth) &&
|
|
149
|
+
(mq1.audioSamplingRate == mq2.audioSamplingRate) &&
|
|
150
|
+
(mq1.codecs == mq2.codecs) &&
|
|
151
|
+
(mq1.contentType == mq2.contentType) &&
|
|
152
|
+
(mq1.frameRate == mq2.frameRate) &&
|
|
153
|
+
(mq1.height == mq2.height) &&
|
|
154
|
+
(mq1.mimeType == mq2.mimeType) &&
|
|
155
|
+
(mq1.channelsCount == mq2.channelsCount) &&
|
|
156
|
+
(mq1.pixelAspectRatio == mq2.pixelAspectRatio) &&
|
|
157
|
+
(mq1.width == mq2.width);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/** @override */
|
|
161
|
+
poll(positionInSeconds, wasSeeking) {
|
|
162
|
+
for (const contentTypeState of this.contentTypeStates_.values()) {
|
|
163
|
+
const qualityAtPosition =
|
|
164
|
+
shaka.media.QualityObserver.getMediaQualityAtPosition_(
|
|
165
|
+
positionInSeconds, contentTypeState);
|
|
166
|
+
if (qualityAtPosition &&
|
|
167
|
+
!shaka.media.QualityObserver.mediaQualitiesAreTheSame_(
|
|
168
|
+
contentTypeState.currentQuality, qualityAtPosition)) {
|
|
169
|
+
if (this.positionIsBuffered_(
|
|
170
|
+
positionInSeconds, qualityAtPosition.contentType)) {
|
|
171
|
+
contentTypeState.currentQuality = qualityAtPosition;
|
|
172
|
+
shaka.log.debug('Media quality changed at position ' +
|
|
173
|
+
positionInSeconds + ' ' + JSON.stringify(qualityAtPosition));
|
|
174
|
+
this.onQualityChange_(
|
|
175
|
+
qualityAtPosition, positionInSeconds);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Determine if a position is buffered for a given content type.
|
|
183
|
+
*
|
|
184
|
+
* @param {!number} position
|
|
185
|
+
* @param {!string} contentType
|
|
186
|
+
* @private
|
|
187
|
+
*/
|
|
188
|
+
positionIsBuffered_(position, contentType) {
|
|
189
|
+
const bufferedInfo = this.getBufferedInfo_();
|
|
190
|
+
const bufferedRanges = bufferedInfo[contentType];
|
|
191
|
+
if (bufferedRanges && bufferedRanges.length > 0) {
|
|
192
|
+
const bufferStart = bufferedRanges[0].start;
|
|
193
|
+
const bufferEnd = bufferedRanges[bufferedRanges.length - 1].end;
|
|
194
|
+
if (position >= bufferStart && position < bufferEnd) {
|
|
195
|
+
return true;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
return false;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Removes the QualityChangePosition(s) that are not relevant to the buffered
|
|
203
|
+
* content of the specified contentType. Note that this function is
|
|
204
|
+
* invoked just before adding the quality change info associated with
|
|
205
|
+
* the next media segment to be appended.
|
|
206
|
+
*
|
|
207
|
+
* @param {!shaka.media.QualityObserver.ContentTypeState} contentTypeState
|
|
208
|
+
* @private
|
|
209
|
+
*/
|
|
210
|
+
purgeQualityChangePositions_(contentTypeState) {
|
|
211
|
+
const bufferedInfo = this.getBufferedInfo_();
|
|
212
|
+
const bufferedRanges = bufferedInfo[contentTypeState.contentType];
|
|
213
|
+
|
|
214
|
+
if (bufferedRanges && bufferedRanges.length > 0) {
|
|
215
|
+
const bufferStart = bufferedRanges[0].start;
|
|
216
|
+
const bufferEnd = bufferedRanges[bufferedRanges.length - 1].end;
|
|
217
|
+
const oldChangePositions = contentTypeState.qualityChangePositions;
|
|
218
|
+
contentTypeState.qualityChangePositions =
|
|
219
|
+
oldChangePositions.filter(
|
|
220
|
+
(qualityChange, index) => {
|
|
221
|
+
// Remove all but last quality change before bufferStart.
|
|
222
|
+
if ((qualityChange.position <= bufferStart) &&
|
|
223
|
+
(index + 1 < oldChangePositions.length) &&
|
|
224
|
+
(oldChangePositions[index + 1].position <= bufferStart)) {
|
|
225
|
+
return false;
|
|
226
|
+
}
|
|
227
|
+
// Remove all quality changes after bufferEnd.
|
|
228
|
+
if (qualityChange.position >= bufferEnd) {
|
|
229
|
+
return false;
|
|
230
|
+
}
|
|
231
|
+
return true;
|
|
232
|
+
});
|
|
233
|
+
} else {
|
|
234
|
+
// Nothing is buffered; so remove all quality changes.
|
|
235
|
+
contentTypeState.qualityChangePositions = [];
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* @typedef {function(shaka.extern.MediaQualityInfo, number)}
|
|
242
|
+
*
|
|
243
|
+
* @description
|
|
244
|
+
* A callback function used to notify the player when media quality changes
|
|
245
|
+
* are detected at the playhead.
|
|
246
|
+
*
|
|
247
|
+
* The first argument is information about media quality at the playhead
|
|
248
|
+
* position.
|
|
249
|
+
*
|
|
250
|
+
* The second argument is the playhead position in seconds.
|
|
251
|
+
*/
|
|
252
|
+
shaka.media.QualityObserver.EventListener;
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* @typedef {{
|
|
256
|
+
* mediaQuality: !shaka.extern.MediaQualityInfo,
|
|
257
|
+
* position: !number
|
|
258
|
+
* }}
|
|
259
|
+
*
|
|
260
|
+
* @description
|
|
261
|
+
* Identifies the position of a media quality change in the
|
|
262
|
+
* source buffer.
|
|
263
|
+
*
|
|
264
|
+
* @property {shaka.extern.MediaQualityInfo} !mediaQuality
|
|
265
|
+
* The new media quality for content after position in the source buffer.
|
|
266
|
+
* @property {number} !position
|
|
267
|
+
* A position in seconds in the source buffer
|
|
268
|
+
*/
|
|
269
|
+
shaka.media.QualityObserver.QualityChangePosition;
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* @typedef {{
|
|
273
|
+
* qualityChangePositions:
|
|
274
|
+
* !Array.<shaka.media.QualityObserver.QualityChangePosition>,
|
|
275
|
+
* currentQuality: ?shaka.extern.MediaQualityInfo,
|
|
276
|
+
* contentType: !string
|
|
277
|
+
* }}
|
|
278
|
+
*
|
|
279
|
+
* @description
|
|
280
|
+
* Contains media quality information for a specific content type
|
|
281
|
+
* e.g video or audio.
|
|
282
|
+
*
|
|
283
|
+
* @property {!Array.<shaka.media.QualityObserver.QualityChangePosition>}
|
|
284
|
+
* qualityChangePositions
|
|
285
|
+
* Quality changes ordered by position ascending.
|
|
286
|
+
* @property {?shaka.media.MediaQualityInfo} currentMediaQuality
|
|
287
|
+
* The media quality at the playhead position.
|
|
288
|
+
* @property {string} contentType
|
|
289
|
+
* The contentType e.g. 'video' or 'audio'
|
|
290
|
+
*/
|
|
291
|
+
shaka.media.QualityObserver.ContentTypeState;
|
|
@@ -347,7 +347,8 @@ shaka.media.SegmentIndex = class {
|
|
|
347
347
|
lastReference.appendWindowStart,
|
|
348
348
|
lastReference.appendWindowEnd,
|
|
349
349
|
lastReference.partialReferences,
|
|
350
|
-
lastReference.tilesLayout
|
|
350
|
+
lastReference.tilesLayout,
|
|
351
|
+
lastReference.tileDuration);
|
|
351
352
|
}
|
|
352
353
|
|
|
353
354
|
|
|
@@ -427,6 +428,12 @@ shaka.media.SegmentIndex = class {
|
|
|
427
428
|
return new shaka.media.SegmentIterator(this, index, partialSegmentIndex);
|
|
428
429
|
}
|
|
429
430
|
|
|
431
|
+
/**
|
|
432
|
+
* @return {boolean}
|
|
433
|
+
*/
|
|
434
|
+
isEmpty() {
|
|
435
|
+
return this.references.length == 0;
|
|
436
|
+
}
|
|
430
437
|
|
|
431
438
|
/**
|
|
432
439
|
* Create a SegmentIndex for a single segment of the given start time and
|
|
@@ -23,11 +23,13 @@ shaka.media.InitSegmentReference = class {
|
|
|
23
23
|
* of the resource containing the segment.
|
|
24
24
|
* @param {number} startByte The offset from the start of the resource to the
|
|
25
25
|
* start of the segment.
|
|
26
|
-
* @param {?number} endByte The offset from the start of the resource
|
|
27
|
-
* end of the segment, inclusive. A value of null indicates that the
|
|
26
|
+
* @param {?number} endByte The offset from the start of the resource
|
|
27
|
+
* to the end of the segment, inclusive. A value of null indicates that the
|
|
28
28
|
* segment extends to the end of the resource.
|
|
29
|
+
* @param {null|shaka.extern.MediaQualityInfo=} mediaQuality Information about
|
|
30
|
+
* the quality of the media associated with this init segment.
|
|
29
31
|
*/
|
|
30
|
-
constructor(uris, startByte, endByte) {
|
|
32
|
+
constructor(uris, startByte, endByte, mediaQuality = null) {
|
|
31
33
|
/** @type {function():!Array.<string>} */
|
|
32
34
|
this.getUris = uris;
|
|
33
35
|
|
|
@@ -36,6 +38,9 @@ shaka.media.InitSegmentReference = class {
|
|
|
36
38
|
|
|
37
39
|
/** @const {?number} */
|
|
38
40
|
this.endByte = endByte;
|
|
41
|
+
|
|
42
|
+
/** @const {shaka.extern.MediaQualityInfo|null} */
|
|
43
|
+
this.mediaQuality = mediaQuality;
|
|
39
44
|
}
|
|
40
45
|
|
|
41
46
|
/**
|
|
@@ -73,6 +78,16 @@ shaka.media.InitSegmentReference = class {
|
|
|
73
78
|
}
|
|
74
79
|
}
|
|
75
80
|
|
|
81
|
+
/**
|
|
82
|
+
* Returns media quality information for the segments associated with
|
|
83
|
+
* this init segment.
|
|
84
|
+
*
|
|
85
|
+
* @return {?shaka.extern.MediaQualityInfo}
|
|
86
|
+
*/
|
|
87
|
+
getMediaQuality() {
|
|
88
|
+
return this.mediaQuality;
|
|
89
|
+
}
|
|
90
|
+
|
|
76
91
|
/**
|
|
77
92
|
* Check if two initSegmentReference have all the same values.
|
|
78
93
|
* @param {?shaka.media.InitSegmentReference} reference1
|
|
@@ -140,11 +155,15 @@ shaka.media.SegmentReference = class {
|
|
|
140
155
|
* The value is a grid-item-dimension consisting of two positive decimal
|
|
141
156
|
* integers in the format: column-x-row ('4x3'). It describes the
|
|
142
157
|
* arrangement of Images in a Grid. The minimum valid LAYOUT is '1x1'.
|
|
158
|
+
* @param {?number=} tileDuration
|
|
159
|
+
* The explicit duration of an individual tile within the tiles grid.
|
|
160
|
+
* If not provided, the duration should be automatically calculated based on
|
|
161
|
+
* the duration of the reference.
|
|
143
162
|
*/
|
|
144
163
|
constructor(
|
|
145
164
|
startTime, endTime, uris, startByte, endByte, initSegmentReference,
|
|
146
165
|
timestampOffset, appendWindowStart, appendWindowEnd,
|
|
147
|
-
partialReferences = [], tilesLayout = '') {
|
|
166
|
+
partialReferences = [], tilesLayout = '', tileDuration = null) {
|
|
148
167
|
// A preload hinted Partial Segment has the same startTime and endTime.
|
|
149
168
|
goog.asserts.assert(startTime <= endTime,
|
|
150
169
|
'startTime must be less than or equal to endTime');
|
|
@@ -191,6 +210,9 @@ shaka.media.SegmentReference = class {
|
|
|
191
210
|
|
|
192
211
|
/** @type {?string} */
|
|
193
212
|
this.tilesLayout = tilesLayout;
|
|
213
|
+
|
|
214
|
+
/** @type {?number} */
|
|
215
|
+
this.tileDuration = tileDuration;
|
|
194
216
|
}
|
|
195
217
|
|
|
196
218
|
/**
|
|
@@ -275,6 +297,17 @@ shaka.media.SegmentReference = class {
|
|
|
275
297
|
getTilesLayout() {
|
|
276
298
|
return this.tilesLayout;
|
|
277
299
|
}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* Returns the segment's explicit tile duration.
|
|
303
|
+
* Only defined in image segments.
|
|
304
|
+
*
|
|
305
|
+
* @return {?number}
|
|
306
|
+
* @export
|
|
307
|
+
*/
|
|
308
|
+
getTileDuration() {
|
|
309
|
+
return this.tileDuration;
|
|
310
|
+
}
|
|
278
311
|
};
|
|
279
312
|
|
|
280
313
|
|
|
@@ -431,6 +431,11 @@ shaka.media.StreamingEngine = class {
|
|
|
431
431
|
this.playerInterface_.mediaSourceEngine.reinitText(fullMimeType);
|
|
432
432
|
}
|
|
433
433
|
|
|
434
|
+
// Releases the segmentIndex of the old stream.
|
|
435
|
+
if (mediaState.stream.closeSegmentIndex) {
|
|
436
|
+
mediaState.stream.closeSegmentIndex();
|
|
437
|
+
}
|
|
438
|
+
|
|
434
439
|
mediaState.stream = stream;
|
|
435
440
|
mediaState.segmentIterator = null;
|
|
436
441
|
|
|
@@ -623,7 +628,7 @@ shaka.media.StreamingEngine = class {
|
|
|
623
628
|
|
|
624
629
|
// Don't clear the buffer unless something is buffered. This extra
|
|
625
630
|
// check prevents extra, useless calls to clear the buffer.
|
|
626
|
-
if (somethingBuffered) {
|
|
631
|
+
if (somethingBuffered || mediaState.performingUpdate) {
|
|
627
632
|
this.forceClearBuffer_(mediaState);
|
|
628
633
|
streamCleared = true;
|
|
629
634
|
}
|
|
@@ -863,7 +868,7 @@ shaka.media.StreamingEngine = class {
|
|
|
863
868
|
return;
|
|
864
869
|
}
|
|
865
870
|
|
|
866
|
-
// Make sure the segment index exists.
|
|
871
|
+
// Make sure the segment index exists. If not, create the segment index.
|
|
867
872
|
if (!mediaState.stream.segmentIndex) {
|
|
868
873
|
const thisStream = mediaState.stream;
|
|
869
874
|
|
|
@@ -873,6 +878,12 @@ shaka.media.StreamingEngine = class {
|
|
|
873
878
|
// We switched streams while in the middle of this async call to
|
|
874
879
|
// createSegmentIndex. Abandon this update and schedule a new one if
|
|
875
880
|
// there's not already one pending.
|
|
881
|
+
// Releases the segmentIndex of the old stream.
|
|
882
|
+
if (thisStream.closeSegmentIndex) {
|
|
883
|
+
goog.asserts.assert(!mediaState.stream.segmentIndex,
|
|
884
|
+
'mediastate.stream should not have segmentIndex yet.');
|
|
885
|
+
thisStream.closeSegmentIndex();
|
|
886
|
+
}
|
|
876
887
|
if (mediaState.updateTimer == null) {
|
|
877
888
|
this.scheduleUpdate_(mediaState, 0);
|
|
878
889
|
}
|
|
@@ -977,7 +988,10 @@ shaka.media.StreamingEngine = class {
|
|
|
977
988
|
unscaledBufferingGoal * this.bufferingGoalScale_;
|
|
978
989
|
|
|
979
990
|
// Check if we've buffered to the end of the presentation.
|
|
980
|
-
|
|
991
|
+
const timeUntilEnd =
|
|
992
|
+
this.manifest_.presentationTimeline.getDuration() - timeNeeded;
|
|
993
|
+
const oneMicrosecond = 1e-6;
|
|
994
|
+
if (timeUntilEnd < oneMicrosecond) {
|
|
981
995
|
// We shouldn't rebuffer if the playhead is close to the end of the
|
|
982
996
|
// presentation.
|
|
983
997
|
shaka.log.debug(logPrefix, 'buffered to end of presentation');
|
|
@@ -1530,6 +1544,8 @@ shaka.media.StreamingEngine = class {
|
|
|
1530
1544
|
throw error;
|
|
1531
1545
|
}
|
|
1532
1546
|
};
|
|
1547
|
+
this.playerInterface_.onInitSegmentAppended(
|
|
1548
|
+
reference.startTime, reference.initSegmentReference);
|
|
1533
1549
|
operations.push(append());
|
|
1534
1550
|
}
|
|
1535
1551
|
}
|
|
@@ -1555,7 +1571,9 @@ shaka.media.StreamingEngine = class {
|
|
|
1555
1571
|
|
|
1556
1572
|
const hasClosedCaptions = stream.closedCaptions &&
|
|
1557
1573
|
stream.closedCaptions.size > 0;
|
|
1558
|
-
if (stream.emsgSchemeIdUris != null &&
|
|
1574
|
+
if ((stream.emsgSchemeIdUris != null &&
|
|
1575
|
+
stream.emsgSchemeIdUris.length > 0) ||
|
|
1576
|
+
this.config_.dispatchAllEmsgBoxes) {
|
|
1559
1577
|
new shaka.util.Mp4Parser()
|
|
1560
1578
|
.fullBox(
|
|
1561
1579
|
'emsg',
|
|
@@ -1641,7 +1659,8 @@ shaka.media.StreamingEngine = class {
|
|
|
1641
1659
|
// See DASH sec. 5.10.3.3.1
|
|
1642
1660
|
// If a DASH client detects an event message box with a scheme that is not
|
|
1643
1661
|
// defined in MPD, the client is expected to ignore it.
|
|
1644
|
-
if (emsgSchemeIdUris.includes(schemeId)
|
|
1662
|
+
if (emsgSchemeIdUris.includes(schemeId) ||
|
|
1663
|
+
this.config_.dispatchAllEmsgBoxes) {
|
|
1645
1664
|
// See DASH sec. 5.10.4.1
|
|
1646
1665
|
// A special scheme in DASH used to signal manifest updates.
|
|
1647
1666
|
if (schemeId == 'urn:mpeg:dash:event:2012') {
|
|
@@ -1662,7 +1681,8 @@ shaka.media.StreamingEngine = class {
|
|
|
1662
1681
|
|
|
1663
1682
|
// Dispatch an event to notify the application about the emsg box.
|
|
1664
1683
|
const eventName = shaka.Player.EventName.Emsg;
|
|
1665
|
-
const
|
|
1684
|
+
const data = (new Map()).set('detail', emsg);
|
|
1685
|
+
const event = new shaka.util.FakeEvent(eventName, data);
|
|
1666
1686
|
this.playerInterface_.onEvent(event);
|
|
1667
1687
|
}
|
|
1668
1688
|
}
|
|
@@ -1763,6 +1783,19 @@ shaka.media.StreamingEngine = class {
|
|
|
1763
1783
|
|
|
1764
1784
|
shaka.log.v2('fetching: reference=', reference);
|
|
1765
1785
|
|
|
1786
|
+
const stream = mediaState.stream;
|
|
1787
|
+
this.playerInterface_.modifySegmentRequest(
|
|
1788
|
+
request,
|
|
1789
|
+
{
|
|
1790
|
+
type: stream.type,
|
|
1791
|
+
init: reference instanceof shaka.media.InitSegmentReference,
|
|
1792
|
+
duration: reference.endTime - reference.startTime,
|
|
1793
|
+
mimeType: stream.mimeType,
|
|
1794
|
+
codecs: stream.codecs,
|
|
1795
|
+
bandwidth: stream.bandwidth,
|
|
1796
|
+
},
|
|
1797
|
+
);
|
|
1798
|
+
|
|
1766
1799
|
const op = this.playerInterface_.netEngine.request(requestType, request);
|
|
1767
1800
|
mediaState.operation = op;
|
|
1768
1801
|
const response = await op.promise;
|
|
@@ -1926,12 +1959,15 @@ shaka.media.StreamingEngine = class {
|
|
|
1926
1959
|
* @typedef {{
|
|
1927
1960
|
* getPresentationTime: function():number,
|
|
1928
1961
|
* getBandwidthEstimate: function():number,
|
|
1962
|
+
* modifySegmentRequest: function(shaka.extern.Request,
|
|
1963
|
+
* shaka.util.CmcdManager.SegmentInfo),
|
|
1929
1964
|
* mediaSourceEngine: !shaka.media.MediaSourceEngine,
|
|
1930
1965
|
* netEngine: shaka.net.NetworkingEngine,
|
|
1931
1966
|
* onError: function(!shaka.util.Error),
|
|
1932
1967
|
* onEvent: function(!Event),
|
|
1933
1968
|
* onManifestUpdate: function(),
|
|
1934
|
-
* onSegmentAppended: function()
|
|
1969
|
+
* onSegmentAppended: function(),
|
|
1970
|
+
* onInitSegmentAppended: function(!number,!shaka.media.InitSegmentReference)
|
|
1935
1971
|
* }}
|
|
1936
1972
|
*
|
|
1937
1973
|
* @property {function():number} getPresentationTime
|
|
@@ -1939,6 +1975,9 @@ shaka.media.StreamingEngine = class {
|
|
|
1939
1975
|
* viewer is seeing on screen right now.
|
|
1940
1976
|
* @property {function():number} getBandwidthEstimate
|
|
1941
1977
|
* Get the estimated bandwidth in bits per second.
|
|
1978
|
+
* @property {function(shaka.extern.Request,
|
|
1979
|
+
* shaka.extern.Cmcd.SegmentInfo)} modifySegmentRequest
|
|
1980
|
+
* The request modifier
|
|
1942
1981
|
* @property {!shaka.media.MediaSourceEngine} mediaSourceEngine
|
|
1943
1982
|
* The MediaSourceEngine. The caller retains ownership.
|
|
1944
1983
|
* @property {shaka.net.NetworkingEngine} netEngine
|
|
@@ -1953,6 +1992,9 @@ shaka.media.StreamingEngine = class {
|
|
|
1953
1992
|
* Called when an embedded 'emsg' box should trigger a manifest update.
|
|
1954
1993
|
* @property {function()} onSegmentAppended
|
|
1955
1994
|
* Called after a segment is successfully appended to a MediaSource.
|
|
1995
|
+
* @property
|
|
1996
|
+
* {function(!number, !shaka.media.InitSegmentReference)} onInitSegmentAppended
|
|
1997
|
+
* Called when an init segment is appended to a MediaSource.
|
|
1956
1998
|
*/
|
|
1957
1999
|
shaka.media.StreamingEngine.PlayerInterface;
|
|
1958
2000
|
|
|
@@ -27,10 +27,12 @@ shaka.net.HttpFetchPlugin = class {
|
|
|
27
27
|
* @param {shaka.net.NetworkingEngine.RequestType} requestType
|
|
28
28
|
* @param {shaka.extern.ProgressUpdated} progressUpdated Called when a
|
|
29
29
|
* progress event happened.
|
|
30
|
+
* @param {shaka.extern.HeadersReceived} headersReceived Called when the
|
|
31
|
+
* headers for the download are received, but before the body is.
|
|
30
32
|
* @return {!shaka.extern.IAbortableOperation.<shaka.extern.Response>}
|
|
31
33
|
* @export
|
|
32
34
|
*/
|
|
33
|
-
static parse(uri, request, requestType, progressUpdated) {
|
|
35
|
+
static parse(uri, request, requestType, progressUpdated, headersReceived) {
|
|
34
36
|
const headers = new shaka.net.HttpFetchPlugin.Headers_();
|
|
35
37
|
shaka.util.MapUtils.asMap(request.headers).forEach((value, key) => {
|
|
36
38
|
headers.append(key, value);
|
|
@@ -55,7 +57,7 @@ shaka.net.HttpFetchPlugin = class {
|
|
|
55
57
|
};
|
|
56
58
|
|
|
57
59
|
const pendingRequest = shaka.net.HttpFetchPlugin.request_(
|
|
58
|
-
uri, requestType, init, abortStatus, progressUpdated,
|
|
60
|
+
uri, requestType, init, abortStatus, progressUpdated, headersReceived,
|
|
59
61
|
request.streamDataCallback);
|
|
60
62
|
|
|
61
63
|
/** @type {!shaka.util.AbortableOperation} */
|
|
@@ -92,12 +94,13 @@ shaka.net.HttpFetchPlugin = class {
|
|
|
92
94
|
* @param {!RequestInit} init
|
|
93
95
|
* @param {shaka.net.HttpFetchPlugin.AbortStatus} abortStatus
|
|
94
96
|
* @param {shaka.extern.ProgressUpdated} progressUpdated
|
|
97
|
+
* @param {shaka.extern.HeadersReceived} headersReceived
|
|
95
98
|
* @param {?function(BufferSource):!Promise} streamDataCallback
|
|
96
99
|
* @return {!Promise<!shaka.extern.Response>}
|
|
97
100
|
* @private
|
|
98
101
|
*/
|
|
99
102
|
static async request_(uri, requestType, init, abortStatus, progressUpdated,
|
|
100
|
-
streamDataCallback) {
|
|
103
|
+
headersReceived, streamDataCallback) {
|
|
101
104
|
const fetch = shaka.net.HttpFetchPlugin.fetch_;
|
|
102
105
|
const ReadableStream = shaka.net.HttpFetchPlugin.ReadableStream_;
|
|
103
106
|
let response;
|
|
@@ -113,6 +116,10 @@ shaka.net.HttpFetchPlugin = class {
|
|
|
113
116
|
// headers are available. The download itself isn't done until the promise
|
|
114
117
|
// for retrieving the data (arrayBuffer, blob, etc) has resolved.
|
|
115
118
|
response = await fetch(uri, init);
|
|
119
|
+
// At this point in the process, we have the headers of the response, but
|
|
120
|
+
// not the body yet.
|
|
121
|
+
headersReceived(shaka.net.HttpFetchPlugin.headersToGenericObject_(
|
|
122
|
+
response.headers));
|
|
116
123
|
// Getting the reader in this way allows us to observe the process of
|
|
117
124
|
// downloading the body, instead of just waiting for an opaque promise to
|
|
118
125
|
// resolve.
|
|
@@ -194,19 +201,28 @@ shaka.net.HttpFetchPlugin = class {
|
|
|
194
201
|
}
|
|
195
202
|
}
|
|
196
203
|
|
|
197
|
-
const headers =
|
|
198
|
-
|
|
199
|
-
const responseHeaders = response.headers;
|
|
200
|
-
responseHeaders.forEach((value, key) => {
|
|
201
|
-
// Since Edge incorrectly return the header with a leading new line
|
|
202
|
-
// character ('\n'), we trim the header here.
|
|
203
|
-
headers[key.trim()] = value;
|
|
204
|
-
});
|
|
204
|
+
const headers = shaka.net.HttpFetchPlugin.headersToGenericObject_(
|
|
205
|
+
response.headers);
|
|
205
206
|
|
|
206
207
|
return shaka.net.HttpPluginUtils.makeResponse(
|
|
207
208
|
headers, arrayBuffer, response.status, uri, response.url, requestType);
|
|
208
209
|
}
|
|
209
210
|
|
|
211
|
+
/**
|
|
212
|
+
* @param {!Headers} headers
|
|
213
|
+
* @return {!Object.<string, string>}
|
|
214
|
+
* @private
|
|
215
|
+
*/
|
|
216
|
+
static headersToGenericObject_(headers) {
|
|
217
|
+
const headersObj = {};
|
|
218
|
+
headers.forEach((value, key) => {
|
|
219
|
+
// Since Edge incorrectly return the header with a leading new line
|
|
220
|
+
// character ('\n'), we trim the header here.
|
|
221
|
+
headersObj[key.trim()] = value;
|
|
222
|
+
});
|
|
223
|
+
return headersObj;
|
|
224
|
+
}
|
|
225
|
+
|
|
210
226
|
/**
|
|
211
227
|
* Determine if the Fetch API is supported in the browser. Note: this is
|
|
212
228
|
* deliberately exposed as a method to allow the client app to use the same
|
|
@@ -290,4 +306,8 @@ if (shaka.net.HttpFetchPlugin.isSupported()) {
|
|
|
290
306
|
'https', shaka.net.HttpFetchPlugin.parse,
|
|
291
307
|
shaka.net.NetworkingEngine.PluginPriority.PREFERRED,
|
|
292
308
|
/* progressSupport= */ true);
|
|
309
|
+
shaka.net.NetworkingEngine.registerScheme(
|
|
310
|
+
'blob', shaka.net.HttpFetchPlugin.parse,
|
|
311
|
+
shaka.net.NetworkingEngine.PluginPriority.PREFERRED,
|
|
312
|
+
/* progressSupport= */ true);
|
|
293
313
|
}
|