[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
This commit is contained in:
KongQun Yang 2018-05-16 10:56:22 -07:00
parent 14caaf1e1c
commit cf40accaa8
18 changed files with 112 additions and 49 deletions

View File

@ -4,10 +4,11 @@
#EXT-X-TARGETDURATION:2 #EXT-X-TARGETDURATION:2
#EXT-X-PLAYLIST-TYPE:VOD #EXT-X-PLAYLIST-TYPE:VOD
#EXT-X-I-FRAMES-ONLY #EXT-X-I-FRAMES-ONLY
#EXT-X-KEY:METHOD=SAMPLE-AES,URI="data:text/plain;base64,MTIzNDU2Nzg5MDEyMzQ1Ng==",IV=0x3334353637383930,KEYFORMAT="identity"
#EXTINF:1.001, #EXTINF:1.001,
#EXT-X-BYTERANGE:15604@376 #EXT-X-BYTERANGE:15604@376
bear-640x360-ac3-video-1.ts 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, #EXTINF:1.001,
#EXT-X-BYTERANGE:18236@376 #EXT-X-BYTERANGE:18236@376
bear-640x360-ac3-video-2.ts bear-640x360-ac3-video-2.ts

View File

@ -4,10 +4,11 @@
#EXT-X-TARGETDURATION:2 #EXT-X-TARGETDURATION:2
#EXT-X-PLAYLIST-TYPE:VOD #EXT-X-PLAYLIST-TYPE:VOD
#EXT-X-I-FRAMES-ONLY #EXT-X-I-FRAMES-ONLY
#EXT-X-KEY:METHOD=SAMPLE-AES,URI="data:text/plain;base64,MTIzNDU2Nzg5MDEyMzQ1Ng==",IV=0x3334353637383930,KEYFORMAT="identity"
#EXTINF:1.001, #EXTINF:1.001,
#EXT-X-BYTERANGE:15604@376 #EXT-X-BYTERANGE:15604@376
bear-640x360-video-1.ts 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, #EXTINF:1.001,
#EXT-X-BYTERANGE:18236@376 #EXT-X-BYTERANGE:18236@376
bear-640x360-video-2.ts bear-640x360-video-2.ts

View File

@ -4,10 +4,11 @@
#EXT-X-TARGETDURATION:2 #EXT-X-TARGETDURATION:2
#EXT-X-PLAYLIST-TYPE:VOD #EXT-X-PLAYLIST-TYPE:VOD
#EXT-X-I-FRAMES-ONLY #EXT-X-I-FRAMES-ONLY
#EXT-X-KEY:METHOD=SAMPLE-AES,URI="data:text/plain;base64,MTIzNDU2Nzg5MDEyMzQ1Ng==",IV=0x3334353637383930,KEYFORMAT="identity"
#EXTINF:1.001, #EXTINF:1.001,
#EXT-X-BYTERANGE:15604@376 #EXT-X-BYTERANGE:15604@376
bear-640x360-ac3-video-1.ts 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, #EXTINF:1.001,
#EXT-X-BYTERANGE:18236@376 #EXT-X-BYTERANGE:18236@376
bear-640x360-ac3-video-2.ts bear-640x360-ac3-video-2.ts

View File

@ -10,3 +10,6 @@ bear-640x360-video-1.ts
#EXTINF:1.001, #EXTINF:1.001,
#EXT-X-BYTERANGE:18236@376 #EXT-X-BYTERANGE:18236@376
bear-640x360-video-2.ts bear-640x360-video-2.ts
#EXTINF:0.667,
#EXT-X-BYTERANGE:19928@376
bear-640x360-video-3.ts

View File

@ -6,4 +6,4 @@
#EXT-X-STREAM-INF:BANDWIDTH=1217518,CODECS="avc1.64001e,mp4a.40.2",RESOLUTION=640x360,AUDIO="default-audio-group" #EXT-X-STREAM-INF:BANDWIDTH=1217518,CODECS="avc1.64001e,mp4a.40.2",RESOLUTION=640x360,AUDIO="default-audio-group"
bear-640x360-video.m3u8 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"

View File

@ -2,12 +2,14 @@
#EXT-X-VERSION:6 #EXT-X-VERSION:6
## Generated with https://github.com/google/shaka-packager version <tag>-<hash>-<test> ## Generated with https://github.com/google/shaka-packager version <tag>-<hash>-<test>
#EXT-X-TARGETDURATION:2 #EXT-X-TARGETDURATION:2
#EXT-X-MEDIA-SEQUENCE:1
#EXT-X-DISCONTINUITY-SEQUENCE:1
#EXT-X-I-FRAMES-ONLY #EXT-X-I-FRAMES-ONLY
#EXT-X-KEY:METHOD=SAMPLE-AES,URI="data:text/plain;base64,MjM0NTY3ODkwMTIzNDU2MQ==",IV=0x3334353637383930,KEYFORMAT="identity" #EXT-X-KEY:METHOD=SAMPLE-AES,URI="data:text/plain;base64,MjM0NTY3ODkwMTIzNDU2MQ==",IV=0x3334353637383930,KEYFORMAT="identity"
#EXTINF:1.001, #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 #EXT-X-BYTERANGE:18236@376
bear-640x360-video-2.ts 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

View File

@ -6,4 +6,4 @@
#EXT-X-STREAM-INF:BANDWIDTH=1217518,CODECS="avc1.64001e,mp4a.40.2",RESOLUTION=640x360,AUDIO="default-audio-group" #EXT-X-STREAM-INF:BANDWIDTH=1217518,CODECS="avc1.64001e,mp4a.40.2",RESOLUTION=640x360,AUDIO="default-audio-group"
bear-640x360-video.m3u8 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"

View File

@ -2,10 +2,11 @@
#EXT-X-VERSION:6 #EXT-X-VERSION:6
## Generated with https://github.com/google/shaka-packager version <tag>-<hash>-<test> ## Generated with https://github.com/google/shaka-packager version <tag>-<hash>-<test>
#EXT-X-TARGETDURATION:2 #EXT-X-TARGETDURATION:2
#EXT-X-MEDIA-SEQUENCE:1
#EXT-X-I-FRAMES-ONLY #EXT-X-I-FRAMES-ONLY
#EXTINF:1.001, #EXTINF:1.001,
#EXT-X-BYTERANGE:15604@376
bear-640x360-video-1.ts
#EXTINF:1.001,
#EXT-X-BYTERANGE:18236@376 #EXT-X-BYTERANGE:18236@376
bear-640x360-video-2.ts bear-640x360-video-2.ts
#EXTINF:0.667,
#EXT-X-BYTERANGE:19928@376
bear-640x360-video-3.ts

View File

@ -6,4 +6,4 @@
#EXT-X-STREAM-INF:BANDWIDTH=1217518,CODECS="avc1.64001e,mp4a.40.2",RESOLUTION=640x360,AUDIO="default-audio-group" #EXT-X-STREAM-INF:BANDWIDTH=1217518,CODECS="avc1.64001e,mp4a.40.2",RESOLUTION=640x360,AUDIO="default-audio-group"
bear-640x360-video.m3u8 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"

View File

@ -4,10 +4,11 @@
#EXT-X-TARGETDURATION:2 #EXT-X-TARGETDURATION:2
#EXT-X-PLAYLIST-TYPE:VOD #EXT-X-PLAYLIST-TYPE:VOD
#EXT-X-I-FRAMES-ONLY #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, #EXTINF:1.001,
#EXT-X-BYTERANGE:15604@376 #EXT-X-BYTERANGE:15604@376
bear-640x360-video-1.ts 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, #EXTINF:1.001,
#EXT-X-BYTERANGE:18236@376 #EXT-X-BYTERANGE:18236@376
bear-640x360-video-2.ts bear-640x360-video-2.ts

View File

@ -4,10 +4,11 @@
#EXT-X-TARGETDURATION:2 #EXT-X-TARGETDURATION:2
#EXT-X-PLAYLIST-TYPE:VOD #EXT-X-PLAYLIST-TYPE:VOD
#EXT-X-I-FRAMES-ONLY #EXT-X-I-FRAMES-ONLY
#EXT-X-KEY:METHOD=SAMPLE-AES,URI="data:text/plain;base64,MTIzNDU2Nzg5MDEyMzQ1Ng==",IV=0x3334353637383930,KEYFORMAT="identity"
#EXTINF:1.000, #EXTINF:1.000,
#EXT-X-BYTERANGE:940@376 #EXT-X-BYTERANGE:940@376
sintel-1024x436-video-1.ts 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, #EXTINF:1.000,
#EXT-X-BYTERANGE:376@376 #EXT-X-BYTERANGE:376@376
sintel-1024x436-video-2.ts sintel-1024x436-video-2.ts

View File

@ -4,10 +4,11 @@
#EXT-X-TARGETDURATION:2 #EXT-X-TARGETDURATION:2
#EXT-X-PLAYLIST-TYPE:VOD #EXT-X-PLAYLIST-TYPE:VOD
#EXT-X-I-FRAMES-ONLY #EXT-X-I-FRAMES-ONLY
#EXT-X-KEY:METHOD=SAMPLE-AES,URI="data:text/plain;base64,MTIzNDU2Nzg5MDEyMzQ1Ng==",IV=0x3334353637383930,KEYFORMAT="identity"
#EXTINF:1.001, #EXTINF:1.001,
#EXT-X-BYTERANGE:15604@376 #EXT-X-BYTERANGE:15604@376
bear-640x360-video-1.ts 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, #EXTINF:1.001,
#EXT-X-BYTERANGE:18236@376 #EXT-X-BYTERANGE:18236@376
bear-640x360-video-2.ts bear-640x360-video-2.ts

View File

@ -4,10 +4,11 @@
#EXT-X-TARGETDURATION:2 #EXT-X-TARGETDURATION:2
#EXT-X-PLAYLIST-TYPE:VOD #EXT-X-PLAYLIST-TYPE:VOD
#EXT-X-I-FRAMES-ONLY #EXT-X-I-FRAMES-ONLY
#EXT-X-KEY:METHOD=SAMPLE-AES,URI="data:text/plain;base64,MTIzNDU2Nzg5MDEyMzQ1Ng==",IV=0x3334353637383930,KEYFORMAT="identity"
#EXTINF:1.001, #EXTINF:1.001,
#EXT-X-BYTERANGE:15604@376 #EXT-X-BYTERANGE:15604@376
bear-640x360-ec3-video-1.ts 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, #EXTINF:1.001,
#EXT-X-BYTERANGE:18236@376 #EXT-X-BYTERANGE:18236@376
bear-640x360-ec3-video-2.ts bear-640x360-ec3-video-2.ts

View File

@ -8,10 +8,10 @@
#EXTINF:1.001, #EXTINF:1.001,
#EXT-X-BYTERANGE:15581@80 #EXT-X-BYTERANGE:15581@80
bear-640x360-video-1.m4s bear-640x360-video-1.m4s
#EXT-X-PLACEMENT-OPPORTUNITY
#EXTINF:1.001, #EXTINF:1.001,
#EXT-X-BYTERANGE:18221@80 #EXT-X-BYTERANGE:18221@80
bear-640x360-video-2.m4s bear-640x360-video-2.m4s
#EXT-X-PLACEMENT-OPPORTUNITY
#EXTINF:0.734, #EXTINF:0.734,
#EXT-X-BYTERANGE:19663@80 #EXT-X-BYTERANGE:19663@80
bear-640x360-video-3.m4s bear-640x360-video-3.m4s

View File

@ -9,10 +9,10 @@
#EXTINF:1.001, #EXTINF:1.001,
#EXT-X-BYTERANGE:15581@1159 #EXT-X-BYTERANGE:15581@1159
bear-640x360-video.mp4 bear-640x360-video.mp4
#EXT-X-PLACEMENT-OPPORTUNITY
#EXTINF:1.001, #EXTINF:1.001,
#EXT-X-BYTERANGE:18754@100472 #EXT-X-BYTERANGE:18754@100472
bear-640x360-video.mp4 bear-640x360-video.mp4
#EXT-X-PLACEMENT-OPPORTUNITY
#EXTINF:0.734, #EXTINF:0.734,
#EXT-X-BYTERANGE:20068@222812 #EXT-X-BYTERANGE:20068@222812
bear-640x360-video.mp4 bear-640x360-video.mp4

View File

@ -151,6 +151,7 @@ class SegmentInfoEntry : public HlsEntry {
std::string ToString() override; std::string ToString() override;
double start_time() const { return start_time_; } double start_time() const { return start_time_; }
double duration() const { return duration_; } double duration() const { return duration_; }
void set_duration(double duration) { duration_ = duration; }
private: private:
SegmentInfoEntry(const SegmentInfoEntry&) = delete; SegmentInfoEntry(const SegmentInfoEntry&) = delete;
@ -158,7 +159,7 @@ class SegmentInfoEntry : public HlsEntry {
const std::string file_name_; const std::string file_name_;
const double start_time_; const double start_time_;
const double duration_; double duration_;
const bool use_byte_range_; const bool use_byte_range_;
const uint64_t start_byte_offset_; const uint64_t start_byte_offset_;
const uint64_t segment_file_size_; const uint64_t segment_file_size_;
@ -379,20 +380,20 @@ void MediaPlaylist::AddSegment(const std::string& file_name,
if (stream_type_ == MediaPlaylistStreamType::kVideoIFramesOnly) { if (stream_type_ == MediaPlaylistStreamType::kVideoIFramesOnly) {
if (key_frames_.empty()) if (key_frames_.empty())
return; 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. AdjustLastSegmentInfoEntryDuration(key_frames_.front().timestamp);
for (auto iter = key_frames_.begin(); iter != std::prev(key_frames_.end());
++iter) { for (auto iter = key_frames_.begin(); iter != key_frames_.end(); ++iter) {
const std::string& segment_file_name = // Last entry duration may be adjusted later when the next iframe becomes
iter->segment_file_name.empty() ? file_name : iter->segment_file_name; // available.
AddSegmentInfoEntry(segment_file_name, iter->timestamp, iter->duration, 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); iter->start_byte_offset, iter->size);
} }
key_frames_.clear();
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;
return; return;
} }
return AddSegmentInfoEntry(file_name, start_time, duration, start_byte_offset, return AddSegmentInfoEntry(file_name, start_time, duration, start_byte_offset,
@ -411,9 +412,6 @@ void MediaPlaylist::AddKeyFrame(uint64_t timestamp,
stream_type_ = MediaPlaylistStreamType::kVideoIFramesOnly; stream_type_ = MediaPlaylistStreamType::kVideoIFramesOnly;
use_byte_range_ = true; 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}); key_frames_.push_back({timestamp, start_byte_offset, size});
} }
@ -439,18 +437,6 @@ void MediaPlaylist::AddPlacementOpportunity() {
} }
bool MediaPlaylist::WriteToFile(const std::string& file_path) { 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_) { if (!target_duration_set_) {
SetTargetDuration(ceil(GetLongestSegmentDuration())); SetTargetDuration(ceil(GetLongestSegmentDuration()));
} }
@ -548,6 +534,25 @@ void MediaPlaylist::AddSegmentInfoEntry(const std::string& segment_file_name,
SlideWindow(); SlideWindow();
} }
void MediaPlaylist::AdjustLastSegmentInfoEntryDuration(
uint64_t next_timestamp) {
if (time_scale_ == 0)
return;
const double scaled_next_timestamp =
static_cast<double>(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<SegmentInfoEntry*>(iter->get());
segment_info->set_duration(scaled_next_timestamp -
segment_info->start_time());
break;
}
}
}
void MediaPlaylist::SlideWindow() { void MediaPlaylist::SlideWindow() {
DCHECK(!entries_.empty()); DCHECK(!entries_.empty());
if (hls_params_.time_shift_buffer_depth <= 0.0 || if (hls_params_.time_shift_buffer_depth <= 0.0 ||

View File

@ -185,6 +185,9 @@ class MediaPlaylist {
uint64_t duration, uint64_t duration,
uint64_t start_byte_offset, uint64_t start_byte_offset,
uint64_t size); 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 // Remove elements from |entries_| for live profile. Increments
// |sequence_number_| by the number of segments removed. // |sequence_number_| by the number of segments removed.
void SlideWindow(); void SlideWindow();
@ -231,7 +234,6 @@ class MediaPlaylist {
uint64_t timestamp; uint64_t timestamp;
uint64_t start_byte_offset; uint64_t start_byte_offset;
uint64_t size; uint64_t size;
uint64_t duration;
std::string segment_file_name; std::string segment_file_name;
}; };
std::list<KeyFrameInfo> key_frames_; std::list<KeyFrameInfo> key_frames_;

View File

@ -811,7 +811,7 @@ TEST_F(IFrameMediaPlaylistTest, SingleSegment) {
"#EXT-X-VERSION:6\n" "#EXT-X-VERSION:6\n"
"## Generated with https://github.com/google/shaka-packager version " "## Generated with https://github.com/google/shaka-packager version "
"test\n" "test\n"
"#EXT-X-TARGETDURATION:9\n" "#EXT-X-TARGETDURATION:8\n"
"#EXT-X-PLAYLIST-TYPE:VOD\n" "#EXT-X-PLAYLIST-TYPE:VOD\n"
"#EXT-X-I-FRAMES-ONLY\n" "#EXT-X-I-FRAMES-ONLY\n"
"#EXT-X-MAP:URI=\"file.mp4\",BYTERANGE=\"501@0\"\n" "#EXT-X-MAP:URI=\"file.mp4\",BYTERANGE=\"501@0\"\n"
@ -875,6 +875,49 @@ TEST_F(IFrameMediaPlaylistTest, MultiSegment) {
ASSERT_FILE_STREQ(kMemoryFilePath, kExpectedOutput); 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 { namespace {
const int kNumPreservedSegmentsOutsideLiveWindow = 3; const int kNumPreservedSegmentsOutsideLiveWindow = 3;
const int kMaxNumSegmentsAvailable = const int kMaxNumSegmentsAvailable =