Skip to content
This repository has been archived by the owner on Jan 12, 2019. It is now read-only.

Commit

Permalink
duration should work when video or audio is missing
Browse files Browse the repository at this point in the history
If audio or video data is missing for an HLS stream, duration calculations should just use the info that is available. Fixes #341.
  • Loading branch information
dmlap committed Jul 13, 2015
1 parent 385ed9f commit 8854d55
Show file tree
Hide file tree
Showing 5 changed files with 139 additions and 16 deletions.
44 changes: 32 additions & 12 deletions src/playlist.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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;
Expand All @@ -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;
}
Expand All @@ -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;
Expand All @@ -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));
}

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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;
Expand Down
12 changes: 8 additions & 4 deletions src/videojs-hls.js
Original file line number Diff line number Diff line change
Expand Up @@ -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()) {
Expand Down
24 changes: 24 additions & 0 deletions test/playlist_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
11 changes: 11 additions & 0 deletions test/segment-parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
64 changes: 64 additions & 0 deletions test/videojs-hls_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
},
Expand Down Expand Up @@ -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 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.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({
Expand Down

0 comments on commit 8854d55

Please sign in to comment.