05391966 by David LaPalomento

fix error with audio- or video-only streams. Closes #348

2 parents 385ed9fb b58a7fcb
......@@ -3,6 +3,7 @@ CHANGELOG
## HEAD (Unreleased)
* do not assume media sequence starts at zero ([view](https://github.com/videojs/videojs-contrib-hls/pull/346))
* fix error with audio- or video-only streams ([view](https://github.com/videojs/videojs-contrib-hls/pull/348))
--------------------
......
......@@ -5,7 +5,23 @@
'use strict';
var DEFAULT_TARGET_DURATION = 10;
var accumulateDuration, ascendingNumeric, duration, intervalDuration, rangeDuration, seekable;
var accumulateDuration, ascendingNumeric, duration, intervalDuration, optionalMin, optionalMax, rangeDuration, seekable;
// Math.min that will return the alternative input if one of its
// parameters in undefined
optionalMin = function(left, right) {
left = isFinite(left) ? left : Infinity;
right = isFinite(right) ? right : Infinity;
return Math.min(left, right);
};
// Math.max that will return the alternative input if one of its
// parameters in undefined
optionalMax = function(left, right) {
left = isFinite(left) ? left: -Infinity;
right = isFinite(right) ? right: -Infinity;
return Math.max(left, right);
};
// Array.sort comparator to sort numbers in ascending order
ascendingNumeric = function(left, right) {
......@@ -91,7 +107,8 @@
// available PTS information
for (left = range.start; left < range.end; left++) {
segment = playlist.segments[left];
if (segment.minVideoPts !== undefined) {
if (segment.minVideoPts !== undefined ||
segment.minAudioPts !== undefined) {
break;
}
result += segment.duration || targetDuration;
......@@ -100,10 +117,12 @@
// see if there's enough information to include the trailing time
if (includeTrailingTime) {
segment = playlist.segments[range.end];
if (segment && segment.minVideoPts !== undefined) {
if (segment &&
(segment.minVideoPts !== undefined ||
segment.minAudioPts !== undefined)) {
result += 0.001 *
(Math.min(segment.minVideoPts, segment.minAudioPts) -
Math.min(playlist.segments[left].minVideoPts,
(optionalMin(segment.minVideoPts, segment.minAudioPts) -
optionalMin(playlist.segments[left].minVideoPts,
playlist.segments[left].minAudioPts));
return result;
}
......@@ -112,7 +131,8 @@
// do the same thing while finding the latest segment
for (right = range.end - 1; right >= left; right--) {
segment = playlist.segments[right];
if (segment.maxVideoPts !== undefined) {
if (segment.maxVideoPts !== undefined ||
segment.maxAudioPts !== undefined) {
break;
}
result += segment.duration || targetDuration;
......@@ -121,9 +141,9 @@
// add in the PTS interval in seconds between them
if (right >= left) {
result += 0.001 *
(Math.max(playlist.segments[right].maxVideoPts,
(optionalMax(playlist.segments[right].maxVideoPts,
playlist.segments[right].maxAudioPts) -
Math.min(playlist.segments[left].minVideoPts,
optionalMin(playlist.segments[left].minVideoPts,
playlist.segments[left].minAudioPts));
}
......@@ -158,7 +178,7 @@
targetDuration = playlist.targetDuration || DEFAULT_TARGET_DURATION;
// estimate expired segment duration using the target duration
expiredSegmentCount = Math.max(playlist.mediaSequence - startSequence, 0);
expiredSegmentCount = optionalMax(playlist.mediaSequence - startSequence, 0);
result += expiredSegmentCount * targetDuration;
// accumulate the segment durations into the result
......@@ -257,9 +277,9 @@
// from the result.
for (i = playlist.segments.length - 1; i >= 0 && liveBuffer > 0; i--) {
segment = playlist.segments[i];
pending = Math.min(duration(playlist,
playlist.mediaSequence + i,
playlist.mediaSequence + i + 1),
pending = optionalMin(duration(playlist,
playlist.mediaSequence + i,
playlist.mediaSequence + i + 1),
liveBuffer);
liveBuffer -= pending;
end -= pending;
......
......@@ -899,10 +899,14 @@ videojs.Hls.prototype.drainBuffer = function(event) {
if (this.segmentParser_.tagsAvailable()) {
// record PTS information for the segment so we can calculate
// accurate durations and seek reliably
segment.minVideoPts = this.segmentParser_.stats.minVideoPts();
segment.maxVideoPts = this.segmentParser_.stats.maxVideoPts();
segment.minAudioPts = this.segmentParser_.stats.minAudioPts();
segment.maxAudioPts = this.segmentParser_.stats.maxAudioPts();
if (this.segmentParser_.stats.h264Tags()) {
segment.minVideoPts = this.segmentParser_.stats.minVideoPts();
segment.maxVideoPts = this.segmentParser_.stats.maxVideoPts();
}
if (this.segmentParser_.stats.aacTags()) {
segment.minAudioPts = this.segmentParser_.stats.minAudioPts();
segment.maxAudioPts = this.segmentParser_.stats.maxAudioPts();
}
}
while (this.segmentParser_.tagsAvailable()) {
......
......@@ -259,6 +259,30 @@
equal(duration, 30.1, 'used the PTS-based interval');
});
test('works for media without audio', function() {
equal(Playlist.duration({
mediaSequence: 0,
endList: true,
segments: [{
minVideoPts: 0,
maxVideoPts: 9 * 1000,
uri: 'no-audio.ts'
}]
}), 9, 'used video PTS values');
});
test('works for media without video', function() {
equal(Playlist.duration({
mediaSequence: 0,
endList: true,
segments: [{
minAudioPts: 0,
maxAudioPts: 9 * 1000,
uri: 'no-video.ts'
}]
}), 9, 'used video PTS values');
});
test('uses the largest continuous available PTS ranges', function() {
var playlist = {
mediaSequence: 0,
......
......@@ -284,6 +284,17 @@
equal(packets.length, 1, 'parsed non-payload metadata packet');
});
test('returns undefined for PTS stats when a track is missing', function() {
parser.parseSegmentBinaryData(new Uint8Array(makePacket({
programs: {
0x01: [0x01]
}
})));
strictEqual(parser.stats.h264Tags(), 0, 'no video tags yet');
strictEqual(parser.stats.aacTags(), 0, 'no audio tags yet');
});
test('parses the first bipbop segment', function() {
parser.parseSegmentBinaryData(window.bcSegment);
......
......@@ -121,12 +121,18 @@ var
]);
this.stats = {
h264Tags: function() {
return tags.length;
},
minVideoPts: function() {
return tags[0].pts;
},
maxVideoPts: function() {
return tags[tags.length - 1].pts;
},
aacTags: function() {
return tags.length;
},
minAudioPts: function() {
return tags[0].pts;
},
......@@ -1044,6 +1050,64 @@ test('records the min and max PTS values for a segment', function() {
equal(player.hls.playlists.media().segments[0].maxAudioPts, 10, 'recorded max audio pts');
});
test('records PTS values for video-only segments', function() {
var tags = [];
videojs.Hls.SegmentParser = mockSegmentParser(tags);
player.src({
src: 'manifest/media.m3u8',
type: 'application/vnd.apple.mpegurl'
});
openMediaSource(player);
standardXHRResponse(requests.pop()); // media.m3u8
player.hls.segmentParser_.stats.aacTags = function() {
return 0;
};
player.hls.segmentParser_.stats.minAudioPts = function() {
throw new Error('No audio tags');
};
player.hls.segmentParser_.stats.maxAudioPts = function() {
throw new Error('No audio tags');
};
tags.push({ pts: 0, bytes: new Uint8Array(1) });
tags.push({ pts: 10, bytes: new Uint8Array(1) });
standardXHRResponse(requests.pop()); // segment 0
equal(player.hls.playlists.media().segments[0].minVideoPts, 0, 'recorded min video pts');
equal(player.hls.playlists.media().segments[0].maxVideoPts, 10, 'recorded max video pts');
strictEqual(player.hls.playlists.media().segments[0].minAudioPts, undefined, 'min audio pts is undefined');
strictEqual(player.hls.playlists.media().segments[0].maxAudioPts, undefined, 'max audio pts is undefined');
});
test('records PTS values for audio-only segments', function() {
var tags = [];
videojs.Hls.SegmentParser = mockSegmentParser(tags);
player.src({
src: 'manifest/media.m3u8',
type: 'application/vnd.apple.mpegurl'
});
openMediaSource(player);
standardXHRResponse(requests.pop()); // media.m3u8
player.hls.segmentParser_.stats.h264Tags = function() {
return 0;
};
player.hls.segmentParser_.stats.minVideoPts = function() {
throw new Error('No video tags');
};
player.hls.segmentParser_.stats.maxVideoPts = function() {
throw new Error('No video tags');
};
tags.push({ pts: 0, bytes: new Uint8Array(1) });
tags.push({ pts: 10, bytes: new Uint8Array(1) });
standardXHRResponse(requests.pop()); // segment 0
equal(player.hls.playlists.media().segments[0].minAudioPts, 0, 'recorded min audio pts');
equal(player.hls.playlists.media().segments[0].maxAudioPts, 10, 'recorded max audio pts');
strictEqual(player.hls.playlists.media().segments[0].minVideoPts, undefined, 'min video pts is undefined');
strictEqual(player.hls.playlists.media().segments[0].maxVideoPts, undefined, 'max video pts is undefined');
});
test('waits to download new segments until the media playlist is stable', function() {
var media;
player.src({
......