Skip to content

Commit 2c35aae

Browse files
committed
Add optional offset param to IVideoSource.selectVideoTrack, which allows a partial clearing of the buffer to occur
1 parent 94937d7 commit 2c35aae

File tree

10 files changed

+145
-17
lines changed

10 files changed

+145
-17
lines changed

AUTHORS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,5 @@ Nick Desaulniers <nick@mozilla.com>
1919
Oskar Arvidsson <oskar@irock.se>
2020
Roi Lipman <roilipman@gmail.com>
2121
Sanborn Hilland <sanbornh@rogers.com>
22+
TalkTalk Plc <*@talktalkplc.com>
2223
uStudio Inc. <*@ustudio.com>

CONTRIBUTORS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424

2525
Jason Palmer <jason@jason-palmer.com>
2626
Joey Parrish <joeyparrish@google.com>
27+
Jono Ward <jonoward@gmail.com>
2728
Natalie Harris <natalieharris@google.com>
2829
Nick Desaulniers <nick@mozilla.com>
2930
Oskar Arvidsson <oskar@irock.se>

lib/media/i_stream.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,9 +74,13 @@ shaka.media.IStream.prototype.hasEnded = function() {};
7474
* when the stream starts for the first time.
7575
* @param {boolean} clearBuffer If true, removes the previous stream's content
7676
* before switching to the new stream.
77+
* @param {number=} opt_clearBufferOffset if |clearBuffer| and
78+
* |opt_clearBufferOffset|
79+
* are truthy, clear the stream buffer from the offset (in front of video
80+
* currentTime) to the end of the stream.
7781
*/
7882
shaka.media.IStream.prototype.switch = function(
79-
streamInfo, minBufferTime, clearBuffer) {};
83+
streamInfo, minBufferTime, clearBuffer, opt_clearBufferOffset) {};
8084

8185

