From 8eb7f7d2d49368f364d945b3227d85a5bc8de9e0 Mon Sep 17 00:00:00 2001 From: Sebastian Piquerez Date: Thu, 18 Sep 2025 14:12:38 -0300 Subject: [PATCH 01/15] refactor: remove redundant config setting for alternative video element --- src/streaming/MediaPlayer.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/streaming/MediaPlayer.js b/src/streaming/MediaPlayer.js index 298d8eaddd..dfdbf1a52c 100644 --- a/src/streaming/MediaPlayer.js +++ b/src/streaming/MediaPlayer.js @@ -2156,9 +2156,6 @@ function MediaPlayer() { } if (alternativeMediaController) { - alternativeMediaController.setConfig({ - alternativeVideoElement: element - }); alternativeMediaController.setAlternativeVideoElement(element); } } From 991f2921a0ffc62e253686b5f8036172bbaee095 Mon Sep 17 00:00:00 2001 From: Sebastian Piquerez Date: Mon, 22 Sep 2025 14:04:50 -0300 Subject: [PATCH 02/15] feat: add setAlternativeVideoElement unit test --- src/streaming/MediaManager.js | 12 ++++- .../controllers/AlternativeMediaController.js | 12 ++--- .../test/streaming/streaming.MediaManager.js | 50 +++++++++++++++++++ 3 files changed, 64 insertions(+), 10 deletions(-) create mode 100644 test/unit/test/streaming/streaming.MediaManager.js diff --git a/src/streaming/MediaManager.js b/src/streaming/MediaManager.js index 83f26b6739..8699dc3269 100644 --- a/src/streaming/MediaManager.js +++ b/src/streaming/MediaManager.js @@ -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, @@ -44,6 +45,7 @@ function MediaManager() { altVideoElement, alternativeContext, logger, + debug, prebufferedPlayers = new Map(), prebufferCleanupInterval = null; @@ -56,8 +58,8 @@ function MediaManager() { videoModel = config.videoModel; } - if (config.logger) { - logger = config.logger; + if (config.debug) { + debug = config.debug; } if (!!config.playbackController && !playbackController) { @@ -74,6 +76,12 @@ function MediaManager() { } function initialize() { + if (!debug) { + debug = Debug(context).getInstance(); + } + + logger = debug.getLogger(instance); + if (!fullscreenDiv) { fullscreenDiv = document.createElement('div'); fullscreenDiv.id = 'fullscreenDiv'; diff --git a/src/streaming/controllers/AlternativeMediaController.js b/src/streaming/controllers/AlternativeMediaController.js index f67be8d47f..ad80cecc3e 100644 --- a/src/streaming/controllers/AlternativeMediaController.js +++ b/src/streaming/controllers/AlternativeMediaController.js @@ -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; @@ -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) { diff --git a/test/unit/test/streaming/streaming.MediaManager.js b/test/unit/test/streaming/streaming.MediaManager.js new file mode 100644 index 0000000000..b613204705 --- /dev/null +++ b/test/unit/test/streaming/streaming.MediaManager.js @@ -0,0 +1,50 @@ +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 playbackControllerMock; + let originalDocument; + let debugMock; + + beforeEach(function () { + // Setup mocks + videoModelMock = new VideoModelMock(); + playbackControllerMock = new PlaybackControllerMock(); + debugMock = new DebugMock(); + + // Create MediaManager instance + mediaManager = MediaManager(); + + // Configure MediaManager + mediaManager.setConfig({ + videoModel: videoModelMock, + playbackController: playbackControllerMock, + debug: debugMock, + hideAlternativePlayerControls: false, + alternativeContext: {} + }); + + }); + + afterEach(function () { + if (mediaManager) { + mediaManager.reset(); + } + global.document = originalDocument; + }); + + describe('setAlternativeVideoElement', function () { + it('should not throw error when setting alternative video element', function () { + const mockVideoElement = videoModelMock.getElement(); + + expect(() => { + mediaManager.setAlternativeVideoElement(mockVideoElement); + }).to.not.throw(); + }); + }); +}); \ No newline at end of file From 1e0048e5e537f6c4e1757f16779d936399274e76 Mon Sep 17 00:00:00 2001 From: Sebastian Piquerez Date: Mon, 22 Sep 2025 14:12:51 -0300 Subject: [PATCH 03/15] fix debug import in the MediaManager --- src/streaming/MediaManager.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/streaming/MediaManager.js b/src/streaming/MediaManager.js index 8699dc3269..83f4c5bea0 100644 --- a/src/streaming/MediaManager.js +++ b/src/streaming/MediaManager.js @@ -32,7 +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'; +import Debug from '../core/Debug.js'; function MediaManager() { let instance, From 40dd8f974570f760fd5c74dfa631d8bedba9059e Mon Sep 17 00:00:00 2001 From: Sebastian Piquerez Date: Mon, 22 Sep 2025 14:15:21 -0300 Subject: [PATCH 04/15] refactor: streamline MediaManager instance creation in tests --- test/unit/test/streaming/streaming.MediaManager.js | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/test/unit/test/streaming/streaming.MediaManager.js b/test/unit/test/streaming/streaming.MediaManager.js index b613204705..d49673a2c9 100644 --- a/test/unit/test/streaming/streaming.MediaManager.js +++ b/test/unit/test/streaming/streaming.MediaManager.js @@ -5,10 +5,9 @@ import DebugMock from '../../mocks/DebugMock.js'; import { expect } from 'chai'; describe('MediaManager', function () { - let mediaManager; + const mediaManager = MediaManager().getInstance(); let videoModelMock; let playbackControllerMock; - let originalDocument; let debugMock; beforeEach(function () { @@ -17,9 +16,6 @@ describe('MediaManager', function () { playbackControllerMock = new PlaybackControllerMock(); debugMock = new DebugMock(); - // Create MediaManager instance - mediaManager = MediaManager(); - // Configure MediaManager mediaManager.setConfig({ videoModel: videoModelMock, @@ -35,11 +31,10 @@ describe('MediaManager', function () { if (mediaManager) { mediaManager.reset(); } - global.document = originalDocument; }); describe('setAlternativeVideoElement', function () { - it('should not throw error when setting alternative video element', function () { + it.only('should not throw error when setting alternative video element', function () { const mockVideoElement = videoModelMock.getElement(); expect(() => { From c270d1c4d34c27155a637eea0949a4df9f0994a5 Mon Sep 17 00:00:00 2001 From: Sebastian Piquerez Date: Mon, 22 Sep 2025 14:17:10 -0300 Subject: [PATCH 05/15] feat: add context variable to MediaManager constructor --- src/streaming/MediaManager.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/streaming/MediaManager.js b/src/streaming/MediaManager.js index 83f4c5bea0..d430a23cb4 100644 --- a/src/streaming/MediaManager.js +++ b/src/streaming/MediaManager.js @@ -49,6 +49,8 @@ function MediaManager() { prebufferedPlayers = new Map(), prebufferCleanupInterval = null; + const context = this.context; + function setConfig(config) { if (!config) { return; From dd2cf077180638c7260345a472642e67dd7e2daa Mon Sep 17 00:00:00 2001 From: Sebastian Piquerez Date: Mon, 22 Sep 2025 14:18:38 -0300 Subject: [PATCH 06/15] fix: ensure MediaManager is initialized in beforeEach --- test/unit/test/streaming/streaming.MediaManager.js | 1 + 1 file changed, 1 insertion(+) diff --git a/test/unit/test/streaming/streaming.MediaManager.js b/test/unit/test/streaming/streaming.MediaManager.js index d49673a2c9..61e11aadc8 100644 --- a/test/unit/test/streaming/streaming.MediaManager.js +++ b/test/unit/test/streaming/streaming.MediaManager.js @@ -25,6 +25,7 @@ describe('MediaManager', function () { alternativeContext: {} }); + mediaManager.initialize(); }); afterEach(function () { From 1884d4cc04288e12de3f2d8bebc7a2a6c04b34a1 Mon Sep 17 00:00:00 2001 From: Sebastian Piquerez Date: Mon, 22 Sep 2025 14:54:20 -0300 Subject: [PATCH 07/15] test: add getAlternativePlayer test for the MediaManager and fix switchToAlternativeContent errors --- samples/alternative/index.html | 11 ++++++-- samples/alternative/manifest-insert.mpd | 2 +- src/streaming/MediaManager.js | 26 +++-------------- src/streaming/MediaPlayer.js | 1 - .../test/streaming/streaming.MediaManager.js | 28 ++++++++++++++++++- 5 files changed, 41 insertions(+), 27 deletions(-) diff --git a/samples/alternative/index.html b/samples/alternative/index.html index f067a88c1f..ea0d8f9a26 100644 --- a/samples/alternative/index.html +++ b/samples/alternative/index.html @@ -15,6 +15,10 @@ width: 640px; height: 360px; } + + #alternative-video-element { + display: none; + } @@ -50,7 +56,8 @@

Manual load single video

- + +
diff --git a/samples/alternative/manifest-insert.mpd b/samples/alternative/manifest-insert.mpd index 64dd3bf6a9..4deba7afec 100644 --- a/samples/alternative/manifest-insert.mpd +++ b/samples/alternative/manifest-insert.mpd @@ -2,7 +2,7 @@ https://dash.akamaized.net/akamai/bbb_30fps/ - + diff --git a/src/streaming/MediaManager.js b/src/streaming/MediaManager.js index d430a23cb4..eb3df7af32 100644 --- a/src/streaming/MediaManager.js +++ b/src/streaming/MediaManager.js @@ -197,35 +197,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'); diff --git a/src/streaming/MediaPlayer.js b/src/streaming/MediaPlayer.js index dfdbf1a52c..dcd0d798e2 100644 --- a/src/streaming/MediaPlayer.js +++ b/src/streaming/MediaPlayer.js @@ -610,7 +610,6 @@ function MediaPlayer() { } if (source) { const playbackTime = time ? time : providedStartTime; - console.log(playbackTime) _initializePlayback(playbackTime); } else { throw SOURCE_NOT_ATTACHED_ERROR; diff --git a/test/unit/test/streaming/streaming.MediaManager.js b/test/unit/test/streaming/streaming.MediaManager.js index 61e11aadc8..c66e947d51 100644 --- a/test/unit/test/streaming/streaming.MediaManager.js +++ b/test/unit/test/streaming/streaming.MediaManager.js @@ -35,7 +35,7 @@ describe('MediaManager', function () { }); describe('setAlternativeVideoElement', function () { - it.only('should not throw error when setting alternative video element', function () { + it('should not throw error when setting alternative video element', function () { const mockVideoElement = videoModelMock.getElement(); expect(() => { @@ -43,4 +43,30 @@ describe('MediaManager', function () { }).to.not.throw(); }); }); + + describe('getAlternativePlayer', function () { + beforeEach(function () { + const mockVideoElement = videoModelMock.getElement(); + mediaManager.setAlternativeVideoElement(mockVideoElement); + }); + + it.only('should return undefined when no alternative player is set', function () { + const result = mediaManager.getAlternativePlayer(); + expect(result).to.be.undefined; + }); + + it.only('should return the alternative player when it is set', function () { + // Initialize an alternative player by calling initializeAlternativePlayer + // We need to access the private method through switchToAlternativeContent + const testUrl = 'http://test.mpd'; + const testPlayerId = 'testPlayer'; + + // Trigger initialization of alternative player + mediaManager.switchToAlternativeContent(testPlayerId, testUrl, 0); + + const result = mediaManager.getAlternativePlayer(); + expect(result).to.not.be.null; + expect(result).to.be.an('object'); + }); + }); }); \ No newline at end of file From 1fe5d5552a45e365ae787cfa12d9b878fe3b7f7a Mon Sep 17 00:00:00 2001 From: Sebastian Piquerez Date: Mon, 22 Sep 2025 14:55:08 -0300 Subject: [PATCH 08/15] fix: update expectation in getAlternativePlayer test to check for undefined instead of null --- test/unit/test/streaming/streaming.MediaManager.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/unit/test/streaming/streaming.MediaManager.js b/test/unit/test/streaming/streaming.MediaManager.js index c66e947d51..d03ec40114 100644 --- a/test/unit/test/streaming/streaming.MediaManager.js +++ b/test/unit/test/streaming/streaming.MediaManager.js @@ -65,7 +65,7 @@ describe('MediaManager', function () { mediaManager.switchToAlternativeContent(testPlayerId, testUrl, 0); const result = mediaManager.getAlternativePlayer(); - expect(result).to.not.be.null; + expect(result).to.not.be.undefined; expect(result).to.be.an('object'); }); }); From 079e650bbed73463e9001134cafb35661ae4e632 Mon Sep 17 00:00:00 2001 From: Sebastian Piquerez Date: Mon, 22 Sep 2025 14:58:11 -0300 Subject: [PATCH 09/15] test: remove 'only' from getAlternativePlayer tests to ensure all tests run --- test/unit/test/streaming/streaming.MediaManager.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/unit/test/streaming/streaming.MediaManager.js b/test/unit/test/streaming/streaming.MediaManager.js index d03ec40114..e2460009e2 100644 --- a/test/unit/test/streaming/streaming.MediaManager.js +++ b/test/unit/test/streaming/streaming.MediaManager.js @@ -50,12 +50,12 @@ describe('MediaManager', function () { mediaManager.setAlternativeVideoElement(mockVideoElement); }); - it.only('should return undefined when no alternative player is set', function () { + it('should return undefined when no alternative player is set', function () { const result = mediaManager.getAlternativePlayer(); expect(result).to.be.undefined; }); - it.only('should return the alternative player when it is set', function () { + it('should return the alternative player when it is set', function () { // Initialize an alternative player by calling initializeAlternativePlayer // We need to access the private method through switchToAlternativeContent const testUrl = 'http://test.mpd'; From ed82fa99229062daee286cf7e239aadd98ecca89 Mon Sep 17 00:00:00 2001 From: Sebastian Piquerez Date: Mon, 22 Sep 2025 15:02:25 -0300 Subject: [PATCH 10/15] refactor: clean up comments in getAlternativePlayer test for clarity --- test/unit/test/streaming/streaming.MediaManager.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/test/unit/test/streaming/streaming.MediaManager.js b/test/unit/test/streaming/streaming.MediaManager.js index e2460009e2..a8f90f7e78 100644 --- a/test/unit/test/streaming/streaming.MediaManager.js +++ b/test/unit/test/streaming/streaming.MediaManager.js @@ -56,12 +56,9 @@ describe('MediaManager', function () { }); it('should return the alternative player when it is set', function () { - // Initialize an alternative player by calling initializeAlternativePlayer - // We need to access the private method through switchToAlternativeContent const testUrl = 'http://test.mpd'; const testPlayerId = 'testPlayer'; - // Trigger initialization of alternative player mediaManager.switchToAlternativeContent(testPlayerId, testUrl, 0); const result = mediaManager.getAlternativePlayer(); From da35d11c8b92b057b37e9b4feec7ef3367007417 Mon Sep 17 00:00:00 2001 From: Sebastian Piquerez Date: Mon, 22 Sep 2025 15:34:18 -0300 Subject: [PATCH 11/15] test: add unit test for switchToAlternativeContent and prebufferAlternativeContent --- src/streaming/MediaManager.js | 2 +- .../test/streaming/streaming.MediaManager.js | 58 ++++++++++++++++--- 2 files changed, 52 insertions(+), 8 deletions(-) diff --git a/src/streaming/MediaManager.js b/src/streaming/MediaManager.js index eb3df7af32..aa12691c05 100644 --- a/src/streaming/MediaManager.js +++ b/src/streaming/MediaManager.js @@ -220,7 +220,7 @@ function MediaManager() { } altPlayer.play(); - logger.info('Alternative content playback started'); + logger.info(`Alternative content playback started for player ${playerId}`); isSwitching = false; } diff --git a/test/unit/test/streaming/streaming.MediaManager.js b/test/unit/test/streaming/streaming.MediaManager.js index e2460009e2..b4be56eec2 100644 --- a/test/unit/test/streaming/streaming.MediaManager.js +++ b/test/unit/test/streaming/streaming.MediaManager.js @@ -11,12 +11,10 @@ describe('MediaManager', function () { let debugMock; beforeEach(function () { - // Setup mocks videoModelMock = new VideoModelMock(); playbackControllerMock = new PlaybackControllerMock(); debugMock = new DebugMock(); - // Configure MediaManager mediaManager.setConfig({ videoModel: videoModelMock, playbackController: playbackControllerMock, @@ -44,24 +42,70 @@ describe('MediaManager', function () { }); }); + describe('prebufferAlternativeContent', function () { + it('should start prebuffering alternative content and log the action', function () { + const testUrl = 'http://test.mpd'; + const testPlayerId = 'testPlayer'; + + mediaManager.prebufferAlternativeContent(testPlayerId, testUrl); + + expect(debugMock.log.info).to.equal(`Starting prebuffering for player ${testPlayerId}`); + }); + }); + + describe('switchToAlternativeContent', function () { + beforeEach(function () { + const mockVideoElement = videoModelMock.getElement(); + 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}`); + }); + + it('should switch to alternative content with prebuffered content', function () { + const testUrl = 'http://test.mpd'; + const testPlayerId = 'testPlayer'; + + mediaManager.prebufferAlternativeContent(testPlayerId, testUrl); + expect(debugMock.log.info).to.equal(`Starting prebuffering for player ${testPlayerId}`); + + mediaManager.switchToAlternativeContent(testPlayerId, testUrl); + expect(debugMock.log.info).to.equal(`Alternative content playback started for player ${testPlayerId}`); + }); + + 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}`); + }); + }); + describe('getAlternativePlayer', function () { beforeEach(function () { const mockVideoElement = videoModelMock.getElement(); mediaManager.setAlternativeVideoElement(mockVideoElement); }); - it('should return undefined when no alternative player is set', function () { + it('should return null when no alternative player is set', function () { const result = mediaManager.getAlternativePlayer(); - expect(result).to.be.undefined; + expect(result).to.be.null; }); it('should return the alternative player when it is set', function () { - // Initialize an alternative player by calling initializeAlternativePlayer - // We need to access the private method through switchToAlternativeContent const testUrl = 'http://test.mpd'; const testPlayerId = 'testPlayer'; - // Trigger initialization of alternative player mediaManager.switchToAlternativeContent(testPlayerId, testUrl, 0); const result = mediaManager.getAlternativePlayer(); From 686e271bb91c444e0b1ce1659756e92beb9c7e51 Mon Sep 17 00:00:00 2001 From: Sebastian Piquerez Date: Mon, 22 Sep 2025 15:57:50 -0300 Subject: [PATCH 12/15] test: implement switch back to main content unit test --- src/streaming/MediaManager.js | 5 +++ .../test/streaming/streaming.MediaManager.js | 35 +++++++++++++++---- 2 files changed, 34 insertions(+), 6 deletions(-) diff --git a/src/streaming/MediaManager.js b/src/streaming/MediaManager.js index aa12691c05..d7424c7209 100644 --- a/src/streaming/MediaManager.js +++ b/src/streaming/MediaManager.js @@ -231,6 +231,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; diff --git a/test/unit/test/streaming/streaming.MediaManager.js b/test/unit/test/streaming/streaming.MediaManager.js index b4be56eec2..a3fad315dd 100644 --- a/test/unit/test/streaming/streaming.MediaManager.js +++ b/test/unit/test/streaming/streaming.MediaManager.js @@ -32,7 +32,7 @@ describe('MediaManager', function () { } }); - describe('setAlternativeVideoElement', function () { + describe('set the alternative video element', function () { it('should not throw error when setting alternative video element', function () { const mockVideoElement = videoModelMock.getElement(); @@ -42,7 +42,7 @@ describe('MediaManager', function () { }); }); - describe('prebufferAlternativeContent', function () { + 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'; @@ -53,7 +53,7 @@ describe('MediaManager', function () { }); }); - describe('switchToAlternativeContent', function () { + describe('switch to the alternative content', function () { beforeEach(function () { const mockVideoElement = videoModelMock.getElement(); mediaManager.setAlternativeVideoElement(mockVideoElement); @@ -73,8 +73,6 @@ describe('MediaManager', function () { const testPlayerId = 'testPlayer'; mediaManager.prebufferAlternativeContent(testPlayerId, testUrl); - expect(debugMock.log.info).to.equal(`Starting prebuffering for player ${testPlayerId}`); - mediaManager.switchToAlternativeContent(testPlayerId, testUrl); expect(debugMock.log.info).to.equal(`Alternative content playback started for player ${testPlayerId}`); }); @@ -91,7 +89,7 @@ describe('MediaManager', function () { }); }); - describe('getAlternativePlayer', function () { + describe('get alternative player', function () { beforeEach(function () { const mockVideoElement = videoModelMock.getElement(); mediaManager.setAlternativeVideoElement(mockVideoElement); @@ -113,4 +111,29 @@ describe('MediaManager', function () { expect(result).to.be.an('object'); }); }); + + describe('switch back to the main content', function () { + beforeEach(function () { + const mockVideoElement = videoModelMock.getElement(); + 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; + }); + }); }); \ No newline at end of file From 8fdbd8d3e5794c68b333b656eca68ed19f549f24 Mon Sep 17 00:00:00 2001 From: Sebastian Piquerez Date: Mon, 22 Sep 2025 17:21:45 -0300 Subject: [PATCH 13/15] test: Improve MediaManager unit test and add switchBackToMain contet tests --- src/streaming/MediaManager.js | 6 +--- test/unit/mocks/VideoModelMock.js | 5 +++ .../test/streaming/streaming.MediaManager.js | 31 ++++++++++++++----- 3 files changed, 29 insertions(+), 13 deletions(-) diff --git a/src/streaming/MediaManager.js b/src/streaming/MediaManager.js index d7424c7209..2700ae737c 100644 --- a/src/streaming/MediaManager.js +++ b/src/streaming/MediaManager.js @@ -108,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}`); @@ -150,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}`); diff --git a/test/unit/mocks/VideoModelMock.js b/test/unit/mocks/VideoModelMock.js index a1f64c0ec1..2ff7095355 100644 --- a/test/unit/mocks/VideoModelMock.js +++ b/test/unit/mocks/VideoModelMock.js @@ -111,9 +111,14 @@ class VideoModelMock { } pause() { + this.isplaying = false; this.ispaused = true; } + isPlaying() { + return this.isplaying; + } + isPaused() { return this.ispaused; } diff --git a/test/unit/test/streaming/streaming.MediaManager.js b/test/unit/test/streaming/streaming.MediaManager.js index a3fad315dd..4fb1062304 100644 --- a/test/unit/test/streaming/streaming.MediaManager.js +++ b/test/unit/test/streaming/streaming.MediaManager.js @@ -5,16 +5,18 @@ import DebugMock from '../../mocks/DebugMock.js'; import { expect } from 'chai'; describe('MediaManager', function () { - const mediaManager = MediaManager().getInstance(); + 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, @@ -34,7 +36,7 @@ describe('MediaManager', function () { describe('set the alternative video element', function () { it('should not throw error when setting alternative video element', function () { - const mockVideoElement = videoModelMock.getElement(); + mockVideoElement = document.createElement('video'); expect(() => { mediaManager.setAlternativeVideoElement(mockVideoElement); @@ -47,7 +49,9 @@ describe('MediaManager', function () { const testUrl = 'http://test.mpd'; const testPlayerId = 'testPlayer'; - mediaManager.prebufferAlternativeContent(testPlayerId, testUrl); + expect(() => { + mediaManager.prebufferAlternativeContent(testPlayerId, testUrl); + }).to.not.throw(); expect(debugMock.log.info).to.equal(`Starting prebuffering for player ${testPlayerId}`); }); @@ -55,7 +59,7 @@ describe('MediaManager', function () { describe('switch to the alternative content', function () { beforeEach(function () { - const mockVideoElement = videoModelMock.getElement(); + mockVideoElement = document.createElement('video'); mediaManager.setAlternativeVideoElement(mockVideoElement); }); @@ -66,6 +70,9 @@ describe('MediaManager', function () { 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 () { @@ -74,7 +81,11 @@ describe('MediaManager', function () { 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 () { @@ -86,18 +97,21 @@ describe('MediaManager', function () { 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 () { - const mockVideoElement = videoModelMock.getElement(); + 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.null; + expect(result).to.be.undefined; }); it('should return the alternative player when it is set', function () { @@ -114,7 +128,7 @@ describe('MediaManager', function () { describe('switch back to the main content', function () { beforeEach(function () { - const mockVideoElement = videoModelMock.getElement(); + mockVideoElement = document.createElement('video'); mediaManager.setAlternativeVideoElement(mockVideoElement); }); @@ -134,6 +148,7 @@ describe('MediaManager', function () { expect(debugMock.log.info).to.equal('Main content playback resumed'); expect(mediaManager.getAlternativePlayer()).to.be.null; + expect(videoModelMock.isPlaying()).to.be.true; }); }); }); \ No newline at end of file From 04f67e0526a2ba38e0792ce2538b1819e6ebb3b3 Mon Sep 17 00:00:00 2001 From: Sebastian Piquerez Date: Tue, 23 Sep 2025 11:47:49 -0300 Subject: [PATCH 14/15] Revert "Merge branch 'alternative-media-presentations/media-manager-unit-tests' into media-manager-unit-test/prebuffer-and-switch-alternative-content" This reverts commit 90541aa8287f0e75cc9d8fc387483de6eed319a6, reversing changes made to da35d11c8b92b057b37e9b4feec7ef3367007417. --- .claude/settings.local.json | 11 --- CLAUDE.md | 86 ------------------- .../alternative-mpd-local.json | 26 ------ test/unit/config/karma.unit.conf.cjs | 2 +- 4 files changed, 1 insertion(+), 124 deletions(-) delete mode 100644 .claude/settings.local.json delete mode 100644 CLAUDE.md delete mode 100644 test/functional/config/test-configurations/alternative-mpd-local.json diff --git a/.claude/settings.local.json b/.claude/settings.local.json deleted file mode 100644 index 317ab125fb..0000000000 --- a/.claude/settings.local.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "permissions": { - "allow": [ - "Bash(npm run test:*)", - "Bash(npm test)", - "Bash(npm test:*)" - ], - "deny": [], - "ask": [] - } -} \ No newline at end of file diff --git a/CLAUDE.md b/CLAUDE.md deleted file mode 100644 index 5c8b168b9c..0000000000 --- a/CLAUDE.md +++ /dev/null @@ -1,86 +0,0 @@ -# CLAUDE.md - -This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. - -## Project Overview - -This is dash.js, a JavaScript implementation for MPEG DASH media playback in browsers using Media Source Extensions (MSE) and Encrypted Media Extensions (EME). The codebase is based on the Dash Industry Forum reference implementation with custom extensions for alternative media presentations. - -## Common Development Commands - -### Build Commands -- `npm run build` - Full build (modern + legacy) -- `npm run build-modern` - Build modern version only -- `npm run build-legacy` - Build legacy version only -- `npm run dev` - Development build with TypeScript compilation and webpack watch mode -- `npm start` - Start webpack dev server - -### Testing -- `npm test` - Run unit tests (Karma) -- `npm run test-functional` - Run functional tests -- `npm run lint` - Run ESLint on source files -- `npm run prebuild` - Clean dist, compile TypeScript, run tests and lint - -### Documentation -- `npm run doc` - Generate JSDoc documentation - -## Architecture Overview - -### Core Structure -The codebase follows a modular architecture with clear separation of concerns: - -- **`src/core/`** - Foundation classes (EventBus, FactoryMaker, Logger, Utils) -- **`src/dash/`** - DASH-specific implementations (parsers, controllers, value objects) -- **`src/streaming/`** - Media streaming logic (MediaPlayer, controllers, models) -- **`src/mss/`** - Microsoft Smooth Streaming support -- **`src/offline/`** - Offline playback capabilities - -### Key Components - -#### MediaPlayer (`src/streaming/MediaPlayer.js`) -The main facade providing the public API. Coordinates all other components and manages the overall player lifecycle. - -#### MediaManager (`src/streaming/MediaManager.js`) -Handles alternative media presentations and switching between main and alternative content. Manages video element lifecycle and prebuffering. - -#### Controllers -- **PlaybackController** - Controls playback state and seeking -- **AbrController** - Adaptive bitrate logic -- **StreamController** - Stream management and switching -- **BufferController** - Buffer management per stream type -- **AlternativeMediaController** - Alternative content switching logic - -#### Models -- **VideoModel** - Video element abstraction -- **ManifestModel** - Manifest data management -- **MediaPlayerModel** - Player configuration and settings - -### Factory Pattern -The codebase extensively uses the FactoryMaker pattern for dependency injection and singleton management. Most components are registered as factories using `FactoryMaker.getSingletonFactory()`. - -### Event System -Built around a centralized EventBus system with strongly-typed events defined in `Events.js`. Components communicate through events rather than direct coupling. - -### Alternative Media Presentations -This fork includes custom functionality for switching between main and alternative content streams: -- MediaManager handles video element switching -- AlternativeMediaController manages the switching logic -- Supports prebuffering of alternative content for seamless transitions - -## Development Notes - -### TypeScript -The project uses TypeScript for development with type definitions in `index.d.ts`. Run `tsc` to compile before building. - -### Testing Framework -- **Unit Tests**: Karma + Mocha + Chai + Sinon -- **Functional Tests**: Karma with browser automation -- Tests are located in `test/unit/` and `test/functional/` - -### Build System -Uses Webpack with separate configurations for modern and legacy builds in `build/webpack/`. - -### Code Style -- ESLint configuration enforces coding standards -- JSDoc comments for API documentation -- Factory pattern for component instantiation \ No newline at end of file diff --git a/test/functional/config/test-configurations/alternative-mpd-local.json b/test/functional/config/test-configurations/alternative-mpd-local.json deleted file mode 100644 index 71ad50d968..0000000000 --- a/test/functional/config/test-configurations/alternative-mpd-local.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "browsers": [ - "chrome_custom" - ], - "hostname": "localhost", - "port": 4200, - "protocol": "http", - "concurrency": 1, - "customLaunchers": { - "chrome_custom": { - "base": "Chrome", - "flags": [ - "--disable-web-security", - "--autoplay-policy=no-user-gesture-required", - "--disable-popup-blocking", - "--disable-search-engine-choice-screen", - "--allow-running-insecure-content", - "--disable-features=VizDisplayCompositor" - ] - } - }, - "reporters": [ - "junit", - "mocha" - ] -} \ No newline at end of file diff --git a/test/unit/config/karma.unit.conf.cjs b/test/unit/config/karma.unit.conf.cjs index b5ec68991f..0fb897139a 100644 --- a/test/unit/config/karma.unit.conf.cjs +++ b/test/unit/config/karma.unit.conf.cjs @@ -110,7 +110,7 @@ module.exports = function (config) { // start these browsers // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher - browsers: ['ChromeHeadless'], + browsers: ['ChromeHeadless', 'FirefoxHeadless'], // Continuous Integration mode // if true, Karma captures browsers, runs the tests and exits From 80ff9eefe746f0527f510bbcaf489b755c67db66 Mon Sep 17 00:00:00 2001 From: Sebastian Piquerez Date: Tue, 23 Sep 2025 11:55:35 -0300 Subject: [PATCH 15/15] Fix formatting of alternative video element in index.html --- samples/alternative/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/alternative/index.html b/samples/alternative/index.html index d30c16f2f9..dc9eaf6942 100644 --- a/samples/alternative/index.html +++ b/samples/alternative/index.html @@ -59,7 +59,7 @@

Alternative Media Presentations

- +