Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
8eb7f7d
refactor: remove redundant config setting for alternative video element
sebastianpiq Sep 18, 2025
991f292
feat: add setAlternativeVideoElement unit test
sebastianpiq Sep 22, 2025
1e0048e
fix debug import in the MediaManager
sebastianpiq Sep 22, 2025
40dd8f9
refactor: streamline MediaManager instance creation in tests
sebastianpiq Sep 22, 2025
c270d1c
feat: add context variable to MediaManager constructor
sebastianpiq Sep 22, 2025
dd2cf07
fix: ensure MediaManager is initialized in beforeEach
sebastianpiq Sep 22, 2025
1884d4c
test: add getAlternativePlayer test for the MediaManager and fix swit…
sebastianpiq Sep 22, 2025
1fe5d55
fix: update expectation in getAlternativePlayer test to check for und…
sebastianpiq Sep 22, 2025
079e650
test: remove 'only' from getAlternativePlayer tests to ensure all tes…
sebastianpiq Sep 22, 2025
ed82fa9
refactor: clean up comments in getAlternativePlayer test for clarity
sebastianpiq Sep 22, 2025
da35d11
test: add unit test for switchToAlternativeContent and prebufferAlter…
sebastianpiq Sep 22, 2025
686e271
test: implement switch back to main content unit test
sebastianpiq Sep 22, 2025
8fdbd8d
test: Improve MediaManager unit test and add switchBackToMain contet …
sebastianpiq Sep 22, 2025
e8256ce
Merge pull request #136 from qualabs/media-manager-unit-test/set-alte…
cotid-qualabs Sep 23, 2025
90541aa
Merge branch 'alternative-media-presentations/media-manager-unit-test…
sebastianpiq Sep 23, 2025
04f67e0
Revert "Merge branch 'alternative-media-presentations/media-manager-u…
sebastianpiq Sep 23, 2025
9de7d08
Merge pull request #137 from qualabs/media-manager-unit-test/prebuffe…
cotid-qualabs Sep 23, 2025
34ec73d
Merge branch 'alternative-media-presentations/media-manager-unit-test…
sebastianpiq Sep 23, 2025
caac29e
Merge pull request #138 from qualabs/media-manager-unit-test/switch-b…
cotid-qualabs Sep 23, 2025
d093ed8
Merge branch 'feature/alternative-media-presentations' into alternati…
sebastianpiq Sep 23, 2025
80ff9ee
Fix formatting of alternative video element in index.html
sebastianpiq Sep 23, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 6 additions & 5 deletions samples/alternative/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@
width: 640px;
height: 360px;
}
#alternativeVideo {

#alternative-video-element {
display: none;
}
</style>
Expand All @@ -31,8 +32,8 @@
// url = 'http://localhost:3000/samples/alternative/manifest-replace-clip.mpd';
// url = 'http://localhost:3000/samples/alternative/manifest-replace-start-with-offset.mpd';

video = document.querySelector('#mainVideo');

video = document.getElementById('video-element');
alternativeVideo = document.getElementById('alternative-video-element');
player = dashjs.MediaPlayer().create();
alternativeVideo = document.querySelector('#alternativeVideo');
player.initialize(video, url, false);
Expand All @@ -57,8 +58,8 @@ <h3>Alternative Media Presentations</h3>
</div>
</div>
<div class="col-md-8">
<video id="mainVideo" controls="true"></video>
<video id="alternativeVideo" controls="true"></video>
<video id="video-element" controls="true"></video>
<video id="alternative-video-element" controls="true"></video>
</div>
</div>
<div class="row">
Expand Down
2 changes: 1 addition & 1 deletion samples/alternative/manifest-insert.mpd
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<BaseURL>https://dash.akamaized.net/akamai/bbb_30fps/</BaseURL>
<Period id="0" start="PT0.0S">
<EventStream xmlns="" schemeIdUri="urn:mpeg:dash:event:alternativeMPD:insert:2025" timescale="1000">
<Event presentationTime="5000" duration="9000" id="1">
<Event presentationTime="5000" duration="3000" id="1">
<InsertPresentation url="https://dash.akamaized.net/dashif/ad-insertion-testcase1/batch2/real/b/ad-insertion-testcase1.mpd" earliestResolutionTimeOffset="5000" maxDuration="7000"/>
</Event>
</EventStream>
Expand Down
53 changes: 23 additions & 30 deletions src/streaming/MediaManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import Events from '../core/events/Events.js';
import MediaPlayerEvents from './MediaPlayerEvents.js';
import MediaPlayer from './MediaPlayer.js';
import FactoryMaker from '../core/FactoryMaker.js';
import Debug from '../core/Debug.js';

