shaka-player 4.7.12 → 4.8.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/AUTHORS +1 -0
- package/CONTRIBUTORS +1 -0
- package/LICENSE +54 -0
- package/README.md +22 -1
- package/dist/controls.css +1 -1
- package/dist/controls.css.map +1 -1
- package/dist/deps.js +36 -25
- package/dist/locales.js +40 -30
- package/dist/shaka-player.compiled.d.ts +196 -46
- package/dist/shaka-player.compiled.debug.d.ts +196 -46
- package/dist/shaka-player.compiled.debug.externs.js +474 -337
- package/dist/shaka-player.compiled.debug.js +992 -871
- package/dist/shaka-player.compiled.debug.map +4 -4
- package/dist/shaka-player.compiled.externs.js +474 -337
- package/dist/shaka-player.compiled.js +1248 -1174
- package/dist/shaka-player.compiled.map +4 -4
- package/dist/shaka-player.ui.d.ts +259 -49
- package/dist/shaka-player.ui.debug.d.ts +259 -49
- package/dist/shaka-player.ui.debug.externs.js +591 -376
- package/dist/shaka-player.ui.debug.js +1208 -1003
- package/dist/shaka-player.ui.debug.map +4 -4
- package/dist/shaka-player.ui.externs.js +591 -376
- package/dist/shaka-player.ui.js +1533 -1408
- package/dist/shaka-player.ui.map +4 -4
- package/externs/device_sensor_event.js +18 -0
- package/externs/hisense.js +16 -0
- package/externs/playstation.js +31 -0
- package/externs/shaka/abr_manager.js +3 -1
- package/externs/shaka/ads.js +5 -0
- package/externs/shaka/codecs.js +13 -0
- package/externs/shaka/manifest.js +69 -7
- package/externs/shaka/net.js +13 -1
- package/externs/shaka/offline.js +7 -1
- package/externs/shaka/player.js +184 -13
- package/externs/shaka/text.js +19 -0
- package/externs/tizen.js +41 -0
- package/externs/webos.js +19 -0
- package/externs/xbox.js +43 -0
- package/lib/abr/simple_abr_manager.js +9 -1
- package/lib/ads/ad_manager.js +19 -2
- package/lib/ads/client_side_ad_manager.js +5 -3
- package/lib/ads/media_tailor_ad_manager.js +9 -0
- package/lib/cast/cast_receiver.js +0 -14
- package/lib/cast/cast_utils.js +1 -0
- package/lib/cea/mp4_cea_parser.js +6 -1
- package/lib/dash/content_protection.js +97 -53
- package/lib/dash/dash_parser.js +240 -179
- package/lib/dash/mpd_utils.js +72 -65
- package/lib/dash/segment_base.js +35 -22
- package/lib/dash/segment_list.js +39 -31
- package/lib/dash/segment_template.js +47 -23
- package/lib/hls/hls_parser.js +376 -165
- package/lib/media/buffering_observer.js +25 -1
- package/lib/media/drm_engine.js +91 -71
- package/lib/media/manifest_filterer.js +220 -0
- package/lib/media/media_source_engine.js +108 -29
- package/lib/media/playhead.js +3 -3
- package/lib/media/preload_manager.js +827 -0
- package/lib/media/presentation_timeline.js +11 -0
- package/lib/media/segment_index.js +60 -49
- package/lib/media/segment_prefetch.js +140 -43
- package/lib/media/segment_reference.js +31 -8
- package/lib/media/segment_utils.js +208 -25
- package/lib/media/streaming_engine.js +321 -77
- package/lib/mss/content_protection.js +31 -25
- package/lib/mss/mss_parser.js +78 -55
- package/lib/net/networking_engine.js +49 -4
- package/lib/offline/indexeddb/v1_storage_cell.js +2 -0
- package/lib/offline/indexeddb/v2_storage_cell.js +2 -0
- package/lib/offline/manifest_converter.js +10 -3
- package/lib/offline/storage.js +5 -3
- package/lib/player.js +896 -674
- package/lib/polyfill/media_capabilities.js +16 -1
- package/lib/text/simple_text_displayer.js +9 -0
- package/lib/text/stub_text_displayer.js +7 -0
- package/lib/text/text_engine.js +16 -3
- package/lib/text/ttml_text_parser.js +116 -114
- package/lib/text/ui_text_displayer.js +28 -11
- package/lib/text/vtt_text_parser.js +54 -115
- package/lib/transmuxer/opus.js +91 -0
- package/lib/transmuxer/ts_transmuxer.js +115 -0
- package/lib/util/cmcd_manager.js +152 -3
- package/lib/util/dom_utils.js +11 -0
- package/lib/util/error.js +5 -0
- package/lib/util/fake_event.js +2 -0
- package/lib/util/manifest_parser_utils.js +97 -3
- package/lib/util/mime_utils.js +10 -5
- package/lib/util/mp4_box_parsers.js +346 -17
- package/lib/util/mp4_generator.js +23 -0
- package/lib/util/periods.js +24 -37
- package/lib/util/platform.js +161 -9
- package/lib/util/player_configuration.js +61 -3
- package/lib/util/stats.js +16 -0
- package/lib/util/stream_utils.js +350 -277
- package/lib/util/string_utils.js +36 -0
- package/lib/util/tXml.js +814 -0
- package/lib/util/ts_parser.js +50 -0
- package/lib/util/xml_utils.js +10 -338
- package/package.json +2 -2
- package/shaka-player.uncompiled.js +8 -0
- package/ui/controls.js +187 -5
- package/ui/enums.js +2 -0
- package/ui/externs/ui.js +30 -3
- package/ui/gl_matrix/matrix_4x4.js +568 -0
- package/ui/gl_matrix/matrix_quaternion.js +45 -0
- package/ui/less/containers.less +11 -0
- package/ui/locales/en-GB.json +2 -0
- package/ui/locales/en.json +2 -0
- package/ui/locales/es-419.json +2 -0
- package/ui/locales/es.json +2 -0
- package/ui/locales/source.json +8 -0
- package/ui/recenter_vr.js +126 -0
- package/ui/resolution_selection.js +25 -5
- package/ui/statistics_button.js +12 -0
- package/ui/text_selection.js +1 -1
- package/ui/toggle_stereoscopic.js +127 -0
- package/ui/ui.js +74 -38
- package/ui/vr_manager.js +557 -0
- package/ui/vr_utils.js +232 -0
- package/ui/vr_webgl.js +697 -0
|
@@ -27,6 +27,7 @@ goog.require('shaka.util.Error');
|
|
|
27
27
|
goog.require('shaka.util.FakeEvent');
|
|
28
28
|
goog.require('shaka.util.IDestroyable');
|
|
29
29
|
goog.require('shaka.util.Id3Utils');
|
|
30
|
+
goog.require('shaka.util.LanguageUtils');
|
|
30
31
|
goog.require('shaka.util.ManifestParserUtils');
|
|
31
32
|
goog.require('shaka.util.MimeUtils');
|
|
32
33
|
goog.require('shaka.util.Mp4BoxParsers');
|
|
@@ -122,6 +123,17 @@ shaka.media.StreamingEngine = class {
|
|
|
122
123
|
|
|
123
124
|
/** @private {number} */
|
|
124
125
|
this.lastMediaSourceReset_ = Date.now() / 1000;
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* @private {!Map<shaka.extern.Stream, !shaka.media.SegmentPrefetch>}
|
|
129
|
+
*/
|
|
130
|
+
this.audioPrefetchMap_ = new Map();
|
|
131
|
+
|
|
132
|
+
/** @private {!shaka.extern.SpatialVideoInfo} */
|
|
133
|
+
this.spatialVideoInfo_ = {
|
|
134
|
+
projection: null,
|
|
135
|
+
hfov: null,
|
|
136
|
+
};
|
|
125
137
|
}
|
|
126
138
|
|
|
127
139
|
/** @override */
|
|
@@ -140,10 +152,14 @@ shaka.media.StreamingEngine = class {
|
|
|
140
152
|
this.cancelUpdate_(state);
|
|
141
153
|
aborts.push(this.abortOperations_(state));
|
|
142
154
|
}
|
|
155
|
+
for (const prefetch of this.audioPrefetchMap_.values()) {
|
|
156
|
+
prefetch.clearAll();
|
|
157
|
+
}
|
|
143
158
|
|
|
144
159
|
await Promise.all(aborts);
|
|
145
160
|
|
|
146
161
|
this.mediaStates_.clear();
|
|
162
|
+
this.audioPrefetchMap_.clear();
|
|
147
163
|
|
|
148
164
|
this.playerInterface_ = null;
|
|
149
165
|
this.manifest_ = null;
|
|
@@ -180,6 +196,40 @@ shaka.media.StreamingEngine = class {
|
|
|
180
196
|
this.failureCallbackBackoff_ =
|
|
181
197
|
new shaka.net.Backoff(failureRetryParams, autoReset);
|
|
182
198
|
|
|
199
|
+
const ContentType = shaka.util.ManifestParserUtils.ContentType;
|
|
200
|
+
|
|
201
|
+
// disable audio segment prefetch if this is now set
|
|
202
|
+
if (config.disableAudioPrefetch) {
|
|
203
|
+
const state = this.mediaStates_.get(ContentType.AUDIO);
|
|
204
|
+
if (state && state.segmentPrefetch) {
|
|
205
|
+
state.segmentPrefetch.clearAll();
|
|
206
|
+
state.segmentPrefetch = null;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
for (const stream of this.audioPrefetchMap_.keys()) {
|
|
210
|
+
const prefetch = this.audioPrefetchMap_.get(stream);
|
|
211
|
+
prefetch.clearAll();
|
|
212
|
+
this.audioPrefetchMap_.delete(stream);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
// disable text segment prefetch if this is now set
|
|
216
|
+
if (config.disableTextPrefetch) {
|
|
217
|
+
const state = this.mediaStates_.get(ContentType.TEXT);
|
|
218
|
+
if (state && state.segmentPrefetch) {
|
|
219
|
+
state.segmentPrefetch.clearAll();
|
|
220
|
+
state.segmentPrefetch = null;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// disable video segment prefetch if this is now set
|
|
225
|
+
if (config.disableVideoPrefetch) {
|
|
226
|
+
const state = this.mediaStates_.get(ContentType.VIDEO);
|
|
227
|
+
if (state && state.segmentPrefetch) {
|
|
228
|
+
state.segmentPrefetch.clearAll();
|
|
229
|
+
state.segmentPrefetch = null;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
183
233
|
// Allow configuring the segment prefetch in middle of the playback.
|
|
184
234
|
for (const type of this.mediaStates_.keys()) {
|
|
185
235
|
const state = this.mediaStates_.get(type);
|
|
@@ -194,6 +244,10 @@ shaka.media.StreamingEngine = class {
|
|
|
194
244
|
state.segmentPrefetch = this.createSegmentPrefetch_(state.stream);
|
|
195
245
|
}
|
|
196
246
|
}
|
|
247
|
+
|
|
248
|
+
if (!config.disableAudioPrefetch) {
|
|
249
|
+
this.updatePrefetchMapForAudio_();
|
|
250
|
+
}
|
|
197
251
|
}
|
|
198
252
|
|
|
199
253
|
|
|
@@ -205,14 +259,17 @@ shaka.media.StreamingEngine = class {
|
|
|
205
259
|
* chosen by a prior call to switchTextStream(). Once the Promise resolves,
|
|
206
260
|
* switch*() may be called freely.
|
|
207
261
|
*
|
|
262
|
+
* @param {!Map.<number, shaka.media.SegmentPrefetch>=} segmentPrefetchById
|
|
263
|
+
* If provided, segments prefetched for these streams will be used as needed
|
|
264
|
+
* during playback.
|
|
208
265
|
* @return {!Promise}
|
|
209
266
|
*/
|
|
210
|
-
async start() {
|
|
267
|
+
async start(segmentPrefetchById) {
|
|
211
268
|
goog.asserts.assert(this.config_,
|
|
212
269
|
'StreamingEngine configure() must be called before init()!');
|
|
213
270
|
|
|
214
271
|
// Setup the initial set of Streams and then begin each update cycle.
|
|
215
|
-
await this.initStreams_();
|
|
272
|
+
await this.initStreams_(segmentPrefetchById || (new Map()));
|
|
216
273
|
this.destroyer_.ensureNotDestroyed();
|
|
217
274
|
|
|
218
275
|
shaka.log.debug('init: completed initial Stream setup');
|
|
@@ -472,7 +529,9 @@ shaka.media.StreamingEngine = class {
|
|
|
472
529
|
return;
|
|
473
530
|
}
|
|
474
531
|
|
|
475
|
-
if (
|
|
532
|
+
if (this.audioPrefetchMap_.has(stream)) {
|
|
533
|
+
mediaState.segmentPrefetch = this.audioPrefetchMap_.get(stream);
|
|
534
|
+
} else if (mediaState.segmentPrefetch) {
|
|
476
535
|
mediaState.segmentPrefetch.switchStream(stream);
|
|
477
536
|
}
|
|
478
537
|
|
|
@@ -487,8 +546,11 @@ shaka.media.StreamingEngine = class {
|
|
|
487
546
|
}
|
|
488
547
|
|
|
489
548
|
// Releases the segmentIndex of the old stream.
|
|
490
|
-
|
|
491
|
-
|
|
549
|
+
// Do not close segment indexes we are prefetching.
|
|
550
|
+
if (!this.audioPrefetchMap_.has(mediaState.stream)) {
|
|
551
|
+
if (mediaState.stream.closeSegmentIndex) {
|
|
552
|
+
mediaState.stream.closeSegmentIndex();
|
|
553
|
+
}
|
|
492
554
|
}
|
|
493
555
|
|
|
494
556
|
mediaState.stream = stream;
|
|
@@ -789,10 +851,11 @@ shaka.media.StreamingEngine = class {
|
|
|
789
851
|
* Initializes the initial streams and media states. This will schedule
|
|
790
852
|
* updates for the given types.
|
|
791
853
|
*
|
|
854
|
+
* @param {!Map.<number, shaka.media.SegmentPrefetch>} segmentPrefetchById
|
|
792
855
|
* @return {!Promise}
|
|
793
856
|
* @private
|
|
794
857
|
*/
|
|
795
|
-
async initStreams_() {
|
|
858
|
+
async initStreams_(segmentPrefetchById) {
|
|
796
859
|
const ContentType = shaka.util.ManifestParserUtils.ContentType;
|
|
797
860
|
|
|
798
861
|
goog.asserts.assert(this.config_,
|
|
@@ -845,6 +908,15 @@ shaka.media.StreamingEngine = class {
|
|
|
845
908
|
const stream = streamsByType.get(type);
|
|
846
909
|
if (!this.mediaStates_.has(type)) {
|
|
847
910
|
const mediaState = this.createMediaState_(stream);
|
|
911
|
+
if (segmentPrefetchById.has(stream.id)) {
|
|
912
|
+
const segmentPrefetch = segmentPrefetchById.get(stream.id);
|
|
913
|
+
segmentPrefetch.replaceFetchDispatcher(
|
|
914
|
+
(reference, stream, streamDataCallback) => {
|
|
915
|
+
return this.dispatchFetch_(
|
|
916
|
+
reference, stream, streamDataCallback);
|
|
917
|
+
});
|
|
918
|
+
mediaState.segmentPrefetch = segmentPrefetch;
|
|
919
|
+
}
|
|
848
920
|
this.mediaStates_.set(type, mediaState);
|
|
849
921
|
this.scheduleUpdate_(mediaState, 0);
|
|
850
922
|
}
|
|
@@ -881,7 +953,6 @@ shaka.media.StreamingEngine = class {
|
|
|
881
953
|
// The playhead might be seeking on startup, if a start time is set, so
|
|
882
954
|
// start "seeked" as true.
|
|
883
955
|
seeked: true,
|
|
884
|
-
needsResync: false,
|
|
885
956
|
recovering: false,
|
|
886
957
|
hasError: false,
|
|
887
958
|
operation: null,
|
|
@@ -896,12 +967,37 @@ shaka.media.StreamingEngine = class {
|
|
|
896
967
|
* @private
|
|
897
968
|
*/
|
|
898
969
|
createSegmentPrefetch_(stream) {
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
970
|
+
const ContentType = shaka.util.ManifestParserUtils.ContentType;
|
|
971
|
+
if (stream.type === ContentType.VIDEO &&
|
|
972
|
+
this.config_.disableVideoPrefetch) {
|
|
973
|
+
return null;
|
|
974
|
+
}
|
|
975
|
+
if (stream.type === ContentType.AUDIO &&
|
|
976
|
+
this.config_.disableAudioPrefetch) {
|
|
903
977
|
return null;
|
|
904
978
|
}
|
|
979
|
+
const MimeUtils = shaka.util.MimeUtils;
|
|
980
|
+
const CEA608_MIME = MimeUtils.CEA608_CLOSED_CAPTION_MIMETYPE;
|
|
981
|
+
const CEA708_MIME = MimeUtils.CEA708_CLOSED_CAPTION_MIMETYPE;
|
|
982
|
+
if (stream.type === ContentType.TEXT &&
|
|
983
|
+
(stream.mimeType == CEA608_MIME || stream.mimeType == CEA708_MIME)) {
|
|
984
|
+
return null;
|
|
985
|
+
}
|
|
986
|
+
if (stream.type === ContentType.TEXT &&
|
|
987
|
+
this.config_.disableTextPrefetch) {
|
|
988
|
+
return null;
|
|
989
|
+
}
|
|
990
|
+
if (this.audioPrefetchMap_.has(stream)) {
|
|
991
|
+
return this.audioPrefetchMap_.get(stream);
|
|
992
|
+
}
|
|
993
|
+
const type = /** @type {!shaka.util.ManifestParserUtils.ContentType} */
|
|
994
|
+
(stream.type);
|
|
995
|
+
const mediaState = this.mediaStates_.get(type);
|
|
996
|
+
const currentSegmentPrefetch = mediaState && mediaState.segmentPrefetch;
|
|
997
|
+
if (currentSegmentPrefetch &&
|
|
998
|
+
stream === currentSegmentPrefetch.getStream()) {
|
|
999
|
+
return currentSegmentPrefetch;
|
|
1000
|
+
}
|
|
905
1001
|
if (this.config_.segmentPrefetchLimit > 0) {
|
|
906
1002
|
return new shaka.media.SegmentPrefetch(
|
|
907
1003
|
this.config_.segmentPrefetchLimit,
|
|
@@ -914,6 +1010,75 @@ shaka.media.StreamingEngine = class {
|
|
|
914
1010
|
return null;
|
|
915
1011
|
}
|
|
916
1012
|
|
|
1013
|
+
/**
|
|
1014
|
+
* Populates the prefetch map depending on the configuration
|
|
1015
|
+
* @private
|
|
1016
|
+
*/
|
|
1017
|
+
updatePrefetchMapForAudio_() {
|
|
1018
|
+
const prefetchLimit = this.config_.segmentPrefetchLimit;
|
|
1019
|
+
const prefetchLanguages = this.config_.prefetchAudioLanguages;
|
|
1020
|
+
const LanguageUtils = shaka.util.LanguageUtils;
|
|
1021
|
+
|
|
1022
|
+
for (const variant of this.manifest_.variants) {
|
|
1023
|
+
if (!variant.audio) {
|
|
1024
|
+
continue;
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
if (this.audioPrefetchMap_.has(variant.audio)) {
|
|
1028
|
+
// if we already have a segment prefetch,
|
|
1029
|
+
// update it's prefetch limit and if the new limit isn't positive,
|
|
1030
|
+
// remove the segment prefetch from our prefetch map.
|
|
1031
|
+
const prefetch = this.audioPrefetchMap_.get(variant.audio);
|
|
1032
|
+
prefetch.resetLimit(prefetchLimit);
|
|
1033
|
+
if (!(prefetchLimit > 0) ||
|
|
1034
|
+
!prefetchLanguages.some(
|
|
1035
|
+
(lang) => LanguageUtils.areLanguageCompatible(
|
|
1036
|
+
variant.audio.language, lang))
|
|
1037
|
+
) {
|
|
1038
|
+
const type = /** @type {!shaka.util.ManifestParserUtils.ContentType}*/
|
|
1039
|
+
(variant.audio.type);
|
|
1040
|
+
const mediaState = this.mediaStates_.get(type);
|
|
1041
|
+
const currentSegmentPrefetch = mediaState &&
|
|
1042
|
+
mediaState.segmentPrefetch;
|
|
1043
|
+
// if this prefetch isn't the current one, we want to clear it
|
|
1044
|
+
if (prefetch !== currentSegmentPrefetch) {
|
|
1045
|
+
prefetch.clearAll();
|
|
1046
|
+
}
|
|
1047
|
+
this.audioPrefetchMap_.delete(variant.audio);
|
|
1048
|
+
}
|
|
1049
|
+
continue;
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
// don't try to create a new segment prefetch if the limit isn't positive.
|
|
1053
|
+
if (prefetchLimit <= 0) {
|
|
1054
|
+
continue;
|
|
1055
|
+
}
|
|
1056
|
+
|
|
1057
|
+
// only create a segment prefetch if its language is configured
|
|
1058
|
+
// to be prefetched
|
|
1059
|
+
if (!prefetchLanguages.some(
|
|
1060
|
+
(lang) => LanguageUtils.areLanguageCompatible(
|
|
1061
|
+
variant.audio.language, lang))) {
|
|
1062
|
+
continue;
|
|
1063
|
+
}
|
|
1064
|
+
|
|
1065
|
+
// use the helper to create a segment prefetch to ensure that existing
|
|
1066
|
+
// objects are reused.
|
|
1067
|
+
const segmentPrefetch = this.createSegmentPrefetch_(variant.audio);
|
|
1068
|
+
|
|
1069
|
+
// if a segment prefetch wasn't created, skip the rest
|
|
1070
|
+
if (!segmentPrefetch) {
|
|
1071
|
+
continue;
|
|
1072
|
+
}
|
|
1073
|
+
|
|
1074
|
+
if (!variant.audio.segmentIndex) {
|
|
1075
|
+
variant.audio.createSegmentIndex();
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
this.audioPrefetchMap_.set(variant.audio, segmentPrefetch);
|
|
1079
|
+
}
|
|
1080
|
+
}
|
|
1081
|
+
|
|
917
1082
|
/**
|
|
918
1083
|
* Sets the MediaSource's duration.
|
|
919
1084
|
*/
|
|
@@ -922,9 +1087,13 @@ shaka.media.StreamingEngine = class {
|
|
|
922
1087
|
if (duration < Infinity) {
|
|
923
1088
|
this.playerInterface_.mediaSourceEngine.setDuration(duration);
|
|
924
1089
|
} else {
|
|
1090
|
+
// To set the media source live duration as Infinity
|
|
1091
|
+
// If infiniteLiveStreamDuration as true
|
|
1092
|
+
const duration =
|
|
1093
|
+
this.config_.infiniteLiveStreamDuration ? Infinity : Math.pow(2, 32);
|
|
925
1094
|
// Not all platforms support infinite durations, so set a finite duration
|
|
926
1095
|
// so we can append segments and so the user agent can seek.
|
|
927
|
-
this.playerInterface_.mediaSourceEngine.setDuration(
|
|
1096
|
+
this.playerInterface_.mediaSourceEngine.setDuration(duration);
|
|
928
1097
|
}
|
|
929
1098
|
}
|
|
930
1099
|
|
|
@@ -1072,6 +1241,15 @@ shaka.media.StreamingEngine = class {
|
|
|
1072
1241
|
// Compute how far we've buffered ahead of the playhead.
|
|
1073
1242
|
const presentationTime = this.playerInterface_.getPresentationTime();
|
|
1074
1243
|
|
|
1244
|
+
if (mediaState.type === ContentType.AUDIO) {
|
|
1245
|
+
// evict all prefetched segments that are before the presentationTime
|
|
1246
|
+
for (const stream of this.audioPrefetchMap_.keys()) {
|
|
1247
|
+
const prefetch = this.audioPrefetchMap_.get(stream);
|
|
1248
|
+
prefetch.evict(presentationTime, /* clearInitSegments= */ true);
|
|
1249
|
+
prefetch.prefetchSegmentsByTime(presentationTime);
|
|
1250
|
+
}
|
|
1251
|
+
}
|
|
1252
|
+
|
|
1075
1253
|
// Get the next timestamp we need.
|
|
1076
1254
|
const timeNeeded = this.getTimeNeeded_(mediaState, presentationTime);
|
|
1077
1255
|
shaka.log.v2(logPrefix, 'timeNeeded=' + timeNeeded);
|
|
@@ -1175,14 +1353,10 @@ shaka.media.StreamingEngine = class {
|
|
|
1175
1353
|
return this.config_.updateIntervalSeconds;
|
|
1176
1354
|
}
|
|
1177
1355
|
|
|
1178
|
-
if (mediaState.segmentPrefetch && mediaState.segmentIterator
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
initSegmentReference, mediaState.lastInitSegmentReference))) {
|
|
1183
|
-
mediaState.segmentPrefetch.prefetchInitSegment(initSegmentReference);
|
|
1184
|
-
}
|
|
1185
|
-
mediaState.segmentPrefetch.prefetchSegments(reference);
|
|
1356
|
+
if (mediaState.segmentPrefetch && mediaState.segmentIterator &&
|
|
1357
|
+
!this.audioPrefetchMap_.has(mediaState.stream)) {
|
|
1358
|
+
mediaState.segmentPrefetch.evict(presentationTime);
|
|
1359
|
+
mediaState.segmentPrefetch.prefetchSegmentsByTime(reference.startTime);
|
|
1186
1360
|
}
|
|
1187
1361
|
|
|
1188
1362
|
const p = this.fetchAndAppend_(mediaState, presentationTime, reference);
|
|
@@ -1248,11 +1422,6 @@ shaka.media.StreamingEngine = class {
|
|
|
1248
1422
|
shaka.log.v1(
|
|
1249
1423
|
logPrefix, 'looking up segment from new stream endTime:', time);
|
|
1250
1424
|
|
|
1251
|
-
// Using a new iterator means we need to resync the stream in sequence
|
|
1252
|
-
// mode. The buffered range might not align perfectly with the last
|
|
1253
|
-
// segment end time, so we may end up repeating a segment. Resyncing
|
|
1254
|
-
// makes this safe to do.
|
|
1255
|
-
mediaState.needsResync = true;
|
|
1256
1425
|
mediaState.segmentIterator =
|
|
1257
1426
|
mediaState.stream.segmentIndex.getIteratorForTime(time);
|
|
1258
1427
|
const ref = mediaState.segmentIterator &&
|
|
@@ -1396,8 +1565,8 @@ shaka.media.StreamingEngine = class {
|
|
|
1396
1565
|
/* isChunkedData= */ true);
|
|
1397
1566
|
|
|
1398
1567
|
if (mediaState.segmentPrefetch && mediaState.segmentIterator) {
|
|
1399
|
-
mediaState.segmentPrefetch.
|
|
1400
|
-
reference, /* skipFirst= */ true);
|
|
1568
|
+
mediaState.segmentPrefetch.prefetchSegmentsByTime(
|
|
1569
|
+
reference.startTime, /* skipFirst= */ true);
|
|
1401
1570
|
}
|
|
1402
1571
|
}
|
|
1403
1572
|
} catch (error) {
|
|
@@ -1437,8 +1606,8 @@ shaka.media.StreamingEngine = class {
|
|
|
1437
1606
|
}
|
|
1438
1607
|
|
|
1439
1608
|
if (mediaState.segmentPrefetch && mediaState.segmentIterator) {
|
|
1440
|
-
mediaState.segmentPrefetch.
|
|
1441
|
-
reference, /* skipFirst= */ true);
|
|
1609
|
+
mediaState.segmentPrefetch.prefetchSegmentsByTime(
|
|
1610
|
+
reference.startTime, /* skipFirst= */ true);
|
|
1442
1611
|
}
|
|
1443
1612
|
} else {
|
|
1444
1613
|
if (this.config_.lowLatencyMode && !isReadableStreamSupported) {
|
|
@@ -1534,18 +1703,18 @@ shaka.media.StreamingEngine = class {
|
|
|
1534
1703
|
|
|
1535
1704
|
/**
|
|
1536
1705
|
* @param {!BufferSource} rawResult
|
|
1537
|
-
* @param {shaka.extern.
|
|
1706
|
+
* @param {shaka.extern.aesKey} aesKey
|
|
1538
1707
|
* @param {number} position
|
|
1539
1708
|
* @return {!Promise.<!BufferSource>} finalResult
|
|
1540
1709
|
* @private
|
|
1541
1710
|
*/
|
|
1542
|
-
async
|
|
1543
|
-
const key =
|
|
1711
|
+
async aesDecrypt_(rawResult, aesKey, position) {
|
|
1712
|
+
const key = aesKey;
|
|
1544
1713
|
if (!key.cryptoKey) {
|
|
1545
|
-
goog.asserts.assert(key.fetchKey, 'If AES
|
|
1714
|
+
goog.asserts.assert(key.fetchKey, 'If AES cryptoKey was not ' +
|
|
1546
1715
|
'preloaded, fetchKey function should be provided');
|
|
1547
1716
|
await key.fetchKey();
|
|
1548
|
-
goog.asserts.assert(key.cryptoKey, 'AES
|
|
1717
|
+
goog.asserts.assert(key.cryptoKey, 'AES cryptoKey should now be set');
|
|
1549
1718
|
}
|
|
1550
1719
|
let iv = key.iv;
|
|
1551
1720
|
if (!iv) {
|
|
@@ -1556,8 +1725,22 @@ shaka.media.StreamingEngine = class {
|
|
|
1556
1725
|
sequence >>= 8;
|
|
1557
1726
|
}
|
|
1558
1727
|
}
|
|
1559
|
-
|
|
1560
|
-
|
|
1728
|
+
let algorithm;
|
|
1729
|
+
if (aesKey.blockCipherMode == 'CBC') {
|
|
1730
|
+
algorithm = {
|
|
1731
|
+
name: 'AES-CBC',
|
|
1732
|
+
iv,
|
|
1733
|
+
};
|
|
1734
|
+
} else {
|
|
1735
|
+
algorithm = {
|
|
1736
|
+
name: 'AES-CTR',
|
|
1737
|
+
counter: iv,
|
|
1738
|
+
// NIST SP800-38A standard suggests that the counter should occupy half
|
|
1739
|
+
// of the counter block
|
|
1740
|
+
length: 64,
|
|
1741
|
+
};
|
|
1742
|
+
}
|
|
1743
|
+
return window.crypto.subtle.decrypt(algorithm, key.cryptoKey, rawResult);
|
|
1561
1744
|
}
|
|
1562
1745
|
|
|
1563
1746
|
|
|
@@ -1730,7 +1913,7 @@ shaka.media.StreamingEngine = class {
|
|
|
1730
1913
|
const isResetMediaSourceNecessary =
|
|
1731
1914
|
mediaState.lastCodecs && mediaState.lastMimeType &&
|
|
1732
1915
|
this.playerInterface_.mediaSourceEngine.isResetMediaSourceNecessary(
|
|
1733
|
-
mediaState.type, mediaState.stream);
|
|
1916
|
+
mediaState.type, mediaState.stream, mimeType, codecs);
|
|
1734
1917
|
if (isResetMediaSourceNecessary) {
|
|
1735
1918
|
let otherState = null;
|
|
1736
1919
|
if (mediaState.type === ContentType.VIDEO) {
|
|
@@ -1739,9 +1922,16 @@ shaka.media.StreamingEngine = class {
|
|
|
1739
1922
|
otherState = this.mediaStates_.get(ContentType.VIDEO);
|
|
1740
1923
|
}
|
|
1741
1924
|
if (otherState) {
|
|
1925
|
+
// First, abort all operations in progress on the other stream.
|
|
1926
|
+
await this.abortOperations_(otherState).catch(() => {});
|
|
1927
|
+
// Then clear our cache of the last init segment, since MSE will be
|
|
1928
|
+
// reloaded and no init segment will be there post-reload.
|
|
1742
1929
|
otherState.lastInitSegmentReference = null;
|
|
1930
|
+
// Now force the existing buffer to be cleared. It is not necessary
|
|
1931
|
+
// to perform the MSE clear operation, but this has the side-effect
|
|
1932
|
+
// that our state for that stream will then match MSE's post-reload
|
|
1933
|
+
// state.
|
|
1743
1934
|
this.forceClearBuffer_(otherState);
|
|
1744
|
-
this.abortOperations_(otherState).catch(() => {});
|
|
1745
1935
|
}
|
|
1746
1936
|
}
|
|
1747
1937
|
|
|
@@ -1772,19 +1962,6 @@ shaka.media.StreamingEngine = class {
|
|
|
1772
1962
|
appendWindowEnd, ignoreTimestampOffset,
|
|
1773
1963
|
reference.mimeType || mediaState.stream.mimeType,
|
|
1774
1964
|
reference.codecs || mediaState.stream.codecs, streamsByType);
|
|
1775
|
-
if (isResetMediaSourceNecessary) {
|
|
1776
|
-
let otherState = null;
|
|
1777
|
-
if (mediaState.type === ContentType.VIDEO) {
|
|
1778
|
-
otherState = this.mediaStates_.get(ContentType.AUDIO);
|
|
1779
|
-
} else if (mediaState.type === ContentType.AUDIO) {
|
|
1780
|
-
otherState = this.mediaStates_.get(ContentType.VIDEO);
|
|
1781
|
-
}
|
|
1782
|
-
if (otherState) {
|
|
1783
|
-
otherState.waitingToClearBuffer = false;
|
|
1784
|
-
otherState.performingUpdate = false;
|
|
1785
|
-
this.forceClearBuffer_(otherState);
|
|
1786
|
-
}
|
|
1787
|
-
}
|
|
1788
1965
|
} catch (error) {
|
|
1789
1966
|
mediaState.lastAppendWindowStart = null;
|
|
1790
1967
|
mediaState.lastAppendWindowEnd = null;
|
|
@@ -1817,8 +1994,15 @@ shaka.media.StreamingEngine = class {
|
|
|
1817
1994
|
let lastTimescale = null;
|
|
1818
1995
|
const timescaleMap = new Map();
|
|
1819
1996
|
|
|
1997
|
+
/** @type {!shaka.extern.SpatialVideoInfo} */
|
|
1998
|
+
const spatialVideoInfo = {
|
|
1999
|
+
projection: null,
|
|
2000
|
+
hfov: null,
|
|
2001
|
+
};
|
|
2002
|
+
|
|
1820
2003
|
const parser = new shaka.util.Mp4Parser();
|
|
1821
2004
|
const Mp4Parser = shaka.util.Mp4Parser;
|
|
2005
|
+
const Mp4BoxParsers = shaka.util.Mp4BoxParsers;
|
|
1822
2006
|
parser.box('moov', Mp4Parser.children)
|
|
1823
2007
|
.box('trak', Mp4Parser.children)
|
|
1824
2008
|
.box('mdia', Mp4Parser.children)
|
|
@@ -1826,13 +2010,12 @@ shaka.media.StreamingEngine = class {
|
|
|
1826
2010
|
goog.asserts.assert(
|
|
1827
2011
|
box.version != null,
|
|
1828
2012
|
'MDHD is a full box and should have a valid version.');
|
|
1829
|
-
const parsedMDHDBox =
|
|
2013
|
+
const parsedMDHDBox = Mp4BoxParsers.parseMDHD(
|
|
1830
2014
|
box.reader, box.version);
|
|
1831
2015
|
lastTimescale = parsedMDHDBox.timescale;
|
|
1832
2016
|
})
|
|
1833
2017
|
.box('hdlr', (box) => {
|
|
1834
|
-
const parsedHDLR =
|
|
1835
|
-
box.reader);
|
|
2018
|
+
const parsedHDLR = Mp4BoxParsers.parseHDLR(box.reader);
|
|
1836
2019
|
switch (parsedHDLR.handlerType) {
|
|
1837
2020
|
case 'soun':
|
|
1838
2021
|
timescaleMap.set(ContentType.AUDIO, lastTimescale);
|
|
@@ -1843,8 +2026,32 @@ shaka.media.StreamingEngine = class {
|
|
|
1843
2026
|
}
|
|
1844
2027
|
lastTimescale = null;
|
|
1845
2028
|
})
|
|
2029
|
+
.box('minf', Mp4Parser.children)
|
|
2030
|
+
.box('stbl', Mp4Parser.children)
|
|
2031
|
+
.fullBox('stsd', Mp4Parser.sampleDescription)
|
|
2032
|
+
.box('encv', Mp4Parser.visualSampleEntry)
|
|
2033
|
+
.box('avc1', Mp4Parser.visualSampleEntry)
|
|
2034
|
+
.box('avc3', Mp4Parser.visualSampleEntry)
|
|
2035
|
+
.box('hev1', Mp4Parser.visualSampleEntry)
|
|
2036
|
+
.box('hvc1', Mp4Parser.visualSampleEntry)
|
|
2037
|
+
.box('dvav', Mp4Parser.visualSampleEntry)
|
|
2038
|
+
.box('dva1', Mp4Parser.visualSampleEntry)
|
|
2039
|
+
.box('dvh1', Mp4Parser.visualSampleEntry)
|
|
2040
|
+
.box('dvhe', Mp4Parser.visualSampleEntry)
|
|
2041
|
+
.box('vexu', Mp4Parser.children)
|
|
2042
|
+
.box('proj', Mp4Parser.children)
|
|
2043
|
+
.fullBox('prji', (box) => {
|
|
2044
|
+
const parsedPRJIBox = Mp4BoxParsers.parsePRJI(box.reader);
|
|
2045
|
+
spatialVideoInfo.projection = parsedPRJIBox.projection;
|
|
2046
|
+
})
|
|
2047
|
+
.box('hfov', (box) => {
|
|
2048
|
+
const parsedHFOVBox = Mp4BoxParsers.parseHFOV(box.reader);
|
|
2049
|
+
spatialVideoInfo.hfov = parsedHFOVBox.hfov;
|
|
2050
|
+
})
|
|
1846
2051
|
.parse(initSegment);
|
|
1847
2052
|
|
|
2053
|
+
this.updateSpatialVideoInfo_(spatialVideoInfo);
|
|
2054
|
+
|
|
1848
2055
|
if (timescaleMap.has(mediaState.type)) {
|
|
1849
2056
|
reference.initSegmentReference.timescale =
|
|
1850
2057
|
timescaleMap.get(mediaState.type);
|
|
@@ -1879,9 +2086,7 @@ shaka.media.StreamingEngine = class {
|
|
|
1879
2086
|
// Across discontinuity bounds, we should resync timestamps for
|
|
1880
2087
|
// sequence mode playbacks. The next segment appended should
|
|
1881
2088
|
// land at its theoretical timestamp from the segment index.
|
|
1882
|
-
if (reference.discontinuitySequence != lastDiscontinuitySequence
|
|
1883
|
-
mediaState.needsResync) {
|
|
1884
|
-
mediaState.needsResync = false;
|
|
2089
|
+
if (reference.discontinuitySequence != lastDiscontinuitySequence) {
|
|
1885
2090
|
operations.push(this.playerInterface_.mediaSourceEngine.resync(
|
|
1886
2091
|
mediaState.type, reference.startTime));
|
|
1887
2092
|
}
|
|
@@ -2164,12 +2369,13 @@ shaka.media.StreamingEngine = class {
|
|
|
2164
2369
|
|
|
2165
2370
|
const overflow = bufferedBehind - bufferBehind;
|
|
2166
2371
|
// See: https://github.com/shaka-project/shaka-player/issues/6240
|
|
2167
|
-
if (overflow <=
|
|
2372
|
+
if (overflow <= this.config_.evictionGoal) {
|
|
2168
2373
|
shaka.log.v2(logPrefix,
|
|
2169
2374
|
'buffer behind okay:',
|
|
2170
2375
|
'presentationTime=' + presentationTime,
|
|
2171
2376
|
'bufferedBehind=' + bufferedBehind,
|
|
2172
2377
|
'bufferBehind=' + bufferBehind,
|
|
2378
|
+
'evictionGoal=' + this.config_.evictionGoal,
|
|
2173
2379
|
'underflow=' + Math.abs(overflow));
|
|
2174
2380
|
return;
|
|
2175
2381
|
}
|
|
@@ -2179,6 +2385,7 @@ shaka.media.StreamingEngine = class {
|
|
|
2179
2385
|
'presentationTime=' + presentationTime,
|
|
2180
2386
|
'bufferedBehind=' + bufferedBehind,
|
|
2181
2387
|
'bufferBehind=' + bufferBehind,
|
|
2388
|
+
'evictionGoal=' + this.config_.evictionGoal,
|
|
2182
2389
|
'overflow=' + overflow);
|
|
2183
2390
|
|
|
2184
2391
|
await this.playerInterface_.mediaSourceEngine.remove(mediaState.type,
|
|
@@ -2217,11 +2424,9 @@ shaka.media.StreamingEngine = class {
|
|
|
2217
2424
|
* @private
|
|
2218
2425
|
*/
|
|
2219
2426
|
async fetch_(mediaState, reference, streamDataCallback) {
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
return segmentData;
|
|
2224
|
-
}
|
|
2427
|
+
const segmentData = reference.getSegmentData();
|
|
2428
|
+
if (segmentData) {
|
|
2429
|
+
return segmentData;
|
|
2225
2430
|
}
|
|
2226
2431
|
let op = null;
|
|
2227
2432
|
if (mediaState.segmentPrefetch) {
|
|
@@ -2242,8 +2447,8 @@ shaka.media.StreamingEngine = class {
|
|
|
2242
2447
|
const response = await op.promise;
|
|
2243
2448
|
mediaState.operation = null;
|
|
2244
2449
|
let result = response.data;
|
|
2245
|
-
if (reference.
|
|
2246
|
-
result = await this.
|
|
2450
|
+
if (reference.aesKey) {
|
|
2451
|
+
result = await this.aesDecrypt_(result, reference.aesKey, position);
|
|
2247
2452
|
}
|
|
2248
2453
|
return result;
|
|
2249
2454
|
}
|
|
@@ -2260,6 +2465,27 @@ shaka.media.StreamingEngine = class {
|
|
|
2260
2465
|
* @private
|
|
2261
2466
|
*/
|
|
2262
2467
|
dispatchFetch_(reference, stream, streamDataCallback) {
|
|
2468
|
+
goog.asserts.assert(
|
|
2469
|
+
this.playerInterface_.netEngine, 'Must have net engine');
|
|
2470
|
+
return shaka.media.StreamingEngine.dispatchFetch(
|
|
2471
|
+
reference, stream, streamDataCallback || null,
|
|
2472
|
+
this.config_.retryParameters, this.playerInterface_.netEngine);
|
|
2473
|
+
}
|
|
2474
|
+
|
|
2475
|
+
/**
|
|
2476
|
+
* Fetches the given segment.
|
|
2477
|
+
*
|
|
2478
|
+
* @param {!shaka.extern.Stream} stream
|
|
2479
|
+
* @param {(!shaka.media.InitSegmentReference|!shaka.media.SegmentReference)}
|
|
2480
|
+
* reference
|
|
2481
|
+
* @param {?function(BufferSource):!Promise} streamDataCallback
|
|
2482
|
+
* @param {shaka.extern.RetryParameters} retryParameters
|
|
2483
|
+
* @param {!shaka.net.NetworkingEngine} netEngine
|
|
2484
|
+
*
|
|
2485
|
+
* @return {!shaka.net.NetworkingEngine.PendingRequest}
|
|
2486
|
+
*/
|
|
2487
|
+
static dispatchFetch(
|
|
2488
|
+
reference, stream, streamDataCallback, retryParameters, netEngine) {
|
|
2263
2489
|
const requestType = shaka.net.NetworkingEngine.RequestType.SEGMENT;
|
|
2264
2490
|
const segment = reference instanceof shaka.media.SegmentReference ?
|
|
2265
2491
|
reference : undefined;
|
|
@@ -2270,17 +2496,13 @@ shaka.media.StreamingEngine = class {
|
|
|
2270
2496
|
reference.getUris(),
|
|
2271
2497
|
reference.startByte,
|
|
2272
2498
|
reference.endByte,
|
|
2273
|
-
|
|
2499
|
+
retryParameters,
|
|
2274
2500
|
streamDataCallback);
|
|
2501
|
+
request.contentType = stream.type;
|
|
2275
2502
|
|
|
2276
2503
|
shaka.log.v2('fetching: reference=', reference);
|
|
2277
2504
|
|
|
2278
|
-
return
|
|
2279
|
-
requestType, request, {
|
|
2280
|
-
type: type,
|
|
2281
|
-
stream: stream,
|
|
2282
|
-
segment: segment,
|
|
2283
|
-
});
|
|
2505
|
+
return netEngine.request(requestType, request, {type, stream, segment});
|
|
2284
2506
|
}
|
|
2285
2507
|
|
|
2286
2508
|
/**
|
|
@@ -2310,7 +2532,8 @@ shaka.media.StreamingEngine = class {
|
|
|
2310
2532
|
mediaState.segmentIterator = null;
|
|
2311
2533
|
|
|
2312
2534
|
shaka.log.debug(logPrefix, 'clearing buffer');
|
|
2313
|
-
if (mediaState.segmentPrefetch
|
|
2535
|
+
if (mediaState.segmentPrefetch &&
|
|
2536
|
+
!this.audioPrefetchMap_.has(mediaState.stream)) {
|
|
2314
2537
|
mediaState.segmentPrefetch.clearAll();
|
|
2315
2538
|
}
|
|
2316
2539
|
|
|
@@ -2504,6 +2727,31 @@ shaka.media.StreamingEngine = class {
|
|
|
2504
2727
|
return true;
|
|
2505
2728
|
}
|
|
2506
2729
|
|
|
2730
|
+
/**
|
|
2731
|
+
* Update the spatial video info and notify to the app.
|
|
2732
|
+
*
|
|
2733
|
+
* @param {shaka.extern.SpatialVideoInfo} info
|
|
2734
|
+
* @private
|
|
2735
|
+
*/
|
|
2736
|
+
updateSpatialVideoInfo_(info) {
|
|
2737
|
+
if (this.spatialVideoInfo_.projection != info.projection ||
|
|
2738
|
+
this.spatialVideoInfo_.hfov != info.hfov) {
|
|
2739
|
+
const EventName = shaka.util.FakeEvent.EventName;
|
|
2740
|
+
let event;
|
|
2741
|
+
if (info.projection != null || info.hfov != null) {
|
|
2742
|
+
const eventName = EventName.SpatialVideoInfoEvent;
|
|
2743
|
+
const data = (new Map()).set('detail', info);
|
|
2744
|
+
event = new shaka.util.FakeEvent(eventName, data);
|
|
2745
|
+
} else {
|
|
2746
|
+
const eventName = EventName.NoSpatialVideoInfoEvent;
|
|
2747
|
+
event = new shaka.util.FakeEvent(eventName);
|
|
2748
|
+
}
|
|
2749
|
+
event.cancelable = true;
|
|
2750
|
+
this.playerInterface_.onEvent(event);
|
|
2751
|
+
this.spatialVideoInfo_ = info;
|
|
2752
|
+
}
|
|
2753
|
+
}
|
|
2754
|
+
|
|
2507
2755
|
/**
|
|
2508
2756
|
* @param {shaka.media.StreamingEngine.MediaState_} mediaState
|
|
2509
2757
|
* @return {string} A log prefix of the form ($CONTENT_TYPE:$STREAM_ID), e.g.,
|
|
@@ -2591,7 +2839,6 @@ shaka.media.StreamingEngine.PlayerInterface;
|
|
|
2591
2839
|
* clearBufferSafeMargin: number,
|
|
2592
2840
|
* clearingBuffer: boolean,
|
|
2593
2841
|
* seeked: boolean,
|
|
2594
|
-
* needsResync: boolean,
|
|
2595
2842
|
* adaptation: boolean,
|
|
2596
2843
|
* recovering: boolean,
|
|
2597
2844
|
* hasError: boolean,
|
|
@@ -2646,9 +2893,6 @@ shaka.media.StreamingEngine.PlayerInterface;
|
|
|
2646
2893
|
* True indicates that the presentation just seeked.
|
|
2647
2894
|
* @property {boolean} adaptation
|
|
2648
2895
|
* True indicates that the presentation just automatically switched variants.
|
|
2649
|
-
* @property {boolean} needsResync
|
|
2650
|
-
* True indicates that the stream needs to be resynced in sequence mode,
|
|
2651
|
-
* regardless of discontinuity sequence.
|
|
2652
2896
|
* @property {boolean} recovering
|
|
2653
2897
|
* True indicates that the last segment was not appended because it could not
|
|
2654
2898
|
* fit in the buffer.
|