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

Commit

Permalink
fix error with audio- or video-only streams. Closes #348
Browse files Browse the repository at this point in the history
  • Loading branch information
dmlap committed Jul 14, 2015
2 parents 385ed9f + b58a7fc commit 0539196
Show file tree
Hide file tree
Showing 6 changed files with 140 additions and 16 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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))

--------------------

Expand Down
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 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({
Expand Down

0 comments on commit 0539196

Please sign in to comment.