From cebe2ff70f36ad7383c11732519c62cdd648d2ae Mon Sep 17 00:00:00 2001 From: Guillaume du Pontavice Date: Tue, 29 Jan 2019 21:58:14 -0800 Subject: [PATCH 01/14] tsdemuxer: if PES does not contain PTS/DTS, use last PES PTS/DTS instead --- src/demux/tsdemuxer.js | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/src/demux/tsdemuxer.js b/src/demux/tsdemuxer.js index 81ae6e7b9e9..9e396b8a4da 100644 --- a/src/demux/tsdemuxer.js +++ b/src/demux/tsdemuxer.js @@ -184,7 +184,7 @@ class TSDemuxer { switch (pid) { case avcId: if (stt) { - if (avcData && (pes = parsePES(avcData)) && pes.pts !== undefined) { + if (avcData && (pes = parsePES(avcData))) { parseAVCPES(pes, false); } @@ -197,7 +197,7 @@ class TSDemuxer { break; case audioId: if (stt) { - if (audioData && (pes = parsePES(audioData)) && pes.pts !== undefined) { + if (audioData && (pes = parsePES(audioData))) { if (audioTrack.isAAC) { parseAACPES(pes); } else { @@ -213,7 +213,7 @@ class TSDemuxer { break; case id3Id: if (stt) { - if (id3Data && (pes = parsePES(id3Data)) && pes.pts !== undefined) { + if (id3Data && (pes = parsePES(id3Data))) { parseID3PES(pes); } @@ -279,7 +279,7 @@ class TSDemuxer { } } // try to parse last PES packets - if (avcData && (pes = parsePES(avcData)) && pes.pts !== undefined) { + if (avcData && (pes = parsePES(avcData))) { parseAVCPES(pes, true); avcTrack.pesData = null; } else { @@ -287,7 +287,7 @@ class TSDemuxer { avcTrack.pesData = avcData; } - if (audioData && (pes = parsePES(audioData)) && pes.pts !== undefined) { + if (audioData && (pes = parsePES(audioData))) { if (audioTrack.isAAC) { parseAACPES(pes); } else { @@ -304,7 +304,7 @@ class TSDemuxer { audioTrack.pesData = audioData; } - if (id3Data && (pes = parsePES(id3Data)) && pes.pts !== undefined) { + if (id3Data && (pes = parsePES(id3Data))) { parseID3PES(pes); id3Track.pesData = null; } else { @@ -534,6 +534,18 @@ class TSDemuxer { if (avcSample.units.length && avcSample.frame) { const samples = avcTrack.samples; const nbSamples = samples.length; + // if sample does not have PTS/DTS, patch with last sample PTS/DTS + if (isNaN(avcSample.pts)) { + if (nbSamples) { + const lastSample = samples[nbSamples - 1]; + avcSample.pts = lastSample.pts; + avcSample.dts = lastSample.dts; + } else { + // dropping samples, no timestamp found + avcTrack.dropped++; + return; + } + } // only push AVC sample if starting with a keyframe is not mandatory OR // if keyframe already found in this fragment OR // keyframe found in last fragment (track.sps) AND From 354b871a609c2eb50f3cf7e265053565cc1ab25b Mon Sep 17 00:00:00 2001 From: alex-gusev Date: Sat, 17 Aug 2019 18:46:11 +0300 Subject: [PATCH 02/14] load audio playlist on MANIFEST_PARSED --- src/controller/audio-track-controller.js | 35 ++++++++++------- .../unit/controller/audio-track-controller.js | 38 ++++++++++++++++++- 2 files changed, 58 insertions(+), 15 deletions(-) diff --git a/src/controller/audio-track-controller.js b/src/controller/audio-track-controller.js index 8d57008f165..e440a05325c 100644 --- a/src/controller/audio-track-controller.js +++ b/src/controller/audio-track-controller.js @@ -91,6 +91,8 @@ class AudioTrackController extends TaskLoop { onManifestParsed (data) { const tracks = this.tracks = data.audioTracks || []; this.hls.trigger(Event.AUDIO_TRACKS_UPDATED, { audioTracks: tracks }); + + this._selectAudioGroup(this.hls.nextLoadLevel); } /** @@ -150,20 +152,7 @@ class AudioTrackController extends TaskLoop { * @param {*} data */ onLevelLoaded (data) { - // FIXME: crashes because currentLevel is undefined - // const levelInfo = this.hls.levels[this.hls.currentLevel]; - - const levelInfo = this.hls.levels[data.level]; - - if (!levelInfo.audioGroupIds) { - return; - } - - const audioGroupId = levelInfo.audioGroupIds[levelInfo.urlId]; - if (this.audioGroupId !== audioGroupId) { - this.audioGroupId = audioGroupId; - this._selectInitialAudioTrack(); - } + this._selectAudioGroup(data.level); } /** @@ -252,6 +241,24 @@ class AudioTrackController extends TaskLoop { this._updateTrack(this._trackId); } + /** + * @param levelId + * @private + */ + _selectAudioGroup (levelId) { + const levelInfo = this.hls.levels[levelId]; + + if (!levelInfo || !levelInfo.audioGroupIds) { + return; + } + + const audioGroupId = levelInfo.audioGroupIds[levelInfo.urlId]; + if (this.audioGroupId !== audioGroupId) { + this.audioGroupId = audioGroupId; + this._selectInitialAudioTrack(); + } + } + /** * Select initial track * @private diff --git a/tests/unit/controller/audio-track-controller.js b/tests/unit/controller/audio-track-controller.js index 6ecae5db444..d54b23c05eb 100644 --- a/tests/unit/controller/audio-track-controller.js +++ b/tests/unit/controller/audio-track-controller.js @@ -3,7 +3,7 @@ import Hls from '../../../src/hls'; const sinon = require('sinon'); -describe('AudioTrackController', function () { +describe.only('AudioTrackController', function () { const tracks = [{ groupId: '1', id: 0, @@ -80,6 +80,42 @@ describe('AudioTrackController', function () { }); }); + it.only('should select audioGroupId and trigger AUDIO_TRACK_SWITCHING', function (done) { + hls.on(Hls.Events.AUDIO_TRACK_SWITCHING, (event, data) => { + done(); + }); + + const levels = [ + { + urlId: 1, + audioGroupIds: ['1', '2'] + } + ]; + + hls.levelController = { + levels + }; + + const newLevelInfo = levels[0]; + const newGroupId = newLevelInfo.audioGroupIds[newLevelInfo.urlId]; + + audioTrackController.audioGroupId = '1'; + audioTrackController.tracks = tracks; + audioTrackController.audioTrack = 2; + + // current track name + const audioTrackName = tracks[audioTrackController.audioTrack].name; + + audioTrackController.onManifestParsed({ + audioTracks: tracks + }); + + // group has switched + expect(audioTrackController.audioGroupId).to.equal(newGroupId); + // name is still the same + expect(tracks[audioTrackController.audioTrack].name).to.equal(audioTrackName); + }); + describe('_needsTrackLoading', function () { it('should not need loading because the audioTrack is embedded in the main playlist', function () { expect(audioTrackController._needsTrackLoading({ details: { live: true } })).to.be.false; From 688944aa6bee1b9e2bd8dad14e6c9f627ebead29 Mon Sep 17 00:00:00 2001 From: alex-gusev Date: Sun, 18 Aug 2019 20:22:10 +0300 Subject: [PATCH 03/14] remove .only --- tests/unit/controller/audio-track-controller.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/unit/controller/audio-track-controller.js b/tests/unit/controller/audio-track-controller.js index d54b23c05eb..8956d220f1c 100644 --- a/tests/unit/controller/audio-track-controller.js +++ b/tests/unit/controller/audio-track-controller.js @@ -3,7 +3,7 @@ import Hls from '../../../src/hls'; const sinon = require('sinon'); -describe.only('AudioTrackController', function () { +describe('AudioTrackController', function () { const tracks = [{ groupId: '1', id: 0, @@ -80,7 +80,7 @@ describe.only('AudioTrackController', function () { }); }); - it.only('should select audioGroupId and trigger AUDIO_TRACK_SWITCHING', function (done) { + it('should select audioGroupId and trigger AUDIO_TRACK_SWITCHING', function (done) { hls.on(Hls.Events.AUDIO_TRACK_SWITCHING, (event, data) => { done(); }); From 61ed761ff4ae750994ffa6a3bd264ccf49308809 Mon Sep 17 00:00:00 2001 From: alex-gusev Date: Sun, 6 Oct 2019 17:38:10 +0300 Subject: [PATCH 04/14] trigger ci From 3fd87fec09671e45affea864db0551d6f45fd2a6 Mon Sep 17 00:00:00 2001 From: jony89 Date: Sat, 18 Jan 2020 12:05:38 +0200 Subject: [PATCH 05/14] fix: computeLivePosition minimum value of media.currentTime --- src/controller/base-stream-controller.js | 3 ++- src/controller/stream-controller.js | 4 ++-- tests/unit/controller/stream-controller.js | 26 ++++++++++++++++++++++ 3 files changed, 30 insertions(+), 3 deletions(-) diff --git a/src/controller/base-stream-controller.js b/src/controller/base-stream-controller.js index ae3348d7cd1..ab3b066da18 100644 --- a/src/controller/base-stream-controller.js +++ b/src/controller/base-stream-controller.js @@ -128,6 +128,7 @@ export default class BaseStreamController extends TaskLoop { computeLivePosition (sliding, levelDetails) { let targetLatency = this.config.liveSyncDuration !== undefined ? this.config.liveSyncDuration : this.config.liveSyncDurationCount * levelDetails.targetduration; - return sliding + Math.max(0, levelDetails.totalduration - targetLatency); + const currentTime = this.media ? this.media.currentTime : null; + return Math.max(sliding + Math.max(0, levelDetails.totalduration - targetLatency), currentTime); } } diff --git a/src/controller/stream-controller.js b/src/controller/stream-controller.js index 701acc58b2f..d0869e372d0 100644 --- a/src/controller/stream-controller.js +++ b/src/controller/stream-controller.js @@ -280,9 +280,9 @@ class StreamController extends BaseStreamController { if (bufferEnd < Math.max(start - config.maxFragLookUpTolerance, end - maxLatency)) { let liveSyncPosition = this.liveSyncPosition = this.computeLivePosition(start, levelDetails); - logger.log(`buffer end: ${bufferEnd.toFixed(3)} is located too far from the end of live sliding playlist, reset currentTime to : ${liveSyncPosition.toFixed(3)}`); bufferEnd = liveSyncPosition; - if (media && !media.paused && media.readyState && media.duration > liveSyncPosition) { + if (media && !media.paused && media.readyState && media.duration > liveSyncPosition && liveSyncPosition > media.currentTime) { + logger.log(`buffer end: ${bufferEnd.toFixed(3)} is located too far from the end of live sliding playlist, reset currentTime to : ${liveSyncPosition.toFixed(3)}`); media.currentTime = liveSyncPosition; } diff --git a/tests/unit/controller/stream-controller.js b/tests/unit/controller/stream-controller.js index 3df11e27ca1..428b25a4bef 100644 --- a/tests/unit/controller/stream-controller.js +++ b/tests/unit/controller/stream-controller.js @@ -305,4 +305,30 @@ describe('StreamController', function () { }); }); }); + + describe('computeLivePosition', function () { + it('should return the current live sync position according to liveSyncDuration', function () { + streamController.config.liveSyncDuration = 2; + const levelDetails = { totalduration: 20 }; + const liveSyncPosition = streamController.computeLivePosition(8, levelDetails); + expect(liveSyncPosition).to.equal(26); + }); + + it('should return the current live sync position according to liveSyncDurationCount', function () { + streamController.config.liveSyncDurationCount = 2; + const levelDetails = { totalduration: 20, targetduration: 0.5 }; + const liveSyncPosition = streamController.computeLivePosition(8, levelDetails); + expect(liveSyncPosition).to.equal(27); + }); + + it('should not return value that is less than the video current time', function () { + streamController.media = { + currentTime: 25 + }; + streamController.config.liveSyncDuration = 1; + const levelDetails = { totalduration: 14 }; + const liveSyncPosition = streamController.computeLivePosition(8, levelDetails); + expect(liveSyncPosition).to.equal(25); + }); + }); }); From b604c4fff23689d95c71c91906fe448d874d2eaf Mon Sep 17 00:00:00 2001 From: Rob Walch Date: Mon, 27 Jan 2020 19:05:19 -0500 Subject: [PATCH 06/14] Use fragment stats in loaders and reset stats on load --- src/loader/fragment-loader.ts | 2 +- src/loader/level-details.ts | 2 +- src/loader/load-stats.ts | 10 ++++++++++ src/loader/m3u8-parser.ts | 2 +- src/types/events.ts | 6 +++--- src/utils/fetch-loader.ts | 3 ++- src/utils/xhr-loader.ts | 3 ++- 7 files changed, 20 insertions(+), 8 deletions(-) diff --git a/src/loader/fragment-loader.ts b/src/loader/fragment-loader.ts index ea608211d0c..21c028fe2fe 100644 --- a/src/loader/fragment-loader.ts +++ b/src/loader/fragment-loader.ts @@ -112,7 +112,7 @@ export default class FragmentLoader { } }; // Assign frag stats to the loader's stats reference - frag.stats = loader.stats; + loader.stats = frag.stats; // Loaders are used once per fragment and should be reset at this point console.assert(!frag.stats.loading.start, 'Frag stats should be unset before loading'); loader.load(loaderContext, loaderConfig, callbacks); diff --git a/src/loader/level-details.ts b/src/loader/level-details.ts index deb821dd745..0f5e59c142f 100644 --- a/src/loader/level-details.ts +++ b/src/loader/level-details.ts @@ -20,7 +20,7 @@ export default class LevelDetails { public tload?: number; public totalduration: number = 0; public type: string | null = null; - public updated?: boolean; // Manifest reload synchronization + public updated: boolean = true; public misses: number = 0; public url: string; public version: number | null = null; diff --git a/src/loader/load-stats.ts b/src/loader/load-stats.ts index 278a4ed0359..c500f30d5ea 100644 --- a/src/loader/load-stats.ts +++ b/src/loader/load-stats.ts @@ -11,3 +11,13 @@ export default class LoadStats implements LoaderStats { parsing: HlsPerformanceTiming = { start: 0, end: 0 }; buffering: HlsProgressivePerformanceTiming = { start: 0, first: 0, end: 0 }; } + +export function reset (stats: LoaderStats) { + stats.loading = { start: 0, first: 0, end: 0 }; + stats.parsing = { start: 0, end: 0 }; + stats.buffering = { start: 0, first: 0, end: 0 }; + stats.loaded = 0; + stats.aborted = false; + stats.retry = 0; + stats.chunkCount = 0; +} diff --git a/src/loader/m3u8-parser.ts b/src/loader/m3u8-parser.ts index 82f59a7eefc..d6731d437d5 100644 --- a/src/loader/m3u8-parser.ts +++ b/src/loader/m3u8-parser.ts @@ -146,7 +146,7 @@ export default class M3U8Parser { return medias; } - static parseLevelPlaylist (string: string, baseurl: string, id: number, type: PlaylistLevelType, levelUrlId: number) { + static parseLevelPlaylist (string: string, baseurl: string, id: number, type: PlaylistLevelType, levelUrlId: number): LevelDetails { let currentSN = 0; let totalduration = 0; const level = new LevelDetails(baseurl); diff --git a/src/types/events.ts b/src/types/events.ts index d8f3b6057c9..5f233f23f37 100644 --- a/src/types/events.ts +++ b/src/types/events.ts @@ -136,7 +136,7 @@ export interface AudioTrackLoadingData { } export interface AudioTrackLoadedData { - details: any; // LevelDetails type? + details: LevelDetails; id: number; stats: LoaderStats; networkDetails: unknown; @@ -160,7 +160,7 @@ export interface SubtitleTrackLoadingData { } export interface SubtitleTrackLoadedData { - details: any; // LevelDetails type? + details: LevelDetails; id: number | null; stats: LoaderStats; networkDetails: unknown; @@ -176,7 +176,7 @@ export interface SubtitleFragProcessed { } export interface FragChangedData { - frag: any; + frag: Fragment; } export interface FPSDropData { diff --git a/src/utils/fetch-loader.ts b/src/utils/fetch-loader.ts index 22273c0ef36..e234ce686a8 100644 --- a/src/utils/fetch-loader.ts +++ b/src/utils/fetch-loader.ts @@ -6,7 +6,7 @@ import { LoaderConfiguration, LoaderOnProgress } from '../types/loader'; -import LoadStats from '../loader/load-stats'; +import LoadStats, { reset } from '../loader/load-stats'; import ChunkCache from '../demux/chunk-cache'; const { fetch, AbortController, ReadableStream, Request, Headers, performance } = self; @@ -56,6 +56,7 @@ class FetchLoader implements Loader { load (context: LoaderContext, config: LoaderConfiguration, callbacks: LoaderCallbacks): void { const stats = this.stats; + reset(stats); stats.loading.start = performance.now(); const initParams = getRequestParameters(context, this.controller.signal); diff --git a/src/utils/xhr-loader.ts b/src/utils/xhr-loader.ts index 1cde17867f6..1513a5f8b36 100644 --- a/src/utils/xhr-loader.ts +++ b/src/utils/xhr-loader.ts @@ -1,6 +1,6 @@ import { logger } from '../utils/logger'; import { LoaderCallbacks, LoaderContext, LoaderStats, Loader, LoaderConfiguration } from '../types/loader'; -import LoadStats from '../loader/load-stats'; +import LoadStats, { reset } from '../loader/load-stats'; class XhrLoader implements Loader { private xhrSetup: Function | null; @@ -49,6 +49,7 @@ class XhrLoader implements Loader { this.context = context; this.config = config; this.callbacks = callbacks; + reset(this.stats); this.stats.loading.start = performance.now(); this.retryDelay = config.retryDelay; this.loadInternal(); From e70a5d370d006efbd717fb683d1288b5d6f752cb Mon Sep 17 00:00:00 2001 From: Rob Walch Date: Tue, 28 Jan 2020 17:13:18 -0500 Subject: [PATCH 07/14] Remove stats assert since loader resets stats --- src/loader/fragment-loader.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/loader/fragment-loader.ts b/src/loader/fragment-loader.ts index 21c028fe2fe..c5b60e73928 100644 --- a/src/loader/fragment-loader.ts +++ b/src/loader/fragment-loader.ts @@ -113,8 +113,6 @@ export default class FragmentLoader { }; // Assign frag stats to the loader's stats reference loader.stats = frag.stats; - // Loaders are used once per fragment and should be reset at this point - console.assert(!frag.stats.loading.start, 'Frag stats should be unset before loading'); loader.load(loaderContext, loaderConfig, callbacks); }); } From 9ee18eaa13140f3290f863790675e0c05784ae0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Go=C3=9Fe?= Date: Tue, 25 Feb 2020 09:33:25 +0100 Subject: [PATCH 08/14] Fix formatting of example --- README.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 80f2ef378db..4fde9fe41d2 100644 --- a/README.md +++ b/README.md @@ -63,22 +63,22 @@ Find the commit on [https://github.com/video-dev/hls.js/blob/deployments/README.