diff --git a/samples/alternative/index.html b/samples/alternative/index.html index 113d808a06..dc9eaf6942 100644 --- a/samples/alternative/index.html +++ b/samples/alternative/index.html @@ -15,7 +15,8 @@ width: 640px; height: 360px; } - #alternativeVideo { + + #alternative-video-element { display: none; } @@ -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); @@ -57,8 +58,8 @@

Alternative Media Presentations

- - + +
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 83f26b6739..2700ae737c 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,9 +45,12 @@ function MediaManager() { altVideoElement, alternativeContext, logger, + debug, prebufferedPlayers = new Map(), prebufferCleanupInterval = null; + const context = this.context; + function setConfig(config) { if (!config) { return; @@ -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) { @@ -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'; @@ -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}`); @@ -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}`); @@ -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'); @@ -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; } @@ -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; diff --git a/src/streaming/MediaPlayer.js b/src/streaming/MediaPlayer.js index bfa2701749..f268a4b431 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/src/streaming/controllers/AlternativeMediaController.js b/src/streaming/controllers/AlternativeMediaController.js index 8d9840c16d..894518d349 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/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 new file mode 100644 index 0000000000..4fb1062304 --- /dev/null +++ b/test/unit/test/streaming/streaming.MediaManager.js @@ -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; + }); + }); +}); \ No newline at end of file