Skip to content

Commit d39b56b

Browse files
authored
Feature/dash handler optimizations (#3727)
* WiP: Optimize the DashHandler.js related functionality * WiP: Optimizations to decide whether a period is completely buffered * WiP: Optimizations to decide whether a period is completely buffered * Fix unit tests * Refactor to numberOfSegments and account for -1 based index * Add comment to DashHandler.js * Minor changes to DashHandler.js * Refactor TimelineSegmentsGetter.js * WiP: Rewrite DashHandler.js * Add mediaFinishedInformation to decide whether all segments of a period have been requested * Add a safety margin of 0.05 to account for rounding issues when comparing media times
1 parent 339f953 commit d39b56b

22 files changed

+332
-208
lines changed

package-lock.json

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/core/Settings.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -232,7 +232,7 @@ import {HTTPRequest} from '../streaming/vo/metrics/HTTPRequest';
232232

233233
/**
234234
* @typedef {Object} Buffer
235-
* @property {boolean} [fastSwitchEnabled=false]
235+
* @property {boolean} [fastSwitchEnabled=true]
236236
* When enabled, after an ABR up-switch in quality, instead of requesting and appending the next fragment at the end of the current buffer range it is requested and appended closer to the current time.
237237
*
238238
* When enabled, The maximum time to render a higher quality is current time + (1.5 * fragment duration).

src/dash/DashHandler.js

Lines changed: 63 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ import {
3737
replaceTokenForTemplate,
3838
unescapeDollarsInTemplate
3939
} from './utils/SegmentsUtils';
40+
import DashConstants from './constants/DashConstants';
4041

4142

4243
function DashHandler(config) {
@@ -54,22 +55,20 @@ function DashHandler(config) {
5455

5556
let instance,
5657
logger,
57-
segmentIndex,
5858
lastSegment,
59-
requestedTime,
6059
isDynamicManifest,
61-
dynamicStreamCompleted;
60+
mediaHasFinished;
6261

6362
function setup() {
6463
logger = debug.getLogger(instance);
6564
resetInitialSettings();
6665

67-
eventBus.on(MediaPlayerEvents.DYNAMIC_TO_STATIC, onDynamicToStatic, instance);
66+
eventBus.on(MediaPlayerEvents.DYNAMIC_TO_STATIC, _onDynamicToStatic, instance);
6867
}
6968

7069
function initialize(isDynamic) {
7170
isDynamicManifest = isDynamic;
72-
dynamicStreamCompleted = false;
71+
mediaHasFinished = false;
7372
segmentsController.initialize(isDynamic);
7473
}
7574

@@ -85,30 +84,16 @@ function DashHandler(config) {
8584
return streamInfo;
8685
}
8786

88-
function setCurrentIndex(value) {
89-
segmentIndex = value;
90-
}
91-
92-
function getCurrentIndex() {
93-
return segmentIndex;
94-
}
95-
96-
function resetIndex() {
97-
segmentIndex = -1;
98-
lastSegment = null;
99-
}
100-
10187
function resetInitialSettings() {
102-
resetIndex();
103-
requestedTime = null;
88+
lastSegment = null;
10489
}
10590

10691
function reset() {
10792
resetInitialSettings();
108-
eventBus.off(MediaPlayerEvents.DYNAMIC_TO_STATIC, onDynamicToStatic, instance);
93+
eventBus.off(MediaPlayerEvents.DYNAMIC_TO_STATIC, _onDynamicToStatic, instance);
10994
}
11095

111-
function setRequestUrl(request, destination, representation) {
96+
function _setRequestUrl(request, destination, representation) {
11297
const baseURL = baseURLController.resolve(representation.path);
11398
let url,
11499
serviceLocation;
@@ -153,7 +138,7 @@ function DashHandler(config) {
153138
request.mediaInfo = mediaInfo;
154139
request.representationId = representation.id;
155140

156-
if (setRequestUrl(request, representation.initialization, representation)) {
141+
if (_setRequestUrl(request, representation.initialization, representation)) {
157142
request.url = replaceTokenForTemplate(request.url, 'Bandwidth', representation.bandwidth);
158143
return request;
159144
}
@@ -186,59 +171,72 @@ function DashHandler(config) {
186171
request.availabilityEndTime = segment.availabilityEndTime;
187172
request.wallStartTime = segment.wallStartTime;
188173
request.quality = representation.index;
189-
request.index = segment.availabilityIdx;
174+
request.index = segment.index;
190175
request.mediaInfo = mediaInfo;
191176
request.adaptationIndex = representation.adaptation.index;
192177
request.representationId = representation.id;
193178

194-
if (setRequestUrl(request, url, representation)) {
179+
if (_setRequestUrl(request, url, representation)) {
195180
return request;
196181
}
197182
}
198183

199-
function isMediaFinished(representation, bufferingTime) {
200-
let isFinished = false;
184+
function lastSegmentRequested(representation, bufferingTime) {
185+
if (!representation || !lastSegment) {
186+
return false;
187+
}
201188

202-
if (!representation || !lastSegment) return isFinished;
189+
// Either transition from dynamic to static was done or no next static segment found
190+
if (mediaHasFinished) {
191+
return true;
192+
}
203193

204-
// if the buffer is filled up we are done
194+
// Period is endless
195+
if (!isFinite(representation.adaptation.period.duration)) {
196+
return false;
197+
}
205198

206-
// we are replacing existing stuff.
199+
// we are replacing existing stuff in the buffer for instance after a track switch
207200
if (lastSegment.presentationStartTime + lastSegment.duration > bufferingTime) {
208201
return false;
209202
}
210203

211-
212-
if (isDynamicManifest && dynamicStreamCompleted) {
213-
isFinished = true;
214-
} else if (lastSegment) {
215-
const time = parseFloat((lastSegment.presentationStartTime - representation.adaptation.period.start).toFixed(5));
216-
const endTime = lastSegment.duration > 0 ? time + lastSegment.duration : time;
217-
const duration = representation.adaptation.period.duration;
218-
219-
return isFinite(duration) && endTime >= duration - 0.05;
204+
// Additional segment references may be added to the last period.
205+
// Additional periods may be added to the end of the MPD.
206+
// Segment references SHALL NOT be added to any period other than the last period.
207+
// An MPD update MAY combine adding segment references to the last period with adding of new periods. An MPD update that adds content MAY be combined with an MPD update that removes content.
208+
// The index of the last requested segment is higher than the number of available segments.
209+
// For SegmentTimeline and SegmentTemplate the index does not include the startNumber.
210+
// For SegmentList the index includes the startnumber which is why the numberOfSegments includes this as well
211+
if (representation.mediaFinishedInformation && !isNaN(representation.mediaFinishedInformation.numberOfSegments) && !isNaN(lastSegment.index) && lastSegment.index >= (representation.mediaFinishedInformation.numberOfSegments - 1)) {
212+
// For static manifests and Template addressing we can compare the index against the number of available segments
213+
if (!isDynamicManifest || representation.segmentInfoType === DashConstants.SEGMENT_TEMPLATE) {
214+
return true;
215+
}
216+
// For SegmentList we need to check if the next period is signaled
217+
else if (isDynamicManifest && representation.segmentInfoType === DashConstants.SEGMENT_LIST && representation.adaptation.period.nextPeriodId) {
218+
return true
219+
}
220220
}
221221

222-
return isFinished;
222+
// For dynamic SegmentTimeline manifests we need to check if the next period is already signaled and the segment we fetched before is the last one that is signaled.
223+
// We can not simply use the index, as numberOfSegments might have decreased after an MPD update
224+
return !!(isDynamicManifest && representation.adaptation.period.nextPeriodId && representation.segmentInfoType === DashConstants.SEGMENT_TIMELINE && representation.mediaFinishedInformation &&
225+
!isNaN(representation.mediaFinishedInformation.mediaTimeOfLastSignaledSegment) && lastSegment && !isNaN(lastSegment.mediaStartTime) && !isNaN(lastSegment.duration) && lastSegment.mediaStartTime + lastSegment.duration >= (representation.mediaFinishedInformation.mediaTimeOfLastSignaledSegment - 0.05));
223226
}
224227

228+
225229
function getSegmentRequestForTime(mediaInfo, representation, time) {
226230
let request = null;
227231

228232
if (!representation || !representation.segmentInfoType) {
229233
return request;
230234
}
231235

232-
if (requestedTime !== time) { // When playing at live edge with 0 delay we may loop back with same time and index until it is available. Reduces verboseness of logs.
233-
requestedTime = time;
234-
logger.debug('Getting the request for time : ' + time);
235-
}
236-
237236
const segment = segmentsController.getSegmentByTime(representation, time);
238237
if (segment) {
239-
segmentIndex = segment.availabilityIdx;
240238
lastSegment = segment;
241-
logger.debug('Index for time ' + time + ' is ' + segmentIndex);
239+
logger.debug('Index for time ' + time + ' is ' + segment.index);
242240
request = _getRequestForSegment(mediaInfo, segment);
243241
}
244242

@@ -253,7 +251,7 @@ function DashHandler(config) {
253251
*/
254252
function getNextSegmentRequestIdempotent(mediaInfo, representation) {
255253
let request = null;
256-
let indexToRequest = segmentIndex + 1;
254+
let indexToRequest = lastSegment ? lastSegment.index + 1 : 0;
257255
const segment = segmentsController.getSegmentByIndex(
258256
representation,
259257
indexToRequest,
@@ -277,42 +275,34 @@ function DashHandler(config) {
277275
return null;
278276
}
279277

280-
requestedTime = null;
278+
let indexToRequest = lastSegment ? lastSegment.index + 1 : 0;
281279

282-
let indexToRequest = segmentIndex + 1;
283-
284-
// check that there is a segment in this index
285280
const segment = segmentsController.getSegmentByIndex(representation, indexToRequest, lastSegment ? lastSegment.mediaStartTime : -1);
286-
if (!segment && isEndlessMedia(representation) && !dynamicStreamCompleted) {
287-
logger.debug(getType() + ' No segment found at index: ' + indexToRequest + '. Wait for next loop');
288-
return null;
289-
} else {
290-
if (segment) {
291-
request = _getRequestForSegment(mediaInfo, segment);
292-
segmentIndex = segment.availabilityIdx;
281+
282+
// No segment found
283+
if (!segment) {
284+
// Dynamic manifest there might be something available in the next iteration
285+
if (isDynamicManifest && !mediaHasFinished) {
286+
logger.debug(getType() + ' No segment found at index: ' + indexToRequest + '. Wait for next loop');
287+
return null;
293288
} else {
294-
if (isDynamicManifest) {
295-
segmentIndex = indexToRequest - 1;
296-
} else {
297-
segmentIndex = indexToRequest;
298-
}
289+
mediaHasFinished = true;
299290
}
300-
}
301-
302-
if (segment) {
291+
} else {
292+
request = _getRequestForSegment(mediaInfo, segment);
303293
lastSegment = segment;
304294
}
305295

306296
return request;
307297
}
308298

309-
function isEndlessMedia(representation) {
310-
return !isFinite(representation.adaptation.period.duration);
299+
function getCurrentIndex() {
300+
return lastSegment ? lastSegment.index : -1;
311301
}
312302

313-
function onDynamicToStatic() {
303+
function _onDynamicToStatic() {
314304
logger.debug('Dynamic stream complete');
315-
dynamicStreamCompleted = true;
305+
mediaHasFinished = true;
316306
}
317307

318308
instance = {
@@ -322,12 +312,10 @@ function DashHandler(config) {
322312
getStreamInfo,
323313
getInitRequest,
324314
getSegmentRequestForTime,
325-
getNextSegmentRequest,
326-
setCurrentIndex,
327315
getCurrentIndex,
328-
isMediaFinished,
316+
getNextSegmentRequest,
317+
lastSegmentRequested,
329318
reset,
330-
resetIndex,
331319
getNextSegmentRequestIdempotent
332320
};
333321

src/dash/controllers/RepresentationController.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,7 @@ function RepresentationController(config) {
142142
if (data[1] && !data[1].error) {
143143
currentRep = _onSegmentsLoaded(currentRep, data[1]);
144144
}
145+
_setMediaFinishedInformation(currentRep);
145146
_onRepresentationUpdated(currentRep);
146147
resolve();
147148
})
@@ -151,6 +152,10 @@ function RepresentationController(config) {
151152
});
152153
}
153154

155+
function _setMediaFinishedInformation(representation) {
156+
representation.mediaFinishedInformation = segmentsController.getMediaFinishedInformation(representation);
157+
}
158+
154159
function _onInitLoaded(representation, e) {
155160
if (!e || e.error || !e.representation) {
156161
return representation;
@@ -192,7 +197,6 @@ function RepresentationController(config) {
192197
}
193198

194199
if (segments.length > 0) {
195-
representation.availableSegmentsNumber = segments.length;
196200
representation.segments = segments;
197201
}
198202

src/dash/controllers/SegmentsController.js

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,12 +91,21 @@ function SegmentsController(config) {
9191
return getter ? getter.getSegmentByTime(representation, time) : null;
9292
}
9393

94+
function getMediaFinishedInformation(representation) {
95+
const getter = getSegmentsGetter(representation);
96+
return getter ? getter.getMediaFinishedInformation(representation) : {
97+
numberOfSegments: 0,
98+
mediaTimeOfLastSignaledSegment: NaN
99+
};
100+
}
101+
94102
instance = {
95103
initialize,
96104
updateInitData,
97105
updateSegmentData,
98106
getSegmentByIndex,
99-
getSegmentByTime
107+
getSegmentByTime,
108+
getMediaFinishedInformation
100109
};
101110

102111
setup();

src/dash/models/DashManifestModel.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -582,6 +582,9 @@ function DashManifestModel() {
582582
}
583583

584584
function calcSegmentDuration(segmentTimeline) {
585+
if (!segmentTimeline || !segmentTimeline.S_asArray) {
586+
return NaN;
587+
}
585588
let s0 = segmentTimeline.S_asArray[0];
586589
let s1 = segmentTimeline.S_asArray[1];
587590
return s0.hasOwnProperty('d') ? s0.d : (s1.t - s0.t);
@@ -689,6 +692,10 @@ function DashManifestModel() {
689692
voPeriod.duration = realPeriod.duration;
690693
}
691694

695+
if (voPreviousPeriod) {
696+
voPreviousPeriod.nextPeriodId = voPeriod.id;
697+
}
698+
692699
voPeriods.push(voPeriod);
693700
realPreviousPeriod = realPeriod;
694701
voPreviousPeriod = voPeriod;

src/dash/utils/ListSegmentsGetter.js

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,22 @@ function ListSegmentsGetter(config, isDynamic) {
4747
}
4848
}
4949

50+
function getMediaFinishedInformation(representation) {
51+
const mediaFinishedInformation = { numberOfSegments: 0, mediaTimeOfLastSignaledSegment: NaN }
52+
53+
if (!representation) {
54+
return mediaFinishedInformation;
55+
}
56+
57+
const list = representation.adaptation.period.mpd.manifest.Period_asArray[representation.adaptation.period.index].AdaptationSet_asArray[representation.adaptation.index].Representation_asArray[representation.index].SegmentList;
58+
const startNumber = representation && !isNaN(representation.startNumber) ? representation.startNumber : 1;
59+
const offset = Math.max(startNumber - 1, 0);
60+
61+
mediaFinishedInformation.numberOfSegments = offset + list.SegmentURL_asArray.length;
62+
63+
return mediaFinishedInformation
64+
}
65+
5066
function getSegmentByIndex(representation, index) {
5167
checkConfig();
5268

@@ -71,13 +87,10 @@ function ListSegmentsGetter(config, isDynamic) {
7187
segment.replacementTime = (startNumber + index - 1) * representation.segmentDuration;
7288
segment.media = s.media ? s.media : '';
7389
segment.mediaRange = s.mediaRange;
74-
segment.index = index;
7590
segment.indexRange = s.indexRange;
7691
}
7792
}
7893

79-
representation.availableSegmentsNumber = len;
80-
8194
return segment;
8295
}
8396

@@ -101,8 +114,9 @@ function ListSegmentsGetter(config, isDynamic) {
101114
}
102115

103116
instance = {
104-
getSegmentByIndex: getSegmentByIndex,
105-
getSegmentByTime: getSegmentByTime
117+
getSegmentByIndex,
118+
getSegmentByTime,
119+
getMediaFinishedInformation
106120
};
107121

108122
return instance;

0 commit comments

Comments
 (0)