[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:
parent
14caaf1e1c
commit
cf40accaa8
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -2,12 +2,14 @@
|
|||
#EXT-X-VERSION:6
|
||||
## Generated with https://github.com/google/shaka-packager version <tag>-<hash>-<test>
|
||||
#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
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -2,10 +2,11 @@
|
|||
#EXT-X-VERSION:6
|
||||
## Generated with https://github.com/google/shaka-packager version <tag>-<hash>-<test>
|
||||
#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
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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<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() {
|
||||
DCHECK(!entries_.empty());
|
||||
if (hls_params_.time_shift_buffer_depth <= 0.0 ||
|
||||
|
|
|
@ -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<KeyFrameInfo> key_frames_;
|
||||
|
|
|
@ -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 =
|
||||
|
|
Loading…
Reference in New Issue