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 using aesiv_array = std::array<uint8_t,AES_BLOCK_SIZE>;
55 #endif
56 
57 #define LOC QString("HLSBuffer: ")
58 
59 // Constants
60 #define PLAYBACK_MINBUFFER 2 // number of segments to prefetch before playback starts
61 #define PLAYBACK_READAHEAD 6 // number of segments download queue ahead of playback
62 #define PLAYLIST_FAILURE 6 // number of consecutive failures after which
63  // playback will abort
64 enum
65 {
66  RET_ERROR = -1,
67  RET_OK = 0,
68 };
69 
70 /* utility methods */
71 
72 static QString decoded_URI(const QString &uri)
73 {
74  QByteArray ba = uri.toLatin1();
75  QUrl url = QUrl::fromEncoded(ba);
76  return url.toString();
77 }
78 
79 static QString relative_URI(const QString &surl, const QString &spath)
80 {
81  QUrl url = QUrl(surl);
82  QUrl path = QUrl(spath);
83 
84  if (!path.isRelative())
85  {
86  return spath;
87  }
88  return url.resolved(path).toString();
89 }
90 
91 static uint64_t mdate(void)
92 {
93  timeval t {};
94  gettimeofday(&t, nullptr);
95  return t.tv_sec * 1000000ULL + t.tv_usec;
96 }
97 
98 static bool downloadURL(const QString &url, QByteArray *buffer, QString &finalURL)
99 {
101  return mdm->download(url, buffer, false, &finalURL);
102 }
103 
104 static bool downloadURL(const QString &url, QByteArray *buffer)
105 {
107  return mdm->download(url, buffer);
108 }
109 
110 static void cancelURL(const QString &url)
111 {
113  mdm->cancelDownload(url);
114 }
115 
116 static void cancelURL(const QStringList &urls)
117 {
119  mdm->cancelDownload(urls);
120 }
121 
122 /* segment container */
123 
125 {
126 public:
127  HLSSegment(const int mduration, const int id, const QString &title,
128  const QString &uri, const QString &current_key_path)
129  {
130  m_duration = mduration; /* seconds */
131  m_id = id;
132  m_url = uri;
133  m_title = title;
134 #ifdef USING_LIBCRYPTO
135  m_pszKeyPath = current_key_path;
136 #else
137  Q_UNUSED(current_key_path);
138 #endif
139  }
140 
141  ~HLSSegment() = default;
142 
143  HLSSegment(const HLSSegment &rhs)
144  {
145  *this = rhs;
146  }
147 
149  {
150  if (this == &rhs)
151  return *this;
152  m_id = rhs.m_id;
153  m_duration = rhs.m_duration;
154  m_bitrate = rhs.m_bitrate;
155  m_url = rhs.m_url;
156  // keep the old data downloaded
157  // m_data = m_data;
158  // m_played = m_played;
159  m_title = rhs.m_title;
160 #ifdef USING_LIBCRYPTO
161  m_pszKeyPath = rhs.m_pszKeyPath;
162  memcpy(&m_aeskey, &(rhs.m_aeskey), sizeof(m_aeskey));
163  m_keyloaded = rhs.m_keyloaded;
164 #endif
166  return *this;
167  }
168 
169  int Duration(void) const
170  {
171  return m_duration;
172  }
173 
174  int Id(void) const
175  {
176  return m_id;
177  }
178 
179  void Lock(void)
180  {
181  m_lock.lock();
182  }
183 
184  void Unlock(void)
185  {
186  m_lock.unlock();
187  }
188 
189  bool IsEmpty(void) const
190  {
191  return m_data.isEmpty();
192  }
193 
194  int32_t Size(void) const
195  {
196  return m_data.size();
197  }
198 
199  int Download(void)
200  {
201  // must own lock
202  m_downloading = true;
203  bool ret = downloadURL(m_url, &m_data);
204  m_downloading = false;
205  // didn't succeed, clear buffer
206  if (!ret)
207  {
208  m_data.clear();
209  return RET_ERROR;
210  }
211  return RET_OK;
212  }
213 
214  void CancelDownload(void)
215  {
216  if (m_downloading)
217  {
218  cancelURL(m_url);
219  QMutexLocker lock(&m_lock);
220  m_downloading = false;
221  }
222  }
223 
224  QString Url(void) const
225  {
226  return m_url;
227  }
228 
229  int32_t SizePlayed(void) const
230  {
231  return m_played;
232  }
233 
234  uint32_t Read(uint8_t *buffer, int32_t length, FILE *fd = nullptr)
235  {
236  int32_t left = m_data.size() - m_played;
237  if (length > left)
238  {
239  length = left;
240  }
241  if (buffer != nullptr)
242  {
243  memcpy(buffer, m_data.constData() + m_played, length);
244  // write data to disk if required
245  if (fd)
246  {
247  fwrite(m_data.constData() + m_played, length, 1, fd);
248  }
249  }
250  m_played += length;
251  return length;
252  }
253 
254  void Reset(void)
255  {
256  m_played = 0;
257  }
258 
259  void Clear(void)
260  {
261  m_played = 0;
262  m_data.clear();
263  }
264 
265  QString Title(void) const
266  {
267  return m_title;
268  }
269  void SetTitle(const QString &x)
270  {
271  m_title = x;
272  }
276  const char *Data(void) const
277  {
278  return m_data.constData();
279  }
280 
281 #ifdef USING_LIBCRYPTO
282  int DownloadKey(void)
283  {
284  // must own lock
285  if (m_keyloaded)
286  return RET_OK;
287  QByteArray key;
288  bool ret = downloadURL(m_pszKeyPath, &key);
289  if (!ret || key.size() != AES_BLOCK_SIZE)
290  {
291  if (ret)
292  {
293  LOG(VB_PLAYBACK, LOG_ERR, LOC +
294  QString("The AES key loaded doesn't have the right size (%1)")
295  .arg(key.size()));
296  }
297  else
298  {
299  LOG(VB_PLAYBACK, LOG_ERR, LOC + "Failed to download AES key");
300  }
301  return RET_ERROR;
302  }
303  AES_set_decrypt_key((const unsigned char*)key.constData(), 128, &m_aeskey);
304  m_keyloaded = true;
305  return RET_OK;
306  }
307 
308  int DecodeData(const aesiv_array &IV, bool iv_valid)
309  {
310  /* Decrypt data using AES-128 */
311  int aeslen = m_data.size() & ~0xf;
312  aesiv_array iv {};
313  char *decrypted_data = new char[m_data.size()];
314  if (!iv_valid)
315  {
316  /*
317  * If the EXT-X-KEY tag does not have the IV attribute, implementations
318  * MUST use the sequence number of the media file as the IV when
319  * encrypting or decrypting that media file. The big-endian binary
320  * representation of the sequence number SHALL be placed in a 16-octet
321  * buffer and padded (on the left) with zeros.
322  */
323  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  std::copy(IV.cbegin(), IV.cend(), iv.begin());
331  }
332  AES_cbc_encrypt((unsigned char*)m_data.constData(),
333  (unsigned char*)decrypted_data, aeslen,
334  &m_aeskey, iv.data(), 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  m_aesIv.fill(0);
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  m_aesIv = rhs.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  if (segment != nullptr)
597  {
598  m_duration -= segment->Duration();
599  if (willdelete)
600  delete segment;
601  }
602  m_segments.removeAt(segnum);
603  }
604 
605  void RemoveListSegments(QHash<HLSSegment*,bool> &table)
606  {
607  for (auto 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_aesIv, m_ivloaded) != 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  std::copy(ba.cbegin(), ba.cend(), m_aesIv.begin());
839  m_ivloaded = true;
840  return true;
841  }
842  aesiv_array 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  aesiv_array m_aesIv {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  QHash<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  std::array<uint8_t,1024> buffer {};
1730  ret = ffurl_read(context, buffer.data(), buffer.size());
1731  if (ret > 0)
1732  {
1733  QByteArray ba((const char*)buffer.data(), 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.midRef(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  if (segment == nullptr)
2414  continue;
2415 
2416  if (segment->Duration() > hls->TargetDuration())
2417  {
2418  LOG(VB_PLAYBACK, LOG_WARNING, LOC +
2419  QString("EXTINF:%1 duration is larger than EXT-X-TARGETDURATION:%2")
2420  .arg(segment->Duration()).arg(hls->TargetDuration()));
2421  }
2422 
2423  wanted_duration += segment->Duration();
2424  if (wanted_duration >= 3 * hls->TargetDuration())
2425  {
2426  /* Start point found */
2427  wanted = i;
2428  segid = segment->Id();
2429  break;
2430  }
2431  i-- ;
2432  }
2433 
2434  LOG(VB_PLAYBACK, LOG_DEBUG, LOC +
2435  QString("Choose segment %1/%2 [%3]")
2436  .arg(wanted).arg(count).arg(segid));
2437  return wanted;
2438 }
2439 
2445 {
2446  // no lock is required as, at this stage, no threads have either been started
2447  // or we are working on a stream list unique to PlaylistWorker
2448  if (streams == nullptr)
2449  {
2450  streams = &m_streams;
2451  }
2452  QMap<int,int> idstart;
2453  // Find the highest starting sequence for each stream
2454  for (int n = streams->size() - 1 ; n >= 0; n--)
2455  {
2456  HLSStream *hls = GetStream(n, streams);
2457  if (hls->NumSegments() == 0)
2458  {
2459  streams->removeAt(n);
2460  continue; // remove it
2461  }
2462 
2463  int id = hls->Id();
2464  int start = hls->StartSequence();
2465  if (!idstart.contains(id))
2466  {
2467  idstart.insert(id, start);
2468  }
2469  int start2 = idstart.value(id);
2470  if (start > start2)
2471  {
2472  idstart.insert(id, start);
2473  }
2474  }
2475  // Find the highest starting sequence for each stream
2476  for (int n = 0; n < streams->size(); n++)
2477  {
2478  HLSStream *hls = GetStream(n, streams);
2479  int id = hls->Id();
2480  int seq = hls->StartSequence();
2481  int newstart= idstart.value(id);
2482  int todrop = newstart - seq;
2483  if (todrop == 0)
2484  {
2485  // perfect, leave it alone
2486  continue;
2487  }
2488  if (todrop >= hls->NumSegments() || todrop < 0)
2489  {
2490  LOG(VB_PLAYBACK, LOG_ERR, LOC +
2491  QString("stream %1 [id=%2] can't be properly adjusted, ignoring")
2492  .arg(n).arg(hls->Id()));
2493  continue;
2494  }
2495  for (int i = 0; i < todrop; i++)
2496  {
2497  hls->RemoveSegment(0);
2498  }
2499  hls->SetStartSequence(newstart);
2500  }
2501 }
2502 
2511 bool HLSRingBuffer::OpenFile(const QString &lfilename, uint /*retry_ms*/)
2512 {
2513  QWriteLocker lock(&m_rwLock);
2514 
2515  m_safeFilename = lfilename;
2516  m_filename = lfilename;
2517  QString finalURL;
2518 
2519  QByteArray buffer;
2520  if (!downloadURL(m_filename, &buffer, finalURL))
2521  {
2522  LOG(VB_PLAYBACK, LOG_ERR, LOC +
2523  QString("Couldn't open URL %1").arg(m_filename));
2524  return false; // can't download file
2525  }
2526  if (m_filename != finalURL)
2527  {
2528  LOG(VB_PLAYBACK, LOG_INFO, LOC +
2529  QString("Redirected %1 -> %2 ").arg(m_filename).arg(finalURL));
2530  m_filename = finalURL;
2531  }
2532  if (!IsHTTPLiveStreaming(&buffer))
2533  {
2534  LOG(VB_PLAYBACK, LOG_ERR, LOC +
2535  QString("%1 isn't a HTTP Live Streaming URL").arg(m_filename));
2536  return false;
2537  }
2538  // let's go
2539  m_m3u8 = m_filename;
2540  LOG(VB_PLAYBACK, LOG_INFO, LOC + QString("HTTP Live Streaming (%1)")
2541  .arg(m_m3u8));
2542 
2543  /* Parse HLS m3u8 content. */
2544  if (ParseM3U8(&buffer, &m_streams) != RET_OK || m_streams.isEmpty())
2545  {
2546  LOG(VB_PLAYBACK, LOG_ERR, LOC +
2547  QString("An error occurred reading M3U8 playlist (%1)").arg(m_filename));
2548  m_error = true;
2549  return false;
2550  }
2551 
2552  SanitizeStreams();
2553 
2554  /* HLS standard doesn't provide any guaranty about streams
2555  being sorted by bitrate, so we sort them, higher bitrate being first */
2556  std::sort(m_streams.begin(), m_streams.end(), HLSStream::IsGreater);
2557 
2558  // if we want as close to live. We should be selecting a further segment
2559  // m_live ? ChooseSegment(0) : 0;
2560 // if (m_live && m_startup < 0)
2561 // {
2562 // LOG(VB_PLAYBACK, LOG_WARNING, LOC +
2563 // "less data than 3 times 'target duration' available for "
2564 // "live playback, playback may stall");
2565 // m_startup = 0;
2566 // }
2567  m_startup = 0;
2569 
2571  m_streamworker->start();
2572 
2574  {
2575  LOG(VB_PLAYBACK, LOG_ERR, LOC +
2576  "fetching first segment failed or didn't complete within 10s.");
2577  m_error = true;
2578  return false;
2579  }
2580 
2581  // set bitrate value used to calculate the size of the stream
2582  HLSStream *hls = GetCurrentStream();
2583  m_bitrate = hls->Bitrate();
2584 
2585  // Set initial seek position (relative to m_startup)
2586  m_playback->SetOffset(0);
2587 
2588  /* Initialize HLS live stream thread */
2589  //if (m_live) // commented out as some streams are marked as VOD, yet
2590  // aren't, they are updated over time
2591  {
2592  m_playlistworker = new PlaylistWorker(this, 0);
2594  }
2595 
2596  return true;
2597 }
2598 
2599 bool HLSRingBuffer::SaveToDisk(const QString &filename, int segstart, int segend)
2600 {
2601  // download it all
2602  FILE *fp = fopen(filename.toLatin1().constData(), "w");
2603  if (fp == nullptr)
2604  return false;
2605  int count = NumSegments();
2606  if (segend < 0)
2607  {
2608  segend = count;
2609  }
2610  for (int i = segstart; i < segend; i++)
2611  {
2612  HLSSegment *segment = GetSegment(i);
2613  if (segment == nullptr)
2614  {
2615  LOG(VB_PLAYBACK, LOG_ERR, LOC +
2616  QString("downloading %1 failed").arg(i));
2617  }
2618  else
2619  {
2620  LOG(VB_PLAYBACK, LOG_INFO, LOC +
2621  QString("download of %1 succeeded")
2622  .arg(i));
2623  fwrite(segment->Data(), segment->Size(), 1, fp);
2624  fflush(fp);
2625  }
2626  }
2627  fclose(fp);
2628  return true;
2629 }
2630 
2631 int64_t HLSRingBuffer::SizeMedia(void) const
2632 {
2633  if (m_error)
2634  return -1;
2635 
2636  HLSStream *hls = GetCurrentStream();
2637  int64_t size = hls->Duration() * m_bitrate / 8;
2638 
2639  return size;
2640 }
2641 
2647 {
2648  bool live = GetCurrentStream()->Live();
2649 
2650  // last seek was to end of media, we are just in seek mode so do not wait
2651  if (m_seektoend)
2652  return;
2653 
2655  (!live && m_streamworker->IsAtEnd()))
2656  {
2657  return;
2658  }
2659 
2660  // danger of getting to the end... pause until we have some more
2661  LOG(VB_PLAYBACK, LOG_INFO, LOC +
2662  QString("pausing until we get sufficient data buffered"));
2664  m_streamworker->Lock();
2665  int retries = 0;
2666  while (!m_error && !m_interrupted &&
2667  (m_streamworker->CurrentPlaybackBuffer(false) < 2) &&
2668  (live || !m_streamworker->IsAtEnd()))
2669  {
2671  retries++;
2672  }
2674 }
2675 
2676 int HLSRingBuffer::SafeRead(void *data, uint sz)
2677 {
2678  if (m_error)
2679  return -1;
2680 
2681  int used = 0;
2682  int i_read = sz;
2683 
2685  if (m_interrupted)
2686  {
2687  LOG(VB_PLAYBACK, LOG_DEBUG, LOC + QString("interrupted"));
2688  return 0;
2689  }
2690 
2691  do
2692  {
2693  int segnum = m_playback->Segment();
2694  if (segnum >= NumSegments())
2695  {
2696  m_playback->AddOffset(used);
2697  return used;
2698  }
2699  int stream = m_streamworker->StreamForSegment(segnum);
2700  if (stream < 0)
2701  {
2702  // we haven't downloaded this segment yet, likely that it was
2703  // dropped (livetv?)
2705  continue;
2706  }
2707  HLSStream *hls = GetStream(stream);
2708  if (hls == nullptr)
2709  break;
2710  HLSSegment *segment = hls->GetSegment(segnum);
2711  if (segment == nullptr)
2712  break;
2713 
2714  segment->Lock();
2715  if (segment->SizePlayed() == segment->Size())
2716  {
2717  if (!hls->Cache() || hls->Live())
2718  {
2719  segment->Clear();
2721  }
2722  else
2723  {
2724  segment->Reset();
2725  }
2726 
2728  segment->Unlock();
2729 
2730  /* signal download thread we're about to use a new segment */
2732  continue;
2733  }
2734 
2735  if (segment->SizePlayed() == 0)
2736  {
2737  LOG(VB_PLAYBACK, LOG_INFO, LOC +
2738  QString("started reading segment %1 [id:%2] from stream %3 (%4 buffered)")
2739  .arg(segnum).arg(segment->Id()).arg(stream)
2741  }
2742 
2743  int32_t len = segment->Read((uint8_t*)data + used, i_read, m_fd);
2744  used += len;
2745  i_read -= len;
2746  segment->Unlock();
2747  }
2748  while (i_read > 0 && !m_interrupted);
2749 
2750  if (m_interrupted)
2751  LOG(VB_PLAYBACK, LOG_DEBUG, LOC + QString("interrupted"));
2752 
2753  m_playback->AddOffset(used);
2754  return used;
2755 }
2756 
2762 {
2763  int segnum = m_playback->Segment();
2764  int stream = m_streamworker->StreamForSegment(segnum);
2765  if (stream < 0)
2766  {
2767  return 0;
2768  }
2769  HLSStream *hls = GetStream(stream);
2770  if (hls == nullptr)
2771  {
2772  return 0;
2773  }
2774  HLSSegment *segment = hls->GetSegment(segnum);
2775  if (segment == nullptr)
2776  {
2777  return 0;
2778  }
2779  auto byterate = (uint64_t)(((double)segment->Size()) /
2780  ((double)segment->Duration()));
2781 
2782  return (int)((size * 1000.0) / byterate);
2783 }
2784 
2786 {
2787  QReadLocker lock(&m_rwLock);
2788  return SizeMedia();
2789 }
2790 
2791 long long HLSRingBuffer::SeekInternal(long long pos, int whence)
2792 {
2793  if (m_error)
2794  return -1;
2795 
2796  if (!IsSeekingAllowed())
2797  {
2798  return m_playback->Offset();
2799  }
2800 
2801  int64_t starting = mdate();
2802 
2803  QWriteLocker lock(&m_posLock);
2804 
2805  int totalsize = SizeMedia();
2806  int64_t where = 0;
2807  switch (whence)
2808  {
2809  case SEEK_CUR:
2810  // return current location, nothing to do
2811  if (pos == 0)
2812  {
2813  return m_playback->Offset();
2814  }
2815  where = m_playback->Offset() + pos;
2816  break;
2817  case SEEK_END:
2818  where = SizeMedia() - pos;
2819  break;
2820  case SEEK_SET:
2821  default:
2822  where = pos;
2823  break;
2824  }
2825 
2826  // We determine the duration at which it was really attempting to seek to
2827  int64_t postime = (where * 8.0) / m_bitrate;
2828  int count = NumSegments();
2829  int segnum = m_playback->Segment();
2830  HLSStream *hls = GetStreamForSegment(segnum);
2831 
2832  /* restore current segment's file position indicator to 0 */
2833  HLSSegment *segment = hls->GetSegment(segnum);
2834  if (segment != nullptr)
2835  {
2836  segment->Lock();
2837  segment->Reset();
2838  segment->Unlock();
2839  }
2840 
2841  if (where > totalsize)
2842  {
2843  // we're at the end, never let seek after last 3 segments
2844  postime -= hls->TargetDuration() * 3;
2845  if (postime < 0)
2846  {
2847  postime = 0;
2848  }
2849  }
2850 
2851  // Find segment containing position
2852  int64_t starttime = 0LL;
2853  int64_t endtime = 0LL;
2854  for (int n = m_startup; n < count; n++)
2855  {
2856  hls = GetStreamForSegment(n);
2857  if (hls == nullptr)
2858  {
2859  // error, should never happen, irrecoverable error
2860  return -1;
2861  }
2862  segment = hls->GetSegment(n);
2863  if (segment == nullptr)
2864  {
2865  // stream doesn't contain segment error can't continue,
2866  // unknown error
2867  return -1;
2868  }
2869  endtime += segment->Duration();
2870  if (postime < endtime)
2871  {
2872  segnum = n;
2873  break;
2874  }
2875  starttime = endtime;
2876  }
2877 
2878  /*
2879  * Live Mode exception:
2880  * FFmpeg seek to the last segment in order to determine the size of the video
2881  * so do not allow seeking to the last segment if in live mode as we don't care
2882  * about the size
2883  * Also do not allow to seek before the current playback segment as segment
2884  * has been cleared from memory
2885  * We only let determine the size if the bandwidth would allow fetching the
2886  * the segments in less than 5s
2887  */
2888  if (hls->Live() && (segnum >= count - 1 || segnum < m_playback->Segment()) &&
2889  ((hls->TargetDuration() * hls->Bitrate() / m_streamworker->Bandwidth()) > 5))
2890  {
2891  return m_playback->Offset();
2892  }
2893  m_seektoend = segnum >= count - 1;
2894 
2895  m_playback->SetSegment(segnum);
2896 
2897  m_streamworker->Seek(segnum);
2898  m_playback->SetOffset(postime * m_bitrate / 8);
2899 
2900  m_streamworker->Lock();
2901 
2902  /* Wait for download to be finished and to buffer 3 segment */
2903  LOG(VB_PLAYBACK, LOG_INFO, LOC +
2904  QString("seek to segment %1").arg(segnum));
2905  int retries = 0;
2906 
2907  // see if we've already got the segment, and at least 2 buffered after
2908  // then no need to wait for streamworker
2909  while (!m_error && !m_interrupted &&
2910  (!m_streamworker->GotBufferedSegments(segnum, 2) &&
2911  (m_streamworker->CurrentPlaybackBuffer(false) < 2) &&
2912  !m_streamworker->IsAtEnd()))
2913  {
2915  retries++;
2916  }
2917  if (m_interrupted)
2918  LOG(VB_PLAYBACK, LOG_DEBUG, LOC + QString("interrupted"));
2919 
2921 
2922  // now seek within found segment
2923  int stream = m_streamworker->StreamForSegment(segnum);
2924  if (stream < 0)
2925  {
2926  // segment didn't get downloaded (timeout?)
2927  LOG(VB_PLAYBACK, LOG_ERR, LOC +
2928  QString("seek error: segment %1 should have been downloaded, but didn't."
2929  " Playback will stall")
2930  .arg(segnum));
2931  }
2932  else
2933  {
2934  if (segment == nullptr) // can never happen, make coverity happy
2935  {
2936  // stream doesn't contain segment error can't continue,
2937  // unknown error
2938  return -1;
2939  }
2940  int32_t skip = ((postime - starttime) * segment->Size()) / segment->Duration();
2941  segment->Read(nullptr, skip);
2942  }
2943  LOG(VB_PLAYBACK, LOG_INFO, LOC +
2944  QString("seek completed in %1s").arg((mdate() - starting) / 1000000.0));
2945 
2946  return m_playback->Offset();
2947 }
2948 
2949 long long HLSRingBuffer::GetReadPosition(void) const
2950 {
2951  if (m_error)
2952  return 0;
2953  return m_playback->Offset();
2954 }
2955 
2956 bool HLSRingBuffer::IsOpen(void) const
2957 {
2958  return !m_error && !m_streams.isEmpty() && NumSegments() > 0;
2959 }
2960 
2962 {
2963  QMutexLocker lock(&m_lock);
2964 
2965  // segment didn't get downloaded (timeout?)
2966  LOG(VB_PLAYBACK, LOG_DEBUG, LOC + QString("requesting interrupt"));
2967  m_interrupted = true;
2968 }
2969 
2971 {
2972  QMutexLocker lock(&m_lock);
2973 
2974  // segment didn't get downloaded (timeout?)
2975  LOG(VB_PLAYBACK, LOG_DEBUG, LOC + QString("requesting restart"));
2976  m_interrupted = false;
2977 }
HLSStream::Cancel
void Cancel(void)
Definition: httplivestreambuffer.cpp:773
force
bool force
Definition: mythtv/programs/mythcommflag/main.cpp:74
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:148
RET_OK
@ RET_OK
Definition: httplivestreambuffer.cpp:67
HLSSegment::HLSSegment
HLSSegment(const HLSSegment &rhs)
Definition: httplivestreambuffer.cpp:143
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:288
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:305
HLSStream::TargetDuration
int TargetDuration(void) const
Definition: httplivestreambuffer.cpp:726
StreamWorker::CurrentPlaybackBuffer
int CurrentPlaybackBuffer(bool lock=true)
Definition: httplivestreambuffer.cpp:1002
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:2631
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:72
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:57
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:194
HLSRingBuffer::TestForHTTPLiveStreaming
static bool TestForHTTPLiveStreaming(const QString &filename)
Definition: httplivestreambuffer.cpp:1718
title
QString title
Definition: mythplugins/mytharchive/mytharchivehelper/main.cpp:636
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:194
HLSSegment::IsEmpty
bool IsEmpty(void) const
Definition: httplivestreambuffer.cpp:189
HLSRingBuffer::GetFirstStream
HLSStream * GetFirstStream(const StreamsList *streams=nullptr) const
Definition: httplivestreambuffer.cpp:1629
HLSPlayback::m_offset
uint64_t m_offset
Definition: httplivestreambuffer.cpp:920
HLSRingBuffer::SafeRead
int SafeRead(void *data, uint sz) override
Definition: httplivestreambuffer.cpp:2676
MythMediaBuffer::m_safeFilename
QString m_safeFilename
Definition: mythmediabuffer.h:180
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:265
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:2444
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:79
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:198
HLSPlayback::SetOffset
void SetOffset(uint64_t val)
Definition: httplivestreambuffer.cpp:885
MythDownloadManager
Definition: mythdownloadmanager.h:48
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:62
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:771
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:174
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:199
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:214
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:224
HLSRingBuffer::SeekInternal
long long SeekInternal(long long pos, int whence) override
Definition: httplivestreambuffer.cpp:2791
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:125
StreamWorker::m_lock
QMutex m_lock
Definition: httplivestreambuffer.cpp:1244
HLSSegment::Clear
void Clear(void)
Definition: httplivestreambuffer.cpp:259
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
HLSStream::RemoveListSegments
void RemoveListSegments(QHash< HLSSegment *, bool > &table)
Definition: httplivestreambuffer.cpp:605
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:2646
PlaylistWorker::PlaylistWorker
PlaylistWorker(HLSRingBuffer *parent, int64_t wait)
Definition: httplivestreambuffer.cpp:1254
filename
QString filename
Definition: mythplugins/mytharchive/mytharchivehelper/main.cpp:637
MThread::RunEpilog
void RunEpilog(void)
Cleans up a thread's resources, call this if you reimplement run().
Definition: mthread.cpp:211
MythDownloadManager::download
bool download(const QString &url, const QString &dest, bool reload=false)
Downloads a URL to a file in blocking mode.
Definition: mythdownloadmanager.cpp:430
HLSSegment::Read
uint32_t Read(uint8_t *buffer, int32_t length, FILE *fd=nullptr)
Definition: httplivestreambuffer.cpp:234
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:60
HLSRingBuffer::m_lock
QMutex m_lock
Definition: httplivestreambuffer.h:109
HLSRingBuffer::Continue
void Continue(void)
Definition: httplivestreambuffer.cpp:2970
HLSRingBuffer::GetStreamForSegment
HLSStream * GetStreamForSegment(int segnum) const
Definition: httplivestreambuffer.cpp:1605
mdate
static uint64_t mdate(void)
Definition: httplivestreambuffer.cpp:91
HLSRingBuffer::SaveToDisk
bool SaveToDisk(const QString &filename, int segstart=0, int segend=-1)
Definition: httplivestreambuffer.cpp:2599
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:1013
StreamWorker::StreamForSegment
int StreamForSegment(int segmentid, bool lock=true) const
return the stream used to download a particular segment or -1 if it was never downloaded
Definition: httplivestreambuffer.cpp:1041
HLSRingBuffer::Interrupt
void Interrupt(void)
Definition: httplivestreambuffer.cpp:2961
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:269
HLSStream::Version
int Version(void) const
Definition: httplivestreambuffer.cpp:710
MythMediaBuffer::m_filename
QString m_filename
Definition: mythmediabuffer.h:183
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:2949
HLSPlayback
Definition: httplivestreambuffer.cpp:876
MythMediaBuffer::KillReadAheadThread
void KillReadAheadThread(void)
Stops the read-ahead thread, and waits for it to stop.
Definition: mythmediabuffer.cpp:649
HLSRingBuffer::GetRealFileSizeInternal
long long GetRealFileSizeInternal(void) const override
Definition: httplivestreambuffer.cpp:2785
MythMediaBuffer::AVFormatInitNetwork
static void AVFormatInitNetwork(void)
Definition: mythmediabuffer.cpp:1853
HLSRingBuffer::~HLSRingBuffer
~HLSRingBuffer() override
Definition: httplivestreambuffer.cpp:1564
RET_ERROR
@ RET_ERROR
Definition: httplivestreambuffer.cpp:66
PLAYBACK_READAHEAD
#define PLAYBACK_READAHEAD
Definition: httplivestreambuffer.cpp:61
HLSSegment::SizePlayed
int32_t SizePlayed(void) const
Definition: httplivestreambuffer.cpp:229
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:184
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:110
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:179
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:254
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:163
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
d
static const iso6937table * d
Definition: iso6937tables.cpp:1025
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:305
downloadURL
static bool downloadURL(const QString &url, QByteArray *buffer, QString &finalURL)
Definition: httplivestreambuffer.cpp:98
HLSStream::Clear
void Clear(void)
Definition: httplivestreambuffer.cpp:509
MythMediaBuffer::m_rwLock
QReadWriteLock m_rwLock
Definition: mythmediabuffer.h:182
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:2511
HLSSegment::Data
const char * Data(void) const
provides pointer to raw segment data
Definition: httplivestreambuffer.cpp:276
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:169
PlaylistWorker::Cancel
void Cancel()
Definition: httplivestreambuffer.cpp:1256
MThread::usleep
static void usleep(unsigned long time)
Definition: mthread.cpp:342
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:2761
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:2956
GetMythDownloadManager
MythDownloadManager * GetMythDownloadManager(void)
Gets the pointer to the MythDownloadManager singleton.
Definition: mythdownloadmanager.cpp:145
HLSSegment::HLSSegment
HLSSegment(const int mduration, const int id, const QString &title, const QString &uri, const QString &current_key_path)
Definition: httplivestreambuffer.cpp:127
HLSRingBuffer::ParseAllowCache
static int ParseAllowCache(HLSStream *hls, const QString &line)
Definition: httplivestreambuffer.cpp:2033
StreamWorker::m_stream
int m_stream
Definition: httplivestreambuffer.cpp:1240