8286
/**

lib/media/simple_abr_manager.js

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,28 @@ shaka.media.SimpleAbrManager.prototype.getInitialVideoTrackId = function() {
158158
};
159159

160160

161+
/**
162+
* Select the specified video track.
163+
*
164+
* @param {shaka.player.VideoTrack} track the track to the switch to
165+
* @param {boolean} clearBuffer If true, removes the previous stream's content
166+
* before switching to the new stream.
167+
* @param {number=} opt_clearBufferOffset if |clearBuffer| and
168+
* |opt_clearBufferOffset| are truthy, clear the stream buffer from the
169+
* offset (in front of video currentTime) to the end of the stream.
170+
*
171+
* @protected
172+
* @expose
173+
*/
174+
shaka.media.SimpleAbrManager.prototype.selectVideoTrack = function(
175+
track, clearBuffer, opt_clearBufferOffset) {
176+
shaka.asserts.assert(this.videoSource_);
177+
178+
this.videoSource_.selectVideoTrack(
179+
track.id, clearBuffer, opt_clearBufferOffset);
180+
};
181+
182+
161183
/**
162184
* Find the active track in the list.
163185
*
@@ -207,7 +229,7 @@ shaka.media.SimpleAbrManager.prototype.onBandwidth_ = function(event) {
207229
}
208230

209231
shaka.log.info('Video adaptation:', chosen);
210-
this.videoSource_.selectVideoTrack(chosen.id, false);
232+
this.selectVideoTrack(chosen, false);
211233
}
212234

213235
// Can't adapt again until we get confirmation of this one.

lib/media/source_buffer_manager.js

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -301,6 +301,39 @@ shaka.media.SourceBufferManager.prototype.clear = function() {
301301
};
302302

303303

304+
/**
305+
* Resets the virtual source buffer and clears all media from the underlying
306+
* SourceBuffer after the given timestamp. The returned promise will resolve
307+
* immediately if there is no media within the underlying SourceBuffer. This
308+
* cannot be called if another operation is in progress.
309+
*
310+
* @param {number} timestamp
311+
*
312+
* @return {!Promise}
313+
*/
314+
shaka.media.SourceBufferManager.prototype.clearAfter = function(timestamp) {
315+
shaka.log.v1('clearAfter');
316+
317+
// Check state.
318+
shaka.asserts.assert(!this.task_);
319+
if (this.task_) {
320+
var error =
321+
new Error('Cannot clearAfter: previous operation not complete.');
322+
error.type = 'stream';
323+
return Promise.reject(error);
324+
}
325+
326+
this.task_ = new shaka.util.Task();
327+
328+
this.task_.append(function() {
329+
var p = this.clearAfter_(timestamp);
330+
return [p, this.abort_.bind(this)];
331+
}.bind(this));
332+
333+
return this.startTask_();
334+
};
335+
336+
304337
/**
305338
* Aborts the current operation if one exists.
306339
* The returned promise will never be rejected.
@@ -456,6 +489,49 @@ shaka.media.SourceBufferManager.prototype.clear_ = function() {
456489
};
457490

458491

492+
/**
493+
* Clear the source buffer after the given timestamp (aligned to the next
494+
* segment boundary).
495+
*
496+
* @param {number} timestamp
497+
*
498+
* @return {!Promise}
499+
* @private
500+
*/
501+
shaka.media.SourceBufferManager.prototype.clearAfter_ = function(timestamp) {
502+
shaka.asserts.assert(!this.operationPromise_);
503+
504+
if (this.sourceBuffer_.buffered.length == 0) {
505+
shaka.log.v1('Nothing to clear.');
506+
shaka.asserts.assert(this.inserted_.length == 0);
507+
return Promise.resolve();
508+
}
509+
510+
var index = shaka.media.SegmentReference.find(this.inserted_, timestamp);
511+
512+
// If no segment found, or it's the last one, bail out gracefully.
513+
if (index == -1 || index == this.inserted_.length - 1) {
514+
shaka.log.v1('No segments on or after timestamp, nothing to clear.');
515+
return Promise.resolve();
516+
}
517+
518+
try {
519+
// This will trigger an 'updateend' event.
520+
this.sourceBuffer_.remove(
521+
this.inserted_[index + 1].startTime,
522+
Number.POSITIVE_INFINITY);
523+
} catch (exception) {
524+
shaka.log.debug('Failed to clear buffer:', exception);
525+
return Promise.reject(exception);
526+
}
527+
528+
this.inserted_ = this.inserted_.slice(0, index + 1);
529+
530+
this.operationPromise_ = new shaka.util.PublicPromise();
531+
return this.operationPromise_;
532+
};
533+
534+
459535
/**
460536
* Abort the current operation on the source buffer.
461537
*

lib/media/stream.js

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -242,7 +242,7 @@ shaka.media.Stream.prototype.hasEnded = function() {
242242

243243
/** @override */
244244
shaka.media.Stream.prototype.switch = function(
245-
streamInfo, minBufferTime, clearBuffer) {
245+
streamInfo, minBufferTime, clearBuffer, opt_clearBufferOffset) {
246246
if (streamInfo == this.streamInfo_) {
247247
shaka.log.debug('Already using stream', streamInfo);
248248
return;
@@ -275,7 +275,7 @@ shaka.media.Stream.prototype.switch = function(
275275
// it was called at an appopriate time.
276276
this.setUpdateTimer_(0);
277277
} else if (clearBuffer) {
278-
this.resync_(true /* forceClear */);
278+
this.resync_(true /* forceClear */, opt_clearBufferOffset);
279279
} else {
280280
shaka.asserts.assert((this.updateTimer_ != null) || this.fetching_);
281281
}
@@ -303,14 +303,20 @@ shaka.media.Stream.prototype.resync = function() {
303303
* Resync the stream.
304304
*
305305
* @param {boolean} forceClear
306+
* @param {number=} opt_clearBufferOffset if |forceClear| and
307+
* |opt_clearBufferOffset| are truthy, clear the stream buffer from the
308+
* offset (in front of video currentTime) to the end of the stream.
309+
*
306310
* @private
307311
*/
308-
shaka.media.Stream.prototype.resync_ = function(forceClear) {
312+
shaka.media.Stream.prototype.resync_ = function(
313+
forceClear, opt_clearBufferOffset) {
309314
if (!this.streamInfo_ || this.resyncing_) {
310315
return;
311316
}
312317

313318
shaka.asserts.assert((this.updateTimer_ != null) || this.fetching_);
319+
shaka.asserts.assert(!opt_clearBufferOffset || opt_clearBufferOffset > 0);
314320

315321
this.resyncing_ = true;
316322
this.cancelUpdateTimer_();
@@ -331,8 +337,15 @@ shaka.media.Stream.prototype.resync_ = function(forceClear) {
331337
!this.sbm_.isBuffered(currentTime) ||
332338
!this.sbm_.isInserted(currentTime)) {
333339
shaka.log.debug('Nudge needed!');
334-
this.needsNudge_ = true;
335-
return this.sbm_.clear();
340+
341+
if (opt_clearBufferOffset) {
342+
return this.sbm_.clearAfter(
343+
this.video_.currentTime + opt_clearBufferOffset);
344+
} else {
345+
this.needsNudge_ = true;
346+
return this.sbm_.clear();
347+
}
348+
336349
} else {
337350
return Promise.resolve();
338351
}

lib/media/text_stream.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ shaka.media.TextStream.prototype.hasEnded = function() {
107107

108108
/** @override */
109109
shaka.media.TextStream.prototype.switch = function(
110-
streamInfo, minBufferTime, clearBuffer) {
110+
streamInfo, minBufferTime, clearBuffer, opt_clearBufferOffset) {
111111
shaka.log.info('Switching stream to', streamInfo);
112112

113113
streamInfo.segmentIndexSource.create().then(shaka.util.TypedBind(this,

lib/player/http_video_source.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,7 @@ shaka.player.HttpVideoSource.prototype.selectConfigurations =
144144

145145
/** @override */
146146
shaka.player.HttpVideoSource.prototype.selectVideoTrack =
147-
function(id, clearBuffer) {
147+
function(id, clearBuffer, opt_clearBufferOffset) {
148148
return false;
149149
};
150150

lib/player/i_video_source.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,12 +147,15 @@ shaka.player.IVideoSource.prototype.selectConfigurations =
147147
* @param {number} id The |id| field of the desired VideoTrack object.
148148
* @param {boolean} clearBuffer If true, removes the previous stream's content
149149
* before switching to the new stream.
150+
* @param {number=} opt_clearBufferOffset if |clearBuffer| and
151+
* |opt_clearBufferOffset| are truthy, clear the stream buffer from the
152+
* offset (in front of video currentTime) to the end of the stream.
150153
*
151154
* @return {boolean} True on success; otherwise, return false if the specified
152155
* VideoTrack does not exist or if a video stream does not exist.
153156
*/
154157
shaka.player.IVideoSource.prototype.selectVideoTrack =
155-
function(id, clearBuffer) {};
158+
function(id, clearBuffer, opt_clearBufferOffset) {};
156159

157160

158161
/**

lib/player/stream_video_source.js

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,8 @@ shaka.player.StreamVideoSource = function(manifestInfo, estimator, abrManager) {
149149

150150
/**
151151
* @private {!Object.<string, {streamInfo: !shaka.media.StreamInfo,
152-
* clearBuffer: boolean}>}
152+
* clearBuffer: boolean,
153+
* clearBufferOffset: (number|undefined)}>}
153154
*/
154155
this.deferredSwitches_ = {};
155156

@@ -689,8 +690,8 @@ shaka.player.StreamVideoSource.prototype.selectConfigurations =
689690

690691
/** @override */
691692
shaka.player.StreamVideoSource.prototype.selectVideoTrack =
692-
function(id, clearBuffer) {
693-
return this.selectTrack_('video', id, clearBuffer);
693+
function(id, clearBuffer, opt_clearBufferOffset) {
694+
return this.selectTrack_('video', id, clearBuffer, opt_clearBufferOffset);
694695
};
695696

696697

@@ -802,12 +803,15 @@ shaka.player.StreamVideoSource.prototype.isLive = function() {
802803
* or 'text'.
803804
* @param {number} id The |uniqueId| field of the desired StreamInfo.
804805
* @param {boolean} clearBuffer
806+
* @param {number=} opt_clearBufferOffset if |clearBuffer| and
807+
* |opt_clearBufferOffset| are truthy, clear the stream buffer from the
808+
* offset (in front of video currentTime) to the end of the stream.
805809
*
806810
* @return {boolean} True on success.
807811
* @private
808812
*/
809813
shaka.player.StreamVideoSource.prototype.selectTrack_ =
810-
function(type, id, clearBuffer) {
814+
function(type, id, clearBuffer, opt_clearBufferOffset) {
811815
if (!this.streamSetsByType.has(type)) {
812816
shaka.log.warning(
813817
'Cannot select', type, 'track', id,
@@ -847,7 +851,9 @@ shaka.player.StreamVideoSource.prototype.selectTrack_ =
847851

848852
this.deferredSwitches_[type] = {
849853
streamInfo: streamInfo,
850-
clearBuffer: (tuple != null && tuple.clearBuffer) || clearBuffer
854+
clearBuffer: (tuple != null && tuple.clearBuffer) || clearBuffer,
855+
clearBufferOffset:
856+
(tuple != null && tuple.clearBufferOffset) || opt_clearBufferOffset
851857
};
852858
return true;
853859
}
@@ -858,7 +864,8 @@ shaka.player.StreamVideoSource.prototype.selectTrack_ =
858864
this.streamsByType_[type].switch(
859865
streamInfo,
860866
this.manifestInfo.minBufferTime,
861-
clearBuffer);
867+
clearBuffer,
868+
opt_clearBufferOffset);
862869
return true;
863870
}
864871
}
@@ -1363,7 +1370,8 @@ shaka.player.StreamVideoSource.prototype.processDeferredSwitches_ = function() {
13631370
stream.switch(
13641371
tuple.streamInfo,
13651372
this.manifestInfo.minBufferTime,
1366-
tuple.clearBuffer);
1373+
tuple.clearBuffer,
1374+
tuple.clearBufferOffset);
13671375
}
13681376

13691377
this.deferredSwitches_ = {};

0 commit comments

Comments
 (0)