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 int HLSReader::Read(uint8_t* buffer, int 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  int 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  StreamContainer::iterator Istream;
358  QString url = RelativeURI(m_segmentBase, uri).toString();
359 
360  if ((Istream = m_streams.find(url)) == 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;
393 
394  if ((Istream = m_streams.find(M3U::DecodedURI(m_m3u8))) ==
395  m_streams.end())
396  {
397  hls = new HLSRecStream(0, 0, m_m3u8, m_segmentBase);
398  if (hls)
399  {
400  LOG(VB_RECORD, LOG_INFO, LOC +
401  QString("Adding new stream '%1'").arg(m_m3u8));
402  Istream = m_streams.insert(m_m3u8, hls);
403  }
404  }
405  else
406  {
407  hls = *Istream;
408  LOG(VB_RECORD, LOG_INFO, LOC +
409  QString("Updating stream '%1'").arg(hls->toString()));
410  }
411 
412  /* Get TARGET-DURATION first */
413  p = buffer.indexOf("#EXT-X-TARGETDURATION:");
414  if (p >= 0)
415  {
416  int duration = 0;
417 
418  text.seek(p);
419  if (!M3U::ParseTargetDuration(text.readLine(), StreamURL(),
420  duration))
421  return false;
422  hls->SetTargetDuration(std::chrono::seconds(duration));
423  }
424  /* Store version */
425  hls->SetVersion(version);
426  }
427  LOG(VB_RECORD, LOG_DEBUG, LOC +
428  QString("%1 Playlist HLS protocol version: %2")
429  .arg(hls->Live() ? "Live": "VOD").arg(version));
430 
431  // rewind
432  text.seek(0);
433 
434  QString title;
435  std::chrono::seconds segment_duration = -1s;
436  int64_t first_sequence = -1;
437  int64_t sequence_num = 0;
438  int skipped = 0;
439 
440  SegmentContainer new_segments;
441 
442  QMutexLocker lock(&m_seqLock);
443  while (!m_cancel)
444  {
445  /* Next line */
446  line = text.readLine();
447  if (line.isNull())
448  break;
449  LOG(VB_RECORD, (m_debug ? LOG_INFO : LOG_DEBUG),
450  LOC + QString("|%1").arg(line));
451 
452  if (line.startsWith(QLatin1String("#EXTINF")))
453  {
454  uint tmp_duration = -1;
455  if (!M3U::ParseSegmentInformation(hls->Version(), line,
456  tmp_duration,
457  title, StreamURL()))
458  return false;
459  segment_duration = std::chrono::seconds(tmp_duration);
460  }
461  else if (line.startsWith(QLatin1String("#EXT-X-TARGETDURATION")))
462  {
463  int duration = 0;
464  if (!M3U::ParseTargetDuration(line, StreamURL(), duration))
465  return false;
466  hls->SetTargetDuration(std::chrono::seconds(duration));
467  }
468  else if (line.startsWith(QLatin1String("#EXT-X-MEDIA-SEQUENCE")))
469  {
470  if (!M3U::ParseMediaSequence(sequence_num, line, StreamURL()))
471  return false;
472  if (first_sequence < 0)
473  first_sequence = sequence_num;
474  }
475  else if (line.startsWith(QLatin1String("#EXT-X-KEY")))
476  {
477 #ifdef USING_LIBCRYPTO
478  QString path;
479  QString iv;
480  if (!M3U::ParseKey(hls->Version(), line, m_aesMsg, StreamURL(),
481  path, iv))
482  return false;
483  if (!path.isEmpty())
484  hls->SetKeyPath(path);
485 
486  if (!iv.isNull() && !hls->SetAESIV(iv))
487  {
488  LOG(VB_RECORD, LOG_ERR, LOC + "invalid IV");
489  return false;
490  }
491 #else
492  LOG(VB_RECORD, LOG_ERR, LOC + "#EXT-X-KEY needs libcrypto");
493  return false;
494 #endif
495  }
496  else if (line.startsWith(QLatin1String("#EXT-X-PROGRAM-DATE-TIME")))
497  {
498  QDateTime date;
499  if (!M3U::ParseProgramDateTime(line, StreamURL(), date))
500  return false;
501  }
502  else if (line.startsWith(QLatin1String("#EXT-X-ALLOW-CACHE")))
503  {
504  bool do_cache = false;
505  if (!M3U::ParseAllowCache(line, StreamURL(), do_cache))
506  return false;
507  hls->SetCache(do_cache);
508  }
509  else if (line.startsWith(QLatin1String("#EXT-X-DISCONTINUITY")))
510  {
511  if (!M3U::ParseDiscontinuity(line, StreamURL()))
512  return false;
513  ResetSequence();
514  }
515  else if (line.startsWith(QLatin1String("#EXT-X-VERSION")))
516  {
517  int version2 = 0;
518  if (!M3U::ParseVersion(line, StreamURL(), version2))
519  return false;
520  hls->SetVersion(version2);
521  }
522  else if (line.startsWith(QLatin1String("#EXT-X-ENDLIST")))
523  {
524  bool is_vod = false;
525  if (!M3U::ParseEndList(StreamURL(), is_vod))
526  return false;
527  hls->SetLive(!is_vod);
528  }
529  else if (!line.startsWith(QLatin1String("#")) && !line.isEmpty())
530  {
531  if (m_curSeq < 0 || sequence_num > m_curSeq)
532  {
533  new_segments.push_back
534  (HLSRecSegment(sequence_num, segment_duration, title,
535  RelativeURI(hls->SegmentBaseUrl(), line)));
536  }
537  else
538  ++skipped;
539 
540  ++sequence_num;
541  segment_duration = -1s; /* reset duration */
542  title.clear();
543  }
544  }
545 
546  if (sequence_num < m_curSeq)
547  {
548  // Sequence has been reset
549  LOG(VB_RECORD, LOG_WARNING, LOC +
550  QString("Sequence number has been reset from %1 to %2")
551  .arg(m_curSeq).arg(first_sequence));
552  ResetSequence();
553  return false;
554  }
555 
556  SegmentContainer::iterator Inew = new_segments.begin();
557  SegmentContainer::iterator Iseg = m_segments.end() - 1;
558 
559  // Does this playlist overlap?
560  if (!m_segments.empty() && !new_segments.empty())
561  {
562  if ((*Iseg).Sequence() >= first_sequence &&
563  (*Iseg).Sequence() < sequence_num)
564  {
565  // Find the beginning of the overlap
566  while (Iseg != m_segments.begin() &&
567  (*Iseg).Sequence() > first_sequence &&
568  (*Iseg).Sequence() < sequence_num)
569  --Iseg;
570 
571  int64_t diff = (*Iseg).Sequence() - (*Inew).Sequence();
572  if (diff >= 0 && new_segments.size() > diff)
573  {
574  Inew += diff;
575 
576  // Update overlapping segment info
577  for ( ; Iseg != m_segments.end(); ++Iseg, ++Inew)
578  {
579  if (Inew == new_segments.end())
580  {
581  LOG(VB_RECORD, LOG_ERR, LOC +
582  QString("Went off the end with %1 left")
583  .arg(m_segments.end() - Iseg));
584  break;
585  }
586  if ((*Iseg).Sequence() != (*Inew).Sequence())
587  {
588  LOG(VB_RECORD, LOG_ERR, LOC +
589  QString("Sequence non-sequential? %1 != %2")
590  .arg((*Iseg).Sequence())
591  .arg((*Inew).Sequence()));
592  break;
593  }
594 
595  (*Iseg).m_duration = (*Inew).Duration();
596  (*Iseg).m_title = (*Inew).Title();
597  (*Iseg).m_url = (*Inew).Url();
598  }
599  }
600  }
601  }
602 
603  for ( ; Inew != new_segments.end(); ++Inew)
604  m_segments.push_back(*Inew);
605 
606  m_playlistSize = new_segments.size() + skipped;
607  int behind = m_segments.size() - m_playlistSize;
608  int max_behind = m_playlistSize / 2;
609  if (behind > max_behind)
610  {
611  LOG(VB_RECORD, LOG_WARNING, LOC +
612  QString("Not downloading fast enough! "
613  "%1 segments behind, skipping %2 segments. "
614  "playlist size: %3, queued: %4")
615  .arg(behind).arg(behind - max_behind)
616  .arg(m_playlistSize).arg(m_segments.size()));
617  m_workerLock.lock();
618  if (m_streamWorker)
620  m_workerLock.unlock();
621 
622  EnableDebugging();
623  Iseg = m_segments.begin() + (behind - max_behind);
624  m_segments.erase(m_segments.begin(), Iseg);
626  }
627  else if (m_debugCnt > 0)
628  --m_debugCnt;
629  else
630  m_debug = false;
631  }
632 
633  LOG(VB_RECORD, LOG_DEBUG, LOC + "ParseM3U8 -- end");
634  return true;
635 }
636 
638 {
639  if (!m_curstream || m_cancel)
640  return false;
641 
642  LOG(VB_RECORD, LOG_DEBUG, LOC +
643  QString("LoadMetaPlaylists stream %1")
644  .arg(m_curstream->toString()));
645 
646  StreamContainer::iterator Istream;
647 
648  m_streamLock.lock();
649  if (m_bandwidthCheck /* && !m_segments.empty() */)
650  {
651  int buffered = PercentBuffered();
652 
653  if (buffered < 15)
654  {
655  // It is taking too long to download the segments
656  LOG(VB_RECORD, LOG_WARNING, LOC +
657  QString("Falling behind: only %1% buffered").arg(buffered));
658  LOG(VB_RECORD, LOG_DEBUG, LOC +
659  QString("playlist size %1, queued %2")
660  .arg(m_playlistSize).arg(m_segments.size()));
661  EnableDebugging();
663  m_bandwidthCheck = false;
664  }
665  else if (buffered > 85)
666  {
667  // Keeping up easily, raise the bitrate.
668  LOG(VB_RECORD, LOG_DEBUG, LOC +
669  QString("Plenty of bandwidth, downloading %1 of %2")
670  .arg(m_playlistSize - m_segments.size())
671  .arg(m_playlistSize));
672  LOG(VB_RECORD, LOG_DEBUG, LOC +
673  QString("playlist size %1, queued %2")
674  .arg(m_playlistSize).arg(m_segments.size()));
676  m_bandwidthCheck = false;
677  }
678  }
679  m_streamLock.unlock();
680 
681  QByteArray buffer;
682 
683 #ifdef HLS_USE_MYTHDOWNLOADMANAGER // MythDownloadManager leaks memory
684  if (!DownloadURL(m_curstream->Url(), &buffer))
685  return false;
686 #else
687  QString redir;
688  if (!downloader.DownloadURL(m_curstream->M3U8Url(), &buffer, 30s, 0, 0, &redir))
689  {
690  LOG(VB_GENERAL, LOG_WARNING,
691  LOC + "Download failed: " + downloader.ErrorString());
692  return false;
693  }
694 #endif
695 
696  if (m_segmentBase != redir)
697  {
698  m_segmentBase = redir;
700  }
701 
702  if (m_cancel || !ParseM3U8(buffer, m_curstream))
703  return false;
704 
705  // Signal SegmentWorker that there is work to do...
707 
708  return true;
709 }
710 
712 {
713  HLSRecStream *hls = nullptr;
714  uint64_t bitrate = m_curstream->Bitrate();
715  uint64_t candidate = 0;
716 
717  for (auto Istream = m_streams.cbegin(); Istream != m_streams.cend(); ++Istream)
718  {
719  if ((*Istream)->Id() != progid)
720  continue;
721  if (bitrate > (*Istream)->Bitrate() &&
722  candidate < (*Istream)->Bitrate())
723  {
724  LOG(VB_RECORD, LOG_DEBUG, LOC +
725  QString("candidate stream '%1' bitrate %2 >= %3")
726  .arg(Istream.key()).arg(bitrate).arg((*Istream)->Bitrate()));
727  hls = *Istream;
728  candidate = hls->Bitrate();
729  }
730  }
731 
732  if (hls)
733  {
734  LOG(VB_RECORD, LOG_INFO, LOC +
735  QString("Switching to a lower bitrate stream %1 -> %2")
736  .arg(bitrate).arg(candidate));
737  m_curstream = hls;
738  }
739  else
740  {
741  LOG(VB_RECORD, LOG_DEBUG, LOC +
742  QString("Already at lowest bitrate %1").arg(bitrate));
743  }
744 }
745 
747 {
748  HLSRecStream *hls = nullptr;
749  uint64_t bitrate = m_curstream->Bitrate();
750  uint64_t candidate = INT_MAX;
751 
752  for (auto Istream = m_streams.cbegin(); Istream != m_streams.cend(); ++Istream)
753  {
754  if ((*Istream)->Id() != progid)
755  continue;
756  if (bitrate < (*Istream)->Bitrate() &&
757  candidate > (*Istream)->Bitrate())
758  {
759  LOG(VB_RECORD, LOG_DEBUG, LOC +
760  QString("candidate stream '%1' bitrate %2 >= %3")
761  .arg(Istream.key()).arg(bitrate).arg((*Istream)->Bitrate()));
762  hls = *Istream;
763  candidate = hls->Bitrate();
764  }
765  }
766 
767  if (hls)
768  {
769  LOG(VB_RECORD, LOG_INFO, LOC +
770  QString("Switching to a higher bitrate stream %1 -> %2")
771  .arg(bitrate).arg(candidate));
772  m_curstream = hls;
773  }
774  else
775  {
776  LOG(VB_RECORD, LOG_DEBUG, LOC +
777  QString("Already at highest bitrate %1").arg(bitrate));
778  }
779 }
780 
782 {
783  LOG(VB_RECORD, LOG_DEBUG, LOC + "LoadSegment -- start");
784 
785  if (!m_curstream)
786  {
787  LOG(VB_RECORD, LOG_ERR, LOC + "LoadSegment: current stream not set.");
789  return false;
790  }
791 
792  HLSRecSegment seg;
793  for (;;)
794  {
795  m_seqLock.lock();
796  if (m_cancel || m_segments.empty())
797  {
798  m_seqLock.unlock();
799  break;
800  }
801 
802  seg = m_segments.front();
803  if (m_segments.size() > m_playlistSize)
804  {
805  LOG(VB_RECORD, (m_debug ? LOG_INFO : LOG_DEBUG), LOC +
806  QString("Downloading segment %1 (1 of %2) with %3 behind")
807  .arg(seg.Sequence())
808  .arg(m_segments.size() + m_playlistSize)
809  .arg(m_segments.size() - m_playlistSize));
810  }
811  else
812  {
813  LOG(VB_RECORD, (m_debug ? LOG_INFO : LOG_DEBUG), LOC +
814  QString("Downloading segment %1 (%2 of %3)")
815  .arg(seg.Sequence())
816  .arg(m_playlistSize - m_segments.size() + 1)
817  .arg(m_playlistSize));
818  }
819  m_seqLock.unlock();
820 
821  m_streamLock.lock();
822  HLSRecStream *hls = m_curstream;
823  m_streamLock.unlock();
824  if (!hls)
825  {
826  LOG(VB_RECORD, LOG_DEBUG, LOC + "LoadSegment -- no current stream");
827  return false;
828  }
829 
830  long throttle = DownloadSegmentData(downloader,hls,seg,m_playlistSize);
831 
832  m_seqLock.lock();
833  if (throttle < 0)
834  {
835  if (m_segments.size() > m_playlistSize)
836  {
837  SegmentContainer::iterator Iseg = m_segments.begin() +
838  (m_segments.size() - m_playlistSize);
839  m_segments.erase(m_segments.begin(), Iseg);
840  }
841  m_seqLock.unlock();
842  return false;
843  }
844 
845  m_curSeq = m_segments.front().Sequence();
846  m_segments.pop_front();
847 
848  m_seqLock.unlock();
849 
850  if (m_throttle && throttle == 0)
851  throttle = 2;
852  else if (throttle > 8)
853  throttle = 8;
854  if (throttle > 0)
855  {
856  LOG(VB_RECORD, LOG_INFO, LOC +
857  QString("Throttling -- sleeping %1 secs.")
858  .arg(throttle));
859  throttle *= 1000;
860  m_throttleLock.lock();
861  if (m_throttleCond.wait(&m_throttleLock, throttle))
862  LOG(VB_RECORD, LOG_INFO, LOC + "Throttle aborted");
863  m_throttleLock.unlock();
864  LOG(VB_RECORD, LOG_INFO, LOC + "Throttle done");
865  }
866  else
867  usleep(5000);
868 
869  if (m_prebufferCnt == 0)
870  {
872  m_prebufferCnt = 2;
873  }
874  else
875  --m_prebufferCnt;
876  }
877 
878  LOG(VB_RECORD, LOG_DEBUG, LOC + "LoadSegment -- end");
879  return true;
880 }
881 
883 {
884  if (m_playlistSize == 0 || m_segments.size() > m_playlistSize)
885  return 0;
886  return (static_cast<float>(m_playlistSize - m_segments.size()) /
887  static_cast<float>(m_playlistSize)) * 100.0F;
888 }
889 
891  HLSRecStream* hls,
892  const HLSRecSegment& segment, int playlist_size)
893 {
894  uint64_t bandwidth = hls->AverageBandwidth();
895 
896  LOG(VB_RECORD, LOG_DEBUG, LOC +
897  QString("Downloading %1 bandwidth %2 bitrate %3")
898  .arg(segment.Sequence()).arg(bandwidth).arg(hls->Bitrate()));
899 
900  /* sanity check - can we download this segment on time? */
901  if ((bandwidth > 0) && (hls->Bitrate() > 0))
902  {
903  uint64_t size = (segment.Duration().count() * hls->Bitrate()); /* bits */
904  auto estimated_time = std::chrono::seconds(size / bandwidth);
905  if (estimated_time > segment.Duration())
906  {
907  LOG(VB_RECORD, LOG_WARNING, LOC +
908  QString("downloading of %1 will take %2s, "
909  "which is longer than its playback (%3s) at %4kiB/s")
910  .arg(segment.Sequence())
911  .arg(estimated_time.count())
912  .arg(segment.Duration().count())
913  .arg(bandwidth / 8192));
914  }
915  }
916 
917  QByteArray buffer;
918  auto start = nowAsDuration<std::chrono::milliseconds>();
919 
920 #ifdef HLS_USE_MYTHDOWNLOADMANAGER // MythDownloadManager leaks memory
921  // and can only handle six download at a time
922  if (!HLSReader::DownloadURL(segment.Url(), &buffer))
923  {
924  LOG(VB_RECORD, LOG_ERR, LOC +
925  QString("%1 failed").arg(segment.Sequence()));
926  if (estimated_time * 2 < segment.Duration())
927  {
928  if (!HLSReader::DownloadURL(segment.Url(), &buffer))
929  {
930  LOG(VB_RECORD, LOG_ERR, LOC + QString("%1 failed")
931  .arg(segment.Sequence()));
932  return -1;
933  }
934  }
935  else
936  return 0;
937  }
938 #else
939  if (!downloader.DownloadURL(segment.Url(), &buffer))
940  {
941  LOG(VB_RECORD, LOG_ERR, LOC + QString("%1 failed: %2")
942  .arg(segment.Sequence()).arg(downloader.ErrorString()));
943  return -1;
944  }
945 #endif
946 
947  auto downloadduration = nowAsDuration<std::chrono::milliseconds>() - start;
948 
949 #ifdef USING_LIBCRYPTO
950  /* If the segment is encrypted, decode it */
951  if (segment.HasKeyPath())
952  {
953  if (!hls->DecodeData(downloader, hls->IVLoaded() ? hls->AESIV() : QByteArray(),
954  segment.KeyPath(),
955  buffer, segment.Sequence()))
956  return 0;
957  }
958 #endif
959 
960  int segment_len = buffer.size();
961 
962  m_bufLock.lock();
963  if (m_buffer.size() > segment_len * playlist_size)
964  {
965  LOG(VB_RECORD, LOG_WARNING, LOC +
966  QString("streambuffer is not reading fast enough. "
967  "buffer size %1").arg(m_buffer.size()));
968  EnableDebugging();
969  if (++m_slowCnt > 15)
970  {
971  m_slowCnt = 15;
972  m_fatal = true;
973  return -1;
974  }
975  }
976  else if (m_slowCnt > 0)
977  --m_slowCnt;
978 
979  if (m_buffer.size() >= segment_len * playlist_size * 2)
980  {
981  LOG(VB_RECORD, LOG_WARNING, LOC +
982  QString("streambuffer is not reading fast enough. "
983  "buffer size %1. Dropping %2 bytes")
984  .arg(m_buffer.size()).arg(segment_len));
985  m_buffer.remove(0, segment_len);
986  }
987 
988  m_buffer += buffer;
989  m_bufLock.unlock();
990 
991  if (hls->Bitrate() == 0 && segment.Duration() > 0s)
992  {
993  /* Try to estimate the bandwidth for this stream */
994  hls->SetBitrate((uint64_t)(((double)segment_len * 8) /
995  ((double)segment.Duration().count())));
996  }
997 
998  if (downloadduration < 1ms)
999  downloadduration = 1ms;
1000 
1001  /* bits/sec */
1002  bandwidth = segment_len * 8 * 1000ULL / downloadduration.count();
1003  hls->AverageBandwidth(bandwidth);
1004  hls->SetCurrentByteRate(static_cast<uint64_t>
1005  ((static_cast<double>(segment_len) /
1006  static_cast<double>(segment.Duration().count()))));
1007 
1008  LOG(VB_RECORD, (m_debug ? LOG_INFO : LOG_DEBUG), LOC +
1009  QString("%1 took %3ms for %4 bytes: "
1010  "bandwidth:%5kiB/s")
1011  .arg(segment.Sequence())
1012  .arg(downloadduration.count())
1013  .arg(segment_len)
1014  .arg(bandwidth / 8192.0));
1015 
1016  return m_slowCnt;
1017 }
1018 
1020 {
1021  QMutexLocker lock(&m_streamLock);
1022  if (m_curstream)
1023  m_curstream->Good();
1024 }
1025 
1027 {
1028  QMutexLocker lock(&m_streamLock);
1029  if (m_curstream)
1030  m_curstream->Retrying();
1031 }
1032 
1034 {
1035  QMutexLocker lock(&m_streamLock);
1036  if (m_curstream)
1037  return m_curstream->RetryCount();
1038  return 0;
1039 }
1040 
1042 {
1043  m_debug = true;
1044  m_debugCnt = 5;
1045  LOG(VB_RECORD, LOG_INFO, LOC + "Debugging enabled");
1046 }
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
HLSRecStream::RetryCount
int RetryCount(void) const
Definition: HLSStream.h:56
HLSReader::IncreaseBitrate
void IncreaseBitrate(int progid)
Definition: HLSReader.cpp:746
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:1033
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
HLSReader::Read
int Read(uint8_t *buffer, int len)
Definition: HLSReader.cpp:210
LOG
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:23
HLSReader::ResetSequence
void ResetSequence(void)
Definition: HLSReader.h:58
MythDownloadManager
Definition: mythdownloadmanager.h:50
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:637
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:44
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: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:781
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:1041
HLSReader::PercentBuffered
uint PercentBuffered(void) const
Definition: HLSReader.cpp:882
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:376
HLSReader::DownloadSegmentData
int DownloadSegmentData(MythSingleDownload &downloader, HLSRecStream *hls, const HLSRecSegment &segment, int playlist_size)
Definition: HLSReader.cpp:890
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:70
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:1026
HLSRecSegment
Definition: HLSSegment.h:11
HLSReader::DecreaseBitrate
void DecreaseBitrate(int progid)
Definition: HLSReader.cpp:711
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: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: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:40
HLSRecStream::Id
int Id(void) const
Definition: HLSStream.h:31
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: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:1019
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