diff --git a/lib/hls/hls_parser.js b/lib/hls/hls_parser.js index 035dc988ea..36cb32c4af 100644 --- a/lib/hls/hls_parser.js +++ b/lib/hls/hls_parser.js @@ -401,6 +401,11 @@ shaka.hls.HlsParser = class { mediaSequenceNumber); stream.segmentIndex.evict(playlistStartTime); } + const oldSegment = segments[0]; + goog.asserts.assert(oldSegment, 'Should have segments!'); + + streamInfo.minTimestamp = oldSegment.startTime; + const newestSegment = segments[segments.length - 1]; goog.asserts.assert(newestSegment, 'Should have segments!'); @@ -578,6 +583,9 @@ shaka.hls.HlsParser = class { * @private */ offsetStreamInfo_(streamInfo, offset) { + // Adjust our accounting of the minimum timestamp. + streamInfo.minTimestamp += offset; + // Adjust our accounting of the maximum timestamp. streamInfo.maxTimestamp += offset; goog.asserts.assert(streamInfo.maxTimestamp >= 0, @@ -1068,7 +1076,7 @@ shaka.hls.HlsParser = class { const PresentationType = shaka.hls.HlsParser.PresentationType_; if (this.presentationType_ == PresentationType.LIVE) { - let segmentAvailabilityDuration = this.getMinDuration_(); + let segmentAvailabilityDuration = this.getLiveDuration_(); // This defaults to the presentation delay, which has the effect of // making the live stream unseekable. This is consistent with Apple's @@ -1927,6 +1935,7 @@ shaka.hls.HlsParser = class { verbatimMediaPlaylistUri, // These values are filled out or updated after lazy-loading: absoluteMediaPlaylistUri: initialMediaPlaylistUri, + minTimestamp: 0, maxTimestamp: 0, mediaSequenceToStartTime: new Map(), canSkipSegments: false, @@ -1972,6 +1981,7 @@ shaka.hls.HlsParser = class { // Copy values from the real stream info to our initial one. streamInfo.absoluteMediaPlaylistUri = absoluteMediaPlaylistUri; + streamInfo.minTimestamp = realStreamInfo.minTimestamp; streamInfo.maxTimestamp = realStreamInfo.maxTimestamp; streamInfo.canSkipSegments = realStreamInfo.canSkipSegments; streamInfo.hasEndList = realStreamInfo.hasEndList; @@ -2098,6 +2108,22 @@ shaka.hls.HlsParser = class { return minDuration; } + /** + * @return {number} + * @private + */ + getLiveDuration_() { + let maxTimestamp = Infinity; + let minTimestamp = Infinity; + for (const streamInfo of this.uriToStreamInfosMap_.values()) { + if (streamInfo.stream.segmentIndex && streamInfo.stream.type != 'text') { + maxTimestamp = Math.min(maxTimestamp, streamInfo.maxTimestamp); + minTimestamp = Math.min(minTimestamp, streamInfo.minTimestamp); + } + } + return maxTimestamp - minTimestamp; + } + /** * @param {!Array.} streams * @private @@ -2236,6 +2262,7 @@ shaka.hls.HlsParser = class { this.determinePresentationType_(playlist); } + const firstStartTime = segments[0].startTime; const lastEndTime = segments[segments.length - 1].endTime; /** @type {!shaka.media.SegmentIndex} */ const segmentIndex = new shaka.media.SegmentIndex(segments); @@ -2258,6 +2285,7 @@ shaka.hls.HlsParser = class { type, verbatimMediaPlaylistUri, absoluteMediaPlaylistUri, + minTimestamp: firstStartTime, maxTimestamp: lastEndTime, canSkipSegments, hasEndList: false, @@ -3554,6 +3582,7 @@ shaka.hls.HlsParser = class { * type: string, * verbatimMediaPlaylistUri: string, * absoluteMediaPlaylistUri: string, + * minTimestamp: number, * maxTimestamp: number, * mediaSequenceToStartTime: !Map., * canSkipSegments: boolean, @@ -3577,6 +3606,8 @@ shaka.hls.HlsParser = class { * @property {string} absoluteMediaPlaylistUri * The absolute media playlist URI, resolved relative to the master playlist * and updated to reflect any redirects. + * @property {number} minTimestamp + * The minimum timestamp found in the stream. * @property {number} maxTimestamp * The maximum timestamp found in the stream. * @property {!Map.} mediaSequenceToStartTime