function MediaManager() {
let instance,
Expand All @@ -44,9 +45,12 @@ function MediaManager() {
altVideoElement,
alternativeContext,
logger,
debug,
prebufferedPlayers = new Map(),
prebufferCleanupInterval = null;

const context = this.context;

function setConfig(config) {
if (!config) {
return;
Expand All @@ -56,8 +60,8 @@ function MediaManager() {
videoModel = config.videoModel;
}

if (config.logger) {
logger = config.logger;
if (config.debug) {
debug = config.debug;
}

if (!!config.playbackController && !playbackController) {
Expand All @@ -74,6 +78,12 @@ function MediaManager() {
}

function initialize() {
if (!debug) {
debug = Debug(context).getInstance();
}

logger = debug.getLogger(instance);

if (!fullscreenDiv) {
fullscreenDiv = document.createElement('div');
fullscreenDiv.id = 'fullscreenDiv';
Expand All @@ -98,7 +108,7 @@ function MediaManager() {
function prebufferAlternativeContent(playerId, alternativeMpdUrl) {
try {
if (prebufferedPlayers.has(playerId)) {
return; // Already prebuffered
return;
}

logger.info(`Starting prebuffering for player ${playerId}`);
Expand Down Expand Up @@ -140,10 +150,6 @@ function MediaManager() {
prebufferedPlayer.player.off(Events.ERROR);
prebufferedPlayer.player.reset();

if (prebufferedPlayer.videoElement && prebufferedPlayer.videoElement?.parentNode) {
prebufferedPlayer.videoElement.parentNode?.removeChild(prebufferedPlayer.videoElement);
}

prebufferedPlayers.delete(playerId);
}
logger.debug(`Cleaned up prebuffered content for ${playerId}`);
Expand Down Expand Up @@ -187,35 +193,17 @@ function MediaManager() {
const prebufferedContent = prebufferedPlayers.get(playerId);

if (prebufferedContent) {
// Use prebuffered content
logger.info(`Using prebuffered content for player ${playerId}`);

// Move prebuffered video element to visible area
altPlayer = prebufferedContent.player;

// Remove from prebuffered storage
prebufferedPlayers.delete(playerId);

// Setup video element for display
altVideoElement.style.display = 'none';
altVideoElement.controls = !hideAlternativePlayerControls;

if (altVideoElement.parentNode !== fullscreenDiv) {
fullscreenDiv.appendChild(altVideoElement);
}

// Insert into DOM if needed
const videoElement = videoModel.getElement();
const parentNode = videoElement && videoElement.parentNode;
if (parentNode && !parentNode.contains(altVideoElement)) {
parentNode.insertBefore(altVideoElement, videoElement.nextSibling);
}

altPlayer.attachView(altVideoElement);
} else {
initializeAlternativePlayer(alternativeMpdUrl);
}

if (altPlayer && altVideoElement) {
altPlayer.attachView(altVideoElement);
}

videoModel.pause();
logger.debug('Main video paused');

Expand All @@ -228,7 +216,7 @@ function MediaManager() {
}

altPlayer.play();
logger.info('Alternative content playback started');
logger.info(`Alternative content playback started for player ${playerId}`);

isSwitching = false;
}
Expand All @@ -239,6 +227,11 @@ function MediaManager() {
logger.debug('Switch already in progress - ignoring request');
return
};

if (!altPlayer) {
logger.warn('No alternative player to switch back from');
return;
}

logger.info('Switching back to main content');
isSwitching = true;
Expand Down
1 change: 0 additions & 1 deletion src/streaming/MediaPlayer.js
Original file line number Diff line number Diff line change
Expand Up @@ -610,7 +610,6 @@ function MediaPlayer() {
}
if (source) {
const playbackTime = time ? time : providedStartTime;
console.log(playbackTime)
_initializePlayback(playbackTime);
} else {
throw SOURCE_NOT_ATTACHED_ERROR;
Expand Down
12 changes: 4 additions & 8 deletions src/streaming/controllers/AlternativeMediaController.js
Original file line number Diff line number Diff line change
Expand Up @@ -76,13 +76,6 @@ function AlternativeMediaController() {
hideAlternativePlayerControls = false,
alternativeVideoElement = null;

function setup() {
if (!debug) {
debug = Debug(context).getInstance();
}
logger = debug.getLogger(instance);
}

function setConfig(config) {
if (!config) {
return;
Expand Down Expand Up @@ -114,7 +107,10 @@ function AlternativeMediaController() {
}

function initialize() {
setup();
if (!debug) {
debug = Debug(context).getInstance();
}
logger = debug.getLogger(instance);

// Initialize the media manager if not already provided via config
if (!mediaManager) {
Expand Down
5 changes: 5 additions & 0 deletions test/unit/mocks/VideoModelMock.js
Original file line number Diff line number Diff line change
Expand Up @@ -111,9 +111,14 @@ class VideoModelMock {
}

pause() {
this.isplaying = false;
this.ispaused = true;
}

isPlaying() {
return this.isplaying;
}

isPaused() {
return this.ispaused;
}
Expand Down
154 changes: 154 additions & 0 deletions test/unit/test/streaming/streaming.MediaManager.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
import MediaManager from '../../../../src/streaming/MediaManager.js';
import VideoModelMock from '../../mocks/VideoModelMock.js';
import PlaybackControllerMock from '../../mocks/PlaybackControllerMock.js';
import DebugMock from '../../mocks/DebugMock.js';
import { expect } from 'chai';

describe('MediaManager', function () {
let mediaManager;
let videoModelMock;
let mockVideoElement;
let playbackControllerMock;
let debugMock;

beforeEach(function () {
mediaManager = MediaManager().getInstance();
videoModelMock = new VideoModelMock();
videoModelMock.play();
playbackControllerMock = new PlaybackControllerMock();
debugMock = new DebugMock();
mediaManager.setConfig({
videoModel: videoModelMock,
playbackController: playbackControllerMock,
debug: debugMock,
hideAlternativePlayerControls: false,
alternativeContext: {}
});

mediaManager.initialize();
});

afterEach(function () {
if (mediaManager) {
mediaManager.reset();
}
});

describe('set the alternative video element', function () {
it('should not throw error when setting alternative video element', function () {
mockVideoElement = document.createElement('video');

expect(() => {
mediaManager.setAlternativeVideoElement(mockVideoElement);
}).to.not.throw();
});
});

describe('prebuffer the alternative content', function () {
it('should start prebuffering alternative content and log the action', function () {
const testUrl = 'http://test.mpd';
const testPlayerId = 'testPlayer';

expect(() => {
mediaManager.prebufferAlternativeContent(testPlayerId, testUrl);
}).to.not.throw();

expect(debugMock.log.info).to.equal(`Starting prebuffering for player ${testPlayerId}`);
});
});

describe('switch to the alternative content', function () {
beforeEach(function () {
mockVideoElement = document.createElement('video');
mediaManager.setAlternativeVideoElement(mockVideoElement);
});

it('should switch to alternative content without prebuffered content', function () {
const testUrl = 'http://test.mpd';
const testPlayerId = 'testPlayer';

mediaManager.switchToAlternativeContent(testPlayerId, testUrl);

expect(debugMock.log.info).to.equal(`Alternative content playback started for player ${testPlayerId}`);
expect(videoModelMock.isPaused()).to.be.true;
expect(mockVideoElement.style.display).to.be.equal('block');
expect(videoModelMock.getElement().style.display).to.be.equal('none');
});

it('should switch to alternative content with prebuffered content', function () {
const testUrl = 'http://test.mpd';
const testPlayerId = 'testPlayer';

mediaManager.prebufferAlternativeContent(testPlayerId, testUrl);
mediaManager.switchToAlternativeContent(testPlayerId, testUrl);

expect(debugMock.log.info).to.equal(`Alternative content playback started for player ${testPlayerId}`);
expect(videoModelMock.isPaused()).to.be.true;
expect(mockVideoElement.style.display).to.be.equal('block');
expect(videoModelMock.getElement().style.display).to.be.equal('none');
});

it('should switch to alternative content and seek to a given time', function () {
const testUrl = 'http://test.mpd';
const testPlayerId = 'testPlayer';
const testTime = 15;

mediaManager.switchToAlternativeContent(testPlayerId, testUrl, testTime);

expect(debugMock.log.debug).to.equal(`Seeking alternative content to time: ${testTime}`);
expect(debugMock.log.info).to.equal(`Alternative content playback started for player ${testPlayerId}`);
expect(videoModelMock.isPaused()).to.be.true;
expect(mockVideoElement.style.display).to.be.equal('block');
expect(videoModelMock.getElement().style.display).to.be.equal('none');
});
});

describe('get alternative player', function () {
beforeEach(function () {
mockVideoElement = document.createElement('video');
mediaManager.setAlternativeVideoElement(mockVideoElement);
});

it('should return null when no alternative player is set', function () {
const result = mediaManager.getAlternativePlayer();
expect(result).to.be.undefined;
});

it('should return the alternative player when it is set', function () {
const testUrl = 'http://test.mpd';
const testPlayerId = 'testPlayer';

mediaManager.switchToAlternativeContent(testPlayerId, testUrl, 0);

const result = mediaManager.getAlternativePlayer();
expect(result).to.not.be.undefined;
expect(result).to.be.an('object');
});
});

describe('switch back to the main content', function () {
beforeEach(function () {
mockVideoElement = document.createElement('video');
mediaManager.setAlternativeVideoElement(mockVideoElement);
});

it('should warn when no alternative player is set', function () {
mediaManager.switchBackToMainContent(10);

expect(debugMock.log.warn).to.equal('No alternative player to switch back from');
});

it('should switch back to main content', function () {
const testUrl = 'http://test.mpd';
const testPlayerId = 'testPlayer';
const seekTime = 20;

mediaManager.switchToAlternativeContent(testPlayerId, testUrl, 0);
mediaManager.switchBackToMainContent(seekTime);

expect(debugMock.log.info).to.equal('Main content playback resumed');
expect(mediaManager.getAlternativePlayer()).to.be.null;
expect(videoModelMock.isPlaying()).to.be.true;
});
});
});