Support HLS characteristics
Add hls_characteristics stream descriptor, which is a colon or semi-colon separated list of strings. It is optional. Fixes #430. Change-Id: Ifcf79316e68768ff065891933de565cd0ff32ec4
This commit is contained in:
parent
74df8d30cc
commit
273ab09f05
|
@ -24,3 +24,9 @@ HLS specific stream descriptor fields
|
||||||
'.m3u8', and is relative to hls_master_playlist_output (see below). Should
|
'.m3u8', and is relative to hls_master_playlist_output (see below). Should
|
||||||
only be set for video streams. If unspecified, no I-Frames only playlist is
|
only be set for video streams. If unspecified, no I-Frames only playlist is
|
||||||
created.
|
created.
|
||||||
|
|
||||||
|
:hls_characteristics (charcs):
|
||||||
|
|
||||||
|
Optional colon or semi-colon separated list of values for the
|
||||||
|
CHARACTERISTICS attribute for EXT-X-MEDIA. See CHARACTERISTICS attribute in
|
||||||
|
http://bit.ly/2OOUkdB for details.
|
||||||
|
|
|
@ -101,7 +101,10 @@ const char kUsage[] =
|
||||||
" - iframe_playlist_name: The optional HLS I-Frames only playlist file\n"
|
" - iframe_playlist_name: The optional HLS I-Frames only playlist file\n"
|
||||||
" to create. Usually ends with '.m3u8', and is relative to\n"
|
" to create. Usually ends with '.m3u8', and is relative to\n"
|
||||||
" hls_master_playlist_output. Should only be set for video streams. If\n"
|
" hls_master_playlist_output. Should only be set for video streams. If\n"
|
||||||
" unspecified, no I-Frames only playlist is created.\n";
|
" unspecified, no I-Frames only playlist is created.\n"
|
||||||
|
" - hls_characteristics (charcs): Optional colon/semicolon separated\n"
|
||||||
|
" list of values for the CHARACTERISTICS attribute for EXT-X-MEDIA.\n"
|
||||||
|
" See CHARACTERISTICS attribute in http://bit.ly/2OOUkdB for details.\n";
|
||||||
|
|
||||||
// Labels for parameters in RawKey key info.
|
// Labels for parameters in RawKey key info.
|
||||||
const char kDrmLabelLabel[] = "label";
|
const char kDrmLabelLabel[] = "label";
|
||||||
|
|
|
@ -30,6 +30,7 @@ enum FieldType {
|
||||||
kTrickPlayFactorField,
|
kTrickPlayFactorField,
|
||||||
kSkipEncryptionField,
|
kSkipEncryptionField,
|
||||||
kDrmStreamLabelField,
|
kDrmStreamLabelField,
|
||||||
|
kHlsCharacteristicsField,
|
||||||
};
|
};
|
||||||
|
|
||||||
struct FieldNameToTypeMapping {
|
struct FieldNameToTypeMapping {
|
||||||
|
@ -63,6 +64,9 @@ const FieldNameToTypeMapping kFieldNameTypeMappings[] = {
|
||||||
{"skip_encryption", kSkipEncryptionField},
|
{"skip_encryption", kSkipEncryptionField},
|
||||||
{"drm_stream_label", kDrmStreamLabelField},
|
{"drm_stream_label", kDrmStreamLabelField},
|
||||||
{"drm_label", kDrmStreamLabelField},
|
{"drm_label", kDrmStreamLabelField},
|
||||||
|
{"hls_characteristics", kHlsCharacteristicsField},
|
||||||
|
{"characteristics", kHlsCharacteristicsField},
|
||||||
|
{"charcs", kHlsCharacteristicsField},
|
||||||
};
|
};
|
||||||
|
|
||||||
FieldType GetFieldType(const std::string& field_name) {
|
FieldType GetFieldType(const std::string& field_name) {
|
||||||
|
@ -164,10 +168,14 @@ base::Optional<StreamDescriptor> ParseStreamDescriptor(
|
||||||
descriptor.skip_encryption = skip_encryption_value > 0;
|
descriptor.skip_encryption = skip_encryption_value > 0;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case kDrmStreamLabelField: {
|
case kDrmStreamLabelField:
|
||||||
descriptor.drm_label = iter->second;
|
descriptor.drm_label = iter->second;
|
||||||
break;
|
break;
|
||||||
}
|
case kHlsCharacteristicsField:
|
||||||
|
descriptor.hls_characteristics =
|
||||||
|
base::SplitString(iter->second, ";:", base::TRIM_WHITESPACE,
|
||||||
|
base::SPLIT_WANT_NONEMPTY);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
LOG(ERROR) << "Unknown field in stream descriptor (\"" << iter->first
|
LOG(ERROR) << "Unknown field in stream descriptor (\"" << iter->first
|
||||||
<< "\").";
|
<< "\").";
|
||||||
|
|
|
@ -271,6 +271,7 @@ class PackagerAppTest(unittest.TestCase):
|
||||||
segmented=False,
|
segmented=False,
|
||||||
using_time_specifier=False,
|
using_time_specifier=False,
|
||||||
hls=False,
|
hls=False,
|
||||||
|
hls_characteristics=None,
|
||||||
trick_play_factor=None,
|
trick_play_factor=None,
|
||||||
drm_label=None,
|
drm_label=None,
|
||||||
skip_encryption=None,
|
skip_encryption=None,
|
||||||
|
@ -290,23 +291,24 @@ class PackagerAppTest(unittest.TestCase):
|
||||||
language: The language override for the input stream.
|
language: The language override for the input stream.
|
||||||
output_file_prefix: The output file prefix. Default to empty if not
|
output_file_prefix: The output file prefix. Default to empty if not
|
||||||
specified.
|
specified.
|
||||||
output_format: Specify the format for the output.
|
output_format: The format for the output.
|
||||||
segmented: Should the output use a segmented formatted. This will affect
|
segmented: Should the output use a segmented formatted. This will affect
|
||||||
the output extensions and manifests.
|
the output extensions and manifests.
|
||||||
using_time_specifier: Use $Time$ in segment name instead of using
|
using_time_specifier: Use $Time$ in segment name instead of using
|
||||||
$Number$. This flag is only relevant if segmented is True.
|
$Number$. This flag is only relevant if segmented is True.
|
||||||
hls: Should the output be for an HLS manifest.
|
hls: Should the output be for an HLS manifest.
|
||||||
|
hls_characteristics: CHARACTERISTICS attribute for the HLS stream.
|
||||||
trick_play_factor: Signals the stream is to be used for a trick play
|
trick_play_factor: Signals the stream is to be used for a trick play
|
||||||
stream and which key frames to use. A trick play factor of 0 is the
|
stream and which key frames to use. A trick play factor of 0 is the
|
||||||
same as not specifying a trick play factor.
|
same as not specifying a trick play factor.
|
||||||
drm_label: Sets the drm label for the stream.
|
drm_label: The drm label for the stream.
|
||||||
skip_encryption: If set to true, the stream will not be encrypted.
|
skip_encryption: If set to true, the stream will not be encrypted.
|
||||||
bandwidth: The expected bandwidth value that should be listed in the
|
bandwidth: The expected bandwidth value that should be listed in the
|
||||||
manifest.
|
manifest.
|
||||||
split_content_on_ad_cues: If set to true, the output file will be split
|
split_content_on_ad_cues: If set to true, the output file will be split
|
||||||
into multiple files, with a total of NumAdCues + 1 files.
|
into multiple files, with a total of NumAdCues + 1 files.
|
||||||
test_file: Specify the input file to use. If the input file is not
|
test_file: The input file to use. If the input file is not specified, a
|
||||||
specify, a default file will be used.
|
default file will be used.
|
||||||
|
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
|
@ -348,6 +350,9 @@ class PackagerAppTest(unittest.TestCase):
|
||||||
stream.Append('iframe_playlist_name',
|
stream.Append('iframe_playlist_name',
|
||||||
output_file_name_base + '-iframe.m3u8')
|
output_file_name_base + '-iframe.m3u8')
|
||||||
|
|
||||||
|
if hls_characteristics:
|
||||||
|
stream.Append('hls_characteristics', hls_characteristics)
|
||||||
|
|
||||||
requires_init_segment = segmented and base_ext not in [
|
requires_init_segment = segmented and base_ext not in [
|
||||||
'aac', 'ac3', 'ec3', 'ts', 'vtt'
|
'aac', 'ac3', 'ec3', 'ts', 'vtt'
|
||||||
]
|
]
|
||||||
|
@ -1503,7 +1508,11 @@ class PackagerFunctionalTest(PackagerAppTest):
|
||||||
streams = self._GetStreams(
|
streams = self._GetStreams(
|
||||||
['audio', 'video'], output_format='ts', segmented=True)
|
['audio', 'video'], output_format='ts', segmented=True)
|
||||||
streams += self._GetStreams(
|
streams += self._GetStreams(
|
||||||
['text'], test_files=['bear-english.vtt'], segmented=True)
|
['text'],
|
||||||
|
test_files=['bear-english.vtt'],
|
||||||
|
segmented=True,
|
||||||
|
hls_characteristics='public.accessibility.transcribes-spoken-dialog;'
|
||||||
|
'private.accessibility.widevine-special')
|
||||||
|
|
||||||
flags = self._GetFlags(output_hls=True)
|
flags = self._GetFlags(output_hls=True)
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
|
|
||||||
#EXT-X-MEDIA:TYPE=AUDIO,URI="stream_1.m3u8",GROUP-ID="default-audio-group",NAME="stream_1",AUTOSELECT=YES,CHANNELS="2"
|
#EXT-X-MEDIA:TYPE=AUDIO,URI="stream_1.m3u8",GROUP-ID="default-audio-group",NAME="stream_1",AUTOSELECT=YES,CHANNELS="2"
|
||||||
|
|
||||||
#EXT-X-MEDIA:TYPE=SUBTITLES,URI="stream_0.m3u8",GROUP-ID="default-text-group",NAME="stream_0",AUTOSELECT=YES
|
#EXT-X-MEDIA:TYPE=SUBTITLES,URI="stream_0.m3u8",GROUP-ID="default-text-group",NAME="stream_0",AUTOSELECT=YES,CHARACTERISTICS="public.accessibility.transcribes-spoken-dialog,private.accessibility.widevine-special"
|
||||||
|
|
||||||
#EXT-X-STREAM-INF:BANDWIDTH=1217518,AVERAGE-BANDWIDTH=1117319,CODECS="avc1.64001e,mp4a.40.2",RESOLUTION=640x360,AUDIO="default-audio-group",SUBTITLES="default-text-group"
|
#EXT-X-STREAM-INF:BANDWIDTH=1217518,AVERAGE-BANDWIDTH=1117319,CODECS="avc1.64001e,mp4a.40.2",RESOLUTION=640x360,AUDIO="default-audio-group",SUBTITLES="default-text-group"
|
||||||
stream_2.m3u8
|
stream_2.m3u8
|
||||||
|
|
|
@ -287,6 +287,12 @@ void BuildMediaTag(const MediaPlaylist& playlist,
|
||||||
tag.AddString("AUTOSELECT", "YES");
|
tag.AddString("AUTOSELECT", "YES");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const std::vector<std::string>& characteristics = playlist.characteristics();
|
||||||
|
if (!characteristics.empty()) {
|
||||||
|
tag.AddQuotedString("CHARACTERISTICS",
|
||||||
|
base::JoinString(characteristics, ","));
|
||||||
|
}
|
||||||
|
|
||||||
const MediaPlaylist::MediaPlaylistStreamType kAudio =
|
const MediaPlaylist::MediaPlaylistStreamType kAudio =
|
||||||
MediaPlaylist::MediaPlaylistStreamType::kAudio;
|
MediaPlaylist::MediaPlaylistStreamType::kAudio;
|
||||||
if (playlist.stream_type() == kAudio) {
|
if (playlist.stream_type() == kAudio) {
|
||||||
|
|
|
@ -399,6 +399,42 @@ TEST_F(MasterPlaylistTest, WriteMasterPlaylistVideosAndTexts) {
|
||||||
ASSERT_EQ(expected, actual);
|
ASSERT_EQ(expected, actual);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_F(MasterPlaylistTest, WriteMasterPlaylistVideoAndTextWithCharacteritics) {
|
||||||
|
// Video, sd.m3u8.
|
||||||
|
std::unique_ptr<MockMediaPlaylist> video =
|
||||||
|
CreateVideoPlaylist("sd.m3u8", "sdvideocodec", 300000, 200000);
|
||||||
|
|
||||||
|
// Text, eng.m3u8.
|
||||||
|
std::unique_ptr<MockMediaPlaylist> text =
|
||||||
|
CreateTextPlaylist("eng.m3u8", "english", "textgroup", "textcodec", "en");
|
||||||
|
text->SetCharacteristicsForTesting(std::vector<std::string>{
|
||||||
|
"public.accessibility.transcribes-spoken-dialog", "public.easy-to-read"});
|
||||||
|
|
||||||
|
const char kBaseUrl[] = "http://playlists.org/";
|
||||||
|
EXPECT_TRUE(master_playlist_.WriteMasterPlaylist(kBaseUrl, test_output_dir_,
|
||||||
|
{video.get(), text.get()}));
|
||||||
|
|
||||||
|
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"
|
||||||
|
"\n"
|
||||||
|
"#EXT-X-MEDIA:TYPE=SUBTITLES,URI=\"http://playlists.org/eng.m3u8\","
|
||||||
|
"GROUP-ID=\"textgroup\",LANGUAGE=\"en\",NAME=\"english\",DEFAULT=YES,"
|
||||||
|
"AUTOSELECT=YES,CHARACTERISTICS=\""
|
||||||
|
"public.accessibility.transcribes-spoken-dialog,public.easy-to-read\"\n"
|
||||||
|
"\n"
|
||||||
|
"#EXT-X-STREAM-INF:BANDWIDTH=300000,AVERAGE-BANDWIDTH=200000,"
|
||||||
|
"CODECS=\"sdvideocodec,textcodec\",RESOLUTION=800x600,"
|
||||||
|
"SUBTITLES=\"textgroup\"\n"
|
||||||
|
"http://playlists.org/sd.m3u8\n";
|
||||||
|
|
||||||
|
ASSERT_EQ(expected, actual);
|
||||||
|
}
|
||||||
|
|
||||||
TEST_F(MasterPlaylistTest, WriteMasterPlaylistVideoAndTextGroups) {
|
TEST_F(MasterPlaylistTest, WriteMasterPlaylistVideoAndTextGroups) {
|
||||||
// Video, sd.m3u8.
|
// Video, sd.m3u8.
|
||||||
std::unique_ptr<MockMediaPlaylist> video =
|
std::unique_ptr<MockMediaPlaylist> video =
|
||||||
|
|
|
@ -348,6 +348,11 @@ void MediaPlaylist::SetLanguageForTesting(const std::string& language) {
|
||||||
language_ = language;
|
language_ = language;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MediaPlaylist::SetCharacteristicsForTesting(
|
||||||
|
const std::vector<std::string>& characteristics) {
|
||||||
|
characteristics_ = characteristics;
|
||||||
|
}
|
||||||
|
|
||||||
bool MediaPlaylist::SetMediaInfo(const MediaInfo& media_info) {
|
bool MediaPlaylist::SetMediaInfo(const MediaInfo& media_info) {
|
||||||
const uint32_t time_scale = GetTimeScale(media_info);
|
const uint32_t time_scale = GetTimeScale(media_info);
|
||||||
if (time_scale == 0) {
|
if (time_scale == 0) {
|
||||||
|
@ -370,6 +375,9 @@ bool MediaPlaylist::SetMediaInfo(const MediaInfo& media_info) {
|
||||||
media_info_ = media_info;
|
media_info_ = media_info;
|
||||||
language_ = GetLanguage(media_info);
|
language_ = GetLanguage(media_info);
|
||||||
use_byte_range_ = !media_info_.has_segment_template_url();
|
use_byte_range_ = !media_info_.has_segment_template_url();
|
||||||
|
characteristics_ =
|
||||||
|
std::vector<std::string>(media_info_.hls_characteristics().begin(),
|
||||||
|
media_info_.hls_characteristics().end());
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
#include <list>
|
#include <list>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
#include "packager/base/macros.h"
|
#include "packager/base/macros.h"
|
||||||
#include "packager/hls/public/hls_params.h"
|
#include "packager/hls/public/hls_params.h"
|
||||||
|
@ -87,6 +88,10 @@ class MediaPlaylist {
|
||||||
/// For testing only.
|
/// For testing only.
|
||||||
void SetLanguageForTesting(const std::string& language);
|
void SetLanguageForTesting(const std::string& language);
|
||||||
|
|
||||||
|
/// For testing only.
|
||||||
|
void SetCharacteristicsForTesting(
|
||||||
|
const std::vector<std::string>& characteristics);
|
||||||
|
|
||||||
/// This must succeed before calling any other public methods.
|
/// This must succeed before calling any other public methods.
|
||||||
/// @param media_info is the info of the segments that are going to be added
|
/// @param media_info is the info of the segments that are going to be added
|
||||||
/// to this playlist.
|
/// to this playlist.
|
||||||
|
@ -182,7 +187,11 @@ class MediaPlaylist {
|
||||||
|
|
||||||
/// @return the language of the media, as an ISO language tag in its shortest
|
/// @return the language of the media, as an ISO language tag in its shortest
|
||||||
/// form. May be an empty string for video.
|
/// form. May be an empty string for video.
|
||||||
std::string language() const { return language_; }
|
const std::string& language() const { return language_; }
|
||||||
|
|
||||||
|
const std::vector<std::string>& characteristics() const {
|
||||||
|
return characteristics_;
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// Add a SegmentInfoEntry (#EXTINF).
|
// Add a SegmentInfoEntry (#EXTINF).
|
||||||
|
@ -213,6 +222,7 @@ class MediaPlaylist {
|
||||||
bool use_byte_range_ = false;
|
bool use_byte_range_ = false;
|
||||||
std::string codec_;
|
std::string codec_;
|
||||||
std::string language_;
|
std::string language_;
|
||||||
|
std::vector<std::string> characteristics_;
|
||||||
int media_sequence_number_ = 0;
|
int media_sequence_number_ = 0;
|
||||||
bool inserted_discontinuity_tag_ = false;
|
bool inserted_discontinuity_tag_ = false;
|
||||||
int discontinuity_sequence_number_ = 0;
|
int discontinuity_sequence_number_ = 0;
|
||||||
|
|
|
@ -18,6 +18,7 @@ namespace shaka {
|
||||||
namespace hls {
|
namespace hls {
|
||||||
|
|
||||||
using ::testing::_;
|
using ::testing::_;
|
||||||
|
using ::testing::ElementsAreArray;
|
||||||
using ::testing::ReturnArg;
|
using ::testing::ReturnArg;
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
@ -483,6 +484,20 @@ TEST_F(MediaPlaylistMultiSegmentTest, GetNumChannels) {
|
||||||
EXPECT_EQ(8, media_playlist_->GetNumChannels());
|
EXPECT_EQ(8, media_playlist_->GetNumChannels());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_F(MediaPlaylistMultiSegmentTest, Characteristics) {
|
||||||
|
MediaInfo media_info;
|
||||||
|
media_info.set_reference_time_scale(kTimeScale);
|
||||||
|
|
||||||
|
static const char* kCharacteristics[] = {"some.characteristic",
|
||||||
|
"another.characteristic"};
|
||||||
|
|
||||||
|
media_info.add_hls_characteristics(kCharacteristics[0]);
|
||||||
|
media_info.add_hls_characteristics(kCharacteristics[1]);
|
||||||
|
ASSERT_TRUE(media_playlist_->SetMediaInfo(media_info));
|
||||||
|
EXPECT_THAT(media_playlist_->characteristics(),
|
||||||
|
ElementsAreArray(kCharacteristics));
|
||||||
|
}
|
||||||
|
|
||||||
TEST_F(MediaPlaylistMultiSegmentTest, InitSegment) {
|
TEST_F(MediaPlaylistMultiSegmentTest, InitSegment) {
|
||||||
valid_video_media_info_.set_reference_time_scale(90000);
|
valid_video_media_info_.set_reference_time_scale(90000);
|
||||||
valid_video_media_info_.set_init_segment_url("init_segment.mp4");
|
valid_video_media_info_.set_init_segment_url("init_segment.mp4");
|
||||||
|
|
|
@ -21,11 +21,13 @@ HlsNotifyMuxerListener::HlsNotifyMuxerListener(
|
||||||
bool iframes_only,
|
bool iframes_only,
|
||||||
const std::string& ext_x_media_name,
|
const std::string& ext_x_media_name,
|
||||||
const std::string& ext_x_media_group_id,
|
const std::string& ext_x_media_group_id,
|
||||||
|
const std::vector<std::string>& characteristics,
|
||||||
hls::HlsNotifier* hls_notifier)
|
hls::HlsNotifier* hls_notifier)
|
||||||
: playlist_name_(playlist_name),
|
: playlist_name_(playlist_name),
|
||||||
iframes_only_(iframes_only),
|
iframes_only_(iframes_only),
|
||||||
ext_x_media_name_(ext_x_media_name),
|
ext_x_media_name_(ext_x_media_name),
|
||||||
ext_x_media_group_id_(ext_x_media_group_id),
|
ext_x_media_group_id_(ext_x_media_group_id),
|
||||||
|
characteristics_(characteristics),
|
||||||
hls_notifier_(hls_notifier) {
|
hls_notifier_(hls_notifier) {
|
||||||
DCHECK(hls_notifier);
|
DCHECK(hls_notifier);
|
||||||
}
|
}
|
||||||
|
@ -90,6 +92,10 @@ void HlsNotifyMuxerListener::OnMediaStart(const MuxerOptions& muxer_options,
|
||||||
LOG(ERROR) << "Failed to generate MediaInfo from input.";
|
LOG(ERROR) << "Failed to generate MediaInfo from input.";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (!characteristics_.empty()) {
|
||||||
|
for (const std::string& characteristic : characteristics_)
|
||||||
|
media_info->add_hls_characteristics(characteristic);
|
||||||
|
}
|
||||||
if (protection_scheme_ != FOURCC_NULL) {
|
if (protection_scheme_ != FOURCC_NULL) {
|
||||||
internal::SetContentProtectionFields(protection_scheme_, next_key_id_,
|
internal::SetContentProtectionFields(protection_scheme_, next_key_id_,
|
||||||
next_key_system_infos_,
|
next_key_system_infos_,
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
#include "packager/base/optional.h"
|
#include "packager/base/optional.h"
|
||||||
#include "packager/media/event/event_info.h"
|
#include "packager/media/event/event_info.h"
|
||||||
|
@ -35,11 +36,15 @@ class HlsNotifyMuxerListener : public MuxerListener {
|
||||||
/// @param ext_x_media_group_id is the group ID for this playlist. This is the
|
/// @param ext_x_media_group_id is the group ID for this playlist. This is the
|
||||||
/// value of GROUP-ID attribute for EXT-X-MEDIA. This may be empty for
|
/// value of GROUP-ID attribute for EXT-X-MEDIA. This may be empty for
|
||||||
/// video.
|
/// video.
|
||||||
|
/// @param characteristics is the characteristics for this playlist. This is
|
||||||
|
/// the value of CHARACTERISTICS attribute for EXT-X-MEDIA. This may be
|
||||||
|
/// empty.
|
||||||
/// @param hls_notifier used by this listener. Ownership does not transfer.
|
/// @param hls_notifier used by this listener. Ownership does not transfer.
|
||||||
HlsNotifyMuxerListener(const std::string& playlist_name,
|
HlsNotifyMuxerListener(const std::string& playlist_name,
|
||||||
bool iframes_only,
|
bool iframes_only,
|
||||||
const std::string& ext_x_media_name,
|
const std::string& ext_x_media_name,
|
||||||
const std::string& ext_x_media_group_id,
|
const std::string& ext_x_media_group_id,
|
||||||
|
const std::vector<std::string>& characteristics,
|
||||||
hls::HlsNotifier* hls_notifier);
|
hls::HlsNotifier* hls_notifier);
|
||||||
~HlsNotifyMuxerListener() override;
|
~HlsNotifyMuxerListener() override;
|
||||||
|
|
||||||
|
@ -77,6 +82,7 @@ class HlsNotifyMuxerListener : public MuxerListener {
|
||||||
const bool iframes_only_;
|
const bool iframes_only_;
|
||||||
const std::string ext_x_media_name_;
|
const std::string ext_x_media_name_;
|
||||||
const std::string ext_x_media_group_id_;
|
const std::string ext_x_media_group_id_;
|
||||||
|
const std::vector<std::string> characteristics_;
|
||||||
hls::HlsNotifier* const hls_notifier_;
|
hls::HlsNotifier* const hls_notifier_;
|
||||||
base::Optional<uint32_t> stream_id_;
|
base::Optional<uint32_t> stream_id_;
|
||||||
|
|
||||||
|
|
|
@ -18,7 +18,9 @@ namespace media {
|
||||||
|
|
||||||
using ::testing::_;
|
using ::testing::_;
|
||||||
using ::testing::Bool;
|
using ::testing::Bool;
|
||||||
|
using ::testing::ElementsAre;
|
||||||
using ::testing::InSequence;
|
using ::testing::InSequence;
|
||||||
|
using ::testing::Property;
|
||||||
using ::testing::Return;
|
using ::testing::Return;
|
||||||
using ::testing::StrEq;
|
using ::testing::StrEq;
|
||||||
using ::testing::TestWithParam;
|
using ::testing::TestWithParam;
|
||||||
|
@ -92,6 +94,8 @@ const bool kIFramesOnlyPlaylist = true;
|
||||||
const char kDefaultPlaylistName[] = "default_playlist.m3u8";
|
const char kDefaultPlaylistName[] = "default_playlist.m3u8";
|
||||||
const char kDefaultName[] = "DEFAULTNAME";
|
const char kDefaultName[] = "DEFAULTNAME";
|
||||||
const char kDefaultGroupId[] = "DEFAULTGROUPID";
|
const char kDefaultGroupId[] = "DEFAULTGROUPID";
|
||||||
|
const char kCharactersticA[] = "public.accessibility.transcribes-spoken-dialog";
|
||||||
|
const char kCharactersticB[] = "public.easy-to-read";
|
||||||
|
|
||||||
MATCHER_P(HasEncryptionScheme, expected_scheme, "") {
|
MATCHER_P(HasEncryptionScheme, expected_scheme, "") {
|
||||||
*result_listener << "it has_protected_content: "
|
*result_listener << "it has_protected_content: "
|
||||||
|
@ -113,6 +117,7 @@ class HlsNotifyMuxerListenerTest : public ::testing::Test {
|
||||||
!kIFramesOnlyPlaylist,
|
!kIFramesOnlyPlaylist,
|
||||||
kDefaultName,
|
kDefaultName,
|
||||||
kDefaultGroupId,
|
kDefaultGroupId,
|
||||||
|
std::vector<std::string>{kCharactersticA, kCharactersticB},
|
||||||
&mock_notifier_) {}
|
&mock_notifier_) {}
|
||||||
|
|
||||||
MuxerListener::MediaRanges GetMediaRanges(
|
MuxerListener::MediaRanges GetMediaRanges(
|
||||||
|
@ -152,9 +157,12 @@ TEST_F(HlsNotifyMuxerListenerTest, OnMediaStart) {
|
||||||
std::shared_ptr<StreamInfo> video_stream_info =
|
std::shared_ptr<StreamInfo> video_stream_info =
|
||||||
CreateVideoStreamInfo(video_params);
|
CreateVideoStreamInfo(video_params);
|
||||||
|
|
||||||
EXPECT_CALL(mock_notifier_,
|
EXPECT_CALL(
|
||||||
NotifyNewStream(_, StrEq(kDefaultPlaylistName),
|
mock_notifier_,
|
||||||
StrEq("DEFAULTNAME"), StrEq("DEFAULTGROUPID"), _))
|
NotifyNewStream(Property(&MediaInfo::hls_characteristics,
|
||||||
|
ElementsAre(kCharactersticA, kCharactersticB)),
|
||||||
|
StrEq(kDefaultPlaylistName), StrEq("DEFAULTNAME"),
|
||||||
|
StrEq("DEFAULTGROUPID"), _))
|
||||||
.WillOnce(Return(true));
|
.WillOnce(Return(true));
|
||||||
|
|
||||||
MuxerOptions muxer_options;
|
MuxerOptions muxer_options;
|
||||||
|
@ -436,6 +444,7 @@ class HlsNotifyMuxerListenerKeyFrameTest : public TestWithParam<bool> {
|
||||||
GetParam(),
|
GetParam(),
|
||||||
kDefaultName,
|
kDefaultName,
|
||||||
kDefaultGroupId,
|
kDefaultGroupId,
|
||||||
|
std::vector<std::string>(), // no characteristics.
|
||||||
&mock_notifier_) {}
|
&mock_notifier_) {}
|
||||||
|
|
||||||
MockHlsNotifier mock_notifier_;
|
MockHlsNotifier mock_notifier_;
|
||||||
|
|
|
@ -44,26 +44,29 @@ std::list<std::unique_ptr<MuxerListener>> CreateHlsListenersInternal(
|
||||||
DCHECK(notifier);
|
DCHECK(notifier);
|
||||||
DCHECK_GE(stream_index, 0);
|
DCHECK_GE(stream_index, 0);
|
||||||
|
|
||||||
std::string group_id = stream.hls_group_id;
|
|
||||||
std::string name = stream.hls_name;
|
std::string name = stream.hls_name;
|
||||||
std::string hls_playlist_name = stream.hls_playlist_name;
|
std::string playlist_name = stream.hls_playlist_name;
|
||||||
std::string hls_iframe_playlist_name = stream.hls_iframe_playlist_name;
|
|
||||||
|
const std::string& group_id = stream.hls_group_id;
|
||||||
|
const std::string& iframe_playlist_name = stream.hls_iframe_playlist_name;
|
||||||
|
const std::vector<std::string>& characteristics = stream.hls_characteristics;
|
||||||
|
|
||||||
if (name.empty()) {
|
if (name.empty()) {
|
||||||
name = base::StringPrintf("stream_%d", stream_index);
|
name = base::StringPrintf("stream_%d", stream_index);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hls_playlist_name.empty()) {
|
if (playlist_name.empty()) {
|
||||||
hls_playlist_name = base::StringPrintf("stream_%d.m3u8", stream_index);
|
playlist_name = base::StringPrintf("stream_%d.m3u8", stream_index);
|
||||||
}
|
}
|
||||||
|
|
||||||
const bool kIFramesOnly = true;
|
const bool kIFramesOnly = true;
|
||||||
std::list<std::unique_ptr<MuxerListener>> listeners;
|
std::list<std::unique_ptr<MuxerListener>> listeners;
|
||||||
listeners.emplace_back(new HlsNotifyMuxerListener(
|
listeners.emplace_back(new HlsNotifyMuxerListener(
|
||||||
hls_playlist_name, !kIFramesOnly, name, group_id, notifier));
|
playlist_name, !kIFramesOnly, name, group_id, characteristics, notifier));
|
||||||
if (!hls_iframe_playlist_name.empty()) {
|
if (!iframe_playlist_name.empty()) {
|
||||||
listeners.emplace_back(new HlsNotifyMuxerListener(
|
listeners.emplace_back(new HlsNotifyMuxerListener(
|
||||||
hls_iframe_playlist_name, kIFramesOnly, name, group_id, notifier));
|
iframe_playlist_name, kIFramesOnly, name, group_id,
|
||||||
|
std::vector<std::string>(), notifier));
|
||||||
}
|
}
|
||||||
return listeners;
|
return listeners;
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
namespace shaka {
|
namespace shaka {
|
||||||
class MpdNotifier;
|
class MpdNotifier;
|
||||||
|
@ -44,6 +45,7 @@ class MuxerListenerFactory {
|
||||||
std::string hls_name;
|
std::string hls_name;
|
||||||
std::string hls_playlist_name;
|
std::string hls_playlist_name;
|
||||||
std::string hls_iframe_playlist_name;
|
std::string hls_iframe_playlist_name;
|
||||||
|
std::vector<std::string> hls_characteristics;
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Create a new muxer listener.
|
/// Create a new muxer listener.
|
||||||
|
|
|
@ -161,4 +161,7 @@ message MediaInfo {
|
||||||
optional string media_file_url = 17;
|
optional string media_file_url = 17;
|
||||||
optional string init_segment_url = 18;
|
optional string init_segment_url = 18;
|
||||||
optional string segment_template_url = 19;
|
optional string segment_template_url = 19;
|
||||||
|
|
||||||
|
// HLS only. Defines CHARACTERISTICS attribute of the stream.
|
||||||
|
repeated string hls_characteristics = 20;
|
||||||
}
|
}
|
||||||
|
|
|
@ -91,6 +91,7 @@ MuxerListenerFactory::StreamData ToMuxerListenerData(
|
||||||
data.hls_name = stream.hls_name;
|
data.hls_name = stream.hls_name;
|
||||||
data.hls_playlist_name = stream.hls_playlist_name;
|
data.hls_playlist_name = stream.hls_playlist_name;
|
||||||
data.hls_iframe_playlist_name = stream.hls_iframe_playlist_name;
|
data.hls_iframe_playlist_name = stream.hls_iframe_playlist_name;
|
||||||
|
data.hls_characteristics = stream.hls_characteristics;
|
||||||
return data;
|
return data;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -106,6 +106,7 @@ struct StreamDescriptor {
|
||||||
/// Optional value which contains a user-specified language tag. If specified,
|
/// Optional value which contains a user-specified language tag. If specified,
|
||||||
/// this value overrides any language metadata in the input stream.
|
/// this value overrides any language metadata in the input stream.
|
||||||
std::string language;
|
std::string language;
|
||||||
|
|
||||||
/// Required for audio when outputting HLS. It defines the name of the output
|
/// Required for audio when outputting HLS. It defines the name of the output
|
||||||
/// stream, which is not necessarily the same as output. This is used as the
|
/// stream, which is not necessarily the same as output. This is used as the
|
||||||
/// `NAME` attribute for EXT-X-MEDIA.
|
/// `NAME` attribute for EXT-X-MEDIA.
|
||||||
|
@ -119,6 +120,9 @@ struct StreamDescriptor {
|
||||||
/// Optional for HLS output. It defines the name of the I-Frames only playlist
|
/// Optional for HLS output. It defines the name of the I-Frames only playlist
|
||||||
/// for the stream. For Video only. Usually ends with `.m3u8`.
|
/// for the stream. For Video only. Usually ends with `.m3u8`.
|
||||||
std::string hls_iframe_playlist_name;
|
std::string hls_iframe_playlist_name;
|
||||||
|
/// Optional for HLS output. It defines the CHARACTERISTICS attribute of the
|
||||||
|
/// stream.
|
||||||
|
std::vector<std::string> hls_characteristics;
|
||||||
};
|
};
|
||||||
|
|
||||||
class SHAKA_EXPORT Packager {
|
class SHAKA_EXPORT Packager {
|
||||||
|
|
Loading…
Reference in New Issue