Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ProducerReferenceTime #3895

Merged
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
d037dae
Calculate and apply time offsets from ProducerReferenceTime when sett…
Feb 18, 2022
30eb52d
Calculate and apply time offsets from ProducerReferenceTime when sett…
Feb 18, 2022
004195e
Merge branch 'producer-reference-time' of https://github.com/mattjugg…
Feb 18, 2022
4242a80
Clearer variables and function names
Feb 21, 2022
8ef6da3
Add collection of PRFTs from Representations
Feb 22, 2022
9b345ff
Calculate and apply time offsets from ProducerReferenceTime when sett…
Feb 18, 2022
ef30e51
Clearer variables and function names
Feb 21, 2022
521543e
Add collection of PRFTs from Representations
Feb 22, 2022
0f362fe
Check for PRFT over all periods and factor in presentationTimeOffset …
Mar 2, 2022
6a165d7
fix conflicts
Mar 2, 2022
8397c07
Merge branch 'development' into producer-reference-time
mattjuggins Mar 23, 2022
77cb57a
Corrections and additional test
Mar 23, 2022
5773ed9
Apply offset to all latency values
Apr 11, 2022
67dce68
Calculate and apply time offsets from ProducerReferenceTime when sett…
Feb 18, 2022
276c723
Clearer variables and function names
Feb 21, 2022
3cc07cc
Add collection of PRFTs from Representations
Feb 22, 2022
a3e027d
Check for PRFT over all periods and factor in presentationTimeOffset …
Mar 2, 2022
a1b26fb
Corrections and additional test
Mar 23, 2022
1b2ac43
Integrate ProducerReferenceTime offset calculations with ServiceDescr…
Apr 14, 2022
009849a
Fix merge errors
Apr 14, 2022
203fee3
Revert tests changed in merge
Apr 14, 2022
393dcc3
Merge branch 'development' into producer-reference-time
mattjuggins Apr 14, 2022
f024522
Fix StreamController formatting
Apr 14, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions src/dash/DashAdapter.js
Original file line number Diff line number Diff line change
Expand Up @@ -384,6 +384,28 @@ function DashAdapter() {
return realAdaptation;
}

/**
* Returns the ProducerReferenceTimes as saved in the DashManifestModel if present
* @param {object} streamInfo
* @param {object} mediaInfo
* @returns {object} producerReferenceTimes
* @memberOf module:DashAdapter
* @instance
*/
function getProducerReferenceTimes(streamInfo, mediaInfo) {
let id, realAdaptation;

const selectedVoPeriod = getPeriodForStreamInfo(streamInfo, voPeriods);
id = mediaInfo ? mediaInfo.id : null;

if (voPeriods.length > 0 && selectedVoPeriod) {
realAdaptation = id ? dashManifestModel.getAdaptationForId(id, voPeriods[0].mpd.manifest, selectedVoPeriod.index) : dashManifestModel.getAdaptationForIndex(mediaInfo ? mediaInfo.index : null, voPeriods[0].mpd.manifest, selectedVoPeriod.index);
}

if (!realAdaptation) return [];
return dashManifestModel.getProducerReferenceTimesForAdaptation(realAdaptation);
}

