MythTV  master
HLSReader.cpp
Go to the documentation of this file.
1 #include <sys/time.h>
2 #include <unistd.h>
3 
4 #include <QtGlobal>
5 #if QT_VERSION >= QT_VERSION_CHECK(6,0,0)
6 #include <QStringConverter>
7 #endif
8 
9 #include "HLSReader.h"
10 #include "HLS/m3u.h"
11 
12 #define LOC QString("%1: ").arg(m_curstream ? m_curstream->M3U8Url() : "HLSReader")
13 
19 static QUrl RelativeURI (const QString& baseString, const QString& uriString)
20 {
21  QUrl base(baseString);
22  QUrl uri(QUrl::fromEncoded(uriString.toLatin1()));
23 
24  return base.resolved(uri);
25 }
26 
28 {
29  LOG(VB_RECORD, LOG_INFO, LOC + "dtor -- start");
30  Close();
31  LOG(VB_RECORD, LOG_INFO, LOC + "dtor -- end");
32 }
33 
34 bool HLSReader::Open(const QString & m3u, int bitrate_index)
35 {
36  LOG(VB_RECORD, LOG_INFO, LOC + QString("Opening '%1'").arg(m3u));
37 
38  m_bitrateIndex = bitrate_index;
39 
40  if (IsOpen(m3u))
41  {
42  LOG(VB_RECORD, LOG_ERR, LOC + "Already open");
43  return true;
44  }
45  Close(true);
46  m_cancel = false;
47 
48  QByteArray buffer;
49 
50 #ifdef HLS_USE_MYTHDOWNLOADMANAGER // MythDownloadManager leaks memory
51  if (!DownloadURL(m3u, &buffer))
52  {
53  LOG(VB_RECORD, LOG_ERR, LOC + "Open failed.");
54  return false; // can't download file
55  }
56 #else
57  MythSingleDownload downloader;
58  QString redir;
59  if (!downloader.DownloadURL(m3u, &buffer, 30s, 0, 0, &redir))
60  {
61  LOG(VB_GENERAL, LOG_ERR,
62  LOC + "Open failed: " + downloader.ErrorString());
63  return false; // can't download file
64  }
65 #endif
66 
67  QTextStream text(&buffer);
68 #if QT_VERSION < QT_VERSION_CHECK(6,0,0)
69  text.setCodec("UTF-8");
70 #else
71  text.setEncoding(QStringConverter::Utf8);
72 #endif
73 
74  if (!IsValidPlaylist(text))
75  {
76  LOG(VB_RECORD, LOG_ERR, LOC +
77  QString("Open '%1': not a valid playlist").arg(m3u));
78  return false;
79  }
80 
81  m_m3u8 = m3u;
82  m_segmentBase = redir.isEmpty() ? m3u : redir;
83 
84  QMutexLocker lock(&m_streamLock);
85  m_streams.clear();
86  m_curstream = nullptr;
87 
88  if (!ParseM3U8(buffer))
89  return false;
90 
91  if (m_bitrateIndex == 0)
92  {
93  // Select the highest bitrate stream
94  StreamContainer::iterator Istream;
95  for (Istream = m_streams.begin(); Istream != m_streams.end(); ++Istream)
96  {
97  if (m_curstream == nullptr ||
98  (*Istream)->Bitrate() > m_curstream->Bitrate())
99  m_curstream = *Istream;
100  }
101  }
102  else
103  {
104  if (m_streams.size() < m_bitrateIndex)
105  {
106  LOG(VB_RECORD, LOG_ERR, LOC +
107  QString("Open '%1': Only %2 bitrates, %3 is not a valid index")
108  .arg(m3u)
109  .arg(m_streams.size())
110  .arg(m_bitrateIndex));
111  m_fatal = true;
112  return false;
113  }
114 
115  auto it = m_streams.begin();
116  for (auto i = 0; i < m_bitrateIndex - 1; i++)
117  it++;
118  m_curstream = *it;
119  }
120 
121  if (!m_curstream)
122  {
123  LOG(VB_RECORD, LOG_ERR, LOC + QString("No stream selected"));
124  m_fatal = true;
125  return false;
126  }
127  LOG(VB_RECORD, LOG_INFO, LOC +
128  QString("Selected stream with %3 bitrate")
129  .arg(m_curstream->Bitrate()));
130 
131  QMutexLocker worker_lock(&m_workerLock);
132 
135 
136  m_streamWorker = new HLSStreamWorker(this);
138 
139  LOG(VB_RECORD, LOG_INFO, LOC + "Open -- end");
140  return true;
141 }
142 
144 {
145  LOG(VB_RECORD, (quiet ? LOG_DEBUG : LOG_INFO), LOC + "Close -- start");
146 
147  Cancel(quiet);
148 
149  QMutexLocker stream_lock(&m_streamLock);
150  m_curstream = nullptr;
151 
152  StreamContainer::iterator Istream;
153  for (Istream = m_streams.begin(); Istream != m_streams.end(); ++Istream)
154  delete *Istream;
155  m_streams.clear();
156 
157  QMutexLocker lock(&m_workerLock);
158 
159  delete m_streamWorker;
160  m_streamWorker = nullptr;
161  delete m_playlistWorker;
162  m_playlistWorker = nullptr;
163 
164  LOG(VB_RECORD, (quiet ? LOG_DEBUG : LOG_INFO), LOC + "Close -- end");
165 }
166 
168 {
169  LOG(VB_RECORD, (quiet ? LOG_DEBUG : LOG_INFO), LOC + "Cancel -- start");
170 
171  m_cancel = true;
172 
173  m_throttleLock.lock();
174  m_throttleCond.wakeAll();
175  m_throttleLock.unlock();
176 
177  QMutexLocker lock(&m_workerLock);
178 
179  if (m_curstream)
180  LOG(VB_RECORD, LOG_INFO, LOC + "Cancel");
181 
182  if (m_playlistWorker)
184 
185  if (m_streamWorker)
187 
188 #ifdef HLS_USE_MYTHDOWNLOADMANAGER // MythDownloadManager leaks memory
189  if (!m_sements.empty())
190  CancelURL(m_segments.front().Url());
191 #endif
192 
193  LOG(VB_RECORD, (quiet ? LOG_DEBUG : LOG_INFO), LOC + "Cancel -- done");
194 }
195 
196 void HLSReader::Throttle(bool val)
197 {
198  LOG(VB_RECORD, LOG_INFO, LOC + QString("Throttle(%1)")
199  .arg(val ? "true" : "false"));
200 
201  m_throttleLock.lock();
202  m_throttle = val;
203  if (val)
204  m_prebufferCnt += 4;
205  else
206  m_throttleCond.wakeAll();
207  m_throttleLock.unlock();
208 }
209 
210 qint64 HLSReader::Read(uint8_t* buffer, qint64 maxlen)
211 {
212  if (!m_curstream)
213  {
214  LOG(VB_RECORD, LOG_ERR, LOC + "Read: no stream selected");
215  return 0;
216  }
217  if (m_cancel)
218  {
219  LOG(VB_RECORD, LOG_DEBUG, LOC + QString("Read: canceled"));
220  return 0;
221  }
222 
223  QMutexLocker lock(&m_bufLock);
224 
225  qint64 len = m_buffer.size() < maxlen ? m_buffer.size() : maxlen;
226  LOG(VB_RECORD, LOG_DEBUG, LOC + QString("Reading %1 of %2 bytes")
227  .arg(len).arg(m_buffer.size()));
228 
229  memcpy(buffer, m_buffer.constData(), len);
230  if (len < m_buffer.size())
231  {
232  m_buffer.remove(0, len);
233  }
234  else
235  m_buffer.clear();
236 
237  return len;
238 }
239 
240 #ifdef HLS_USE_MYTHDOWNLOADMANAGER // MythDownloadManager leaks memory
241 bool HLSReader::DownloadURL(const QString &url, QByteArray *buffer)
242 {
244  return mdm->download(url, buffer);
245 }
246 
247 void HLSReader::CancelURL(const QString &url)
248 {
250  mdm->cancelDownload(url);
251  delete mdm;
252 }
253 
254 void HLSReader::CancelURL(const QStringList &urls)
255 {
257  mdm->cancelDownload(urls);
258 }
259 #endif
260 
261 bool HLSReader::IsValidPlaylist(QTextStream & text)
262 {
263  /* Parse stream and search for
264  * EXT-X-TARGETDURATION or EXT-X-STREAM-INF tag, see
265  * http://tools.ietf.org/html/draft-pantos-http-live-streaming-04#page-8 */
266  QString line = text.readLine();
267  if (!line.startsWith((const char*)"#EXTM3U"))
268  return false;
269 
270  for (;;)
271  {
272  line = text.readLine();
273  if (line.isNull())
274  break;
275  LOG(VB_RECORD, LOG_DEBUG,
276  QString("IsValidPlaylist: |'%1'").arg(line));
277  if (line.startsWith(QLatin1String("#EXT-X-TARGETDURATION")) ||
278  line.startsWith(QLatin1String("#EXT-X-MEDIA-SEQUENCE")) ||
279  line.startsWith(QLatin1String("#EXT-X-KEY")) ||
280  line.startsWith(QLatin1String("#EXT-X-ALLOW-CACHE")) ||
281  line.startsWith(QLatin1String("#EXT-X-ENDLIST")) ||
282  line.startsWith(QLatin1String("#EXT-X-STREAM-INF")) ||
283  line.startsWith(QLatin1String("#EXT-X-DISCONTINUITY")) ||
284  line.startsWith(QLatin1String("#EXT-X-VERSION")))
285  {
286  return true;
287  }
288  }
289 
290  LOG(VB_RECORD, LOG_ERR, QString("IsValidPlaylist: false"));
291  return false;
292 }
293 
294 bool HLSReader::ParseM3U8(const QByteArray& buffer, HLSRecStream* stream)
295 {
304  LOG(VB_RECORD, LOG_DEBUG, LOC + "ParseM3U8 -- begin");
305 
306  QTextStream text(buffer);
307 
308  QString line = text.readLine();
309  if (line.isNull())
310  {
311  LOG(VB_RECORD, LOG_ERR, LOC + "ParseM3U8: empty line");
312  return false;
313  }
314 
315  if (!line.startsWith(QLatin1String("#EXTM3U")))
316  {
317  LOG(VB_RECORD, LOG_ERR, LOC +
318  "ParseM3U8: missing #EXTM3U tag .. aborting");
319  return false;
320  }
321 
322  /* What is the version ? */
323  int version = 1;
324  int p = buffer.indexOf("#EXT-X-VERSION:");
325  if (p >= 0)
326  {
327  text.seek(p);
328  if (!M3U::ParseVersion(text.readLine(), StreamURL(), version))
329  return false;
330  }
331 
332  if (buffer.indexOf("#EXT-X-STREAM-INF") >= 0)
333  {
334  // Meta index file
335  LOG(VB_RECORD, LOG_INFO, LOC + "Meta index file");
336 
337  /* M3U8 Meta Index file */
338  text.seek(0); // rewind
339  while (!m_cancel)
340  {
341  line = text.readLine();
342  if (line.isNull())
343  break;
344 
345  LOG(VB_RECORD, LOG_INFO, LOC + QString("|%1").arg(line));
346  if (line.startsWith(QLatin1String("#EXT-X-STREAM-INF")))
347  {
348  QString uri = text.readLine();
349  if (uri.isNull() || uri.startsWith(QLatin1String("#")))
350  {
351  LOG(VB_RECORD, LOG_INFO, LOC +
352  QString("ParseM3U8: Invalid EXT-X-STREAM-INF data '%1'")
353  .arg(uri));
354  }
355  else
356  {
357  QString url = RelativeURI(m_segmentBase, uri).toString();
358 
359  StreamContainer::iterator Istream = m_streams.find(url);
360  if (Istream == m_streams.end())
361  {
362  int id = 0;
363  uint64_t bandwidth = 0;
364  if (!M3U::ParseStreamInformation(line, url, StreamURL(),
365  id, bandwidth))
366  break;
367  auto *hls = new HLSRecStream(id, bandwidth, url,
368  m_segmentBase);
369  if (hls)
370  {
371  LOG(VB_RECORD, LOG_INFO, LOC +
372  QString("Adding stream %1")
373  .arg(hls->toString()));
374  Istream = m_streams.insert(url, hls);
375  }
376  }
377  else
378  LOG(VB_RECORD, LOG_INFO, LOC +
379  QString("Already have stream '%1'").arg(url));
380  }
381  }
382  }
383  }
384  else
385  {
386  LOG(VB_RECORD, LOG_DEBUG, LOC + "Meta playlist");
387 
388  HLSRecStream *hls = stream;
389  if (stream == nullptr)
390  {
391  /* No Meta playlist used */
392  StreamContainer::iterator Istream =
394  if (Istream == m_streams.end())
395  {
396  hls = new HLSRecStream(0, 0, m_m3u8, m_segmentBase);
397  if (hls)
398  {
399  LOG(VB_RECORD, LOG_INFO, LOC +
400  QString("Adding new stream '%1'").arg(m_m3u8));
401  Istream = m_streams.insert(m_m3u8, hls);
402  }
403  }
404  else
405  {
406  hls = *Istream;
407  LOG(VB_RECORD, LOG_INFO, LOC +
408  QString("Updating stream '%1'").arg(hls->toString()));
409  }
410 
411  /* Get TARGET-DURATION first */
412  p = buffer.indexOf("#EXT-X-TARGETDURATION:");
413  if (p >= 0)
414  {
415  int duration = 0;
416 
417  text.seek(p);
418  if (!M3U::ParseTargetDuration(text.readLine(), StreamURL(),
419  duration))
420  return false;
421  hls->SetTargetDuration(std::chrono::seconds(duration));
422  }
423  /* Store version */
424  hls->SetVersion(version);
425  }
426  LOG(VB_RECORD, LOG_DEBUG, LOC +
427  QString("%1 Playlist HLS protocol version: %2")
428  .arg(hls->Live() ? "Live": "VOD").arg(version));
429 
430  // rewind
431  text.seek(0);
432 
433  QString title;
434  std::chrono::seconds segment_duration = -1s;
435  int64_t first_sequence = -1;
436  int64_t sequence_num = 0;
437  int skipped = 0;
438 
439  SegmentContainer new_segments;
440 
441  QMutexLocker lock(&m_seqLock);
442  while (!m_cancel)
443  {
444  /* Next line */
445  line = text.readLine();
446  if (line.isNull())
447  break;
448  LOG(VB_RECORD, (m_debug ? LOG_INFO : LOG_DEBUG),
449  LOC + QString("|%1").arg(line));
450 
451  if (line.startsWith(QLatin1String("#EXTINF")))
452  {
453  uint tmp_duration = -1;
454  if (!M3U::ParseSegmentInformation(hls->Version(), line,
455  tmp_duration,
456  title, StreamURL()))
457  return false;
458  segment_duration = std::chrono::seconds(tmp_duration);
459  }
460  else if (line.startsWith(QLatin1String("#EXT-X-TARGETDURATION")))
461  {
462  int duration = 0;
463  if (!M3U::ParseTargetDuration(line, StreamURL(), duration))
464  return false;
465  hls->SetTargetDuration(std::chrono::seconds(duration));
466  }
467  else if (line.startsWith(QLatin1String("#EXT-X-MEDIA-SEQUENCE")))
468  {
469  if (!M3U::ParseMediaSequence(sequence_num, line, StreamURL()))
470  return false;
471  if (first_sequence < 0)
472  first_sequence = sequence_num;
473  }
474  else if (line.startsWith(QLatin1String("#EXT-X-KEY")))
475  {
476 #ifdef USING_LIBCRYPTO
477  QString path;
478  QString iv;
479  if (!M3U::ParseKey(hls->Version(), line, m_aesMsg, StreamURL(),
480  path, iv))
481  return false;
482  if (!path.isEmpty())
483  hls->SetKeyPath(path);
484 
485  if (!iv.isNull() && !hls->SetAESIV(iv))
486  {
487  LOG(VB_RECORD, LOG_ERR, LOC + "invalid IV");
488  return false;
489  }
490 #else
491  LOG(VB_RECORD, LOG_ERR, LOC + "#EXT-X-KEY needs libcrypto");
492  return false;
493 #endif
494  }
495  else if (line.startsWith(QLatin1String("#EXT-X-PROGRAM-DATE-TIME")))
496  {
497  QDateTime date;
498  if (!M3U::ParseProgramDateTime(line, StreamURL(), date))
499  return false;
500  }
501  else if (line.startsWith(QLatin1String("#EXT-X-ALLOW-CACHE")))
502  {
503  bool do_cache = false;
504  if (!M3U::ParseAllowCache(line, StreamURL(), do_cache))
505  return false;
506  hls->SetCache(do_cache);
507  }
508  else if (line.startsWith(QLatin1String("#EXT-X-DISCONTINUITY")))
509  {
510  if (!M3U::ParseDiscontinuity(line, StreamURL()))
511  return false;
512  ResetSequence();
513  }
514  else if (line.startsWith(QLatin1String("#EXT-X-VERSION")))
515  {
516  int version2 = 0;
517  if (!M3U::ParseVersion(line, StreamURL(), version2))
518  return false;
519  hls->SetVersion(version2);
520  }
521  else if (line.startsWith(QLatin1String("#EXT-X-ENDLIST")))
522  {
523  bool is_vod = false;
524  if (!M3U::ParseEndList(StreamURL(), is_vod))
525  return false;
526  hls->SetLive(!is_vod);
527  }
528  else if (!line.startsWith(QLatin1String("#")) && !line.isEmpty())
529  {
530  if (m_curSeq < 0 || sequence_num > m_curSeq)
531  {
532  new_segments.push_back
533  (HLSRecSegment(sequence_num, segment_duration, title,
534  RelativeURI(hls->SegmentBaseUrl(), line)));
535  }
536  else
537  ++skipped;
538 
539  ++sequence_num;
540  segment_duration = -1s; /* reset duration */
541  title.clear();
542  }
543  }
544 
545  if (sequence_num < m_curSeq)
546  {
547  // Sequence has been reset
548  LOG(VB_RECORD, LOG_WARNING, LOC +
549  QString("Sequence number has been reset from %1 to %2")
550  .arg(m_curSeq).arg(first_sequence));
551  ResetSequence();
552  return false;
553  }
554 
555  SegmentContainer::iterator Inew = new_segments.begin();
556  SegmentContainer::iterator Iseg = m_segments.end() - 1;
557 
558  // Does this playlist overlap?
559  if (!m_segments.empty() && !new_segments.empty())
560  {
561  if ((*Iseg).Sequence() >= first_sequence &&
562  (*Iseg).Sequence() < sequence_num)
563  {
564  // Find the beginning of the overlap
565  while (Iseg != m_segments.begin() &&
566  (*Iseg).Sequence() > first_sequence &&
567  (*Iseg).Sequence() < sequence_num)
568  --Iseg;
569 
570  int64_t diff = (*Iseg).Sequence() - (*Inew).Sequence();
571  if (diff >= 0 && new_segments.size() > diff)
572  {
573  Inew += diff;
574 
575  // Update overlapping segment info
576  for ( ; Iseg != m_segments.end(); ++Iseg, ++Inew)
577  {
578  if (Inew == new_segments.end())
579  {
580  LOG(VB_RECORD, LOG_ERR, LOC +
581  QString("Went off the end with %1 left")
582  .arg(m_segments.end() - Iseg));
583  break;
584  }
585  if ((*Iseg).Sequence() != (*Inew).Sequence())
586  {
587  LOG(VB_RECORD, LOG_ERR, LOC +
588  QString("Sequence non-sequential? %1 != %2")
589  .arg((*Iseg).Sequence())
590  .arg((*Inew).Sequence()));
591  break;
592  }
593 
594  (*Iseg).m_duration = (*Inew).Duration();
595  (*Iseg).m_title = (*Inew).Title();
596  (*Iseg).m_url = (*Inew).Url();
597  }
598  }
599  }
600  }
601 
602  for ( ; Inew != new_segments.end(); ++Inew)
603  m_segments.push_back(*Inew);
604 
605  m_playlistSize = new_segments.size() + skipped;
606  int behind = m_segments.size() - m_playlistSize;
607  int max_behind = m_playlistSize / 2;
608  if (behind > max_behind)
609  {
610  LOG(VB_RECORD, LOG_WARNING, LOC +
611  QString("Not downloading fast enough! "
612  "%1 segments behind, skipping %2 segments. "
613  "playlist size: %3, queued: %4")
614  .arg(behind).arg(behind - max_behind)
615  .arg(m_playlistSize).arg(m_segments.size()));
616  m_workerLock.lock();
617  if (m_streamWorker)
619  m_workerLock.unlock();
620 
621  EnableDebugging();
622  Iseg = m_segments.begin() + (behind - max_behind);
623  m_segments.erase(m_segments.begin(), Iseg);
625  }
626  else if (m_debugCnt > 0)
627  --m_debugCnt;
628  else
629  m_debug = false;
630  }
631 
632  LOG(VB_RECORD, LOG_DEBUG, LOC + "ParseM3U8 -- end");
633  return true;
634 }
635 
637 {
638  if (!m_curstream || m_cancel)
639  return false;
640 
641  LOG(VB_RECORD, LOG_DEBUG, LOC +
642  QString("LoadMetaPlaylists stream %1")
643  .arg(m_curstream->toString()));
644 
645  StreamContainer::iterator Istream;
646 
647  m_streamLock.lock();
648  if (m_bandwidthCheck /* && !m_segments.empty() */)
649  {
650  int buffered = PercentBuffered();
651 
652  if (buffered < 15)
653  {
654  // It is taking too long to download the segments
655  LOG(VB_RECORD, LOG_WARNING, LOC +
656  QString("Falling behind: only %1% buffered").arg(buffered));
657  LOG(VB_RECORD, LOG_DEBUG, LOC +
658  QString("playlist size %1, queued %2")
659  .arg(m_playlistSize).arg(m_segments.size()));
660  EnableDebugging();
662  m_bandwidthCheck = false;
663  }
664  else if (buffered > 85)
665  {
666  // Keeping up easily, raise the bitrate.
667  LOG(VB_RECORD, LOG_DEBUG, LOC +
668  QString("Plenty of bandwidth, downloading %1 of %2")
669  .arg(m_playlistSize - m_segments.size())
670  .arg(m_playlistSize));
671  LOG(VB_RECORD, LOG_DEBUG, LOC +
672  QString("playlist size %1, queued %2")
673  .arg(m_playlistSize).arg(m_segments.size()));
675  m_bandwidthCheck = false;
676  }
677  }
678  m_streamLock.unlock();
679 
680  QByteArray buffer;
681 
682 #ifdef HLS_USE_MYTHDOWNLOADMANAGER // MythDownloadManager leaks memory
683  if (!DownloadURL(m_curstream->Url(), &buffer))
684  return false;
685 #else
686  QString redir;
687  if (!downloader.DownloadURL(m_curstream->M3U8Url(), &buffer, 30s, 0, 0, &redir))
688  {
689  LOG(VB_GENERAL, LOG_WARNING,
690  LOC + "Download failed: " + downloader.ErrorString());
691  return false;
692  }
693 #endif
694 
695  if (m_segmentBase != redir)
696  {
697  m_segmentBase = redir;
699  }
700 
701  if (m_cancel || !ParseM3U8(buffer, m_curstream))
702  return false;
703 
704  // Signal SegmentWorker that there is work to do...
706 
707  return true;
708 }
709 
711 {
712  HLSRecStream *hls = nullptr;
713  uint64_t bitrate = m_curstream->Bitrate();
714  uint64_t candidate = 0;
715 
716  for (auto Istream = m_streams.cbegin(); Istream != m_streams.cend(); ++Istream)
717  {
718  if ((*Istream)->Id() != progid)
719  continue;
720  if (bitrate > (*Istream)->Bitrate() &&
721  candidate < (*Istream)->Bitrate())
722  {
723  LOG(VB_RECORD, LOG_DEBUG, LOC +
724  QString("candidate stream '%1' bitrate %2 >= %3")
725  .arg(Istream.key()).arg(bitrate).arg((*Istream)->Bitrate()));
726  hls = *Istream;
727  candidate = hls->Bitrate();
728  }
729  }
730 
731  if (hls)
732  {
733  LOG(VB_RECORD, LOG_INFO, LOC +
734  QString("Switching to a lower bitrate stream %1 -> %2")
735  .arg(bitrate).arg(candidate));
736  m_curstream = hls;
737  }
738  else
739  {
740  LOG(VB_RECORD, LOG_DEBUG, LOC +
741  QString("Already at lowest bitrate %1").arg(bitrate));
742  }
743 }
744 
746 {
747  HLSRecStream *hls = nullptr;
748  uint64_t bitrate = m_curstream->Bitrate();
749  uint64_t candidate = INT_MAX;
750 
751  for (auto Istream = m_streams.cbegin(); Istream != m_streams.cend(); ++Istream)
752  {
753  if ((*Istream)->Id() != progid)
754  continue;
755  if (bitrate < (*Istream)->Bitrate() &&
756  candidate > (*Istream)->Bitrate())
757  {
758  LOG(VB_RECORD, LOG_DEBUG, LOC +
759  QString("candidate stream '%1' bitrate %2 >= %3")
760  .arg(Istream.key()).arg(bitrate).arg((*Istream)->Bitrate()));
761  hls = *Istream;
762  candidate = hls->Bitrate();
763  }
764  }
765 
766  if (hls)
767  {
768  LOG(VB_RECORD, LOG_INFO, LOC +
769  QString("Switching to a higher bitrate stream %1 -> %2")
770  .arg(bitrate).arg(candidate));
771  m_curstream = hls;
772  }
773  else
774  {
775  LOG(VB_RECORD, LOG_DEBUG, LOC +
776  QString("Already at highest bitrate %1").arg(bitrate));
777  }
778 }
779 
781 {
782  LOG(VB_RECORD, LOG_DEBUG, LOC + "LoadSegment -- start");
783 
784  if (!m_curstream)
785  {
786  LOG(VB_RECORD, LOG_ERR, LOC + "LoadSegment: current stream not set.");
788  return false;
789  }
790 
791  HLSRecSegment seg;
792  for (;;)
793  {
794  m_seqLock.lock();
795  if (m_cancel || m_segments.empty())
796  {
797  m_seqLock.unlock();
798  break;
799  }
800 
801  seg = m_segments.front();
802  if (m_segments.size() > m_playlistSize)
803  {
804  LOG(VB_RECORD, (m_debug ? LOG_INFO : LOG_DEBUG), LOC +
805  QString("Downloading segment %1 (1 of %2) with %3 behind")
806  .arg(seg.Sequence())
807  .arg(m_segments.size() + m_playlistSize)
808  .arg(m_segments.size() - m_playlistSize));
809  }
810  else
811  {
812  LOG(VB_RECORD, (m_debug ? LOG_INFO : LOG_DEBUG), LOC +
813  QString("Downloading segment %1 (%2 of %3)")
814  .arg(seg.Sequence())
815  .arg(m_playlistSize - m_segments.size() + 1)
816  .arg(m_playlistSize));
817  }
818  m_seqLock.unlock();
819 
820  m_streamLock.lock();
821  HLSRecStream *hls = m_curstream;
822  m_streamLock.unlock();
823  if (!hls)
824  {
825  LOG(VB_RECORD, LOG_DEBUG, LOC + "LoadSegment -- no current stream");
826  return false;
827  }
828 
829  long throttle = DownloadSegmentData(downloader,hls,seg,m_playlistSize);
830 
831  m_seqLock.lock();
832  if (throttle < 0)
833  {
834  if (m_segments.size() > m_playlistSize)
835  {
836  SegmentContainer::iterator Iseg = m_segments.begin() +
837  (m_segments.size() - m_playlistSize);
838  m_segments.erase(m_segments.begin(), Iseg);
839  }
840  m_seqLock.unlock();
841  return false;
842  }
843 
844  m_curSeq = m_segments.front().Sequence();
845  m_segments.pop_front();
846 
847  m_seqLock.unlock();
848 
849  if (m_throttle && throttle == 0)
850  throttle = 2;
851  else if (throttle > 8)
852  throttle = 8;
853  if (throttle > 0)
854  {
855  LOG(VB_RECORD, LOG_INFO, LOC +
856  QString("Throttling -- sleeping %1 secs.")
857  .arg(throttle));
858  throttle *= 1000;
859  m_throttleLock.lock();
860  if (m_throttleCond.wait(&m_throttleLock, throttle))
861  LOG(VB_RECORD, LOG_INFO, LOC + "Throttle aborted");
862  m_throttleLock.unlock();
863  LOG(VB_RECORD, LOG_INFO, LOC + "Throttle done");
864  }
865  else
866  usleep(5000);
867 
868  if (m_prebufferCnt == 0)
869  {
871  m_prebufferCnt = 2;
872  }
873  else
874  --m_prebufferCnt;
875  }
876 
877  LOG(VB_RECORD, LOG_DEBUG, LOC + "LoadSegment -- end");
878  return true;
879 }
880 
882 {
883  if (m_playlistSize == 0 || m_segments.size() > m_playlistSize)
884  return 0;
885  return (static_cast<float>(m_playlistSize - m_segments.size()) /
886  static_cast<float>(m_playlistSize)) * 100.0F;
887 }
888 
890  HLSRecStream* hls,
891  const HLSRecSegment& segment, int playlist_size)
892 {
893  uint64_t bandwidth = hls->AverageBandwidth();
894 
895  LOG(VB_RECORD, LOG_DEBUG, LOC +
896  QString("Downloading %1 bandwidth %2 bitrate %3")
897  .arg(segment.Sequence()).arg(bandwidth).arg(hls->Bitrate()));
898 
899  /* sanity check - can we download this segment on time? */
900  if ((bandwidth > 0) && (hls->Bitrate() > 0))
901  {
902  uint64_t size = (segment.Duration().count() * hls->Bitrate()); /* bits */
903  auto estimated_time = std::chrono::seconds(size / bandwidth);
904  if (estimated_time > segment.Duration())
905  {
906  LOG(VB_RECORD, LOG_WARNING, LOC +
907  QString("downloading of %1 will take %2s, "
908  "which is longer than its playback (%3s) at %4kiB/s")
909  .arg(segment.Sequence())
910  .arg(estimated_time.count())
911  .arg(segment.Duration().count())
912  .arg(bandwidth / 8192));
913  }
914  }
915 
916  QByteArray buffer;
917  auto start = nowAsDuration<std::chrono::milliseconds>();
918 
919 #ifdef HLS_USE_MYTHDOWNLOADMANAGER // MythDownloadManager leaks memory
920  // and can only handle six download at a time
921  if (!HLSReader::DownloadURL(segment.Url(), &buffer))
922  {
923  LOG(VB_RECORD, LOG_ERR, LOC +
924  QString("%1 failed").arg(segment.Sequence()));
925  if (estimated_time * 2 < segment.Duration())
926  {
927  if (!HLSReader::DownloadURL(segment.Url(), &buffer))
928  {
929  LOG(VB_RECORD, LOG_ERR, LOC + QString("%1 failed")
930  .arg(segment.Sequence()));
931  return -1;
932  }
933  }
934  else
935  return 0;
936  }
937 #else
938  if (!downloader.DownloadURL(segment.Url(), &buffer))
939  {
940  LOG(VB_RECORD, LOG_ERR, LOC + QString("%1 failed: %2")
941  .arg(segment.Sequence()).arg(downloader.ErrorString()));
942  return -1;
943  }
944 #endif
945 
946  auto downloadduration = nowAsDuration<std::chrono::milliseconds>() - start;
947 
948 #ifdef USING_LIBCRYPTO
949  /* If the segment is encrypted, decode it */
950  if (segment.HasKeyPath())
951  {
952  if (!hls->DecodeData(downloader, hls->IVLoaded() ? hls->AESIV() : QByteArray(),
953  segment.KeyPath(),
954  buffer, segment.Sequence()))
955  return 0;
956  }
957 #endif
958 
959  int segment_len = buffer.size();
960 
961  m_bufLock.lock();
962  if (m_buffer.size() > segment_len * playlist_size)
963  {
964  LOG(VB_RECORD, LOG_WARNING, LOC +
965  QString("streambuffer is not reading fast enough. "
966  "buffer size %1").arg(m_buffer.size()));
967  EnableDebugging();
968  if (++m_slowCnt > 15)
969  {
970  m_slowCnt = 15;
971  m_fatal = true;
972  return -1;
973  }
974  }
975  else if (m_slowCnt > 0)
976  --m_slowCnt;
977 
978  if (m_buffer.size() >= segment_len * playlist_size * 2)
979  {
980  LOG(VB_RECORD, LOG_WARNING, LOC +
981  QString("streambuffer is not reading fast enough. "
982  "buffer size %1. Dropping %2 bytes")
983  .arg(m_buffer.size()).arg(segment_len));
984  m_buffer.remove(0, segment_len);
985  }
986 
987  m_buffer += buffer;
988  m_bufLock.unlock();
989 
990  if (hls->Bitrate() == 0 && segment.Duration() > 0s)
991  {
992  /* Try to estimate the bandwidth for this stream */
993  hls->SetBitrate((uint64_t)(((double)segment_len * 8) /
994  ((double)segment.Duration().count())));
995  }
996 
997  if (downloadduration < 1ms)
998  downloadduration = 1ms;
999 
1000  /* bits/sec */
1001  bandwidth = 8 * 1000ULL * segment_len / downloadduration.count();
1002  hls->AverageBandwidth(bandwidth);
1003  hls->SetCurrentByteRate(static_cast<uint64_t>
1004  ((static_cast<double>(segment_len) /
1005  static_cast<double>(segment.Duration().count()))));
1006 
1007  LOG(VB_RECORD, (m_debug ? LOG_INFO : LOG_DEBUG), LOC +
1008  QString("%1 took %3ms for %4 bytes: "
1009  "bandwidth:%5kiB/s")
1010  .arg(segment.Sequence())
1011  .arg(downloadduration.count())
1012  .arg(segment_len)
1013  .arg(bandwidth / 8192.0));
1014 
1015  return m_slowCnt;
1016 }
1017 
1019 {
1020  QMutexLocker lock(&m_streamLock);
1021  if (m_curstream)
1022  m_curstream->Good();
1023 }
1024 
1026 {
1027  QMutexLocker lock(&m_streamLock);
1028  if (m_curstream)
1029  m_curstream->Retrying();
1030 }
1031 
1033 {
1034  QMutexLocker lock(&m_streamLock);
1035  if (m_curstream)
1036  return m_curstream->RetryCount();
1037  return 0;
1038 }
1039 
1041 {
1042  m_debug = true;
1043  m_debugCnt = 5;
1044  LOG(VB_RECORD, LOG_INFO, LOC + "Debugging enabled");
1045 }
M3U::ParseProgramDateTime
bool ParseProgramDateTime(const QString &line, const QString &loc, QDateTime &)
Definition: m3u.cpp:361
HLSReader::HLSPlaylistWorker
friend class HLSPlaylistWorker
Definition: HLSReader.h:37
MThread::start
void start(QThread::Priority p=QThread::InheritPriority)
Tell MThread to start running the thread in the near future.
Definition: mthread.cpp:283
HLSRecStream::Version
int Version(void) const
Definition: HLSStream.h:31
HLSReader::m_seqLock
QMutex m_seqLock
Definition: HLSReader.h:118
HLSReader::m_segments
SegmentContainer m_segments
Definition: HLSReader.h:101
HLSReader::SegmentContainer
QVector< HLSRecSegment > SegmentContainer
Definition: HLSReader.h:41
HLSRecStream::RetryCount
int RetryCount(void) const
Definition: HLSStream.h:55
HLSReader::IncreaseBitrate
void IncreaseBitrate(int progid)
Definition: HLSReader.cpp:745
HLSReader::IsOpen
bool IsOpen(const QString &url) const
Definition: HLSReader.h:51
HLSRecStream::SetVersion
void SetVersion(int x)
Definition: HLSStream.h:32
HLSReader::m_streams
StreamContainer m_streams
Definition: HLSReader.h:100
HLSReader::PlaylistRetryCount
int PlaylistRetryCount(void) const
Definition: HLSReader.cpp:1032
HLSReader::m_playlistWorker
HLSPlaylistWorker * m_playlistWorker
Definition: HLSReader.h:112
HLSStreamWorker::Cancel
void Cancel(void)
Definition: HLSStreamWorker.cpp:18
HLSReader::m_debugCnt
int m_debugCnt
Definition: HLSReader.h:124
HLSReader::m_debug
bool m_debug
Definition: HLSReader.h:123
RelativeURI
static QUrl RelativeURI(const QString &baseString, const QString &uriString)
Handles relative URLs without breaking URI encoded parameters by avoiding storing the decoded URL in ...
Definition: HLSReader.cpp:19
quiet
int quiet
Definition: mythcommflag.cpp:68
LOG
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:39
M3U::ParseKey
bool ParseKey(int version, const QString &line, [[maybe_unused]] bool &aesmsg, const QString &loc, QString &path, QString &iv)
Definition: m3u.cpp:278
HLSReader::ResetSequence
void ResetSequence(void)
Definition: HLSReader.h:58
MythDownloadManager
Definition: mythdownloadmanager.h:48
HLSReader::StreamURL
QString StreamURL(void) const
Definition: HLSReader.h:60
HLSRecStream::Good
void Good(void)
Definition: HLSStream.h:53
HLSRecStream
Definition: HLSStream.h:15
HLSReader::CancelURL
static void CancelURL(const QString &url)
HLSReader::m_curSeq
int64_t m_curSeq
Definition: HLSReader.h:103
HLSRecSegment::Duration
std::chrono::seconds Duration(void) const
Definition: HLSSegment.h:31
M3U::ParseMediaSequence
bool ParseMediaSequence(int64_t &sequence_num, const QString &line, const QString &loc)
Definition: m3u.cpp:257
HLSReader::m_workerLock
QMutex m_workerLock
Definition: HLSReader.h:120
HLSReader::LoadMetaPlaylists
bool LoadMetaPlaylists(MythSingleDownload &downloader)
Definition: HLSReader.cpp:636
HLSStreamWorker::CancelCurrentDownload
void CancelCurrentDownload(void)
Definition: HLSStreamWorker.cpp:28
HLSReader::Cancel
void Cancel(bool quiet=false)
Definition: HLSReader.cpp:167
HLSReader::m_bufLock
QMutex m_bufLock
Definition: HLSReader.h:129
HLSRecStream::SetLive
void SetLive(bool x)
Definition: HLSStream.h:43
HLSRecSegment::Url
QUrl Url(void) const
Definition: HLSSegment.h:30
HLSReader::IsValidPlaylist
static bool IsValidPlaylist(QTextStream &text)
Definition: HLSReader.cpp:261
HLSRecStream::SegmentBaseUrl
QString SegmentBaseUrl(void) const
Definition: HLSStream.h:45
HLSReader::LoadSegments
bool LoadSegments(MythSingleDownload &downloader)
Definition: HLSReader.cpp:780
HLSRecStream::Retrying
void Retrying(void)
Definition: HLSStream.h:54
hardwareprofile.config.p
p
Definition: config.py:33
HLSRecStream::SetBitrate
void SetBitrate(uint64_t bitrate)
Definition: HLSStream.h:37
MythSingleDownload::DownloadURL
bool DownloadURL(const QUrl &url, QByteArray *buffer, std::chrono::seconds timeout=30s, uint redirs=0, qint64 maxsize=0, QString *final_url=nullptr)
Definition: mythsingledownload.cpp:14
HLSReader::m_playlistSize
int m_playlistSize
Definition: HLSReader.h:115
HLSReader::m_m3u8
QString m_m3u8
Definition: HLSReader.h:98
HLSRecStream::AverageBandwidth
uint64_t AverageBandwidth(void) const
Definition: HLSStream.h:35
HLSReader::EnableDebugging
void EnableDebugging(void)
Definition: HLSReader.cpp:1040
HLSReader::PercentBuffered
uint PercentBuffered(void) const
Definition: HLSReader.cpp:881
HLSReader::ParseM3U8
bool ParseM3U8(const QByteArray &buffer, HLSRecStream *stream=nullptr)
Definition: HLSReader.cpp:294
M3U::ParseAllowCache
bool ParseAllowCache(const QString &line, const QString &loc, bool &do_cache)
Definition: m3u.cpp:373
HLSReader::DownloadSegmentData
int DownloadSegmentData(MythSingleDownload &downloader, HLSRecStream *hls, const HLSRecSegment &segment, int playlist_size)
Definition: HLSReader.cpp:889
MythDownloadManager::download
bool download(const QString &url, const QString &dest, bool reload=false)
Downloads a URL to a file in blocking mode.
Definition: mythdownloadmanager.cpp:430
M3U::DecodedURI
QString DecodedURI(const QString &uri)
Definition: m3u.cpp:9
HLSReader::Read
qint64 Read(uint8_t *buffer, qint64 len)
Definition: HLSReader.cpp:210
HLSRecStream::toString
QString toString(void) const
Definition: HLSStream.cpp:34
MythDownloadManager::cancelDownload
void cancelDownload(const QString &url, bool block=true)
Cancel a queued or current download.
Definition: mythdownloadmanager.cpp:1009
M3U::ParseDiscontinuity
bool ParseDiscontinuity(const QString &line, const QString &loc)
Definition: m3u.cpp:404
uint
unsigned int uint
Definition: compat.h:81
HLSReader::m_throttle
bool m_throttle
Definition: HLSReader.h:108
HLSReader::m_cancel
bool m_cancel
Definition: HLSReader.h:107
HLSReader::m_throttleCond
QWaitCondition m_throttleCond
Definition: HLSReader.h:122
HLSReader::m_segmentBase
QString m_segmentBase
Definition: HLSReader.h:99
HLSReader::m_curstream
HLSRecStream * m_curstream
Definition: HLSReader.h:102
HLSReader::PlaylistRetrying
void PlaylistRetrying(void)
Definition: HLSReader.cpp:1025
HLSRecSegment
Definition: HLSSegment.h:11
HLSReader::DecreaseBitrate
void DecreaseBitrate(int progid)
Definition: HLSReader.cpp:710
HLSReader::m_prebufferCnt
uint m_prebufferCnt
Definition: HLSReader.h:117
HLSReader::m_fatal
bool m_fatal
Definition: HLSReader.h:106
LOC
#define LOC
Definition: HLSReader.cpp:12
MythSingleDownload
Definition: mythsingledownload.h:25
HLSReader::Close
void Close(bool quiet=false)
Definition: HLSReader.cpp:143
HLSRecStream::SetTargetDuration
void SetTargetDuration(std::chrono::seconds x)
Definition: HLSStream.h:34
HLSRecStream::Live
bool Live(void) const
Definition: HLSStream.h:42
HLSReader::m_buffer
QByteArray m_buffer
Definition: HLSReader.h:128
HLSRecStream::Bitrate
uint64_t Bitrate(void) const
Definition: HLSStream.h:36
HLSReader::m_bitrateIndex
int m_bitrateIndex
Definition: HLSReader.h:104
HLSPlaylistWorker::Cancel
void Cancel(void)
Definition: HLSPlaylistWorker.cpp:20
HLSReader::m_aesMsg
bool m_aesMsg
Definition: HLSReader.h:110
MythSingleDownload::ErrorString
QString ErrorString(void) const
Definition: mythsingledownload.h:36
HLSReader::HLSStreamWorker
friend class HLSStreamWorker
Definition: HLSReader.h:36
HLSRecStream::SetCache
void SetCache(bool x)
Definition: HLSStream.h:41
HLSRecStream::SetSegmentBaseUrl
void SetSegmentBaseUrl(const QString &n)
Definition: HLSStream.h:46
HLSRecSegment::Sequence
int64_t Sequence(void) const
Definition: HLSSegment.h:28
M3U::ParseVersion
bool ParseVersion(const QString &line, const QString &loc, int &version)
Definition: m3u.cpp:82
HLSReader::Throttle
void Throttle(bool val)
Definition: HLSReader.cpp:196
HLSReader::Open
bool Open(const QString &m3u, int bitrate_index=0)
Definition: HLSReader.cpp:34
HLSRecStream::SetCurrentByteRate
void SetCurrentByteRate(uint64_t byterate)
Definition: HLSStream.h:39
HLSRecStream::Id
int Id(void) const
Definition: HLSStream.h:30
HLSReader::~HLSReader
~HLSReader(void)
Definition: HLSReader.cpp:27
M3U::ParseStreamInformation
bool ParseStreamInformation(const QString &line, const QString &url, const QString &loc, int &id, uint64_t &bandwidth)
Definition: m3u.cpp:115
M3U::ParseSegmentInformation
bool ParseSegmentInformation(int version, const QString &line, uint &duration, QString &title, const QString &loc)
Definition: m3u.cpp:183
HLSReader::m_throttleLock
QMutex m_throttleLock
Definition: HLSReader.h:121
HLSReader::m_slowCnt
int m_slowCnt
Definition: HLSReader.h:127
HLSReader::m_bandwidthCheck
bool m_bandwidthCheck
Definition: HLSReader.h:116
M3U::ParseEndList
bool ParseEndList(const QString &loc, bool &is_vod)
Definition: m3u.cpp:412
M3U::ParseTargetDuration
bool ParseTargetDuration(const QString &line, const QString &loc, int &duration)
Definition: m3u.cpp:164
HLSStreamWorker::Wakeup
void Wakeup(void)
Definition: HLSStreamWorker.h:20
m3u.h
nv_python_libs.bbciplayer.bbciplayer_api.version
string version
Definition: bbciplayer_api.py:77
HLSReader::PlaylistGood
void PlaylistGood(void)
Definition: HLSReader.cpp:1018
HLSReader::m_streamLock
QMutex m_streamLock
Definition: HLSReader.h:119
HLSRecStream::M3U8Url
QString M3U8Url(void) const
Definition: HLSStream.h:44
HLSReader::m_streamWorker
HLSStreamWorker * m_streamWorker
Definition: HLSReader.h:113
GetMythDownloadManager
MythDownloadManager * GetMythDownloadManager(void)
Gets the pointer to the MythDownloadManager singleton.
Definition: mythdownloadmanager.cpp:145
HLSReader.h