Skip to content

Commit

Permalink
feat(Ads): Add basic support to DASH OverlayEvent (#7701)
Browse files Browse the repository at this point in the history
The spec is not finalized, but the info can be found at
https://demuxed2024.qualabs.com/home/alex-giladi
  • Loading branch information
avelad authored Dec 3, 2024
1 parent 2953b6f commit b704af0
Show file tree
Hide file tree
Showing 3 changed files with 172 additions and 8 deletions.
25 changes: 17 additions & 8 deletions lib/ads/ad_manager.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ goog.require('shaka.util.Error');
goog.require('shaka.util.FakeEvent');
goog.require('shaka.util.FakeEventTarget');
goog.require('shaka.util.IReleasable');
goog.require('shaka.util.TXml');


/**
Expand Down Expand Up @@ -843,14 +844,22 @@ shaka.ads.AdManager = class extends shaka.util.FakeEventTarget {
if (this.config_ && this.config_.disableDASHInterstitial) {
return;
}
if (region.schemeIdUri != 'urn:mpeg:dash:event:alternativeMPD:2022') {
return;
}
if (!this.interstitialAdManager_) {
this.initInterstitial(/* adContainer= */ null, basePlayer, baseVideo);
}
if (this.interstitialAdManager_) {
this.interstitialAdManager_.addRegion(region);
if (region.schemeIdUri == 'urn:mpeg:dash:event:alternativeMPD:2022') {
if (!this.interstitialAdManager_) {
this.initInterstitial(/* adContainer= */ null, basePlayer, baseVideo);
}
if (this.interstitialAdManager_) {
this.interstitialAdManager_.addRegion(region);
}
} else if (region.schemeIdUri == 'urn:mpeg:dash:event:2012' &&
region.eventNode &&
shaka.util.TXml.findChild(region.eventNode, 'OverlayEvent')) {
if (!this.interstitialAdManager_) {
this.initInterstitial(/* adContainer= */ null, basePlayer, baseVideo);
}
if (this.interstitialAdManager_) {
this.interstitialAdManager_.addOverlayRegion(region);
}
}
}

Expand Down
93 changes: 93 additions & 0 deletions lib/ads/interstitial_ad_manager.js
Original file line number Diff line number Diff line change
Expand Up @@ -359,6 +359,99 @@ shaka.ads.InterstitialAdManager = class {
this.addInterstitials([interstitial]);
}

/**
* @param {shaka.extern.TimelineRegionInfo} region
*/
addOverlayRegion(region) {
const TXml = shaka.util.TXml;

goog.asserts.assert(region.eventNode, 'Need a region eventNode');
const overlayEvent = TXml.findChild(region.eventNode, 'OverlayEvent');
const uri = overlayEvent.attributes['uri'];
const mimeType = overlayEvent.attributes['mimeType'];
const loop = overlayEvent.attributes['loop'] == 'true';
if (!uri || !mimeType) {
shaka.log.warning('Unsupported OverlayEvent', region);
return;
}

const viewport = TXml.findChild(overlayEvent, 'Viewport');
const topLeft = TXml.findChild(overlayEvent, 'TopLeft');
const size = TXml.findChild(overlayEvent, 'Size');
if (!viewport || !topLeft || !size) {
shaka.log.warning('Unsupported OverlayEvent', region);
return;
}
const viewportX = TXml.parseAttr(viewport, 'x', TXml.parseInt);
if (viewportX == null) {
shaka.log.warning('Unsupported OverlayEvent', region);
return;
}
const viewportY = TXml.parseAttr(viewport, 'y', TXml.parseInt);
if (viewportY == null) {
shaka.log.warning('Unsupported OverlayEvent', region);
return;
}
const topLeftX = TXml.parseAttr(topLeft, 'x', TXml.parseInt);
if (topLeftX == null) {
shaka.log.warning('Unsupported OverlayEvent', region);
return;
}
const topLeftY = TXml.parseAttr(topLeft, 'y', TXml.parseInt);
if (topLeftY == null) {
shaka.log.warning('Unsupported OverlayEvent', region);
return;
}
const sizeX = TXml.parseAttr(size, 'x', TXml.parseInt);
if (sizeX == null) {
shaka.log.warning('Unsupported OverlayEvent', region);
return;
}
const sizeY = TXml.parseAttr(size, 'y', TXml.parseInt);
if (sizeY == null) {
shaka.log.warning('Unsupported OverlayEvent', region);
return;
}

/** @type {!shaka.extern.AdInterstitialOverlay} */
const overlay = {
viewport: {
x: viewportX,
y: viewportY,
},
topLeft: {
x: topLeftX,
y: topLeftY,
},
size: {
x: sizeX,
y: sizeY,
},
};

/** @type {!shaka.extern.AdInterstitial} */
const interstitial = {
id: region.id,
startTime: region.startTime,
endTime: region.endTime,
uri,
mimeType,
isSkippable: false,
skipOffset: null,
skipFor: null,
canJump: true,
resumeOffset: null,
playoutLimit: null,
once: false,
pre: false,
post: false,
timelineRange: true,
loop,
overlay,
};
this.addInterstitials([interstitial]);
}

/**
* @param {string} url
* @return {!Promise}
Expand Down
62 changes: 62 additions & 0 deletions test/ads/interstitial_ad_manager_unit.js
Original file line number Diff line number Diff line change
Expand Up @@ -818,6 +818,68 @@ describe('Interstitial Ad manager', () => {

expect(onEventSpy).not.toHaveBeenCalled();
});

it('supports overlay events', async () => {
const eventString = [
'<Event duration="1" id="OVERLAY" presentationTime="0">',
'<OverlayEvent mimeType="application/dash+xml" uri="test.mpd">',
'<Viewport x="1920" y="1080"/>',
'<TopLeft x="0" y="720"/>',
'<Size x="480" y="360"/>',
'</OverlayEvent>',
'</Event>',
].join('');
const eventNode = TXml.parseXmlString(eventString);
goog.asserts.assert(eventNode, 'Should have a event node!');
const region = {
startTime: 0,
endTime: 1,
id: 'OVERLAY',
schemeIdUri: 'urn:mpeg:dash:event:2012',
eventNode,
eventElement: TXml.txmlNodeToDomElement(eventNode),
value: '',
};
await interstitialAdManager.addOverlayRegion(region);

expect(onEventSpy).not.toHaveBeenCalled();

const interstitials = interstitialAdManager.getInterstitials();
expect(interstitials.length).toBe(1);
const expectedInterstitial = {
id: 'OVERLAY',
startTime: 0,
endTime: 1,
uri: 'test.mpd',
mimeType: 'application/dash+xml',
isSkippable: false,
skipOffset: null,
skipFor: null,
canJump: true,
resumeOffset: null,
playoutLimit: null,
once: false,
pre: false,
post: false,
timelineRange: true,
loop: false,
overlay: {
viewport: {
x: 1920,
y: 1080,
},
topLeft: {
x: 0,
y: 720,
},
size: {
x: 480,
y: 360,
},
},
};
expect(interstitials[0]).toEqual(expectedInterstitial);
});
});

describe('custom', () => {
Expand Down

0 comments on commit b704af0

Please sign in to comment.