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