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