/**
* Return all EssentialProperties of a Representation
* @param {object} representation
Expand Down Expand Up @@ -1165,6 +1187,7 @@ function DashAdapter() {
getAllMediaInfoForType,
getAdaptationForType,
getRealAdaptation,
getProducerReferenceTimes,
getRealPeriodByIndex,
getEssentialPropertiesForRepresentation,
getVoRepresentations,
Expand Down
5 changes: 5 additions & 0 deletions src/dash/constants/DashConstants.js
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ class DashConstants {
this.ESSENTIAL_PROPERTY = 'EssentialProperty';
this.SUPPLEMENTAL_PROPERTY = 'SupplementalProperty';
this.INBAND_EVENT_STREAM = 'InbandEventStream';
this.PRODUCER_REFERENCE_TIME = 'ProducerReferenceTime';
this.ACCESSIBILITY = 'Accessibility';
this.ROLE = 'Role';
this.RATING = 'Rating';
Expand All @@ -98,6 +99,8 @@ class DashConstants {
this.LANG = 'lang';
this.VIEWPOINT = 'Viewpoint';
this.ROLE_ASARRAY = 'Role_asArray';
this.REPRESENTATION_ASARRAY = 'Representation_asArray';
this.PRODUCERREFERENCETIME_ASARRAY = 'ProducerReferenceTime_asArray';
this.ACCESSIBILITY_ASARRAY = 'Accessibility_asArray';
this.AUDIOCHANNELCONFIGURATION_ASARRAY = 'AudioChannelConfiguration_asArray';
this.CONTENTPROTECTION_ASARRAY = 'ContentProtection_asArray';
Expand Down Expand Up @@ -135,6 +138,8 @@ class DashConstants {
this.PUBLISH_TIME = 'publishTime';
this.ORIGINAL_PUBLISH_TIME = 'originalPublishTime';
this.ORIGINAL_MPD_ID = 'mpdId';
this.WALL_CLOCK_TIME = 'wallClockTime';
this.PRESENTATION_TIME = 'presentationTime';
}

constructor () {
Expand Down
52 changes: 51 additions & 1 deletion src/dash/models/DashManifestModel.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import UTCTiming from '../vo/UTCTiming';
import Event from '../vo/Event';
import BaseURL from '../vo/BaseURL';
import EventStream from '../vo/EventStream';
import ProducerReferenceTime from '../vo/ProducerReferenceTime';
import ObjectUtils from '../../streaming/utils/ObjectUtils';
import URLUtils from '../../streaming/utils/URLUtils';
import FactoryMaker from '../../core/FactoryMaker';
Expand Down Expand Up @@ -162,6 +163,53 @@ function DashManifestModel() {
return getIsTypeOf(adaptation, Constants.IMAGE);
}

function getProducerReferenceTimesForAdaptation(adaptation) {
const prtArray = adaptation && adaptation.hasOwnProperty(DashConstants.PRODUCERREFERENCETIME_ASARRAY) ? adaptation[DashConstants.PRODUCERREFERENCETIME_ASARRAY] : [];

// ProducerReferenceTime elements can also be contained in Representations
const representationsArray = adaptation && adaptation.hasOwnProperty(DashConstants.REPRESENTATION_ASARRAY) ? adaptation[DashConstants.REPRESENTATION_ASARRAY] : [];

representationsArray.forEach((rep) => {
if (rep.hasOwnProperty(DashConstants.PRODUCERREFERENCETIME_ASARRAY)) {
prtArray.push(...rep[DashConstants.PRODUCERREFERENCETIME_ASARRAY]);
}
});

const prtsForAdaptation = [];

// Unlikely to have multiple ProducerReferenceTimes.
prtArray.forEach((prt) => {
const entry = new ProducerReferenceTime();

if (prt.hasOwnProperty(DashConstants.ID)) {
entry[DashConstants.ID] = prt[DashConstants.ID];
} else {
// Ignore. Missing mandatory attribute
return;
}

if (prt.hasOwnProperty(DashConstants.WALL_CLOCK_TIME)) {
entry[DashConstants.WALL_CLOCK_TIME] = prt[DashConstants.WALL_CLOCK_TIME];
} else {
// Ignore. Missing mandatory attribute
return;
}

if (prt.hasOwnProperty(DashConstants.PRESENTATION_TIME)) {
entry[DashConstants.PRESENTATION_TIME] = prt[DashConstants.PRESENTATION_TIME];
} else {
// Ignore. Missing mandatory attribute
return;
}

// Not intereseted in other attributes for now
dsilhavy marked this conversation as resolved.
Show resolved Hide resolved
// UTC element contained must be same as that in the MPD
prtsForAdaptation.push(entry);
})

return prtsForAdaptation;
}

function getLanguageForAdaptation(adaptation) {
let lang = '';

Expand Down Expand Up @@ -1116,7 +1164,8 @@ function DashManifestModel() {
latency = {
target: sd[prop].target,
max: sd[prop].max,
min: sd[prop].min
min: sd[prop].min,
referenceId: sd[prop].referenceId
};
} else if (prop === DashConstants.SERVICE_DESCRIPTION_PLAYBACK_RATE) {
playbackRate = {
Expand Down Expand Up @@ -1170,6 +1219,7 @@ function DashManifestModel() {
getIsTypeOf,
getIsText,
getIsFragmented,
getProducerReferenceTimesForAdaptation,
getLanguageForAdaptation,
getViewpointForAdaptation,
getRolesForAdaptation,
Expand Down
46 changes: 46 additions & 0 deletions src/dash/vo/ProducerReferenceTime.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/**
* The copyright in this software is being made available under the BSD License,
* included below. This software may be subject to other third party and contributor
* rights, including patent rights, and no such rights are granted under this license.
*
* Copyright (c) 2013, Dash Industry Forum.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation and/or
* other materials provided with the distribution.
* * Neither the name of Dash Industry Forum nor the names of its
* contributors may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS AS IS AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
/**
* @class
* @ignore
*/
class ProducerReferenceTime {
constructor() {
this.id = null;
this.inband = false;
this.type = 'encoder';
this.applicationScheme = null;
this.wallClockTime = null;
this.presentationTime = NaN;
dsilhavy marked this conversation as resolved.
Show resolved Hide resolved
}
}

