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