1 /*
2 * Copyright (c) 2024-2024 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 "HlsPlayListDownloader"
16 #include <mutex>
17 #include "plugin/plugin_time.h"
18 #include "hls_playlist_downloader.h"
19 #include "common/media_source.h"
20 #include <unistd.h>
21
22 namespace OHOS {
23 namespace Media {
24 namespace Plugins {
25 namespace HttpPlugin {
26 namespace {
27 constexpr unsigned int SLEEP_TIME = 1;
28 constexpr size_t RETRY_TIMES = 1000;
29 constexpr int MIN_PRE_PARSE_NUM = 2; // at least 2 ts frag
30 const std::string M3U8_START_TAG = "#EXTM3U";
31 const std::string M3U8_END_TAG = "#EXT-X-ENDLIST";
32 const std::string M3U8_TS_TAG = "#EXTINF";
33 }
34 // StateMachine thread: call plugin SetSource -> call Open
35 // StateMachine thread: call plugin GetSeekable -> call GetSeekable
36 // PlayListDownload thread: call ParseManifest
37 // First call Open, then start PlayListDownload thread, it seems no lock is required.
38 // [In future] StateMachine thread: call plugin GetDuration -> call GetDuration
Open(const std::string & url,const std::map<std::string,std::string> & httpHeader)39 void HlsPlayListDownloader::Open(const std::string& url, const std::map<std::string, std::string>& httpHeader)
40 {
41 url_ = url;
42 master_ = nullptr;
43 SaveHttpHeader(httpHeader);
44 if (mimeType_ == AVMimeTypes::APPLICATION_M3U8) {
45 DoOpenNative(url_);
46 } else {
47 DoOpen(url_);
48 }
49 }
50
~HlsPlayListDownloader()51 HlsPlayListDownloader::~HlsPlayListDownloader()
52 {
53 MEDIA_LOG_I("~HlsPlayListDownloader in");
54 if (updateTask_ != nullptr) {
55 updateTask_->Stop();
56 }
57 if (downloader_ != nullptr) {
58 downloader_->Stop(false);
59 }
60 MEDIA_LOG_I("~HlsPlayListDownloader out");
61 }
62
UpdateManifest()63 void HlsPlayListDownloader::UpdateManifest()
64 {
65 if (currentVariant_ && currentVariant_->m3u8_ && !currentVariant_->m3u8_->uri_.empty()) {
66 isNotifyPlayListFinished_ = false;
67 DoOpen(currentVariant_->m3u8_->uri_);
68 } else {
69 MEDIA_LOG_E("UpdateManifest currentVariant_ not ready.");
70 }
71 }
72
SetPlayListCallback(PlayListChangeCallback * callback)73 void HlsPlayListDownloader::SetPlayListCallback(PlayListChangeCallback* callback)
74 {
75 callback_ = callback;
76 }
77
IsParseAndNotifyFinished()78 bool HlsPlayListDownloader::IsParseAndNotifyFinished()
79 {
80 return isParseFinished_ && isNotifyPlayListFinished_;
81 }
82
IsParseFinished()83 bool HlsPlayListDownloader::IsParseFinished()
84 {
85 return isParseFinished_;
86 }
87
GetDuration() const88 int64_t HlsPlayListDownloader::GetDuration() const
89 {
90 if (!master_) {
91 return 0;
92 }
93 return master_->bLive_ ? -1 : ((int64_t)(master_->duration_ * HST_SECOND) / HST_NSECOND); // -1
94 }
95
GetSeekable() const96 Seekable HlsPlayListDownloader::GetSeekable() const
97 {
98 // need wait master_ not null
99 size_t times = 0;
100 while (times < RETRY_TIMES && !isInterruptNeeded_) {
101 if (master_ && master_->isSimple_ && isParseFinished_) {
102 break;
103 }
104 OSAL::SleepFor(SLEEP_TIME); // 1 ms
105 times++;
106 }
107 if (times >= RETRY_TIMES || isInterruptNeeded_) {
108 return Seekable::INVALID;
109 }
110 return master_->bLive_ ? Seekable::UNSEEKABLE : Seekable::SEEKABLE;
111 }
112
NotifyListChange()113 void HlsPlayListDownloader::NotifyListChange()
114 {
115 if (currentVariant_ == nullptr || callback_ == nullptr) {
116 return;
117 }
118 if (currentVariant_->m3u8_ == nullptr) {
119 return;
120 }
121 auto files = currentVariant_->m3u8_->files_;
122 auto playList = std::vector<PlayInfo>();
123 if (currentVariant_->m3u8_->isDecryptAble_) {
124 while (!currentVariant_->m3u8_->isDecryptKeyReady_) {
125 Task::SleepInTask(10); // sleep 10ms
126 }
127 callback_->OnSourceKeyChange(currentVariant_->m3u8_->key_, currentVariant_->m3u8_->keyLen_,
128 currentVariant_->m3u8_->iv_);
129 } else {
130 MEDIA_LOG_E("Decrypkey is not needed.");
131 if (master_ != nullptr) {
132 callback_->OnSourceKeyChange(master_->key_, master_->keyLen_, master_->iv_);
133 } else {
134 callback_->OnSourceKeyChange(nullptr, 0, nullptr);
135 }
136 }
137 playList.reserve(files.size());
138 for (const auto &file: files) {
139 PlayInfo palyInfo;
140 palyInfo.url_ = file->uri_;
141 palyInfo.duration_ = file->duration_;
142 playList.push_back(palyInfo);
143 }
144 if (!currentVariant_->m3u8_->localDrmInfos_.empty()) {
145 callback_->OnDrmInfoChanged(currentVariant_->m3u8_->localDrmInfos_);
146 }
147 callback_->OnPlayListChanged(playList);
148 if (isParseFinished_) {
149 isNotifyPlayListFinished_ = true;
150 if (master_->bLive_ && !updateTask_->IsTaskRunning() && !isLiveUpdateTaskStarted_) {
151 isLiveUpdateTaskStarted_ = true;
152 updateTask_->Start();
153 }
154 }
155 }
156
ParseManifest(const std::string & location,bool isPreParse)157 void HlsPlayListDownloader::ParseManifest(const std::string& location, bool isPreParse)
158 {
159 if (!location.empty()) {
160 url_ = location;
161 }
162 if (!master_) {
163 master_ = std::make_shared<M3U8MasterPlaylist>(playList_, url_, initResolution_, httpHeader_);
164 currentVariant_ = master_->defaultVariant_;
165 if (currentVariant_ && currentVariant_->m3u8_) {
166 currentVariant_->m3u8_->httpHeader_ = httpHeader_;
167 }
168 if (!master_->isSimple_) {
169 UpdateManifest();
170 } else {
171 // need notify , avoid delay 5s
172 isParseFinished_ = isPreParse ? false : true;
173 NotifyListChange();
174 }
175 } else {
176 UpdateMasterAndNotifyList(isPreParse);
177 }
178 if (!master_->isParseSuccess_ && eventCallback_ != nullptr) {
179 MEDIA_LOG_E("ParseManifest parse failed.");
180 eventCallback_->OnEvent({PluginEventType::CLIENT_ERROR,
181 {NetworkClientErrorCode::ERROR_TIME_OUT}, "parse m3u8"});
182 }
183 }
184
UpdateMasterAndNotifyList(bool isPreParse)185 void HlsPlayListDownloader::UpdateMasterAndNotifyList(bool isPreParse)
186 {
187 bool ret = false;
188 if (!master_->isSimple_) {
189 currentVariant_ = master_->defaultVariant_;
190 if (currentVariant_ && currentVariant_->m3u8_) {
191 ret = currentVariant_->m3u8_->Update(playList_, true);
192 }
193 }
194 if (currentVariant_ && currentVariant_->m3u8_) {
195 currentVariant_->m3u8_->httpHeader_ = httpHeader_;
196 }
197 if (master_->isSimple_ && currentVariant_ && currentVariant_->m3u8_) {
198 ret = currentVariant_->m3u8_->Update(playList_, isParseFinished_);
199 master_->isParseSuccess_ = ret;
200 }
201 if (ret) {
202 UpdateMasterInfo(isPreParse);
203 if (!master_->isSimple_) {
204 master_->isSimple_ = true;
205 }
206 NotifyListChange();
207 }
208 }
209
UpdateMasterInfo(bool isPreParse)210 void HlsPlayListDownloader::UpdateMasterInfo(bool isPreParse)
211 {
212 master_->bLive_ = currentVariant_->m3u8_->IsLive();
213 master_->duration_ = currentVariant_->m3u8_->GetDuration();
214 master_->segmentOffsets_ = currentVariant_->m3u8_->segmentOffsets_;
215 master_->hasDiscontinuity_ = currentVariant_->m3u8_->hasDiscontinuity_;
216 isParseFinished_ = isPreParse ? false : true;
217 }
218
PreParseManifest(const std::string & location)219 void HlsPlayListDownloader::PreParseManifest(const std::string& location)
220 {
221 if (master_ && master_->isSimple_) {
222 return;
223 }
224 if (playList_.find(M3U8_START_TAG) == std::string::npos ||
225 playList_.find(M3U8_END_TAG) != std::string::npos) {
226 return;
227 }
228 int tsNum = 0;
229 int tsIndex = 0;
230 int firstTsTagIndex = 0;
231 int lastTsTagIndex = 0;
232 std::string tsTag = M3U8_TS_TAG;
233 int tsTagSize = static_cast<int>(tsTag.size());
234 while ((tsIndex = static_cast<int>(playList_.find(tsTag, tsIndex))) <
235 static_cast<int>(playList_.length()) && tsIndex != -1) { // -1
236 if (tsNum == 0) {
237 firstTsTagIndex = tsIndex;
238 }
239 tsNum++;
240 lastTsTagIndex = tsIndex;
241 if (tsNum >= MIN_PRE_PARSE_NUM) {
242 break;
243 }
244 tsIndex += tsTagSize;
245 }
246 if (tsNum < MIN_PRE_PARSE_NUM) {
247 return;
248 }
249 std::string backUpPlayList = playList_;
250 playList_ = playList_.substr(0, lastTsTagIndex).append(tsTag);
251 isParseFinished_ = false;
252 ParseManifest(location, true);
253 playList_ = backUpPlayList.erase(firstTsTagIndex, lastTsTagIndex - firstTsTagIndex);
254 }
255
SelectBitRate(uint32_t bitRate)256 void HlsPlayListDownloader::SelectBitRate(uint32_t bitRate)
257 {
258 if (newVariant_ == nullptr) {
259 return;
260 }
261 currentVariant_ = newVariant_;
262 MEDIA_LOG_I("SelectBitRate currentVariant_ " PUBLIC_LOG_U64, currentVariant_->bandWidth_);
263 }
264
IsBitrateSame(uint32_t bitRate)265 bool HlsPlayListDownloader::IsBitrateSame(uint32_t bitRate)
266 {
267 uint32_t maxGap = 0;
268 bool isFirstSelect = true;
269 for (const auto &item : master_->variants_) {
270 uint32_t tempGap = (item->bandWidth_ > bitRate) ? (item->bandWidth_ - bitRate) : (bitRate - item->bandWidth_);
271 if (isFirstSelect || (tempGap < maxGap)) {
272 isFirstSelect = false;
273 maxGap = tempGap;
274 newVariant_ = item;
275 }
276 }
277 if (currentVariant_ != nullptr && newVariant_->bandWidth_ == currentVariant_->bandWidth_) {
278 return true;
279 }
280 return false;
281 }
282
GetBitRates()283 std::vector<uint32_t> HlsPlayListDownloader::GetBitRates()
284 {
285 std::vector<uint32_t> bitRates;
286 for (const auto &item : master_->variants_) {
287 if (item->bandWidth_) {
288 bitRates.push_back(item->bandWidth_);
289 }
290 }
291 return bitRates;
292 }
293
GetCurBitrate()294 uint32_t HlsPlayListDownloader::GetCurBitrate()
295 {
296 if (currentVariant_==nullptr) {
297 return 0;
298 }
299 return currentVariant_->bandWidth_;
300 }
301
GetCurrentBitRate()302 uint64_t HlsPlayListDownloader::GetCurrentBitRate()
303 {
304 if (currentVariant_==nullptr) {
305 return 0;
306 }
307 MEDIA_LOG_I("HlsPlayListDownloader currentBitrate: " PUBLIC_LOG_D64, currentVariant_->bandWidth_);
308 return currentVariant_->bandWidth_;
309 }
310
GetVedioWidth()311 int HlsPlayListDownloader::GetVedioWidth()
312 {
313 if (currentVariant_==nullptr) {
314 return 0;
315 }
316 MEDIA_LOG_I("HlsPlayListDownloader currentWidth: " PUBLIC_LOG_D64, currentVariant_->bandWidth_);
317 return currentVariant_->width_;
318 }
319
GetVedioHeight()320 int HlsPlayListDownloader::GetVedioHeight()
321 {
322 if (currentVariant_==nullptr) {
323 return 0;
324 }
325 MEDIA_LOG_I("HlsPlayListDownloader currentHeight: " PUBLIC_LOG_D64, currentVariant_->bandWidth_);
326 return currentVariant_->height_;
327 }
328
IsLive() const329 bool HlsPlayListDownloader::IsLive() const
330 {
331 MEDIA_LOG_I("HlsPlayListDownloader IsLive enter.");
332 if (!master_) {
333 return false;
334 }
335 return master_->bLive_;
336 }
337
GetUrl()338 std::string HlsPlayListDownloader::GetUrl()
339 {
340 return url_;
341 }
342
GetMaster()343 std::shared_ptr<M3U8MasterPlaylist> HlsPlayListDownloader::GetMaster()
344 {
345 return master_;
346 }
347
GetCurrentVariant()348 std::shared_ptr<M3U8VariantStream> HlsPlayListDownloader::GetCurrentVariant()
349 {
350 return currentVariant_;
351 }
352
GetNewVariant()353 std::shared_ptr<M3U8VariantStream> HlsPlayListDownloader::GetNewVariant()
354 {
355 return newVariant_;
356 }
357
SetMimeType(const std::string & mimeType)358 void HlsPlayListDownloader::SetMimeType(const std::string& mimeType)
359 {
360 mimeType_ = mimeType;
361 }
362
GetSegmentOffset(uint32_t tsIndex)363 size_t HlsPlayListDownloader::GetSegmentOffset(uint32_t tsIndex)
364 {
365 if (master_ && master_->segmentOffsets_.size() > tsIndex) {
366 return master_->segmentOffsets_[tsIndex];
367 }
368 return 0;
369 }
370
GetHLSDiscontinuity()371 bool HlsPlayListDownloader::GetHLSDiscontinuity()
372 {
373 if (master_) {
374 return master_->hasDiscontinuity_;
375 }
376 return false;
377 }
378
SetInitResolution(uint32_t width,uint32_t height)379 void HlsPlayListDownloader::SetInitResolution(uint32_t width, uint32_t height)
380 {
381 MEDIA_LOG_I("SetInitResolution, width:" PUBLIC_LOG_U32 ", height:" PUBLIC_LOG_U32, width, height);
382 if (width > 0 && height > 0) {
383 initResolution_ = width * height;
384 }
385 }
386 }
387 }
388 }
389 }