9dce22d1 by David LaPalomento

Account for expired segments when placing ID3 cues

Adjust the media timeline position a ID3-based cue is inserted at based on the amount of content that has slid off of a live playlist.
1 parent cd17637b
...@@ -389,8 +389,8 @@ ...@@ -389,8 +389,8 @@
389 this.media_.mediaSequence, 389 this.media_.mediaSequence,
390 lastDiscontinuity); 390 lastDiscontinuity);
391 this.expiredPostDiscontinuity_ += Playlist.duration(this.media_, 391 this.expiredPostDiscontinuity_ += Playlist.duration(this.media_,
392 lastDiscontinuity, 392 lastDiscontinuity,
393 this.media_.mediaSequence + expiredCount); 393 update.mediaSequence);
394 } 394 }
395 395
396 this.media_ = this.master.playlists[update.uri]; 396 this.media_ = this.master.playlists[update.uri];
......
...@@ -313,11 +313,14 @@ videojs.Hls.prototype.setupMetadataCueTranslation_ = function() { ...@@ -313,11 +313,14 @@ videojs.Hls.prototype.setupMetadataCueTranslation_ = function() {
313 313
314 videojs.Hls.prototype.addCuesForMetadata_ = function(segmentInfo) { 314 videojs.Hls.prototype.addCuesForMetadata_ = function(segmentInfo) {
315 var i, cue, frame, metadata, minPts, segment, segmentOffset, textTrack, time; 315 var i, cue, frame, metadata, minPts, segment, segmentOffset, textTrack, time;
316 segmentOffset = videojs.Hls.Playlist.duration(segmentInfo.playlist, 316 segmentOffset = this.playlists.expiredPreDiscontinuity_;
317 segmentInfo.playlist.mediaSequence, 317 segmentOffset += this.playlists.expiredPostDiscontinuity_;
318 segmentInfo.playlist.mediaSequence + segmentInfo.mediaIndex); 318 segmentOffset += videojs.Hls.Playlist.duration(segmentInfo.playlist,
319 segmentInfo.playlist.mediaSequence,
320 segmentInfo.playlist.mediaSequence + segmentInfo.mediaIndex);
319 segment = segmentInfo.playlist.segments[segmentInfo.mediaIndex]; 321 segment = segmentInfo.playlist.segments[segmentInfo.mediaIndex];
320 minPts = Math.min(segment.minVideoPts, segment.minAudioPts); 322 minPts = Math.min(isFinite(segment.minVideoPts) ? segment.minVideoPts : Infinity,
323 isFinite(segment.minAudioPts) ? segment.minAudioPts : Infinity);
321 324
322 while (segmentInfo.pendingMetadata.length) { 325 while (segmentInfo.pendingMetadata.length) {
323 metadata = segmentInfo.pendingMetadata[0].metadata; 326 metadata = segmentInfo.pendingMetadata[0].metadata;
......
...@@ -1473,6 +1473,73 @@ test('translates ID3 PTS values to cue media timeline positions', function() { ...@@ -1473,6 +1473,73 @@ test('translates ID3 PTS values to cue media timeline positions', function() {
1473 equal(track.cues[0].endTime, 1, 'translated startTime'); 1473 equal(track.cues[0].endTime, 1, 'translated startTime');
1474 }); 1474 });
1475 1475
1476 test('translates ID3 PTS values with expired segments', function() {
1477 var tags = [{ pts: 4 * 1000, bytes: new Uint8Array(1) }], track;
1478 videojs.Hls.SegmentParser = mockSegmentParser(tags);
1479 player.src({
1480 src: 'live.m3u8',
1481 type: 'application/vnd.apple.mpegurl'
1482 });
1483 openMediaSource(player);
1484 player.play();
1485
1486 // 20.9 seconds of content have expired
1487 player.hls.playlists.expiredPostDiscontinuity_ = 20.9;
1488
1489 player.hls.segmentParser_.parseSegmentBinaryData = function() {
1490 // trigger a metadata event
1491 player.hls.segmentParser_.metadataStream.trigger('data', {
1492 pts: 5 * 1000,
1493 data: new Uint8Array([]),
1494 frames: [{
1495 id: 'TXXX',
1496 value: 'cue text'
1497 }]
1498 });
1499 };
1500 requests.shift().respond(200, null,
1501 '#EXTM3U\n' +
1502 '#EXT-X-MEDIA-SEQUENCE:2\n' +
1503 '#EXTINF:10,\n' +
1504 '2.ts\n' +
1505 '#EXTINF:10,\n' +
1506 '3.ts\n'); // media
1507 standardXHRResponse(requests.shift()); // segment 0
1508
1509 track = player.textTracks()[0];
1510 equal(track.cues[0].startTime, 20.9 + 1, 'translated startTime');
1511 equal(track.cues[0].endTime, 20.9 + 1, 'translated startTime');
1512 });
1513
1514 test('translates id3 PTS values for audio-only media', function() {
1515 var tags = [{ pts: 4 * 1000, bytes: new Uint8Array(1) }], track;
1516 videojs.Hls.SegmentParser = mockSegmentParser(tags);
1517 player.src({
1518 src: 'manifest/media.m3u8',
1519 type: 'application/vnd.apple.mpegurl'
1520 });
1521 openMediaSource(player);
1522
1523 player.hls.segmentParser_.parseSegmentBinaryData = function() {
1524 // trigger a metadata event
1525 player.hls.segmentParser_.metadataStream.trigger('data', {
1526 pts: 5 * 1000,
1527 data: new Uint8Array([]),
1528 frames: [{
1529 id: 'TXXX',
1530 value: 'cue text'
1531 }]
1532 });
1533 };
1534 player.hls.segmentParser_.stats.h264Tags = function() { return 0; };
1535 player.hls.segmentParser_.stats.minVideoPts = null;
1536 standardXHRResponse(requests.shift()); // media
1537 standardXHRResponse(requests.shift()); // segment 0
1538
1539 track = player.textTracks()[0];
1540 equal(track.cues[0].startTime, 1, 'translated startTime');
1541 });
1542
1476 test('translates ID3 PTS values across discontinuities', function() { 1543 test('translates ID3 PTS values across discontinuities', function() {
1477 var tags = [], events = [], track; 1544 var tags = [], events = [], track;
1478 videojs.Hls.SegmentParser = mockSegmentParser(tags); 1545 videojs.Hls.SegmentParser = mockSegmentParser(tags);
......