From 8257eea804e011b7bb80b367532d4b21d68064ea Mon Sep 17 00:00:00 2001 From: KongQun Yang Date: Mon, 5 Feb 2018 11:21:28 -0800 Subject: [PATCH] Bug fixes and clean-ups for I-Frame playlists - Add empty lines between different types of renditions to improve readability. - Group variants with the same audio/text group together, as it is where the Adaptation occurs. - Write master playlist after writing media playlists. This makes more sense and it is also necessary to have the bandwidth of the last iframe playist segment correctly computed. - For fMP4, I-Frame segment must include the 'moof' header. - Fix a problem that hls_iframe_playlist_name is not passed to MuxerListenerFactory. Issue: #287 Change-Id: Icf37c5de1dc29f85ae3f419cbc3264d04ca491a4 --- .../bear-640x360-av-ac3-master-golden.m3u8 | 2 + ...40x360-av-ac3-ts-to-mp4-master-golden.m3u8 | 2 + .../bear-640x360-av-master-golden.m3u8 | 2 + ...ear-640x360-av-mp4-master-cenc-golden.m3u8 | 2 + .../bear-640x360-av-mp4-master-golden.m3u8 | 2 + ...640x360-ec3-av-mp4-master-cenc-golden.m3u8 | 2 + .../sintel-1024x436-v-enc-master-golden.m3u8 | 1 + packager/hls/base/master_playlist.cc | 28 ++++++-- packager/hls/base/master_playlist_unittest.cc | 72 +++++++++++-------- packager/hls/base/simple_hls_notifier.cc | 23 +++--- .../hls/base/simple_hls_notifier_unittest.cc | 14 ++-- packager/media/formats/mp4/segmenter.cc | 18 ++++- packager/packager.cc | 1 + 13 files changed, 110 insertions(+), 59 deletions(-) diff --git a/packager/app/test/testdata/bear-640x360-av-ac3-master-golden.m3u8 b/packager/app/test/testdata/bear-640x360-av-ac3-master-golden.m3u8 index 6267ceef0d..5b874572c1 100644 --- a/packager/app/test/testdata/bear-640x360-av-ac3-master-golden.m3u8 +++ b/packager/app/test/testdata/bear-640x360-av-ac3-master-golden.m3u8 @@ -1,5 +1,7 @@ #EXTM3U ## Generated with https://github.com/google/shaka-packager version -- + #EXT-X-MEDIA:TYPE=AUDIO,URI="audio.m3u8",GROUP-ID="default-audio-group",NAME="stream_0",AUTOSELECT=YES,CHANNELS="2" + #EXT-X-STREAM-INF:BANDWIDTH=1242703,CODECS="avc1.64001e,ac-3",RESOLUTION=640x360,AUDIO="default-audio-group" video.m3u8 diff --git a/packager/app/test/testdata/bear-640x360-av-ac3-ts-to-mp4-master-golden.m3u8 b/packager/app/test/testdata/bear-640x360-av-ac3-ts-to-mp4-master-golden.m3u8 index 5f76149de8..f788d95f5d 100644 --- a/packager/app/test/testdata/bear-640x360-av-ac3-ts-to-mp4-master-golden.m3u8 +++ b/packager/app/test/testdata/bear-640x360-av-ac3-ts-to-mp4-master-golden.m3u8 @@ -1,5 +1,7 @@ #EXTM3U ## Generated with https://github.com/google/shaka-packager version -- + #EXT-X-MEDIA:TYPE=AUDIO,URI="audio.m3u8",GROUP-ID="default-audio-group",NAME="stream_0",AUTOSELECT=YES,CHANNELS="2" + #EXT-X-STREAM-INF:BANDWIDTH=1168277,CODECS="avc1.64001e,ac-3",RESOLUTION=640x360,AUDIO="default-audio-group" video.m3u8 diff --git a/packager/app/test/testdata/bear-640x360-av-master-golden.m3u8 b/packager/app/test/testdata/bear-640x360-av-master-golden.m3u8 index a535ebf18f..dbd74f0f68 100644 --- a/packager/app/test/testdata/bear-640x360-av-master-golden.m3u8 +++ b/packager/app/test/testdata/bear-640x360-av-master-golden.m3u8 @@ -1,5 +1,7 @@ #EXTM3U ## Generated with https://github.com/google/shaka-packager version -- + #EXT-X-MEDIA:TYPE=AUDIO,URI="audio.m3u8",GROUP-ID="default-audio-group",NAME="stream_0",AUTOSELECT=YES,CHANNELS="2" + #EXT-X-STREAM-INF:BANDWIDTH=1217518,CODECS="avc1.64001e,mp4a.40.2",RESOLUTION=640x360,AUDIO="default-audio-group" video.m3u8 diff --git a/packager/app/test/testdata/bear-640x360-av-mp4-master-cenc-golden.m3u8 b/packager/app/test/testdata/bear-640x360-av-mp4-master-cenc-golden.m3u8 index e98c91a8b9..b3f0c6f629 100644 --- a/packager/app/test/testdata/bear-640x360-av-mp4-master-cenc-golden.m3u8 +++ b/packager/app/test/testdata/bear-640x360-av-mp4-master-cenc-golden.m3u8 @@ -1,5 +1,7 @@ #EXTM3U ## Generated with https://github.com/google/shaka-packager version -- + #EXT-X-MEDIA:TYPE=AUDIO,URI="audio.m3u8",GROUP-ID="default-audio-group",NAME="stream_0",AUTOSELECT=YES,CHANNELS="2" + #EXT-X-STREAM-INF:BANDWIDTH=1111340,CODECS="avc1.64001e,mp4a.40.2",RESOLUTION=640x360,AUDIO="default-audio-group" video.m3u8 diff --git a/packager/app/test/testdata/bear-640x360-av-mp4-master-golden.m3u8 b/packager/app/test/testdata/bear-640x360-av-mp4-master-golden.m3u8 index 698fb25f6a..7ce0555ec8 100644 --- a/packager/app/test/testdata/bear-640x360-av-mp4-master-golden.m3u8 +++ b/packager/app/test/testdata/bear-640x360-av-mp4-master-golden.m3u8 @@ -1,5 +1,7 @@ #EXTM3U ## Generated with https://github.com/google/shaka-packager version -- + #EXT-X-MEDIA:TYPE=AUDIO,URI="audio/audio.m3u8",GROUP-ID="default-audio-group",NAME="stream_0",AUTOSELECT=YES,CHANNELS="2" + #EXT-X-STREAM-INF:BANDWIDTH=1105163,CODECS="avc1.64001e,mp4a.40.2",RESOLUTION=640x360,AUDIO="default-audio-group" video/video.m3u8 diff --git a/packager/app/test/testdata/bear-640x360-ec3-av-mp4-master-cenc-golden.m3u8 b/packager/app/test/testdata/bear-640x360-ec3-av-mp4-master-cenc-golden.m3u8 index 61b927c4f9..b7bec5bcf8 100644 --- a/packager/app/test/testdata/bear-640x360-ec3-av-mp4-master-cenc-golden.m3u8 +++ b/packager/app/test/testdata/bear-640x360-ec3-av-mp4-master-cenc-golden.m3u8 @@ -1,5 +1,7 @@ #EXTM3U ## Generated with https://github.com/google/shaka-packager version -- + #EXT-X-MEDIA:TYPE=AUDIO,URI="audio.m3u8",GROUP-ID="default-audio-group",NAME="stream_0",AUTOSELECT=YES,CHANNELS="2" + #EXT-X-STREAM-INF:BANDWIDTH=1174135,CODECS="avc1.64001e,ec-3",RESOLUTION=640x360,AUDIO="default-audio-group" video.m3u8 diff --git a/packager/app/test/testdata/sintel-1024x436-v-enc-master-golden.m3u8 b/packager/app/test/testdata/sintel-1024x436-v-enc-master-golden.m3u8 index 57a59039f3..1ddf769ad5 100644 --- a/packager/app/test/testdata/sintel-1024x436-v-enc-master-golden.m3u8 +++ b/packager/app/test/testdata/sintel-1024x436-v-enc-master-golden.m3u8 @@ -1,4 +1,5 @@ #EXTM3U ## Generated with https://github.com/google/shaka-packager version -- + #EXT-X-STREAM-INF:BANDWIDTH=1183948,CODECS="avc1.64001f",RESOLUTION=1024x436 video.m3u8 diff --git a/packager/hls/base/master_playlist.cc b/packager/hls/base/master_playlist.cc index afc0707a52..27224dca17 100644 --- a/packager/hls/base/master_playlist.cc +++ b/packager/hls/base/master_playlist.cc @@ -325,20 +325,34 @@ void AppendPlaylists(const std::string& default_language, } } - BuildMediaTags(audio_playlist_groups, default_language, base_url, content); - BuildMediaTags(subtitle_playlist_groups, default_language, base_url, content); + if (!audio_playlist_groups.empty()) { + content->append("\n"); + BuildMediaTags(audio_playlist_groups, default_language, base_url, content); + } + + if (!subtitle_playlist_groups.empty()) { + content->append("\n"); + BuildMediaTags(subtitle_playlist_groups, default_language, base_url, + content); + } std::list variants = BuildVariants(audio_playlist_groups, subtitle_playlist_groups); - for (const auto& playlist : video_playlists) { - for (const auto& variant : variants) { + for (const auto& variant : variants) { + if (video_playlists.empty()) + break; + content->append("\n"); + for (const auto& playlist : video_playlists) { BuildStreamInfTag(*playlist, variant, base_url, content); } } - for (const auto& playlist : iframe_playlists) { - // I-Frame playlists do not have variant. Just use the default. - BuildStreamInfTag(*playlist, Variant(), base_url, content); + if (!iframe_playlists.empty()) { + content->append("\n"); + for (const auto& playlist : iframe_playlists) { + // I-Frame playlists do not have variant. Just use the default. + BuildStreamInfTag(*playlist, Variant(), base_url, content); + } } } diff --git a/packager/hls/base/master_playlist_unittest.cc b/packager/hls/base/master_playlist_unittest.cc index 8dae49c2f9..748fe3cad3 100644 --- a/packager/hls/base/master_playlist_unittest.cc +++ b/packager/hls/base/master_playlist_unittest.cc @@ -143,6 +143,7 @@ TEST_F(MasterPlaylistTest, WriteMasterPlaylistOneVideo) { "#EXTM3U\n" "## Generated with https://github.com/google/shaka-packager version " "test\n" + "\n" "#EXT-X-STREAM-INF:BANDWIDTH=435889,CODECS=\"avc1\",RESOLUTION=800x600\n" "http://myplaylistdomain.com/media1.m3u8\n"; @@ -166,6 +167,7 @@ TEST_F(MasterPlaylistTest, WriteMasterPlaylistOneIframePlaylist) { "#EXTM3U\n" "## Generated with https://github.com/google/shaka-packager version " "test\n" + "\n" "#EXT-X-I-FRAME-STREAM-INF:BANDWIDTH=435889,CODECS=\"avc1\",RESOLUTION=" "800x600,URI=\"http://myplaylistdomain.com/media1.m3u8\"\n"; @@ -213,12 +215,14 @@ TEST_F(MasterPlaylistTest, WriteMasterPlaylistVideoAndAudio) { "#EXTM3U\n" "## Generated with https://github.com/google/shaka-packager version " "test\n" + "\n" "#EXT-X-MEDIA:TYPE=AUDIO,URI=\"http://playlists.org/eng.m3u8\"," "GROUP-ID=\"audiogroup\",LANGUAGE=\"en\",NAME=\"english\"," "DEFAULT=YES,AUTOSELECT=YES,CHANNELS=\"2\"\n" "#EXT-X-MEDIA:TYPE=AUDIO,URI=\"http://playlists.org/spa.m3u8\"," "GROUP-ID=\"audiogroup\",LANGUAGE=\"es\",NAME=\"espanol\"," "AUTOSELECT=YES,CHANNELS=\"5\"\n" + "\n" "#EXT-X-STREAM-INF:BANDWIDTH=360000,CODECS=\"sdvideocodec,audiocodec\"," "RESOLUTION=800x600,AUDIO=\"audiogroup\"\n" "http://playlists.org/sd.m3u8\n" @@ -264,15 +268,18 @@ TEST_F(MasterPlaylistTest, WriteMasterPlaylistMultipleAudioGroups) { "#EXTM3U\n" "## Generated with https://github.com/google/shaka-packager version " "test\n" + "\n" "#EXT-X-MEDIA:TYPE=AUDIO,URI=\"http://anydomain.com/eng_hi.m3u8\"," "GROUP-ID=\"audio_hi\",LANGUAGE=\"en\",NAME=\"english_hi\"," "DEFAULT=YES,AUTOSELECT=YES,CHANNELS=\"8\"\n" "#EXT-X-MEDIA:TYPE=AUDIO,URI=\"http://anydomain.com/eng_lo.m3u8\"," "GROUP-ID=\"audio_lo\",LANGUAGE=\"en\",NAME=\"english_lo\"," "DEFAULT=YES,AUTOSELECT=YES,CHANNELS=\"1\"\n" + "\n" "#EXT-X-STREAM-INF:BANDWIDTH=400000,CODECS=\"videocodec,audiocodec_hi\"," "RESOLUTION=800x600,AUDIO=\"audio_hi\"\n" "http://anydomain.com/video.m3u8\n" + "\n" "#EXT-X-STREAM-INF:BANDWIDTH=350000,CODECS=\"videocodec,audiocodec_lo\"," "RESOLUTION=800x600,AUDIO=\"audio_lo\"\n" "http://anydomain.com/video.m3u8\n"; @@ -304,11 +311,13 @@ TEST_F(MasterPlaylistTest, WriteMasterPlaylistSameAudioGroupSameLanguage) { "#EXTM3U\n" "## Generated with https://github.com/google/shaka-packager version " "test\n" + "\n" "#EXT-X-MEDIA:TYPE=AUDIO,URI=\"http://anydomain.com/eng_lo.m3u8\"," "GROUP-ID=\"audio\",LANGUAGE=\"en\",NAME=\"english\"," "DEFAULT=YES,AUTOSELECT=YES,CHANNELS=\"1\"\n" "#EXT-X-MEDIA:TYPE=AUDIO,URI=\"http://anydomain.com/eng_hi.m3u8\"," "GROUP-ID=\"audio\",LANGUAGE=\"en\",NAME=\"english\",CHANNELS=\"8\"\n" + "\n" "#EXT-X-STREAM-INF:BANDWIDTH=400000,CODECS=\"videocodec,audiocodec\"," "RESOLUTION=800x600,AUDIO=\"audio\"\n" "http://anydomain.com/video.m3u8\n"; @@ -345,11 +354,13 @@ TEST_F(MasterPlaylistTest, WriteMasterPlaylistVideosAndTexts) { "#EXTM3U\n" "## Generated with https://github.com/google/shaka-packager version " "test\n" + "\n" "#EXT-X-MEDIA:TYPE=SUBTITLES,URI=\"http://playlists.org/eng.m3u8\"," "GROUP-ID=\"textgroup\",LANGUAGE=\"en\",NAME=\"english\",DEFAULT=YES," "AUTOSELECT=YES\n" "#EXT-X-MEDIA:TYPE=SUBTITLES,URI=\"http://playlists.org/fr.m3u8\"," "GROUP-ID=\"textgroup\",LANGUAGE=\"fr\",NAME=\"french\",AUTOSELECT=YES\n" + "\n" "#EXT-X-STREAM-INF:BANDWIDTH=300000,CODECS=\"sdvideocodec\"," "RESOLUTION=800x600,SUBTITLES=\"textgroup\"\n" "http://playlists.org/sd.m3u8\n" @@ -385,15 +396,18 @@ TEST_F(MasterPlaylistTest, WriteMasterPlaylistVideoAndTextGroups) { "#EXTM3U\n" "## Generated with https://github.com/google/shaka-packager version " "test\n" + "\n" "#EXT-X-MEDIA:TYPE=SUBTITLES,URI=\"http://playlists.org/eng.m3u8\"," - "GROUP-ID=\"en-text-group\",LANGUAGE=\"en\",NAME=\"english\",DEFAULT=YES," - "AUTOSELECT=YES\n" + "GROUP-ID=\"en-text-group\",LANGUAGE=\"en\",NAME=\"english\"," + "DEFAULT=YES,AUTOSELECT=YES\n" "#EXT-X-MEDIA:TYPE=SUBTITLES,URI=\"http://playlists.org/fr.m3u8\"," - "GROUP-ID=\"fr-text-group\",LANGUAGE=\"fr\",NAME=\"french\",AUTOSELECT=" - "YES\n" + "GROUP-ID=\"fr-text-group\",LANGUAGE=\"fr\",NAME=\"french\"," + "AUTOSELECT=YES\n" + "\n" "#EXT-X-STREAM-INF:BANDWIDTH=300000,CODECS=\"sdvideocodec\"," "RESOLUTION=800x600,SUBTITLES=\"en-text-group\"\n" "http://playlists.org/sd.m3u8\n" + "\n" "#EXT-X-STREAM-INF:BANDWIDTH=300000,CODECS=\"sdvideocodec\"," "RESOLUTION=800x600,SUBTITLES=\"fr-text-group\"\n" "http://playlists.org/sd.m3u8\n"; @@ -425,12 +439,15 @@ TEST_F(MasterPlaylistTest, WriteMasterPlaylistVideoAndAudioAndText) { "#EXTM3U\n" "## Generated with https://github.com/google/shaka-packager version " "test\n" + "\n" "#EXT-X-MEDIA:TYPE=AUDIO,URI=\"http://playlists.org/eng.m3u8\"," "GROUP-ID=\"audiogroup\",LANGUAGE=\"en\",NAME=\"english\"," "DEFAULT=YES,AUTOSELECT=YES,CHANNELS=\"2\"\n" + "\n" "#EXT-X-MEDIA:TYPE=SUBTITLES,URI=\"http://playlists.org/eng.m3u8\"," "GROUP-ID=\"textgroup\",LANGUAGE=\"en\",NAME=\"english\",DEFAULT=YES," "AUTOSELECT=YES\n" + "\n" "#EXT-X-STREAM-INF:BANDWIDTH=350000,CODECS=\"sdvideocodec,audiocodec\"," "RESOLUTION=800x600,AUDIO=\"audiogroup\",SUBTITLES=\"textgroup\"\n" "http://playlists.org/sd.m3u8\n"; @@ -481,58 +498,51 @@ TEST_F(MasterPlaylistTest, WriteMasterPlaylistMixedPlaylistsDifferentGroups) { "#EXTM3U\n" "## Generated with https://github.com/google/shaka-packager version " "test\n" - + "\n" "#EXT-X-MEDIA:TYPE=AUDIO,URI=\"http://playlists.org/audio-1.m3u8\"," "GROUP-ID=\"audio-group-1\",LANGUAGE=\"en\",NAME=\"audio 1\"," "DEFAULT=YES,AUTOSELECT=YES,CHANNELS=\"2\"\n" - "#EXT-X-MEDIA:TYPE=AUDIO,URI=\"http://playlists.org/audio-2.m3u8\"," "GROUP-ID=\"audio-group-2\",LANGUAGE=\"en\",NAME=\"audio 2\"," "DEFAULT=YES,AUTOSELECT=YES,CHANNELS=\"2\"\n" - + "\n" "#EXT-X-MEDIA:TYPE=SUBTITLES,URI=\"http://playlists.org/text-1.m3u8\"," - "GROUP-ID=\"text-group-1\",LANGUAGE=\"en\",NAME=\"text " - "1\",DEFAULT=YES,AUTOSELECT=YES\n" - + "GROUP-ID=\"text-group-1\",LANGUAGE=\"en\",NAME=\"text 1\"," + "DEFAULT=YES,AUTOSELECT=YES\n" "#EXT-X-MEDIA:TYPE=SUBTITLES,URI=\"http://playlists.org/text-2.m3u8\"," - "GROUP-ID=\"text-group-2\",LANGUAGE=\"en\",NAME=\"text " - "2\",DEFAULT=YES,AUTOSELECT=YES\n" - + "GROUP-ID=\"text-group-2\",LANGUAGE=\"en\",NAME=\"text 2\"," + "DEFAULT=YES,AUTOSELECT=YES\n" + "\n" "#EXT-X-STREAM-INF:BANDWIDTH=350000,CODECS=\"sdvideocodec,audiocodec\"," "RESOLUTION=800x600,AUDIO=\"audio-group-1\",SUBTITLES=\"text-group-1\"\n" "http://playlists.org/video-1.m3u8\n" - - "#EXT-X-STREAM-INF:BANDWIDTH=350000,CODECS=\"sdvideocodec,audiocodec\"," - "RESOLUTION=800x600,AUDIO=\"audio-group-1\",SUBTITLES=\"text-group-2\"\n" - "http://playlists.org/video-1.m3u8\n" - - "#EXT-X-STREAM-INF:BANDWIDTH=350000,CODECS=\"sdvideocodec,audiocodec\"," - "RESOLUTION=800x600,AUDIO=\"audio-group-2\",SUBTITLES=\"text-group-1\"\n" - "http://playlists.org/video-1.m3u8\n" - - "#EXT-X-STREAM-INF:BANDWIDTH=350000,CODECS=\"sdvideocodec,audiocodec\"," - "RESOLUTION=800x600,AUDIO=\"audio-group-2\",SUBTITLES=\"text-group-2\"\n" - "http://playlists.org/video-1.m3u8\n" - "#EXT-X-STREAM-INF:BANDWIDTH=350000,CODECS=\"sdvideocodec,audiocodec\"," "RESOLUTION=800x600,AUDIO=\"audio-group-1\",SUBTITLES=\"text-group-1\"\n" "http://playlists.org/video-2.m3u8\n" - + "\n" + "#EXT-X-STREAM-INF:BANDWIDTH=350000,CODECS=\"sdvideocodec,audiocodec\"," + "RESOLUTION=800x600,AUDIO=\"audio-group-1\",SUBTITLES=\"text-group-2\"\n" + "http://playlists.org/video-1.m3u8\n" "#EXT-X-STREAM-INF:BANDWIDTH=350000,CODECS=\"sdvideocodec,audiocodec\"," "RESOLUTION=800x600,AUDIO=\"audio-group-1\",SUBTITLES=\"text-group-2\"\n" "http://playlists.org/video-2.m3u8\n" - + "\n" + "#EXT-X-STREAM-INF:BANDWIDTH=350000,CODECS=\"sdvideocodec,audiocodec\"," + "RESOLUTION=800x600,AUDIO=\"audio-group-2\",SUBTITLES=\"text-group-1\"\n" + "http://playlists.org/video-1.m3u8\n" "#EXT-X-STREAM-INF:BANDWIDTH=350000,CODECS=\"sdvideocodec,audiocodec\"," "RESOLUTION=800x600,AUDIO=\"audio-group-2\",SUBTITLES=\"text-group-1\"\n" "http://playlists.org/video-2.m3u8\n" - + "\n" + "#EXT-X-STREAM-INF:BANDWIDTH=350000,CODECS=\"sdvideocodec,audiocodec\"," + "RESOLUTION=800x600,AUDIO=\"audio-group-2\",SUBTITLES=\"text-group-2\"\n" + "http://playlists.org/video-1.m3u8\n" "#EXT-X-STREAM-INF:BANDWIDTH=350000,CODECS=\"sdvideocodec,audiocodec\"," "RESOLUTION=800x600,AUDIO=\"audio-group-2\",SUBTITLES=\"text-group-2\"\n" "http://playlists.org/video-2.m3u8\n" - + "\n" "#EXT-X-I-FRAME-STREAM-INF:BANDWIDTH=100000,CODECS=\"sdvideocodec\"," "RESOLUTION=800x600,URI=\"http://playlists.org/iframe-1.m3u8\"\n" - "#EXT-X-I-FRAME-STREAM-INF:BANDWIDTH=100000,CODECS=\"sdvideocodec\"," "RESOLUTION=800x600,URI=\"http://playlists.org/iframe-2.m3u8\"\n"; diff --git a/packager/hls/base/simple_hls_notifier.cc b/packager/hls/base/simple_hls_notifier.cc index 32d5289b10..a491b1adf4 100644 --- a/packager/hls/base/simple_hls_notifier.cc +++ b/packager/hls/base/simple_hls_notifier.cc @@ -348,11 +348,6 @@ bool SimpleHlsNotifier::NotifyNewSegment(uint32_t stream_id, // Update the playlists when there is new segments in live mode. if (playlist_type() == HlsPlaylistType::kLive || playlist_type() == HlsPlaylistType::kEvent) { - if (!master_playlist_->WriteMasterPlaylist(prefix_, output_dir_, - media_playlists_)) { - LOG(ERROR) << "Failed to write master playlist."; - return false; - } // Update all playlists if target duration is updated. if (target_duration_updated) { for (MediaPlaylist* playlist : media_playlists_) { @@ -361,7 +356,13 @@ bool SimpleHlsNotifier::NotifyNewSegment(uint32_t stream_id, return false; } } else { - return WriteMediaPlaylist(output_dir_, media_playlist.get()); + if (!WriteMediaPlaylist(output_dir_, media_playlist.get())) + return false; + } + if (!master_playlist_->WriteMasterPlaylist(prefix_, output_dir_, + media_playlists_)) { + LOG(ERROR) << "Failed to write master playlist."; + return false; } } return true; @@ -461,16 +462,16 @@ bool SimpleHlsNotifier::NotifyEncryptionUpdate( bool SimpleHlsNotifier::Flush() { base::AutoLock auto_lock(lock_); - if (!master_playlist_->WriteMasterPlaylist(prefix_, output_dir_, - media_playlists_)) { - LOG(ERROR) << "Failed to write master playlist."; - return false; - } for (MediaPlaylist* playlist : media_playlists_) { playlist->SetTargetDuration(target_duration_); if (!WriteMediaPlaylist(output_dir_, playlist)) return false; } + if (!master_playlist_->WriteMasterPlaylist(prefix_, output_dir_, + media_playlists_)) { + LOG(ERROR) << "Failed to write master playlist."; + return false; + } return true; } diff --git a/packager/hls/base/simple_hls_notifier_unittest.cc b/packager/hls/base/simple_hls_notifier_unittest.cc index 023bebe4c5..2036edc2bf 100644 --- a/packager/hls/base/simple_hls_notifier_unittest.cc +++ b/packager/hls/base/simple_hls_notifier_unittest.cc @@ -988,11 +988,6 @@ TEST_P(LiveOrEventSimpleHlsNotifierTest, NotifyNewSegmentsWithMultipleStreams) { EXPECT_CALL(*mock_media_playlist1, GetLongestSegmentDuration()) .WillOnce(Return(kLongestSegmentDuration)); - EXPECT_CALL( - *mock_master_playlist_ptr, - WriteMasterPlaylist( - _, _, ElementsAre(mock_media_playlist1, mock_media_playlist2))) - .WillOnce(Return(true)); // SetTargetDuration and update all playlists as target duration is updated. EXPECT_CALL(*mock_media_playlist1, SetTargetDuration(kTargetDuration)) .Times(1); @@ -1010,14 +1005,17 @@ TEST_P(LiveOrEventSimpleHlsNotifierTest, NotifyNewSegmentsWithMultipleStreams) { .Append(base::FilePath::FromUTF8Unsafe("playlist2.m3u8")) .AsUTF8Unsafe()))) .WillOnce(Return(true)); + EXPECT_CALL( + *mock_master_playlist_ptr, + WriteMasterPlaylist( + _, _, ElementsAre(mock_media_playlist1, mock_media_playlist2))) + .WillOnce(Return(true)); EXPECT_TRUE(notifier.NotifyNewSegment(stream_id1, "segment_name", kStartTime, kDuration, 0, kSize)); EXPECT_CALL(*mock_media_playlist2, AddSegment(_, _, _, _, _)).Times(1); EXPECT_CALL(*mock_media_playlist2, GetLongestSegmentDuration()) .WillOnce(Return(kLongestSegmentDuration)); - EXPECT_CALL(*mock_master_playlist_ptr, WriteMasterPlaylist(_, _, _)) - .WillOnce(Return(true)); // Not updating other playlists as target duration does not change. EXPECT_CALL(*mock_media_playlist2, WriteToFile(StrEq( @@ -1025,6 +1023,8 @@ TEST_P(LiveOrEventSimpleHlsNotifierTest, NotifyNewSegmentsWithMultipleStreams) { .Append(base::FilePath::FromUTF8Unsafe("playlist2.m3u8")) .AsUTF8Unsafe()))) .WillOnce(Return(true)); + EXPECT_CALL(*mock_master_playlist_ptr, WriteMasterPlaylist(_, _, _)) + .WillOnce(Return(true)); EXPECT_TRUE(notifier.NotifyNewSegment(stream_id2, "segment_name", kStartTime, kDuration, 0, kSize)); } diff --git a/packager/media/formats/mp4/segmenter.cc b/packager/media/formats/mp4/segmenter.cc index 51eb978279..46345254f9 100644 --- a/packager/media/formats/mp4/segmenter.cc +++ b/packager/media/formats/mp4/segmenter.cc @@ -189,13 +189,25 @@ Status Segmenter::FinalizeSegment(size_t stream_id, sidx_->references[sidx_->references.size() - 1].referenced_size = data_offset + mdat.data_size; + const uint64_t moof_start_offset = fragment_buffer_->Size(); + // Write the fragment to buffer. moof_->Write(fragment_buffer_.get()); mdat.WriteHeader(fragment_buffer_.get()); + + bool first_key_frame = true; for (const std::unique_ptr& fragmenter : fragmenters_) { - for (const KeyFrameInfo& key_frame_info : fragmenter->key_frame_infos()) { - key_frame_infos_.push_back(key_frame_info); - key_frame_infos_.back().start_byte_offset += fragment_buffer_->Size(); + // https://goo.gl/xcFus6 6. Trick play requirements + // 6.10. If using fMP4, I-frame segments must include the 'moof' header + // associated with the I-frame. It also implies that only the first key + // frame can be included. + if (!fragmenter->key_frame_infos().empty() && first_key_frame) { + const KeyFrameInfo& key_frame_info = + fragmenter->key_frame_infos().front(); + first_key_frame = false; + key_frame_infos_.push_back( + {key_frame_info.timestamp, moof_start_offset, + fragment_buffer_->Size() - moof_start_offset + key_frame_info.size}); } fragment_buffer_->AppendBuffer(*fragmenter->data()); } diff --git a/packager/packager.cc b/packager/packager.cc index 0c9181bfb5..f53e6cc910 100644 --- a/packager/packager.cc +++ b/packager/packager.cc @@ -81,6 +81,7 @@ MuxerListenerFactory::StreamData ToMuxerListenerData( data.hls_group_id = stream.hls_group_id; data.hls_name = stream.hls_name; data.hls_playlist_name = stream.hls_playlist_name; + data.hls_iframe_playlist_name = stream.hls_iframe_playlist_name; return data; };