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 
710  for (auto Istream = m_streams.cbegin(); Istream != m_streams.cend(); ++Istream)
711  {
712  if ((*Istream)->Id() != progid)
713  continue;
714  if (bitrate > (*Istream)->Bitrate() &&
715  candidate < (*Istream)->Bitrate())
716  {
717  LOG(VB_RECORD, LOG_DEBUG, LOC +
718  QString("candidate stream '%1' bitrate %2 >= %3")
719  .arg(Istream.key()).arg(bitrate).arg((*Istream)->Bitrate()));
720  hls = *Istream;
721  candidate = hls->Bitrate();
722  }
723  }
724 
725  if (hls)
726  {
727  LOG(VB_RECORD, LOG_INFO, LOC +
728  QString("Switching to a lower bitrate stream %1 -> %2")
729  .arg(bitrate).arg(candidate));
730  m_curstream = hls;
731  }
732  else
733  {
734  LOG(VB_RECORD, LOG_DEBUG, LOC +
735  QString("Already at lowest bitrate %1").arg(bitrate));
736  }
737 }
738 
740 {
741  HLSRecStream *hls = nullptr;
742  uint64_t bitrate = m_curstream->Bitrate();
743  uint64_t candidate = INT_MAX;
744 
745  for (auto Istream = m_streams.cbegin(); Istream != m_streams.cend(); ++Istream)
746  {
747  if ((*Istream)->Id() != progid)
748  continue;
749  if (bitrate < (*Istream)->Bitrate() &&
750  candidate > (*Istream)->Bitrate())
751  {
752  LOG(VB_RECORD, LOG_DEBUG, LOC +
753  QString("candidate stream '%1' bitrate %2 >= %3")
754  .arg(Istream.key()).arg(bitrate).arg((*Istream)->Bitrate()));
755  hls = *Istream;
756  candidate = hls->Bitrate();
757  }
758  }
759 
760  if (hls)
761  {
762  LOG(VB_RECORD, LOG_INFO, LOC +
763  QString("Switching to a higher bitrate stream %1 -> %2")
764  .arg(bitrate).arg(candidate));
765  m_curstream = hls;
766  }
767  else
768  {
769  LOG(VB_RECORD, LOG_DEBUG, LOC +
770  QString("Already at highest bitrate %1").arg(bitrate));
771  }
772 }
773 
775 {
776  LOG(VB_RECORD, LOG_DEBUG, LOC + "LoadSegment -- start");
777 
778  if (!m_curstream)
779  {
780  LOG(VB_RECORD, LOG_ERR, LOC + "LoadSegment: current stream not set.");
782  return false;
783  }
784 
785  HLSRecSegment seg;
786  for (;;)
787  {
788  m_seqLock.lock();
789  if (m_cancel || m_segments.empty())
790  {
791  m_seqLock.unlock();
792  break;
793  }
794 
795  seg = m_segments.front();
796  if (m_segments.size() > m_playlistSize)
797  {
798  LOG(VB_RECORD, (m_debug ? LOG_INFO : LOG_DEBUG), LOC +
799  QString("Downloading segment %1 (1 of %2) with %3 behind")
800  .arg(seg.Sequence())
801  .arg(m_segments.size() + m_playlistSize)
802  .arg(m_segments.size() - m_playlistSize));
803  }
804  else
805  {
806  LOG(VB_RECORD, (m_debug ? LOG_INFO : LOG_DEBUG), LOC +
807  QString("Downloading segment %1 (%2 of %3)")
808  .arg(seg.Sequence())
809  .arg(m_playlistSize - m_segments.size() + 1)
810  .arg(m_playlistSize));
811  }
812  m_seqLock.unlock();
813 
814  m_streamLock.lock();
815  HLSRecStream *hls = m_curstream;
816  m_streamLock.unlock();
817  if (!hls)
818  {
819  LOG(VB_RECORD, LOG_DEBUG, LOC + "LoadSegment -- no current stream");
820  return false;
821  }
822 
823  long throttle = DownloadSegmentData(downloader,hls,seg,m_playlistSize);
824 
825  m_seqLock.lock();
826  if (throttle < 0)
827  {
828  if (m_segments.size() > m_playlistSize)
829  {
830  SegmentContainer::iterator Iseg = m_segments.begin() +
831  (m_segments.size() - m_playlistSize);
832  m_segments.erase(m_segments.begin(), Iseg);
833  }
834  m_seqLock.unlock();
835  return false;
836  }
837 
838  m_curSeq = m_segments.front().Sequence();
839  m_segments.pop_front();
840 
841  m_seqLock.unlock();
842 
843  if (m_throttle && throttle == 0)
844  throttle = 2;
845  else if (throttle > 8)
846  throttle = 8;
847  if (throttle > 0)
848  {
849  LOG(VB_RECORD, LOG_INFO, LOC +
850  QString("Throttling -- sleeping %1 secs.")
851  .arg(throttle));
852  throttle *= 1000;
853  m_throttleLock.lock();
854  if (m_throttleCond.wait(&m_throttleLock, throttle))
855  LOG(VB_RECORD, LOG_INFO, LOC + "Throttle aborted");
856  m_throttleLock.unlock();
857  LOG(VB_RECORD, LOG_INFO, LOC + "Throttle done");
858  }
859  else
860  usleep(5000);
861 
862  if (m_prebufferCnt == 0)
863  {
865  m_prebufferCnt = 2;
866  }
867  else
868  --m_prebufferCnt;
869  }
870 
871  LOG(VB_RECORD, LOG_DEBUG, LOC + "LoadSegment -- end");
872  return true;
873 }
874 
876 {
877  if (m_playlistSize == 0 || m_segments.size() > m_playlistSize)
878  return 0;
879  return (static_cast<float>(m_playlistSize - m_segments.size()) /
880  static_cast<float>(m_playlistSize)) * 100.0F;
881 }
882 
884  HLSRecStream* hls,
885  const HLSRecSegment& segment, int playlist_size)
886 {
887  uint64_t bandwidth = hls->AverageBandwidth();
888 
889  LOG(VB_RECORD, LOG_DEBUG, LOC +
890  QString("Downloading %1 bandwidth %2 bitrate %3")
891  .arg(segment.Sequence()).arg(bandwidth).arg(hls->Bitrate()));
892 
893  /* sanity check - can we download this segment on time? */
894  if ((bandwidth > 0) && (hls->Bitrate() > 0))
895  {
896  uint64_t size = (segment.Duration() * hls->Bitrate()); /* bits */
897  int estimated_time = (int)(size / bandwidth);
898  if (estimated_time > segment.Duration())
899  {
900  LOG(VB_RECORD, LOG_WARNING, LOC +
901  QString("downloading of %1 will take %2s, "
902  "which is longer than its playback (%3s) at %4kiB/s")
903  .arg(segment.Sequence())
904  .arg(estimated_time)
905  .arg(segment.Duration())
906  .arg(bandwidth / 8192));
907  }
908  }
909 
910  QByteArray buffer;
911  uint64_t start = MDate();
912 
913 #ifdef HLS_USE_MYTHDOWNLOADMANAGER // MythDownloadManager leaks memory
914  // and can only handle six download at a time
915  if (!HLSReader::DownloadURL(segment.Url(), &buffer))
916  {
917  LOG(VB_RECORD, LOG_ERR, LOC +
918  QString("%1 failed").arg(segment.Sequence()));
919  if (estimated_time * 2 < segment.Duration())
920  {
921  if (!HLSReader::DownloadURL(segment.Url(), &buffer))
922  {
923  LOG(VB_RECORD, LOG_ERR, LOC + QString("%1 failed")
924  .arg(segment.Sequence()));
925  return -1;
926  }
927  }
928  else
929  return 0;
930  }
931 #else
932  if (!downloader.DownloadURL(segment.Url(), &buffer))
933  {
934  LOG(VB_RECORD, LOG_ERR, LOC + QString("%1 failed: %2")
935  .arg(segment.Sequence()).arg(downloader.ErrorString()));
936  return -1;
937  }
938 #endif
939 
940  uint64_t downloadduration = (MDate() - start) / 1000;
941 
942 #ifdef USING_LIBCRYPTO
943  /* If the segment is encrypted, decode it */
944  if (segment.HasKeyPath())
945  {
946  if (!hls->DecodeData(downloader, hls->IVLoaded() ? hls->AESIV() : QByteArray(),
947  segment.KeyPath(),
948  buffer, segment.Sequence()))
949  return 0;
950  }
951 #endif
952 
953  int segment_len = buffer.size();
954 
955  m_bufLock.lock();
956  if (m_buffer.size() > segment_len * playlist_size)
957  {
958  LOG(VB_RECORD, LOG_WARNING, LOC +
959  QString("streambuffer is not reading fast enough. "
960  "buffer size %1").arg(m_buffer.size()));
961  EnableDebugging();
962  if (++m_slowCnt > 15)
963  {
964  m_slowCnt = 15;
965  m_fatal = true;
966  return -1;
967  }
968  }
969  else if (m_slowCnt > 0)
970  --m_slowCnt;
971 
972  if (m_buffer.size() >= segment_len * playlist_size * 2)
973  {
974  LOG(VB_RECORD, LOG_WARNING, LOC +
975  QString("streambuffer is not reading fast enough. "
976  "buffer size %1. Dropping %2 bytes")
977  .arg(m_buffer.size()).arg(segment_len));
978  m_buffer.remove(0, segment_len);
979  }
980 
981  m_buffer += buffer;
982  m_bufLock.unlock();
983 
984  if (hls->Bitrate() == 0 && segment.Duration() > 0)
985  {
986  /* Try to estimate the bandwidth for this stream */
987  hls->SetBitrate((uint64_t)(((double)segment_len * 8) /
988  ((double)segment.Duration())));
989  }
990 
991  if (downloadduration < 1)
992  downloadduration = 1;
993 
994  /* bits/sec */
995  bandwidth = segment_len * 8 * 1000ULL / downloadduration;
996  hls->AverageBandwidth(bandwidth);
997  hls->SetCurrentByteRate(static_cast<uint64_t>
998  ((static_cast<double>(segment_len) /
999  static_cast<double>(segment.Duration()))));
1000 
1001  LOG(VB_RECORD, (m_debug ? LOG_INFO : LOG_DEBUG), LOC +
1002  QString("%1 took %3ms for %4 bytes: "
1003  "bandwidth:%5kiB/s")
1004  .arg(segment.Sequence())
1005  .arg(downloadduration)
1006  .arg(segment_len)
1007  .arg(bandwidth / 8192.0));
1008 
1009  return m_slowCnt;
1010 }
1011 
1013 {
1014  QMutexLocker lock(&m_streamLock);
1015  if (m_curstream)
1016  m_curstream->Good();
1017 }
1018 
1020 {
1021  QMutexLocker lock(&m_streamLock);
1022  if (m_curstream)
1023  m_curstream->Retrying();
1024 }
1025 
1027 {
1028  QMutexLocker lock(&m_streamLock);
1029  if (m_curstream)
1030  return m_curstream->RetryCount();
1031  return 0;
1032 }
1033 
1035 {
1036  m_debug = true;
1037  m_debugCnt = 5;
1038  LOG(VB_RECORD, LOG_INFO, LOC + "Debugging enabled");
1039 }
M3U::ParseProgramDateTime
bool ParseProgramDateTime(const QString &line, const QString &loc, QDateTime &)
Definition: m3u.cpp:365
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:289
HLSRecStream::Version
int Version(void) const
Definition: HLSStream.h:32
HLSReader::m_seqLock
QMutex m_seqLock
Definition: HLSReader.h:118
HLSReader::m_segments
SegmentContainer m_segments
Definition: HLSReader.h:101
HLSReader::SegmentContainer
QVector< HLSRecSegment > SegmentContainer
Definition: HLSReader.h:41
title
QString title
Definition: mythplugins/mytharchive/mytharchivehelper/main.cpp:639
HLSRecStream::RetryCount
int RetryCount(void) const
Definition: HLSStream.h:56
HLSReader::IncreaseBitrate
void IncreaseBitrate(int progid)
Definition: HLSReader.cpp:739
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:1026
arg
arg(title).arg(filename).arg(doDelete))
HLSReader::m_playlistWorker
HLSPlaylistWorker * m_playlistWorker
Definition: HLSReader.h:112
HLSStreamWorker::Cancel
void Cancel(void)
Definition: HLSStreamWorker.cpp:18
HLSReader::m_debugCnt
int m_debugCnt
Definition: HLSReader.h:124
HLSReader::m_debug
bool m_debug
Definition: HLSReader.h:123
RelativeURI
static QUrl RelativeURI(const QString &baseString, const QString &uriString)
Handles relative URLs without breaking URI encoded parameters by avoiding storing the decoded URL in ...
Definition: HLSReader.cpp:14
HLSReader::Read
int Read(uint8_t *buffer, int len)
Definition: HLSReader.cpp:205
LOG
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:23
HLSReader::ResetSequence
void ResetSequence(void)
Definition: HLSReader.h:58
MythDownloadManager
Definition: mythdownloadmanager.h:48
HLSReader::StreamURL
QString StreamURL(void) const
Definition: HLSReader.h:60
HLSRecStream::Good
void Good(void)
Definition: HLSStream.h:54
HLSRecStream
Definition: HLSStream.h:17
HLSReader::CancelURL
static void CancelURL(const QString &url)
HLSReader::m_curSeq
int64_t m_curSeq
Definition: HLSReader.h:103
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
HLSRecStream::SetTargetDuration
void SetTargetDuration(int x)
Definition: HLSStream.h:35
HLSReader::LoadMetaPlaylists
bool LoadMetaPlaylists(MythSingleDownload &downloader)
Definition: HLSReader.cpp:630
HLSStreamWorker::CancelCurrentDownload
void CancelCurrentDownload(void)
Definition: HLSStreamWorker.cpp:28
HLSReader::Cancel
void Cancel(bool quiet=false)
Definition: HLSReader.cpp:162
HLSReader::m_bufLock
QMutex m_bufLock
Definition: HLSReader.h:129
HLSRecStream::SetLive
void SetLive(bool x)
Definition: HLSStream.h:44
MDate
static uint64_t MDate(void)
Definition: HLSReader.cpp:22
HLSRecSegment::Url
QUrl Url(void) const
Definition: HLSSegment.h:28
HLSReader::IsValidPlaylist
static bool IsValidPlaylist(QTextStream &text)
Definition: HLSReader.cpp:256
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:279
HLSReader::LoadSegments
bool LoadSegments(MythSingleDownload &downloader)
Definition: HLSReader.cpp:774
HLSRecStream::Retrying
void Retrying(void)
Definition: HLSStream.h:55
HLSRecSegment::Duration
int Duration(void) const
Definition: HLSSegment.h:29
hardwareprofile.config.p
p
Definition: config.py:33
HLSRecStream::SetBitrate
void SetBitrate(uint64_t bitrate)
Definition: HLSStream.h:38
hardwareprofile.i18n.t
t
Definition: i18n.py:36
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:1034
HLSReader::PercentBuffered
uint PercentBuffered(void) const
Definition: HLSReader.cpp:875
HLSReader::ParseM3U8
bool ParseM3U8(const QByteArray &buffer, HLSRecStream *stream=nullptr)
Definition: HLSReader.cpp:289
M3U::ParseAllowCache
bool ParseAllowCache(const QString &line, const QString &loc, bool &do_cache)
Definition: m3u.cpp:377
HLSReader::DownloadSegmentData
int DownloadSegmentData(MythSingleDownload &downloader, HLSRecStream *hls, const HLSRecSegment &segment, int playlist_size)
Definition: HLSReader.cpp:883
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:432
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:73
M3U::ParseDiscontinuity
bool ParseDiscontinuity(const QString &line, const QString &loc)
Definition: m3u.cpp:408
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:1019
HLSRecSegment
Definition: HLSSegment.h:10
HLSReader::DecreaseBitrate
void DecreaseBitrate(int progid)
Definition: HLSReader.cpp:704
HLSReader::m_prebufferCnt
uint m_prebufferCnt
Definition: HLSReader.h:117
HLSReader::m_fatal
bool m_fatal
Definition: HLSReader.h:106
LOC
#define LOC
Definition: HLSReader.cpp:7
MythSingleDownload
Definition: mythsingledownload.h:24
HLSReader::Close
void Close(bool quiet=false)
Definition: HLSReader.cpp:138
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::DownloadURL
bool DownloadURL(const QUrl &url, QByteArray *buffer, uint timeout=30000, uint redirs=0, qint64 maxsize=0, QString *final_url=nullptr)
Definition: mythsingledownload.cpp:14
MythSingleDownload::ErrorString
QString ErrorString(void) const
Definition: mythsingledownload.h:34
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:26
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:191
HLSReader::Open
bool Open(const QString &m3u, int bitrate_index=0)
Definition: HLSReader.cpp:36
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:29
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:416
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:1012
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:147
HLSReader.h