| ... | ... | @@ -52,8 +52,6 @@ player.updateSettings({ |
|
|
|
})
|
|
|
|
```
|
|
|
|
|
|
|
|
For further details on background synchronization check the Section _Background synchronization_
|
|
|
|
|
|
|
|
#### Synchronization after MPD updates
|
|
|
|
By default, dash.js initiates a synchronization request after each MPD update. This behavior is modified by certain settings parameters. The general workflow is as follows:
|
|
|
|
|
| ... | ... | @@ -89,7 +87,73 @@ player.updateSettings({ |
|
|
|
})
|
|
|
|
```
|
|
|
|
#### Post-synchronization parameter adjustment
|
|
|
|
After each regular synchronization attempt dash.js adjusts the `timeBetweenSyncAttempts` parameter based
|
|
|
|
After each regular synchronization attempt dash.js adjusts the `timeBetweenSyncAttempts` parameter based on certain criteria:
|
|
|
|
|
|
|
|
```javascript
|
|
|
|
function _adjustTimeBetweenSyncAttempts(offset) {
|
|
|
|
try {
|
|
|
|
const isOffsetDriftWithinThreshold = _isOffsetDriftWithinThreshold(offset);
|
|
|
|
const timeBetweenSyncAttempts = !isNaN(settings.get().streaming.utcSynchronization.timeBetweenSyncAttempts) ? settings.get().streaming.utcSynchronization.timeBetweenSyncAttempts : DEFAULT_TIME_BETWEEN_SYNC_ATTEMPTS;
|
|
|
|
const timeBetweenSyncAttemptsAdjustmentFactor = !isNaN(settings.get().streaming.utcSynchronization.timeBetweenSyncAttemptsAdjustmentFactor) ? settings.get().streaming.utcSynchronization.timeBetweenSyncAttemptsAdjustmentFactor : DEFAULT_TIME_BETWEEN_SYNC_ATTEMPTS_ADJUSTMENT_FACTOR;
|
|
|
|
const maximumTimeBetweenSyncAttempts = !isNaN(settings.get().streaming.utcSynchronization.maximumTimeBetweenSyncAttempts) ? settings.get().streaming.utcSynchronization.maximumTimeBetweenSyncAttempts : DEFAULT_MAXIMUM_TIME_BETWEEN_SYNC;
|
|
|
|
const minimumTimeBetweenSyncAttempts = !isNaN(settings.get().streaming.utcSynchronization.minimumTimeBetweenSyncAttempts) ? settings.get().streaming.utcSynchronization.minimumTimeBetweenSyncAttempts : DEFAULT_MINIMUM_TIME_BETWEEN_SYNC;
|
|
|
|
let adjustedTimeBetweenSyncAttempts;
|
|
|
|
|
|
|
|
if (isOffsetDriftWithinThreshold) {
|
|
|
|
// The drift between the current offset and the last offset is within the allowed threshold. Increase sync time
|
|
|
|
adjustedTimeBetweenSyncAttempts = Math.min(timeBetweenSyncAttempts * timeBetweenSyncAttemptsAdjustmentFactor, maximumTimeBetweenSyncAttempts);
|
|
|
|
logger.debug(`Increasing timeBetweenSyncAttempts to ${adjustedTimeBetweenSyncAttempts}`);
|
|
|
|
} else {
|
|
|
|
// Drift between the current offset and the last offset is not within the allowed threshold. Decrease sync time
|
|
|
|
adjustedTimeBetweenSyncAttempts = Math.max(timeBetweenSyncAttempts / timeBetweenSyncAttemptsAdjustmentFactor, minimumTimeBetweenSyncAttempts);
|
|
|
|
logger.debug(`Decreasing timeBetweenSyncAttempts to ${adjustedTimeBetweenSyncAttempts}`);
|
|
|
|
}
|
|
|
|
|
|
|
|
settings.update({ streaming: { utcSynchronization: { timeBetweenSyncAttempts: adjustedTimeBetweenSyncAttempts } } });
|
|
|
|
} catch (e) {
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
In the first step the player checks if the offset is within certain boundaries:
|
|
|
|
|
|
|
|
```javascript
|
|
|
|
|
|
|
|
function _isOffsetDriftWithinThreshold(offset) {
|
|
|
|
try {
|
|
|
|
if (isNaN(lastOffset)) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
const maxAllowedDrift = settings.get().streaming.utcSynchronization.maximumAllowedDrift && !isNaN(settings.get().streaming.utcSynchronization.maximumAllowedDrift) ? settings.get().streaming.utcSynchronization.maximumAllowedDrift : DEFAULT_MAXIMUM_ALLOWED_DRIFT;
|
|
|
|
const lowerBound = lastOffset - maxAllowedDrift;
|
|
|
|
const upperBound = lastOffset + maxAllowedDrift;
|
|
|
|
|
|
|
|
return offset >= lowerBound && offset <= upperBound;
|
|
|
|
} catch (e) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
Depending on whether the offset is included in the calculated boundaries `timeBetweenSyncAttempts` is either multiplied or divided by `timeBetweenSyncAttemptsAdjustmentFactor`. By assigning specific values to `maximumTimeBetweenSyncAttempts` and `minimumTimeBetweenSyncAttempts` upper and lower bounds for `timeBetweenSyncAttempts`.
|
|
|
|
|
|
|
|
The parameters can be adjusted in the settings:
|
|
|
|
```javascript
|
|
|
|
player.updateSettings({
|
|
|
|
streaming: {
|
|
|
|
utcSynchronization: {
|
|
|
|
timeBetweenSyncAttempts: 30,
|
|
|
|
maximumTimeBetweenSyncAttempts: 600,
|
|
|
|
minimumTimeBetweenSyncAttempts: 2,
|
|
|
|
timeBetweenSyncAttemptsAdjustmentFactor: 2,
|
|
|
|
maximumAllowedDrift: 100,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
})
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### Synchronization after download errors
|
|
|
|
In addition to regular synchronization attempts dash.js triggers a background synchronization in case requests to media segments result in errors (e.g 404 errors). This is to make sure that the client clock is still synchronized and the request error is not caused by an erroneous offset.
|
| ... | ... | @@ -105,12 +169,53 @@ player.updateSettings({ |
|
|
|
})
|
|
|
|
```
|
|
|
|
|
|
|
|
### Background synchronization
|
|
|
|
|
|
|
|
### Offset calculation
|
|
|
|
The offset between two consecutive synchronization requests is calculated by accounting for the round trip time:
|
|
|
|
```javascript
|
|
|
|
function _calculateOffset(deviceTimeBeforeSync, deviceTimeAfterSync, serverTime) {
|
|
|
|
const deviceReferenceTime = deviceTimeAfterSync - ((deviceTimeAfterSync - deviceTimeBeforeSync) / 2);
|
|
|
|
|
|
|
|
return serverTime - deviceReferenceTime;
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
### Configuration example
|
|
|
|
|
|
|
|
The available configuration paramaters:
|
|
|
|
|
|
|
|
| Parameter | Description |
|
|
|
|
| ------------- |:-------------:|
|
|
|
|
| backgroundAttempts | Number of synchronization attempts to perform in the background after an initial synchronization request has been done. This is used to verify that the derived client-server offset is correct. |
|
|
|
|
| timeBetweenSyncAttempts | The time in seconds between two consecutive sync attempts. |
|
|
|
|
| maximumTimeBetweenSyncAttempts | The maximum time in seconds between two consecutive sync attempts. |
|
|
|
|
| minimumTimeBetweenSyncAttempts | The minimum time in seconds between two consecutive sync attempts|
|
|
|
|
| timeBetweenSyncAttemptsAdjustmentFactor | The factor used to multiply or divide the timeBetweenSyncAttempts parameter after a sync. The maximumAllowedDrift defines whether this value is used as a factor or a dividend. |
|
|
|
|
| maximumAllowedDrift | The maximum allowed drift specified in milliseconds between two consecutive synchronization attempts. |
|
|
|
|
| enableBackgroundSyncAfterSegmentDownloadError | Enables or disables the background sync after the player ran into a segment download error. |
|
|
|
|
| defaultTimingSource | The default timing source to be used. The timing sources in the MPD take precedence over this one. |
|
|
|
|
|
|
|
|
|
|
|
|
An example of a full configuration object looks the following:
|
|
|
|
|
|
|
|
```javascript
|
|
|
|
player.updateSettings({
|
|
|
|
streaming: {
|
|
|
|
backgroundAttempts: 2,
|
|
|
|
timeBetweenSyncAttempts: 30,
|
|
|
|
maximumTimeBetweenSyncAttempts: 600,
|
|
|
|
minimumTimeBetweenSyncAttempts: 2,
|
|
|
|
timeBetweenSyncAttemptsAdjustmentFactor: 2,
|
|
|
|
maximumAllowedDrift: 100,
|
|
|
|
enableBackgroundSyncAfterSegmentDownloadError: true,
|
|
|
|
defaultTimingSource: {
|
|
|
|
scheme: 'urn:mpeg:dash:utc:http-xsdate:2014',
|
|
|
|
value: 'http://time.akamai.com/?iso&ms'
|
|
|
|
}
|
|
|
|
}
|
|
|
|
})
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
## References
|
|
|
|
* [1] [DASH-IF Implementation Guidelines](https://dashif-documents.azurewebsites.net/Guidelines-TimingModel/master/Guidelines-TimingModel.html#clock-sync)
|
|
|
|
* [2] [DASH-IF IOP Guidelines](https://dash-industry-forum.github.io/docs/DASH-IF-IOP-v4.3.pdf) |
|
|
\ No newline at end of file |