From 32d26094ba9f5f1e8f6cf4aeb8d40258ce04a459 Mon Sep 17 00:00:00 2001 From: KongQun Yang Date: Wed, 17 Jan 2018 15:43:41 -0800 Subject: [PATCH] [HLS] Support AUTOSELECT and DEFAULT in EXT-X-MEDIA According to HLS spec: https://goo.gl/MiqjNd 4.3.4.1.1. Rendition Groups - A Group MUST NOT have more than one member with a DEFAULT attribute of YES. - Each EXT-X-MEDIA tag with an AUTOSELECT=YES attribute SHOULD have a combination of LANGUAGE[RFC5646], ASSOC-LANGUAGE, FORCED, and CHARACTERISTICS attributes that is distinct from those of other AUTOSELECT=YES members of its Group. We tag the first rendition with a particular language in an audio group with 'AUTOSELECT'; it is tagged with 'DEFAULT' too if the language matches --default_language. Fixes #315 Change-Id: Iacc0bc8c89ebffce8717fa65e82d6daf5a1f6adc --- packager/app/packager_main.cc | 1 + .../bear-640x360-av-master-golden.m3u8 | 2 +- ...ear-640x360-av-mp4-master-cenc-golden.m3u8 | 2 +- .../bear-640x360-av-mp4-master-golden.m3u8 | 2 +- packager/hls/base/master_playlist.cc | 38 +++++- packager/hls/base/master_playlist.h | 6 +- packager/hls/base/master_playlist_unittest.cc | 84 ++++++++++++- packager/hls/base/simple_hls_notifier.cc | 26 ++-- packager/hls/base/simple_hls_notifier.h | 23 +--- .../hls/base/simple_hls_notifier_unittest.cc | 112 +++++++----------- packager/hls/public/hls_params.h | 4 + packager/packager.cc | 10 +- 12 files changed, 187 insertions(+), 123 deletions(-) diff --git a/packager/app/packager_main.cc b/packager/app/packager_main.cc index 1968d7f21e..f0e5c6e39a 100644 --- a/packager/app/packager_main.cc +++ b/packager/app/packager_main.cc @@ -406,6 +406,7 @@ base::Optional GetPackagingParams() { hls_params.base_url = FLAGS_hls_base_url; hls_params.key_uri = FLAGS_hls_key_uri; hls_params.time_shift_buffer_depth = FLAGS_time_shift_buffer_depth; + hls_params.default_language = FLAGS_default_language; TestParams& test_params = packaging_params.test_params; test_params.dump_stream_info = FLAGS_dump_stream_info; 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 53b169982e..2f4d6c0850 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,5 @@ #EXTM3U ## Generated with https://github.com/google/shaka-packager version -- -#EXT-X-MEDIA:TYPE=AUDIO,URI="audio.m3u8",GROUP-ID="audio",NAME="stream_0",CHANNELS="2" +#EXT-X-MEDIA:TYPE=AUDIO,URI="audio.m3u8",GROUP-ID="audio",NAME="stream_0",AUTOSELECT=YES,CHANNELS="2" #EXT-X-STREAM-INF:BANDWIDTH=1217518,CODECS="avc1.64001e,mp4a.40.2",RESOLUTION=640x360,AUDIO="audio" 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 87952b537c..82993be545 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,5 @@ #EXTM3U ## Generated with https://github.com/google/shaka-packager version -- -#EXT-X-MEDIA:TYPE=AUDIO,URI="audio.m3u8",GROUP-ID="audio",NAME="stream_0",CHANNELS="2" +#EXT-X-MEDIA:TYPE=AUDIO,URI="audio.m3u8",GROUP-ID="audio",NAME="stream_0",AUTOSELECT=YES,CHANNELS="2" #EXT-X-STREAM-INF:BANDWIDTH=1111147,CODECS="avc1.64001e,mp4a.40.2",RESOLUTION=640x360,AUDIO="audio" 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 7e1a2c6e00..d50de13a66 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,5 @@ #EXTM3U ## Generated with https://github.com/google/shaka-packager version -- -#EXT-X-MEDIA:TYPE=AUDIO,URI="audio/audio.m3u8",GROUP-ID="audio",NAME="stream_0",CHANNELS="2" +#EXT-X-MEDIA:TYPE=AUDIO,URI="audio/audio.m3u8",GROUP-ID="audio",NAME="stream_0",AUTOSELECT=YES,CHANNELS="2" #EXT-X-STREAM-INF:BANDWIDTH=1105129,CODECS="avc1.64001e,mp4a.40.2",RESOLUTION=640x360,AUDIO="audio" video/video.m3u8 diff --git a/packager/hls/base/master_playlist.cc b/packager/hls/base/master_playlist.cc index 8751b51917..efe33fdd71 100644 --- a/packager/hls/base/master_playlist.cc +++ b/packager/hls/base/master_playlist.cc @@ -23,6 +23,9 @@ namespace { void AppendMediaTag(const std::string& base_url, const std::string& group_id, const MediaPlaylist* audio_playlist, + const std::string& language, + bool is_default, + bool is_autoselect, std::string* out) { DCHECK(audio_playlist); DCHECK(out); @@ -31,10 +34,13 @@ void AppendMediaTag(const std::string& base_url, base::StringAppendF(out, ",URI=\"%s\"", (base_url + audio_playlist->file_name()).c_str()); base::StringAppendF(out, ",GROUP-ID=\"%s\"", group_id.c_str()); - std::string language = audio_playlist->GetLanguage(); if (!language.empty()) base::StringAppendF(out, ",LANGUAGE=\"%s\"", language.c_str()); base::StringAppendF(out, ",NAME=\"%s\"", audio_playlist->name().c_str()); + if (is_default) + base::StringAppendF(out, ",DEFAULT=YES"); + if (is_autoselect) + base::StringAppendF(out, ",AUTOSELECT=YES"); base::StringAppendF(out, ",CHANNELS=\"%d\"", audio_playlist->GetNumChannels()); out->append("\n"); @@ -62,8 +68,9 @@ void AppendStreamInfoTag(uint64_t bitrate, } } // namespace -MasterPlaylist::MasterPlaylist(const std::string& file_name) - : file_name_(file_name) {} +MasterPlaylist::MasterPlaylist(const std::string& file_name, + const std::string& default_language) + : file_name_(file_name), default_language_(default_language) {} MasterPlaylist::~MasterPlaylist() {} void MasterPlaylist::AddMediaPlaylist(MediaPlaylist* media_playlist) { @@ -99,9 +106,32 @@ bool MasterPlaylist::WriteMasterPlaylist(const std::string& base_url, const std::list& audio_playlists = group_id_audio_playlists.second; + // Tracks the language of the playlist in this group. + // According to HLS spec: https://goo.gl/MiqjNd 4.3.4.1.1. Rendition Groups + // - A Group MUST NOT have more than one member with a DEFAULT attribute of + // YES. + // - Each EXT-X-MEDIA tag with an AUTOSELECT=YES attribute SHOULD have a + // combination of LANGUAGE[RFC5646], ASSOC-LANGUAGE, FORCED, and + // CHARACTERISTICS attributes that is distinct from those of other + // AUTOSELECT=YES members of its Group. + // We tag the first rendition encountered with a particular language with + // 'AUTOSELECT'; it is tagged with 'DEFAULT' too if the language matches + // |default_language_|. + std::set languages; + uint64_t max_audio_bitrate = 0; for (const MediaPlaylist* audio_playlist : audio_playlists) { - AppendMediaTag(base_url, group_id, audio_playlist, &audio_output); + bool is_default = false; + bool is_autoselect = false; + const std::string language = audio_playlist->GetLanguage(); + if (languages.find(language) == languages.end()) { + is_default = !language.empty() && language == default_language_; + is_autoselect = true; + languages.insert(language); + } + + AppendMediaTag(base_url, group_id, audio_playlist, language, is_default, + is_autoselect, &audio_output); const uint64_t audio_bitrate = audio_playlist->Bitrate(); if (audio_bitrate > max_audio_bitrate) max_audio_bitrate = audio_bitrate; diff --git a/packager/hls/base/master_playlist.h b/packager/hls/base/master_playlist.h index bcd8affd32..4f521314dc 100644 --- a/packager/hls/base/master_playlist.h +++ b/packager/hls/base/master_playlist.h @@ -23,7 +23,10 @@ class MediaPlaylist; class MasterPlaylist { public: /// @param file_name is the file name of the master playlist. - explicit MasterPlaylist(const std::string& file_name); + /// @param default_language determines the rendition that should be tagged + /// with 'DEFAULT'. + MasterPlaylist(const std::string& file_name, + const std::string& default_language); virtual ~MasterPlaylist(); /// @param media_playlist is a MediaPlaylist that should get added to this @@ -46,6 +49,7 @@ class MasterPlaylist { private: std::string written_playlist_; const std::string file_name_; + const std::string default_language_; std::list all_playlists_; std::list video_playlists_; // The key is the audio group name. diff --git a/packager/hls/base/master_playlist_unittest.cc b/packager/hls/base/master_playlist_unittest.cc index 2b9ba5ffa6..a274aa2ef2 100644 --- a/packager/hls/base/master_playlist_unittest.cc +++ b/packager/hls/base/master_playlist_unittest.cc @@ -28,6 +28,7 @@ using base::FilePath; namespace { const char kDefaultMasterPlaylistName[] = "playlist.m3u8"; +const char kDefaultLanguage[] = "en"; const uint32_t kWidth = 800; const uint32_t kHeight = 600; const HlsPlaylistType kVodPlaylist = HlsPlaylistType::kVod; @@ -36,7 +37,7 @@ const HlsPlaylistType kVodPlaylist = HlsPlaylistType::kVod; class MasterPlaylistTest : public ::testing::Test { protected: MasterPlaylistTest() - : master_playlist_(kDefaultMasterPlaylistName), + : master_playlist_(kDefaultMasterPlaylistName, kDefaultLanguage), test_output_dir_("memory://test_dir"), master_playlist_path_( FilePath::FromUTF8Unsafe(test_output_dir_) @@ -166,10 +167,10 @@ TEST_F(MasterPlaylistTest, WriteMasterPlaylistVideoAndAudio) { "test\n" "#EXT-X-MEDIA:TYPE=AUDIO,URI=\"http://playlists.org/eng.m3u8\"," "GROUP-ID=\"audiogroup\",LANGUAGE=\"en\",NAME=\"english\"," - "CHANNELS=\"2\"\n" + "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\"," - "CHANNELS=\"5\"\n" + "AUTOSELECT=YES,CHANNELS=\"5\"\n" "#EXT-X-STREAM-INF:BANDWIDTH=360000,CODECS=\"sdvideocodec,audiocodec\"," "RESOLUTION=800x600,AUDIO=\"audiogroup\"\n" "http://playlists.org/sd.m3u8\n" @@ -181,7 +182,7 @@ TEST_F(MasterPlaylistTest, WriteMasterPlaylistVideoAndAudio) { } TEST_F(MasterPlaylistTest, WriteMasterPlaylistMultipleAudioGroups) { - // First video, sd.m3u8. + // First video, video.m3u8. std::string video_codec = "videocodec"; MockMediaPlaylist video_playlist(kVodPlaylist, "video.m3u8", "somename", "somegroupid"); @@ -213,6 +214,7 @@ TEST_F(MasterPlaylistTest, WriteMasterPlaylistMultipleAudioGroups) { .Times(0); master_playlist_.AddMediaPlaylist(&eng_lo_playlist); + // Second audio, eng_hi.m3u8. std::string audio_codec_hi = "audiocodec_hi"; MockMediaPlaylist eng_hi_playlist(kVodPlaylist, "eng_hi.m3u8", "english_hi", "audio_hi"); @@ -240,10 +242,10 @@ TEST_F(MasterPlaylistTest, WriteMasterPlaylistMultipleAudioGroups) { "test\n" "#EXT-X-MEDIA:TYPE=AUDIO,URI=\"http://anydomain.com/eng_hi.m3u8\"," "GROUP-ID=\"audio_hi\",LANGUAGE=\"en\",NAME=\"english_hi\"," - "CHANNELS=\"8\"\n" + "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\"," - "CHANNELS=\"1\"\n" + "DEFAULT=YES,AUTOSELECT=YES,CHANNELS=\"1\"\n" "#EXT-X-STREAM-INF:BANDWIDTH=400000,CODECS=\"videocodec,audiocodec_hi\"," "RESOLUTION=800x600,AUDIO=\"audio_hi\"\n" "http://anydomain.com/video.m3u8\n" @@ -254,5 +256,75 @@ TEST_F(MasterPlaylistTest, WriteMasterPlaylistMultipleAudioGroups) { ASSERT_EQ(expected, actual); } +TEST_F(MasterPlaylistTest, WriteMasterPlaylistSameAudioGroupSameLanguage) { + // First video, video.m3u8. + std::string video_codec = "videocodec"; + MockMediaPlaylist video_playlist(kVodPlaylist, "video.m3u8", "somename", + "somegroupid"); + video_playlist.SetStreamTypeForTesting( + MediaPlaylist::MediaPlaylistStreamType::kPlayListVideo); + video_playlist.SetCodecForTesting(video_codec); + EXPECT_CALL(video_playlist, Bitrate()) + .Times(AtLeast(1)) + .WillRepeatedly(Return(300000)); + EXPECT_CALL(video_playlist, GetDisplayResolution(NotNull(), NotNull())) + .WillRepeatedly(DoAll(SetArgPointee<0>(kWidth), SetArgPointee<1>(kHeight), + Return(true))); + master_playlist_.AddMediaPlaylist(&video_playlist); + + // First audio, eng_lo.m3u8. + const std::string audio_codec = "audiocodec"; + MockMediaPlaylist eng_lo_playlist(kVodPlaylist, "eng_lo.m3u8", "english", + "audio"); + EXPECT_CALL(eng_lo_playlist, GetLanguage()).WillRepeatedly(Return("en")); + EXPECT_CALL(eng_lo_playlist, GetNumChannels()).WillRepeatedly(Return(1)); + eng_lo_playlist.SetStreamTypeForTesting( + MediaPlaylist::MediaPlaylistStreamType::kPlayListAudio); + eng_lo_playlist.SetCodecForTesting(audio_codec); + EXPECT_CALL(eng_lo_playlist, Bitrate()) + .Times(AtLeast(1)) + .WillRepeatedly(Return(50000)); + EXPECT_CALL(eng_lo_playlist, GetDisplayResolution(NotNull(), NotNull())) + .Times(0); + master_playlist_.AddMediaPlaylist(&eng_lo_playlist); + + // Second audio, eng_hi.m3u8. + MockMediaPlaylist eng_hi_playlist(kVodPlaylist, "eng_hi.m3u8", "english", + "audio"); + EXPECT_CALL(eng_hi_playlist, GetLanguage()).WillRepeatedly(Return("en")); + EXPECT_CALL(eng_hi_playlist, GetNumChannels()).WillRepeatedly(Return(8)); + eng_hi_playlist.SetStreamTypeForTesting( + MediaPlaylist::MediaPlaylistStreamType::kPlayListAudio); + eng_hi_playlist.SetCodecForTesting(audio_codec); + EXPECT_CALL(eng_hi_playlist, Bitrate()) + .Times(AtLeast(1)) + .WillRepeatedly(Return(100000)); + EXPECT_CALL(eng_hi_playlist, GetDisplayResolution(NotNull(), NotNull())) + .Times(0); + master_playlist_.AddMediaPlaylist(&eng_hi_playlist); + + const char kBaseUrl[] = "http://anydomain.com/"; + EXPECT_TRUE(master_playlist_.WriteMasterPlaylist(kBaseUrl, test_output_dir_)); + + std::string actual; + ASSERT_TRUE(File::ReadFileToString(master_playlist_path_.c_str(), &actual)); + + const std::string expected = + "#EXTM3U\n" + "## Generated with https://github.com/google/shaka-packager version " + "test\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" + "#EXT-X-STREAM-INF:BANDWIDTH=400000,CODECS=\"videocodec,audiocodec\"," + "RESOLUTION=800x600,AUDIO=\"audio\"\n" + "http://anydomain.com/video.m3u8\n"; + + ASSERT_EQ(expected, actual); +} + } // namespace hls } // namespace shaka diff --git a/packager/hls/base/simple_hls_notifier.cc b/packager/hls/base/simple_hls_notifier.cc index 460d72ff7d..34cd5cfc44 100644 --- a/packager/hls/base/simple_hls_notifier.cc +++ b/packager/hls/base/simple_hls_notifier.cc @@ -247,19 +247,19 @@ std::unique_ptr MediaPlaylistFactory::Create( type, time_shift_buffer_depth, file_name, name, group_id)); } -SimpleHlsNotifier::SimpleHlsNotifier(HlsPlaylistType playlist_type, - double time_shift_buffer_depth, - const std::string& prefix, - const std::string& key_uri, - const std::string& output_dir, - const std::string& master_playlist_name) - : HlsNotifier(playlist_type), - time_shift_buffer_depth_(time_shift_buffer_depth), - prefix_(prefix), - key_uri_(key_uri), - output_dir_(output_dir), - media_playlist_factory_(new MediaPlaylistFactory()), - master_playlist_(new MasterPlaylist(master_playlist_name)) {} +SimpleHlsNotifier::SimpleHlsNotifier(const HlsParams& hls_params) + : HlsNotifier(hls_params.playlist_type), + time_shift_buffer_depth_(hls_params.time_shift_buffer_depth), + prefix_(hls_params.base_url), + key_uri_(hls_params.key_uri), + media_playlist_factory_(new MediaPlaylistFactory()) { + const base::FilePath master_playlist_path( + base::FilePath::FromUTF8Unsafe(hls_params.master_playlist_output)); + output_dir_ = master_playlist_path.DirName().AsUTF8Unsafe(); + master_playlist_.reset( + new MasterPlaylist(master_playlist_path.BaseName().AsUTF8Unsafe(), + hls_params.default_language)); +} SimpleHlsNotifier::~SimpleHlsNotifier() {} diff --git a/packager/hls/base/simple_hls_notifier.h b/packager/hls/base/simple_hls_notifier.h index ffe75e3185..8051f0834a 100644 --- a/packager/hls/base/simple_hls_notifier.h +++ b/packager/hls/base/simple_hls_notifier.h @@ -38,25 +38,8 @@ class MediaPlaylistFactory { /// This is thread safe. class SimpleHlsNotifier : public HlsNotifier { public: - /// @a prefix is used as hte prefix for all the URIs for Media Playlist. This - /// includes the segment URIs in the Media Playlists. - /// @param playlist_type is the type of the playlists. - /// @param time_shift_buffer_depth determines the duration of the time - /// shifting buffer, only for live HLS. - /// @param prefix is the used as the prefix for MediaPlaylist URIs. May be - /// empty for relative URI from the playlist. - /// @param key_uri defines the key uri for "identity" and - /// "com.apple.streamingkeydelivery" key formats. Ignored if the - /// playlist is not encrypted or not using the above key formats. - /// @param output_dir is the output directory of the playlists. May be empty - /// to write to current directory. - /// @param master_playlist_name is the name of the master playlist. - SimpleHlsNotifier(HlsPlaylistType playlist_type, - double time_shift_buffer_depth, - const std::string& prefix, - const std::string& key_uri, - const std::string& output_dir, - const std::string& master_playlist_name); + /// @param hls_params contains parameters for setting up the notifier. + explicit SimpleHlsNotifier(const HlsParams& hls_params); ~SimpleHlsNotifier() override; /// @name HlsNotifier implemetation overrides. @@ -94,7 +77,7 @@ class SimpleHlsNotifier : public HlsNotifier { const double time_shift_buffer_depth_ = 0; const std::string prefix_; const std::string key_uri_; - const std::string output_dir_; + std::string output_dir_; uint32_t target_duration_ = 0; std::unique_ptr media_playlist_factory_; diff --git a/packager/hls/base/simple_hls_notifier_unittest.cc b/packager/hls/base/simple_hls_notifier_unittest.cc index cdf1c0117b..cb3dc79b97 100644 --- a/packager/hls/base/simple_hls_notifier_unittest.cc +++ b/packager/hls/base/simple_hls_notifier_unittest.cc @@ -31,6 +31,7 @@ using ::testing::_; namespace { const char kMasterPlaylistName[] = "master.m3u8"; +const char kDefaultLanguage[] = "en"; const char kEmptyKeyUri[] = ""; const char kFairplayKeyUri[] = "skd://www.license.com/getkey?key_id=testing"; const char kIdentityKeyUri[] = "https://www.license.com/getkey?key_id=testing"; @@ -39,7 +40,8 @@ const HlsPlaylistType kLivePlaylist = HlsPlaylistType::kLive; class MockMasterPlaylist : public MasterPlaylist { public: - MockMasterPlaylist() : MasterPlaylist(kMasterPlaylistName) {} + MockMasterPlaylist() + : MasterPlaylist(kMasterPlaylistName, kDefaultLanguage) {} MOCK_METHOD1(AddMediaPlaylist, void(MediaPlaylist* media_playlist)); MOCK_METHOD2(WriteMasterPlaylist, @@ -68,7 +70,7 @@ class MockMediaPlaylistFactory : public MediaPlaylistFactory { const double kTestTimeShiftBufferDepth = 1800.0; const char kTestPrefix[] = "http://testprefix.com/"; const char kEmptyPrefix[] = ""; -const char kAnyOutputDir[] = "anything/"; +const char kAnyOutputDir[] = "anything"; const uint64_t kAnyStartTime = 10; const uint64_t kAnyDuration = 1000; @@ -92,7 +94,14 @@ class SimpleHlsNotifierTest : public ::testing::Test { media::kCommonSystemId + arraysize(media::kCommonSystemId)), fairplay_system_id_( media::kFairplaySystemId, - media::kFairplaySystemId + arraysize(media::kFairplaySystemId)) {} + media::kFairplaySystemId + arraysize(media::kFairplaySystemId)) { + hls_params_.playlist_type = kVodPlaylist; + hls_params_.time_shift_buffer_depth = kTestTimeShiftBufferDepth; + hls_params_.base_url = kTestPrefix; + hls_params_.key_uri = kEmptyKeyUri; + hls_params_.master_playlist_output = + std::string(kAnyOutputDir) + "/" + kMasterPlaylistName; + } void InjectMediaPlaylistFactory(std::unique_ptr factory, SimpleHlsNotifier* notifier) { @@ -138,12 +147,11 @@ class SimpleHlsNotifierTest : public ::testing::Test { const std::vector widevine_system_id_; const std::vector common_system_id_; const std::vector fairplay_system_id_; + HlsParams hls_params_; }; TEST_F(SimpleHlsNotifierTest, Init) { - SimpleHlsNotifier notifier(kVodPlaylist, kTestTimeShiftBufferDepth, - kTestPrefix, kEmptyKeyUri, kAnyOutputDir, - kMasterPlaylistName); + SimpleHlsNotifier notifier(hls_params_); EXPECT_TRUE(notifier.Init()); } @@ -174,9 +182,7 @@ TEST_F(SimpleHlsNotifierTest, RebaseSegmentUrl) { StrEq("groupid"))) .WillOnce(Return(mock_media_playlist)); - SimpleHlsNotifier notifier(kVodPlaylist, kTestTimeShiftBufferDepth, - kTestPrefix, kEmptyKeyUri, kAnyOutputDir, - kMasterPlaylistName); + SimpleHlsNotifier notifier(hls_params_); InjectMasterPlaylist(std::move(mock_master_playlist), ¬ifier); InjectMediaPlaylistFactory(std::move(factory), ¬ifier); @@ -215,9 +221,7 @@ TEST_F(SimpleHlsNotifierTest, RebaseInitSegmentUrl) { StrEq("groupid"))) .WillOnce(Return(mock_media_playlist)); - SimpleHlsNotifier notifier(kVodPlaylist, kTestTimeShiftBufferDepth, - kTestPrefix, kEmptyKeyUri, kAnyOutputDir, - kMasterPlaylistName); + SimpleHlsNotifier notifier(hls_params_); InjectMasterPlaylist(std::move(mock_master_playlist), ¬ifier); InjectMediaPlaylistFactory(std::move(factory), ¬ifier); @@ -255,9 +259,8 @@ TEST_F(SimpleHlsNotifierTest, RebaseSegmentUrlRelativeToPlaylist) { StrEq("groupid"))) .WillOnce(Return(mock_media_playlist)); - SimpleHlsNotifier notifier(kVodPlaylist, kTestTimeShiftBufferDepth, - kEmptyPrefix, kEmptyKeyUri, kAnyOutputDir, - kMasterPlaylistName); + hls_params_.base_url = kEmptyPrefix; + SimpleHlsNotifier notifier(hls_params_); InjectMasterPlaylist(std::move(mock_master_playlist), ¬ifier); InjectMediaPlaylistFactory(std::move(factory), ¬ifier); @@ -277,9 +280,9 @@ TEST_F(SimpleHlsNotifierTest, RebaseSegmentUrlRelativeToPlaylist) { // prefix is stripped from segment path. TEST_F(SimpleHlsNotifierTest, RebaseAbsoluteSegmentPrefixAndOutputDirMatch) { const char kAbsoluteOutputDir[] = "/tmp/something/"; - SimpleHlsNotifier test_notifier(kVodPlaylist, kTestTimeShiftBufferDepth, - kTestPrefix, kEmptyKeyUri, kAbsoluteOutputDir, - kMasterPlaylistName); + hls_params_.master_playlist_output = + std::string(kAbsoluteOutputDir) + kMasterPlaylistName; + SimpleHlsNotifier test_notifier(hls_params_); std::unique_ptr mock_master_playlist( new MockMasterPlaylist()); @@ -319,9 +322,9 @@ TEST_F(SimpleHlsNotifierTest, RebaseAbsoluteSegmentPrefixAndOutputDirMatch) { TEST_F(SimpleHlsNotifierTest, RebaseAbsoluteSegmentCompletelyDifferentDirectory) { const char kAbsoluteOutputDir[] = "/tmp/something/"; - SimpleHlsNotifier test_notifier(kVodPlaylist, kTestTimeShiftBufferDepth, - kTestPrefix, kEmptyKeyUri, kAbsoluteOutputDir, - kMasterPlaylistName); + hls_params_.master_playlist_output = + std::string(kAbsoluteOutputDir) + kMasterPlaylistName; + SimpleHlsNotifier test_notifier(hls_params_); std::unique_ptr mock_master_playlist( new MockMasterPlaylist()); @@ -356,9 +359,7 @@ TEST_F(SimpleHlsNotifierTest, } TEST_F(SimpleHlsNotifierTest, Flush) { - SimpleHlsNotifier notifier(kVodPlaylist, kTestTimeShiftBufferDepth, - kTestPrefix, kEmptyKeyUri, kAnyOutputDir, - kMasterPlaylistName); + SimpleHlsNotifier notifier(hls_params_); std::unique_ptr mock_master_playlist( new MockMasterPlaylist()); EXPECT_CALL(*mock_master_playlist, @@ -386,9 +387,7 @@ TEST_F(SimpleHlsNotifierTest, NotifyNewStream) { StrEq("groupid"))) .WillOnce(Return(mock_media_playlist)); - SimpleHlsNotifier notifier(kVodPlaylist, kTestTimeShiftBufferDepth, - kTestPrefix, kEmptyKeyUri, kAnyOutputDir, - kMasterPlaylistName); + SimpleHlsNotifier notifier(hls_params_); InjectMasterPlaylist(std::move(mock_master_playlist), ¬ifier); InjectMediaPlaylistFactory(std::move(factory), ¬ifier); @@ -430,9 +429,7 @@ TEST_F(SimpleHlsNotifierTest, NotifyNewSegment) { EXPECT_CALL(*mock_media_playlist, GetLongestSegmentDuration()) .WillOnce(Return(kLongestSegmentDuration)); - SimpleHlsNotifier notifier(kVodPlaylist, kTestTimeShiftBufferDepth, - kTestPrefix, kEmptyKeyUri, kAnyOutputDir, - kMasterPlaylistName); + SimpleHlsNotifier notifier(hls_params_); MockMasterPlaylist* mock_master_playlist_ptr = mock_master_playlist.get(); InjectMasterPlaylist(std::move(mock_master_playlist), ¬ifier); InjectMediaPlaylistFactory(std::move(factory), ¬ifier); @@ -463,9 +460,7 @@ TEST_F(SimpleHlsNotifierTest, NotifyNewSegment) { } TEST_F(SimpleHlsNotifierTest, NotifyNewSegmentWithoutStreamsRegistered) { - SimpleHlsNotifier notifier(kVodPlaylist, kTestTimeShiftBufferDepth, - kTestPrefix, kEmptyKeyUri, kAnyOutputDir, - kMasterPlaylistName); + SimpleHlsNotifier notifier(hls_params_); EXPECT_TRUE(notifier.Init()); EXPECT_FALSE(notifier.NotifyNewSegment(1u, "anything", 0u, 0u, 0u, 0u)); } @@ -474,9 +469,7 @@ TEST_F(SimpleHlsNotifierTest, NotifyEncryptionUpdateWidevine) { // Pointer released by SimpleHlsNotifier. MockMediaPlaylist* mock_media_playlist = new MockMediaPlaylist(kVodPlaylist, "playlist.m3u8", "", ""); - SimpleHlsNotifier notifier(kVodPlaylist, kTestTimeShiftBufferDepth, - kTestPrefix, kEmptyKeyUri, kAnyOutputDir, - kMasterPlaylistName); + SimpleHlsNotifier notifier(hls_params_); const uint32_t stream_id = SetupStream(kSampleAesProtectionScheme, mock_media_playlist, ¬ifier); @@ -537,9 +530,7 @@ TEST_F(SimpleHlsNotifierTest, NotifyEncryptionUpdateWidevineNoKeyidsInPssh) { // Pointer released by SimpleHlsNotifier. MockMediaPlaylist* mock_media_playlist = new MockMediaPlaylist(kVodPlaylist, "playlist.m3u8", "", ""); - SimpleHlsNotifier notifier(kVodPlaylist, kTestTimeShiftBufferDepth, - kTestPrefix, kEmptyKeyUri, kAnyOutputDir, - kMasterPlaylistName); + SimpleHlsNotifier notifier(hls_params_); const uint32_t stream_id = SetupStream(kSampleAesProtectionScheme, mock_media_playlist, ¬ifier); @@ -596,9 +587,7 @@ TEST_F(SimpleHlsNotifierTest, NotifyEncryptionUpdateIdentityKey) { // Pointer released by SimpleHlsNotifier. MockMediaPlaylist* mock_media_playlist = new MockMediaPlaylist(kVodPlaylist, "playlist.m3u8", "", ""); - SimpleHlsNotifier notifier(kVodPlaylist, kTestTimeShiftBufferDepth, - kTestPrefix, kEmptyKeyUri, kAnyOutputDir, - kMasterPlaylistName); + SimpleHlsNotifier notifier(hls_params_); const uint32_t stream_id = SetupStream(kSampleAesProtectionScheme, mock_media_playlist, ¬ifier); @@ -626,9 +615,7 @@ TEST_F(SimpleHlsNotifierTest, WidevineMultipleKeyIdsNoContentIdInPssh) { // Pointer released by SimpleHlsNotifier. MockMediaPlaylist* mock_media_playlist = new MockMediaPlaylist(kVodPlaylist, "playlist.m3u8", "", ""); - SimpleHlsNotifier notifier(kVodPlaylist, kTestTimeShiftBufferDepth, - kTestPrefix, kEmptyKeyUri, kAnyOutputDir, - kMasterPlaylistName); + SimpleHlsNotifier notifier(hls_params_); uint32_t stream_id = SetupStream(kSampleAesProtectionScheme, mock_media_playlist, ¬ifier); @@ -704,9 +691,8 @@ TEST_F(SimpleHlsNotifierTest, EncryptionScheme) { // Pointer released by SimpleHlsNotifier. MockMediaPlaylist* mock_media_playlist = new MockMediaPlaylist(kVodPlaylist, "playlist.m3u8", "", ""); - SimpleHlsNotifier notifier(kVodPlaylist, kTestTimeShiftBufferDepth, - kTestPrefix, kIdentityKeyUri, kAnyOutputDir, - kMasterPlaylistName); + hls_params_.key_uri = kIdentityKeyUri; + SimpleHlsNotifier notifier(hls_params_); const uint32_t stream_id = SetupStream(kCencProtectionScheme, mock_media_playlist, ¬ifier); @@ -729,9 +715,9 @@ TEST_F(SimpleHlsNotifierTest, NotifyEncryptionUpdateFairplay) { // Pointer released by SimpleHlsNotifier. MockMediaPlaylist* mock_media_playlist = new MockMediaPlaylist(kLivePlaylist, "playlist.m3u8", "", ""); - SimpleHlsNotifier notifier(kLivePlaylist, kTestTimeShiftBufferDepth, - kTestPrefix, kFairplayKeyUri, kAnyOutputDir, - kMasterPlaylistName); + hls_params_.playlist_type = kLivePlaylist; + hls_params_.key_uri = kFairplayKeyUri; + SimpleHlsNotifier notifier(hls_params_); const uint32_t stream_id = SetupStream(kSampleAesProtectionScheme, mock_media_playlist, ¬ifier); const std::vector key_id(16, 0x12); @@ -752,9 +738,7 @@ TEST_F(SimpleHlsNotifierTest, WidevineCencEncryptionScheme) { // Pointer released by SimpleHlsNotifier. MockMediaPlaylist* mock_media_playlist = new MockMediaPlaylist(kVodPlaylist, "playlist.m3u8", "", ""); - SimpleHlsNotifier notifier(kVodPlaylist, kTestTimeShiftBufferDepth, - kTestPrefix, kEmptyKeyUri, kAnyOutputDir, - kMasterPlaylistName); + SimpleHlsNotifier notifier(hls_params_); const uint32_t stream_id = SetupStream(kCencProtectionScheme, mock_media_playlist, ¬ifier); @@ -801,9 +785,7 @@ TEST_F(SimpleHlsNotifierTest, WidevineNotifyEncryptionUpdateEmptyIv) { // Pointer released by SimpleHlsNotifier. MockMediaPlaylist* mock_media_playlist = new MockMediaPlaylist(kVodPlaylist, "playlist.m3u8", "", ""); - SimpleHlsNotifier notifier(kVodPlaylist, kTestTimeShiftBufferDepth, - kTestPrefix, kEmptyKeyUri, kAnyOutputDir, - kMasterPlaylistName); + SimpleHlsNotifier notifier(hls_params_); const uint32_t stream_id = SetupStream(kSampleAesProtectionScheme, mock_media_playlist, ¬ifier); @@ -869,9 +851,7 @@ TEST_F(SimpleHlsNotifierTest, NotifyEncryptionUpdateWithoutStreamsRegistered) { std::vector iv; std::vector pssh_data; std::vector key_id; - SimpleHlsNotifier notifier(kVodPlaylist, kTestTimeShiftBufferDepth, - kTestPrefix, kEmptyKeyUri, kAnyOutputDir, - kMasterPlaylistName); + SimpleHlsNotifier notifier(hls_params_); EXPECT_TRUE(notifier.Init()); EXPECT_FALSE( notifier.NotifyEncryptionUpdate(1238u, key_id, system_id, iv, pssh_data)); @@ -881,9 +861,7 @@ TEST_F(SimpleHlsNotifierTest, NotifyCueEvent) { // Pointer released by SimpleHlsNotifier. MockMediaPlaylist* mock_media_playlist = new MockMediaPlaylist(kVodPlaylist, "playlist.m3u8", "", ""); - SimpleHlsNotifier notifier(kVodPlaylist, kTestTimeShiftBufferDepth, - kTestPrefix, kIdentityKeyUri, kAnyOutputDir, - kMasterPlaylistName); + SimpleHlsNotifier notifier(hls_params_); const uint32_t stream_id = SetupStream(kCencProtectionScheme, mock_media_playlist, ¬ifier); @@ -945,8 +923,8 @@ TEST_P(LiveOrEventSimpleHlsNotifierTest, NotifyNewSegment) { .AsUTF8Unsafe()))) .WillOnce(Return(true)); - SimpleHlsNotifier notifier(GetParam(), kTestTimeShiftBufferDepth, kTestPrefix, - kEmptyKeyUri, kAnyOutputDir, kMasterPlaylistName); + hls_params_.playlist_type = GetParam(); + SimpleHlsNotifier notifier(hls_params_); InjectMasterPlaylist(std::move(mock_master_playlist), ¬ifier); InjectMediaPlaylistFactory(std::move(factory), ¬ifier); EXPECT_TRUE(notifier.Init()); @@ -990,8 +968,8 @@ TEST_P(LiveOrEventSimpleHlsNotifierTest, NotifyNewSegmentsWithMultipleStreams) { *mock_master_playlist, AddMediaPlaylist(static_cast(mock_media_playlist2))); - SimpleHlsNotifier notifier(GetParam(), kTestTimeShiftBufferDepth, kTestPrefix, - kEmptyKeyUri, kAnyOutputDir, kMasterPlaylistName); + hls_params_.playlist_type = GetParam(); + SimpleHlsNotifier notifier(hls_params_); MockMasterPlaylist* mock_master_playlist_ptr = mock_master_playlist.get(); InjectMasterPlaylist(std::move(mock_master_playlist), ¬ifier); InjectMediaPlaylistFactory(std::move(factory), ¬ifier); diff --git a/packager/hls/public/hls_params.h b/packager/hls/public/hls_params.h index 3b0adcaae9..fa21786cef 100644 --- a/packager/hls/public/hls_params.h +++ b/packager/hls/public/hls_params.h @@ -35,6 +35,10 @@ struct HlsParams { /// key formats. Ignored if the playlist is not encrypted or not using the /// above key formats. std::string key_uri; + /// The renditions tagged with this language will have 'DEFAULT' set to 'YES' + /// in 'EXT-X-MEDIA' tag. This allows the player to choose the correct default + /// language for the content. + std::string default_language; }; } // namespace shaka diff --git a/packager/packager.cc b/packager/packager.cc index aacbf7b4b4..06d03cd8f6 100644 --- a/packager/packager.cc +++ b/packager/packager.cc @@ -815,15 +815,7 @@ Status Packager::Initialize( } if (!hls_params.master_playlist_output.empty()) { - base::FilePath master_playlist_path( - base::FilePath::FromUTF8Unsafe(hls_params.master_playlist_output)); - base::FilePath master_playlist_name = master_playlist_path.BaseName(); - - internal->hls_notifier.reset(new hls::SimpleHlsNotifier( - hls_params.playlist_type, hls_params.time_shift_buffer_depth, - hls_params.base_url, hls_params.key_uri, - master_playlist_path.DirName().AsEndingWithSeparator().AsUTF8Unsafe(), - master_playlist_name.AsUTF8Unsafe())); + internal->hls_notifier.reset(new hls::SimpleHlsNotifier(hls_params)); } std::vector streams_for_jobs;