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