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.
Showing
3 changed files
with
76 additions
and
6 deletions
... | @@ -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); | ... | ... |
-
Please register or sign in to post a comment