diff --git a/samples/overlays/lib/img/dashjs-logo.png b/samples/overlays/lib/img/dashjs-logo.png
new file mode 100644
index 0000000000..f208775e85
Binary files /dev/null and b/samples/overlays/lib/img/dashjs-logo.png differ
diff --git a/samples/overlays/overlays.html b/samples/overlays/overlays.html
new file mode 100644
index 0000000000..d3e16ed96e
--- /dev/null
+++ b/samples/overlays/overlays.html
@@ -0,0 +1,84 @@
+
+
+
+ Dash.js Rocks
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/dash/constants/DashConstants.js b/src/dash/constants/DashConstants.js
index 347e33f2c3..95d9cbbb42 100644
--- a/src/dash/constants/DashConstants.js
+++ b/src/dash/constants/DashConstants.js
@@ -122,6 +122,7 @@ export default {
MPD: 'MPD',
ORIGINAL_MPD_ID: 'mpdId',
ORIGINAL_PUBLISH_TIME: 'originalPublishTime',
+ OVERLAY: 'OverlayEvent',
PATCH_LOCATION: 'PatchLocation',
PERIOD: 'Period',
PRESENTATION_TIME: 'presentationTime',
diff --git a/src/dash/models/DashManifestModel.js b/src/dash/models/DashManifestModel.js
index e67615f5ef..63233e7c75 100644
--- a/src/dash/models/DashManifestModel.js
+++ b/src/dash/models/DashManifestModel.js
@@ -1061,6 +1061,12 @@ function DashManifestModel() {
} else {
event.id = null;
}
+ if (currentMpdEvent.hasOwnProperty(DashConstants.OVERLAY)) {
+ event.overlay = currentMpdEvent.OverlayEvent;
+ if (event.overlay.earliestResolutionTime) {
+ event.calculatedPresentationTime -= event.overlay.earliestResolutionTime / eventStream.timescale;
+ }
+ }
if (currentMpdEvent.Signal && currentMpdEvent.Signal.Binary) {
// toString is used to manage both regular and namespaced tags
diff --git a/src/streaming/MediaPlayer.js b/src/streaming/MediaPlayer.js
index e212ce0cc6..38047920a1 100644
--- a/src/streaming/MediaPlayer.js
+++ b/src/streaming/MediaPlayer.js
@@ -39,6 +39,7 @@ import CatchupController from './controllers/CatchupController.js';
import ServiceDescriptionController from '../dash/controllers/ServiceDescriptionController.js';
import ContentSteeringController from '../dash/controllers/ContentSteeringController.js';
import MediaController from './controllers/MediaController.js';
+import OverlayController from './controllers/OverlayController.js';
import BaseURLController from './controllers/BaseURLController.js';
import ManifestLoader from './ManifestLoader.js';
import ErrorHandler from './utils/ErrorHandler.js';
@@ -144,6 +145,7 @@ function MediaPlayer() {
schemeLoaderFactory,
timelineConverter,
mediaController,
+ overlayController,
protectionController,
metricsReportingController,
mssHandler,
@@ -257,6 +259,9 @@ function MediaPlayer() {
if (config.mediaController) {
mediaController = config.mediaController;
}
+ if (config.overlayController) {
+ overlayController = config.overlayController;
+ }
if (config.settings) {
settings = config.settings;
}
@@ -328,6 +333,10 @@ function MediaPlayer() {
mediaController = MediaController(context).getInstance();
}
+ if (!overlayController) {
+ overlayController = OverlayController(context).getInstance();
+ }
+
if (!streamController) {
streamController = StreamController(context).getInstance();
}
@@ -413,6 +422,10 @@ function MediaPlayer() {
videoModel
});
+ overlayController.setConfig({
+ videoModel
+ });
+
mediaPlayerModel.setConfig({
playbackController,
serviceDescriptionController
@@ -472,6 +485,9 @@ function MediaPlayer() {
if (customParametersModel) {
customParametersModel.reset();
}
+ if (overlayController) {
+ overlayController.reset()
+ }
settings.reset();
@@ -1468,6 +1484,16 @@ function MediaPlayer() {
return videoModel ? videoModel.getTTMLRenderingDiv() : null;
}
+ /**
+ * Returns instance of Div that was attached by calling attachOverlayRenderingDiv()
+ * @returns {Object}
+ * @memberof module:MediaPlayer
+ * @instance
+ */
+ function getOverlayRenderingDiv() {
+ return videoModel ? videoModel.getOverlayRenderingDiv() : null;
+ }
+
/**
* Use this method to attach an HTML5 div for dash.js to render rich TTML subtitles.
*
@@ -1490,6 +1516,18 @@ function MediaPlayer() {
videoModel.setVttRenderingDiv(div);
}
+ function attachOverlayRenderingDiv(overlayDiv) {
+ const videoElement = videoModel.getElement();
+ if (!videoElement) {
+ throw ELEMENT_NOT_ATTACHED_ERROR;
+ }
+
+ overlayController.configureVideoElementForOverlay();
+ videoModel.setOverlayRenderingDiv(overlayDiv);
+
+ overlayController.setupOverlayEvents();
+ }
+
/*
---------------------------------------------------------------------------
@@ -2088,6 +2126,12 @@ function MediaPlayer() {
_resetPlaybackControllers();
}
+ const overlayDiv = videoModel.getOverlayRenderingDiv()
+
+ if (overlayDiv) {
+ overlayController.reset();
+ }
+
if (isReady()) {
_initializePlayback(providedStartTime);
}
@@ -2740,6 +2784,7 @@ function MediaPlayer() {
attachProtectionController,
attachSource,
attachTTMLRenderingDiv,
+ attachOverlayRenderingDiv,
attachView,
attachVttRenderingDiv,
clearDefaultUTCTimingSources,
@@ -2778,6 +2823,7 @@ function MediaPlayer() {
getSource,
getStreamsFromManifest,
getTTMLRenderingDiv,
+ getOverlayRenderingDiv,
getTargetLiveDelay,
getTracksFor,
getTracksForTypeFromManifest,
diff --git a/src/streaming/constants/Constants.js b/src/streaming/constants/Constants.js
index 5ba4ba19e9..f7c99e97b8 100644
--- a/src/streaming/constants/Constants.js
+++ b/src/streaming/constants/Constants.js
@@ -341,5 +341,14 @@ export default {
DTSC: 'dtsc',
AVC: 'avc',
HEVC: 'hevc'
+ },
+
+ OVERLAY: {
+ SCHEME_ID: 'urn:scte:dash:scte214-events',
+ START_MODE: 'start',
+ STOP_MODE: 'stop',
+ EXTEND_MODE: 'extend',
+ VIDEO_MIMETYPE: 'video/mp4',
+ IFRMAE_MIMETYPE: 'text/html'
}
}
diff --git a/src/streaming/controllers/OverlayController.js b/src/streaming/controllers/OverlayController.js
new file mode 100644
index 0000000000..f922dbfb99
--- /dev/null
+++ b/src/streaming/controllers/OverlayController.js
@@ -0,0 +1,326 @@
+/**
+ * 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.
+ */
+import Constants from '../constants/Constants.js';
+import EventBus from './../../core/EventBus.js';
+import FactoryMaker from '../../core/FactoryMaker.js';
+import PlaybackController from './PlaybackController.js';
+import Utils from '../../core/Utils.js';
+
+function OverlayController() {
+
+ const context = this.context;
+ const eventBus = EventBus(context).getInstance();
+ const playbackController = PlaybackController(context).getInstance();
+
+ let instance,
+ videoModel,
+ schedulerInitialized,
+ resizeObserver,
+ overlayList = [];
+
+ function setConfig(config) {
+ if (!config) {
+ return;
+ }
+
+ if (config.videoModel) {
+ videoModel = config.videoModel;
+ }
+ }
+
+ function configureVideoElementForOverlay() {
+ const videoElement = videoModel.getElement();
+ videoElement.style.width = '100%';
+ videoElement.style.height = '100%';
+ videoElement.style.display = 'block';
+ videoElement.style.transform = 'scale(1)';
+
+ const parent = videoElement.parentElement;
+ parent.style.position = 'relative';
+ parent.style.overflow = 'hidden';
+ }
+
+ function setupOverlayEvents() {
+ eventBus.on(Constants.OVERLAY.SCHEME_ID, _handleOverlayEvent);
+ }
+
+ function reset() {
+ _stopAllOverlayEvents();
+
+ overlayList = [];
+
+ clearInterval(schedulerInitialized);
+ schedulerInitialized = false;
+
+ if (resizeObserver) {
+ resizeObserver.disconnect();
+ }
+
+ _removeOverlayStyles()
+ }
+
+ function _stopAllOverlayEvents () {
+ overlayList.forEach((overlay) => {
+ _stopOverlayEvent(overlay.eventId);
+ })
+ }
+
+ function _removeOverlayStyles() {
+ const overlayDiv = videoModel.getOverlayRenderingDiv();
+ if (!overlayDiv) {
+ return
+ }
+
+ videoModel.setOverlayRenderingDiv(overlayDiv);
+ }
+
+ function _initializeScheduler() {
+ if (!schedulerInitialized) {
+ schedulerInitialized = setInterval(_handleScheduleInerval, 100);
+ }
+ }
+
+ function _stopScheduler() {
+ clearInterval(schedulerInitialized);
+ schedulerInitialized = false;
+ }
+
+ function _handleScheduleInerval() {
+ if (overlayList.length) {
+ _startScheduledOverlay();
+ _stopScheduledOverlay();
+ }
+ }
+
+ function _startScheduledOverlay() {
+ const currentTime = playbackController.getTime();
+ overlayList.forEach((scheduledOverlay) => {
+ const { eventId, duration, presentationTime, overlay, overlayElement } = scheduledOverlay;
+ if (!scheduledOverlay.started && _canSetOverlayElement(presentationTime, currentTime, duration)) {
+ scheduledOverlay.started = true;
+ _stylizeOverlayContainter(overlay);
+ overlayElement.loop = overlay.loop === 'true';
+ videoModel.setOverlayElement(overlayElement, eventId);
+ }
+ });
+ }
+
+ function _stopScheduledOverlay() {
+ const currentTime = playbackController.getTime();
+ overlayList.forEach((scheduledOverlay) => {
+ const { eventId, duration, presentationTime } = scheduledOverlay;
+ if (duration && _overlayEventIsFinished(presentationTime, duration, currentTime)) {
+ _stopOverlayEvent(eventId);
+ }
+ });
+ if (!overlayList.length) {
+ _stopScheduler();
+ }
+ }
+
+ function _handleOverlayEvent(e) {
+ const { event } = e;
+ if (!event || !event.overlay) {
+ return
+ }
+ const overlayMode = event.overlay.mode ?? Constants.OVERLAY.START_MODE;
+ switch (overlayMode) {
+ case Constants.OVERLAY.START_MODE:
+ _overlayStartMode(event);
+ break;
+ case Constants.OVERLAY.EXTEND_MODE:
+ _overlayExtendMode(event);
+ break;
+ case Constants.OVERLAY.STOP_MODE:
+ _overlayStopMode(event);
+ break;
+ }
+ }
+
+ function _overlayStartMode(event) {
+ let overlayElement
+ if (event.overlay.mimeType === Constants.OVERLAY.VIDEO_MIMETYPE) {
+ overlayElement = _createVideoOverlayElement(event);
+ } else if (event.overlay.mimeType === Constants.OVERLAY.IFRMAE_MIMETYPE) {
+ overlayElement = _createIframeOverlayElement(event);
+ }
+ _adaptOverlayElement(overlayElement, event.overlay.uri);
+
+ const eventId = event.id ?? `${Utils.generateUuid()}`;
+ const presentationTime = event.presentationTime / 1000;
+ overlayList.push({
+ eventId,
+ duration: event.duration,
+ presentationTime,
+ overlay: event.overlay,
+ overlayElement,
+ started: false
+ });
+ _initializeScheduler();
+ }
+
+ function _overlayExtendMode(event) {
+ if (event.duration) {
+ const overlayElement = overlayList.find(element => element.eventId == event.overlay.refId);
+ if (overlayElement) {
+ overlayElement.duration = event.duration;
+ overlayElement.presentationTime = event.presentationTime / 1000;
+ }
+ }
+ }
+
+ function _overlayStopMode(event) {
+ _stopOverlayEvent(event.overlay.refId);
+ }
+
+ function _stopOverlayEvent(refId) {
+ videoModel.removeOverlayElementById(refId);
+ configureVideoElementForOverlay();
+ // Do not filter if we want to reuse the overlays once they end.
+ overlayList = overlayList.filter((element) => element.eventId != refId);
+ }
+
+ function _overlayEventIsFinished(presentationTime, duration, currentTime) {
+ return presentationTime + duration <= currentTime
+ }
+
+ function _canSetOverlayElement(presentationTime, currentTime, duration) {
+ return presentationTime <= currentTime && (!_overlayEventIsFinished(presentationTime, duration, currentTime) || !duration);
+ }
+
+ function _createVideoOverlayElement (event) {
+ const overlayElement = document.createElement('video');
+ overlayElement.preload = 'auto';
+ overlayElement.autoplay = true;
+ _setVideoOverlayEvents(event, overlayElement);
+ return overlayElement;
+ }
+
+ function _setVideoOverlayEvents(event, overlayElement) {
+ eventBus.on(dashjs.MediaPlayer.events.PLAYBACK_PLAYING, function() {
+ overlayElement.play();
+ });
+
+ eventBus.on(dashjs.MediaPlayer.events.PLAYBACK_PAUSED, function() {
+ overlayElement.pause();
+ });
+
+ eventBus.on(dashjs.MediaPlayer.events.PLAYBACK_SEEKING, function() {
+ const presentationTime = event.presentationTime / 1000;
+ const seekTime = videoModel.getElement().currentTime - presentationTime;
+ if (seekTime > presentationTime && (!_overlayEventIsFinished(presentationTime, seekTime, event.duration) || !event.duration)) {
+ overlayElement.currentTime = seekTime;
+ } else if (seekTime < 0) {
+ _stopOverlayEvent(event.id);
+ }
+ });
+ }
+
+ function _createIframeOverlayElement (event) {
+ const overlayElement = document.createElement('iframe');
+ overlayElement.style.border = 'none';
+ eventBus.on(dashjs.MediaPlayer.events.PLAYBACK_SEEKING, function() {
+ const presentationTime = event.presentationTime / 1000;
+ const seekTime = videoModel.getElement().currentTime - presentationTime;
+ if (seekTime < 0) {
+ _stopOverlayEvent(event.id);
+ }
+ });
+ return overlayElement;
+ }
+
+ function _adaptOverlayElement(overlayElement, uri) {
+ overlayElement.src = uri;
+ overlayElement.style.width = '100%';
+ overlayElement.style.height = '100%';
+ }
+
+ function _stylizeOverlayContainter(overlayEvent) {
+ const videoElement = videoModel.getElement();
+ const overlayDiv = videoModel.getOverlayRenderingDiv();
+ const { Viewport, Size, TopLeft, SqueezeCurrent, z } = overlayEvent;
+
+ if (!isNaN(z)) {
+ overlayDiv.style['z-index'] = z;
+ }
+
+ if (SqueezeCurrent && z == -1) {
+ const squeezeCurrent = SqueezeCurrent.percentage;
+ videoElement.style.transition = 'transform';
+ videoElement.style['transform-origin'] = 'top left';
+ videoElement.style.transform = `scale(${squeezeCurrent})`;
+ }
+
+ if (!Viewport || !Viewport?.x || !Viewport?.y ) {
+ return;
+ }
+
+ const { overlaySize, overlayTopLeft } = _calculateOverlayDimensions(Viewport, Size, TopLeft);
+ const resizeFunction = (entries) => {
+ const entry = entries[0];
+ const { width, height } = entry.contentRect;
+ overlayDiv.style.width = `${width * overlaySize.x}px`;
+ overlayDiv.style.height = `${height * overlaySize.y}px`;
+ overlayDiv.style.left = `${width * overlayTopLeft.x}px`;
+ overlayDiv.style.top = `${height * overlayTopLeft.y}px`;
+ };
+ resizeObserver = new ResizeObserver(resizeFunction);
+ resizeObserver.observe(videoElement);
+ }
+
+ function _calculateOverlayDimensions(viewport, size, topLeft) {
+ const overlaySize = {
+ x: size && size.x ? size.x / viewport.x : 1,
+ y: size && size.y ? size.y / viewport.y : 1
+ };
+
+ const overlayTopLeft = {
+ x: topLeft && topLeft.x ? topLeft.x / viewport.x : 0,
+ y: topLeft && topLeft.y ? topLeft.y / viewport.y : 0
+ };
+
+ return { overlaySize, overlayTopLeft };
+ }
+
+ instance = {
+ setConfig,
+ configureVideoElementForOverlay,
+ setupOverlayEvents,
+ reset
+ };
+
+ return instance;
+}
+
+OverlayController.__dashjs_factory_name = 'OverlayController';
+const factory = FactoryMaker.getSingletonFactory(OverlayController);
+FactoryMaker.updateSingletonFactory(OverlayController.__dashjs_factory_name, factory);
+export default factory;
diff --git a/src/streaming/models/VideoModel.js b/src/streaming/models/VideoModel.js
index 0fbc080c95..a754a3a7bc 100644
--- a/src/streaming/models/VideoModel.js
+++ b/src/streaming/models/VideoModel.js
@@ -53,9 +53,12 @@ function VideoModel() {
setCurrentTimeReadyStateFunction,
TTMLRenderingDiv,
vttRenderingDiv,
+ overlayRenderingDiv,
previousPlaybackRate,
timeout;
+ let overlayElements = [];
+
const VIDEO_MODEL_WRONG_ELEMENT_TYPE = 'element is not video or audio DOM type!';
const context = this.context;
@@ -213,6 +216,10 @@ function VideoModel() {
return vttRenderingDiv;
}
+ function getOverlayRenderingDiv() {
+ return overlayRenderingDiv;
+ }
+
function setTTMLRenderingDiv(div) {
TTMLRenderingDiv = div;
// The styling will allow the captions to match the video window size and position.
@@ -228,6 +235,38 @@ function VideoModel() {
vttRenderingDiv = div;
}
+ function setOverlayRenderingDiv(div) {
+ overlayRenderingDiv = div;
+ overlayRenderingDiv.style.position = 'absolute';
+ overlayRenderingDiv.style.width = '100%';
+ overlayRenderingDiv.style.height = '100%';
+ overlayRenderingDiv.style.pointerEvents = 'none';
+ overlayRenderingDiv.style['z-index'] = 'auto';
+ overlayRenderingDiv.style.top = 0;
+ overlayRenderingDiv.style.left = 0;
+ }
+
+ function setOverlayElement(element, id) {
+ overlayRenderingDiv.appendChild(element);
+ overlayElements.push({
+ element,
+ id,
+ });
+ }
+
+ function getOverlayElementById(id) {
+ return overlayElements.find((element) => element.id == id);
+ }
+
+ function removeOverlayElementById(id) {
+ const overlayElement = overlayElements.find((element) => element.id == id);
+ if (!overlayElement) {
+ return;
+ }
+ overlayElement.element.remove();
+ overlayElements = overlayElements.filter((element) => element.id != id);
+ }
+
function setStallState(type, state) {
stallStream(type, state);
}
@@ -495,6 +534,8 @@ function VideoModel() {
getReadyState,
getSource,
getTTMLRenderingDiv,
+ getOverlayElementById,
+ getOverlayRenderingDiv,
getTextTrack,
getTextTracks,
getTime,
@@ -512,6 +553,7 @@ function VideoModel() {
play,
removeChild,
removeEventListener,
+ removeOverlayElementById,
reset,
setCurrentTime,
setDisableRemotePlayback,
@@ -521,6 +563,8 @@ function VideoModel() {
setStallState,
setTTMLRenderingDiv,
setVttRenderingDiv,
+ setOverlayElement,
+ setOverlayRenderingDiv,
waitForReadyState,
};
diff --git a/test/unit/mocks/VideoElementMock.js b/test/unit/mocks/VideoElementMock.js
index 30c2b52e4a..aea58279e5 100644
--- a/test/unit/mocks/VideoElementMock.js
+++ b/test/unit/mocks/VideoElementMock.js
@@ -26,6 +26,10 @@ class VideoElementMock {
this.nodeName = 'VIDEO';
this.videoWidth = 800;
this.videoHeight = 600;
+ this.style = {};
+ this.parentElement = {
+ style: {}
+ };
}
constructor() {
diff --git a/test/unit/mocks/VideoModelMock.js b/test/unit/mocks/VideoModelMock.js
index a1f64c0ec1..f20a8dafdb 100644
--- a/test/unit/mocks/VideoModelMock.js
+++ b/test/unit/mocks/VideoModelMock.js
@@ -14,6 +14,7 @@ class VideoModelMock {
this.height = 600;
this.width = 800;
this.events = {};
+ this.overlayRenderingDiv = document.createElement('div');
}
addEventListener(name, handler) {
@@ -181,7 +182,11 @@ class VideoModelMock {
}
getVttRenderingDiv() {
- return
+ return;
+ }
+
+ getOverlayRenderingDiv() {
+ return;
}
setSource(source) {
@@ -199,6 +204,10 @@ class VideoModelMock {
setPlaybackRate() {
}
+
+ removeOverlayElementById () {
+ return;
+ }
}
export default VideoModelMock;
diff --git a/test/unit/test/streaming/streaming.MediaPlayer.js b/test/unit/test/streaming/streaming.MediaPlayer.js
index 12dacb5b64..45fa8d0369 100644
--- a/test/unit/test/streaming/streaming.MediaPlayer.js
+++ b/test/unit/test/streaming/streaming.MediaPlayer.js
@@ -968,6 +968,21 @@ describe('MediaPlayer', function () {
expect(areEquals).to.be.true;
});
+ it('should be able to attach Overlay renderer div', function () {
+ let overlayRenderer = player.getOverlayRenderingDiv();
+ expect(overlayRenderer).to.be.undefined;
+
+ const myOverlatRenderer = {
+ style: {}
+ };
+
+ player.attachOverlayRenderingDiv(myOverlatRenderer);
+
+ overlayRenderer = player.getOverlayRenderingDiv();
+ const areEquals = objectUtils.areEqual(overlayRenderer, myOverlatRenderer);
+ expect(areEquals).to.be.true;
+ });
+
it('Method attachView should throw an exception when attaching a view which is not VIDEO or AUDIO DOM element', function () {
player.attachView(null);
const myNewView = {
diff --git a/test/unit/test/streaming/streaming.controllers.OverlayController.js b/test/unit/test/streaming/streaming.controllers.OverlayController.js
new file mode 100644
index 0000000000..595d206344
--- /dev/null
+++ b/test/unit/test/streaming/streaming.controllers.OverlayController.js
@@ -0,0 +1,132 @@
+import OverlayController from '../../../../src/streaming/controllers/OverlayController.js';
+import EventBus from '../../../../src/core/EventBus.js';
+import VideoModelMock from '../../mocks/VideoModelMock.js';
+import Constants from '../../../../src/streaming/constants/Constants.js';
+
+import {expect} from 'chai';
+const context = {};
+
+const eventBus = EventBus(context).getInstance();
+
+describe('OverlayController', function () {
+
+ let overlayController,
+ videoModelMock,
+ videoElement,
+ parentElement
+
+ beforeEach(function () {
+ overlayController = OverlayController(context).getInstance();
+ videoModelMock = new VideoModelMock();
+
+ overlayController.setConfig({
+ videoModel: videoModelMock
+ });
+
+ videoElement = videoModelMock.element
+ parentElement = document.createElement('div');
+ parentElement.appendChild(videoElement);
+
+ overlayController.setupOverlayEvents();
+ });
+
+ this.afterEach(function () {
+ overlayController.reset()
+ })
+
+ describe('configure video element for overlay', function () {
+ it('should apply the correct style properties to the video element', function () {
+ overlayController.configureVideoElementForOverlay();
+
+ expect(videoElement.style.width).to.equal('100%');
+ expect(videoElement.style.height).to.equal('100%');
+ expect(videoElement.style.transform).to.equal('scale(1)');
+ });
+
+ it('should apply the correct style properties to the parent element', function () {
+ overlayController.configureVideoElementForOverlay();
+
+ expect(parentElement.style.position).to.equal('relative');
+ expect(parentElement.style.overflow).to.equal('hidden');
+ });
+ });
+
+ describe('setupOverlayEvents', function () {
+ it('should execute without throwing errors', function () {
+ expect(() => overlayController.setupOverlayEvents()).to.not.throw();
+ });
+
+ it('should handle triggering an empty event without errors', function () {
+
+ expect(() => {
+ eventBus.trigger(Constants.OVERLAY.SCHEME_ID, {});
+ }).to.not.throw();
+ });
+
+ it('should handle triggering the event start mode and video mimetype without errors', function () {
+ expect(() => {
+ eventBus.trigger(Constants.OVERLAY.SCHEME_ID, { event: {
+ overlay: {
+ mode: Constants.OVERLAY.START_MODE,
+ mimeType: Constants.OVERLAY.VIDEO_MIMETYPE
+ }
+ }});
+ }).to.not.throw();
+ });
+
+ it('should handle triggering the event start mode and iframe mimetype without errors', function () {
+ expect(() => {
+ eventBus.trigger(Constants.OVERLAY.SCHEME_ID, { event: {
+ overlay: {
+ mode: Constants.OVERLAY.START_MODE,
+ mimeType: Constants.OVERLAY.IFRMAE_MIMETYPE
+ }
+ }});
+ }).to.not.throw();
+ });
+
+ it('should handle triggering the event extend mode without errors', function () {
+ expect(() => {
+ eventBus.trigger(Constants.OVERLAY.SCHEME_ID, { event: {
+ overlay: {
+ mode: Constants.OVERLAY.START_MODE,
+ mimeType: Constants.OVERLAY.VIDEO_MIMETYPE,
+ },
+ duration: 5,
+ presentationTime: 3000,
+ id: '1234'
+ }});
+
+ eventBus.trigger(Constants.OVERLAY.SCHEME_ID, { event: {
+ overlay: {
+ mode: Constants.OVERLAY.EXTEND_MODE,
+ refId: '1234'
+ },
+ duration: 10,
+ presentationTime: 4000
+ }});
+ }).to.not.throw();
+ });
+
+ it('should handle triggering the event stop mode without errors', function () {
+ expect(() => {
+ eventBus.trigger(Constants.OVERLAY.SCHEME_ID, { event: {
+ overlay: {
+ mode: Constants.OVERLAY.START_MODE,
+ mimeType: Constants.OVERLAY.VIDEO_MIMETYPE,
+ },
+ duration: 5,
+ presentationTime: 3000,
+ id: '1234'
+ }});
+
+ eventBus.trigger(Constants.OVERLAY.SCHEME_ID, { event: {
+ overlay: {
+ mode: Constants.OVERLAY.STOP_MODE,
+ refId: '1234'
+ }
+ }});
+ }).to.not.throw();
+ });
+ });
+})
\ No newline at end of file