Skip to content

Commit 65981e2

Browse files
authored
feat: add a live sync panic mode (#6149)
This PR introduces a live sync panic mode (`streaming.liveSyncPanicMode`) which sets the player into the `streaming.liveSyncMinPlaybackRate` while we're within the `streaming.liveSyncPanicThreshold`. This should help reduce the change of subsequent rebuffering events by moving further away from the live edge. Related to #6131.
1 parent 701ec9b commit 65981e2

File tree

6 files changed

+66
-5
lines changed

6 files changed

+66
-5
lines changed

demo/config.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -462,6 +462,9 @@ shakaDemo.Config = class {
462462
'streaming.liveSyncMinPlaybackRate',
463463
/* canBeDecimal= */ true,
464464
/* canBeZero= */ false)
465+
.addBoolInput_('Live Sync Panic Mode', 'streaming.liveSyncPanicMode')
466+
.addNumberInput_('Live Sync Panic Mode Threshold',
467+
'streaming.liveSyncPanicThreshold')
465468
.addBoolInput_('Allow Media Source recoveries',
466469
'streaming.allowMediaSourceRecoveries')
467470
.addNumberInput_('Minimum time between recoveries',

externs/shaka/player.js

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1164,6 +1164,8 @@ shaka.extern.ManifestConfiguration;
11641164
* liveSyncPlaybackRate: number,
11651165
* liveSyncMinLatency: number,
11661166
* liveSyncMinPlaybackRate: number,
1167+
* liveSyncPanicMode: boolean,
1168+
* liveSyncPanicThreshold: number,
11671169
* allowMediaSourceRecoveries: boolean,
11681170
* minTimeBetweenRecoveries: number
11691171
* }}
@@ -1265,7 +1267,7 @@ shaka.extern.ManifestConfiguration;
12651267
* If true, all emsg boxes are parsed and dispatched.
12661268
* @property {boolean} observeQualityChanges
12671269
* If true, monitor media quality changes and emit
1268-
* <code.shaka.Player.MediaQualityChangedEvent</code>.
1270+
* <code>shaka.Player.MediaQualityChangedEvent</code>.
12691271
* @property {number} maxDisabledTime
12701272
* The maximum time a variant can be disabled when NETWORK HTTP_ERROR
12711273
* is reached, in seconds.
@@ -1301,6 +1303,14 @@ shaka.extern.ManifestConfiguration;
13011303
* Minimum playback rate used for latency chasing. It is recommended to use a
13021304
* value between 0 and 1. Effective only if liveSync is true. Defaults to
13031305
* <code>1</code>.
1306+
* @property {boolean} liveSyncPanicMode
1307+
* If <code>true</code>, panic mode for live sync is enabled. When enabled,
1308+
* will set the playback rate to the <code>liveSyncMinPlaybackRate</code>
1309+
* until playback has continued past a rebuffering for longer than the
1310+
* <code>liveSyncPanicThreshold</code>. Defaults to <code>false</code>.
1311+
* @property {number} liveSyncPanicThreshold
1312+
* Number of seconds that playback stays in panic mode after a rebuffering.
1313+
* Defaults to <code>60</code>
13041314
* @property {boolean} allowMediaSourceRecoveries
13051315
* Indicate if we should recover from VIDEO_ERROR resetting Media Source.
13061316
* Defaults to <code>true</code>.

lib/media/buffering_observer.js

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@ shaka.media.BufferingObserver = class {
2828
this.thresholds_ = new Map()
2929
.set(State.SATISFIED, thresholdWhenSatisfied)
3030
.set(State.STARVING, thresholdWhenStarving);
31+
32+
/** @private {number} */
33+
this.lastRebufferTime_ = 0;
3134
}
3235

3336
/**
@@ -73,7 +76,13 @@ shaka.media.BufferingObserver = class {
7376
this.previousState_ = newState;
7477

7578
// Return |true| only when the state has changed.
76-
return oldState != newState;
79+
const stateChanged = oldState != newState;
80+
81+
if (stateChanged && newState === State.SATISFIED) {
82+
this.lastRebufferTime_ = Date.now();
83+
}
84+
85+
return stateChanged;
7786
}
7887

7988
/**
@@ -93,6 +102,21 @@ shaka.media.BufferingObserver = class {
93102
getState() {
94103
return this.previousState_;
95104
}
105+
106+
/**
107+
* Return the last time that the state went from |STARVING| to |SATISFIED|.
108+
* @return {number}
109+
*/
110+
getLastRebufferTime() {
111+
return this.lastRebufferTime_;
112+
}
113+
114+
/**
115+
* Reset the last rebuffer time to zero.
116+
*/
117+
resetLastRebufferTime() {
118+
this.lastRebufferTime_ = 0;
119+
}
96120
};
97121

