1 /*
2 * Copyright (c) 2022-2022 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15 #define HST_LOG_TAG "M3U8"
16
17 #include <algorithm>
18 #include <utility>
19 #include "foundation/log.h"
20 #include "m3u8.h"
21
22 namespace OHOS {
23 namespace Media {
24 namespace Plugin {
25 namespace HttpPlugin {
26 namespace {
StrHasPrefix(std::string & str,const std::string & prefix)27 bool StrHasPrefix(std::string& str, const std::string& prefix)
28 {
29 return str.find(prefix) == 0;
30 }
31
UriJoin(std::string & baseUrl,const std::string & uri)32 std::string UriJoin(std::string& baseUrl, const std::string& uri)
33 {
34 if ((uri.find("http://") != std::string::npos) || (uri.find("https://") != std::string::npos)) {
35 return uri;
36 } else if (uri.find("//") == 0) { // start with "//"
37 return baseUrl.substr(0, baseUrl.find('/')) + uri;
38 } else {
39 std::string::size_type pos = baseUrl.rfind('/');
40 return baseUrl.substr(0, pos + 1) + uri;
41 }
42 }
43 }
44
M3U8Fragment(std::string uri,std::string title,double duration,int sequence,bool discont)45 M3U8Fragment::M3U8Fragment(std::string uri, std::string title, double duration, int sequence, bool discont)
46 : uri_(std::move(uri)), title_(std::move(title)), duration_(duration), sequence_(sequence), discont_(discont)
47 {
48 }
49
M3U8(std::string uri,std::string name)50 M3U8::M3U8(std::string uri, std::string name) : uri_(std::move(uri)), name_(std::move(name))
51 {
52 InitTagUpdatersMap();
53 }
54
Update(std::string & playList)55 bool M3U8::Update(std::string& playList)
56 {
57 if (playList_ == playList) {
58 MEDIA_LOG_I("playlist does not change ");
59 return true;
60 }
61 if (!StrHasPrefix(playList, "#EXTM3U")) {
62 MEDIA_LOG_I("playlist doesn't start with #EXTM3U " PUBLIC_LOG_S, playList.c_str());
63 return false;
64 }
65 if (playList.find("\n#EXT-X-STREAM-INF:") != std::string::npos) {
66 MEDIA_LOG_I("Not a media playlist, but a master playlist! " PUBLIC_LOG_S, playList.c_str());
67 return false;
68 }
69 files_.clear();
70 MEDIA_LOG_I("media playlist " PUBLIC_LOG_S, playList.c_str());
71 auto tags = ParseEntries(playList);
72 UpdateFromTags(tags);
73 tags.clear();
74 playList_ = playList;
75 return true;
76 }
77
InitTagUpdatersMap()78 void M3U8::InitTagUpdatersMap()
79 {
80 tagUpdatersMap_[HlsTag::EXTXPLAYLISTTYPE] = [this] (std::shared_ptr<Tag>& tag, M3U8Info& info) {
81 bLive_ = !info.bVod && (std::static_pointer_cast<SingleValueTag>(tag)->GetValue().QuotedString() != "VOD");
82 };
83
84 tagUpdatersMap_[HlsTag::EXTXTARGETDURATION] = [this] (std::shared_ptr<Tag>& tag, M3U8Info& info) {
85 std::ignore = info;
86 targetDuration_ = std::static_pointer_cast<SingleValueTag>(tag)->GetValue().FloatingPoint();
87 };
88
89 tagUpdatersMap_[HlsTag::EXTXMEDIASEQUENCE] = [this] (std::shared_ptr<Tag>& tag, M3U8Info& info) {
90 std::ignore = info;
91 sequence_ = std::static_pointer_cast<SingleValueTag>(tag)->GetValue().Decimal();
92 };
93
94 tagUpdatersMap_[HlsTag::EXTXDISCONTINUITYSEQUENCE] = [this](std::shared_ptr<Tag> &tag, M3U8Info &info) {
95 discontSequence_ = static_cast<int>(std::static_pointer_cast<SingleValueTag>(tag)->GetValue().Decimal());
96 info.discontinuity = true;
97 };
98
99 tagUpdatersMap_[HlsTag::EXTINF] = [this](std::shared_ptr<Tag> &tag, M3U8Info &info) {
100 GetExtInf(tag, info.duration, info.title);
101 };
102
103 tagUpdatersMap_[HlsTag::URI] = [this](std::shared_ptr<Tag> &tag, M3U8Info &info) {
104 info.uri = UriJoin(uri_, std::static_pointer_cast<SingleValueTag>(tag)->GetValue().QuotedString());
105 };
106
107 tagUpdatersMap_[HlsTag::EXTXBYTERANGE] = [](std::shared_ptr<Tag> &tag, M3U8Info &info) {
108 std::ignore = tag;
109 std::ignore = info;
110 MEDIA_LOG_I("need to parse EXTXBYTERANGE");
111 };
112
113 tagUpdatersMap_[HlsTag::EXTXDISCONTINUITY] = [this](std::shared_ptr<Tag> &tag, M3U8Info &info) {
114 std::ignore = tag;
115 discontSequence_++;
116 info.discontinuity = true;
117 };
118
119 tagUpdatersMap_[HlsTag::EXTXKEY] = [](std::shared_ptr<Tag> &tag, M3U8Info &info) {
120 std::ignore = tag;
121 std::ignore = info;
122 MEDIA_LOG_I("need to parse EXTXKEY");
123 };
124
125 tagUpdatersMap_[HlsTag::EXTXMAP] = [](std::shared_ptr<Tag> &tag, M3U8Info &info) {
126 std::ignore = tag;
127 std::ignore = info;
128 MEDIA_LOG_I("need to parse EXTXMAP");
129 };
130 }
131
UpdateFromTags(std::list<std::shared_ptr<Tag>> & tags)132 void M3U8::UpdateFromTags(std::list<std::shared_ptr<Tag>>& tags)
133 {
134 M3U8Info info;
135 info.bVod = !tags.empty() && tags.back()->GetType() == HlsTag::EXTXENDLIST;
136 bLive_ = !info.bVod;
137 for (auto& tag : tags) {
138 HlsTag hlsTag = tag->GetType();
139 auto iter = tagUpdatersMap_.find(hlsTag);
140 if (iter != tagUpdatersMap_.end()) {
141 auto updater = iter->second;
142 updater(tag, info);
143 }
144
145 if (!info.uri.empty()) {
146 files_.emplace_back(std::make_shared<M3U8Fragment>(info.uri, info.title, info.duration, sequence_++,
147 info.discontinuity));
148 info.uri = "", info.title = "", info.duration = 0, info.discontinuity = false;
149 }
150 }
151 }
152
GetExtInf(const std::shared_ptr<Tag> & tag,double & duration,std::string & title) const153 void M3U8::GetExtInf(const std::shared_ptr<Tag>& tag, double& duration, std::string& title) const
154 {
155 auto item = std::static_pointer_cast<ValuesListTag>(tag);
156 duration = item ->GetAttributeByName("DURATION")->FloatingPoint();
157 title = item ->GetAttributeByName("TITLE")->QuotedString();
158 }
159
GetDuration() const160 double M3U8::GetDuration() const
161 {
162 double duration = 0;
163 for (auto file : files_) {
164 duration += file->duration_;
165 }
166 return duration;
167 }
168
IsLive() const169 bool M3U8::IsLive() const
170 {
171 return bLive_;
172 }
173
M3U8VariantStream(std::string name,std::string uri,std::shared_ptr<M3U8> m3u8)174 M3U8VariantStream::M3U8VariantStream(std::string name, std::string uri, std::shared_ptr<M3U8> m3u8)
175 : name_(std::move(name)), uri_(std::move(uri)), m3u8_(std::move(m3u8))
176 {
177 }
178
M3U8MasterPlaylist(std::string & playList,const std::string & uri)179 M3U8MasterPlaylist::M3U8MasterPlaylist(std::string& playList, const std::string& uri)
180 {
181 playList_ = playList;
182 uri_ = uri;
183 if (!StrHasPrefix(playList_, "#EXTM3U")) {
184 MEDIA_LOG_I("playlist doesn't start with #EXTM3U " PUBLIC_LOG_S, uri.c_str());
185 }
186 if (playList_.find("\n#EXTINF:") != std::string::npos) {
187 UpdateMediaPlaylist();
188 } else {
189 UpdateMasterPlaylist();
190 }
191 }
192
UpdateMediaPlaylist()193 void M3U8MasterPlaylist::UpdateMediaPlaylist()
194 {
195 MEDIA_LOG_I("This is a simple media playlist, not a master playlist " PUBLIC_LOG_S, uri_.c_str());
196 isSimple_ = true;
197 auto m3u8 = std::make_shared<M3U8>(uri_, "");
198 auto stream = std::make_shared<M3U8VariantStream>(uri_, uri_, m3u8);
199 variants_.emplace_back(stream);
200 defaultVariant_ = stream;
201 m3u8->Update(playList_);
202 duration_ = m3u8->GetDuration();
203 bLive_ = m3u8->IsLive();
204 MEDIA_LOG_D("UpdateMediaPlaylist called, duration_ = " PUBLIC_LOG_F, duration_);
205 }
206
UpdateMasterPlaylist()207 void M3U8MasterPlaylist::UpdateMasterPlaylist()
208 {
209 MEDIA_LOG_I("master playlist " PUBLIC_LOG_S, playList_.c_str());
210 auto tags = ParseEntries(playList_);
211 std::for_each(tags.begin(), tags.end(), [this] (std::shared_ptr<Tag>& tag) {
212 switch (tag->GetType()) {
213 case HlsTag::EXTXSTREAMINF:
214 case HlsTag::EXTXIFRAMESTREAMINF: {
215 auto item = std::static_pointer_cast<AttributesTag>(tag);
216 auto uriAttribute = item->GetAttributeByName("URI");
217 if (uriAttribute) {
218 auto name = uriAttribute->QuotedString();
219 auto uri = UriJoin(uri_, name);
220 auto stream = std::make_shared<M3U8VariantStream>(name, uri, std::make_shared<M3U8>(uri, name));
221 if (tag->GetType() == HlsTag::EXTXIFRAMESTREAMINF) {
222 stream->iframe_ = true;
223 }
224 auto bandWidthAttribute = item->GetAttributeByName("BANDWIDTH");
225 if (bandWidthAttribute) {
226 stream->bandWidth_ = bandWidthAttribute->Decimal();
227 }
228 auto resolutionAttribute = item->GetAttributeByName("RESOLUTION");
229 if (resolutionAttribute) {
230 stream->width_ = resolutionAttribute->GetResolution().first;
231 stream->height_ = resolutionAttribute->GetResolution().second;
232 }
233 variants_.emplace_back(stream);
234 if (defaultVariant_ == nullptr) {
235 defaultVariant_ = stream;
236 }
237 }
238 break;
239 }
240 default:
241 break;
242 }
243 });
244 tags.clear();
245 }
246 }
247 }
248 }
249 }