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