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