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