Skip to content

Commit 79f30d4

Browse files
committed
Add additional unit tests for manualQualitySwitches
1 parent 3363547 commit 79f30d4

File tree

5 files changed

+121
-13
lines changed

5 files changed

+121
-13
lines changed

src/dash/utils/SegmentsUtils.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -271,7 +271,7 @@ function getTotalNumberOfPartialSegments(element) {
271271

272272
export {
273273
getIndexBasedSegment,
274-
getTotalNumberOfPartialSegments,
275274
getTimeBasedSegment,
275+
getTotalNumberOfPartialSegments,
276276
processUriTemplate,
277277
};

src/dash/utils/TimelineSegmentsGetter.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ function TimelineSegmentsGetter(config, isDynamic) {
5151
if (!representation) {
5252
return null;
5353
}
54-
const safetyOffset = 0.05;
54+
const safetyOffset = 0.01;
5555
const requestedPresentationTime = lastSegment && !isNaN(lastSegment.presentationStartTime) ? lastSegment.presentationStartTime + lastSegment.duration + safetyOffset : 0;
5656

5757
return getSegmentByTime(representation, requestedPresentationTime);

src/streaming/controllers/AbrController.js

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -566,12 +566,12 @@ function AbrController() {
566566
function _createRulesContext(streamProcessor, currentRequest) {
567567
return RulesContext(context).create({
568568
abrController: instance,
569-
streamProcessor,
569+
adapter,
570570
currentRequest,
571-
switchRequestHistory,
572571
droppedFramesHistory,
572+
streamProcessor,
573+
switchRequestHistory,
573574
throughputController,
574-
adapter,
575575
videoModel
576576
});
577577
}
@@ -880,7 +880,6 @@ function AbrController() {
880880
*/
881881
function _changeQuality(type, oldRepresentation, newRepresentation, reason) {
882882
const streamId = newRepresentation.mediaInfo.streamInfo.id;
883-
884883
if (!type || !streamProcessorDict[streamId] || !streamProcessorDict[streamId][type]) {
885884
return false
886885
}
@@ -891,7 +890,7 @@ function AbrController() {
891890
const oldBitrate = oldRepresentation ? oldRepresentation.bitrateInKbit : 0;
892891

893892
logger.info(`[AbrController]: Switching quality in period ${streamId} for media type ${type}. Switch from bitrate ${oldBitrate} to bitrate ${newRepresentation.bitrateInKbit}. Current buffer level: ${bufferLevel}. Reason:` + (reason ? JSON.stringify(reason) : '/'));
894-
eventBus.trigger(Events.QUALITY_CHANGE_REQUESTED,
893+
eventBus.trigger(MediaPlayerEvents.QUALITY_CHANGE_REQUESTED,
895894
{
896895
isAdaptationSetSwitch,
897896
mediaType: type,

test/unit/mocks/AdapterMock.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,10 @@ function AdapterMock() {
221221
}];
222222
};
223223

224+
this.areMediaInfosEqual = function () {
225+
return true
226+
}
227+
224228
}
225229

226230
export default AdapterMock;

test/unit/test/streaming/streaming.controllers.AbrController.js

Lines changed: 111 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -442,7 +442,7 @@ describe('AbrController', function () {
442442
});
443443
})
444444

445-
describe('abrCtrl.canPerformQualitySwitch()', function () {
445+
describe('canPerformQualitySwitch()', function () {
446446

447447
it('should return true if lastSegment is undefined', function() {
448448
expect(abrCtrl.canPerformQualitySwitch(undefined, {})).to.be.true;
@@ -563,6 +563,111 @@ describe('AbrController', function () {
563563

564564
})
565565

566+
describe('manuallySetPlaybackQuality', function () {
567+
568+
it('should immediately switch quality when canPerformQualitySwitch returns true', function (done) {
569+
// Override streamProcessor to simulate current representation and last segment allowing switch
570+
const rep0 = dummyRepresentations[0];
571+
const rep1 = dummyRepresentations[1];
572+
const streamInfo = dummyMediaInfo.streamInfo;
573+
574+
streamProcessor.getRepresentation = () => rep0; // current representation
575+
streamProcessor.getRepresentationController = () => {
576+
return {
577+
getCurrentCompositeRepresentation: () => rep0
578+
}
579+
}
580+
streamProcessor.getLastSegment = () => undefined;
581+
582+
const onQualityChange = function (e) {
583+
expect(e.oldRepresentation.id).to.be.equal(rep0.id);
584+
expect(e.newRepresentation.id).to.be.equal(rep1.id);
585+
eventBus.off(MediaPlayerEvents.QUALITY_CHANGE_REQUESTED, onQualityChange);
586+
done();
587+
};
588+
eventBus.on(MediaPlayerEvents.QUALITY_CHANGE_REQUESTED, onQualityChange, this);
589+
590+
// First set current quality manually to rep0 so subsequent switch has oldRepresentation
591+
abrCtrl.setPlaybackQuality(Constants.VIDEO, streamInfo, rep0);
592+
// Now manually trigger switch to rep1
593+
abrCtrl.manuallySetPlaybackQuality(Constants.VIDEO, streamInfo, rep1, { reason: 'manual-test' });
594+
});
595+
596+
597+
it('should queue manual quality switch when canPerformQualitySwitch returns false', function () {
598+
const rep0 = dummyRepresentations[0];
599+
const rep1 = dummyRepresentations[1];
600+
const streamInfo = dummyMediaInfo.streamInfo;
601+
602+
// Set current representation
603+
streamProcessor.getRepresentation = () => rep0;
604+
streamProcessor.getRepresentationController = () => {
605+
return {
606+
getCurrentCompositeRepresentation: () => rep0
607+
}
608+
}
609+
// Simulate a last segment that blocks switching (partial segment with unsuitable properties)
610+
streamProcessor.getLastSegment = () => ({
611+
isPartialSegment: true,
612+
totalNumberOfPartialSegments: 4,
613+
replacementSubNumber: 1
614+
});
615+
// Current representation missing segmentSequenceProperties => canPerformQualitySwitch should be false
616+
rep0.segmentSequenceProperties = [];
617+
618+
const spy = sinon.spy();
619+
eventBus.on(MediaPlayerEvents.QUALITY_CHANGE_REQUESTED, spy, this);
620+
621+
abrCtrl.manuallySetPlaybackQuality(Constants.VIDEO, streamInfo, rep1, { reason: 'manual-test' });
622+
623+
// No immediate quality change event emitted
624+
expect(spy.notCalled).to.be.true;
625+
});
626+
627+
it('should execute queued manual quality switch when handlePendingManualQualitySwitch is called and conditions allow switching', function (done) {
628+
const rep0 = dummyRepresentations[0];
629+
const rep1 = dummyRepresentations[1];
630+
const streamInfo = dummyMediaInfo.streamInfo;
631+
632+
// First block switching so it gets queued
633+
streamProcessor.getRepresentation = () => rep0;
634+
streamProcessor.getRepresentationController = () => {
635+
return {
636+
getCurrentCompositeRepresentation: () => rep0
637+
}
638+
}
639+
streamProcessor.getLastSegment = () => ({
640+
isPartialSegment: true,
641+
totalNumberOfPartialSegments: 4,
642+
replacementSubNumber: 1
643+
});
644+
rep0.segmentSequenceProperties = [];
645+
646+
abrCtrl.manuallySetPlaybackQuality(Constants.VIDEO, streamInfo, rep1, { reason: 'queue-test' });
647+
648+
// Now allow switching by changing last segment to end of sequence
649+
streamProcessor.getLastSegment = () => ({
650+
isPartialSegment: true,
651+
totalNumberOfPartialSegments: 4,
652+
replacementSubNumber: 3 // end of sequence -> allows switch
653+
});
654+
// Provide segmentSequenceProperties so canPerformQualitySwitch returns true; but because replacementSubNumber === totalNumber-1 it already allows
655+
rep0.segmentSequenceProperties = [new SegmentSequenceProperties()];
656+
657+
const onQualityChange = (e) => {
658+
expect(e.oldRepresentation.id).to.be.equal(rep0.id);
659+
expect(e.newRepresentation.id).to.be.equal(rep1.id);
660+
eventBus.off(MediaPlayerEvents.QUALITY_CHANGE_REQUESTED, onQualityChange);
661+
done();
662+
};
663+
eventBus.on(MediaPlayerEvents.QUALITY_CHANGE_REQUESTED, onQualityChange, this);
664+
665+
// Trigger handling of queued switch
666+
const handled = abrCtrl.handlePendingManualQualitySwitch(streamInfo.id, Constants.VIDEO);
667+
expect(handled).to.be.true;
668+
});
669+
})
670+
566671
describe('Additional Tests', function () {
567672
it('should return null when attempting to get abandonment state when abandonmentStateDict array is empty', function () {
568673
const state = abrCtrl.getAbandonmentStateFor('1', Constants.AUDIO);
@@ -595,20 +700,20 @@ describe('AbrController', function () {
595700
it('should switch to a new enhancement Representation and have the correct dependentRep', function (done) {
596701
const enhancementRepresentation = dummyRepresentations[2];
597702
const dependentRepresentation = dummyRepresentations[0];
598-
703+
599704
const onQualityChange = (e) => {
600705
expect(e.oldRepresentation).to.not.exist;
601-
706+
602707
// Representation 2 should have dependentRepresentation with id 0
603708
expect(e.newRepresentation.id).to.be.equal(enhancementRepresentation.id);
604709
expect(e.newRepresentation.dependentRepresentation.id).to.be.equal(dependentRepresentation.id);
605-
710+
606711
eventBus.off(MediaPlayerEvents.QUALITY_CHANGE_REQUESTED, onQualityChange);
607712
done();
608713
}
609-
714+
610715
eventBus.on(MediaPlayerEvents.QUALITY_CHANGE_REQUESTED, onQualityChange, this);
611-
716+
612717
abrCtrl.setPlaybackQuality(Constants.VIDEO, enhancementMediaInfo.streamInfo, enhancementRepresentation);
613718
});
614719

0 commit comments

Comments
 (0)