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
|
||||
only be set for video streams. If unspecified, no I-Frames only playlist is
|
||||
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"
|
||||
" to create. Usually ends with '.m3u8', and is relative to\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.
|
||||
const char kDrmLabelLabel[] = "label";
|
||||
|
|
|
@ -30,6 +30,7 @@ enum FieldType {
|
|||
kTrickPlayFactorField,
|
||||
kSkipEncryptionField,
|
||||
kDrmStreamLabelField,
|
||||
kHlsCharacteristicsField,
|
||||
};
|
||||
|
||||
struct FieldNameToTypeMapping {
|
||||
|
@ -63,6 +64,9 @@ const FieldNameToTypeMapping kFieldNameTypeMappings[] = {
|
|||
{"skip_encryption", kSkipEncryptionField},
|
||||
{"drm_stream_label", kDrmStreamLabelField},
|
||||
{"drm_label", kDrmStreamLabelField},
|
||||
{"hls_characteristics", kHlsCharacteristicsField},
|
||||
{"characteristics", kHlsCharacteristicsField},
|
||||
{"charcs", kHlsCharacteristicsField},
|
||||
};
|
||||
|
||||
FieldType GetFieldType(const std::string& field_name) {
|
||||
|
@ -164,10 +168,14 @@ base::Optional<StreamDescriptor> ParseStreamDescriptor(
|
|||
descriptor.skip_encryption = skip_encryption_value > 0;
|
||||
break;
|
||||
}
|
||||
case kDrmStreamLabelField: {
|
||||
case kDrmStreamLabelField:
|
||||
descriptor.drm_label = iter->second;
|
||||
break;
|
||||
}
|
||||
case kHlsCharacteristicsField:
|
||||
descriptor.hls_characteristics =
|
||||
base::SplitString(iter->second, ";:", base::TRIM_WHITESPACE,
|
||||
base::SPLIT_WANT_NONEMPTY);
|
||||
break;
|
||||
default:
|
||||
LOG(ERROR) << "Unknown field in stream descriptor (\"" << iter->first
|
||||
<< "\").";
|
||||
|
|
|
@ -271,6 +271,7 @@ class PackagerAppTest(unittest.TestCase):
|
|||
segmented=False,
|
||||
using_time_specifier=False,
|
||||
hls=False,
|
||||
hls_characteristics=None,
|
||||
trick_play_factor=None,
|
||||
drm_label=None,
|
||||
skip_encryption=None,
|
||||
|
@ -290,23 +291,24 @@ class PackagerAppTest(unittest.TestCase):
|
|||
language: The language override for the input stream.
|
||||
output_file_prefix: The output file prefix. Default to empty if not
|
||||
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
|
||||
the output extensions and manifests.
|
||||
using_time_specifier: Use $Time$ in segment name instead of using
|
||||
$Number$. This flag is only relevant if segmented is True.
|
||||
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
|
||||
stream and which key frames to use. A trick play factor of 0 is the
|
||||
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.
|
||||
bandwidth: The expected bandwidth value that should be listed in the
|
||||
manifest.
|
||||
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.
|
||||
test_file: Specify the input file to use. If the input file is not
|
||||
specify, a default file will be used.
|
||||
test_file: The input file to use. If the input file is not specified, a
|
||||
default file will be used.
|
||||
|
||||
|
||||
Returns:
|
||||
|
@ -348,6 +350,9 @@ class PackagerAppTest(unittest.TestCase):
|
|||
stream.Append('iframe_playlist_name',
|
||||
output_file_name_base + '-iframe.m3u8')
|
||||
|
||||
if hls_characteristics:
|
||||
stream.Append('hls_characteristics', hls_characteristics)
|
||||
|
||||
requires_init_segment = segmented and base_ext not in [
|
||||
'aac', 'ac3', 'ec3', 'ts', 'vtt'
|
||||
]
|
||||
|
@ -1503,7 +1508,11 @@ class PackagerFunctionalTest(PackagerAppTest):
|
|||
streams = self._GetStreams(
|
||||
['audio', 'video'], output_format='ts', segmented=True)
|
||||
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)
|
||||
|
||||
|
|
|
@ -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=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"
|
||||
stream_2.m3u8
|
||||
|
|
|
@ -287,6 +287,12 @@ void BuildMediaTag(const MediaPlaylist& playlist,
|
|||
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 =
|
||||
MediaPlaylist::MediaPlaylistStreamType::kAudio;
|
||||
if (playlist.stream_type() == kAudio) {
|
||||
|
|
|
@ -399,6 +399,42 @@ TEST_F(MasterPlaylistTest, WriteMasterPlaylistVideosAndTexts) {
|
|||
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) {
|
||||
// Video, sd.m3u8.
|
||||
std::unique_ptr<MockMediaPlaylist> video =
|
||||
|
|
|
@ -348,6 +348,11 @@ void MediaPlaylist::SetLanguageForTesting(const std::string& language) {
|
|||
language_ = language;
|
||||
}
|
||||
|
||||
void MediaPlaylist::SetCharacteristicsForTesting(
|
||||
const std::vector<std::string>& characteristics) {
|
||||
characteristics_ = characteristics;
|
||||
}
|
||||
|
||||
bool MediaPlaylist::SetMediaInfo(const MediaInfo& media_info) {
|
||||
const uint32_t time_scale = GetTimeScale(media_info);
|
||||
if (time_scale == 0) {
|
||||
|
@ -370,6 +375,9 @@ bool MediaPlaylist::SetMediaInfo(const MediaInfo& media_info) {
|
|||
media_info_ = media_info;
|
||||
language_ = GetLanguage(media_info);
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
#include <list>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "packager/base/macros.h"
|
||||
#include "packager/hls/public/hls_params.h"
|
||||
|
@ -87,6 +88,10 @@ class MediaPlaylist {
|
|||
/// For testing only.
|
||||
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.
|
||||
/// @param media_info is the info of the segments that are going to be added
|
||||
/// to this playlist.
|
||||
|
@ -182,7 +187,11 @@ class MediaPlaylist {
|
|||
|
||||
/// @return the language of the media, as an ISO language tag in its shortest
|
||||
/// 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:
|
||||
// Add a SegmentInfoEntry (#EXTINF).
|
||||
|
@ -213,6 +222,7 @@ class MediaPlaylist {
|
|||
bool use_byte_range_ = false;
|
||||
std::string codec_;
|
||||
std::string language_;
|
||||
std::vector<std::string> characteristics_;
|
||||
int media_sequence_number_ = 0;
|
||||
bool inserted_discontinuity_tag_ = false;
|
||||
int discontinuity_sequence_number_ = 0;
|
||||
|
|
|
@ -18,6 +18,7 @@ namespace shaka {
|
|||
namespace hls {
|
||||
|
||||
using ::testing::_;
|
||||
using ::testing::ElementsAreArray;
|
||||
using ::testing::ReturnArg;
|
||||
|
||||
namespace {
|
||||
|
@ -483,6 +484,20 @@ TEST_F(MediaPlaylistMultiSegmentTest, 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) {
|
||||
valid_video_media_info_.set_reference_time_scale(90000);
|
||||
valid_video_media_info_.set_init_segment_url("init_segment.mp4");
|
||||
|
|
|
@ -21,11 +21,13 @@ HlsNotifyMuxerListener::HlsNotifyMuxerListener(
|
|||
bool iframes_only,
|
||||
const std::string& ext_x_media_name,
|
||||
const std::string& ext_x_media_group_id,
|
||||
const std::vector<std::string>& characteristics,
|
||||
hls::HlsNotifier* hls_notifier)
|
||||
: playlist_name_(playlist_name),
|
||||
iframes_only_(iframes_only),
|
||||
ext_x_media_name_(ext_x_media_name),
|
||||
ext_x_media_group_id_(ext_x_media_group_id),
|
||||
characteristics_(characteristics),
|
||||
hls_notifier_(hls_notifier) {
|
||||
DCHECK(hls_notifier);
|
||||
}
|
||||
|
@ -90,6 +92,10 @@ void HlsNotifyMuxerListener::OnMediaStart(const MuxerOptions& muxer_options,
|
|||
LOG(ERROR) << "Failed to generate MediaInfo from input.";
|
||||
return;
|
||||
}
|
||||
if (!characteristics_.empty()) {
|
||||
for (const std::string& characteristic : characteristics_)
|
||||
media_info->add_hls_characteristics(characteristic);
|
||||
}
|
||||
if (protection_scheme_ != FOURCC_NULL) {
|
||||
internal::SetContentProtectionFields(protection_scheme_, next_key_id_,
|
||||
next_key_system_infos_,
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "packager/base/optional.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
|
||||
/// value of GROUP-ID attribute for EXT-X-MEDIA. This may be empty for
|
||||
/// 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.
|
||||
HlsNotifyMuxerListener(const std::string& playlist_name,
|
||||
bool iframes_only,
|
||||
const std::string& ext_x_media_name,
|
||||
const std::string& ext_x_media_group_id,
|
||||
const std::vector<std::string>& characteristics,
|
||||
hls::HlsNotifier* hls_notifier);
|
||||
~HlsNotifyMuxerListener() override;
|
||||
|
||||
|
@ -77,6 +82,7 @@ class HlsNotifyMuxerListener : public MuxerListener {
|
|||
const bool iframes_only_;
|
||||
const std::string ext_x_media_name_;
|
||||
const std::string ext_x_media_group_id_;
|
||||
const std::vector<std::string> characteristics_;
|
||||
hls::HlsNotifier* const hls_notifier_;
|
||||
base::Optional<uint32_t> stream_id_;
|
||||
|
||||
|
|
|
@ -18,7 +18,9 @@ namespace media {
|
|||
|
||||
using ::testing::_;
|
||||
using ::testing::Bool;
|
||||
using ::testing::ElementsAre;
|
||||
using ::testing::InSequence;
|
||||
using ::testing::Property;
|
||||
using ::testing::Return;
|
||||
using ::testing::StrEq;
|
||||
using ::testing::TestWithParam;
|
||||
|
@ -92,6 +94,8 @@ const bool kIFramesOnlyPlaylist = true;
|
|||
const char kDefaultPlaylistName[] = "default_playlist.m3u8";
|
||||
const char kDefaultName[] = "DEFAULTNAME";
|
||||
const char kDefaultGroupId[] = "DEFAULTGROUPID";
|
||||
const char kCharactersticA[] = "public.accessibility.transcribes-spoken-dialog";
|
||||
const char kCharactersticB[] = "public.easy-to-read";
|
||||
|
||||
MATCHER_P(HasEncryptionScheme, expected_scheme, "") {
|
||||
*result_listener << "it has_protected_content: "
|
||||
|
@ -113,6 +117,7 @@ class HlsNotifyMuxerListenerTest : public ::testing::Test {
|
|||
!kIFramesOnlyPlaylist,
|
||||
kDefaultName,
|
||||
kDefaultGroupId,
|
||||
std::vector<std::string>{kCharactersticA, kCharactersticB},
|
||||
&mock_notifier_) {}
|
||||
|
||||
MuxerListener::MediaRanges GetMediaRanges(
|
||||
|
@ -152,9 +157,12 @@ TEST_F(HlsNotifyMuxerListenerTest, OnMediaStart) {
|
|||
std::shared_ptr<StreamInfo> video_stream_info =
|
||||
CreateVideoStreamInfo(video_params);
|
||||
|
||||
EXPECT_CALL(mock_notifier_,
|
||||
NotifyNewStream(_, StrEq(kDefaultPlaylistName),
|
||||
StrEq("DEFAULTNAME"), StrEq("DEFAULTGROUPID"), _))
|
||||
EXPECT_CALL(
|
||||
mock_notifier_,
|
||||
NotifyNewStream(Property(&MediaInfo::hls_characteristics,
|
||||
ElementsAre(kCharactersticA, kCharactersticB)),
|
||||
StrEq(kDefaultPlaylistName), StrEq("DEFAULTNAME"),
|
||||
StrEq("DEFAULTGROUPID"), _))
|
||||
.WillOnce(Return(true));
|
||||
|
||||
MuxerOptions muxer_options;
|
||||
|
@ -436,6 +444,7 @@ class HlsNotifyMuxerListenerKeyFrameTest : public TestWithParam<bool> {
|
|||
GetParam(),
|
||||
kDefaultName,
|
||||
kDefaultGroupId,
|
||||
std::vector<std::string>(), // no characteristics.
|
||||
&mock_notifier_) {}
|
||||
|
||||
MockHlsNotifier mock_notifier_;
|
||||
|
|
|
@ -44,26 +44,29 @@ std::list<std::unique_ptr<MuxerListener>> CreateHlsListenersInternal(
|
|||
DCHECK(notifier);
|
||||
DCHECK_GE(stream_index, 0);
|
||||
|
||||
std::string group_id = stream.hls_group_id;
|
||||
std::string name = stream.hls_name;
|
||||
std::string hls_playlist_name = stream.hls_playlist_name;
|
||||
std::string hls_iframe_playlist_name = stream.hls_iframe_playlist_name;
|
||||
std::string playlist_name = stream.hls_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()) {
|
||||
name = base::StringPrintf("stream_%d", stream_index);
|
||||
}
|
||||
|
||||
if (hls_playlist_name.empty()) {
|
||||
hls_playlist_name = base::StringPrintf("stream_%d.m3u8", stream_index);
|
||||
if (playlist_name.empty()) {
|
||||
playlist_name = base::StringPrintf("stream_%d.m3u8", stream_index);
|
||||
}
|
||||
|
||||
const bool kIFramesOnly = true;
|
||||
std::list<std::unique_ptr<MuxerListener>> listeners;
|
||||
listeners.emplace_back(new HlsNotifyMuxerListener(
|
||||
hls_playlist_name, !kIFramesOnly, name, group_id, notifier));
|
||||
if (!hls_iframe_playlist_name.empty()) {
|
||||
playlist_name, !kIFramesOnly, name, group_id, characteristics, notifier));
|
||||
if (!iframe_playlist_name.empty()) {
|
||||
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;
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace shaka {
|
||||
class MpdNotifier;
|
||||
|
@ -44,6 +45,7 @@ class MuxerListenerFactory {
|
|||
std::string hls_name;
|
||||
std::string hls_playlist_name;
|
||||
std::string hls_iframe_playlist_name;
|
||||
std::vector<std::string> hls_characteristics;
|
||||
};
|
||||
|
||||
/// Create a new muxer listener.
|
||||
|
|
|
@ -161,4 +161,7 @@ message MediaInfo {
|
|||
optional string media_file_url = 17;
|
||||
optional string init_segment_url = 18;
|
||||
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_playlist_name = stream.hls_playlist_name;
|
||||
data.hls_iframe_playlist_name = stream.hls_iframe_playlist_name;
|
||||
data.hls_characteristics = stream.hls_characteristics;
|
||||
return data;
|
||||
};
|
||||
|
||||
|
|
|
@ -106,6 +106,7 @@ struct StreamDescriptor {
|
|||
/// Optional value which contains a user-specified language tag. If specified,
|
||||
/// this value overrides any language metadata in the input stream.
|
||||
std::string language;
|
||||
|
||||
/// 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
|
||||
/// `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
|
||||
/// for the stream. For Video only. Usually ends with `.m3u8`.
|
||||
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 {
|
||||
|
|
Loading…
Reference in New Issue