Skip to content

Commit

Permalink
fix: Fix v1 emsg box start/end times (shaka-project#3198)
Browse files Browse the repository at this point in the history
The start/end times of emsg event must be in presentation time units.
Fix the bug where times are incorrect when the timestamps do not start at 0.
For emsg v1 the entries in the box are in media time units.
Convert them back to presentation time units by subtracing the offset before raising the event.
  • Loading branch information
duggaraju authored Mar 11, 2021
1 parent d882d28 commit fc3afeb
Show file tree
Hide file tree
Showing 4 changed files with 103 additions and 13 deletions.
2 changes: 1 addition & 1 deletion lib/media/streaming_engine.js
Original file line number Diff line number Diff line change
Expand Up @@ -1613,7 +1613,7 @@ shaka.media.StreamingEngine = class {
} else {
timescale = box.reader.readUint32();
const pts = box.reader.readUint64();
startTime = pts / timescale;
startTime = (pts / timescale) + reference.timestampOffset;
presentationTimeDelta = startTime - reference.startTime;
eventDuration = box.reader.readUint32();
id = box.reader.readUint32();
Expand Down
99 changes: 91 additions & 8 deletions test/media/streaming_engine_unit.js
Original file line number Diff line number Diff line change
Expand Up @@ -120,8 +120,10 @@ describe('StreamingEngine', () => {
jasmine.clock().mockDate();
});

/** @param {boolean=} trickMode */
function setupVod(trickMode) {
/** @param {boolean=} trickMode
* @param {number=} mediaOffset The offset from 0 for the segment start times
*/
function setupVod(trickMode, mediaOffset) {
// For VOD, we fake a presentation that has 2 Periods of equal duration
// (20 seconds), where each Period has 1 Variant and 1 text stream.
//
Expand All @@ -132,6 +134,11 @@ describe('StreamingEngine', () => {
// first Period, and 2 audio, 2 video, and 2 text for the second Period.
// All media segments are (by default) 10 seconds long.

const offset = mediaOffset || 0;
// timestampOffset is -ve since it is added to bring the timeline to 0.
// -0 and 0 are not same so explicitly set to 0.
const timestampOffset = offset === 0 ? 0 : -offset;

// Create SegmentData map for FakeMediaSourceEngine.
const initSegmentSizeAudio = initSegmentRanges[ContentType.AUDIO][1] -
initSegmentRanges[ContentType.AUDIO][0] + 1;
Expand All @@ -151,8 +158,9 @@ describe('StreamingEngine', () => {
makeBuffer(segmentSizes[ContentType.AUDIO]),
makeBuffer(segmentSizes[ContentType.AUDIO]),
],
segmentStartTimes: [0, 10, 20, 30],
segmentStartTimes: [offset, offset+10, offset+20, offset+30],
segmentDuration: 10,
timestampOffset: timestampOffset,
},
video: {
initSegments: [
Expand All @@ -165,8 +173,9 @@ describe('StreamingEngine', () => {
makeBuffer(segmentSizes[ContentType.VIDEO]),
makeBuffer(segmentSizes[ContentType.VIDEO]),
],
segmentStartTimes: [0, 10, 20, 30],
segmentStartTimes: [offset, offset+10, offset+20, offset+30],
segmentDuration: 10,
timestampOffset: timestampOffset,
},
text: {
initSegments: [],
Expand All @@ -176,8 +185,9 @@ describe('StreamingEngine', () => {
makeBuffer(segmentSizes[ContentType.TEXT]),
makeBuffer(segmentSizes[ContentType.TEXT]),
],
segmentStartTimes: [0, 10, 20, 30],
segmentStartTimes: [offset, offset+10, offset+20, offset+30],
segmentDuration: 10,
timestampOffset: timestampOffset,
},
};
if (trickMode) {
Expand All @@ -192,8 +202,9 @@ describe('StreamingEngine', () => {
makeBuffer(segmentSizes[ContentType.VIDEO]),
makeBuffer(segmentSizes[ContentType.VIDEO]),
],
segmentStartTimes: [0, 10, 20, 30],
segmentStartTimes: [offset, offset+10, offset+20, offset+30],
segmentDuration: 10,
timestampOffset: timestampOffset,
};
}

Expand Down Expand Up @@ -372,14 +383,24 @@ describe('StreamingEngine', () => {
video: segmentData[ContentType.VIDEO].segmentDuration,
text: segmentData[ContentType.TEXT].segmentDuration,
};

const timestampOffsets = {
audio: segmentData[ContentType.AUDIO].timestampOffset,
video: segmentData[ContentType.VIDEO].timestampOffset,
text: segmentData[ContentType.TEXT].timestampOffset,
};

if (segmentData['trickvideo']) {
segmentDurations['trickvideo'] =
segmentData['trickvideo'].segmentDuration;
timestampOffsets['trickvideo'] =
segmentData['trickvideo'].timestampOffset;
}
manifest = shaka.test.StreamingEngineUtil.createManifest(
/** @type {!shaka.media.PresentationTimeline} */(timeline),
[firstPeriodStartTime, secondPeriodStartTime],
presentationDuration, segmentDurations, initSegmentRanges);
presentationDuration, segmentDurations, initSegmentRanges,
timestampOffsets);

audioStream = manifest.variants[0].audio;
videoStream = manifest.variants[0].video;
Expand Down Expand Up @@ -2616,7 +2637,7 @@ describe('StreamingEngine', () => {
expect(event.detail).toEqual(emsgObj);
});

it('raises an event for registered embedded v1 emsg boxes ', async () => {
it('raises an event for registered embedded v1 emsg boxes', async () => {
// same event but verison 1.
const v1EmsgSegment = Uint8ArrayUtils.fromHex(
'0000003f656d7367010000000000000100000000000000080000ffff00000001' +
Expand Down Expand Up @@ -2717,6 +2738,68 @@ describe('StreamingEngine', () => {
});
});

describe('embedded emsg boxes with non zero timestamps', () => {
const emsgSegment = Uint8ArrayUtils.fromHex(
'0000003b656d736700000000666f6f3a6261723a637573746f6d646174617363' +
'68656d6500310000000001000000080000ffff0000000174657374');
const emsgObj = {
startTime: 8,
endTime: 0xffff + 8,
schemeIdUri: 'foo:bar:customdatascheme',
value: '1',
timescale: 1,
presentationTimeDelta: 8,
eventDuration: 0xffff,
id: 1,
messageData: new Uint8Array([0x74, 0x65, 0x73, 0x74]),
};

beforeEach(() => {
// setup an offset for the timestamps.
setupVod(false, 10);
mediaSourceEngine = new shaka.test.FakeMediaSourceEngine(segmentData);
createStreamingEngine();
});

it('event start matches presentation times for emsg boxes', async () => {
segmentData[ContentType.VIDEO].segments[0] = emsgSegment;
videoStream.emsgSchemeIdUris = [emsgObj.schemeIdUri];

// Here we go!
streamingEngine.switchVariant(variant);
streamingEngine.switchTextStream(textStream);
await streamingEngine.start();
playing = true;
await runTest();

expect(onEvent).toHaveBeenCalledTimes(1);

const event = onEvent.calls.argsFor(0)[0];
expect(event.detail).toEqual(emsgObj);
});

it('event start matches presentation time for v1 emsg boxes', async () => {
// same event but verison 1. start time is 18.
const v1EmsgSegment = Uint8ArrayUtils.fromHex(
'0000003f656d7367010000000000000100000000000000120000ffff00000001' +
'666f6f3a6261723a637573746f6d64617461736368656d6500310074657374');
segmentData[ContentType.VIDEO].segments[0] = v1EmsgSegment;
videoStream.emsgSchemeIdUris = [emsgObj.schemeIdUri];

// Here we go!
streamingEngine.switchVariant(variant);
streamingEngine.switchTextStream(textStream);
await streamingEngine.start();
playing = true;
await runTest();

expect(onEvent).toHaveBeenCalledTimes(1);

const event = onEvent.calls.argsFor(0)[0];
expect(event.detail).toEqual(emsgObj);
});
});

describe('network downgrading', () => {
/** @type {shaka.extern.Variant} */
let initialVariant;
Expand Down
8 changes: 6 additions & 2 deletions test/test/util/fake_media_source_engine.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ shaka.test.FakeMediaSourceEngine = class {
this.initSegments[type] = data.initSegments.map(() => false);
this.segments[type] = data.segments.map(() => false);

this.timestampOffsets_[type] = 0;
this.timestampOffsets_[type] = data.timestampOffset || 0;
}

/** @type {!jasmine.Spy} */
Expand Down Expand Up @@ -416,7 +416,8 @@ shaka.test.FakeMediaSourceEngine = class {
* initSegments: !Array.<!BufferSource>,
* segments: !Array.<!BufferSource>,
* segmentStartTimes: !Array.<number>,
* segmentDuration: number
* segmentDuration: number,
* timestampOffset: number,
* }}
*
* @property {!Array.<!BufferSource>} initSegments
Expand All @@ -429,5 +430,8 @@ shaka.test.FakeMediaSourceEngine = class {
* baseMediaDecodeTime (or equivalent) values.
* @property {number} segmentDuration
* The duration of each media segment.
* @property {number=} timestampOffset
* The offset to the segment start times that is added to create
* the media timeline.
*/
shaka.test.FakeMediaSourceEngine.SegmentData;
7 changes: 5 additions & 2 deletions test/test/util/streaming_engine_util.js
Original file line number Diff line number Diff line change
Expand Up @@ -162,11 +162,13 @@ shaka.test.StreamingEngineUtil = class {
* type of segment.
* @param {!Object.<string, !Array.<number>>} initSegmentRanges The byte
* ranges for each type of init segment.
* @param {!Object.<string,number>=} timestampOffsets The timestamp offset
* for each type of segment
* @return {shaka.extern.Manifest}
*/
static createManifest(
presentationTimeline, periodStartTimes, presentationDuration,
segmentDurations, initSegmentRanges) {
segmentDurations, initSegmentRanges, timestampOffsets) {
const Util = shaka.test.Util;

/**
Expand Down Expand Up @@ -263,6 +265,7 @@ shaka.test.StreamingEngineUtil = class {
const d = segmentDurations[type];
const getUris = () => [periodIndex + '_' + type + '_' + position];
const periodStart = periodStartTimes[periodIndex];
const timestampOffset = (timestampOffsets && timestampOffsets[type]) || 0;
const appendWindowStart = periodStartTimes[periodIndex];
const appendWindowEnd = periodIndex == periodStartTimes.length - 1?
presentationDuration : periodStartTimes[periodIndex + 1];
Expand All @@ -274,7 +277,7 @@ shaka.test.StreamingEngineUtil = class {
/* startByte= */ 0,
/* endByte= */ null,
initSegmentReference,
/* timestampOffset= */ 0,
timestampOffset,
appendWindowStart,
appendWindowEnd);
};
Expand Down

0 comments on commit fc3afeb

Please sign in to comment.