Skip to content
3 changes: 1 addition & 2 deletions src/dash/DashAdapter.js
Original file line number Diff line number Diff line change
Expand Up @@ -496,7 +496,7 @@ function DashAdapter() {

const duration = eventBox.event_duration / timescale;
const id = eventBox.id;
const messageData = eventBox.message_data;
const messageData = schemeIdUri === constants.MPD_CALLBACK.SCHEME & eventBox.value ? eventBox.value : eventBox.message_data;

event.eventStream = eventStream;
event.eventStream.value = value;
Expand All @@ -511,7 +511,6 @@ function DashAdapter() {
} else {
event.parsedMessageData = (messageData instanceof Uint8Array) ? utf8ArrayToStr(messageData) : null;
}

return event;
} catch (e) {
return null;
Expand Down
4 changes: 4 additions & 0 deletions src/streaming/constants/Constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -343,5 +343,9 @@ export default {
DTSC: 'dtsc',
AVC: 'avc',
HEVC: 'hevc'
},
MPD_CALLBACK: {
SCHEME: 'urn:mpeg:dash:event:callback:2015',
VALUE: 1,
}
}
9 changes: 3 additions & 6 deletions src/streaming/controllers/EventController.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,15 +34,13 @@ import Debug from '../../core/Debug.js';
import EventBus from '../../core/EventBus.js';
import MediaPlayerEvents from '../../streaming/MediaPlayerEvents.js';
import XHRLoader from '../net/XHRLoader.js';
import Constants from '../constants/Constants.js';

