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;
Copy link
Copy Markdown

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
Copy Markdown
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