Skip to content

Commit df5cb38

Browse files
committed
Add a setting blacklistExpiryTime to allow baseUrls to be returned to after a failure; refactor ContentSteering TTL to use blacklistController expiry
1 parent 00a296a commit df5cb38

5 files changed

Lines changed: 84 additions & 13 deletions

File tree

index.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1828,6 +1828,7 @@ export class MediaPlayerSettingClass {
18281828
prioritizeRoleMain?: boolean;
18291829
assumeDefaultRoleAsMain?: boolean;
18301830
selectionModeForInitialTrack?: TrackSelectionMode;
1831+
blacklistExpiryTime?: number;
18311832
fragmentRequestTimeout?: number;
18321833
fragmentRequestProgressTimeout?: number;
18331834
manifestRequestTimeout?: number;

src/core/Settings.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,7 @@ import SwitchRequest from '../streaming/rules/SwitchRequest.js';
205205
* prioritizeRoleMain: true,
206206
* assumeDefaultRoleAsMain: true,
207207
* selectionModeForInitialTrack: Constants.TRACK_SELECTION_MODE_LOWEST_STARTUP_DELAY,
208+
* blacklistExpiryTime: -1,
208209
* fragmentRequestTimeout: 20000,
209210
* fragmentRequestProgressTimeout: -1,
210211
* manifestRequestTimeout: 10000,
@@ -1106,6 +1107,9 @@ import SwitchRequest from '../streaming/rules/SwitchRequest.js';
11061107
* - Constants.TRACK_SELECTION_MODE_WIDEST_RANGE
11071108
* This mode makes the player select the track with a widest range of bitrates.
11081109
*
1110+
* @property {number} [blacklistExpiryTime=-1]
1111+
* Time in seconds that a Service Location should be added to the blacklist for. After this time has passed the Service Location will be available for selection when failing over a second time, but it will not actively return to it.
1112+
* This is ignored when using Content Steering, as the blacklist expiry will be set to the TTL value, and Service Locations may be actively switched back to according to the steering algorithm.
11091113
*
11101114
* @property {number} [fragmentRequestTimeout=20000]
11111115
* Time in milliseconds before timing out on loading a media fragment.
@@ -1340,6 +1344,7 @@ function Settings() {
13401344
prioritizeRoleMain: true,
13411345
assumeDefaultRoleAsMain: true,
13421346
selectionModeForInitialTrack: Constants.TRACK_SELECTION_MODE_LOWEST_STARTUP_DELAY,
1347+
blacklistExpiryTime: -1,
13431348
fragmentRequestTimeout: 20000,
13441349
fragmentRequestProgressTimeout: -1,
13451350
manifestRequestTimeout: 10000,

src/streaming/controllers/BlacklistController.js

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,14 +31,17 @@
3131

3232
import FactoryMaker from '../../core/FactoryMaker.js';
3333
import EventBus from '../../core/EventBus.js';
34+
import Settings from '../../core/Settings.js';
3435

3536
function BlackListController(config) {
3637

3738
config = config || {};
3839
let instance;
3940
let blacklist = [];
41+
let blacklistExpiry = null;
4042

4143
const eventBus = EventBus(this.context).getInstance();
44+
const settings = Settings(this.context).getInstance();
4245
const updateEventName = config.updateEventName;
4346
const addBlacklistEventName = config.addBlacklistEventName;
4447

@@ -47,21 +50,29 @@ function BlackListController(config) {
4750
return false;
4851
}
4952

50-
return (blacklist.indexOf(query) !== -1);
53+
return (blacklist.findIndex(item => item.entry === query) !== -1);
5154
}
5255

5356
function add(entry) {
54-
if (blacklist.indexOf(entry) !== -1) {
57+
if (blacklist.findIndex(item => item.entry === entry) !== -1) {
5558
return;
5659
}
5760

58-
blacklist.push(entry);
61+
const expiry = blacklistExpiry || settings.get().streaming.blacklistExpiryTime;
62+
if (expiry && expiry > 0) {
63+
const timeoutId = setTimeout(() => {
64+
remove(entry);
65+
}, expiry * 1000);
66+
blacklist.push({ entry: entry, timeoutId: timeoutId });
67+
} else {
68+
blacklist.push({ entry: entry });
69+
}
5970

6071
eventBus.trigger(updateEventName, { entry: entry });
6172
}
6273

6374
function remove(entry) {
64-
const index = blacklist.indexOf(entry);
75+
const index = blacklist.findIndex(item => item.entry === entry);
6576
if (index !== -1) {
6677
blacklist.splice(index, 1)
6778
}
@@ -71,6 +82,10 @@ function BlackListController(config) {
7182
add(e.entry);
7283
}
7384

85+
function setContentSteeringBlacklistExpiry(seconds) {
86+
blacklistExpiry = seconds;
87+
}
88+
7489
function setup() {
7590
if (addBlacklistEventName) {
7691
eventBus.on(addBlacklistEventName, onAddBlackList, instance);
@@ -81,14 +96,21 @@ function BlackListController(config) {
8196
if (addBlacklistEventName) {
8297
eventBus.off(addBlacklistEventName, onAddBlackList, instance);
8398
}
99+
for (const item of blacklist) {
100+
if (item.timeoutId) {
101+
clearTimeout(item.timeoutId);
102+
}
103+
}
84104
blacklist = [];
105+
blacklistExpiry = null;
85106
}
86107

87108
instance = {
88109
add: add,
89110
remove: remove,
90111
contains: contains,
91-
reset: reset
112+
reset: reset,
113+
setContentSteeringBlacklistExpiry: setContentSteeringBlacklistExpiry
92114
};
93115

94116
setup();

src/streaming/utils/baseUrlResolution/ContentSteeringSelector.js

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
import FactoryMaker from '../../../core/FactoryMaker.js';
3333
import ContentSteeringController from '../../../dash/controllers/ContentSteeringController.js';
3434
import EventBus from '../../../core/EventBus.js';
35+
import MediaPlayerEvents from '../../MediaPlayerEvents.js';
3536

3637
function ContentSteeringSelector() {
3738

@@ -53,7 +54,7 @@ function ContentSteeringSelector() {
5354
if (config.contentSteeringController) {
5455
contentSteeringController = config.contentSteeringController;
5556
}
56-
eventBus.on(config.addBlacklistEventName, _onAddBlackList, instance);
57+
eventBus.on(MediaPlayerEvents.CONTENT_STEERING_REQUEST_COMPLETED, _onContentSteeringRequestCompleted, instance);
5758
}
5859

5960
function selectBaseUrlIndex(data) {
@@ -100,17 +101,16 @@ function ContentSteeringSelector() {
100101
}
101102

102103

103-
function _onAddBlackList(e) {
104+
function _onContentSteeringRequestCompleted() {
104105
const currentSteeringResponseData = contentSteeringController.getCurrentSteeringResponseData();
105106
if (!currentSteeringResponseData) {
106107
return
107108
}
108-
const entry = e.entry
109-
const timer = setTimeout(() => {
110-
blacklistController.remove(entry);
111-
blacklistResetTimeout.splice(blacklistResetTimeout.indexOf(timer), 1)
112-
}, currentSteeringResponseData.ttl * 1000);
113-
blacklistResetTimeout.push(timer)
109+
if (currentSteeringResponseData.ttl) {
110+
blacklistController.setContentSteeringBlacklistExpiry(currentSteeringResponseData.ttl);
111+
} else {
112+
blacklistController.setContentSteeringBlacklistExpiry(-1);
113+
}
114114
}
115115

116116

test/unit/test/streaming/streaming.controllers.BlacklistController.js

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,38 @@
11
import BlacklistController from '../../../../src/streaming/controllers/BlacklistController.js';
22
import EventBus from '../../../../src/core/EventBus.js';
3+
import Settings from '../../../../src/core/Settings.js';
34
import chai from 'chai';
45
import spies from 'chai-spies';
6+
import sinon from 'sinon';
57

68
const expect = chai.expect;
79

810
chai.use(spies);
911

1012
describe('BlacklistController', function () {
1113
const context = {};
14+
const settings = Settings(context).getInstance();
1215
const eventBus = EventBus(context).getInstance();
1316

1417
const SERVICE_LOCATION = 'testServiceLocation';
1518
const EVENT_NAME = 'blacklistControllerTestEvent';
1619

1720
const defaultConfig = { updateEventName: '' };
1821

22+
let clock;
23+
24+
this.beforeEach(() => {
25+
clock = sinon.useFakeTimers();
26+
});
27+
28+
this.afterEach(() => {
29+
if (clock) {
30+
clock.restore();
31+
clock = null;
32+
}
33+
settings.reset();
34+
});
35+
1936
it('should return false when calling contains after initialisation', () => {
2037
const blacklistController = BlacklistController(context).create(defaultConfig);
2138

@@ -107,4 +124,30 @@ describe('BlacklistController', function () {
107124
expect(containsBeforeReset).to.be.true; // jshint ignore:line
108125
expect(containsAfterReset).to.be.false; // jshint ignore:line
109126
});
127+
128+
it('should remove an entry after a content steering TTL blacklist expiry time has passed', () => {
129+
const config = { updateEventName: '' };
130+
const blacklistController = BlacklistController(context).create(config);
131+
blacklistController.setContentSteeringBlacklistExpiry(60);
132+
133+
blacklistController.add(SERVICE_LOCATION);
134+
clock.tick(30 * 1000);
135+
expect(blacklistController.contains(SERVICE_LOCATION)).to.be.true;
136+
137+
clock.tick(30 * 1000);
138+
expect(blacklistController.contains(SERVICE_LOCATION)).to.be.false;
139+
});
140+
141+
it('should remove an entry after a blacklist expiry time from settings has passed', () => {
142+
const config = { updateEventName: '' };
143+
const blacklistController = BlacklistController(context).create(config);
144+
settings.update({streaming: { blacklistExpiryTime: 60 }});
145+
146+
blacklistController.add(SERVICE_LOCATION);
147+
clock.tick(30 * 1000);
148+
expect(blacklistController.contains(SERVICE_LOCATION)).to.be.true;
149+
150+
clock.tick(30 * 1000);
151+
expect(blacklistController.contains(SERVICE_LOCATION)).to.be.false;
152+
});
110153
});

0 commit comments

Comments
 (0)