diff --git a/package-lock.json b/package-lock.json index 32decc05d..807a1151a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6684,9 +6684,9 @@ "dev": true }, "m3u8-parser": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/m3u8-parser/-/m3u8-parser-2.1.0.tgz", - "integrity": "sha1-yBcDKewc1RXQ1Yu4t2LamJbLA2g=" + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/m3u8-parser/-/m3u8-parser-4.2.0.tgz", + "integrity": "sha512-LVHw0U6IPJjwk9i9f7Xe26NqaUHTNlIt4SSWoEfYFROeVKHN6MIjOhbRheI3dg8Jbq5WCuMFQ0QU3EgZpmzFPg==" }, "make-dir": { "version": "1.1.0", diff --git a/package.json b/package.json index 334beb71d..6bc8aa026 100644 --- a/package.json +++ b/package.json @@ -91,8 +91,8 @@ "dependencies": { "aes-decrypter": "1.0.3", "global": "^4.3.0", - "m3u8-parser": "2.1.0", "mpd-parser": "0.4.0", + "m3u8-parser": "4.2.0", "mux.js": "4.3.2", "url-toolkit": "^2.1.3", "video.js": "^6.2.0", diff --git a/src/sync-controller.js b/src/sync-controller.js index 560166b54..6b1d0c4d2 100644 --- a/src/sync-controller.js +++ b/src/sync-controller.js @@ -29,17 +29,38 @@ export const syncPointStrategies = [ { name: 'ProgramDateTime', run: (syncController, playlist, duration, currentTimeline, currentTime) => { - if (syncController.datetimeToDisplayTime && playlist.dateTimeObject) { - let playlistTime = playlist.dateTimeObject.getTime() / 1000; - let playlistStart = playlistTime + syncController.datetimeToDisplayTime; - let syncPoint = { - time: playlistStart, - segmentIndex: 0 - }; + if (!syncController.datetimeToDisplayTime) { + return null; + } - return syncPoint; + let segments = playlist.segments || []; + let syncPoint = null; + let lastDistance = null; + + currentTime = currentTime || 0; + + for (let i = 0; i < segments.length; i++) { + let segment = segments[i]; + + if (segment.dateTimeObject) { + let segmentTime = segment.dateTimeObject.getTime() / 1000; + let segmentStart = segmentTime + syncController.datetimeToDisplayTime; + let distance = Math.abs(currentTime - segmentStart); + + // Once the distance begins to increase, we have passed + // currentTime and can stop looking for better candidates + if (lastDistance !== null && lastDistance < distance) { + break; + } + + lastDistance = distance; + syncPoint = { + time: segmentStart, + segmentIndex: i + }; + } } - return null; + return syncPoint; } }, // Stategy "Segment": We have a known time mapping for a timeline and a @@ -336,8 +357,11 @@ export default class SyncController extends videojs.EventTarget { * @param {Playlist} playlist - The currently active playlist */ setDateTimeMapping(playlist) { - if (!this.datetimeToDisplayTime && playlist.dateTimeObject) { - let playlistTimestamp = playlist.dateTimeObject.getTime() / 1000; + if (!this.datetimeToDisplayTime && + playlist.segments && + playlist.segments.length && + playlist.segments[0].dateTimeObject) { + let playlistTimestamp = playlist.segments[0].dateTimeObject.getTime() / 1000; this.datetimeToDisplayTime = -playlistTimestamp; } diff --git a/test/sync-controller.test.js b/test/sync-controller.test.js index fa99ec9e5..3d1988c6a 100644 --- a/test/sync-controller.test.js +++ b/test/sync-controller.test.js @@ -46,7 +46,7 @@ QUnit.test('returns correct sync point for ProgramDateTime strategy', function(a assert.equal(syncPoint, null, 'no syncpoint when datetimeToDisplayTime not set'); - playlist.dateTimeObject = datetime; + playlist.segments[0].dateTimeObject = datetime; this.syncController.setDateTimeMapping(playlist); @@ -56,7 +56,7 @@ QUnit.test('returns correct sync point for ProgramDateTime strategy', function(a assert.equal(syncPoint, null, 'no syncpoint when datetimeObject not set on playlist'); - newPlaylist.dateTimeObject = new Date(2012, 11, 12, 12, 12, 22); + newPlaylist.segments[0].dateTimeObject = new Date(2012, 11, 12, 12, 12, 22); syncPoint = strategy.run(this.syncController, newPlaylist, duration, timeline); @@ -66,6 +66,58 @@ QUnit.test('returns correct sync point for ProgramDateTime strategy', function(a }, 'syncpoint found for ProgramDateTime set'); }); +QUnit.test('ProgramDateTime strategy finds nearest segment for sync', function(assert) { + let strategy = getStrategy('ProgramDateTime'); + let playlist = playlistWithDuration(40); + let timeline = 0; + let duration = Infinity; + let syncPoint; + + syncPoint = strategy.run(this.syncController, playlist, duration, timeline, 23); + + assert.equal(syncPoint, null, 'no syncpoint when datetimeToDisplayTime not set'); + + playlist.segments.forEach((segment, index) => { + segment.dateTimeObject = new Date(2012, 11, 12, 12, 12, 12 + (index * 10)); + }); + + this.syncController.setDateTimeMapping(playlist); + + let newPlaylist = playlistWithDuration(40); + + syncPoint = strategy.run(this.syncController, newPlaylist, duration, timeline); + + assert.equal(syncPoint, null, 'no syncpoint when datetimeObject not set on playlist'); + + newPlaylist.segments.forEach((segment, index) => { + segment.dateTimeObject = new Date(2012, 11, 12, 12, 12, 22 + (index * 10)); + }); + + syncPoint = strategy.run(this.syncController, newPlaylist, duration, timeline, 23); + + assert.deepEqual(syncPoint, { + time: 20, + segmentIndex: 1 + }, 'syncpoint found for ProgramDateTime set'); +}); + +QUnit.test('Does not set date time mapping if date time info not on first segment', +function(assert) { + let playlist = playlistWithDuration(40); + + playlist.segments[1].dateTimeObject = new Date(2012, 11, 12, 12, 12, 12); + + this.syncController.setDateTimeMapping(playlist); + + assert.notOk(this.syncController.datetimeToDisplayTime, 'did not set datetime mapping'); + + playlist.segments[0].dateTimeObject = new Date(2012, 11, 12, 12, 12, 2); + + this.syncController.setDateTimeMapping(playlist); + + assert.ok(this.syncController.datetimeToDisplayTime, 'did set date time mapping'); +}); + QUnit.test('returns correct sync point for Segment strategy', function(assert) { let strategy = getStrategy('Segment'); let playlist = {