MythTV  master
httplivestreambuffer.cpp
Go to the documentation of this file.
1 /*****************************************************************************
2  * httplivestreambuffer.cpp
3  * MythTV
4  *
5  * Created by Jean-Yves Avenard on 6/05/12.
6  * Copyright (c) 2012 Bubblestuff Pty Ltd. All rights reserved.
7  *
8  * Based on httplive.c by Jean-Paul Saman <jpsaman _AT_ videolan _DOT_ org>
9  *
10  * This program is free software; you can redistribute it and/or modify
11  * it under the terms of the GNU General Public License as published by
12  * the Free Software Foundation; either version 2 of the License, or
13  * (at your option) any later version.
14  *
15  * This program is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18  * GNU General Public License for more details.
19  *
20  * You should have received a copy of the GNU General Public License
21  * along with this program; if not, write to the Free Software
22  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
23  *****************************************************************************/
24 
25 
26 
27 // QT
28 #include <QObject>
29 #include <QString>
30 #include <QStringList>
31 #include <QtAlgorithms>
32 #if QT_VERSION >= QT_VERSION_CHECK(6,0,0)
33 #include <QStringConverter>
34 #endif
35 #include <QUrl>
36 
37 // C++
38 #include <algorithm> // for min/max
39 using std::max;
40 using std::min;
41 
42 // libmythbase
43 #include "mthread.h"
44 #include "mythdownloadmanager.h"
45 #include "mythlogging.h"
46 
47 // libmythtv
48 #include "httplivestreambuffer.h"
49 
50 #ifdef USING_LIBCRYPTO
51 // encryption related stuff
52 #include <openssl/aes.h>
53 #define AES_BLOCK_SIZE 16 // HLS only support AES-128
54 using aesiv_array = std::array<uint8_t,AES_BLOCK_SIZE>;
55 #endif
56 
57 #define LOC QString("HLSBuffer: ")
58 
59 // Constants
60 #define PLAYBACK_MINBUFFER 2 // number of segments to prefetch before playback starts
61 #define PLAYBACK_READAHEAD 6 // number of segments download queue ahead of playback
62 #define PLAYLIST_FAILURE 6 // number of consecutive failures after which
63  // playback will abort
64 enum
65 {
66  RET_ERROR = -1,
67  RET_OK = 0,
68 };
69 
70 /* utility methods */
71 
72 static QString decoded_URI(const QString &uri)
73 {
74  QByteArray ba = uri.toLatin1();
75  QUrl url = QUrl::fromEncoded(ba);
76  return url.toString();
77 }
78 
79 static QString relative_URI(const QString &surl, const QString &spath)
80 {
81  QUrl url = QUrl(surl);
82  QUrl path = QUrl(spath);
83 
84  if (!path.isRelative())
85  {
86  return spath;
87  }
88  return url.resolved(path).toString();
89 }
90 
91 static std::chrono::microseconds mdate(void)
92 {
93  return nowAsDuration<std::chrono::microseconds>();
94 }
95 
96 static bool downloadURL(const QString &url, QByteArray *buffer, QString &finalURL)
97 {
99  return mdm->download(url, buffer, false, &finalURL);
100 }
101 
102 static bool downloadURL(const QString &url, QByteArray *buffer)
103 {
105  return mdm->download(url, buffer);
106 }
107 
108 static void cancelURL(const QString &url)
109 {
111  mdm->cancelDownload(url);
112 }
113 
114 static void cancelURL(const QStringList &urls)
115 {
117  mdm->cancelDownload(urls);
118 }
119 
120 /* segment container */
121 
123 {
124  public:
125  HLSSegment(const std::chrono::seconds mduration, const int id, QString title,
126  QString uri, QString current_key_path)
127  : m_id(id),
128  m_duration(mduration), // Seconds
129  m_title(std::move(title)),
130  m_url(std::move(uri))
131  {
132 #ifdef USING_LIBCRYPTO
133  m_pszKeyPath = std::move(current_key_path);
134 #else
135  Q_UNUSED(current_key_path);
136 #endif
137  }
138 
139  ~HLSSegment() = default;
140 
141  HLSSegment(const HLSSegment &rhs)
142  {
143  *this = rhs;
144  }
145 
147  {
148  if (this == &rhs)
149  return *this;
150  m_id = rhs.m_id;
151  m_duration = rhs.m_duration;
152  m_bitrate = rhs.m_bitrate;
153  m_url = rhs.m_url;
154  // keep the old data downloaded
155  // m_data = m_data;
156  // m_played = m_played;
157  m_title = rhs.m_title;
158 #ifdef USING_LIBCRYPTO
159  m_pszKeyPath = rhs.m_pszKeyPath;
160  memcpy(&m_aeskey, &(rhs.m_aeskey), sizeof(m_aeskey));
161  m_keyloaded = rhs.m_keyloaded;
162 #endif
164  return *this;
165  }
166 
167  std::chrono::seconds Duration(void) const
168  {
169  return m_duration;
170  }
171 
172  int Id(void) const
173  {
174  return m_id;
175  }
176 
177  void Lock(void)
178  {
179  m_lock.lock();
180  }
181 
182  void Unlock(void)
183  {
184  m_lock.unlock();
185  }
186 
187  bool IsEmpty(void) const
188  {
189  return m_data.isEmpty();
190  }
191 
192  int32_t Size(void) const
193  {
194  return m_data.size();
195  }
196 
197  int Download(void)
198  {
199  // must own lock
200  m_downloading = true;
201  bool ret = downloadURL(m_url, &m_data);
202  m_downloading = false;
203  // didn't succeed, clear buffer
204  if (!ret)
205  {
206  m_data.clear();
207  return RET_ERROR;
208  }
209  return RET_OK;
210  }
211 
212  void CancelDownload(void)
213  {
214  if (m_downloading)
215  {
216  cancelURL(m_url);
217  QMutexLocker lock(&m_lock);
218  m_downloading = false;
219  }
220  }
221 
222  QString Url(void) const
223  {
224  return m_url;
225  }
226 
227  int32_t SizePlayed(void) const
228  {
229  return m_played;
230  }
231 
232  uint32_t Read(uint8_t *buffer, int32_t length, FILE *fd = nullptr)
233  {
234  int32_t left = m_data.size() - m_played;
235  if (length > left)
236  {
237  length = left;
238  }
239  if (buffer != nullptr)
240  {
241  memcpy(buffer, m_data.constData() + m_played, length);
242  // write data to disk if required
243  if (fd)
244  {
245  fwrite(m_data.constData() + m_played, length, 1, fd);
246  }
247  }
248  m_played += length;
249  return length;
250  }
251 
252  void Reset(void)
253  {
254  m_played = 0;
255  }
256 
257  void Clear(void)
258  {
259  m_played = 0;
260  m_data.clear();
261  }
262 
263  QString Title(void) const
264  {
265  return m_title;
266  }
267  void SetTitle(const QString &x)
268  {
269  m_title = x;
270  }
274  const char *Data(void) const
275  {
276  return m_data.constData();
277  }
278 
279 #ifdef USING_LIBCRYPTO
280  int DownloadKey(void)
281  {
282  // must own lock
283  if (m_keyloaded)
284  return RET_OK;
285  QByteArray key;
286  bool ret = downloadURL(m_pszKeyPath, &key);
287  if (!ret || key.size() != AES_BLOCK_SIZE)
288  {
289  if (ret)
290  {
291  LOG(VB_PLAYBACK, LOG_ERR, LOC +
292  QString("The AES key loaded doesn't have the right size (%1)")
293  .arg(key.size()));
294  }
295  else
296  {
297  LOG(VB_PLAYBACK, LOG_ERR, LOC + "Failed to download AES key");
298  }
299  return RET_ERROR;
300  }
301  AES_set_decrypt_key((const unsigned char*)key.constData(), 128, &m_aeskey);
302  m_keyloaded = true;
303  return RET_OK;
304  }
305 
306  int DecodeData(const aesiv_array IV, bool iv_valid)
307  {
308  /* Decrypt data using AES-128 */
309  int aeslen = m_data.size() & ~0xf;
310  aesiv_array iv {};
311  char *decrypted_data = new char[m_data.size()];
312  if (!iv_valid)
313  {
314  /*
315  * If the EXT-X-KEY tag does not have the IV attribute, implementations
316  * MUST use the sequence number of the media file as the IV when
317  * encrypting or decrypting that media file. The big-endian binary
318  * representation of the sequence number SHALL be placed in a 16-octet
319  * buffer and padded (on the left) with zeros.
320  */
321  iv[15] = m_id & 0xff;
322  iv[14] = (m_id >> 8) & 0xff;
323  iv[13] = (m_id >> 16) & 0xff;
324  iv[12] = (m_id >> 24) & 0xff;
325  }
326  else
327  {
328  std::copy(IV.cbegin(), IV.cend(), iv.begin());
329  }
330  AES_cbc_encrypt((unsigned char*)m_data.constData(),
331  (unsigned char*)decrypted_data, aeslen,
332  &m_aeskey, iv.data(), AES_DECRYPT);
333  memcpy(decrypted_data + aeslen, m_data.constData() + aeslen,
334  m_data.size() - aeslen);
335 
336  // remove the PKCS#7 padding from the buffer
337  // NOLINTNEXTLINE(bugprone-signed-char-misuse)
338  int pad = decrypted_data[m_data.size()-1];
339  if (pad <= 0 || pad > AES_BLOCK_SIZE)
340  {
341  LOG(VB_PLAYBACK, LOG_ERR, LOC +
342  QString("bad padding character (0x%1)").arg(pad, 0, 16, QLatin1Char('0')));
343  delete[] decrypted_data;
344  return RET_ERROR;
345  }
346  aeslen = m_data.size() - pad;
347  m_data = QByteArray(decrypted_data, aeslen);
348  delete[] decrypted_data;
349 
350  return RET_OK;
351  }
352 
353  bool HasKeyPath(void) const
354  {
355  return !m_pszKeyPath.isEmpty();
356  }
357 
358  bool KeyLoaded(void) const
359  {
360  return m_keyloaded;
361  }
362 
363  QString KeyPath(void) const
364  {
365  return m_pszKeyPath;
366  }
367 
368  void SetKeyPath(const QString &path)
369  {
370  m_pszKeyPath = path;
371  }
372 
373  void CopyAESKey(const HLSSegment &segment)
374  {
375  memcpy(&m_aeskey, &(segment.m_aeskey), sizeof(m_aeskey));
376  m_keyloaded = segment.m_keyloaded;
377  }
378 private:
379  AES_KEY m_aeskey {}; // AES-128 key
380  bool m_keyloaded {false};
381  QString m_pszKeyPath; // URL key path
382 #endif
383 
384 private:
385  int m_id {0}; // unique sequence number
386  std::chrono::seconds m_duration {0s}; // segment duration
387  uint64_t m_bitrate {0}; // bitrate of segment's content (bits per second)
388  QString m_title; // human-readable informative title of the media segment
389 
390  QString m_url;
391  QByteArray m_data; // raw data
392  int32_t m_played {0}; // bytes counter of data already read from segment
393  QMutex m_lock;
394  bool m_downloading {false};
395 };
396 
397 /* stream class */
398 
400 {
401  public:
402  HLSStream(const int mid, const uint64_t bitrate, QString uri)
403  : m_id(mid),
404  m_bitrate(bitrate),
405  m_url(std::move(uri))
406  {
407 #ifdef USING_LIBCRYPTO
408  m_aesIv.fill(0);
409 #endif
410  }
411 
412  HLSStream(const HLSStream &rhs, bool copy = true)
413  {
414  (*this) = rhs;
415  if (!copy)
416  return;
417  // copy all the segments across
418  for (auto *old : qAsConst(m_segments))
419  {
420  auto *segment = new HLSSegment(*old);
421  AppendSegment(segment);
422  }
423  }
424 
426  {
427  for (const auto & segment : qAsConst(m_segments))
428  delete segment;
429  }
430 
432  {
433  if (this == &rhs)
434  return *this;
435  // do not copy segments
436  m_id = rhs.m_id;
437  m_version = rhs.m_version;
440  m_bitrate = rhs.m_bitrate;
441  m_size = rhs.m_size;
442  m_duration = rhs.m_duration;
443  m_live = rhs.m_live;
444  m_url = rhs.m_url;
445  m_cache = rhs.m_cache;
446 #ifdef USING_LIBCRYPTO
447  m_keypath = rhs.m_keypath;
448  m_ivloaded = rhs.m_ivloaded;
449  m_aesIv = rhs.m_aesIv;
450 #endif
451  return *this;
452  }
453 
454  static bool IsGreater(const HLSStream *s1, const HLSStream *s2)
455  {
456  return s1->Bitrate() > s2->Bitrate();
457  }
458 
459  bool operator<(const HLSStream &b) const
460  {
461  return this->Bitrate() < b.Bitrate();
462  }
463 
464  bool operator>(const HLSStream &b) const
465  {
466  return this->Bitrate() > b.Bitrate();
467  }
468 
474  uint64_t Size(bool force = false)
475  {
476  if (m_size > 0 && !force)
477  return m_size;
478  QMutexLocker lock(&m_lock);
479 
480  int64_t size = 0;
481  int count = NumSegments();
482 
483  for (int i = 0; i < count; i++)
484  {
485  HLSSegment *segment = GetSegment(i);
486  segment->Lock();
487  if (segment->Size() > 0)
488  {
489  size += (int64_t)segment->Size();
490  }
491  else
492  {
493  size += segment->Duration().count() * Bitrate() / 8;
494  }
495  segment->Unlock();
496  }
497  m_size = size;
498  return m_size;
499  }
500 
501  std::chrono::seconds Duration(void)
502  {
503  QMutexLocker lock(&m_lock);
504  return m_duration;
505  }
506 
507  void Clear(void)
508  {
509  m_segments.clear();
510  }
511 
512  int NumSegments(void) const
513  {
514  return m_segments.size();
515  }
516 
517  void AppendSegment(HLSSegment *segment)
518  {
519  // must own lock
520  m_segments.append(segment);
521  }
522 
523  HLSSegment *GetSegment(const int wanted) const
524  {
525  int count = NumSegments();
526  if (count <= 0)
527  return nullptr;
528  if ((wanted < 0) || (wanted >= count))
529  return nullptr;
530  return m_segments[wanted];
531  }
532 
533  HLSSegment *FindSegment(const int id, int *segnum = nullptr) const
534  {
535  int count = NumSegments();
536  if (count <= 0)
537  return nullptr;
538  for (int n = 0; n < count; n++)
539  {
540  HLSSegment *segment = GetSegment(n);
541  if (segment == nullptr)
542  break;
543  if (segment->Id() == id)
544  {
545  if (segnum != nullptr)
546  {
547  *segnum = n;
548  }
549  return segment;
550  }
551  }
552  return nullptr;
553  }
554 
555  void AddSegment(const std::chrono::seconds duration, const QString &title, const QString &uri)
556  {
557  QMutexLocker lock(&m_lock);
558  QString psz_uri = relative_URI(m_url, uri);
559  int id = NumSegments() + m_startsequence;
560 #ifndef USING_LIBCRYPTO
561  QString m_keypath;
562 #endif
563  auto *segment = new HLSSegment(duration, id, title, psz_uri, m_keypath);
564  AppendSegment(segment);
565  m_duration += duration;
566  }
567 
568  void RemoveSegment(HLSSegment *segment, bool willdelete = true)
569  {
570  QMutexLocker lock(&m_lock);
571  m_duration -= segment->Duration();
572  if (willdelete)
573  {
574  delete segment;
575  }
576  int count = NumSegments();
577  if (count <= 0)
578  return;
579  for (int n = 0; n < count; n++)
580  {
581  HLSSegment *old = GetSegment(n);
582  if (old == segment)
583  {
584  m_segments.removeAt(n);
585  break;
586  }
587  }
588  }
589 
590  void RemoveSegment(int segnum, bool willdelete = true)
591  {
592  QMutexLocker lock(&m_lock);
593  HLSSegment *segment = GetSegment(segnum);
594  if (segment != nullptr)
595  {
596  m_duration -= segment->Duration();
597  if (willdelete)
598  delete segment;
599  }
600  m_segments.removeAt(segnum);
601  }
602 
603  void RemoveListSegments(QHash<HLSSegment*,bool> &table)
604  {
605  for (auto it = table.begin(); it != table.end(); ++it)
606  {
607  bool todelete = *it;
608  HLSSegment *p = it.key();
609  RemoveSegment(p, todelete);
610  }
611  }
612 
613  int DownloadSegmentData(int segnum, uint64_t &bandwidth, int stream)
614  {
615  HLSSegment *segment = GetSegment(segnum);
616  if (segment == nullptr)
617  return RET_ERROR;
618 
619  segment->Lock();
620  if (!segment->IsEmpty())
621  {
622  /* Segment already downloaded */
623  segment->Unlock();
624  return RET_OK;
625  }
626 
627  LOG(VB_PLAYBACK, LOG_DEBUG, LOC +
628  QString("started download of segment %1 [%2/%3] using stream %4")
629  .arg(segnum).arg(segment->Id()).arg(NumSegments()+m_startsequence)
630  .arg(stream));
631 
632  /* sanity check - can we download this segment on time? */
633  if ((bandwidth > 0) && (m_bitrate > 0))
634  {
635  uint64_t size = (segment->Duration().count() * m_bitrate); /* bits */
636  auto estimated = std::chrono::seconds(size / bandwidth);
637  if (estimated > segment->Duration())
638  {
639  LOG(VB_PLAYBACK, LOG_INFO, LOC +
640  QString("downloading of segment %1 [id:%2] will take %3s, "
641  "which is longer than its playback (%4s) at %5bit/s")
642  .arg(segnum)
643  .arg(segment->Id())
644  .arg(estimated.count())
645  .arg(segment->Duration().count())
646  .arg(bandwidth));
647  }
648  }
649 
650  std::chrono::microseconds start = mdate();
651  if (segment->Download() != RET_OK)
652  {
653  LOG(VB_PLAYBACK, LOG_ERR, LOC +
654  QString("downloaded segment %1 [id:%2] from stream %3 failed")
655  .arg(segnum).arg(segment->Id()).arg(m_id));
656  segment->Unlock();
657  return RET_ERROR;
658  }
659 
660  std::chrono::microseconds downloadduration = mdate() - start;
661  if (m_bitrate == 0 && segment->Duration() > 0s)
662  {
663  /* Try to estimate the bandwidth for this stream */
664  m_bitrate = (uint64_t)(((double)segment->Size() * 8) /
665  ((double)segment->Duration().count()));
666  }
667 
668 #ifdef USING_LIBCRYPTO
669  /* If the segment is encrypted, decode it */
670  if (segment->HasKeyPath())
671  {
672  /* Do we have loaded the key ? */
673  if (!segment->KeyLoaded())
674  {
675  if (ManageSegmentKeys() != RET_OK)
676  {
677  LOG(VB_PLAYBACK, LOG_ERR, LOC +
678  "couldn't retrieve segment AES-128 key");
679  segment->Unlock();
680  return RET_OK;
681  }
682  }
683  if (segment->DecodeData(m_aesIv, m_ivloaded) != RET_OK)
684  {
685  segment->Unlock();
686  return RET_ERROR;
687  }
688  }
689 #endif
690  segment->Unlock();
691 
692  downloadduration = std::min(1us, downloadduration);
693  bandwidth = segment->Size() * 8 * 1000000ULL / downloadduration.count(); /* bits / s */
694  LOG(VB_PLAYBACK, LOG_DEBUG, LOC +
695  QString("downloaded segment %1 [id:%2] took %3ms for %4 bytes: bandwidth:%5kiB/s")
696  .arg(segnum)
697  .arg(segment->Id())
698  .arg(duration_cast<std::chrono::milliseconds>(downloadduration).count())
699  .arg(segment->Size())
700  .arg(bandwidth / 8192.0));
701 
702  return RET_OK;
703  }
704  int Id(void) const
705  {
706  return m_id;
707  }
708  int Version(void) const
709  {
710  return m_version;
711  }
712  void SetVersion(int x)
713  {
714  m_version = x;
715  }
716  int StartSequence(void) const
717  {
718  return m_startsequence;
719  }
720  void SetStartSequence(int x)
721  {
722  m_startsequence = x;
723  }
724  std::chrono::seconds TargetDuration(void) const
725  {
726  return m_targetduration;
727  }
728  void SetTargetDuration(std::chrono::seconds x)
729  {
730  m_targetduration = x;
731  }
732  uint64_t Bitrate(void) const
733  {
734  return m_bitrate;
735  }
736  bool Cache(void) const
737  {
738  return m_cache;
739  }
740  void SetCache(bool x)
741  {
742  m_cache = x;
743  }
744  bool Live(void) const
745  {
746  return m_live;
747  }
748  void SetLive(bool x)
749  {
750  m_live = x;
751  }
752  void Lock(void)
753  {
754  m_lock.lock();
755  }
756  void Unlock(void)
757  {
758  m_lock.unlock();
759  }
760  QString Url(void) const
761  {
762  return m_url;
763  }
764  void UpdateWith(const HLSStream &upd)
765  {
766  QMutexLocker lock(&m_lock);
769  m_cache = upd.m_cache;
770  }
771  void Cancel(void)
772  {
773  QMutexLocker lock(&m_lock);
774  for (const auto & segment : qAsConst(m_segments))
775  {
776  if (segment)
777  {
778  segment->CancelDownload();
779  }
780  }
781  }
782 
783 #ifdef USING_LIBCRYPTO
784 
788  int ManageSegmentKeys() const
789  {
790  HLSSegment *seg = nullptr;
791  HLSSegment *prev_seg = nullptr;
792  int count = NumSegments();
793 
794  for (int i = 0; i < count; i++)
795  {
796  prev_seg = seg;
797  seg = GetSegment(i);
798  if (seg == nullptr )
799  continue;
800  if (!seg->HasKeyPath())
801  continue; /* No key to load ? continue */
802  if (seg->KeyLoaded())
803  continue; /* The key is already loaded */
804 
805  /* if the key has not changed, and already available from previous segment,
806  * try to copy it, and don't load the key */
807  if (prev_seg && prev_seg->KeyLoaded() &&
808  (seg->KeyPath() == prev_seg->KeyPath()))
809  {
810  seg->CopyAESKey(*prev_seg);
811  continue;
812  }
813  if (seg->DownloadKey() != RET_OK)
814  return RET_ERROR;
815  }
816  return RET_OK;
817  }
818  bool SetAESIV(QString line)
819  {
820  /*
821  * If the EXT-X-KEY tag has the IV attribute, implementations MUST use
822  * the attribute value as the IV when encrypting or decrypting with that
823  * key. The value MUST be interpreted as a 128-bit hexadecimal number
824  * and MUST be prefixed with 0x or 0X.
825  */
826  if (!line.startsWith(QLatin1String("0x"), Qt::CaseInsensitive))
827  return false;
828  if (line.size() % 2)
829  {
830  // not even size, pad with front 0
831  line.insert(2, QLatin1String("0"));
832  }
833  int padding = max(0, AES_BLOCK_SIZE - (static_cast<int>(line.size()) - 2));
834  QByteArray ba = QByteArray(padding, 0x0);
835  ba.append(QByteArray::fromHex(QByteArray(line.toLatin1().constData() + 2)));
836  std::copy(ba.cbegin(), ba.cend(), m_aesIv.begin());
837  m_ivloaded = true;
838  return true;
839  }
840  aesiv_array AESIV(void)
841  {
842  return m_aesIv;
843  }
844  void SetKeyPath(const QString &x)
845  {
846  m_keypath = x;
847  }
848 
849 private:
850  QString m_keypath; // URL path of the encrypted key
851  bool m_ivloaded {false};
852  aesiv_array m_aesIv {0}; // IV used when decypher the block
853 #endif
854 
855 private:
856  int m_id {0}; // program id
857  int m_version {1}; // protocol version should be 1
858  int m_startsequence {0}; // media starting sequence number
859  std::chrono::seconds m_targetduration {-1s}; // maximum duration per segment
860  uint64_t m_bitrate {0LL}; // bitrate of stream content (bits per second)
861  uint64_t m_size {0LL}; // stream length is calculated by taking the sum
862  // foreach segment of (segment->duration * hls->bitrate/8)
863  std::chrono::seconds m_duration {0s}; // duration of the stream
864  bool m_live {true};
865 
866  QList<HLSSegment*> m_segments; // list of segments
867  QString m_url; // uri to m3u8
868  QMutex m_lock;
869  bool m_cache {true};// allow caching
870 };
871 
872 // Playback Stream Information
874 {
875 public:
876  HLSPlayback(void) = default;
877 
878  /* offset is only used from main thread, no need for locking */
879  uint64_t Offset(void) const
880  {
881  return m_offset;
882  }
883  void SetOffset(uint64_t val)
884  {
885  m_offset = val;
886  }
887  void AddOffset(uint64_t val)
888  {
889  m_offset += val;
890  }
891  int Stream(void)
892  {
893  QMutexLocker lock(&m_lock);
894  return m_stream;
895  }
896  void SetStream(int val)
897  {
898  QMutexLocker lock(&m_lock);
899  m_stream = val;
900  }
901  int Segment(void)
902  {
903  QMutexLocker lock(&m_lock);
904  return m_segment;
905  }
906  void SetSegment(int val)
907  {
908  QMutexLocker lock(&m_lock);
909  m_segment = val;
910  }
911  int IncrSegment(void)
912  {
913  QMutexLocker lock(&m_lock);
914  return ++m_segment;
915  }
916 
917 private:
918  uint64_t m_offset {0}; // current offset in media
919  int m_stream {0}; // current HLSStream
920  int m_segment {0}; // current segment for playback
921  QMutex m_lock;
922 };
923 
924 // Stream Download Thread
925 class StreamWorker : public MThread
926 {
927 public:
928  StreamWorker(HLSRingBuffer *parent, int startup, int buffer) : MThread("HLSStream"),
929  m_parent(parent), m_segment(startup), m_buffer(buffer)
930  {
931  }
932  void Cancel(void)
933  {
934  m_interrupted = true;
935  Wakeup();
936  m_lock.lock();
937  // Interrupt on-going downloads of all segments
938  int streams = m_parent->NumStreams();
939  for (int i = 0; i < streams; i++)
940  {
941  HLSStream *hls = m_parent->GetStream(i);
942  if (hls)
943  {
944  hls->Cancel();
945  }
946  }
947  m_lock.unlock();
948  wait();
949  }
950  int CurrentStream(void)
951  {
952  QMutexLocker lock(&m_lock);
953  return m_stream;
954  }
955  int Segment(void)
956  {
957  QMutexLocker lock(&m_lock);
958  return m_segment;
959  }
960  void Seek(int val)
961  {
962  m_lock.lock();
963  m_segment = val;
964  m_lock.unlock();
965  Wakeup();
966  }
967  bool IsAtEnd(bool lock = false)
968  {
969  if (lock)
970  {
971  m_lock.lock();
972  }
973  int count = m_parent->NumSegments();
974  bool ret = m_segment >= count;
975  if (lock)
976  {
977  m_lock.unlock();
978  }
979  return ret;
980  }
981 
985  bool GotBufferedSegments(int from, int count) const
986  {
987  if (from + count > m_parent->NumSegments())
988  return false;
989 
990  for (int i = from; i < from + count; i++)
991  {
992  if (StreamForSegment(i, false) < 0)
993  {
994  return false;
995  }
996  }
997  return true;
998  }
999 
1000  int CurrentPlaybackBuffer(bool lock = true)
1001  {
1002  if (lock)
1003  {
1004  m_lock.lock();
1005  }
1006  int ret = m_segment - m_parent->m_playback->Segment();
1007  if (lock)
1008  {
1009  m_lock.unlock();
1010  }
1011  return ret;
1012  }
1014  {
1015  return m_parent->NumSegments() - m_segment;
1016  }
1017  void SetBuffer(int val)
1018  {
1019  QMutexLocker lock(&m_lock);
1020  m_buffer = val;
1021  }
1022  void AddSegmentToStream(int segnum, int stream)
1023  {
1024  if (m_interrupted)
1025  return;
1026  QMutexLocker lock(&m_lock);
1027  m_segmap.insert(segnum, stream);
1028  }
1029  void RemoveSegmentFromStream(int segnum)
1030  {
1031  QMutexLocker lock(&m_lock);
1032  m_segmap.remove(segnum);
1033  }
1034 
1039  int StreamForSegment(int segmentid, bool lock = true) const
1040  {
1041  if (lock)
1042  {
1043  m_lock.lock();
1044  }
1045  int ret = 0;
1046  if (!m_segmap.contains(segmentid))
1047  {
1048  ret = -1; // we never downloaded that segment on any streams
1049  }
1050  else
1051  {
1052  ret = m_segmap[segmentid];
1053  }
1054  if (lock)
1055  {
1056  m_lock.unlock();
1057  }
1058  return ret;
1059  }
1060 
1061  void Wakeup(void)
1062  {
1063  // send a wake signal
1064  m_waitcond.wakeAll();
1065  }
1066  void WaitForSignal(std::chrono::milliseconds time = std::chrono::milliseconds::max())
1067  {
1068  // must own lock
1069  m_waitcond.wait(&m_lock, time.count());
1070  }
1071  void Lock(void)
1072  {
1073  m_lock.lock();
1074  }
1075  void Unlock(void)
1076  {
1077  m_lock.unlock();
1078  }
1079  int64_t Bandwidth(void) const
1080  {
1081  return m_bandwidth;
1082  }
1083  double AverageNewBandwidth(int64_t bandwidth)
1084  {
1085  m_sumbandwidth += bandwidth;
1086  m_countbandwidth++;
1088  return m_bandwidth;
1089  }
1090 
1091 protected:
1092  void run(void) override // MThread
1093  {
1094  RunProlog();
1095 
1096  int retries = 0;
1097  while (!m_interrupted)
1098  {
1099  /*
1100  * we can go into waiting if:
1101  * - not live and download is more than 3 segments ahead of playback
1102  * - we are at the end of the stream
1103  */
1104  Lock();
1106  int dnldsegment = m_segment;
1107  int playsegment = m_parent->m_playback->Segment();
1108  if ((!hls->Live() && (playsegment < dnldsegment - m_buffer)) ||
1109  IsAtEnd())
1110  {
1111  /* wait until
1112  * 1- got interrupted
1113  * 2- we are less than 6 segments ahead of playback
1114  * 3- got asked to seek to a particular segment */
1115  while (!m_interrupted && (m_segment == dnldsegment) &&
1116  (((m_segment - playsegment) > m_buffer) || IsAtEnd()))
1117  {
1118  WaitForSignal();
1119  // do we have new segments available added by PlaylistWork?
1120  if (hls->Live() && !IsAtEnd())
1121  break;
1122  playsegment = m_parent->m_playback->Segment();
1123  }
1124  dnldsegment = m_segment;
1125  }
1126  Unlock();
1127 
1128  if (m_interrupted)
1129  {
1130  Wakeup();
1131  break;
1132  }
1133  // have we already downloaded the required segment?
1134  if (StreamForSegment(dnldsegment) < 0)
1135  {
1136  uint64_t bw = m_bandwidth;
1137  int err = hls->DownloadSegmentData(dnldsegment, bw, m_stream);
1138  if (m_interrupted)
1139  {
1140  // interrupt early
1141  Wakeup();
1142  break;
1143  }
1144  bw = AverageNewBandwidth(bw);
1145  if (err != RET_OK)
1146  {
1147  retries++;
1148  LOG(VB_PLAYBACK, LOG_DEBUG, LOC +
1149  QString("download failed, retry #%1").arg(retries));
1150  if (retries == 1) // first error
1151  continue; // will retry immediately
1152  usleep(500ms); // cppcheck-suppress usleepCalled
1153  if (retries == 2) // and retry once again
1154  continue;
1155  if (!m_parent->m_meta) // NOLINT(bugprone-branch-clone)
1156  {
1157  // no other stream to default to, skip packet
1158  retries = 0;
1159  }
1160  else
1161  {
1162  // TODO: should switch to another stream
1163  retries = 0;
1164  }
1165  }
1166  else
1167  {
1168  LOG(VB_PLAYBACK, LOG_DEBUG, LOC +
1169  QString("download completed, %1 segments ahead")
1170  .arg(CurrentLiveBuffer()));
1171  AddSegmentToStream(dnldsegment, m_stream);
1172  if (m_parent->m_meta && hls->Bitrate() != bw)
1173  {
1174  int newstream = BandwidthAdaptation(hls->Id(), bw);
1175 
1176  if (newstream >= 0 && newstream != m_stream)
1177  {
1178  LOG(VB_PLAYBACK, LOG_INFO, LOC +
1179  QString("switching to %1 bitrate %2 stream; changing "
1180  "from stream %3 to stream %4")
1181  .arg(bw >= hls->Bitrate() ? "faster" : "lower")
1182  .arg(bw).arg(m_stream).arg(newstream));
1183  m_stream = newstream;
1184  }
1185  }
1186  }
1187  }
1188  Lock();
1189  if (dnldsegment == m_segment) // false if seek was called
1190  {
1191  m_segment++;
1192  }
1193  Unlock();
1194  // Signal we're done
1195  Wakeup();
1196  }
1197 
1198  RunEpilog();
1199  }
1200 
1201  int BandwidthAdaptation(int progid, uint64_t &bandwidth) const
1202  {
1203  int candidate = -1;
1204  uint64_t bw = bandwidth;
1205  uint64_t bw_candidate = 0;
1206 
1207  int count = m_parent->NumStreams();
1208  for (int n = 0; n < count; n++)
1209  {
1210  /* Select best bandwidth match */
1211  HLSStream *hls = m_parent->GetStream(n);
1212  if (hls == nullptr)
1213  break;
1214 
1215  /* only consider streams with the same PROGRAM-ID */
1216  if (hls->Id() == progid)
1217  {
1218  if ((bw >= hls->Bitrate()) &&
1219  (bw_candidate < hls->Bitrate()))
1220  {
1221  LOG(VB_PLAYBACK, LOG_DEBUG, LOC +
1222  QString("candidate stream %1 bitrate %2 >= %3")
1223  .arg(n).arg(bw).arg(hls->Bitrate()));
1224  bw_candidate = hls->Bitrate();
1225  candidate = n; /* possible candidate */
1226  }
1227  }
1228  }
1229  bandwidth = bw_candidate;
1230  return candidate;
1231  }
1232 
1233 private:
1235  bool m_interrupted {false};
1236  // measured average download bandwidth (bits per second)
1237  int64_t m_bandwidth {0};
1238  int m_stream {0};// current HLSStream
1239  int m_segment; // current segment for downloading
1240  int m_buffer; // buffer kept between download and playback
1241  QMap<int,int> m_segmap; // segment with streamid used for download
1242  mutable QMutex m_lock;
1243  QWaitCondition m_waitcond;
1244  double m_sumbandwidth {0.0};
1246 };
1247 
1248 // Playlist Refresh Thread
1249 class PlaylistWorker : public MThread
1250 {
1251 public:
1252  PlaylistWorker(HLSRingBuffer *parent, std::chrono::milliseconds wait) : MThread("HLSStream"),
1253  m_parent(parent), m_wakeup(wait) {}
1254  void Cancel()
1255  {
1256  m_interrupted = true;
1257  Wakeup();
1258  m_lock.lock();
1259  // Interrupt on-going downloads of all stream playlists
1260  int streams = m_parent->NumStreams();
1261  QStringList listurls;
1262  for (int i = 0; i < streams; i++)
1263  {
1264  HLSStream *hls = m_parent->GetStream(i);
1265  if (hls)
1266  {
1267  listurls.append(hls->Url());
1268  }
1269  }
1270  m_lock.unlock();
1271  cancelURL(listurls);
1272  wait();
1273  }
1274 
1275  void Wakeup(void)
1276  {
1277  m_lock.lock();
1278  m_wokenup = true;
1279  m_lock.unlock();
1280  // send a wake signal
1281  m_waitcond.wakeAll();
1282  }
1283  void WaitForSignal(std::chrono::milliseconds time = std::chrono::milliseconds::max())
1284  {
1285  // must own lock
1286  m_waitcond.wait(&m_lock, time.count());
1287  }
1288  void Lock(void)
1289  {
1290  m_lock.lock();
1291  }
1292  void Unlock(void)
1293  {
1294  m_lock.unlock();
1295  }
1296 
1297 protected:
1298  void run(void) override // MThread
1299  {
1300  RunProlog();
1301 
1302  double wait = 0.5;
1303  double factor = m_parent->GetCurrentStream()->Live() ? 1.0 : 2.0;
1304 
1305  QWaitCondition mcond;
1306 
1307  while (!m_interrupted)
1308  {
1309  if (m_parent->m_streamworker == nullptr)
1310  {
1311  // streamworker not running
1312  LOG(VB_PLAYBACK, LOG_ERR, LOC +
1313  "StreamWorker not running, aborting live playback");
1314  m_interrupted = true;
1315  break;
1316  }
1317 
1318  Lock();
1319  if (!m_wokenup)
1320  {
1321  std::chrono::milliseconds waittime = std::min(100ms, m_wakeup);
1322  LOG(VB_PLAYBACK, LOG_DEBUG, LOC +
1323  QString("PlayListWorker refreshing in %1s")
1324  .arg(duration_cast<std::chrono::seconds>(waittime).count()));
1325  WaitForSignal(waittime);
1326  }
1327  m_wokenup = false;
1328  Unlock();
1329 
1330  /* reload the m3u8 */
1331  if (ReloadPlaylist() != RET_OK)
1332  {
1333  /* No change in playlist, then backoff */
1334  m_retries++;
1335  if (m_retries == 1) wait = 0.5;
1336  else if (m_retries == 2) wait = 1;
1337  else if (m_retries >= 3) wait = 2;
1338 
1339  // If we haven't been able to reload the playlist after x times
1340  // it probably means the stream got deleted, so abort
1342  {
1343  LOG(VB_PLAYBACK, LOG_ERR, LOC +
1344  QString("reloading the playlist failed after %1 attempts."
1345  "aborting.").arg(PLAYLIST_FAILURE));
1346  m_parent->m_error = true;
1347  }
1348 
1349  /* Can we afford to backoff? */
1351  {
1352  if (m_retries == 1)
1353  continue; // restart immediately if it's the first try
1354  m_retries = 0;
1355  wait = 0.5;
1356  }
1357  }
1358  else
1359  {
1360  // make streamworker process things
1362  m_retries = 0;
1363  wait = 0.5;
1364  }
1365 
1367  if (hls == nullptr)
1368  {
1369  // an irrevocable error has occured. exit
1370  LOG(VB_PLAYBACK, LOG_ERR, LOC +
1371  "unable to retrieve current stream, aborting live playback");
1372  m_interrupted = true;
1373  break;
1374  }
1375 
1376  /* determine next time to update playlist */
1377  m_wakeup = duration_cast<std::chrono::seconds>(hls->TargetDuration() * wait * factor);
1378  }
1379 
1380  RunEpilog();
1381  }
1382 
1383 private:
1387  int ReloadPlaylist(void)
1388  {
1389  auto *streams = new StreamsList;
1390 
1391  LOG(VB_PLAYBACK, LOG_INFO, LOC + "reloading HLS live meta playlist");
1392 
1393  if (GetHTTPLiveMetaPlaylist(streams) != RET_OK)
1394  {
1395  LOG(VB_PLAYBACK, LOG_ERR, LOC + "reloading playlist failed");
1396  m_parent->FreeStreamsList(streams);
1397  return RET_ERROR;
1398  }
1399 
1400  /* merge playlists */
1401  int count = streams->size();
1402  for (int n = 0; n < count; n++)
1403  {
1404  HLSStream *hls_new = m_parent->GetStream(n, streams);
1405  if (hls_new == nullptr)
1406  continue;
1407 
1408  HLSStream *hls_old = m_parent->FindStream(hls_new);
1409  if (hls_old == nullptr)
1410  { /* new hls stream - append */
1411  m_parent->m_streams.append(hls_new);
1412  LOG(VB_PLAYBACK, LOG_INFO, LOC +
1413  QString("new HLS stream appended (id=%1, bitrate=%2)")
1414  .arg(hls_new->Id()).arg(hls_new->Bitrate()));
1415  }
1416  else if (UpdatePlaylist(hls_new, hls_old) != RET_OK)
1417  {
1418  LOG(VB_PLAYBACK, LOG_ERR, LOC +
1419  QString("failed updating HLS stream (id=%1, bandwidth=%2)")
1420  .arg(hls_new->Id()).arg(hls_new->Bitrate()));
1421  m_parent->FreeStreamsList(streams);
1422  return RET_ERROR;
1423  }
1424  }
1425  delete streams;
1426  return RET_OK;
1427  }
1428 
1429  static int UpdatePlaylist(HLSStream *hls_new, HLSStream *hls)
1430  {
1431  int count = hls_new->NumSegments();
1432 
1433  LOG(VB_PLAYBACK, LOG_INFO, LOC +
1434  QString("updated hls stream (program-id=%1, bitrate=%2) has %3 segments")
1435  .arg(hls_new->Id()).arg(hls_new->Bitrate()).arg(count));
1436  QHash<HLSSegment*,bool> table;
1437 
1438  for (int n = 0; n < count; n++)
1439  {
1440  HLSSegment *p = hls_new->GetSegment(n);
1441  if (p == nullptr)
1442  return RET_ERROR;
1443 
1444  hls->Lock();
1445  HLSSegment *segment = hls->FindSegment(p->Id());
1446  if (segment)
1447  {
1448  segment->Lock();
1449  /* they should be the same */
1450  if ((p->Id() != segment->Id()) ||
1451  (p->Duration() != segment->Duration()) ||
1452  (p->Url() != segment->Url()))
1453  {
1454  LOG(VB_PLAYBACK, LOG_WARNING, LOC +
1455  QString("existing segment found with different content - resetting"));
1456  LOG(VB_PLAYBACK, LOG_WARNING, LOC +
1457  QString("- id: new=%1, old=%2")
1458  .arg(p->Id()).arg(segment->Id()));
1459  LOG(VB_PLAYBACK, LOG_WARNING, LOC +
1460  QString("- duration: new=%1, old=%2")
1461  .arg(p->Duration().count()).arg(segment->Duration().count()));
1462  LOG(VB_PLAYBACK, LOG_WARNING, LOC +
1463  QString("- file: new=%1 old=%2")
1464  .arg(p->Url(), segment->Url()));
1465 
1466  /* Resetting content */
1467  *segment = *p;
1468  }
1469  // mark segment to be removed from new stream, and deleted
1470  table.insert(p, true);
1471  segment->Unlock();
1472  }
1473  else
1474  {
1475  int last = hls->NumSegments() - 1;
1476  HLSSegment *l = hls->GetSegment(last);
1477  if (l == nullptr)
1478  {
1479  hls->Unlock();
1480  return RET_ERROR;
1481  }
1482 
1483  if ((l->Id() + 1) != p->Id())
1484  {
1485  LOG(VB_PLAYBACK, LOG_ERR, LOC +
1486  QString("gap in id numbers found: new=%1 expected %2")
1487  .arg(p->Id()).arg(l->Id()+1));
1488  }
1489  hls->AppendSegment(p);
1490  LOG(VB_PLAYBACK, LOG_INFO, LOC +
1491  QString("- segment %1 appended")
1492  .arg(p->Id()));
1493  // segment was moved to another stream, so do not delete it
1494  table.insert(p, false);
1495  }
1496  hls->Unlock();
1497  }
1498  hls_new->RemoveListSegments(table);
1499 
1500  /* update meta information */
1501  hls->UpdateWith(*hls_new);
1502  return RET_OK;
1503  }
1504 
1506  {
1507  int err = RET_ERROR;
1508 
1509  /* Duplicate HLS stream META information */
1510  for (int i = 0; i < m_parent->m_streams.size() && !m_interrupted; i++)
1511  {
1512  auto *src = m_parent->GetStream(i);
1513  if (src == nullptr)
1514  return RET_ERROR;
1515 
1516  auto *dst = new HLSStream(*src);
1517  streams->append(dst);
1518 
1519  /* Download playlist file from server */
1520  QByteArray buffer;
1521  if (!downloadURL(dst->Url(), &buffer) || m_interrupted)
1522  {
1523  return RET_ERROR;
1524  }
1525  /* Parse HLS m3u8 content. */
1526  err = m_parent->ParseM3U8(&buffer, streams);
1527  }
1528  m_parent->SanitizeStreams(streams);
1529  return err;
1530  }
1531 
1532  // private variable members
1534  bool m_interrupted {false};
1535  std::chrono::milliseconds m_wakeup; // next reload time
1536  int m_retries {0}; // number of consecutive failures
1537  bool m_wokenup {false};
1538  QMutex m_lock;
1539  QWaitCondition m_waitcond;
1540 };
1541 
1542 HLSRingBuffer::HLSRingBuffer(const QString &lfilename) :
1544  m_playback(new HLSPlayback())
1545 {
1546  m_startReadAhead = false;
1547  HLSRingBuffer::OpenFile(lfilename);
1548 }
1549 
1550 HLSRingBuffer::HLSRingBuffer(const QString &lfilename, bool open) :
1552  m_playback(new HLSPlayback())
1553 {
1554  m_startReadAhead = false;
1555  if (open)
1556  {
1557  HLSRingBuffer::OpenFile(lfilename);
1558  }
1559 }
1560 
1562 {
1564 
1565  QWriteLocker lock(&m_rwLock);
1566 
1567  m_killed = true;
1568 
1569  if (m_playlistworker)
1570  {
1572  delete m_playlistworker;
1573  }
1574  // stream worker must be deleted after playlist worker
1575  if (m_streamworker)
1576  {
1578  delete m_streamworker;
1579  }
1581  delete m_playback;
1582  if (m_fd)
1583  {
1584  fclose(m_fd);
1585  }
1586 }
1587 
1589 {
1590  /* Free hls streams */
1591  for (int i = 0; i < streams->size(); i++)
1592  {
1593  HLSStream *hls = GetStream(i, streams);
1594  delete hls;
1595  }
1596  if (streams != &m_streams)
1597  {
1598  delete streams;
1599  }
1600 }
1601 
1603 {
1604  int stream = m_streamworker->StreamForSegment(segnum);
1605  if (stream < 0)
1606  {
1607  return GetCurrentStream();
1608  }
1609  return GetStream(stream);
1610 }
1611 
1612 HLSStream *HLSRingBuffer::GetStream(const int wanted, const StreamsList *streams) const
1613 {
1614  if (streams == nullptr)
1615  {
1616  streams = &m_streams;
1617  }
1618  int count = streams->size();
1619  if (count <= 0)
1620  return nullptr;
1621  if ((wanted < 0) || (wanted >= count))
1622  return nullptr;
1623  return streams->at(wanted);
1624 }
1625 
1627 {
1628  return GetStream(0, streams);
1629 }
1630 
1632 {
1633  if (streams == nullptr)
1634  {
1635  streams = &m_streams;
1636  }
1637  int count = streams->size();
1638  if (count <= 0)
1639  return nullptr;
1640  count--;
1641  return GetStream(count, streams);
1642 }
1643 
1645  const StreamsList *streams) const
1646 {
1647  if (streams == nullptr)
1648  {
1649  streams = &m_streams;
1650  }
1651  int count = streams->size();
1652  for (int n = 0; n < count; n++)
1653  {
1654  HLSStream *hls = GetStream(n, streams);
1655  if (hls)
1656  {
1657  /* compare */
1658  if ((hls->Id() == hls_new->Id()) &&
1659  ((hls->Bitrate() == hls_new->Bitrate()) ||
1660  (hls_new->Bitrate() == 0)))
1661  {
1662  return hls;
1663  }
1664  }
1665  }
1666  return nullptr;
1667 }
1668 
1673 {
1674  if (!m_streamworker)
1675  {
1676  return nullptr;
1677  }
1679 }
1680 
1682 {
1683  if (!s || s->size() < 7)
1684  return false;
1685 
1686  if (!s->startsWith((const char*)"#EXTM3U"))
1687  return false;
1688 
1689  QTextStream stream(s);
1690  /* Parse stream and search for
1691  * EXT-X-TARGETDURATION or EXT-X-STREAM-INF tag, see
1692  * http://tools.ietf.org/html/draft-pantos-http-live-streaming-04#page-8 */
1693  while (true)
1694  {
1695  QString line = stream.readLine();
1696  if (line.isNull())
1697  break;
1698  LOG(VB_PLAYBACK, LOG_DEBUG, LOC +
1699  QString("IsHTTPLiveStreaming: %1").arg(line));
1700  if (line.startsWith(QLatin1String("#EXT-X-TARGETDURATION")) ||
1701  line.startsWith(QLatin1String("#EXT-X-MEDIA-SEQUENCE")) ||
1702  line.startsWith(QLatin1String("#EXT-X-KEY")) ||
1703  line.startsWith(QLatin1String("#EXT-X-ALLOW-CACHE")) ||
1704  line.startsWith(QLatin1String("#EXT-X-ENDLIST")) ||
1705  line.startsWith(QLatin1String("#EXT-X-STREAM-INF")) ||
1706  line.startsWith(QLatin1String("#EXT-X-DISCONTINUITY")) ||
1707  line.startsWith(QLatin1String("#EXT-X-VERSION")))
1708  {
1709  return true;
1710  }
1711  }
1712  return false;
1713 }
1714 
1716 {
1717  bool isHLS = false;
1718  URLContext *context = nullptr;
1719 
1720  // Do a peek on the URL to test the format
1722  int ret = ffurl_open(&context, filename.toLatin1(),
1723  AVIO_FLAG_READ, nullptr, nullptr);
1724  if (ret >= 0)
1725  {
1726  std::array<uint8_t,1024> buffer {};
1727  ret = ffurl_read(context, buffer.data(), buffer.size());
1728  if (ret > 0)
1729  {
1730  QByteArray ba((const char*)buffer.data(), ret);
1731  isHLS = IsHTTPLiveStreaming(&ba);
1732  }
1733  ffurl_close(context);
1734  }
1735  else
1736  {
1737  // couldn't peek, rely on URL analysis
1738  QUrl url = filename;
1739  isHLS =
1740  url.path().endsWith(QLatin1String("m3u8"), Qt::CaseInsensitive) ||
1741  url.query( QUrl::FullyEncoded ).contains(QLatin1String("m3u8"), Qt::CaseInsensitive);
1742  }
1743  return isHLS;
1744 }
1745 
1746 /* Parsing */
1747 QString HLSRingBuffer::ParseAttributes(const QString &line, const char *attr)
1748 {
1749  int p = line.indexOf(QLatin1String(":"));
1750  if (p < 0)
1751  return QString();
1752 
1753  QStringList list = line.mid(p+1).split(',');
1754  for (const auto& it : qAsConst(list))
1755  {
1756  QString arg = it.trimmed();
1757  if (arg.startsWith(attr))
1758  {
1759  int pos = arg.indexOf(QLatin1String("="));
1760  if (pos < 0)
1761  continue;
1762  return arg.mid(pos+1);
1763  }
1764  }
1765  return QString();
1766 }
1767 
1772 int HLSRingBuffer::ParseDecimalValue(const QString &line, int &target)
1773 {
1774  int p = line.indexOf(QLatin1String(":"));
1775  if (p < 0)
1776  return RET_ERROR;
1777  int i = p;
1778  while (++i < line.size() && line[i].isNumber());
1779  if (i == p + 1)
1780  return RET_ERROR;
1781  target = line.mid(p+1, i - p - 1).toInt();
1782  return RET_OK;
1783 }
1784 
1785 int HLSRingBuffer::ParseSegmentInformation(const HLSStream *hls, const QString &line,
1786  int &duration, QString &title)
1787 {
1788  /*
1789  * #EXTINF:<duration>,<title>
1790  *
1791  * "duration" is an integer that specifies the duration of the media
1792  * file in seconds. Durations SHOULD be rounded to the nearest integer.
1793  * The remainder of the line following the comma is the title of the
1794  * media file, which is an optional human-readable informative title of
1795  * the media segment
1796  */
1797  int p = line.indexOf(QLatin1String(":"));
1798  if (p < 0)
1799  return RET_ERROR;
1800 
1801  QStringList list = line.mid(p+1).split(',');
1802 
1803  /* read duration */
1804  if (list.isEmpty())
1805  {
1806  return RET_ERROR;
1807  }
1808  QString val = list[0];
1809 
1810  if (hls->Version() < 3)
1811  {
1812  bool ok = false;
1813  duration = val.toInt(&ok);
1814  if (!ok)
1815  {
1816  duration = -1;
1817  return RET_ERROR;
1818  }
1819  }
1820  else
1821  {
1822  bool ok = false;
1823  double d = val.toDouble(&ok);
1824  if (!ok)
1825  {
1826  duration = -1;
1827  return RET_ERROR;
1828  }
1829  if ((d) - ((int)d) >= 0.5)
1830  duration = ((int)d) + 1;
1831  else
1832  duration = ((int)d);
1833  }
1834 
1835  if (list.size() >= 2)
1836  {
1837  title = list[1];
1838  }
1839 
1840  /* Ignore the rest of the line */
1841  return RET_OK;
1842 }
1843 
1844 int HLSRingBuffer::ParseTargetDuration(HLSStream *hls, const QString &line)
1845 {
1846  /*
1847  * #EXT-X-TARGETDURATION:<s>
1848  *
1849  * where s is an integer indicating the target duration in seconds.
1850  */
1851  int duration = -1;
1852 
1853  if (ParseDecimalValue(line, duration) != RET_OK)
1854  {
1855  LOG(VB_PLAYBACK, LOG_ERR, LOC + "expected #EXT-X-TARGETDURATION:<s>");
1856  return RET_ERROR;
1857  }
1858  hls->SetTargetDuration(std::chrono::seconds(duration));
1859  return RET_OK;
1860 }
1861 
1862 HLSStream *HLSRingBuffer::ParseStreamInformation(const QString &line, const QString &uri) const
1863 {
1864  /*
1865  * #EXT-X-STREAM-INF:[attribute=value][,attribute=value]*
1866  * <URI>
1867  */
1868  int id = 0;
1869  QString attr;
1870 
1871  attr = ParseAttributes(line, "PROGRAM-ID");
1872  if (attr.isNull())
1873  {
1874  LOG(VB_PLAYBACK, LOG_INFO, LOC + "#EXT-X-STREAM-INF: expected PROGRAM-ID=<value>, using -1");
1875  id = -1;
1876  }
1877  else
1878  {
1879  id = attr.toInt();
1880  }
1881 
1882  attr = ParseAttributes(line, "BANDWIDTH");
1883  if (attr.isNull())
1884  {
1885  LOG(VB_PLAYBACK, LOG_ERR, LOC + "#EXT-X-STREAM-INF: expected BANDWIDTH=<value>");
1886  return nullptr;
1887  }
1888  uint64_t bw = attr.toInt();
1889 
1890  if (bw == 0)
1891  {
1892  LOG(VB_PLAYBACK, LOG_ERR, LOC + "#EXT-X-STREAM-INF: bandwidth cannot be 0");
1893  return nullptr;
1894  }
1895 
1896  LOG(VB_PLAYBACK, LOG_INFO, LOC +
1897  QString("bandwidth adaptation detected (program-id=%1, bandwidth=%2")
1898  .arg(id).arg(bw));
1899 
1900  QString psz_uri = relative_URI(m_m3u8, uri);
1901 
1902  return new HLSStream(id, bw, psz_uri);
1903 }
1904 
1905 int HLSRingBuffer::ParseMediaSequence(HLSStream *hls, const QString &line)
1906 {
1907  /*
1908  * #EXT-X-MEDIA-SEQUENCE:<number>
1909  *
1910  * A Playlist file MUST NOT contain more than one EXT-X-MEDIA-SEQUENCE
1911  * tag. If the Playlist file does not contain an EXT-X-MEDIA-SEQUENCE
1912  * tag then the sequence number of the first URI in the playlist SHALL
1913  * be considered to be 0.
1914  */
1915  int sequence = 0;
1916 
1917  if (ParseDecimalValue(line, sequence) != RET_OK)
1918  {
1919  LOG(VB_PLAYBACK, LOG_ERR, LOC + "expected #EXT-X-MEDIA-SEQUENCE:<s>");
1920  return RET_ERROR;
1921  }
1922 
1923  if (hls->StartSequence() > 0 && !hls->Live())
1924  {
1925  LOG(VB_PLAYBACK, LOG_ERR, LOC +
1926  QString("EXT-X-MEDIA-SEQUENCE already present in playlist (new=%1, old=%2)")
1927  .arg(sequence).arg(hls->StartSequence()));
1928  }
1929  hls->SetStartSequence(sequence);
1930  return RET_OK;
1931 }
1932 
1933 
1934 int HLSRingBuffer::ParseKey(HLSStream *hls, const QString &line)
1935 {
1936  /*
1937  * #EXT-X-KEY:METHOD=<method>[,URI="<URI>"][,IV=<IV>]
1938  *
1939  * The METHOD attribute specifies the encryption method. Two encryption
1940  * methods are defined: NONE and AES-128.
1941  */
1942  int err = 0;
1943  QString attr = ParseAttributes(line, "METHOD");
1944  if (attr.isNull())
1945  {
1946  LOG(VB_PLAYBACK, LOG_ERR, LOC + "#EXT-X-KEY: expected METHOD=<value>");
1947  return RET_ERROR;
1948  }
1949 
1950  if (attr.startsWith(QLatin1String("NONE")))
1951  {
1952  QString uri = ParseAttributes(line, "URI");
1953  if (!uri.isNull())
1954  {
1955  LOG(VB_PLAYBACK, LOG_ERR, LOC + "#EXT-X-KEY: URI not expected");
1956  err = RET_ERROR;
1957  }
1958  /* IV is only supported in version 2 and above */
1959  if (hls->Version() >= 2)
1960  {
1961  QString iv = ParseAttributes(line, "IV");
1962  if (!iv.isNull())
1963  {
1964  LOG(VB_PLAYBACK, LOG_ERR, LOC + "#EXT-X-KEY: IV not expected");
1965  err = RET_ERROR;
1966  }
1967  }
1968  }
1969 #ifdef USING_LIBCRYPTO
1970  else if (attr.startsWith(QLatin1String("AES-128")))
1971  {
1972  QString uri;
1973  QString iv;
1974  if (!m_aesmsg)
1975  {
1976  LOG(VB_PLAYBACK, LOG_INFO, LOC +
1977  "playback of AES-128 encrypted HTTP Live media detected.");
1978  m_aesmsg = true;
1979  }
1980  uri = ParseAttributes(line, "URI");
1981  if (uri.isNull())
1982  {
1983  LOG(VB_PLAYBACK, LOG_ERR, LOC +
1984  "#EXT-X-KEY: URI not found for encrypted HTTP Live media in AES-128");
1985  return RET_ERROR;
1986  }
1987 
1988  /* Url is between quotes, remove them */
1989  hls->SetKeyPath(decoded_URI(uri.remove(QChar(QLatin1Char('"')))));
1990 
1991  iv = ParseAttributes(line, "IV");
1992  if (!iv.isNull() && !hls->SetAESIV(iv))
1993  {
1994  LOG(VB_PLAYBACK, LOG_ERR, LOC + "invalid IV");
1995  err = RET_ERROR;
1996  }
1997  }
1998 #endif
1999  else
2000  {
2001 #ifndef _MSC_VER
2002  LOG(VB_PLAYBACK, LOG_ERR, LOC +
2003  "invalid encryption type, only NONE "
2004 #ifdef USING_LIBCRYPTO
2005  "and AES-128 are supported"
2006 #else
2007  "is supported."
2008 #endif
2009  );
2010 #else
2011 // msvc doesn't like #ifdef in the middle of the LOG macro.
2012 // Errors with '#':invalid character: possibly the result of a macro expansion
2013 #endif
2014  err = RET_ERROR;
2015  }
2016  return err;
2017 }
2018 
2019 int HLSRingBuffer::ParseProgramDateTime(HLSStream */*hls*/, const QString &line)
2020 {
2021  /*
2022  * #EXT-X-PROGRAM-DATE-TIME:<YYYY-MM-DDThh:mm:ssZ>
2023  */
2024  LOG(VB_PLAYBACK, LOG_WARNING, LOC +
2025  QString("tag not supported: #EXT-X-PROGRAM-DATE-TIME %1")
2026  .arg(line));
2027  return RET_OK;
2028 }
2029 
2030 int HLSRingBuffer::ParseAllowCache(HLSStream *hls, const QString &line)
2031 {
2032  /*
2033  * The EXT-X-ALLOW-CACHE tag indicates whether the client MAY or MUST
2034  * NOT cache downloaded media files for later replay. It MAY occur
2035  * anywhere in the Playlist file; it MUST NOT occur more than once. The
2036  * EXT-X-ALLOW-CACHE tag applies to all segments in the playlist. Its
2037  * format is:
2038  *
2039  * #EXT-X-ALLOW-CACHE:<YES|NO>
2040  */
2041  int pos = line.indexOf(QLatin1String(":"));
2042  if (pos < 0)
2043  return RET_ERROR;
2044  QString answer = line.mid(pos+1, 3);
2045  if (answer.size() < 2)
2046  {
2047  LOG(VB_PLAYBACK, LOG_ERR, LOC + "#EXT-X-ALLOW-CACHE, ignoring ...");
2048  return RET_ERROR;
2049  }
2050  hls->SetCache(!answer.startsWith(QLatin1String("NO")));
2051  return RET_OK;
2052 }
2053 
2054 int HLSRingBuffer::ParseVersion(const QString &line, int &version)
2055 {
2056  /*
2057  * The EXT-X-VERSION tag indicates the compatibility version of the
2058  * Playlist file. The Playlist file, its associated media, and its
2059  * server MUST comply with all provisions of the most-recent version of
2060  * this document describing the protocol version indicated by the tag
2061  * value.
2062  *
2063  * Its format is:
2064  *
2065  * #EXT-X-VERSION:<n>
2066  */
2067 
2068  if (ParseDecimalValue(line, version) != RET_OK)
2069  {
2070  LOG(VB_PLAYBACK, LOG_ERR, LOC +
2071  "#EXT-X-VERSION: no protocol version found, should be version 1.");
2072  return RET_ERROR;
2073  }
2074 
2075  if (version <= 0 || version > 3)
2076  {
2077  LOG(VB_PLAYBACK, LOG_ERR, LOC +
2078  QString("#EXT-X-VERSION should be version 1, 2 or 3 iso %1")
2079  .arg(version));
2080  return RET_ERROR;
2081  }
2082  return RET_OK;
2083 }
2084 
2086 {
2087  /*
2088  * The EXT-X-ENDLIST tag indicates that no more media files will be
2089  * added to the Playlist file. It MAY occur anywhere in the Playlist
2090  * file; it MUST NOT occur more than once. Its format is:
2091  */
2092  hls->SetLive(false);
2093  LOG(VB_PLAYBACK, LOG_INFO, LOC + "video on demand (vod) mode");
2094  return RET_OK;
2095 }
2096 
2097 int HLSRingBuffer::ParseDiscontinuity(HLSStream */*hls*/, const QString &line)
2098 {
2099  /* Not handled, never seen so far */
2100  LOG(VB_PLAYBACK, LOG_DEBUG, LOC + QString("#EXT-X-DISCONTINUITY %1").arg(line));
2101  return RET_OK;
2102 }
2103 
2104 int HLSRingBuffer::ParseM3U8(const QByteArray *buffer, StreamsList *streams)
2105 {
2113  if (streams == nullptr)
2114  {
2115  streams = &m_streams;
2116  }
2117  QTextStream stream(*buffer);
2118 #if QT_VERSION < QT_VERSION_CHECK(6,0,0)
2119  stream.setCodec("UTF-8");
2120 #else
2121  stream.setEncoding(QStringConverter::Utf8);
2122 #endif
2123 
2124  QString line = stream.readLine();
2125  if (line.isNull())
2126  return RET_ERROR;
2127 
2128  if (!line.startsWith(QLatin1String("#EXTM3U")))
2129  {
2130  LOG(VB_PLAYBACK, LOG_ERR, LOC + "missing #EXTM3U tag .. aborting");
2131  return RET_ERROR;
2132  }
2133 
2134  /* What is the version ? */
2135  int version = 1;
2136  int p = buffer->indexOf("#EXT-X-VERSION:");
2137  if (p >= 0)
2138  {
2139  stream.seek(p);
2140  QString psz_version = stream.readLine();
2141  if (psz_version.isNull())
2142  return RET_ERROR;
2143  int ret = ParseVersion(psz_version, version);
2144  if (ret != RET_OK)
2145  {
2146  LOG(VB_GENERAL, LOG_WARNING, LOC +
2147  "#EXT-X-VERSION: no protocol version found, assuming version 1.");
2148  version = 1;
2149  }
2150  }
2151 
2152  /* Is it a meta index file ? */
2153  bool meta = buffer->indexOf("#EXT-X-STREAM-INF") >= 0;
2154 
2155  int err = RET_OK;
2156 
2157  if (meta)
2158  {
2159  LOG(VB_PLAYBACK, LOG_INFO, LOC + "Meta playlist");
2160 
2161  /* M3U8 Meta Index file */
2162  stream.seek(0); // rewind
2163  while (!m_killed)
2164  {
2165  line = stream.readLine();
2166  if (line.isNull())
2167  break;
2168 
2169  if (line.startsWith(QLatin1String("#EXT-X-STREAM-INF")))
2170  {
2171  m_meta = true;
2172  QString uri = stream.readLine();
2173  if (uri.isNull())
2174  {
2175  err = RET_ERROR;
2176  break;
2177  }
2178  if (uri.startsWith(QLatin1String("#")))
2179  {
2180  LOG(VB_GENERAL, LOG_INFO, LOC +
2181  QString("Skipping invalid stream-inf: %1")
2182  .arg(uri));
2183  }
2184  else
2185  {
2186  HLSStream *hls = ParseStreamInformation(line, decoded_URI(uri));
2187  if (hls)
2188  {
2189  /* Download playlist file from server */
2190  QByteArray buf;
2191  bool ret = downloadURL(hls->Url(), &buf);
2192  if (!ret)
2193  {
2194  LOG(VB_GENERAL, LOG_INFO, LOC +
2195  QString("Skipping invalid stream, couldn't download: %1")
2196  .arg(hls->Url()));
2197  delete hls;
2198  continue;
2199  }
2200  streams->append(hls);
2201  // One last chance to abort early
2202  if (m_killed)
2203  {
2204  err = RET_ERROR;
2205  break;
2206  }
2207  /* Parse HLS m3u8 content. */
2208  err = ParseM3U8(&buf, streams);
2209  if (err != RET_OK)
2210  break;
2211  hls->SetVersion(version);
2212  }
2213  }
2214  }
2215  }
2216  }
2217  else
2218  {
2219  HLSStream *hls = nullptr;
2220  if (m_meta)
2221  hls = GetLastStream(streams);
2222  else
2223  {
2224  /* No Meta playlist used */
2225  hls = new HLSStream(0, 0, m_m3u8);
2226  streams->append(hls);
2227  /* Get TARGET-DURATION first */
2228  p = buffer->indexOf("#EXT-X-TARGETDURATION:");
2229  if (p >= 0)
2230  {
2231  stream.seek(p);
2232  QString psz_duration = stream.readLine();
2233  if (psz_duration.isNull())
2234  return RET_ERROR;
2235  err = ParseTargetDuration(hls, psz_duration);
2236  }
2237  /* Store version */
2238  hls->SetVersion(version);
2239  }
2240  LOG(VB_PLAYBACK, LOG_INFO, LOC +
2241  QString("%1 Playlist HLS protocol version: %2")
2242  .arg(hls->Live() ? "Live": "VOD").arg(version));
2243 
2244  // rewind
2245  stream.seek(0);
2246  /* */
2247  std::chrono::seconds segment_duration = -1s;
2248  QString title;
2249  do
2250  {
2251  /* Next line */
2252  line = stream.readLine();
2253  if (line.isNull())
2254  break;
2255  LOG(VB_PLAYBACK, LOG_DEBUG, LOC + QString("ParseM3U8: %1")
2256  .arg(line));
2257 
2258  if (line.startsWith(QLatin1String("#EXTINF")))
2259  {
2260  int tmp = -1;
2261  err = ParseSegmentInformation(hls, line, tmp, title);
2262  segment_duration = std::chrono::seconds(tmp);
2263  }
2264  else if (line.startsWith(QLatin1String("#EXT-X-TARGETDURATION")))
2265  err = ParseTargetDuration(hls, line);
2266  else if (line.startsWith(QLatin1String("#EXT-X-MEDIA-SEQUENCE")))
2267  err = ParseMediaSequence(hls, line);
2268  else if (line.startsWith(QLatin1String("#EXT-X-KEY")))
2269  err = ParseKey(hls, line);
2270  else if (line.startsWith(QLatin1String("#EXT-X-PROGRAM-DATE-TIME")))
2271  err = ParseProgramDateTime(hls, line);
2272  else if (line.startsWith(QLatin1String("#EXT-X-ALLOW-CACHE")))
2273  err = ParseAllowCache(hls, line);
2274  else if (line.startsWith(QLatin1String("#EXT-X-DISCONTINUITY")))
2275  err = ParseDiscontinuity(hls, line);
2276  else if (line.startsWith(QLatin1String("#EXT-X-VERSION")))
2277  {
2278  int version2 = 0;
2279  err = ParseVersion(line, version2);
2280  hls->SetVersion(version2);
2281  }
2282  else if (line.startsWith(QLatin1String("#EXT-X-ENDLIST")))
2283  err = ParseEndList(hls);
2284  else if (!line.startsWith(QLatin1String("#")) && !line.isEmpty())
2285  {
2286  hls->AddSegment(segment_duration, title, decoded_URI(line));
2287  segment_duration = -1s; /* reset duration */
2288  title = "";
2289  }
2290  }
2291  while (err == RET_OK);
2292  }
2293  return err;
2294 }
2295 
2296 // stream content functions
2301 {
2302  int retries = 0;
2303  std::chrono::microseconds starttime = mdate();
2304  LOG(VB_PLAYBACK, LOG_INFO, LOC +
2305  QString("Starting Prefetch for %2 segments")
2306  .arg(count));
2307  m_streamworker->Lock();
2309  while (!m_error && !m_killed && (retries < 20) &&
2310  (m_streamworker->CurrentPlaybackBuffer(false) < count) &&
2311  !m_streamworker->IsAtEnd())
2312  {
2314  retries++;
2315  }
2317  LOG(VB_PLAYBACK, LOG_INFO, LOC + QString("Finished Prefetch (%1s)")
2318  .arg(duration_cast<std::chrono::seconds>(mdate() - starttime).count()));
2319  // we waited more than 10s abort
2320  if (retries >= 10)
2321  return RET_ERROR;
2322  return RET_OK;
2323 }
2324 
2326 {
2327  bool live = hls->Live();
2328  /* sanity check */
2329  if ((m_streamworker->CurrentPlaybackBuffer() == 0) &&
2330  (!m_streamworker->IsAtEnd(true) || live))
2331  {
2332  LOG(VB_PLAYBACK, LOG_WARNING, LOC + "playback will stall");
2333  }
2335  (!m_streamworker->IsAtEnd(true) || live))
2336  {
2337  LOG(VB_PLAYBACK, LOG_WARNING, LOC + "playback in danger of stalling");
2338  }
2339  else if (live && m_streamworker->IsAtEnd(true) &&
2341  {
2342  LOG(VB_PLAYBACK, LOG_WARNING, LOC + "playback will exit soon, starving for data");
2343  }
2344 }
2345 
2351 HLSSegment *HLSRingBuffer::GetSegment(int segnum, std::chrono::milliseconds timeout)
2352 {
2353  HLSSegment *segment = nullptr;
2354  int stream = m_streamworker->StreamForSegment(segnum);
2355  if (stream < 0)
2356  {
2357  // we haven't downloaded that segment, request it
2358  // we should never be into this condition for normal playback
2359  m_streamworker->Seek(segnum);
2360  m_streamworker->Lock();
2361  /* Wait for download to be finished */
2362  LOG(VB_PLAYBACK, LOG_WARNING, LOC +
2363  LOC + QString("waiting to get segment %1")
2364  .arg(segnum));
2365  int retries = 0;
2366  while (!m_error && (stream < 0) && (retries < 10))
2367  {
2369  stream = m_streamworker->StreamForSegment(segnum, false);
2370  retries++;
2371  }
2373  if (stream < 0)
2374  return nullptr;
2375  }
2376  HLSStream *hls = GetStream(stream);
2377  hls->Lock();
2378  segment = hls->GetSegment(segnum);
2379  hls->Unlock();
2380  LOG(VB_PLAYBACK, LOG_DEBUG, LOC +
2381  QString("GetSegment %1 [%2] stream[%3] (bitrate:%4)")
2382  .arg(segnum).arg(segment->Id()).arg(stream).arg(hls->Bitrate()));
2383  SanityCheck(hls);
2384  return segment;
2385 }
2386 
2388 {
2389  return m_streams.size();
2390 }
2391 
2393 {
2394  HLSStream *hls = GetStream(0);
2395  if (hls == nullptr)
2396  return 0;
2397  hls->Lock();
2398  int count = hls->NumSegments();
2399  hls->Unlock();
2400  return count;
2401 }
2402 
2403 int HLSRingBuffer::ChooseSegment(int stream) const
2404 {
2405  /* Choose a segment to start which is no closer than
2406  * 3 times the target duration from the end of the playlist.
2407  */
2408  int wanted = 0;
2409  int segid = 0;
2410  std::chrono::seconds wanted_duration = 0s;
2411  int count = NumSegments();
2412  int i = count - 1;
2413 
2414  HLSStream *hls = GetStream(stream);
2415  while(i >= 0)
2416  {
2417  HLSSegment *segment = hls->GetSegment(i);
2418  if (segment == nullptr)
2419  continue;
2420 
2421  if (segment->Duration() > hls->TargetDuration())
2422  {
2423  LOG(VB_PLAYBACK, LOG_WARNING, LOC +
2424  QString("EXTINF:%1 duration is larger than EXT-X-TARGETDURATION:%2")
2425  .arg(segment->Duration().count()).arg(hls->TargetDuration().count()));
2426  }
2427 
2428  wanted_duration += segment->Duration();
2429  if (wanted_duration >= 3 * hls->TargetDuration())
2430  {
2431  /* Start point found */
2432  wanted = i;
2433  segid = segment->Id();
2434  break;
2435  }
2436  i-- ;
2437  }
2438 
2439  LOG(VB_PLAYBACK, LOG_DEBUG, LOC +
2440  QString("Choose segment %1/%2 [%3]")
2441  .arg(wanted).arg(count).arg(segid));
2442  return wanted;
2443 }
2444 
2450 {
2451  // no lock is required as, at this stage, no threads have either been started
2452  // or we are working on a stream list unique to PlaylistWorker
2453  if (streams == nullptr)
2454  {
2455  streams = &m_streams;
2456  }
2457  QMap<int,int> idstart;
2458  // Find the highest starting sequence for each stream
2459  for (int n = streams->size() - 1 ; n >= 0; n--)
2460  {
2461  HLSStream *hls = GetStream(n, streams);
2462  if (hls->NumSegments() == 0)
2463  {
2464  streams->removeAt(n);
2465  continue; // remove it
2466  }
2467 
2468  int id = hls->Id();
2469  int start = hls->StartSequence();
2470  if (!idstart.contains(id))
2471  {
2472  idstart.insert(id, start);
2473  }
2474  int start2 = idstart.value(id);
2475  if (start > start2)
2476  {
2477  idstart.insert(id, start);
2478  }
2479  }
2480  // Find the highest starting sequence for each stream
2481  for (int n = 0; n < streams->size(); n++)
2482  {
2483  HLSStream *hls = GetStream(n, streams);
2484  int id = hls->Id();
2485  int seq = hls->StartSequence();
2486  int newstart= idstart.value(id);
2487  int todrop = newstart - seq;
2488  if (todrop == 0)
2489  {
2490  // perfect, leave it alone
2491  continue;
2492  }
2493  if (todrop >= hls->NumSegments() || todrop < 0)
2494  {
2495  LOG(VB_PLAYBACK, LOG_ERR, LOC +
2496  QString("stream %1 [id=%2] can't be properly adjusted, ignoring")
2497  .arg(n).arg(hls->Id()));
2498  continue;
2499  }
2500  for (int i = 0; i < todrop; i++)
2501  {
2502  hls->RemoveSegment(0);
2503  }
2504  hls->SetStartSequence(newstart);
2505  }
2506 }
2507 
2516 bool HLSRingBuffer::OpenFile(const QString &lfilename, std::chrono::milliseconds /*retry_ms*/)
2517 {
2518  QWriteLocker lock(&m_rwLock);
2519 
2520  m_safeFilename = lfilename;
2521  m_filename = lfilename;
2522  QString finalURL;
2523 
2524  QByteArray buffer;
2525  if (!downloadURL(m_filename, &buffer, finalURL))
2526  {
2527  LOG(VB_PLAYBACK, LOG_ERR, LOC +
2528  QString("Couldn't open URL %1").arg(m_filename));
2529  return false; // can't download file
2530  }
2531  if (m_filename != finalURL)
2532  {
2533  LOG(VB_PLAYBACK, LOG_INFO, LOC +
2534  QString("Redirected %1 -> %2 ").arg(m_filename, finalURL));
2535  m_filename = finalURL;
2536  }
2537  if (!IsHTTPLiveStreaming(&buffer))
2538  {
2539  LOG(VB_PLAYBACK, LOG_ERR, LOC +
2540  QString("%1 isn't a HTTP Live Streaming URL").arg(m_filename));
2541  return false;
2542  }
2543  // let's go
2544  m_m3u8 = m_filename;
2545  LOG(VB_PLAYBACK, LOG_INFO, LOC + QString("HTTP Live Streaming (%1)")
2546  .arg(m_m3u8));
2547 
2548  /* Parse HLS m3u8 content. */
2549  if (ParseM3U8(&buffer, &m_streams) != RET_OK || m_streams.isEmpty())
2550  {
2551  LOG(VB_PLAYBACK, LOG_ERR, LOC +
2552  QString("An error occurred reading M3U8 playlist (%1)").arg(m_filename));
2553  m_error = true;
2554  return false;
2555  }
2556 
2557  SanitizeStreams();
2558 
2559  /* HLS standard doesn't provide any guaranty about streams
2560  being sorted by bitrate, so we sort them, higher bitrate being first */
2561  std::sort(m_streams.begin(), m_streams.end(), HLSStream::IsGreater);
2562 
2563  // if we want as close to live. We should be selecting a further segment
2564  // m_live ? ChooseSegment(0) : 0;
2565 // if (m_live && m_startup < 0)
2566 // {
2567 // LOG(VB_PLAYBACK, LOG_WARNING, LOC +
2568 // "less data than 3 times 'target duration' available for "
2569 // "live playback, playback may stall");
2570 // m_startup = 0;
2571 // }
2572  m_startup = 0;
2574 
2576  m_streamworker->start();
2577 
2579  {
2580  LOG(VB_PLAYBACK, LOG_ERR, LOC +
2581  "fetching first segment failed or didn't complete within 10s.");
2582  m_error = true;
2583  return false;
2584  }
2585 
2586  // set bitrate value used to calculate the size of the stream
2587  HLSStream *hls = GetCurrentStream();
2588  m_bitrate = hls->Bitrate();
2589 
2590  // Set initial seek position (relative to m_startup)
2591  m_playback->SetOffset(0);
2592 
2593  /* Initialize HLS live stream thread */
2594  //if (m_live) // commented out as some streams are marked as VOD, yet
2595  // aren't, they are updated over time
2596  {
2597  m_playlistworker = new PlaylistWorker(this, 0ms);
2599  }
2600 
2601  return true;
2602 }
2603 
2604 bool HLSRingBuffer::SaveToDisk(const QString &filename, int segstart, int segend)
2605 {
2606  // download it all
2607  FILE *fp = fopen(filename.toLatin1().constData(), "w");
2608  if (fp == nullptr)
2609  return false;
2610  int count = NumSegments();
2611  if (segend < 0)
2612  {
2613  segend = count;
2614  }
2615  for (int i = segstart; i < segend; i++)
2616  {
2617  HLSSegment *segment = GetSegment(i);
2618  if (segment == nullptr)
2619  {
2620  LOG(VB_PLAYBACK, LOG_ERR, LOC +
2621  QString("downloading %1 failed").arg(i));
2622  }
2623  else
2624  {
2625  LOG(VB_PLAYBACK, LOG_INFO, LOC +
2626  QString("download of %1 succeeded")
2627  .arg(i));
2628  fwrite(segment->Data(), segment->Size(), 1, fp);
2629  fflush(fp);
2630  }
2631  }
2632  fclose(fp);
2633  return true;
2634 }
2635 
2636 int64_t HLSRingBuffer::SizeMedia(void) const
2637 {
2638  if (m_error)
2639  return -1;
2640 
2641  HLSStream *hls = GetCurrentStream();
2642  int64_t size = hls->Duration().count() * m_bitrate / 8;
2643 
2644  return size;
2645 }
2646 
2652 {
2653  bool live = GetCurrentStream()->Live();
2654 
2655  // last seek was to end of media, we are just in seek mode so do not wait
2656  if (m_seektoend)
2657  return;
2658 
2660  (!live && m_streamworker->IsAtEnd()))
2661  {
2662  return;
2663  }
2664 
2665  // danger of getting to the end... pause until we have some more
2666  LOG(VB_PLAYBACK, LOG_INFO, LOC +
2667  QString("pausing until we get sufficient data buffered"));
2669  m_streamworker->Lock();
2670  int retries = 0;
2671  while (!m_error && !m_interrupted &&
2672  (m_streamworker->CurrentPlaybackBuffer(false) < 2) &&
2673  (live || !m_streamworker->IsAtEnd()))
2674  {
2676  retries++;
2677  }
2679 }
2680 
2681 int HLSRingBuffer::SafeRead(void *data, uint sz)
2682 {
2683  if (m_error)
2684  return -1;
2685 
2686  int used = 0;
2687  int i_read = sz;
2688 
2690  if (m_interrupted)
2691  {
2692  LOG(VB_PLAYBACK, LOG_DEBUG, LOC + QString("interrupted"));
2693  return 0;
2694  }
2695 
2696  do
2697  {
2698  int segnum = m_playback->Segment();
2699  if (segnum >= NumSegments())
2700  {
2701  m_playback->AddOffset(used);
2702  return used;
2703  }
2704  int stream = m_streamworker->StreamForSegment(segnum);
2705  if (stream < 0)
2706  {
2707  // we haven't downloaded this segment yet, likely that it was
2708  // dropped (livetv?)
2710  continue;
2711  }
2712  HLSStream *hls = GetStream(stream);
2713  if (hls == nullptr)
2714  break;
2715  HLSSegment *segment = hls->GetSegment(segnum);
2716  if (segment == nullptr)
2717  break;
2718 
2719  segment->Lock();
2720  if (segment->SizePlayed() == segment->Size())
2721  {
2722  if (!hls->Cache() || hls->Live())
2723  {
2724  segment->Clear();
2726  }
2727  else
2728  {
2729  segment->Reset();
2730  }
2731 
2733  segment->Unlock();
2734 
2735  /* signal download thread we're about to use a new segment */
2737  continue;
2738  }
2739 
2740  if (segment->SizePlayed() == 0)
2741  {
2742  LOG(VB_PLAYBACK, LOG_INFO, LOC +
2743  QString("started reading segment %1 [id:%2] from stream %3 (%4 buffered)")
2744  .arg(segnum).arg(segment->Id()).arg(stream)
2746  }
2747 
2748  int32_t len = segment->Read((uint8_t*)data + used, i_read, m_fd);
2749  used += len;
2750  i_read -= len;
2751  segment->Unlock();
2752  }
2753  while (i_read > 0 && !m_interrupted);
2754 
2755  if (m_interrupted)
2756  LOG(VB_PLAYBACK, LOG_DEBUG, LOC + QString("interrupted"));
2757 
2758  m_playback->AddOffset(used);
2759  return used;
2760 }
2761 
2767 {
2768  int segnum = m_playback->Segment();
2769  int stream = m_streamworker->StreamForSegment(segnum);
2770  if (stream < 0)
2771  {
2772  return 0;
2773  }
2774  HLSStream *hls = GetStream(stream);
2775  if (hls == nullptr)
2776  {
2777  return 0;
2778  }
2779  HLSSegment *segment = hls->GetSegment(segnum);
2780  if (segment == nullptr)
2781  {
2782  return 0;
2783  }
2784  auto byterate = (uint64_t)(((double)segment->Size()) /
2785  ((double)segment->Duration().count()));
2786 
2787  return (int)((size * 1000.0) / byterate);
2788 }
2789 
2791 {
2792  QReadLocker lock(&m_rwLock);
2793  return SizeMedia();
2794 }
2795 
2796 long long HLSRingBuffer::SeekInternal(long long pos, int whence)
2797 {
2798  if (m_error)
2799  return -1;
2800 
2801  if (!IsSeekingAllowed())
2802  {
2803  return m_playback->Offset();
2804  }
2805 
2806  std::chrono::microseconds starting = mdate();
2807 
2808  QWriteLocker lock(&m_posLock);
2809 
2810  int totalsize = SizeMedia();
2811  int64_t where = 0;
2812  switch (whence)
2813  {
2814  case SEEK_CUR:
2815  // return current location, nothing to do
2816  if (pos == 0)
2817  {
2818  return m_playback->Offset();
2819  }
2820  where = m_playback->Offset() + pos;
2821  break;
2822  case SEEK_END:
2823  where = SizeMedia() - pos;
2824  break;
2825  case SEEK_SET:
2826  default:
2827  where = pos;
2828  break;
2829  }
2830 
2831  // We determine the duration at which it was really attempting to seek to
2832  auto postime = secondsFromFloat((where * 8.0) / m_bitrate);
2833  int count = NumSegments();
2834  int segnum = m_playback->Segment();
2835  HLSStream *hls = GetStreamForSegment(segnum);
2836 
2837  /* restore current segment's file position indicator to 0 */
2838  HLSSegment *segment = hls->GetSegment(segnum);
2839  if (segment != nullptr)
2840  {
2841  segment->Lock();
2842  segment->Reset();
2843  segment->Unlock();
2844  }
2845 
2846  if (where > totalsize)
2847  {
2848  // we're at the end, never let seek after last 3 segments
2849  postime -= hls->TargetDuration() * 3;
2850  if (postime < 0s)
2851  {
2852  postime = 0s;
2853  }
2854  }
2855 
2856  // Find segment containing position
2857  std::chrono::seconds starttime = 0s;
2858  std::chrono::seconds endtime = 0s;
2859  for (int n = m_startup; n < count; n++)
2860  {
2861  hls = GetStreamForSegment(n);
2862  if (hls == nullptr)
2863  {
2864  // error, should never happen, irrecoverable error
2865  return -1;
2866  }
2867  segment = hls->GetSegment(n);
2868  if (segment == nullptr)
2869  {
2870  // stream doesn't contain segment error can't continue,
2871  // unknown error
2872  return -1;
2873  }
2874  endtime += segment->Duration();
2875  if (postime < endtime)
2876  {
2877  segnum = n;
2878  break;
2879  }
2880  starttime = endtime;
2881  }
2882 
2883  /*
2884  * Live Mode exception:
2885  * FFmpeg seek to the last segment in order to determine the size of the video
2886  * so do not allow seeking to the last segment if in live mode as we don't care
2887  * about the size
2888  * Also do not allow to seek before the current playback segment as segment
2889  * has been cleared from memory
2890  * We only let determine the size if the bandwidth would allow fetching the
2891  * the segments in less than 5s
2892  */
2893  if (hls->Live() && (segnum >= count - 1 || segnum < m_playback->Segment()) &&
2894  ((hls->TargetDuration() * hls->Bitrate() / m_streamworker->Bandwidth()) > 5s))
2895  {
2896  return m_playback->Offset();
2897  }
2898  m_seektoend = segnum >= count - 1;
2899 
2900  m_playback->SetSegment(segnum);
2901 
2902  m_streamworker->Seek(segnum);
2903  m_playback->SetOffset(postime.count() * m_bitrate / 8);
2904 
2905  m_streamworker->Lock();
2906 
2907  /* Wait for download to be finished and to buffer 3 segment */
2908  LOG(VB_PLAYBACK, LOG_INFO, LOC +
2909  QString("seek to segment %1").arg(segnum));
2910  int retries = 0;
2911 
2912  // see if we've already got the segment, and at least 2 buffered after
2913  // then no need to wait for streamworker
2914  while (!m_error && !m_interrupted &&
2915  (!m_streamworker->GotBufferedSegments(segnum, 2) &&
2916  (m_streamworker->CurrentPlaybackBuffer(false) < 2) &&
2917  !m_streamworker->IsAtEnd()))
2918  {
2920  retries++;
2921  }
2922  if (m_interrupted)
2923  LOG(VB_PLAYBACK, LOG_DEBUG, LOC + QString("interrupted"));
2924 
2926 
2927  // now seek within found segment
2928  int stream = m_streamworker->StreamForSegment(segnum);
2929  if (stream < 0)
2930  {
2931  // segment didn't get downloaded (timeout?)
2932  LOG(VB_PLAYBACK, LOG_ERR, LOC +
2933  QString("seek error: segment %1 should have been downloaded, but didn't."
2934  " Playback will stall")
2935  .arg(segnum));
2936  }
2937  else
2938  {
2939  if (segment == nullptr) // can never happen, make coverity happy
2940  {
2941  // stream doesn't contain segment error can't continue,
2942  // unknown error
2943  return -1;
2944  }
2945  int32_t skip = ((postime - starttime) * segment->Size()) / segment->Duration();
2946  segment->Read(nullptr, skip);
2947  }
2948  LOG(VB_PLAYBACK, LOG_INFO, LOC + QString("seek completed in %1s")
2949  .arg(duration_cast<std::chrono::seconds>(mdate() - starting).count()));
2950 
2951  return m_playback->Offset();
2952 }
2953 
2954 long long HLSRingBuffer::GetReadPosition(void) const
2955 {
2956  if (m_error)
2957  return 0;
2958  return m_playback->Offset();
2959 }
2960 
2961 bool HLSRingBuffer::IsOpen(void) const
2962 {
2963  return !m_error && !m_streams.isEmpty() && NumSegments() > 0;
2964 }
2965 
2967 {
2968  QMutexLocker lock(&m_lock);
2969 
2970  // segment didn't get downloaded (timeout?)
2971  LOG(VB_PLAYBACK, LOG_DEBUG, LOC + QString("requesting interrupt"));
2972  m_interrupted = true;
2973 }
2974 
2976 {
2977  QMutexLocker lock(&m_lock);
2978 
2979  // segment didn't get downloaded (timeout?)
2980  LOG(VB_PLAYBACK, LOG_DEBUG, LOC + QString("requesting restart"));
2981  m_interrupted = false;
2982 }
HLSStream::Cancel
void Cancel(void)
Definition: httplivestreambuffer.cpp:771
force
bool force
Definition: mythtv/programs/mythcommflag/main.cpp:72
HLSStream::Id
int Id(void) const
Definition: httplivestreambuffer.cpp:704
HLSRingBuffer::m_error
bool m_error
Definition: httplivestreambuffer.h:111
HLSSegment::operator=
HLSSegment & operator=(const HLSSegment &rhs)
Definition: httplivestreambuffer.cpp:146
RET_OK
@ RET_OK
Definition: httplivestreambuffer.cpp:67
secondsFromFloat
std::enable_if_t< std::is_floating_point_v< T >, std::chrono::seconds > secondsFromFloat(T value)
Helper function for convert a floating point number to a duration.
Definition: mythchrono.h:80
HLSSegment::HLSSegment
HLSSegment(const HLSSegment &rhs)
Definition: httplivestreambuffer.cpp:141
HLSRingBuffer::ParseProgramDateTime
static int ParseProgramDateTime(HLSStream *hls, const QString &line)
Definition: httplivestreambuffer.cpp:2019
StreamWorker::Cancel
void Cancel(void)
Definition: httplivestreambuffer.cpp:932
HLSRingBuffer::m_meta
bool m_meta
Definition: httplivestreambuffer.h:110
StreamWorker::AddSegmentToStream
void AddSegmentToStream(int segnum, int stream)
Definition: httplivestreambuffer.cpp:1022
MThread::start
void start(QThread::Priority p=QThread::InheritPriority)
Tell MThread to start running the thread in the near future.
Definition: mthread.cpp:286
hardwareprofile.smolt.timeout
float timeout
Definition: smolt.py:103
HLSStream::m_lock
QMutex m_lock
Definition: httplivestreambuffer.cpp:868
HLSRingBuffer::ChooseSegment
int ChooseSegment(int stream) const
Definition: httplivestreambuffer.cpp:2403
HLSStream::operator>
bool operator>(const HLSStream &b) const
Definition: httplivestreambuffer.cpp:464
PlaylistWorker::UpdatePlaylist
static int UpdatePlaylist(HLSStream *hls_new, HLSStream *hls)
Definition: httplivestreambuffer.cpp:1429
HLSStream::Unlock
void Unlock(void)
Definition: httplivestreambuffer.cpp:756
HLSRingBuffer::HLSRingBuffer
HLSRingBuffer(const QString &lfilename)
Definition: httplivestreambuffer.cpp:1542
PlaylistWorker::WaitForSignal
void WaitForSignal(std::chrono::milliseconds time=std::chrono::milliseconds::max())
Definition: httplivestreambuffer.cpp:1283
copy
long long copy(QFile &dst, QFile &src, uint block_size)
Copies src file to dst file.
Definition: mythmiscutil.cpp:311
StreamWorker::CurrentPlaybackBuffer
int CurrentPlaybackBuffer(bool lock=true)
Definition: httplivestreambuffer.cpp:1000
HLSStream::HLSStream
HLSStream(const int mid, const uint64_t bitrate, QString uri)
Definition: httplivestreambuffer.cpp:402
HLSRingBuffer::FindStream
HLSStream * FindStream(const HLSStream *hls_new, const StreamsList *streams=nullptr) const
Definition: httplivestreambuffer.cpp:1644
HLSRingBuffer::NumStreams
int NumStreams(void) const
Definition: httplivestreambuffer.cpp:2387
HLSRingBuffer::SizeMedia
int64_t SizeMedia(void) const
Definition: httplivestreambuffer.cpp:2636
PlaylistWorker::m_waitcond
QWaitCondition m_waitcond
Definition: httplivestreambuffer.cpp:1539
HLSSegment::m_bitrate
uint64_t m_bitrate
Definition: httplivestreambuffer.cpp:387
decoded_URI
static QString decoded_URI(const QString &uri)
Definition: httplivestreambuffer.cpp:72
StreamWorker::m_sumbandwidth
double m_sumbandwidth
Definition: httplivestreambuffer.cpp:1244
StreamWorker::run
void run(void) override
Runs the Qt event loop unless we have a QRunnable, in which case we run the runnable run instead.
Definition: httplivestreambuffer.cpp:1092
LOC
#define LOC
Definition: httplivestreambuffer.cpp:57
HLSRingBuffer::NumSegments
int NumSegments(void) const
Definition: httplivestreambuffer.cpp:2392
HLSStream::RemoveSegment
void RemoveSegment(HLSSegment *segment, bool willdelete=true)
Definition: httplivestreambuffer.cpp:568
HLSStream::TargetDuration
std::chrono::seconds TargetDuration(void) const
Definition: httplivestreambuffer.cpp:724
MythMediaBuffer::m_startReadAhead
bool m_startReadAhead
Definition: mythmediabuffer.h:195
HLSRingBuffer::TestForHTTPLiveStreaming
static bool TestForHTTPLiveStreaming(const QString &filename)
Definition: httplivestreambuffer.cpp:1715
MThread::wait
bool wait(std::chrono::milliseconds time=std::chrono::milliseconds::max())
Wait for the MThread to exit, with a maximum timeout.
Definition: mthread.cpp:303
HLSRingBuffer::ParseKey
int ParseKey(HLSStream *hls, const QString &line)
Definition: httplivestreambuffer.cpp:1934
PlaylistWorker::m_retries
int m_retries
Definition: httplivestreambuffer.cpp:1536
PlaylistWorker::PlaylistWorker
PlaylistWorker(HLSRingBuffer *parent, std::chrono::milliseconds wait)
Definition: httplivestreambuffer.cpp:1252
HLSSegment::Size
int32_t Size(void) const
Definition: httplivestreambuffer.cpp:192
HLSSegment::IsEmpty
bool IsEmpty(void) const
Definition: httplivestreambuffer.cpp:187
HLSRingBuffer::GetFirstStream
HLSStream * GetFirstStream(const StreamsList *streams=nullptr) const
Definition: httplivestreambuffer.cpp:1626
HLSPlayback::m_offset
uint64_t m_offset
Definition: httplivestreambuffer.cpp:918
HLSRingBuffer::SafeRead
int SafeRead(void *data, uint sz) override
Definition: httplivestreambuffer.cpp:2681
MythMediaBuffer::m_safeFilename
QString m_safeFilename
Definition: mythmediabuffer.h:181
HLSStream::DownloadSegmentData
int DownloadSegmentData(int segnum, uint64_t &bandwidth, int stream)
Definition: httplivestreambuffer.cpp:613
MThread::usleep
static void usleep(std::chrono::microseconds time)
Definition: mthread.cpp:338
HLSStream::operator=
HLSStream & operator=(const HLSStream &rhs)
Definition: httplivestreambuffer.cpp:431
HLSStream::SetVersion
void SetVersion(int x)
Definition: httplivestreambuffer.cpp:712
HLSSegment::Title
QString Title(void) const
Definition: httplivestreambuffer.cpp:263
StreamWorker::Bandwidth
int64_t Bandwidth(void) const
Definition: httplivestreambuffer.cpp:1079
HLSRingBuffer::SanitizeStreams
void SanitizeStreams(StreamsList *streams=nullptr)
Streams may not be all starting at the same sequence number, so attempt to align their starting seque...
Definition: httplivestreambuffer.cpp:2449
HLSStream::SetTargetDuration
void SetTargetDuration(std::chrono::seconds x)
Definition: httplivestreambuffer.cpp:728
HLSStream::Cache
bool Cache(void) const
Definition: httplivestreambuffer.cpp:736
relative_URI
static QString relative_URI(const QString &surl, const QString &spath)
Definition: httplivestreambuffer.cpp:79
HLSStream::GetSegment
HLSSegment * GetSegment(const int wanted) const
Definition: httplivestreambuffer.cpp:523
x0
static int x0
Definition: mythsocket.cpp:56
HLSStream::Live
bool Live(void) const
Definition: httplivestreambuffer.cpp:744
StreamWorker::Seek
void Seek(int val)
Definition: httplivestreambuffer.cpp:960
MythMediaBuffer
Definition: mythmediabuffer.h:50
StreamWorker
Definition: httplivestreambuffer.cpp:925
LOG
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:23
MThread::RunProlog
void RunProlog(void)
Sets up a thread, call this if you reimplement run().
Definition: mthread.cpp:196
HLSPlayback::SetOffset
void SetOffset(uint64_t val)
Definition: httplivestreambuffer.cpp:883
MythDownloadManager
Definition: mythdownloadmanager.h:50
HLSRingBuffer::m_interrupted
bool m_interrupted
Definition: httplivestreambuffer.h:134
HLSStream::AppendSegment
void AppendSegment(HLSSegment *segment)
Definition: httplivestreambuffer.cpp:517
HLSStream::m_live
bool m_live
Definition: httplivestreambuffer.cpp:864
HLSStream::m_segments
QList< HLSSegment * > m_segments
Definition: httplivestreambuffer.cpp:866
PlaylistWorker::m_interrupted
bool m_interrupted
Definition: httplivestreambuffer.cpp:1534
StreamWorker::IsAtEnd
bool IsAtEnd(bool lock=false)
Definition: httplivestreambuffer.cpp:967
PLAYLIST_FAILURE
#define PLAYLIST_FAILURE
Definition: httplivestreambuffer.cpp:62
StreamWorker::BandwidthAdaptation
int BandwidthAdaptation(int progid, uint64_t &bandwidth) const
Definition: httplivestreambuffer.cpp:1201
HLSStream::IsGreater
static bool IsGreater(const HLSStream *s1, const HLSStream *s2)
Definition: httplivestreambuffer.cpp:454
PlaylistWorker::ReloadPlaylist
int ReloadPlaylist(void)
Reload playlist.
Definition: httplivestreambuffer.cpp:1387
startup
static int startup()
Definition: mythtv/programs/mythshutdown/main.cpp:771
HLSRingBuffer::m_playback
HLSPlayback * m_playback
Definition: httplivestreambuffer.h:105
StreamWorker::WaitForSignal
void WaitForSignal(std::chrono::milliseconds time=std::chrono::milliseconds::max())
Definition: httplivestreambuffer.cpp:1066
HLSStream::UpdateWith
void UpdateWith(const HLSStream &upd)
Definition: httplivestreambuffer.cpp:764
HLSSegment::m_duration
std::chrono::seconds m_duration
Definition: httplivestreambuffer.cpp:386
kMythBufferHLS
@ kMythBufferHLS
Definition: mythmediabuffer.h:46
mdate
static std::chrono::microseconds mdate(void)
Definition: httplivestreambuffer.cpp:91
HLSSegment::Duration
std::chrono::seconds Duration(void) const
Definition: httplivestreambuffer.cpp:167
HLSRingBuffer::ParseEndList
static int ParseEndList(HLSStream *hls)
Definition: httplivestreambuffer.cpp:2085
StreamWorker::Lock
void Lock(void)
Definition: httplivestreambuffer.cpp:1071
mythburn.FILE
int FILE
Definition: mythburn.py:139
HLSStream::RemoveSegment
void RemoveSegment(int segnum, bool willdelete=true)
Definition: httplivestreambuffer.cpp:590
HLSSegment::Id
int Id(void) const
Definition: httplivestreambuffer.cpp:172
HLSRingBuffer::m_bitrate
int64_t m_bitrate
assumed bitrate of playback used for the purpose of calculating length and seek position.
Definition: httplivestreambuffer.h:121
HLSSegment::Download
int Download(void)
Definition: httplivestreambuffer.cpp:197
tmp
static guint32 * tmp
Definition: goom_core.cpp:31
HLSRingBuffer::m_playlistworker
PlaylistWorker * m_playlistworker
Definition: httplivestreambuffer.h:132
StreamWorker::m_countbandwidth
int m_countbandwidth
Definition: httplivestreambuffer.cpp:1245
HLSStream::m_targetduration
std::chrono::seconds m_targetduration
Definition: httplivestreambuffer.cpp:859
HLSRingBuffer::IsHTTPLiveStreaming
static bool IsHTTPLiveStreaming(QByteArray *s)
Definition: httplivestreambuffer.cpp:1681
StreamWorker::Unlock
void Unlock(void)
Definition: httplivestreambuffer.cpp:1075
HLSSegment::CancelDownload
void CancelDownload(void)
Definition: httplivestreambuffer.cpp:212
HLSPlayback::SetStream
void SetStream(int val)
Definition: httplivestreambuffer.cpp:896
HLSRingBuffer::m_fd
FILE * m_fd
Definition: httplivestreambuffer.h:133
StreamWorker::Wakeup
void Wakeup(void)
Definition: httplivestreambuffer.cpp:1061
StreamWorker::m_parent
HLSRingBuffer * m_parent
Definition: httplivestreambuffer.cpp:1234
HLSStream::m_id
int m_id
Definition: httplivestreambuffer.cpp:856
StreamWorker::Segment
int Segment(void)
Definition: httplivestreambuffer.cpp:955
HLSRingBuffer::OpenFile
bool OpenFile(const QString &lfilename, std::chrono::milliseconds retry_ms=kDefaultOpenTimeout) override
Opens an HTTP Live Stream for reading.
Definition: httplivestreambuffer.cpp:2516
HLSSegment::m_lock
QMutex m_lock
Definition: httplivestreambuffer.cpp:393
HLSSegment::Url
QString Url(void) const
Definition: httplivestreambuffer.cpp:222
HLSRingBuffer::SeekInternal
long long SeekInternal(long long pos, int whence) override
Definition: httplivestreambuffer.cpp:2796
mythlogging.h
PlaylistWorker::Wakeup
void Wakeup(void)
Definition: httplivestreambuffer.cpp:1275
StreamWorker::CurrentLiveBuffer
int CurrentLiveBuffer(void)
Definition: httplivestreambuffer.cpp:1013
StreamWorker::SetBuffer
void SetBuffer(int val)
Definition: httplivestreambuffer.cpp:1017
StreamWorker::m_waitcond
QWaitCondition m_waitcond
Definition: httplivestreambuffer.cpp:1243
HLSSegment::m_data
QByteArray m_data
Definition: httplivestreambuffer.cpp:391
hardwareprofile.config.p
p
Definition: config.py:33
HLSSegment::~HLSSegment
~HLSSegment()=default
PlaylistWorker::Lock
void Lock(void)
Definition: httplivestreambuffer.cpp:1288
HLSSegment
Definition: httplivestreambuffer.cpp:122
StreamWorker::m_lock
QMutex m_lock
Definition: httplivestreambuffer.cpp:1242
HLSSegment::Clear
void Clear(void)
Definition: httplivestreambuffer.cpp:257
HLSStream::Bitrate
uint64_t Bitrate(void) const
Definition: httplivestreambuffer.cpp:732
StreamWorker::GotBufferedSegments
bool GotBufferedSegments(int from, int count) const
check that we have at least [count] segments buffered from position [from]
Definition: httplivestreambuffer.cpp:985
StreamWorker::m_bandwidth
int64_t m_bandwidth
Definition: httplivestreambuffer.cpp:1237
HLSRingBuffer::ParseDecimalValue
static int ParseDecimalValue(const QString &line, int &target)
Return the decimal argument in a line of type: blah:<decimal> presence of value <decimal> is compulso...
Definition: httplivestreambuffer.cpp:1772
PlaylistWorker
Definition: httplivestreambuffer.cpp:1249
PlaylistWorker::GetHTTPLiveMetaPlaylist
int GetHTTPLiveMetaPlaylist(StreamsList *streams)
Definition: httplivestreambuffer.cpp:1505
HLSSegment::m_title
QString m_title
Definition: httplivestreambuffer.cpp:388
HLSRingBuffer
Definition: httplivestreambuffer.h:43
HLSRingBuffer::PlaylistWorker
friend class PlaylistWorker
Definition: httplivestreambuffer.h:131
HLSRingBuffer::GetSegment
HLSSegment * GetSegment(int segnum, std::chrono::milliseconds timeout=1s)
Retrieve segment [segnum] from any available streams.
Definition: httplivestreambuffer.cpp:2351
HLSStream::RemoveListSegments
void RemoveListSegments(QHash< HLSSegment *, bool > &table)
Definition: httplivestreambuffer.cpp:603
HLSRingBuffer::Prefetch
int Prefetch(int count)
Preferetch the first x segments of the stream.
Definition: httplivestreambuffer.cpp:2300
HLSPlayback::Segment
int Segment(void)
Definition: httplivestreambuffer.cpp:901
HLSRingBuffer::WaitUntilBuffered
void WaitUntilBuffered(void)
Wait until we have enough segments buffered to allow smooth playback Do not wait if VOD and at end of...
Definition: httplivestreambuffer.cpp:2651
MThread::RunEpilog
void RunEpilog(void)
Cleans up a thread's resources, call this if you reimplement run().
Definition: mthread.cpp:209
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:430
HLSSegment::Read
uint32_t Read(uint8_t *buffer, int32_t length, FILE *fd=nullptr)
Definition: httplivestreambuffer.cpp:232
HLSPlayback::Offset
uint64_t Offset(void) const
Definition: httplivestreambuffer.cpp:879
HLSRingBuffer::ParseTargetDuration
static int ParseTargetDuration(HLSStream *hls, const QString &line)
Definition: httplivestreambuffer.cpp:1844
HLSStream::m_size
uint64_t m_size
Definition: httplivestreambuffer.cpp:861
PLAYBACK_MINBUFFER
#define PLAYBACK_MINBUFFER
Definition: httplivestreambuffer.cpp:60
HLSRingBuffer::m_lock
QMutex m_lock
Definition: httplivestreambuffer.h:109
HLSRingBuffer::Continue
void Continue(void)
Definition: httplivestreambuffer.cpp:2975
HLSRingBuffer::GetStreamForSegment
HLSStream * GetStreamForSegment(int segnum) const
Definition: httplivestreambuffer.cpp:1602
HLSRingBuffer::SaveToDisk
bool SaveToDisk(const QString &filename, int segstart=0, int segend=-1)
Definition: httplivestreambuffer.cpp:2604
MythDownloadManager::cancelDownload
void cancelDownload(const QString &url, bool block=true)
Cancel a queued or current download.
Definition: mythdownloadmanager.cpp:1013
StreamWorker::StreamForSegment
int StreamForSegment(int segmentid, bool lock=true) const
return the stream used to download a particular segment or -1 if it was never downloaded
Definition: httplivestreambuffer.cpp:1039
HLSRingBuffer::Interrupt
void Interrupt(void)
Definition: httplivestreambuffer.cpp:2966
HLSRingBuffer::m_m3u8
QString m_m3u8
Definition: httplivestreambuffer.h:102
HLSRingBuffer::ParseDiscontinuity
static int ParseDiscontinuity(HLSStream *hls, const QString &line)
Definition: httplivestreambuffer.cpp:2097
HLSStream::SetCache
void SetCache(bool x)
Definition: httplivestreambuffer.cpp:740
uint
unsigned int uint
Definition: compat.h:140
HLSRingBuffer::ParseMediaSequence
static int ParseMediaSequence(HLSStream *hls, const QString &line)
Definition: httplivestreambuffer.cpp:1905
HLSPlayback::HLSPlayback
HLSPlayback(void)=default
HLSSegment::SetTitle
void SetTitle(const QString &x)
Definition: httplivestreambuffer.cpp:267
HLSStream::Version
int Version(void) const
Definition: httplivestreambuffer.cpp:708
MythMediaBuffer::m_filename
QString m_filename
Definition: mythmediabuffer.h:184
HLSSegment::m_id
int m_id
Definition: httplivestreambuffer.cpp:385
HLSRingBuffer::ParseSegmentInformation
static int ParseSegmentInformation(const HLSStream *hls, const QString &line, int &duration, QString &title)
Definition: httplivestreambuffer.cpp:1785
HLSStream::~HLSStream
~HLSStream()
Definition: httplivestreambuffer.cpp:425
HLSStream::m_bitrate
uint64_t m_bitrate
Definition: httplivestreambuffer.cpp:860
HLSPlayback::AddOffset
void AddOffset(uint64_t val)
Definition: httplivestreambuffer.cpp:887
StreamWorker::m_segmap
QMap< int, int > m_segmap
Definition: httplivestreambuffer.cpp:1241
HLSStream::NumSegments
int NumSegments(void) const
Definition: httplivestreambuffer.cpp:512
HLSRingBuffer::ParseAttributes
static QString ParseAttributes(const QString &line, const char *attr)
Definition: httplivestreambuffer.cpp:1747
HLSRingBuffer::m_streamworker
StreamWorker * m_streamworker
Definition: httplivestreambuffer.h:130
PlaylistWorker::Unlock
void Unlock(void)
Definition: httplivestreambuffer.cpp:1292
StreamWorker::CurrentStream
int CurrentStream(void)
Definition: httplivestreambuffer.cpp:950
HLSRingBuffer::StreamWorker
friend class StreamWorker
Definition: httplivestreambuffer.h:129
HLSStream::Url
QString Url(void) const
Definition: httplivestreambuffer.cpp:760
StreamWorker::AverageNewBandwidth
double AverageNewBandwidth(int64_t bandwidth)
Definition: httplivestreambuffer.cpp:1083
StreamWorker::m_interrupted
bool m_interrupted
Definition: httplivestreambuffer.cpp:1235
HLSStream::m_duration
std::chrono::seconds m_duration
Definition: httplivestreambuffer.cpp:863
HLSRingBuffer::GetStream
HLSStream * GetStream(int wanted, const StreamsList *streams=nullptr) const
Definition: httplivestreambuffer.cpp:1612
HLSStream::m_startsequence
int m_startsequence
Definition: httplivestreambuffer.cpp:858
HLSStream::HLSStream
HLSStream(const HLSStream &rhs, bool copy=true)
Definition: httplivestreambuffer.cpp:412
HLSRingBuffer::m_startup
int m_startup
Definition: httplivestreambuffer.h:115
PlaylistWorker::m_wakeup
std::chrono::milliseconds m_wakeup
Definition: httplivestreambuffer.cpp:1535
HLSRingBuffer::GetReadPosition
long long GetReadPosition(void) const override
Definition: httplivestreambuffer.cpp:2954
HLSPlayback
Definition: httplivestreambuffer.cpp:873
MythMediaBuffer::KillReadAheadThread
void KillReadAheadThread(void)
Stops the read-ahead thread, and waits for it to stop.
Definition: mythmediabuffer.cpp:646
HLSRingBuffer::GetRealFileSizeInternal
long long GetRealFileSizeInternal(void) const override
Definition: httplivestreambuffer.cpp:2790
MythMediaBuffer::AVFormatInitNetwork
static void AVFormatInitNetwork(void)
Definition: mythmediabuffer.cpp:1860
HLSRingBuffer::~HLSRingBuffer
~HLSRingBuffer() override
Definition: httplivestreambuffer.cpp:1561
RET_ERROR
@ RET_ERROR
Definition: httplivestreambuffer.cpp:66
PLAYBACK_READAHEAD
#define PLAYBACK_READAHEAD
Definition: httplivestreambuffer.cpp:61
HLSSegment::SizePlayed
int32_t SizePlayed(void) const
Definition: httplivestreambuffer.cpp:227
HLSStream::SetLive
void SetLive(bool x)
Definition: httplivestreambuffer.cpp:748
httplivestreambuffer.h
HLSRingBuffer::IsSeekingAllowed
bool IsSeekingAllowed(void) override
Definition: httplivestreambuffer.h:55
HLSStream::FindSegment
HLSSegment * FindSegment(const int id, int *segnum=nullptr) const
Definition: httplivestreambuffer.cpp:533
HLSRingBuffer::ParseVersion
static int ParseVersion(const QString &line, int &version)
Definition: httplivestreambuffer.cpp:2054
HLSStream::m_version
int m_version
Definition: httplivestreambuffer.cpp:857
HLSRingBuffer::ParseM3U8
int ParseM3U8(const QByteArray *buffer, StreamsList *streams=nullptr)
Definition: httplivestreambuffer.cpp:2104
PlaylistWorker::run
void run(void) override
Runs the Qt event loop unless we have a QRunnable, in which case we run the runnable run instead.
Definition: httplivestreambuffer.cpp:1298
StreamWorker::RemoveSegmentFromStream
void RemoveSegmentFromStream(int segnum)
Definition: httplivestreambuffer.cpp:1029
std
Definition: mythchrono.h:23
HLSSegment::Unlock
void Unlock(void)
Definition: httplivestreambuffer.cpp:182
HLSPlayback::m_lock
QMutex m_lock
Definition: httplivestreambuffer.cpp:921
StreamsList
QList< HLSStream * > StreamsList
Definition: httplivestreambuffer.h:41
HLSStream::m_cache
bool m_cache
Definition: httplivestreambuffer.cpp:869
PlaylistWorker::m_parent
HLSRingBuffer * m_parent
Definition: httplivestreambuffer.cpp:1533
StreamWorker::StreamWorker
StreamWorker(HLSRingBuffer *parent, int startup, int buffer)
Definition: httplivestreambuffer.cpp:928
HLSRingBuffer::FreeStreamsList
void FreeStreamsList(QList< HLSStream * > *streams) const
Definition: httplivestreambuffer.cpp:1588
HLSStream::AddSegment
void AddSegment(const std::chrono::seconds duration, const QString &title, const QString &uri)
Definition: httplivestreambuffer.cpp:555
MThread
This is a wrapper around QThread that does several additional things.
Definition: mthread.h:48
cancelURL
static void cancelURL(const QString &url)
Definition: httplivestreambuffer.cpp:108
mthread.h
StreamWorker::m_segment
int m_segment
Definition: httplivestreambuffer.cpp:1239
HLSRingBuffer::m_streams
StreamsList m_streams
Definition: httplivestreambuffer.h:108
HLSSegment::m_played
int32_t m_played
Definition: httplivestreambuffer.cpp:392
HLSSegment::Lock
void Lock(void)
Definition: httplivestreambuffer.cpp:177
HLSSegment::m_downloading
bool m_downloading
Definition: httplivestreambuffer.cpp:394
HLSSegment::HLSSegment
HLSSegment(const std::chrono::seconds mduration, const int id, QString title, QString uri, QString current_key_path)
Definition: httplivestreambuffer.cpp:125
HLSRingBuffer::ParseStreamInformation
HLSStream * ParseStreamInformation(const QString &line, const QString &uri) const
Definition: httplivestreambuffer.cpp:1862
HLSSegment::Reset
void Reset(void)
Definition: httplivestreambuffer.cpp:252
HLSStream::Size
uint64_t Size(bool force=false)
Return the estimated size of the stream in bytes if a segment hasn't been downloaded,...
Definition: httplivestreambuffer.cpp:474
PlaylistWorker::m_wokenup
bool m_wokenup
Definition: httplivestreambuffer.cpp:1537
StreamWorker::m_buffer
int m_buffer
Definition: httplivestreambuffer.cpp:1240
HLSRingBuffer::m_killed
bool m_killed
Definition: httplivestreambuffer.h:135
HLSPlayback::m_stream
int m_stream
Definition: httplivestreambuffer.cpp:919
MythMediaBuffer::m_posLock
QReadWriteLock m_posLock
Definition: mythmediabuffer.h:164
HLSStream::StartSequence
int StartSequence(void) const
Definition: httplivestreambuffer.cpp:716
HLSPlayback::Stream
int Stream(void)
Definition: httplivestreambuffer.cpp:891
HLSRingBuffer::SanityCheck
void SanityCheck(const HLSStream *hls) const
Definition: httplivestreambuffer.cpp:2325
HLSStream
Definition: httplivestreambuffer.cpp:399
HLSStream::Lock
void Lock(void)
Definition: httplivestreambuffer.cpp:752
d
static const iso6937table * d
Definition: iso6937tables.cpp:1025
HLSPlayback::SetSegment
void SetSegment(int val)
Definition: httplivestreambuffer.cpp:906
HLSPlayback::IncrSegment
int IncrSegment(void)
Definition: httplivestreambuffer.cpp:911
HLSStream::Duration
std::chrono::seconds Duration(void)
Definition: httplivestreambuffer.cpp:501
downloadURL
static bool downloadURL(const QString &url, QByteArray *buffer, QString &finalURL)
Definition: httplivestreambuffer.cpp:96
HLSStream::Clear
void Clear(void)
Definition: httplivestreambuffer.cpp:507
MythMediaBuffer::m_rwLock
QReadWriteLock m_rwLock
Definition: mythmediabuffer.h:183
HLSStream::m_url
QString m_url
Definition: httplivestreambuffer.cpp:867
mythdownloadmanager.h
HLSSegment::m_url
QString m_url
Definition: httplivestreambuffer.cpp:390
build_compdb.filename
filename
Definition: build_compdb.py:21
HLSSegment::Data
const char * Data(void) const
provides pointer to raw segment data
Definition: httplivestreambuffer.cpp:274
HLSRingBuffer::GetCurrentStream
HLSStream * GetCurrentStream(void) const
return the stream we are currently streaming from
Definition: httplivestreambuffer.cpp:1672
nv_python_libs.bbciplayer.bbciplayer_api.version
string version
Definition: bbciplayer_api.py:81
HLSRingBuffer::m_seektoend
bool m_seektoend
FFmpeg seek to the end of the stream in order to determine the length of the video.
Definition: httplivestreambuffer.h:127
PlaylistWorker::m_lock
QMutex m_lock
Definition: httplivestreambuffer.cpp:1538
PlaylistWorker::Cancel
void Cancel()
Definition: httplivestreambuffer.cpp:1254
HLSRingBuffer::GetLastStream
HLSStream * GetLastStream(const StreamsList *streams=nullptr) const
Definition: httplivestreambuffer.cpp:1631
HLSPlayback::m_segment
int m_segment
Definition: httplivestreambuffer.cpp:920
HLSStream::SetStartSequence
void SetStartSequence(int x)
Definition: httplivestreambuffer.cpp:720
HLSRingBuffer::DurationForBytes
int DurationForBytes(uint size)
returns an estimated duration in ms for size amount of data returns 0 if we can't estimate the durati...
Definition: httplivestreambuffer.cpp:2766
HLSStream::operator<
bool operator<(const HLSStream &b) const
Definition: httplivestreambuffer.cpp:459
HLSRingBuffer::IsOpen
bool IsOpen(void) const override
Definition: httplivestreambuffer.cpp:2961
GetMythDownloadManager
MythDownloadManager * GetMythDownloadManager(void)
Gets the pointer to the MythDownloadManager singleton.
Definition: mythdownloadmanager.cpp:145
HLSRingBuffer::ParseAllowCache
static int ParseAllowCache(HLSStream *hls, const QString &line)
Definition: httplivestreambuffer.cpp:2030
StreamWorker::m_stream
int m_stream
Definition: httplivestreambuffer.cpp:1238