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