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

Duration should work when video or audio is missing #348

Merged
merged 1 commit into from
Jul 14, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 ||
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if I add .hls.playlists.media().segments = new Array(1) I will get error TypeError: Cannot read property 'minVideoPts' of undefined

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The entries of a media playlist should be segment objects-- [undefined] is not a legal value for that array. Side note: it would be better to file this sort of thing as an issue. Line comments might get overlooked.

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