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