Skip to content

Commit

Permalink
Add MediaPlayer capability to refresh manifest (#4330)
Browse files Browse the repository at this point in the history
  • Loading branch information
eirikbjornr committed Jan 8, 2024
1 parent fcb89e1 commit 3116846
Show file tree
Hide file tree
Showing 7 changed files with 199 additions and 0 deletions.
2 changes: 2 additions & 0 deletions index.d.ts
Expand Up @@ -1254,6 +1254,8 @@ declare namespace dashjs {

attachSource(urlOrManifest: string | object, startTime?: number | string): void;

refreshManifest(callback: (manifest: object | null, error: unknown) => void): void;

isReady(): boolean;

preload(): void;
Expand Down
37 changes: 37 additions & 0 deletions src/streaming/MediaPlayer.js
Expand Up @@ -1907,6 +1907,42 @@ function MediaPlayer() {
}
}

/**
* Reload the manifest that the player is currently using.
*
* @memberof module:MediaPlayer
* @param {function} callback - A Callback function provided when retrieving manifests
* @instance
*/
function refreshManifest(callback) {
if (!mediaPlayerInitialized) {
throw MEDIA_PLAYER_NOT_INITIALIZED_ERROR;
}

if (!isReady()) {
return callback(null, SOURCE_NOT_ATTACHED_ERROR);
}

let self = this;

if (typeof callback === 'function') {
const handler = function (e) {
eventBus.off(Events.INTERNAL_MANIFEST_LOADED, handler, self);

if (e.error) {
callback(null, e.error);
return;
}

callback(e.manifest);
};

eventBus.on(Events.INTERNAL_MANIFEST_LOADED, handler, self);
}

streamController.refreshManifest();
}

/**
* Get the current settings object being used on the player.
* @returns {PlayerSettings} The settings object being used.
Expand Down Expand Up @@ -2449,6 +2485,7 @@ function MediaPlayer() {
extend,
attachView,
attachSource,
refreshManifest,
isReady,
preload,
play,
Expand Down
10 changes: 10 additions & 0 deletions src/streaming/controllers/StreamController.js
Expand Up @@ -1522,6 +1522,9 @@ function StreamController() {
if (config.segmentBaseController) {
segmentBaseController = config.segmentBaseController;
}
if (config.manifestUpdater) {
manifestUpdater = config.manifestUpdater;
}
}

function setProtectionData(protData) {
Expand Down Expand Up @@ -1607,6 +1610,12 @@ function StreamController() {
}
}

function refreshManifest() {
if (!manifestUpdater.getIsUpdating()) {
manifestUpdater.refreshManifest();
}
}

function getStreams() {
return streams;
}
Expand All @@ -1632,6 +1641,7 @@ function StreamController() {
getActiveStream,
getInitialPlayback,
getAutoPlay,
refreshManifest,
reset
};

Expand Down
4 changes: 4 additions & 0 deletions test/unit/mocks/ManifestUpdaterMock.js
Expand Up @@ -9,6 +9,10 @@ class ManifestUpdaterMock {
getManifestLoader() {
return this.manifestLoader;
}

refreshManifest() {}

getIsUpdating() {}
}

export default ManifestUpdaterMock;
2 changes: 2 additions & 0 deletions test/unit/mocks/StreamControllerMock.js
Expand Up @@ -14,6 +14,8 @@ class StreamControllerMock {
this.streams = streams;
}

refreshManifest() {}

getStreams() {
return this.streams;
}
Expand Down
111 changes: 111 additions & 0 deletions test/unit/streaming.MediaPlayer.js
Expand Up @@ -10,12 +10,16 @@ import MediaPlayerModelMock from './mocks//MediaPlayerModelMock';
import MediaControllerMock from './mocks/MediaControllerMock';
import ObjectUtils from './../../src/streaming/utils/ObjectUtils';
import Constants from '../../src/streaming/constants/Constants';
import Events from '../../src/core/events/Events';
import EventBus from '../../src/core/EventBus'
import Settings from '../../src/core/Settings';
import ABRRulesCollection from '../../src/streaming/rules/abr/ABRRulesCollection';
import CustomParametersModel from '../../src/streaming/models/CustomParametersModel';

const sinon = require('sinon');
const expect = require('chai').expect;
const ELEMENT_NOT_ATTACHED_ERROR = 'You must first call attachView() to set the video element before calling this method';
const SOURCE_NOT_ATTACHED_ERROR = 'You must first call attachSource() with a valid source before calling this method';
const PLAYBACK_NOT_INITIALIZED_ERROR = 'You must first call initialize() and set a valid source and view before calling this method';
const STREAMING_NOT_INITIALIZED_ERROR = 'You must first call initialize() and set a source before calling this method';
const MEDIA_PLAYER_NOT_INITIALIZED_ERROR = 'MediaPlayer not initialized!';
Expand Down Expand Up @@ -1082,6 +1086,22 @@ describe('MediaPlayer', function () {
it('Method attachSource should throw an exception', function () {
expect(player.attachSource).to.throw(MediaPlayer.NOT_INITIALIZED_ERROR_MSG);
});

it('Method refreshManifest should throw an exception', () => {
expect(player.refreshManifest).to.throw(MEDIA_PLAYER_NOT_INITIALIZED_ERROR);
});
});

describe('When it is not ready', () => {
it('triggers refreshManifest callback with an error', () => {
player.initialize(videoElementMock, null, false);

const stub = sinon.spy()

player.refreshManifest(stub)

expect(stub.calledWith(null, SOURCE_NOT_ATTACHED_ERROR)).to.be.true;
})
});
});

Expand Down Expand Up @@ -1113,3 +1133,94 @@ describe('MediaPlayer', function () {
});
});
});

describe('MediaPlayer with context injected', () => {
const specHelper = new SpecHelper();
const videoElementMock = new VideoElementMock();
const capaMock = new CapabilitiesMock();
const streamControllerMock = new StreamControllerMock();
const abrControllerMock = new AbrControllerMock();
const playbackControllerMock = new PlaybackControllerMock();
const mediaPlayerModel = new MediaPlayerModelMock();
const mediaControllerMock = new MediaControllerMock();

let player;
let eventBus;
let settings;

beforeEach(function () {
// tear down
player = null;
settings?.reset();
settings = null;
global.dashjs = {};

// init
const context = {};

const customParametersModel = CustomParametersModel(context).getInstance();
eventBus = EventBus(context).getInstance();
settings = Settings(context).getInstance();

player = MediaPlayer(context).create();

// to avoid unwanted log
const debug = player.getDebug();
expect(debug).to.exist; // jshint ignore:line

player.setConfig({
streamController: streamControllerMock,
capabilities: capaMock,
playbackController: playbackControllerMock,
mediaPlayerModel: mediaPlayerModel,
abrController: abrControllerMock,
mediaController: mediaControllerMock,
settings: settings,
customParametersModel
});
});

describe('Tools Functions', () => {
describe('When the player is initialised', () => {
before(() => {
sinon.spy(streamControllerMock, 'refreshManifest');
})

beforeEach(() => {
streamControllerMock.refreshManifest.resetHistory();

mediaControllerMock.reset();
});

it('should refresh manifest on the current stream', () => {
player.initialize(videoElementMock, specHelper.getDummyUrl(), false);

const stub = sinon.spy();

player.refreshManifest(stub);

expect(streamControllerMock.refreshManifest.calledOnce).to.be.true;

eventBus.trigger(Events.INTERNAL_MANIFEST_LOADED, { manifest: { __mocked: true } });

expect(stub.calledOnce).to.be.true;
expect(stub.calledWith(sinon.match({ __mocked: true }))).to.be.true;
});

it('should trigger refreshManifest callback with an error if refresh failed', () => {
player.initialize(videoElementMock, specHelper.getDummyUrl(), false);

const stub = sinon.spy();

player.refreshManifest(stub);

expect(streamControllerMock.refreshManifest.calledOnce).to.be.true;

eventBus.trigger(Events.INTERNAL_MANIFEST_LOADED, { error: 'Mocked!' });

expect(stub.calledOnce).to.be.true;
expect(stub.calledWith(null, 'Mocked!')).to.be.true;
});
})
})
})
33 changes: 33 additions & 0 deletions test/unit/streaming.controllers.StreamController.js
Expand Up @@ -19,6 +19,7 @@ import URIFragmentModelMock from './mocks/URIFragmentModelMock';
import CapabilitiesFilterMock from './mocks/CapabilitiesFilterMock';
import ContentSteeringControllerMock from './mocks/ContentSteeringControllerMock';
import TextControllerMock from './mocks/TextControllerMock';
import ManifestUpdaterMock from './mocks/ManifestUpdaterMock';
import ServiceDescriptionController from '../../src/dash/controllers/ServiceDescriptionController';

const chai = require('chai');
Expand Down Expand Up @@ -48,6 +49,7 @@ const uriFragmentModelMock = new URIFragmentModelMock();
const capabilitiesFilterMock = new CapabilitiesFilterMock();
const textControllerMock = new TextControllerMock();
const contentSteeringControllerMock = new ContentSteeringControllerMock();
const manifestUpdaterMock = new ManifestUpdaterMock();

Events.extend(ProtectionEvents);

Expand Down Expand Up @@ -479,4 +481,35 @@ describe('StreamController', function () {
});
});
});

describe('refreshManifest', function () {
beforeEach(function () {
streamController.setConfig({
manifestUpdater: manifestUpdaterMock
});
sinon.spy(manifestUpdaterMock, 'refreshManifest');
sinon.stub(manifestUpdaterMock, 'getIsUpdating');
});


afterEach(function () {
manifestUpdaterMock.refreshManifest.restore();
manifestUpdaterMock.getIsUpdating.restore();
});


it('calls refreshManifest on ManifestUpdater', function () {
streamController.refreshManifest();

expect(manifestUpdaterMock.refreshManifest.calledOnce).to.be.true;
});

it('does not call refreshManifest on ManifrstUpdater if it is already updating', function () {
manifestUpdaterMock.getIsUpdating.returns(true)

streamController.refreshManifest();

expect(manifestUpdaterMock.refreshManifest.notCalled).to.be.true;
})
});
});

0 comments on commit 3116846

Please sign in to comment.