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