From e34576d578f42eeaa28d1ec7d482e9f324707279 Mon Sep 17 00:00:00 2001 From: "AM\\haguirre" Date: Thu, 8 Mar 2018 20:25:53 -0800 Subject: [PATCH 1/3] Added 3 unit tests and changed some values that were milliseconds to seconds as they would be in real case scenario in order to properly test the feature --- tests/unit/controller/stream-controller.js | 101 +++++++++++++++------ 1 file changed, 75 insertions(+), 26 deletions(-) diff --git a/tests/unit/controller/stream-controller.js b/tests/unit/controller/stream-controller.js index fddb8991044..df92177dc01 100644 --- a/tests/unit/controller/stream-controller.js +++ b/tests/unit/controller/stream-controller.js @@ -85,13 +85,14 @@ describe('StreamController tests', function () { }); describe('PDT vs SN tests for discontinuities with PDT', function () { + let PDT = "Fri Sep 15 2017 12:11:01 GMT-0700 (Pacific Daylight Time)"; let fragPrevious = { pdt: 1505502671523, endPdt: 1505502676523, - duration: 5000, + duration: 5.000, level: 1, - start: 10000, - sn: 2, + start: 10.000, + sn: 2, //Fragment with PDT 1505502671523 in level 1 does not have the same sn as in level 2 where cc is 1 cc: 0 }; @@ -100,46 +101,46 @@ describe('StreamController tests', function () { pdt: 1505502661523, endPdt: 1505502666523, level: 2, - duration: 5000, + duration: 5.000, start: 0, sn: 0, cc: 0 }, - // Discontinuity with PDT 1505502671523 + // Discontinuity with PDT 1505502671523 which does not exist in level 1 as per fragPrevious { pdt: 1505502671523, endPdt: 1505502676523, level: 2, - duration: 5000, - start: 5000, - sn: 2, + duration: 5.000, + start: 5.000, + sn: 1, cc: 1 }, { pdt: 1505502676523, endPdt: 1505502681523, level: 2, - duration: 5000, - start: 10000, - sn: 3, + duration: 5.000, + start: 10.000, + sn: 2, cc: 1 }, { pdt: 1505502681523, endPdt: 1505502686523, level: 2, - duration: 5000, - start: 15000, - sn: 4, + duration: 5.000, + start: 15.000, + sn: 3, cc: 1 }, { pdt: 1505502686523, endPdt: 1505502691523, level: 2, - duration: 5000, - start: 20000, - sn: 5, + duration: 5.000, + start: 20.000, + sn: 4, cc: 1 } ]; @@ -148,23 +149,39 @@ describe('StreamController tests', function () { let levelDetails = { startSN: fragments[0].sn, endSN: fragments[fragments.length - 1].sn, - programDateTime: undefined // If this field is undefined SN search is used by default + programDateTime: undefined // If this field is undefined SN search is used by default, if set is PDT }; let bufferEnd = fragPrevious.start + fragPrevious.duration; let end = fragments[fragments.length - 1].start + fragments[fragments.length - 1].duration; - it('SN search choosing fragment after level loaded', function () { + it('SN search choosing wrong fragment (3 instead of 2) after level loaded', function () { let config = {}; let hls = { config: config, on: function () {} }; + levelDetails.programDateTime = undefined; let streamController = new StreamController(hls); let foundFragment = streamController._findFragment(0, fragPrevious, fragLen, fragments, bufferEnd, end, levelDetails); let resultSN = foundFragment ? foundFragment.sn : -1; - assert.equal(foundFragment, fragments[3], 'Expected sn 4, found sn segment ' + resultSN); + assert.equal(foundFragment, fragments[3], 'Expected sn 3, found sn segment ' + resultSN); + }); + + it('SN search choosing the right segment if fragPrevious is not available', function () { + let config = {}; + let hls = { + config: config, + on: function () {} + }; + levelDetails.programDateTime = undefined; + + let streamController = new StreamController(hls); + let foundFragment = streamController._findFragment(0, null, fragLen, fragments, bufferEnd, end, levelDetails); + + let resultSN = foundFragment ? foundFragment.sn : -1; + assert.equal(foundFragment, fragments[2], 'Expected sn 2, found sn segment ' + resultSN); }); it('PDT search choosing fragment after level loaded', function () { @@ -173,13 +190,45 @@ describe('StreamController tests', function () { config: config, on: function () {} }; - levelDetails.programDateTime = true;// If programDateTime contains a date then PDT is used (boolean used to mock) + levelDetails.programDateTime = PDT;// If programDateTime contains a date then PDT is used let streamController = new StreamController(hls); let foundFragment = streamController._findFragment(0, fragPrevious, fragLen, fragments, bufferEnd, end, levelDetails); let resultSN = foundFragment ? foundFragment.sn : -1; - assert.equal(foundFragment, fragments[2], 'Expected sn 3, found sn segment ' + resultSN); + assert.equal(foundFragment, fragments[2], 'Expected sn 2, found sn segment ' + resultSN); + }); + + it('PDT search choosing fragment after starting/seeking to a new position (bufferEnd used)', function () { + let config = {}; + let hls = { + config: config, + on: function () {} + }; + levelDetails.programDateTime = PDT;// If programDateTime contains a date then PDT is used + let mediaSeekingTime = 17.00; + + let streamController = new StreamController(hls); + let foundFragment = streamController._findFragment(0, null, fragLen, fragments, mediaSeekingTime, end, levelDetails); + + let resultSN = foundFragment ? foundFragment.sn : -1; + assert.equal(foundFragment, fragments[2], 'Expected sn 2, found sn segment ' + resultSN); + }); + + it('PDT serch hitting empty discontinuity', function () { + let config = {}; + let hls = { + config: config, + on: function () {} + }; + levelDetails.programDateTime = PDT;// If programDateTime contains a date then PDT is used + let discontinuityPDTHit = 6.00; + + let streamController = new StreamController(hls); + let foundFragment = streamController._findFragment(0, null, fragLen, fragments, discontinuityPDTHit, end, levelDetails); + + let resultSN = foundFragment ? foundFragment.sn : -1; + assert.equal(foundFragment, fragments[1], 'Expected sn 1, found sn segment ' + resultSN); }); it('Unit test _findFragmentBySN', function () { @@ -192,7 +241,7 @@ describe('StreamController tests', function () { let foundFragment = streamController._findFragmentBySN(fragPrevious, fragments, bufferEnd, end); let resultSN = foundFragment ? foundFragment.sn : -1; - assert.equal(foundFragment, fragments[3], 'Expected sn 4, found sn segment ' + resultSN); + assert.equal(foundFragment, fragments[3], 'Expected sn 3, found sn segment ' + resultSN); }); it('Unit test _findFragmentByPDT usual behaviour', function () { @@ -205,7 +254,7 @@ describe('StreamController tests', function () { let foundFragment = streamController._findFragmentByPDT(fragments, fragPrevious.endPdt + 1); let resultSN = foundFragment ? foundFragment.sn : -1; - assert.equal(foundFragment, fragments[2], 'Expected sn 3, found sn segment ' + resultSN); + assert.equal(foundFragment, fragments[2], 'Expected sn 2, found sn segment ' + resultSN); }); it('Unit test _findFragmentByPDT beyond limits', function () { @@ -234,7 +283,7 @@ describe('StreamController tests', function () { let foundFragment = streamController._findFragmentByPDT(fragments, fragments[0].pdt); let resultSN = foundFragment ? foundFragment.sn : -1; - assert.equal(foundFragment, fragments[0], 'Expected sn 1, found sn segment ' + resultSN); + assert.equal(foundFragment, fragments[0], 'Expected sn 0, found sn segment ' + resultSN); }); it('Unit test _findFragmentByPDT for last segment', function () { @@ -247,7 +296,7 @@ describe('StreamController tests', function () { let foundFragment = streamController._findFragmentByPDT(fragments, fragments[fragments.length - 1].pdt); let resultSN = foundFragment ? foundFragment.sn : -1; - assert.equal(foundFragment, fragments[4], 'Expected sn 5, found sn segment ' + resultSN); + assert.equal(foundFragment, fragments[4], 'Expected sn 4, found sn segment ' + resultSN); }); }); }); From 8680003e632f970815e35a3ca10bcf44a7cbee16 Mon Sep 17 00:00:00 2001 From: "AM\\haguirre" Date: Thu, 8 Mar 2018 20:35:41 -0800 Subject: [PATCH 2/3] Use a for instead of binary search to ensure that PDT discontinuities still return the next segment Fixed an error with the unit tests that made me think that BufferEnd was milliseconds when it is actually seconds MediaSeek should set fragPrevious to null in order not to confuse the algorithm --- src/controller/stream-controller.js | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/controller/stream-controller.js b/src/controller/stream-controller.js index 657c7d22d16..9b71a7c4577 100644 --- a/src/controller/stream-controller.js +++ b/src/controller/stream-controller.js @@ -383,9 +383,14 @@ class StreamController extends TaskLoop { if (PDTValue >= lastSegment.endPdt) return null; - return BinarySearch.search(fragments, function (frag) { - return PDTValue < frag.pdt ? -1 : PDTValue >= frag.endPdt ? 1 : 0; - }); + for(let seg = 0; seg < fragments.length; ++seg) + { + let frag = fragments[seg]; + if(PDTValue < frag.endPdt){ + return frag; + } + } + return null; } _findFragmentBySN (fragPrevious, fragments, bufferEnd, end) { @@ -440,7 +445,7 @@ class StreamController extends TaskLoop { if (!levelDetails.programDateTime) { // Uses buffer and sequence number to calculate switch segment (required if using EXT-X-DISCONTINUITY-SEQUENCE) foundFrag = this._findFragmentBySN(fragPrevious, fragments, bufferEnd, end); } else { // Relies on PDT in order to switch bitrates (Support EXT-X-DISCONTINUITY without EXT-X-DISCONTINUITY-SEQUENCE) - foundFrag = this._findFragmentByPDT(fragments, fragPrevious ? fragPrevious.endPdt + 1 : bufferEnd + (levelDetails.programDateTime ? Date.parse(levelDetails.programDateTime) : 0)); + foundFrag = this._findFragmentByPDT(fragments, fragPrevious ? fragPrevious.endPdt + 1 : (bufferEnd * 1000) + (levelDetails.programDateTime ? Date.parse(levelDetails.programDateTime) : 0)); } } else { // reach end of playlist @@ -813,6 +818,8 @@ class StreamController extends TaskLoop { if (!this.loadedmetadata) this.nextLoadPosition = this.startPosition = currentTime; + this.fragPrevious = null; + // tick to speed up processing this.tick(); } From 3da90f403fde33ec157cb3741a34a2c75e70a1d8 Mon Sep 17 00:00:00 2001 From: "AM\\haguirre" Date: Sat, 17 Mar 2018 14:58:31 +0900 Subject: [PATCH 3/3] Relying on BufferEnd rather than previous fragment as I can't ensure contiguity after a media seek. --- src/controller/stream-controller.js | 4 +--- tests/unit/controller/stream-controller.js | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/controller/stream-controller.js b/src/controller/stream-controller.js index 9b71a7c4577..6638ee5da66 100644 --- a/src/controller/stream-controller.js +++ b/src/controller/stream-controller.js @@ -445,7 +445,7 @@ class StreamController extends TaskLoop { if (!levelDetails.programDateTime) { // Uses buffer and sequence number to calculate switch segment (required if using EXT-X-DISCONTINUITY-SEQUENCE) foundFrag = this._findFragmentBySN(fragPrevious, fragments, bufferEnd, end); } else { // Relies on PDT in order to switch bitrates (Support EXT-X-DISCONTINUITY without EXT-X-DISCONTINUITY-SEQUENCE) - foundFrag = this._findFragmentByPDT(fragments, fragPrevious ? fragPrevious.endPdt + 1 : (bufferEnd * 1000) + (levelDetails.programDateTime ? Date.parse(levelDetails.programDateTime) : 0)); + foundFrag = this._findFragmentByPDT(fragments, (bufferEnd * 1000) + (levelDetails.programDateTime ? Date.parse(levelDetails.programDateTime) : 0)); } } else { // reach end of playlist @@ -818,8 +818,6 @@ class StreamController extends TaskLoop { if (!this.loadedmetadata) this.nextLoadPosition = this.startPosition = currentTime; - this.fragPrevious = null; - // tick to speed up processing this.tick(); } diff --git a/tests/unit/controller/stream-controller.js b/tests/unit/controller/stream-controller.js index df92177dc01..6c5841f260e 100644 --- a/tests/unit/controller/stream-controller.js +++ b/tests/unit/controller/stream-controller.js @@ -85,7 +85,7 @@ describe('StreamController tests', function () { }); describe('PDT vs SN tests for discontinuities with PDT', function () { - let PDT = "Fri Sep 15 2017 12:11:01 GMT-0700 (Pacific Daylight Time)"; + let PDT = "Fri Sep 15 2017 12:11:01:523 GMT-0700 (Pacific Daylight Time)"; let fragPrevious = { pdt: 1505502671523, endPdt: 1505502676523,