Add support for --default_text_language
It allows users to override the default language for text tracks. If not specified, --default_language applies to both audio and text tracks. Issue #430. Change-Id: I86a9baba2072be27b6661fa7b65a8bc8b6adb3cc
This commit is contained in:
parent
273ab09f05
commit
4b97a6d8a2
|
@ -64,6 +64,14 @@ DASH options
|
||||||
<Role ... value=\"main\" /> in the manifest. This allows the player to
|
<Role ... value=\"main\" /> in the manifest. This allows the player to
|
||||||
choose the correct default language for the content.
|
choose the correct default language for the content.
|
||||||
|
|
||||||
|
This applies to both audio and text tracks. The default language for text
|
||||||
|
tracks can be overriden by 'default_text_language'.
|
||||||
|
|
||||||
|
--default_text_language <text_language>
|
||||||
|
|
||||||
|
Same as above, but this applies to text tracks only, and overrides the
|
||||||
|
default language for text tracks.
|
||||||
|
|
||||||
--allow_approximate_segment_timeline
|
--allow_approximate_segment_timeline
|
||||||
|
|
||||||
For live profile only.
|
For live profile only.
|
||||||
|
|
|
@ -43,3 +43,11 @@ HLS options
|
||||||
The first audio/text rendition in a group tagged with this language will
|
The first audio/text rendition in a group tagged with this language will
|
||||||
have 'DEFAULT' attribute set to 'YES'. This allows the player to choose the
|
have 'DEFAULT' attribute set to 'YES'. This allows the player to choose the
|
||||||
correct default language for the content.
|
correct default language for the content.
|
||||||
|
|
||||||
|
This applies to both audio and text tracks. The default language for text
|
||||||
|
tracks can be overriden by 'default_text_language'.
|
||||||
|
|
||||||
|
--default_text_language <text_language>
|
||||||
|
|
||||||
|
Same as above, but this applies to text tracks only, and overrides the
|
||||||
|
default language for text tracks.
|
||||||
|
|
|
@ -25,4 +25,11 @@ DEFINE_string(default_language,
|
||||||
"have <Role ... value=\"main\" /> in the manifest; For HLS, the "
|
"have <Role ... value=\"main\" /> in the manifest; For HLS, the "
|
||||||
"first audio/text rendition in a group tagged with this language "
|
"first audio/text rendition in a group tagged with this language "
|
||||||
"will have 'DEFAULT' attribute set to 'YES'. This allows the "
|
"will have 'DEFAULT' attribute set to 'YES'. This allows the "
|
||||||
"player to choose the correct default language for the content.");
|
"player to choose the correct default language for the content."
|
||||||
|
"This applies to both audio and text tracks. The default "
|
||||||
|
"language for text tracks can be overriden by "
|
||||||
|
"'--default_text_language'.");
|
||||||
|
DEFINE_string(default_text_language,
|
||||||
|
"",
|
||||||
|
"Same as above, but this applies to text tracks only, and "
|
||||||
|
"overrides the default language for text tracks.");
|
||||||
|
|
|
@ -14,5 +14,6 @@
|
||||||
DECLARE_double(time_shift_buffer_depth);
|
DECLARE_double(time_shift_buffer_depth);
|
||||||
DECLARE_uint64(preserved_segments_outside_live_window);
|
DECLARE_uint64(preserved_segments_outside_live_window);
|
||||||
DECLARE_string(default_language);
|
DECLARE_string(default_language);
|
||||||
|
DECLARE_string(default_text_language);
|
||||||
|
|
||||||
#endif // PACKAGER_APP_MANIFEST_FLAGS_H_
|
#endif // PACKAGER_APP_MANIFEST_FLAGS_H_
|
||||||
|
|
|
@ -443,6 +443,7 @@ base::Optional<PackagingParams> GetPackagingParams() {
|
||||||
}
|
}
|
||||||
|
|
||||||
mpd_params.default_language = FLAGS_default_language;
|
mpd_params.default_language = FLAGS_default_language;
|
||||||
|
mpd_params.default_text_language = FLAGS_default_text_language;
|
||||||
mpd_params.generate_static_live_mpd = FLAGS_generate_static_mpd;
|
mpd_params.generate_static_live_mpd = FLAGS_generate_static_mpd;
|
||||||
mpd_params.generate_dash_if_iop_compliant_mpd =
|
mpd_params.generate_dash_if_iop_compliant_mpd =
|
||||||
FLAGS_generate_dash_if_iop_compliant_mpd;
|
FLAGS_generate_dash_if_iop_compliant_mpd;
|
||||||
|
@ -460,6 +461,7 @@ base::Optional<PackagingParams> GetPackagingParams() {
|
||||||
hls_params.preserved_segments_outside_live_window =
|
hls_params.preserved_segments_outside_live_window =
|
||||||
FLAGS_preserved_segments_outside_live_window;
|
FLAGS_preserved_segments_outside_live_window;
|
||||||
hls_params.default_language = FLAGS_default_language;
|
hls_params.default_language = FLAGS_default_language;
|
||||||
|
hls_params.default_text_language = FLAGS_default_text_language;
|
||||||
|
|
||||||
TestParams& test_params = packaging_params.test_params;
|
TestParams& test_params = packaging_params.test_params;
|
||||||
test_params.dump_stream_info = FLAGS_dump_stream_info;
|
test_params.dump_stream_info = FLAGS_dump_stream_info;
|
||||||
|
|
|
@ -343,7 +343,8 @@ void BuildMediaTags(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void AppendPlaylists(const std::string& default_language,
|
void AppendPlaylists(const std::string& default_audio_language,
|
||||||
|
const std::string& default_text_language,
|
||||||
const std::string& base_url,
|
const std::string& base_url,
|
||||||
const std::list<MediaPlaylist*>& playlists,
|
const std::list<MediaPlaylist*>& playlists,
|
||||||
std::string* content) {
|
std::string* content) {
|
||||||
|
@ -374,12 +375,13 @@ void AppendPlaylists(const std::string& default_language,
|
||||||
|
|
||||||
if (!audio_playlist_groups.empty()) {
|
if (!audio_playlist_groups.empty()) {
|
||||||
content->append("\n");
|
content->append("\n");
|
||||||
BuildMediaTags(audio_playlist_groups, default_language, base_url, content);
|
BuildMediaTags(audio_playlist_groups, default_audio_language, base_url,
|
||||||
|
content);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!subtitle_playlist_groups.empty()) {
|
if (!subtitle_playlist_groups.empty()) {
|
||||||
content->append("\n");
|
content->append("\n");
|
||||||
BuildMediaTags(subtitle_playlist_groups, default_language, base_url,
|
BuildMediaTags(subtitle_playlist_groups, default_text_language, base_url,
|
||||||
content);
|
content);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -406,8 +408,12 @@ void AppendPlaylists(const std::string& default_language,
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
MasterPlaylist::MasterPlaylist(const std::string& file_name,
|
MasterPlaylist::MasterPlaylist(const std::string& file_name,
|
||||||
const std::string& default_language)
|
const std::string& default_audio_language,
|
||||||
: file_name_(file_name), default_language_(default_language) {}
|
const std::string& default_text_language)
|
||||||
|
: file_name_(file_name),
|
||||||
|
default_audio_language_(default_audio_language),
|
||||||
|
default_text_language_(default_text_language) {}
|
||||||
|
|
||||||
MasterPlaylist::~MasterPlaylist() {}
|
MasterPlaylist::~MasterPlaylist() {}
|
||||||
|
|
||||||
bool MasterPlaylist::WriteMasterPlaylist(
|
bool MasterPlaylist::WriteMasterPlaylist(
|
||||||
|
@ -416,7 +422,8 @@ bool MasterPlaylist::WriteMasterPlaylist(
|
||||||
const std::list<MediaPlaylist*>& playlists) {
|
const std::list<MediaPlaylist*>& playlists) {
|
||||||
std::string content = "#EXTM3U\n";
|
std::string content = "#EXTM3U\n";
|
||||||
AppendVersionString(&content);
|
AppendVersionString(&content);
|
||||||
AppendPlaylists(default_language_, base_url, playlists, &content);
|
AppendPlaylists(default_audio_language_, default_text_language_, base_url,
|
||||||
|
playlists, &content);
|
||||||
|
|
||||||
// Skip if the playlist is already written.
|
// Skip if the playlist is already written.
|
||||||
if (content == written_playlist_)
|
if (content == written_playlist_)
|
||||||
|
|
|
@ -20,10 +20,13 @@ class MediaPlaylist;
|
||||||
class MasterPlaylist {
|
class MasterPlaylist {
|
||||||
public:
|
public:
|
||||||
/// @param file_name is the file name of the master playlist.
|
/// @param file_name is the file name of the master playlist.
|
||||||
/// @param default_language determines the rendition that should be tagged
|
/// @param default_audio_language determines the audio rendition that should
|
||||||
/// with 'DEFAULT'.
|
/// be tagged with 'DEFAULT'.
|
||||||
|
/// @param default_text_language determines the text rendition that should be
|
||||||
|
/// tagged with 'DEFAULT'.
|
||||||
MasterPlaylist(const std::string& file_name,
|
MasterPlaylist(const std::string& file_name,
|
||||||
const std::string& default_language);
|
const std::string& default_audio_language,
|
||||||
|
const std::string& default_text_language);
|
||||||
virtual ~MasterPlaylist();
|
virtual ~MasterPlaylist();
|
||||||
|
|
||||||
/// Writes Master Playlist to output_dir + <name of playlist>.
|
/// Writes Master Playlist to output_dir + <name of playlist>.
|
||||||
|
@ -45,7 +48,8 @@ class MasterPlaylist {
|
||||||
|
|
||||||
std::string written_playlist_;
|
std::string written_playlist_;
|
||||||
const std::string file_name_;
|
const std::string file_name_;
|
||||||
const std::string default_language_;
|
const std::string default_audio_language_;
|
||||||
|
const std::string default_text_language_;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace hls
|
} // namespace hls
|
||||||
|
|
|
@ -28,7 +28,8 @@ using ::testing::StrEq;
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
const char kDefaultMasterPlaylistName[] = "playlist.m3u8";
|
const char kDefaultMasterPlaylistName[] = "playlist.m3u8";
|
||||||
const char kDefaultLanguage[] = "en";
|
const char kDefaultAudioLanguage[] = "en";
|
||||||
|
const char kDefaultTextLanguage[] = "fr";
|
||||||
const uint32_t kWidth = 800;
|
const uint32_t kWidth = 800;
|
||||||
const uint32_t kHeight = 600;
|
const uint32_t kHeight = 600;
|
||||||
|
|
||||||
|
@ -123,7 +124,9 @@ std::unique_ptr<MockMediaPlaylist> CreateTextPlaylist(
|
||||||
class MasterPlaylistTest : public ::testing::Test {
|
class MasterPlaylistTest : public ::testing::Test {
|
||||||
protected:
|
protected:
|
||||||
MasterPlaylistTest()
|
MasterPlaylistTest()
|
||||||
: master_playlist_(kDefaultMasterPlaylistName, kDefaultLanguage),
|
: master_playlist_(kDefaultMasterPlaylistName,
|
||||||
|
kDefaultAudioLanguage,
|
||||||
|
kDefaultTextLanguage),
|
||||||
test_output_dir_("memory://test_dir"),
|
test_output_dir_("memory://test_dir"),
|
||||||
master_playlist_path_(
|
master_playlist_path_(
|
||||||
FilePath::FromUTF8Unsafe(test_output_dir_)
|
FilePath::FromUTF8Unsafe(test_output_dir_)
|
||||||
|
@ -382,10 +385,11 @@ TEST_F(MasterPlaylistTest, WriteMasterPlaylistVideosAndTexts) {
|
||||||
"test\n"
|
"test\n"
|
||||||
"\n"
|
"\n"
|
||||||
"#EXT-X-MEDIA:TYPE=SUBTITLES,URI=\"http://playlists.org/eng.m3u8\","
|
"#EXT-X-MEDIA:TYPE=SUBTITLES,URI=\"http://playlists.org/eng.m3u8\","
|
||||||
"GROUP-ID=\"textgroup\",LANGUAGE=\"en\",NAME=\"english\",DEFAULT=YES,"
|
"GROUP-ID=\"textgroup\",LANGUAGE=\"en\",NAME=\"english\","
|
||||||
"AUTOSELECT=YES\n"
|
"AUTOSELECT=YES\n"
|
||||||
"#EXT-X-MEDIA:TYPE=SUBTITLES,URI=\"http://playlists.org/fr.m3u8\","
|
"#EXT-X-MEDIA:TYPE=SUBTITLES,URI=\"http://playlists.org/fr.m3u8\","
|
||||||
"GROUP-ID=\"textgroup\",LANGUAGE=\"fr\",NAME=\"french\",AUTOSELECT=YES\n"
|
"GROUP-ID=\"textgroup\",LANGUAGE=\"fr\",NAME=\"french\",DEFAULT=YES,"
|
||||||
|
"AUTOSELECT=YES\n"
|
||||||
"\n"
|
"\n"
|
||||||
"#EXT-X-STREAM-INF:BANDWIDTH=300000,AVERAGE-BANDWIDTH=200000,"
|
"#EXT-X-STREAM-INF:BANDWIDTH=300000,AVERAGE-BANDWIDTH=200000,"
|
||||||
"CODECS=\"sdvideocodec,textcodec\",RESOLUTION=800x600,"
|
"CODECS=\"sdvideocodec,textcodec\",RESOLUTION=800x600,"
|
||||||
|
@ -423,8 +427,8 @@ TEST_F(MasterPlaylistTest, WriteMasterPlaylistVideoAndTextWithCharacteritics) {
|
||||||
"test\n"
|
"test\n"
|
||||||
"\n"
|
"\n"
|
||||||
"#EXT-X-MEDIA:TYPE=SUBTITLES,URI=\"http://playlists.org/eng.m3u8\","
|
"#EXT-X-MEDIA:TYPE=SUBTITLES,URI=\"http://playlists.org/eng.m3u8\","
|
||||||
"GROUP-ID=\"textgroup\",LANGUAGE=\"en\",NAME=\"english\",DEFAULT=YES,"
|
"GROUP-ID=\"textgroup\",LANGUAGE=\"en\",NAME=\"english\",AUTOSELECT=YES,"
|
||||||
"AUTOSELECT=YES,CHARACTERISTICS=\""
|
"CHARACTERISTICS=\""
|
||||||
"public.accessibility.transcribes-spoken-dialog,public.easy-to-read\"\n"
|
"public.accessibility.transcribes-spoken-dialog,public.easy-to-read\"\n"
|
||||||
"\n"
|
"\n"
|
||||||
"#EXT-X-STREAM-INF:BANDWIDTH=300000,AVERAGE-BANDWIDTH=200000,"
|
"#EXT-X-STREAM-INF:BANDWIDTH=300000,AVERAGE-BANDWIDTH=200000,"
|
||||||
|
@ -463,10 +467,10 @@ TEST_F(MasterPlaylistTest, WriteMasterPlaylistVideoAndTextGroups) {
|
||||||
"\n"
|
"\n"
|
||||||
"#EXT-X-MEDIA:TYPE=SUBTITLES,URI=\"http://playlists.org/eng.m3u8\","
|
"#EXT-X-MEDIA:TYPE=SUBTITLES,URI=\"http://playlists.org/eng.m3u8\","
|
||||||
"GROUP-ID=\"en-text-group\",LANGUAGE=\"en\",NAME=\"english\","
|
"GROUP-ID=\"en-text-group\",LANGUAGE=\"en\",NAME=\"english\","
|
||||||
"DEFAULT=YES,AUTOSELECT=YES\n"
|
"AUTOSELECT=YES\n"
|
||||||
"#EXT-X-MEDIA:TYPE=SUBTITLES,URI=\"http://playlists.org/fr.m3u8\","
|
"#EXT-X-MEDIA:TYPE=SUBTITLES,URI=\"http://playlists.org/fr.m3u8\","
|
||||||
"GROUP-ID=\"fr-text-group\",LANGUAGE=\"fr\",NAME=\"french\","
|
"GROUP-ID=\"fr-text-group\",LANGUAGE=\"fr\",NAME=\"french\","
|
||||||
"AUTOSELECT=YES\n"
|
"DEFAULT=YES,AUTOSELECT=YES\n"
|
||||||
"\n"
|
"\n"
|
||||||
"#EXT-X-STREAM-INF:BANDWIDTH=300000,AVERAGE-BANDWIDTH=200000,"
|
"#EXT-X-STREAM-INF:BANDWIDTH=300000,AVERAGE-BANDWIDTH=200000,"
|
||||||
"CODECS=\"sdvideocodec,textcodec\",RESOLUTION=800x600,"
|
"CODECS=\"sdvideocodec,textcodec\",RESOLUTION=800x600,"
|
||||||
|
@ -511,7 +515,7 @@ TEST_F(MasterPlaylistTest, WriteMasterPlaylistVideoAndAudioAndText) {
|
||||||
"DEFAULT=YES,AUTOSELECT=YES,CHANNELS=\"2\"\n"
|
"DEFAULT=YES,AUTOSELECT=YES,CHANNELS=\"2\"\n"
|
||||||
"\n"
|
"\n"
|
||||||
"#EXT-X-MEDIA:TYPE=SUBTITLES,URI=\"http://playlists.org/eng.m3u8\","
|
"#EXT-X-MEDIA:TYPE=SUBTITLES,URI=\"http://playlists.org/eng.m3u8\","
|
||||||
"GROUP-ID=\"textgroup\",LANGUAGE=\"en\",NAME=\"english\",DEFAULT=YES,"
|
"GROUP-ID=\"textgroup\",LANGUAGE=\"en\",NAME=\"english\","
|
||||||
"AUTOSELECT=YES\n"
|
"AUTOSELECT=YES\n"
|
||||||
"\n"
|
"\n"
|
||||||
"#EXT-X-STREAM-INF:BANDWIDTH=350000,AVERAGE-BANDWIDTH=230000,"
|
"#EXT-X-STREAM-INF:BANDWIDTH=350000,AVERAGE-BANDWIDTH=230000,"
|
||||||
|
@ -537,14 +541,14 @@ TEST_F(MasterPlaylistTest, WriteMasterPlaylistMixedPlaylistsDifferentGroups) {
|
||||||
"audiocodec", "en", kAudioChannels, kAudioMaxBitrate,
|
"audiocodec", "en", kAudioChannels, kAudioMaxBitrate,
|
||||||
kAudioAvgBitrate),
|
kAudioAvgBitrate),
|
||||||
CreateAudioPlaylist("audio-2.m3u8", "audio 2", "audio-group-2",
|
CreateAudioPlaylist("audio-2.m3u8", "audio 2", "audio-group-2",
|
||||||
"audiocodec", "en", kAudioChannels, kAudioMaxBitrate,
|
"audiocodec", "fr", kAudioChannels, kAudioMaxBitrate,
|
||||||
kAudioAvgBitrate),
|
kAudioAvgBitrate),
|
||||||
|
|
||||||
// SUBTITLES
|
// SUBTITLES
|
||||||
CreateTextPlaylist("text-1.m3u8", "text 1", "text-group-1", "textcodec",
|
CreateTextPlaylist("text-1.m3u8", "text 1", "text-group-1", "textcodec",
|
||||||
"en"),
|
"en"),
|
||||||
CreateTextPlaylist("text-2.m3u8", "text 2", "text-group-2", "textcodec",
|
CreateTextPlaylist("text-2.m3u8", "text 2", "text-group-2", "textcodec",
|
||||||
"en"),
|
"fr"),
|
||||||
|
|
||||||
// VIDEO
|
// VIDEO
|
||||||
CreateVideoPlaylist("video-1.m3u8", "sdvideocodec", kVideoMaxBitrate,
|
CreateVideoPlaylist("video-1.m3u8", "sdvideocodec", kVideoMaxBitrate,
|
||||||
|
@ -581,14 +585,14 @@ TEST_F(MasterPlaylistTest, WriteMasterPlaylistMixedPlaylistsDifferentGroups) {
|
||||||
"GROUP-ID=\"audio-group-1\",LANGUAGE=\"en\",NAME=\"audio 1\","
|
"GROUP-ID=\"audio-group-1\",LANGUAGE=\"en\",NAME=\"audio 1\","
|
||||||
"DEFAULT=YES,AUTOSELECT=YES,CHANNELS=\"2\"\n"
|
"DEFAULT=YES,AUTOSELECT=YES,CHANNELS=\"2\"\n"
|
||||||
"#EXT-X-MEDIA:TYPE=AUDIO,URI=\"http://playlists.org/audio-2.m3u8\","
|
"#EXT-X-MEDIA:TYPE=AUDIO,URI=\"http://playlists.org/audio-2.m3u8\","
|
||||||
"GROUP-ID=\"audio-group-2\",LANGUAGE=\"en\",NAME=\"audio 2\","
|
"GROUP-ID=\"audio-group-2\",LANGUAGE=\"fr\",NAME=\"audio 2\","
|
||||||
"DEFAULT=YES,AUTOSELECT=YES,CHANNELS=\"2\"\n"
|
"AUTOSELECT=YES,CHANNELS=\"2\"\n"
|
||||||
"\n"
|
"\n"
|
||||||
"#EXT-X-MEDIA:TYPE=SUBTITLES,URI=\"http://playlists.org/text-1.m3u8\","
|
"#EXT-X-MEDIA:TYPE=SUBTITLES,URI=\"http://playlists.org/text-1.m3u8\","
|
||||||
"GROUP-ID=\"text-group-1\",LANGUAGE=\"en\",NAME=\"text 1\","
|
"GROUP-ID=\"text-group-1\",LANGUAGE=\"en\",NAME=\"text 1\","
|
||||||
"DEFAULT=YES,AUTOSELECT=YES\n"
|
"AUTOSELECT=YES\n"
|
||||||
"#EXT-X-MEDIA:TYPE=SUBTITLES,URI=\"http://playlists.org/text-2.m3u8\","
|
"#EXT-X-MEDIA:TYPE=SUBTITLES,URI=\"http://playlists.org/text-2.m3u8\","
|
||||||
"GROUP-ID=\"text-group-2\",LANGUAGE=\"en\",NAME=\"text 2\","
|
"GROUP-ID=\"text-group-2\",LANGUAGE=\"fr\",NAME=\"text 2\","
|
||||||
"DEFAULT=YES,AUTOSELECT=YES\n"
|
"DEFAULT=YES,AUTOSELECT=YES\n"
|
||||||
"\n"
|
"\n"
|
||||||
"#EXT-X-STREAM-INF:BANDWIDTH=350000,AVERAGE-BANDWIDTH=130000,"
|
"#EXT-X-STREAM-INF:BANDWIDTH=350000,AVERAGE-BANDWIDTH=130000,"
|
||||||
|
|
|
@ -282,9 +282,14 @@ SimpleHlsNotifier::SimpleHlsNotifier(const HlsParams& hls_params)
|
||||||
const base::FilePath master_playlist_path(
|
const base::FilePath master_playlist_path(
|
||||||
base::FilePath::FromUTF8Unsafe(hls_params.master_playlist_output));
|
base::FilePath::FromUTF8Unsafe(hls_params.master_playlist_output));
|
||||||
output_dir_ = master_playlist_path.DirName().AsUTF8Unsafe();
|
output_dir_ = master_playlist_path.DirName().AsUTF8Unsafe();
|
||||||
|
const std::string& default_audio_langauge = hls_params.default_language;
|
||||||
|
const std::string& default_text_language =
|
||||||
|
hls_params.default_text_language.empty()
|
||||||
|
? hls_params.default_language
|
||||||
|
: hls_params.default_text_language;
|
||||||
master_playlist_.reset(
|
master_playlist_.reset(
|
||||||
new MasterPlaylist(master_playlist_path.BaseName().AsUTF8Unsafe(),
|
new MasterPlaylist(master_playlist_path.BaseName().AsUTF8Unsafe(),
|
||||||
hls_params.default_language));
|
default_audio_langauge, default_text_language));
|
||||||
}
|
}
|
||||||
|
|
||||||
SimpleHlsNotifier::~SimpleHlsNotifier() {}
|
SimpleHlsNotifier::~SimpleHlsNotifier() {}
|
||||||
|
|
|
@ -35,7 +35,8 @@ using ::testing::WithParamInterface;
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
const char kMasterPlaylistName[] = "master.m3u8";
|
const char kMasterPlaylistName[] = "master.m3u8";
|
||||||
const char kDefaultLanguage[] = "en";
|
const char kDefaultAudioLanguage[] = "en";
|
||||||
|
const char kDefaultTextLanguage[] = "fr";
|
||||||
const char kEmptyKeyUri[] = "";
|
const char kEmptyKeyUri[] = "";
|
||||||
const char kFairPlayKeyUri[] = "skd://www.license.com/getkey?key_id=testing";
|
const char kFairPlayKeyUri[] = "skd://www.license.com/getkey?key_id=testing";
|
||||||
const char kIdentityKeyUri[] = "https://www.license.com/getkey?key_id=testing";
|
const char kIdentityKeyUri[] = "https://www.license.com/getkey?key_id=testing";
|
||||||
|
@ -45,7 +46,9 @@ const HlsPlaylistType kLivePlaylist = HlsPlaylistType::kLive;
|
||||||
class MockMasterPlaylist : public MasterPlaylist {
|
class MockMasterPlaylist : public MasterPlaylist {
|
||||||
public:
|
public:
|
||||||
MockMasterPlaylist()
|
MockMasterPlaylist()
|
||||||
: MasterPlaylist(kMasterPlaylistName, kDefaultLanguage) {}
|
: MasterPlaylist(kMasterPlaylistName,
|
||||||
|
kDefaultAudioLanguage,
|
||||||
|
kDefaultTextLanguage) {}
|
||||||
|
|
||||||
MOCK_METHOD3(WriteMasterPlaylist,
|
MOCK_METHOD3(WriteMasterPlaylist,
|
||||||
bool(const std::string& prefix,
|
bool(const std::string& prefix,
|
||||||
|
|
|
@ -45,7 +45,12 @@ struct HlsParams {
|
||||||
/// The renditions tagged with this language will have 'DEFAULT' set to 'YES'
|
/// 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
|
/// in 'EXT-X-MEDIA' tag. This allows the player to choose the correct default
|
||||||
/// language for the content.
|
/// language for the content.
|
||||||
|
/// This applies to both audio and text tracks. The default language for text
|
||||||
|
/// tracks can be overriden by 'default_text_language'.
|
||||||
std::string default_language;
|
std::string default_language;
|
||||||
|
/// Same as above, but this overrides the default language for text tracks,
|
||||||
|
/// i.e. subtitles or close-captions.
|
||||||
|
std::string default_text_language;
|
||||||
/// This is the target segment duration requested by the user. The actual
|
/// This is the target segment duration requested by the user. The actual
|
||||||
/// segment duration may be different to the target segment duration.
|
/// segment duration may be different to the target segment duration.
|
||||||
/// This parameter is included here to for bandwidth estimator to exclude the
|
/// This parameter is included here to for bandwidth estimator to exclude the
|
||||||
|
|
|
@ -32,6 +32,16 @@ std::set<std::string> GetUUIDs(
|
||||||
return uuids;
|
return uuids;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const std::string& GetDefaultAudioLanguage(const MpdOptions& mpd_options) {
|
||||||
|
return mpd_options.mpd_params.default_language;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string& GetDefaultTextLanguage(const MpdOptions& mpd_options) {
|
||||||
|
return mpd_options.mpd_params.default_text_language.empty()
|
||||||
|
? mpd_options.mpd_params.default_language
|
||||||
|
: mpd_options.mpd_params.default_text_language;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
Period::Period(uint32_t period_id,
|
Period::Period(uint32_t period_id,
|
||||||
|
@ -149,8 +159,14 @@ bool Period::SetNewAdaptationSetAttributes(
|
||||||
const MediaInfo& media_info,
|
const MediaInfo& media_info,
|
||||||
const std::list<AdaptationSet*>& adaptation_sets,
|
const std::list<AdaptationSet*>& adaptation_sets,
|
||||||
AdaptationSet* new_adaptation_set) {
|
AdaptationSet* new_adaptation_set) {
|
||||||
if (!language.empty() && language == mpd_options_.mpd_params.default_language)
|
if (!language.empty()) {
|
||||||
new_adaptation_set->AddRole(AdaptationSet::kRoleMain);
|
const bool is_main_role =
|
||||||
|
language == (media_info.has_audio_info()
|
||||||
|
? GetDefaultAudioLanguage(mpd_options_)
|
||||||
|
: GetDefaultTextLanguage(mpd_options_));
|
||||||
|
if (is_main_role)
|
||||||
|
new_adaptation_set->AddRole(AdaptationSet::kRoleMain);
|
||||||
|
}
|
||||||
|
|
||||||
if (media_info.has_video_info()) {
|
if (media_info.has_video_info()) {
|
||||||
// Because 'language' is ignored for videos, |adaptation_sets| must have
|
// Because 'language' is ignored for videos, |adaptation_sets| must have
|
||||||
|
|
|
@ -93,15 +93,13 @@ class TestablePeriod : public Period {
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
class PeriodTest : public ::testing::TestWithParam<bool> {
|
class PeriodTest : public ::testing::Test {
|
||||||
public:
|
public:
|
||||||
PeriodTest()
|
PeriodTest()
|
||||||
: testable_period_(mpd_options_),
|
: testable_period_(mpd_options_),
|
||||||
default_adaptation_set_(new StrictMock<MockAdaptationSet>()),
|
default_adaptation_set_(new StrictMock<MockAdaptationSet>()),
|
||||||
default_adaptation_set_ptr_(default_adaptation_set_.get()) {}
|
default_adaptation_set_ptr_(default_adaptation_set_.get()) {}
|
||||||
|
|
||||||
void SetUp() override { content_protection_in_adaptation_set_ = GetParam(); }
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
MpdOptions mpd_options_;
|
MpdOptions mpd_options_;
|
||||||
TestablePeriod testable_period_;
|
TestablePeriod testable_period_;
|
||||||
|
@ -112,7 +110,7 @@ class PeriodTest : public ::testing::TestWithParam<bool> {
|
||||||
StrictMock<MockAdaptationSet>* default_adaptation_set_ptr_;
|
StrictMock<MockAdaptationSet>* default_adaptation_set_ptr_;
|
||||||
};
|
};
|
||||||
|
|
||||||
TEST_P(PeriodTest, GetXml) {
|
TEST_F(PeriodTest, GetXml) {
|
||||||
const char kVideoMediaInfo[] =
|
const char kVideoMediaInfo[] =
|
||||||
"video_info {\n"
|
"video_info {\n"
|
||||||
" codec: 'avc1'\n"
|
" codec: 'avc1'\n"
|
||||||
|
@ -143,7 +141,7 @@ TEST_P(PeriodTest, GetXml) {
|
||||||
XmlNodeEqual(kExpectedXml));
|
XmlNodeEqual(kExpectedXml));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_P(PeriodTest, DynamicMpdGetXml) {
|
TEST_F(PeriodTest, DynamicMpdGetXml) {
|
||||||
const char kVideoMediaInfo[] =
|
const char kVideoMediaInfo[] =
|
||||||
"video_info {\n"
|
"video_info {\n"
|
||||||
" codec: 'avc1'\n"
|
" codec: 'avc1'\n"
|
||||||
|
@ -175,7 +173,7 @@ TEST_P(PeriodTest, DynamicMpdGetXml) {
|
||||||
XmlNodeEqual(kExpectedXml));
|
XmlNodeEqual(kExpectedXml));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_P(PeriodTest, SetDurationAndGetXml) {
|
TEST_F(PeriodTest, SetDurationAndGetXml) {
|
||||||
const char kVideoMediaInfo[] =
|
const char kVideoMediaInfo[] =
|
||||||
"video_info {\n"
|
"video_info {\n"
|
||||||
" codec: 'avc1'\n"
|
" codec: 'avc1'\n"
|
||||||
|
@ -217,7 +215,7 @@ TEST_P(PeriodTest, SetDurationAndGetXml) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify ForceSetSegmentAlignment is called.
|
// Verify ForceSetSegmentAlignment is called.
|
||||||
TEST_P(PeriodTest, Text) {
|
TEST_F(PeriodTest, Text) {
|
||||||
const char kTextMediaInfo[] =
|
const char kTextMediaInfo[] =
|
||||||
"text_info {\n"
|
"text_info {\n"
|
||||||
" codec: 'ttml'\n"
|
" codec: 'ttml'\n"
|
||||||
|
@ -235,7 +233,7 @@ TEST_P(PeriodTest, Text) {
|
||||||
content_protection_in_adaptation_set_));
|
content_protection_in_adaptation_set_));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_P(PeriodTest, TrickPlayWithMatchingAdaptationSet) {
|
TEST_F(PeriodTest, TrickPlayWithMatchingAdaptationSet) {
|
||||||
const char kVideoMediaInfo[] =
|
const char kVideoMediaInfo[] =
|
||||||
"video_info {\n"
|
"video_info {\n"
|
||||||
" codec: 'avc1'\n"
|
" codec: 'avc1'\n"
|
||||||
|
@ -282,7 +280,7 @@ TEST_P(PeriodTest, TrickPlayWithMatchingAdaptationSet) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify no AdaptationSet is returned on trickplay media info.
|
// Verify no AdaptationSet is returned on trickplay media info.
|
||||||
TEST_P(PeriodTest, TrickPlayWithNoMatchingAdaptationSet) {
|
TEST_F(PeriodTest, TrickPlayWithNoMatchingAdaptationSet) {
|
||||||
const char kVideoMediaInfo[] =
|
const char kVideoMediaInfo[] =
|
||||||
"video_info {\n"
|
"video_info {\n"
|
||||||
" codec: 'avc1'\n"
|
" codec: 'avc1'\n"
|
||||||
|
@ -324,11 +322,338 @@ TEST_P(PeriodTest, TrickPlayWithNoMatchingAdaptationSet) {
|
||||||
content_protection_in_adaptation_set_));
|
content_protection_in_adaptation_set_));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Don't put different audio languages or codecs in the same AdaptationSet.
|
||||||
|
TEST_F(PeriodTest, SplitAdaptationSetsByLanguageAndCodec) {
|
||||||
|
const char kAacEnglishAudioContent[] =
|
||||||
|
"audio_info {\n"
|
||||||
|
" codec: 'mp4a.40.2'\n"
|
||||||
|
" sampling_frequency: 44100\n"
|
||||||
|
" time_scale: 1200\n"
|
||||||
|
" num_channels: 2\n"
|
||||||
|
" language: 'eng'\n"
|
||||||
|
"}\n"
|
||||||
|
"reference_time_scale: 50\n"
|
||||||
|
"container_type: CONTAINER_MP4\n"
|
||||||
|
"media_duration_seconds: 10.5\n";
|
||||||
|
const char kAacGermanAudioContent[] =
|
||||||
|
"audio_info {\n"
|
||||||
|
" codec: 'mp4a.40.2'\n"
|
||||||
|
" sampling_frequency: 44100\n"
|
||||||
|
" time_scale: 1200\n"
|
||||||
|
" num_channels: 2\n"
|
||||||
|
" language: 'ger'\n"
|
||||||
|
"}\n"
|
||||||
|
"reference_time_scale: 50\n"
|
||||||
|
"container_type: CONTAINER_MP4\n"
|
||||||
|
"media_duration_seconds: 10.5\n";
|
||||||
|
const char kVorbisGermanAudioContent1[] =
|
||||||
|
"audio_info {\n"
|
||||||
|
" codec: 'vorbis'\n"
|
||||||
|
" sampling_frequency: 44100\n"
|
||||||
|
" time_scale: 1200\n"
|
||||||
|
" num_channels: 2\n"
|
||||||
|
" language: 'ger'\n"
|
||||||
|
"}\n"
|
||||||
|
"reference_time_scale: 50\n"
|
||||||
|
"container_type: CONTAINER_WEBM\n"
|
||||||
|
"media_duration_seconds: 10.5\n";
|
||||||
|
const char kVorbisGermanAudioContent2[] =
|
||||||
|
"audio_info {\n"
|
||||||
|
" codec: 'vorbis'\n"
|
||||||
|
" sampling_frequency: 44100\n"
|
||||||
|
" time_scale: 1200\n"
|
||||||
|
" num_channels: 2\n"
|
||||||
|
" language: 'ger'\n"
|
||||||
|
"}\n"
|
||||||
|
"reference_time_scale: 50\n"
|
||||||
|
"container_type: CONTAINER_WEBM\n"
|
||||||
|
"media_duration_seconds: 10.5\n";
|
||||||
|
|
||||||
|
std::unique_ptr<StrictMock<MockAdaptationSet>> aac_eng_adaptation_set(
|
||||||
|
new StrictMock<MockAdaptationSet>());
|
||||||
|
auto* aac_eng_adaptation_set_ptr = aac_eng_adaptation_set.get();
|
||||||
|
std::unique_ptr<StrictMock<MockAdaptationSet>> aac_ger_adaptation_set(
|
||||||
|
new StrictMock<MockAdaptationSet>());
|
||||||
|
auto* aac_ger_adaptation_set_ptr = aac_ger_adaptation_set.get();
|
||||||
|
std::unique_ptr<StrictMock<MockAdaptationSet>> vorbis_german_adaptation_set(
|
||||||
|
new StrictMock<MockAdaptationSet>());
|
||||||
|
auto* vorbis_german_adaptation_set_ptr = vorbis_german_adaptation_set.get();
|
||||||
|
|
||||||
|
// We expect three AdaptationSets.
|
||||||
|
EXPECT_CALL(testable_period_, NewAdaptationSet(_, _, _))
|
||||||
|
.WillOnce(Return(ByMove(std::move(aac_eng_adaptation_set))))
|
||||||
|
.WillOnce(Return(ByMove(std::move(aac_ger_adaptation_set))))
|
||||||
|
.WillOnce(Return(ByMove(std::move(vorbis_german_adaptation_set))));
|
||||||
|
|
||||||
|
ASSERT_EQ(aac_eng_adaptation_set_ptr,
|
||||||
|
testable_period_.GetOrCreateAdaptationSet(
|
||||||
|
ConvertToMediaInfo(kAacEnglishAudioContent),
|
||||||
|
content_protection_in_adaptation_set_));
|
||||||
|
ASSERT_EQ(aac_ger_adaptation_set_ptr,
|
||||||
|
testable_period_.GetOrCreateAdaptationSet(
|
||||||
|
ConvertToMediaInfo(kAacGermanAudioContent),
|
||||||
|
content_protection_in_adaptation_set_));
|
||||||
|
ASSERT_EQ(vorbis_german_adaptation_set_ptr,
|
||||||
|
testable_period_.GetOrCreateAdaptationSet(
|
||||||
|
ConvertToMediaInfo(kVorbisGermanAudioContent1),
|
||||||
|
content_protection_in_adaptation_set_));
|
||||||
|
// The same AdaptationSet is returned.
|
||||||
|
ASSERT_EQ(vorbis_german_adaptation_set_ptr,
|
||||||
|
testable_period_.GetOrCreateAdaptationSet(
|
||||||
|
ConvertToMediaInfo(kVorbisGermanAudioContent2),
|
||||||
|
content_protection_in_adaptation_set_));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(PeriodTest, GetAdaptationSets) {
|
||||||
|
const char kContent1[] =
|
||||||
|
"audio_info {\n"
|
||||||
|
" codec: 'mp4a.40.2'\n"
|
||||||
|
" sampling_frequency: 44100\n"
|
||||||
|
" time_scale: 1200\n"
|
||||||
|
" num_channels: 2\n"
|
||||||
|
" language: 'eng'\n"
|
||||||
|
"}\n"
|
||||||
|
"reference_time_scale: 50\n"
|
||||||
|
"container_type: CONTAINER_MP4\n"
|
||||||
|
"media_duration_seconds: 10.5\n";
|
||||||
|
const char kContent2[] =
|
||||||
|
"audio_info {\n"
|
||||||
|
" codec: 'mp4a.40.2'\n"
|
||||||
|
" sampling_frequency: 44100\n"
|
||||||
|
" time_scale: 1200\n"
|
||||||
|
" num_channels: 2\n"
|
||||||
|
" language: 'ger'\n"
|
||||||
|
"}\n"
|
||||||
|
"reference_time_scale: 50\n"
|
||||||
|
"container_type: CONTAINER_MP4\n"
|
||||||
|
"media_duration_seconds: 10.5\n";
|
||||||
|
|
||||||
|
std::unique_ptr<StrictMock<MockAdaptationSet>> adaptation_set_1(
|
||||||
|
new StrictMock<MockAdaptationSet>());
|
||||||
|
auto* adaptation_set_1_ptr = adaptation_set_1.get();
|
||||||
|
std::unique_ptr<StrictMock<MockAdaptationSet>> adaptation_set_2(
|
||||||
|
new StrictMock<MockAdaptationSet>());
|
||||||
|
auto* adaptation_set_2_ptr = adaptation_set_2.get();
|
||||||
|
|
||||||
|
EXPECT_CALL(testable_period_, NewAdaptationSet(_, _, _))
|
||||||
|
.WillOnce(Return(ByMove(std::move(adaptation_set_1))))
|
||||||
|
.WillOnce(Return(ByMove(std::move(adaptation_set_2))));
|
||||||
|
|
||||||
|
ASSERT_EQ(adaptation_set_1_ptr, testable_period_.GetOrCreateAdaptationSet(
|
||||||
|
ConvertToMediaInfo(kContent1),
|
||||||
|
content_protection_in_adaptation_set_));
|
||||||
|
EXPECT_THAT(testable_period_.GetAdaptationSets(),
|
||||||
|
ElementsAre(adaptation_set_1_ptr));
|
||||||
|
|
||||||
|
ASSERT_EQ(adaptation_set_2_ptr, testable_period_.GetOrCreateAdaptationSet(
|
||||||
|
ConvertToMediaInfo(kContent2),
|
||||||
|
content_protection_in_adaptation_set_));
|
||||||
|
EXPECT_THAT(testable_period_.GetAdaptationSets(),
|
||||||
|
ElementsAre(adaptation_set_1_ptr, adaptation_set_2_ptr));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(PeriodTest, OrderedByAdaptationSetId) {
|
||||||
|
const char kContent1[] =
|
||||||
|
"audio_info {\n"
|
||||||
|
" codec: 'mp4a.40.2'\n"
|
||||||
|
" sampling_frequency: 44100\n"
|
||||||
|
" time_scale: 1200\n"
|
||||||
|
" num_channels: 2\n"
|
||||||
|
" language: 'eng'\n"
|
||||||
|
"}\n"
|
||||||
|
"reference_time_scale: 50\n"
|
||||||
|
"container_type: CONTAINER_MP4\n"
|
||||||
|
"media_duration_seconds: 10.5\n";
|
||||||
|
const char kContent2[] =
|
||||||
|
"audio_info {\n"
|
||||||
|
" codec: 'mp4a.40.2'\n"
|
||||||
|
" sampling_frequency: 44100\n"
|
||||||
|
" time_scale: 1200\n"
|
||||||
|
" num_channels: 2\n"
|
||||||
|
" language: 'ger'\n"
|
||||||
|
"}\n"
|
||||||
|
"reference_time_scale: 50\n"
|
||||||
|
"container_type: CONTAINER_MP4\n"
|
||||||
|
"media_duration_seconds: 10.5\n";
|
||||||
|
|
||||||
|
std::unique_ptr<StrictMock<MockAdaptationSet>> adaptation_set_1(
|
||||||
|
new StrictMock<MockAdaptationSet>());
|
||||||
|
auto* adaptation_set_1_ptr = adaptation_set_1.get();
|
||||||
|
std::unique_ptr<StrictMock<MockAdaptationSet>> adaptation_set_2(
|
||||||
|
new StrictMock<MockAdaptationSet>());
|
||||||
|
auto* adaptation_set_2_ptr = adaptation_set_2.get();
|
||||||
|
|
||||||
|
EXPECT_CALL(testable_period_, NewAdaptationSet(_, _, _))
|
||||||
|
.WillOnce(Return(ByMove(std::move(adaptation_set_1))))
|
||||||
|
.WillOnce(Return(ByMove(std::move(adaptation_set_2))));
|
||||||
|
|
||||||
|
ASSERT_EQ(adaptation_set_1_ptr, testable_period_.GetOrCreateAdaptationSet(
|
||||||
|
ConvertToMediaInfo(kContent1),
|
||||||
|
content_protection_in_adaptation_set_));
|
||||||
|
ASSERT_EQ(adaptation_set_2_ptr, testable_period_.GetOrCreateAdaptationSet(
|
||||||
|
ConvertToMediaInfo(kContent2),
|
||||||
|
content_protection_in_adaptation_set_));
|
||||||
|
|
||||||
|
adaptation_set_1_ptr->set_id(2);
|
||||||
|
adaptation_set_2_ptr->set_id(1);
|
||||||
|
const char kExpectedXml[] =
|
||||||
|
R"(<Period id="9">)"
|
||||||
|
// ContentType and Representation elements are populated after
|
||||||
|
// Representation::Init() is called.
|
||||||
|
R"( <AdaptationSet id="1" contentType=""/>)"
|
||||||
|
R"( <AdaptationSet id="2" contentType=""/>)"
|
||||||
|
R"(</Period>)";
|
||||||
|
EXPECT_THAT(testable_period_.GetXml(!kOutputPeriodDuration).get(),
|
||||||
|
XmlNodeEqual(kExpectedXml));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(PeriodTest, AudioAdaptationSetDefaultLanguage) {
|
||||||
|
mpd_options_.mpd_params.default_language = "en";
|
||||||
|
const char kEnglishAudioContent[] =
|
||||||
|
"audio_info {\n"
|
||||||
|
" codec: 'mp4a.40.2'\n"
|
||||||
|
" sampling_frequency: 44100\n"
|
||||||
|
" time_scale: 1200\n"
|
||||||
|
" num_channels: 2\n"
|
||||||
|
" language: 'en'\n"
|
||||||
|
"}\n"
|
||||||
|
"reference_time_scale: 50\n"
|
||||||
|
"container_type: CONTAINER_MP4\n"
|
||||||
|
"media_duration_seconds: 10.5\n";
|
||||||
|
std::unique_ptr<StrictMock<MockAdaptationSet>> adaptation_set(
|
||||||
|
new StrictMock<MockAdaptationSet>());
|
||||||
|
auto* adaptation_set_ptr = adaptation_set.get();
|
||||||
|
|
||||||
|
EXPECT_CALL(testable_period_, NewAdaptationSet(_, _, _))
|
||||||
|
.WillOnce(Return(ByMove(std::move(adaptation_set))));
|
||||||
|
EXPECT_CALL(*adaptation_set_ptr, AddRole(AdaptationSet::kRoleMain));
|
||||||
|
ASSERT_EQ(adaptation_set_ptr, testable_period_.GetOrCreateAdaptationSet(
|
||||||
|
ConvertToMediaInfo(kEnglishAudioContent),
|
||||||
|
content_protection_in_adaptation_set_));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(PeriodTest, AudioAdaptationSetNonDefaultLanguage) {
|
||||||
|
mpd_options_.mpd_params.default_language = "fr";
|
||||||
|
const char kEnglishAudioContent[] =
|
||||||
|
"audio_info {\n"
|
||||||
|
" codec: 'mp4a.40.2'\n"
|
||||||
|
" sampling_frequency: 44100\n"
|
||||||
|
" time_scale: 1200\n"
|
||||||
|
" num_channels: 2\n"
|
||||||
|
" language: 'en'\n"
|
||||||
|
"}\n"
|
||||||
|
"reference_time_scale: 50\n"
|
||||||
|
"container_type: CONTAINER_MP4\n"
|
||||||
|
"media_duration_seconds: 10.5\n";
|
||||||
|
std::unique_ptr<StrictMock<MockAdaptationSet>> adaptation_set(
|
||||||
|
new StrictMock<MockAdaptationSet>());
|
||||||
|
auto* adaptation_set_ptr = adaptation_set.get();
|
||||||
|
|
||||||
|
EXPECT_CALL(testable_period_, NewAdaptationSet(_, _, _))
|
||||||
|
.WillOnce(Return(ByMove(std::move(adaptation_set))));
|
||||||
|
EXPECT_CALL(*adaptation_set_ptr, AddRole(AdaptationSet::kRoleMain)).Times(0);
|
||||||
|
ASSERT_EQ(adaptation_set_ptr, testable_period_.GetOrCreateAdaptationSet(
|
||||||
|
ConvertToMediaInfo(kEnglishAudioContent),
|
||||||
|
content_protection_in_adaptation_set_));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(PeriodTest, TextAdaptationSetDefaultLanguage) {
|
||||||
|
mpd_options_.mpd_params.default_language = "en";
|
||||||
|
const char kEnglishTextContent[] =
|
||||||
|
"text_info {\n"
|
||||||
|
" codec: 'webvtt'\n"
|
||||||
|
" language: 'en'\n"
|
||||||
|
" type: SUBTITLE\n"
|
||||||
|
"}";
|
||||||
|
std::unique_ptr<StrictMock<MockAdaptationSet>> adaptation_set(
|
||||||
|
new StrictMock<MockAdaptationSet>());
|
||||||
|
auto* adaptation_set_ptr = adaptation_set.get();
|
||||||
|
|
||||||
|
EXPECT_CALL(testable_period_, NewAdaptationSet(_, _, _))
|
||||||
|
.WillOnce(Return(ByMove(std::move(adaptation_set))));
|
||||||
|
EXPECT_CALL(*adaptation_set_ptr, AddRole(AdaptationSet::kRoleMain));
|
||||||
|
EXPECT_CALL(*adaptation_set_ptr, ForceSetSegmentAlignment(true));
|
||||||
|
ASSERT_EQ(adaptation_set_ptr, testable_period_.GetOrCreateAdaptationSet(
|
||||||
|
ConvertToMediaInfo(kEnglishTextContent),
|
||||||
|
content_protection_in_adaptation_set_));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(PeriodTest, TextAdaptationSetNonDefaultLanguage) {
|
||||||
|
mpd_options_.mpd_params.default_language = "fr";
|
||||||
|
const char kEnglishTextContent[] =
|
||||||
|
"text_info {\n"
|
||||||
|
" codec: 'webvtt'\n"
|
||||||
|
" language: 'en'\n"
|
||||||
|
" type: SUBTITLE\n"
|
||||||
|
"}";
|
||||||
|
std::unique_ptr<StrictMock<MockAdaptationSet>> adaptation_set(
|
||||||
|
new StrictMock<MockAdaptationSet>());
|
||||||
|
auto* adaptation_set_ptr = adaptation_set.get();
|
||||||
|
|
||||||
|
EXPECT_CALL(testable_period_, NewAdaptationSet(_, _, _))
|
||||||
|
.WillOnce(Return(ByMove(std::move(adaptation_set))));
|
||||||
|
EXPECT_CALL(*adaptation_set_ptr, AddRole(AdaptationSet::kRoleMain)).Times(0);
|
||||||
|
EXPECT_CALL(*adaptation_set_ptr, ForceSetSegmentAlignment(true));
|
||||||
|
ASSERT_EQ(adaptation_set_ptr, testable_period_.GetOrCreateAdaptationSet(
|
||||||
|
ConvertToMediaInfo(kEnglishTextContent),
|
||||||
|
content_protection_in_adaptation_set_));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(PeriodTest, TextAdaptationSetNonDefaultLanguageButDefaultTextLanguage) {
|
||||||
|
mpd_options_.mpd_params.default_language = "fr";
|
||||||
|
mpd_options_.mpd_params.default_text_language = "en";
|
||||||
|
const char kEnglishTextContent[] =
|
||||||
|
"text_info {\n"
|
||||||
|
" codec: 'webvtt'\n"
|
||||||
|
" language: 'en'\n"
|
||||||
|
" type: SUBTITLE\n"
|
||||||
|
"}";
|
||||||
|
std::unique_ptr<StrictMock<MockAdaptationSet>> adaptation_set(
|
||||||
|
new StrictMock<MockAdaptationSet>());
|
||||||
|
auto* adaptation_set_ptr = adaptation_set.get();
|
||||||
|
|
||||||
|
EXPECT_CALL(testable_period_, NewAdaptationSet(_, _, _))
|
||||||
|
.WillOnce(Return(ByMove(std::move(adaptation_set))));
|
||||||
|
EXPECT_CALL(*adaptation_set_ptr, AddRole(AdaptationSet::kRoleMain));
|
||||||
|
EXPECT_CALL(*adaptation_set_ptr, ForceSetSegmentAlignment(true));
|
||||||
|
ASSERT_EQ(adaptation_set_ptr, testable_period_.GetOrCreateAdaptationSet(
|
||||||
|
ConvertToMediaInfo(kEnglishTextContent),
|
||||||
|
content_protection_in_adaptation_set_));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(PeriodTest, TextAdaptationSetDefaultLanguageButNonDefaultTextLanguage) {
|
||||||
|
mpd_options_.mpd_params.default_language = "en";
|
||||||
|
mpd_options_.mpd_params.default_text_language = "fr";
|
||||||
|
const char kEnglishTextContent[] =
|
||||||
|
"text_info {\n"
|
||||||
|
" codec: 'webvtt'\n"
|
||||||
|
" language: 'en'\n"
|
||||||
|
" type: SUBTITLE\n"
|
||||||
|
"}";
|
||||||
|
std::unique_ptr<StrictMock<MockAdaptationSet>> adaptation_set(
|
||||||
|
new StrictMock<MockAdaptationSet>());
|
||||||
|
auto* adaptation_set_ptr = adaptation_set.get();
|
||||||
|
|
||||||
|
EXPECT_CALL(testable_period_, NewAdaptationSet(_, _, _))
|
||||||
|
.WillOnce(Return(ByMove(std::move(adaptation_set))));
|
||||||
|
EXPECT_CALL(*adaptation_set_ptr, AddRole(AdaptationSet::kRoleMain)).Times(0);
|
||||||
|
EXPECT_CALL(*adaptation_set_ptr, ForceSetSegmentAlignment(true));
|
||||||
|
ASSERT_EQ(adaptation_set_ptr, testable_period_.GetOrCreateAdaptationSet(
|
||||||
|
ConvertToMediaInfo(kEnglishTextContent),
|
||||||
|
content_protection_in_adaptation_set_));
|
||||||
|
}
|
||||||
|
|
||||||
|
class PeriodTestWithContentProtection
|
||||||
|
: public PeriodTest,
|
||||||
|
public ::testing::WithParamInterface<bool> {
|
||||||
|
void SetUp() override { content_protection_in_adaptation_set_ = GetParam(); }
|
||||||
|
};
|
||||||
|
|
||||||
// With content_protection_adaptation_set_ == true, verify with different
|
// With content_protection_adaptation_set_ == true, verify with different
|
||||||
// MediaInfo::ProtectedContent, two AdaptationSets should be created.
|
// MediaInfo::ProtectedContent, two AdaptationSets should be created.
|
||||||
// AdaptationSets with different DRM won't be switchable.
|
// AdaptationSets with different DRM won't be switchable.
|
||||||
// Otherwise, only one AdaptationSet is created.
|
// Otherwise, only one AdaptationSet is created.
|
||||||
TEST_P(PeriodTest, DifferentProtectedContent) {
|
TEST_P(PeriodTestWithContentProtection, DifferentProtectedContent) {
|
||||||
// Note they both have different (bogus) pssh, like real use case.
|
// Note they both have different (bogus) pssh, like real use case.
|
||||||
// default Key ID = _default_key_id_
|
// default Key ID = _default_key_id_
|
||||||
const char kSdProtectedContent[] =
|
const char kSdProtectedContent[] =
|
||||||
|
@ -435,7 +760,7 @@ TEST_P(PeriodTest, DifferentProtectedContent) {
|
||||||
// Verify with the same MediaInfo::ProtectedContent, only one AdaptationSets
|
// Verify with the same MediaInfo::ProtectedContent, only one AdaptationSets
|
||||||
// should be created regardless of the value of
|
// should be created regardless of the value of
|
||||||
// content_protection_in_adaptation_set_.
|
// content_protection_in_adaptation_set_.
|
||||||
TEST_P(PeriodTest, SameProtectedContent) {
|
TEST_P(PeriodTestWithContentProtection, SameProtectedContent) {
|
||||||
// These have the same default key ID and PSSH.
|
// These have the same default key ID and PSSH.
|
||||||
const char kSdProtectedContent[] =
|
const char kSdProtectedContent[] =
|
||||||
"video_info {\n"
|
"video_info {\n"
|
||||||
|
@ -527,7 +852,7 @@ TEST_P(PeriodTest, SameProtectedContent) {
|
||||||
// 3. Add a 4k protected content. This should also make a new AdaptationSet.
|
// 3. Add a 4k protected content. This should also make a new AdaptationSet.
|
||||||
// It should be switchable with SD/HD AdaptationSet.
|
// It should be switchable with SD/HD AdaptationSet.
|
||||||
// Otherwise only one AdaptationSet is created.
|
// Otherwise only one AdaptationSet is created.
|
||||||
TEST_P(PeriodTest, SetAdaptationSetSwitching) {
|
TEST_P(PeriodTestWithContentProtection, SetAdaptationSetSwitching) {
|
||||||
// These have the same default key ID and PSSH.
|
// These have the same default key ID and PSSH.
|
||||||
const char kSdProtectedContent[] =
|
const char kSdProtectedContent[] =
|
||||||
"video_info {\n"
|
"video_info {\n"
|
||||||
|
@ -668,7 +993,8 @@ TEST_P(PeriodTest, SetAdaptationSetSwitching) {
|
||||||
|
|
||||||
// Even if the UUIDs match, video and audio AdaptationSets should not be
|
// Even if the UUIDs match, video and audio AdaptationSets should not be
|
||||||
// switchable.
|
// switchable.
|
||||||
TEST_P(PeriodTest, DoNotSetAdaptationSetSwitchingIfContentTypesDifferent) {
|
TEST_P(PeriodTestWithContentProtection,
|
||||||
|
DoNotSetAdaptationSetSwitchingIfContentTypesDifferent) {
|
||||||
// These have the same default key ID and PSSH.
|
// These have the same default key ID and PSSH.
|
||||||
const char kVideoContent[] =
|
const char kVideoContent[] =
|
||||||
"video_info {\n"
|
"video_info {\n"
|
||||||
|
@ -745,192 +1071,8 @@ TEST_P(PeriodTest, DoNotSetAdaptationSetSwitchingIfContentTypesDifferent) {
|
||||||
content_protection_in_adaptation_set_));
|
content_protection_in_adaptation_set_));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Don't put different audio languages or codecs in the same AdaptationSet.
|
|
||||||
TEST_P(PeriodTest, SplitAdaptationSetsByLanguageAndCodec) {
|
|
||||||
const char kAacEnglishAudioContent[] =
|
|
||||||
"audio_info {\n"
|
|
||||||
" codec: 'mp4a.40.2'\n"
|
|
||||||
" sampling_frequency: 44100\n"
|
|
||||||
" time_scale: 1200\n"
|
|
||||||
" num_channels: 2\n"
|
|
||||||
" language: 'eng'\n"
|
|
||||||
"}\n"
|
|
||||||
"reference_time_scale: 50\n"
|
|
||||||
"container_type: CONTAINER_MP4\n"
|
|
||||||
"media_duration_seconds: 10.5\n";
|
|
||||||
const char kAacGermanAudioContent[] =
|
|
||||||
"audio_info {\n"
|
|
||||||
" codec: 'mp4a.40.2'\n"
|
|
||||||
" sampling_frequency: 44100\n"
|
|
||||||
" time_scale: 1200\n"
|
|
||||||
" num_channels: 2\n"
|
|
||||||
" language: 'ger'\n"
|
|
||||||
"}\n"
|
|
||||||
"reference_time_scale: 50\n"
|
|
||||||
"container_type: CONTAINER_MP4\n"
|
|
||||||
"media_duration_seconds: 10.5\n";
|
|
||||||
const char kVorbisGermanAudioContent1[] =
|
|
||||||
"audio_info {\n"
|
|
||||||
" codec: 'vorbis'\n"
|
|
||||||
" sampling_frequency: 44100\n"
|
|
||||||
" time_scale: 1200\n"
|
|
||||||
" num_channels: 2\n"
|
|
||||||
" language: 'ger'\n"
|
|
||||||
"}\n"
|
|
||||||
"reference_time_scale: 50\n"
|
|
||||||
"container_type: CONTAINER_WEBM\n"
|
|
||||||
"media_duration_seconds: 10.5\n";
|
|
||||||
const char kVorbisGermanAudioContent2[] =
|
|
||||||
"audio_info {\n"
|
|
||||||
" codec: 'vorbis'\n"
|
|
||||||
" sampling_frequency: 44100\n"
|
|
||||||
" time_scale: 1200\n"
|
|
||||||
" num_channels: 2\n"
|
|
||||||
" language: 'ger'\n"
|
|
||||||
"}\n"
|
|
||||||
"reference_time_scale: 50\n"
|
|
||||||
"container_type: CONTAINER_WEBM\n"
|
|
||||||
"media_duration_seconds: 10.5\n";
|
|
||||||
|
|
||||||
std::unique_ptr<StrictMock<MockAdaptationSet>> aac_eng_adaptation_set(
|
|
||||||
new StrictMock<MockAdaptationSet>());
|
|
||||||
auto* aac_eng_adaptation_set_ptr = aac_eng_adaptation_set.get();
|
|
||||||
std::unique_ptr<StrictMock<MockAdaptationSet>> aac_ger_adaptation_set(
|
|
||||||
new StrictMock<MockAdaptationSet>());
|
|
||||||
auto* aac_ger_adaptation_set_ptr = aac_ger_adaptation_set.get();
|
|
||||||
std::unique_ptr<StrictMock<MockAdaptationSet>> vorbis_german_adaptation_set(
|
|
||||||
new StrictMock<MockAdaptationSet>());
|
|
||||||
auto* vorbis_german_adaptation_set_ptr = vorbis_german_adaptation_set.get();
|
|
||||||
|
|
||||||
// We expect three AdaptationSets.
|
|
||||||
EXPECT_CALL(testable_period_, NewAdaptationSet(_, _, _))
|
|
||||||
.WillOnce(Return(ByMove(std::move(aac_eng_adaptation_set))))
|
|
||||||
.WillOnce(Return(ByMove(std::move(aac_ger_adaptation_set))))
|
|
||||||
.WillOnce(Return(ByMove(std::move(vorbis_german_adaptation_set))));
|
|
||||||
|
|
||||||
ASSERT_EQ(aac_eng_adaptation_set_ptr,
|
|
||||||
testable_period_.GetOrCreateAdaptationSet(
|
|
||||||
ConvertToMediaInfo(kAacEnglishAudioContent),
|
|
||||||
content_protection_in_adaptation_set_));
|
|
||||||
ASSERT_EQ(aac_ger_adaptation_set_ptr,
|
|
||||||
testable_period_.GetOrCreateAdaptationSet(
|
|
||||||
ConvertToMediaInfo(kAacGermanAudioContent),
|
|
||||||
content_protection_in_adaptation_set_));
|
|
||||||
ASSERT_EQ(vorbis_german_adaptation_set_ptr,
|
|
||||||
testable_period_.GetOrCreateAdaptationSet(
|
|
||||||
ConvertToMediaInfo(kVorbisGermanAudioContent1),
|
|
||||||
content_protection_in_adaptation_set_));
|
|
||||||
// The same AdaptationSet is returned.
|
|
||||||
ASSERT_EQ(vorbis_german_adaptation_set_ptr,
|
|
||||||
testable_period_.GetOrCreateAdaptationSet(
|
|
||||||
ConvertToMediaInfo(kVorbisGermanAudioContent2),
|
|
||||||
content_protection_in_adaptation_set_));
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_P(PeriodTest, GetAdaptationSets) {
|
|
||||||
const char kContent1[] =
|
|
||||||
"audio_info {\n"
|
|
||||||
" codec: 'mp4a.40.2'\n"
|
|
||||||
" sampling_frequency: 44100\n"
|
|
||||||
" time_scale: 1200\n"
|
|
||||||
" num_channels: 2\n"
|
|
||||||
" language: 'eng'\n"
|
|
||||||
"}\n"
|
|
||||||
"reference_time_scale: 50\n"
|
|
||||||
"container_type: CONTAINER_MP4\n"
|
|
||||||
"media_duration_seconds: 10.5\n";
|
|
||||||
const char kContent2[] =
|
|
||||||
"audio_info {\n"
|
|
||||||
" codec: 'mp4a.40.2'\n"
|
|
||||||
" sampling_frequency: 44100\n"
|
|
||||||
" time_scale: 1200\n"
|
|
||||||
" num_channels: 2\n"
|
|
||||||
" language: 'ger'\n"
|
|
||||||
"}\n"
|
|
||||||
"reference_time_scale: 50\n"
|
|
||||||
"container_type: CONTAINER_MP4\n"
|
|
||||||
"media_duration_seconds: 10.5\n";
|
|
||||||
|
|
||||||
std::unique_ptr<StrictMock<MockAdaptationSet>> adaptation_set_1(
|
|
||||||
new StrictMock<MockAdaptationSet>());
|
|
||||||
auto* adaptation_set_1_ptr = adaptation_set_1.get();
|
|
||||||
std::unique_ptr<StrictMock<MockAdaptationSet>> adaptation_set_2(
|
|
||||||
new StrictMock<MockAdaptationSet>());
|
|
||||||
auto* adaptation_set_2_ptr = adaptation_set_2.get();
|
|
||||||
|
|
||||||
EXPECT_CALL(testable_period_, NewAdaptationSet(_, _, _))
|
|
||||||
.WillOnce(Return(ByMove(std::move(adaptation_set_1))))
|
|
||||||
.WillOnce(Return(ByMove(std::move(adaptation_set_2))));
|
|
||||||
|
|
||||||
ASSERT_EQ(adaptation_set_1_ptr, testable_period_.GetOrCreateAdaptationSet(
|
|
||||||
ConvertToMediaInfo(kContent1),
|
|
||||||
content_protection_in_adaptation_set_));
|
|
||||||
EXPECT_THAT(testable_period_.GetAdaptationSets(),
|
|
||||||
ElementsAre(adaptation_set_1_ptr));
|
|
||||||
|
|
||||||
ASSERT_EQ(adaptation_set_2_ptr, testable_period_.GetOrCreateAdaptationSet(
|
|
||||||
ConvertToMediaInfo(kContent2),
|
|
||||||
content_protection_in_adaptation_set_));
|
|
||||||
EXPECT_THAT(testable_period_.GetAdaptationSets(),
|
|
||||||
ElementsAre(adaptation_set_1_ptr, adaptation_set_2_ptr));
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_P(PeriodTest, OrderedByAdaptationSetId) {
|
|
||||||
const char kContent1[] =
|
|
||||||
"audio_info {\n"
|
|
||||||
" codec: 'mp4a.40.2'\n"
|
|
||||||
" sampling_frequency: 44100\n"
|
|
||||||
" time_scale: 1200\n"
|
|
||||||
" num_channels: 2\n"
|
|
||||||
" language: 'eng'\n"
|
|
||||||
"}\n"
|
|
||||||
"reference_time_scale: 50\n"
|
|
||||||
"container_type: CONTAINER_MP4\n"
|
|
||||||
"media_duration_seconds: 10.5\n";
|
|
||||||
const char kContent2[] =
|
|
||||||
"audio_info {\n"
|
|
||||||
" codec: 'mp4a.40.2'\n"
|
|
||||||
" sampling_frequency: 44100\n"
|
|
||||||
" time_scale: 1200\n"
|
|
||||||
" num_channels: 2\n"
|
|
||||||
" language: 'ger'\n"
|
|
||||||
"}\n"
|
|
||||||
"reference_time_scale: 50\n"
|
|
||||||
"container_type: CONTAINER_MP4\n"
|
|
||||||
"media_duration_seconds: 10.5\n";
|
|
||||||
|
|
||||||
std::unique_ptr<StrictMock<MockAdaptationSet>> adaptation_set_1(
|
|
||||||
new StrictMock<MockAdaptationSet>());
|
|
||||||
auto* adaptation_set_1_ptr = adaptation_set_1.get();
|
|
||||||
std::unique_ptr<StrictMock<MockAdaptationSet>> adaptation_set_2(
|
|
||||||
new StrictMock<MockAdaptationSet>());
|
|
||||||
auto* adaptation_set_2_ptr = adaptation_set_2.get();
|
|
||||||
|
|
||||||
EXPECT_CALL(testable_period_, NewAdaptationSet(_, _, _))
|
|
||||||
.WillOnce(Return(ByMove(std::move(adaptation_set_1))))
|
|
||||||
.WillOnce(Return(ByMove(std::move(adaptation_set_2))));
|
|
||||||
|
|
||||||
ASSERT_EQ(adaptation_set_1_ptr, testable_period_.GetOrCreateAdaptationSet(
|
|
||||||
ConvertToMediaInfo(kContent1),
|
|
||||||
content_protection_in_adaptation_set_));
|
|
||||||
ASSERT_EQ(adaptation_set_2_ptr, testable_period_.GetOrCreateAdaptationSet(
|
|
||||||
ConvertToMediaInfo(kContent2),
|
|
||||||
content_protection_in_adaptation_set_));
|
|
||||||
|
|
||||||
adaptation_set_1_ptr->set_id(2);
|
|
||||||
adaptation_set_2_ptr->set_id(1);
|
|
||||||
const char kExpectedXml[] =
|
|
||||||
R"(<Period id="9">)"
|
|
||||||
// ContentType and Representation elements are populated after
|
|
||||||
// Representation::Init() is called.
|
|
||||||
R"( <AdaptationSet id="1" contentType=""/>)"
|
|
||||||
R"( <AdaptationSet id="2" contentType=""/>)"
|
|
||||||
R"(</Period>)";
|
|
||||||
EXPECT_THAT(testable_period_.GetXml(!kOutputPeriodDuration).get(),
|
|
||||||
XmlNodeEqual(kExpectedXml));
|
|
||||||
}
|
|
||||||
INSTANTIATE_TEST_CASE_P(ContentProtectionInAdaptationSet,
|
INSTANTIATE_TEST_CASE_P(ContentProtectionInAdaptationSet,
|
||||||
PeriodTest,
|
PeriodTestWithContentProtection,
|
||||||
::testing::Bool());
|
::testing::Bool());
|
||||||
|
|
||||||
} // namespace shaka
|
} // namespace shaka
|
||||||
|
|
|
@ -53,7 +53,12 @@ struct MpdParams {
|
||||||
/// The tracks tagged with this language will have <Role ... value=\"main\" />
|
/// The tracks tagged with this language will have <Role ... value=\"main\" />
|
||||||
/// in the manifest. This allows the player to choose the correct default
|
/// in the manifest. This allows the player to choose the correct default
|
||||||
/// language for the content.
|
/// language for the content.
|
||||||
|
/// This applies to both audio and text tracks. The default language for text
|
||||||
|
/// tracks can be overriden by 'default_text_language'.
|
||||||
std::string default_language;
|
std::string default_language;
|
||||||
|
/// Same as above, but this overrides the default language for text tracks,
|
||||||
|
/// i.e. subtitles or close-captions.
|
||||||
|
std::string default_text_language;
|
||||||
/// Generate static MPD for live profile. Note that this flag has no effect
|
/// Generate static MPD for live profile. Note that this flag has no effect
|
||||||
/// for on-demand profile, in which case static MPD is always used.
|
/// for on-demand profile, in which case static MPD is always used.
|
||||||
bool generate_static_live_mpd = false;
|
bool generate_static_live_mpd = false;
|
||||||
|
|
|
@ -895,8 +895,12 @@ Status Packager::Initialize(
|
||||||
// in the shortest form.
|
// in the shortest form.
|
||||||
mpd_params.default_language =
|
mpd_params.default_language =
|
||||||
LanguageToShortestForm(mpd_params.default_language);
|
LanguageToShortestForm(mpd_params.default_language);
|
||||||
|
mpd_params.default_text_language =
|
||||||
|
LanguageToShortestForm(mpd_params.default_text_language);
|
||||||
hls_params.default_language =
|
hls_params.default_language =
|
||||||
LanguageToShortestForm(hls_params.default_language);
|
LanguageToShortestForm(hls_params.default_language);
|
||||||
|
hls_params.default_text_language =
|
||||||
|
LanguageToShortestForm(hls_params.default_text_language);
|
||||||
|
|
||||||
if (!mpd_params.mpd_output.empty()) {
|
if (!mpd_params.mpd_output.empty()) {
|
||||||
const bool on_demand_dash_profile =
|
const bool on_demand_dash_profile =
|
||||||
|
|
Loading…
Reference in New Issue