export default ProducerReferenceTime;
40 changes: 36 additions & 4 deletions src/streaming/controllers/PlaybackController.js
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,7 @@ function PlaybackController() {
* @returns {number} object
* @memberof PlaybackController#
*/
function computeAndSetLiveDelay(fragmentDuration, manifestInfo) {
function computeAndSetLiveDelay(fragmentDuration, timeOffsets, manifestInfo) {
let delay,
ret,
startTime;
Expand All @@ -238,7 +238,7 @@ function PlaybackController() {

// Apply live delay from ServiceDescription
if (settings.get().streaming.delay.applyServiceDescription && isNaN(settings.get().streaming.delay.liveDelay) && isNaN(settings.get().streaming.delay.liveDelayFragmentCount)) {
_applyServiceDescription(manifestInfo);
_applyServiceDescription(manifestInfo, timeOffsets);
}

if (mediaPlayerModel.getLiveDelay()) {
Expand Down Expand Up @@ -272,7 +272,13 @@ function PlaybackController() {
return ret;
}

function _applyServiceDescription(manifestInfo) {
/**
* Applys service description Latency and PlaybackRate attributes to liveDelay and catchup settings
* @param {Object} manifestInfo
* @param {Array} prftTimeOffsets - Time offests calculated from ProducerReferenceTime elements
* @private
*/
function _applyServiceDescription(manifestInfo, prftTimeOffsets) {
if (!manifestInfo || !manifestInfo.serviceDescriptions) {
return;
}
Expand All @@ -293,7 +299,7 @@ function PlaybackController() {
settings.update({
streaming: {
delay: {
liveDelay: llsd.latency.target / 1000
liveDelay: _calculateDelayFromServiceDescription(prftTimeOffsets, llsd.latency),
}
}
});
Expand Down Expand Up @@ -321,6 +327,32 @@ function PlaybackController() {
}
}

/**
* Calculates offset to apply to live delay as described in TS 103 285 Clause 10.20.4
* @param {Array} prftTimeOffsets
* @param {Object} llsdLatency
* @returns {number}
* @private
*/
function _calculateDelayFromServiceDescription(prftTimeOffsets, llsdLatency) {
let to = 0;
let offset = prftTimeOffsets.filter(prt => {
return prt.id === llsdLatency.referenceId;
});

// If only one ProducerReferenceTime to generate one TO, then use that regardless of matching ids
if (offset.length === 0) {
to = (prftTimeOffsets.length > 0) ? prftTimeOffsets[0].to : 0;
} else {
// If multiple id matches, use the first but this should be invalid
to = offset[0].to || 0;
}

// TS 103 285 Clause 10.20.4. 3) Subtract calculated offset from Latency@target converted from milliseconds
// liveLatency does not consider ST@availabilityTimeOffset so leave out that step
return ((llsdLatency.target / 1000) - to)
}

function getAvailabilityStartTime() {
return availabilityStartTime;
}
Expand Down
56 changes: 55 additions & 1 deletion src/streaming/controllers/StreamController.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ import DashJSError from '../vo/DashJSError';
import Errors from '../../core/errors/Errors';
import EventController from './EventController';
import ConformanceViolationConstants from '../constants/ConformanceViolationConstants';
import DashConstants from '../../dash/constants/DashConstants';

const PLAYBACK_ENDED_TIMER_INTERVAL = 200;
const DVR_WAITING_OFFSET = 2;
Expand Down Expand Up @@ -344,7 +345,8 @@ function StreamController() {
if (adapter.getIsDynamic() && streams.length) {
const manifestInfo = streamsInfo[0].manifestInfo;
const fragmentDuration = _getFragmentDurationForLiveDelayCalculation(streamsInfo, manifestInfo);
playbackController.computeAndSetLiveDelay(fragmentDuration, manifestInfo);
const timeOffsets = _getLiveDelayTimeOffsets(streamsInfo);
playbackController.computeAndSetLiveDelay(fragmentDuration, timeOffsets, manifestInfo);
}

// Figure out the correct start time and the correct start period
Expand Down Expand Up @@ -1150,6 +1152,58 @@ function StreamController() {
}
}

/**
* Calculates an array of time offsets each with matching ProducerReferenceTime id
* @param {Array} streamInfos
* @returns {Array}
* @private
*/
function _getLiveDelayTimeOffsets(streamInfos) {
try {
let timeOffsets = [];
if (streamInfos) {
const mediaTypes = [Constants.VIDEO, Constants.AUDIO, Constants.TEXT];
const astInSeconds = adapter.getAvailabilityStartTime() / 1000;

streamInfos.forEach((streamInfo) => {
const offsets = mediaTypes
.reduce((acc, mediaType) => {
acc = acc.concat(adapter.getAllMediaInfoForType(streamInfo, mediaType));
return acc;
}, [])
.reduce((acc, mediaInfo) => {
const prts = adapter.getProducerReferenceTimes(streamInfo, mediaInfo);
prts.forEach((prt) => {
const voRepresentations = adapter.getVoRepresentations(mediaInfo);
if (voRepresentations && voRepresentations.length > 0 && voRepresentations[0].adaptation && voRepresentations[0].segmentInfoType === DashConstants.SEGMENT_TEMPLATE) {
const voRep = voRepresentations[0];
const d = new Date(prt[DashConstants.WALL_CLOCK_TIME]);
const wallClockTime = d.getTime() / 1000;
// TS 103 285 Clause 10.20.4
// 1) Calculate PRT0
// i) take the PRT@presentationTime and subtract any ST@presentationTime
dsilhavy marked this conversation as resolved.
Show resolved Hide resolved
// ii) convert this time to seconds by dividing by ST@timescale
// iii) Add this to start time of period that contains PRT.
// N.B presentationTimeOffset is already divided by timescale at this point
const prt0 = wallClockTime - (((prt[DashConstants.PRESENTATION_TIME] / voRep[DashConstants.TIMESCALE]) - voRep[DashConstants.PRESENTATION_TIME_OFFSET]) + streamInfo.start);
// 2) Calculate TO between PRT at the start of MPD timeline and the AST
const to = astInSeconds - prt0;
// 3) Not applicable as liveLatency does not consider ST@availabilityTimeOffset
acc.push({id: prt[DashConstants.ID], to});
}
});
return acc;
}, [])

timeOffsets = timeOffsets.concat(offsets);
})
}
return timeOffsets;
} catch (e) {
return [];
}
};

/**
* Callback handler after the manifest has been updated. Trigger an update in the adapter and filter unsupported stuff.
* Finally attempt UTC sync
Expand Down
17 changes: 16 additions & 1 deletion test/unit/dash.DashAdapter.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ const manifest_with_ll_service_description = {
ServiceDescription: {},
ServiceDescription_asArray: [{
Scope: { schemeIdUri: 'urn:dvb:dash:lowlatency:scope:2019' },
Latency: { target: 3000, max: 5000, min: 2000 },
Latency: { target: 3000, max: 5000, min: 2000, referenceId: 7 },
PlaybackRate: { max: 1.5, min: 0.5 }
}],
Period_asArray: [{
Expand Down Expand Up @@ -235,6 +235,20 @@ describe('DashAdapter', function () {
expect(realAdaptation).to.be.undefined; // jshint ignore:line
});

it('should return empty array when getProducerReferenceTimes is called and streamInfo parameter is null or undefined', () => {
const producerReferenceTimes = dashAdapter.getProducerReferenceTimes(null, voHelper.getDummyMediaInfo());

expect(producerReferenceTimes).to.be.instanceOf(Array); // jshint ignore:line
expect(producerReferenceTimes).to.be.empty; // jshint ignore:line
});

it('should return empty array when getProducerReferenceTimes is called and mediaInfo parameter is null or undefined', () => {
const producerReferenceTimes = dashAdapter.getProducerReferenceTimes(voHelper.getDummyStreamInfo(), null);

expect(producerReferenceTimes).to.be.instanceOf(Array); // jshint ignore:line
expect(producerReferenceTimes).to.be.empty; // jshint ignore:line
});

it('should return empty array when getUTCTimingSources is called and no period is defined', function () {
const timingSources = dashAdapter.getUTCTimingSources();

Expand Down Expand Up @@ -470,6 +484,7 @@ describe('DashAdapter', function () {
expect(streamInfos[0].manifestInfo.serviceDescriptions[0].latency.target).equals(3000); // jshint ignore:line
expect(streamInfos[0].manifestInfo.serviceDescriptions[0].latency.max).equals(5000); // jshint ignore:line
expect(streamInfos[0].manifestInfo.serviceDescriptions[0].latency.min).equals(2000); // jshint ignore:line
expect(streamInfos[0].manifestInfo.serviceDescriptions[0].latency.referenceId).equals(7); // jshint ignore:line
expect(streamInfos[0].manifestInfo.serviceDescriptions[0].playbackRate.max).equals(1.5); // jshint ignore:line
expect(streamInfos[0].manifestInfo.serviceDescriptions[0].playbackRate.min).equals(0.5); // jshint ignore:line
});
Expand Down
Loading