From cf40accaa8313a2e01c492c7f48156b5477dad28 Mon Sep 17 00:00:00 2001 From: KongQun Yang Date: Wed, 16 May 2018 10:56:22 -0700 Subject: [PATCH] [HLS] Fixing AdCues handling in iframe playlists Previously for the last iframe in a segment, we wait for the next segment to arrive before writing the EXTINF tag. If an Ad Cue comes in before the next segment, the EXT-X-PLACEMENT_OPPORTUNITY tag would be inserted before the iframe in previous segment. Fixes #378, #396. Change-Id: I1ede72a4d4edca94781c7b05bc25397d67916d1a --- .../bear-640x360-ac3-video-iframe.m3u8 | 3 +- .../bear-640x360-video-iframe.m3u8 | 3 +- .../bear-640x360-ac3-video-iframe.m3u8 | 3 +- .../bear-640x360-video-iframe.m3u8 | 3 + .../avc-ts-event-playlist/output.m3u8 | 2 +- .../bear-640x360-video-iframe.m3u8 | 10 +-- .../output.m3u8 | 2 +- .../bear-640x360-video-iframe.m3u8 | 7 ++- .../testdata/avc-ts-live-playlist/output.m3u8 | 2 +- .../bear-640x360-video-iframe.m3u8 | 3 +- .../sintel-1024x436-video-iframe.m3u8 | 3 +- .../bear-640x360-video-iframe.m3u8 | 3 +- .../bear-640x360-ec3-video-iframe.m3u8 | 3 +- .../bear-640x360-video-iframe.m3u8 | 2 +- .../bear-640x360-video-iframe.m3u8 | 2 +- packager/hls/base/media_playlist.cc | 61 ++++++++++--------- packager/hls/base/media_playlist.h | 4 +- packager/hls/base/media_playlist_unittest.cc | 45 +++++++++++++- 18 files changed, 112 insertions(+), 49 deletions(-) diff --git a/packager/app/test/testdata/avc-ac3-ts-with-encryption/bear-640x360-ac3-video-iframe.m3u8 b/packager/app/test/testdata/avc-ac3-ts-with-encryption/bear-640x360-ac3-video-iframe.m3u8 index 2c3bb4b23c9..4608d70c57f 100644 --- a/packager/app/test/testdata/avc-ac3-ts-with-encryption/bear-640x360-ac3-video-iframe.m3u8 +++ b/packager/app/test/testdata/avc-ac3-ts-with-encryption/bear-640x360-ac3-video-iframe.m3u8 @@ -4,10 +4,11 @@ #EXT-X-TARGETDURATION:2 #EXT-X-PLAYLIST-TYPE:VOD #EXT-X-I-FRAMES-ONLY -#EXT-X-KEY:METHOD=SAMPLE-AES,URI="data:text/plain;base64,MTIzNDU2Nzg5MDEyMzQ1Ng==",IV=0x3334353637383930,KEYFORMAT="identity" #EXTINF:1.001, #EXT-X-BYTERANGE:15604@376 bear-640x360-ac3-video-1.ts +#EXT-X-DISCONTINUITY +#EXT-X-KEY:METHOD=SAMPLE-AES,URI="data:text/plain;base64,MTIzNDU2Nzg5MDEyMzQ1Ng==",IV=0x3334353637383930,KEYFORMAT="identity" #EXTINF:1.001, #EXT-X-BYTERANGE:18236@376 bear-640x360-ac3-video-2.ts diff --git a/packager/app/test/testdata/avc-ts-aac-packed-audio-with-encryption/bear-640x360-video-iframe.m3u8 b/packager/app/test/testdata/avc-ts-aac-packed-audio-with-encryption/bear-640x360-video-iframe.m3u8 index ad51e2c4b3a..962e06ba1cb 100644 --- a/packager/app/test/testdata/avc-ts-aac-packed-audio-with-encryption/bear-640x360-video-iframe.m3u8 +++ b/packager/app/test/testdata/avc-ts-aac-packed-audio-with-encryption/bear-640x360-video-iframe.m3u8 @@ -4,10 +4,11 @@ #EXT-X-TARGETDURATION:2 #EXT-X-PLAYLIST-TYPE:VOD #EXT-X-I-FRAMES-ONLY -#EXT-X-KEY:METHOD=SAMPLE-AES,URI="data:text/plain;base64,MTIzNDU2Nzg5MDEyMzQ1Ng==",IV=0x3334353637383930,KEYFORMAT="identity" #EXTINF:1.001, #EXT-X-BYTERANGE:15604@376 bear-640x360-video-1.ts +#EXT-X-DISCONTINUITY +#EXT-X-KEY:METHOD=SAMPLE-AES,URI="data:text/plain;base64,MTIzNDU2Nzg5MDEyMzQ1Ng==",IV=0x3334353637383930,KEYFORMAT="identity" #EXTINF:1.001, #EXT-X-BYTERANGE:18236@376 bear-640x360-video-2.ts diff --git a/packager/app/test/testdata/avc-ts-ac3-packed-audio-with-encryption/bear-640x360-ac3-video-iframe.m3u8 b/packager/app/test/testdata/avc-ts-ac3-packed-audio-with-encryption/bear-640x360-ac3-video-iframe.m3u8 index 2c3bb4b23c9..4608d70c57f 100644 --- a/packager/app/test/testdata/avc-ts-ac3-packed-audio-with-encryption/bear-640x360-ac3-video-iframe.m3u8 +++ b/packager/app/test/testdata/avc-ts-ac3-packed-audio-with-encryption/bear-640x360-ac3-video-iframe.m3u8 @@ -4,10 +4,11 @@ #EXT-X-TARGETDURATION:2 #EXT-X-PLAYLIST-TYPE:VOD #EXT-X-I-FRAMES-ONLY -#EXT-X-KEY:METHOD=SAMPLE-AES,URI="data:text/plain;base64,MTIzNDU2Nzg5MDEyMzQ1Ng==",IV=0x3334353637383930,KEYFORMAT="identity" #EXTINF:1.001, #EXT-X-BYTERANGE:15604@376 bear-640x360-ac3-video-1.ts +#EXT-X-DISCONTINUITY +#EXT-X-KEY:METHOD=SAMPLE-AES,URI="data:text/plain;base64,MTIzNDU2Nzg5MDEyMzQ1Ng==",IV=0x3334353637383930,KEYFORMAT="identity" #EXTINF:1.001, #EXT-X-BYTERANGE:18236@376 bear-640x360-ac3-video-2.ts diff --git a/packager/app/test/testdata/avc-ts-event-playlist/bear-640x360-video-iframe.m3u8 b/packager/app/test/testdata/avc-ts-event-playlist/bear-640x360-video-iframe.m3u8 index 2a25108bfb4..97925b5bac0 100644 --- a/packager/app/test/testdata/avc-ts-event-playlist/bear-640x360-video-iframe.m3u8 +++ b/packager/app/test/testdata/avc-ts-event-playlist/bear-640x360-video-iframe.m3u8 @@ -10,3 +10,6 @@ bear-640x360-video-1.ts #EXTINF:1.001, #EXT-X-BYTERANGE:18236@376 bear-640x360-video-2.ts +#EXTINF:0.667, +#EXT-X-BYTERANGE:19928@376 +bear-640x360-video-3.ts diff --git a/packager/app/test/testdata/avc-ts-event-playlist/output.m3u8 b/packager/app/test/testdata/avc-ts-event-playlist/output.m3u8 index 9911c7613d7..02b545801a2 100644 --- a/packager/app/test/testdata/avc-ts-event-playlist/output.m3u8 +++ b/packager/app/test/testdata/avc-ts-event-playlist/output.m3u8 @@ -6,4 +6,4 @@ #EXT-X-STREAM-INF:BANDWIDTH=1217518,CODECS="avc1.64001e,mp4a.40.2",RESOLUTION=640x360,AUDIO="default-audio-group" bear-640x360-video.m3u8 -#EXT-X-I-FRAME-STREAM-INF:BANDWIDTH=145742,CODECS="avc1.64001e",RESOLUTION=640x360,URI="bear-640x360-video-iframe.m3u8" +#EXT-X-I-FRAME-STREAM-INF:BANDWIDTH=238897,CODECS="avc1.64001e",RESOLUTION=640x360,URI="bear-640x360-video-iframe.m3u8" diff --git a/packager/app/test/testdata/avc-ts-live-playlist-with-key-rotation/bear-640x360-video-iframe.m3u8 b/packager/app/test/testdata/avc-ts-live-playlist-with-key-rotation/bear-640x360-video-iframe.m3u8 index f74afa06206..ff2034f60cd 100644 --- a/packager/app/test/testdata/avc-ts-live-playlist-with-key-rotation/bear-640x360-video-iframe.m3u8 +++ b/packager/app/test/testdata/avc-ts-live-playlist-with-key-rotation/bear-640x360-video-iframe.m3u8 @@ -2,12 +2,14 @@ #EXT-X-VERSION:6 ## Generated with https://github.com/google/shaka-packager version -- #EXT-X-TARGETDURATION:2 +#EXT-X-MEDIA-SEQUENCE:1 +#EXT-X-DISCONTINUITY-SEQUENCE:1 #EXT-X-I-FRAMES-ONLY #EXT-X-KEY:METHOD=SAMPLE-AES,URI="data:text/plain;base64,MjM0NTY3ODkwMTIzNDU2MQ==",IV=0x3334353637383930,KEYFORMAT="identity" #EXTINF:1.001, -#EXT-X-BYTERANGE:15604@376 -bear-640x360-video-1.ts -#EXT-X-KEY:METHOD=SAMPLE-AES,URI="data:text/plain;base64,MzQ1Njc4OTAxMjM0NTYxMg==",IV=0x3334353637383930,KEYFORMAT="identity" -#EXTINF:1.001, #EXT-X-BYTERANGE:18236@376 bear-640x360-video-2.ts +#EXT-X-KEY:METHOD=SAMPLE-AES,URI="data:text/plain;base64,MzQ1Njc4OTAxMjM0NTYxMg==",IV=0x3334353637383930,KEYFORMAT="identity" +#EXTINF:0.667, +#EXT-X-BYTERANGE:19928@376 +bear-640x360-video-3.ts diff --git a/packager/app/test/testdata/avc-ts-live-playlist-with-key-rotation/output.m3u8 b/packager/app/test/testdata/avc-ts-live-playlist-with-key-rotation/output.m3u8 index 9911c7613d7..02b545801a2 100644 --- a/packager/app/test/testdata/avc-ts-live-playlist-with-key-rotation/output.m3u8 +++ b/packager/app/test/testdata/avc-ts-live-playlist-with-key-rotation/output.m3u8 @@ -6,4 +6,4 @@ #EXT-X-STREAM-INF:BANDWIDTH=1217518,CODECS="avc1.64001e,mp4a.40.2",RESOLUTION=640x360,AUDIO="default-audio-group" bear-640x360-video.m3u8 -#EXT-X-I-FRAME-STREAM-INF:BANDWIDTH=145742,CODECS="avc1.64001e",RESOLUTION=640x360,URI="bear-640x360-video-iframe.m3u8" +#EXT-X-I-FRAME-STREAM-INF:BANDWIDTH=238897,CODECS="avc1.64001e",RESOLUTION=640x360,URI="bear-640x360-video-iframe.m3u8" diff --git a/packager/app/test/testdata/avc-ts-live-playlist/bear-640x360-video-iframe.m3u8 b/packager/app/test/testdata/avc-ts-live-playlist/bear-640x360-video-iframe.m3u8 index fe2f000d24d..fe4bfd8fe64 100644 --- a/packager/app/test/testdata/avc-ts-live-playlist/bear-640x360-video-iframe.m3u8 +++ b/packager/app/test/testdata/avc-ts-live-playlist/bear-640x360-video-iframe.m3u8 @@ -2,10 +2,11 @@ #EXT-X-VERSION:6 ## Generated with https://github.com/google/shaka-packager version -- #EXT-X-TARGETDURATION:2 +#EXT-X-MEDIA-SEQUENCE:1 #EXT-X-I-FRAMES-ONLY #EXTINF:1.001, -#EXT-X-BYTERANGE:15604@376 -bear-640x360-video-1.ts -#EXTINF:1.001, #EXT-X-BYTERANGE:18236@376 bear-640x360-video-2.ts +#EXTINF:0.667, +#EXT-X-BYTERANGE:19928@376 +bear-640x360-video-3.ts diff --git a/packager/app/test/testdata/avc-ts-live-playlist/output.m3u8 b/packager/app/test/testdata/avc-ts-live-playlist/output.m3u8 index 9911c7613d7..02b545801a2 100644 --- a/packager/app/test/testdata/avc-ts-live-playlist/output.m3u8 +++ b/packager/app/test/testdata/avc-ts-live-playlist/output.m3u8 @@ -6,4 +6,4 @@ #EXT-X-STREAM-INF:BANDWIDTH=1217518,CODECS="avc1.64001e,mp4a.40.2",RESOLUTION=640x360,AUDIO="default-audio-group" bear-640x360-video.m3u8 -#EXT-X-I-FRAME-STREAM-INF:BANDWIDTH=145742,CODECS="avc1.64001e",RESOLUTION=640x360,URI="bear-640x360-video-iframe.m3u8" +#EXT-X-I-FRAME-STREAM-INF:BANDWIDTH=238897,CODECS="avc1.64001e",RESOLUTION=640x360,URI="bear-640x360-video-iframe.m3u8" diff --git a/packager/app/test/testdata/avc-ts-with-encryption-and-fairplay/bear-640x360-video-iframe.m3u8 b/packager/app/test/testdata/avc-ts-with-encryption-and-fairplay/bear-640x360-video-iframe.m3u8 index 44bb1b98324..005f83116f1 100644 --- a/packager/app/test/testdata/avc-ts-with-encryption-and-fairplay/bear-640x360-video-iframe.m3u8 +++ b/packager/app/test/testdata/avc-ts-with-encryption-and-fairplay/bear-640x360-video-iframe.m3u8 @@ -4,10 +4,11 @@ #EXT-X-TARGETDURATION:2 #EXT-X-PLAYLIST-TYPE:VOD #EXT-X-I-FRAMES-ONLY -#EXT-X-KEY:METHOD=SAMPLE-AES,URI="skd://www.license.com/getkey?KeyId=31323334-3536-3738-3930-313233343536",KEYFORMATVERSIONS="1",KEYFORMAT="com.apple.streamingkeydelivery" #EXTINF:1.001, #EXT-X-BYTERANGE:15604@376 bear-640x360-video-1.ts +#EXT-X-DISCONTINUITY +#EXT-X-KEY:METHOD=SAMPLE-AES,URI="skd://www.license.com/getkey?KeyId=31323334-3536-3738-3930-313233343536",KEYFORMATVERSIONS="1",KEYFORMAT="com.apple.streamingkeydelivery" #EXTINF:1.001, #EXT-X-BYTERANGE:18236@376 bear-640x360-video-2.ts diff --git a/packager/app/test/testdata/avc-ts-with-encryption-exercise-emulation-prevention/sintel-1024x436-video-iframe.m3u8 b/packager/app/test/testdata/avc-ts-with-encryption-exercise-emulation-prevention/sintel-1024x436-video-iframe.m3u8 index d4e92ec12f3..19fd260111c 100644 --- a/packager/app/test/testdata/avc-ts-with-encryption-exercise-emulation-prevention/sintel-1024x436-video-iframe.m3u8 +++ b/packager/app/test/testdata/avc-ts-with-encryption-exercise-emulation-prevention/sintel-1024x436-video-iframe.m3u8 @@ -4,10 +4,11 @@ #EXT-X-TARGETDURATION:2 #EXT-X-PLAYLIST-TYPE:VOD #EXT-X-I-FRAMES-ONLY -#EXT-X-KEY:METHOD=SAMPLE-AES,URI="data:text/plain;base64,MTIzNDU2Nzg5MDEyMzQ1Ng==",IV=0x3334353637383930,KEYFORMAT="identity" #EXTINF:1.000, #EXT-X-BYTERANGE:940@376 sintel-1024x436-video-1.ts +#EXT-X-DISCONTINUITY +#EXT-X-KEY:METHOD=SAMPLE-AES,URI="data:text/plain;base64,MTIzNDU2Nzg5MDEyMzQ1Ng==",IV=0x3334353637383930,KEYFORMAT="identity" #EXTINF:1.000, #EXT-X-BYTERANGE:376@376 sintel-1024x436-video-2.ts diff --git a/packager/app/test/testdata/avc-ts-with-encryption/bear-640x360-video-iframe.m3u8 b/packager/app/test/testdata/avc-ts-with-encryption/bear-640x360-video-iframe.m3u8 index ad51e2c4b3a..962e06ba1cb 100644 --- a/packager/app/test/testdata/avc-ts-with-encryption/bear-640x360-video-iframe.m3u8 +++ b/packager/app/test/testdata/avc-ts-with-encryption/bear-640x360-video-iframe.m3u8 @@ -4,10 +4,11 @@ #EXT-X-TARGETDURATION:2 #EXT-X-PLAYLIST-TYPE:VOD #EXT-X-I-FRAMES-ONLY -#EXT-X-KEY:METHOD=SAMPLE-AES,URI="data:text/plain;base64,MTIzNDU2Nzg5MDEyMzQ1Ng==",IV=0x3334353637383930,KEYFORMAT="identity" #EXTINF:1.001, #EXT-X-BYTERANGE:15604@376 bear-640x360-video-1.ts +#EXT-X-DISCONTINUITY +#EXT-X-KEY:METHOD=SAMPLE-AES,URI="data:text/plain;base64,MTIzNDU2Nzg5MDEyMzQ1Ng==",IV=0x3334353637383930,KEYFORMAT="identity" #EXTINF:1.001, #EXT-X-BYTERANGE:18236@376 bear-640x360-video-2.ts diff --git a/packager/app/test/testdata/ec3-packed-audio-encrypted/bear-640x360-ec3-video-iframe.m3u8 b/packager/app/test/testdata/ec3-packed-audio-encrypted/bear-640x360-ec3-video-iframe.m3u8 index 4458420b7a6..4bb48342455 100644 --- a/packager/app/test/testdata/ec3-packed-audio-encrypted/bear-640x360-ec3-video-iframe.m3u8 +++ b/packager/app/test/testdata/ec3-packed-audio-encrypted/bear-640x360-ec3-video-iframe.m3u8 @@ -4,10 +4,11 @@ #EXT-X-TARGETDURATION:2 #EXT-X-PLAYLIST-TYPE:VOD #EXT-X-I-FRAMES-ONLY -#EXT-X-KEY:METHOD=SAMPLE-AES,URI="data:text/plain;base64,MTIzNDU2Nzg5MDEyMzQ1Ng==",IV=0x3334353637383930,KEYFORMAT="identity" #EXTINF:1.001, #EXT-X-BYTERANGE:15604@376 bear-640x360-ec3-video-1.ts +#EXT-X-DISCONTINUITY +#EXT-X-KEY:METHOD=SAMPLE-AES,URI="data:text/plain;base64,MTIzNDU2Nzg5MDEyMzQ1Ng==",IV=0x3334353637383930,KEYFORMAT="identity" #EXTINF:1.001, #EXT-X-BYTERANGE:18236@376 bear-640x360-ec3-video-2.ts diff --git a/packager/app/test/testdata/hls-audio-video-text-with-ad-cues/bear-640x360-video-iframe.m3u8 b/packager/app/test/testdata/hls-audio-video-text-with-ad-cues/bear-640x360-video-iframe.m3u8 index 09f586e6296..756a032739e 100644 --- a/packager/app/test/testdata/hls-audio-video-text-with-ad-cues/bear-640x360-video-iframe.m3u8 +++ b/packager/app/test/testdata/hls-audio-video-text-with-ad-cues/bear-640x360-video-iframe.m3u8 @@ -8,10 +8,10 @@ #EXTINF:1.001, #EXT-X-BYTERANGE:15581@80 bear-640x360-video-1.m4s -#EXT-X-PLACEMENT-OPPORTUNITY #EXTINF:1.001, #EXT-X-BYTERANGE:18221@80 bear-640x360-video-2.m4s +#EXT-X-PLACEMENT-OPPORTUNITY #EXTINF:0.734, #EXT-X-BYTERANGE:19663@80 bear-640x360-video-3.m4s diff --git a/packager/app/test/testdata/hls-single-segment-mp4-encrypted-and-ad-cues/bear-640x360-video-iframe.m3u8 b/packager/app/test/testdata/hls-single-segment-mp4-encrypted-and-ad-cues/bear-640x360-video-iframe.m3u8 index 3b7cf9a41fc..be3f071a9c5 100644 --- a/packager/app/test/testdata/hls-single-segment-mp4-encrypted-and-ad-cues/bear-640x360-video-iframe.m3u8 +++ b/packager/app/test/testdata/hls-single-segment-mp4-encrypted-and-ad-cues/bear-640x360-video-iframe.m3u8 @@ -9,10 +9,10 @@ #EXTINF:1.001, #EXT-X-BYTERANGE:15581@1159 bear-640x360-video.mp4 -#EXT-X-PLACEMENT-OPPORTUNITY #EXTINF:1.001, #EXT-X-BYTERANGE:18754@100472 bear-640x360-video.mp4 +#EXT-X-PLACEMENT-OPPORTUNITY #EXTINF:0.734, #EXT-X-BYTERANGE:20068@222812 bear-640x360-video.mp4 diff --git a/packager/hls/base/media_playlist.cc b/packager/hls/base/media_playlist.cc index a0970793b3e..5811140b507 100644 --- a/packager/hls/base/media_playlist.cc +++ b/packager/hls/base/media_playlist.cc @@ -151,6 +151,7 @@ class SegmentInfoEntry : public HlsEntry { std::string ToString() override; double start_time() const { return start_time_; } double duration() const { return duration_; } + void set_duration(double duration) { duration_ = duration; } private: SegmentInfoEntry(const SegmentInfoEntry&) = delete; @@ -158,7 +159,7 @@ class SegmentInfoEntry : public HlsEntry { const std::string file_name_; const double start_time_; - const double duration_; + double duration_; const bool use_byte_range_; const uint64_t start_byte_offset_; const uint64_t segment_file_size_; @@ -379,20 +380,20 @@ void MediaPlaylist::AddSegment(const std::string& file_name, if (stream_type_ == MediaPlaylistStreamType::kVideoIFramesOnly) { if (key_frames_.empty()) return; - // Skip the last entry as the duration of the key frames are defined by the - // next key frame, which we don't know yet. - for (auto iter = key_frames_.begin(); iter != std::prev(key_frames_.end()); - ++iter) { - const std::string& segment_file_name = - iter->segment_file_name.empty() ? file_name : iter->segment_file_name; - AddSegmentInfoEntry(segment_file_name, iter->timestamp, iter->duration, + + AdjustLastSegmentInfoEntryDuration(key_frames_.front().timestamp); + + for (auto iter = key_frames_.begin(); iter != key_frames_.end(); ++iter) { + // Last entry duration may be adjusted later when the next iframe becomes + // available. + const uint64_t next_timestamp = std::next(iter) == key_frames_.end() + ? (start_time + duration) + : std::next(iter)->timestamp; + AddSegmentInfoEntry(file_name, iter->timestamp, + next_timestamp - iter->timestamp, iter->start_byte_offset, iter->size); } - - key_frames_.erase(key_frames_.begin(), std::prev(key_frames_.end())); - KeyFrameInfo& key_frame = key_frames_.front(); - key_frame.segment_file_name = file_name; - key_frame.duration = start_time + duration - key_frame.timestamp; + key_frames_.clear(); return; } return AddSegmentInfoEntry(file_name, start_time, duration, start_byte_offset, @@ -411,9 +412,6 @@ void MediaPlaylist::AddKeyFrame(uint64_t timestamp, stream_type_ = MediaPlaylistStreamType::kVideoIFramesOnly; use_byte_range_ = true; } - if (!key_frames_.empty()) { - key_frames_.back().duration = timestamp - key_frames_.back().timestamp; - } key_frames_.push_back({timestamp, start_byte_offset, size}); } @@ -439,18 +437,6 @@ void MediaPlaylist::AddPlacementOpportunity() { } bool MediaPlaylist::WriteToFile(const std::string& file_path) { - if (!key_frames_.empty() && - hls_params_.playlist_type == HlsPlaylistType::kVod) { - // Flush remaining key frames. This assumes |WriteToFile| is only called - // once at the end of the file in VOD. - CHECK_EQ(key_frames_.size(), 1u); - const KeyFrameInfo& key_frame = key_frames_.front(); - AddSegmentInfoEntry(key_frame.segment_file_name, key_frame.timestamp, - key_frame.duration, key_frame.start_byte_offset, - key_frame.size); - key_frames_.clear(); - } - if (!target_duration_set_) { SetTargetDuration(ceil(GetLongestSegmentDuration())); } @@ -548,6 +534,25 @@ void MediaPlaylist::AddSegmentInfoEntry(const std::string& segment_file_name, SlideWindow(); } +void MediaPlaylist::AdjustLastSegmentInfoEntryDuration( + uint64_t next_timestamp) { + if (time_scale_ == 0) + return; + + const double scaled_next_timestamp = + static_cast(next_timestamp) / time_scale_; + + for (auto iter = entries_.rbegin(); iter != entries_.rend(); ++iter) { + if (iter->get()->type() == HlsEntry::EntryType::kExtInf) { + SegmentInfoEntry* segment_info = + reinterpret_cast(iter->get()); + segment_info->set_duration(scaled_next_timestamp - + segment_info->start_time()); + break; + } + } +} + void MediaPlaylist::SlideWindow() { DCHECK(!entries_.empty()); if (hls_params_.time_shift_buffer_depth <= 0.0 || diff --git a/packager/hls/base/media_playlist.h b/packager/hls/base/media_playlist.h index d256408f916..c96c83e2555 100644 --- a/packager/hls/base/media_playlist.h +++ b/packager/hls/base/media_playlist.h @@ -185,6 +185,9 @@ class MediaPlaylist { uint64_t duration, uint64_t start_byte_offset, uint64_t size); + // Adjust the duration of the last SegmentInfoEntry to end on + // |next_timestamp|. + void AdjustLastSegmentInfoEntryDuration(uint64_t next_timestamp); // Remove elements from |entries_| for live profile. Increments // |sequence_number_| by the number of segments removed. void SlideWindow(); @@ -231,7 +234,6 @@ class MediaPlaylist { uint64_t timestamp; uint64_t start_byte_offset; uint64_t size; - uint64_t duration; std::string segment_file_name; }; std::list key_frames_; diff --git a/packager/hls/base/media_playlist_unittest.cc b/packager/hls/base/media_playlist_unittest.cc index 257df1a9af6..c369cfd9fcb 100644 --- a/packager/hls/base/media_playlist_unittest.cc +++ b/packager/hls/base/media_playlist_unittest.cc @@ -811,7 +811,7 @@ TEST_F(IFrameMediaPlaylistTest, SingleSegment) { "#EXT-X-VERSION:6\n" "## Generated with https://github.com/google/shaka-packager version " "test\n" - "#EXT-X-TARGETDURATION:9\n" + "#EXT-X-TARGETDURATION:8\n" "#EXT-X-PLAYLIST-TYPE:VOD\n" "#EXT-X-I-FRAMES-ONLY\n" "#EXT-X-MAP:URI=\"file.mp4\",BYTERANGE=\"501@0\"\n" @@ -875,6 +875,49 @@ TEST_F(IFrameMediaPlaylistTest, MultiSegment) { ASSERT_FILE_STREQ(kMemoryFilePath, kExpectedOutput); } +TEST_F(IFrameMediaPlaylistTest, MultiSegmentWithPlacementOpportunity) { + valid_video_media_info_.set_reference_time_scale(90000); + valid_video_media_info_.set_segment_template_url("file$Number$.ts"); + ASSERT_TRUE(media_playlist_->SetMediaInfo(valid_video_media_info_)); + + media_playlist_->AddKeyFrame(0, 1000, 2345); + media_playlist_->AddKeyFrame(2 * kTimeScale, 5000, 6345); + media_playlist_->AddSegment("file1.ts", 0, 10 * kTimeScale, kZeroByteOffset, + kMBytes); + media_playlist_->AddPlacementOpportunity(); + media_playlist_->AddKeyFrame(11 * kTimeScale, 1000, 2345); + media_playlist_->AddKeyFrame(15 * kTimeScale, 3345, 12345); + media_playlist_->AddSegment("file2.ts", 10 * kTimeScale, 30 * kTimeScale, + kZeroByteOffset, 5 * kMBytes); + + const char kExpectedOutput[] = + "#EXTM3U\n" + "#EXT-X-VERSION:6\n" + "## Generated with https://github.com/google/shaka-packager version " + "test\n" + "#EXT-X-TARGETDURATION:25\n" + "#EXT-X-PLAYLIST-TYPE:VOD\n" + "#EXT-X-I-FRAMES-ONLY\n" + "#EXTINF:2.000,\n" + "#EXT-X-BYTERANGE:2345@1000\n" + "file1.ts\n" + "#EXTINF:9.000,\n" + "#EXT-X-BYTERANGE:6345@5000\n" + "file1.ts\n" + "#EXT-X-PLACEMENT-OPPORTUNITY\n" + "#EXTINF:4.000,\n" + "#EXT-X-BYTERANGE:2345@1000\n" + "file2.ts\n" + "#EXTINF:25.000,\n" + "#EXT-X-BYTERANGE:12345\n" + "file2.ts\n" + "#EXT-X-ENDLIST\n"; + + const char kMemoryFilePath[] = "memory://media.m3u8"; + EXPECT_TRUE(media_playlist_->WriteToFile(kMemoryFilePath)); + ASSERT_FILE_STREQ(kMemoryFilePath, kExpectedOutput); +} + namespace { const int kNumPreservedSegmentsOutsideLiveWindow = 3; const int kMaxNumSegmentsAvailable =