98122
/**

lib/player.js

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2052,7 +2052,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
20522052
const isLive = this.isLive();
20532053

20542054
if (isLive && (this.config_.streaming.liveSync ||
2055-
this.manifest_.serviceDescription)) {
2055+
this.manifest_.serviceDescription ||
2056+
this.config_.streaming.liveSyncPanicMode)) {
20562057
const onTimeUpdate = () => this.onTimeUpdate_();
20572058
this.loadEventManager_.listen(mediaElement, 'timeupdate', onTimeUpdate);
20582059
} else if (!isLive) {
@@ -2383,7 +2384,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
23832384

23842385
const isLive = this.isLive();
23852386

2386-
if (isLive && this.config_.streaming.liveSync) {
2387+
if (isLive && (this.config_.streaming.liveSync ||
2388+
this.config_.streaming.liveSyncPanicMode)) {
23872389
const onTimeUpdate = () => this.onTimeUpdate_();
23882390
this.loadEventManager_.listen(mediaElement, 'timeupdate', onTimeUpdate);
23892391
} else if (!isLive) {
@@ -5707,7 +5709,24 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
57075709
}
57085710
}
57095711

5710-
if (liveSyncMaxLatency && liveSyncPlaybackRate &&
5712+
const panicMode = this.config_.streaming.liveSyncPanicMode;
5713+
const panicThreshold = this.config_.streaming.liveSyncPanicThreshold * 1000;
5714+
const timeSinceLastRebuffer =
5715+
Date.now() - this.bufferObserver_.getLastRebufferTime();
5716+
if (panicMode && !liveSyncMinPlaybackRate) {
5717+
liveSyncMinPlaybackRate = this.config_.streaming.liveSyncMinPlaybackRate;
5718+
}
5719+
5720+
if (panicMode && liveSyncMinPlaybackRate &&
5721+
timeSinceLastRebuffer <= panicThreshold) {
5722+
if (playbackRate != liveSyncMinPlaybackRate) {
5723+
shaka.log.debug('Time since last rebuffer (' +
5724+
timeSinceLastRebuffer + 's) ' +
5725+
'is less than the liveSyncPanicThreshold (' + panicThreshold +
5726+
's). Updating playbackRate to ' + liveSyncMinPlaybackRate);
5727+
this.trickPlay(liveSyncMinPlaybackRate);
5728+
}
5729+
} else if (liveSyncMaxLatency && liveSyncPlaybackRate &&
57115730
(latency - offset) > liveSyncMaxLatency) {
57125731
if (playbackRate != liveSyncPlaybackRate) {
57135732
shaka.log.debug('Latency (' + latency + 's) ' +

lib/util/player_configuration.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,8 @@ shaka.util.PlayerConfiguration = class {
229229
liveSyncPlaybackRate: 1.1,
230230
liveSyncMinLatency: 0,
231231
liveSyncMinPlaybackRate: 1,
232+
liveSyncPanicMode: false,
233+
liveSyncPanicThreshold: 60,
232234
allowMediaSourceRecoveries: true,
233235
minTimeBetweenRecoveries: 5,
234236
};

test/media/buffering_observer_unit.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ describe('BufferingObserver', () => {
101101
beforeEach(() => {
102102
controller.setState(State.STARVING);
103103
expect(controller.getState()).toBe(State.STARVING);
104+
expect(controller.getLastRebufferTime()).toBe(0);
104105
});
105106

106107
it('becomes satisfied when enough content is buffered', () => {
@@ -122,6 +123,7 @@ describe('BufferingObserver', () => {
122123
changed = controller.update(/* lead= */ 5, /* toEnd= */ false);
123124
expect(changed).toBeTruthy();
124125
expect(controller.getState()).toBe(State.SATISFIED);
126+
expect(controller.getLastRebufferTime()).toBe(Date.now());
125127
});
126128

127129
it('becomes satisfied when the end is buffered', () => {
@@ -138,6 +140,7 @@ describe('BufferingObserver', () => {
138140
changed = controller.update(/* lead= */ 3, /* toEnd= */ true);
139141
expect(changed).toBeTruthy();
140142
expect(controller.getState()).toBe(State.SATISFIED);
143+
expect(controller.getLastRebufferTime()).toBe(Date.now());
141144
});
142145
});
143146
});

0 commit comments

Comments
 (0)