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 }