function EventController() {

const MPD_RELOAD_SCHEME = 'urn:mpeg:dash:event:2012';
const MPD_RELOAD_VALUE = 1;

const MPD_CALLBACK_SCHEME = 'urn:mpeg:dash:event:callback:2015';
const MPD_CALLBACK_VALUE = 1;

const REMAINING_EVENTS_THRESHOLD = 300;

const EVENT_HANDLED_STATES = {
Expand Down Expand Up @@ -154,7 +152,6 @@ function EventController() {
logger.error(e);
}
}

/**
* Iterate over a list of events and trigger the ones for which the presentation time is within the current timing interval
* @param {object} events
Expand Down Expand Up @@ -485,7 +482,7 @@ function EventController() {
logger.debug(`Starting manifest refresh event ${eventId} at ${currentVideoTime}`);
_refreshManifest();
}
} else if (event.eventStream.schemeIdUri === MPD_CALLBACK_SCHEME && event.eventStream.value == MPD_CALLBACK_VALUE) {
} else if (event.eventStream.schemeIdUri === Constants.MPD_CALLBACK.SCHEME && event.eventStream.value == Constants.MPD_CALLBACK.VALUE) {
logger.debug(`Starting callback event ${eventId} at ${currentVideoTime}`);
_sendCallbackRequest(event);
} else {
Expand Down Expand Up @@ -544,7 +541,7 @@ function EventController() {
*/
function _sendCallbackRequest(event) {
try {
const url = event.parsedMessageData ?? event.value;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why should we remove this?

I think that this is needed for inlineEvents. When an event is inline, it doesn't have the parsedMessageData, and the URL value is in the event.value.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On src/dash/DashAdapter.js line 499 we changed the value of messageData (which then is used to get parsedMessageData) to fix a bug where message_data was undefined, because value was received instead. In order to prevent errors, the logic to define which value to use (message_data or value) was moved to the Dash Adapter, so that decision is made before reaching src/streaming/controllers/EventController.js line 547 in this case. At this current point parsedMessageData is either message_data or value, depending the workflow.

const url = event.parsedMessageData;
if (!url) {
throw new Error('callback request URL is missing or invalid.');
}
Expand Down
14 changes: 14 additions & 0 deletions test/unit/test/dash/dash.DashAdapter.js
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,20 @@ describe('DashAdapter', function () {
expect(event).to.be.an('object');
});

it('should return an event with a valid parsedMessageData', function () {
const representation = { presentationTimeOffset: 0, adaptation: { period: { start: 0 } } };
const messageData = new Uint8Array([10, 20, 30]);
const eventBox = {
scheme_id_uri: 'id',
value: 'value',
message_data: messageData,
}

const event = dashAdapter.getEvent( eventBox, { 'id/value': {} }, 0, representation);
expect(event.parsedMessageData).to.be.a('string').and.not.empty;
expect(event.parsedMessageData).to.not.be.undefined;
});

it('should calculate correct start time for a version 0 event without PTO', function () {
const representation = { adaptation: { period: { start: 10 } } };
const eventBox = { scheme_id_uri: 'id', value: 'value', presentation_time_delta: 12, version: 0 };
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@ import ManifestUpdaterMock from '../../mocks/ManifestUpdaterMock.js';
import Settings from '../../../../src/core/Settings.js';

import {expect} from 'chai';
import sinon from 'sinon';
const context = {};
const eventBus = EventBus(context).getInstance();

describe('EventController', function () {
let eventController;

let manifestUpdaterMock = new ManifestUpdaterMock();
let playbackControllerMock = new PlaybackControllerMock();
const settings = Settings(context).getInstance();
Expand Down Expand Up @@ -575,5 +575,52 @@ describe('EventController', function () {

eventBus.off(MediaPlayerEvents.MANIFEST_VALIDITY_CHANGED, manifestValidityExpiredHandler, this);
});

it('should fire callback event', async function () {
const periodId = 'periodId';
let events = [{
eventStream: {
timescale: 3,
schemeIdUri: 'urn:mpeg:dash:event:callback:2015',
period: {
id: periodId
},
value: 1,
},
id: 'event0',
calculatedPresentationTime: 0,
duration: 5,
value: 'https://example.com/api',
triggeredReceivedEvent: true
}];

const xhrStub = sinon.useFakeXMLHttpRequest();
let requests = [];

xhrStub.onCreate = function (xhr) {
requests.push(xhr);
setTimeout(() => {
xhr.respond(200, { 'Content-Type': 'application/json' }, JSON.stringify({ success: true }));
});
};

eventController.addInbandEvents(events, periodId);

await new Promise(resolve => {
const checkRequest = () => {
if (requests.some(req => req.url === events[0].value)) {
resolve();
} else {
setImmediate(checkRequest);
}
};
checkRequest();
});

expect(requests.some(req => req.url === events[0].value)).to.be.true;

xhrStub.restore();
});

});
});
65 changes: 64 additions & 1 deletion test/unit/test/streaming/streaming.net.XHRLoader.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
import XHRLoader from '../../../../src/streaming/net/XHRLoader.js';

import ExtUrlQueryInfoController from '../../../../src/streaming/controllers/ExtUrlQueryInfoController.js';
import {expect} from 'chai';
import sinon from 'sinon';

const context = {};

let xhrLoader;
let extUrlQueryInfoController;


describe('XHRLoader', function () {

before(() => {
extUrlQueryInfoController = ExtUrlQueryInfoController(context).getInstance();
});

beforeEach(function () {
window.XMLHttpRequest = sinon.useFakeXMLHttpRequest();

Expand Down Expand Up @@ -111,4 +117,61 @@ describe('XHRLoader', function () {
xhrLoader.load(request, {});
expect(xhrLoader.getXhr().timeout).to.be.equal(100);
});

it('should add query parameters to callback request', function () {
const manifest = {
url: 'http://manifesturl.com/Manifest.mpd?urlParam1=urlValue1',
Period : [{
AdaptationSet: [
{
Representation: [{},{}]
},
{
Representation: [{},{}]
},
],
}],
SupplementalProperty: [{
schemeIdUri: 'urn:mpeg:dash:urlparam:2016',
ExtUrlQueryInfo: {
tagName: 'UrlQueryInfo',
queryTemplate: '$querypart$',
useMPDUrlQuery: 'true',
queryString: 'callbackParam=callbackParamValue',
includeInRequests: 'callback',
}
}],
};
extUrlQueryInfoController.createFinalQueryStrings(manifest);

const httpRequest = {
url: 'https://example.com/api',
type: 'callback',
representation: {
index: 0,
adaptation: {
index: 0,
period: {
index: 0
}
}
},
customData: {
periodIndex: 0
}
};

const xhrStub = sinon.useFakeXMLHttpRequest();
xhrStub.onCreate = function (xhr) {
setTimeout(() => {
xhr.respond(200, { 'Content-Type': 'application/json' }, JSON.stringify({ success: true }));
});
};

xhrLoader = XHRLoader(context).create({});
xhrLoader.load(httpRequest, {});

expect(httpRequest.url).to.include('?');
expect(httpRequest.url).to.match(/callbackParam=callbackParamValue&urlParam1=urlValue1/);
});
});
Loading