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 {0}; // 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  foreach (auto old, m_segments)
414  {
415  auto *segment = new HLSSegment(*old);
416  AppendSegment(segment);
417  }
418  }
419 
421  {
422  foreach (auto & segment, m_segments)
423  delete segment;
424  }
425 
427  {
428  if (this == &rhs)
429  return *this;
430  // do not copy segments
431  m_id = rhs.m_id;
432  m_version = rhs.m_version;
435  m_bitrate = rhs.m_bitrate;
436  m_size = rhs.m_size;
437  m_duration = rhs.m_duration;
438  m_live = rhs.m_live;
439  m_url = rhs.m_url;
440  m_cache = rhs.m_cache;
441 #ifdef USING_LIBCRYPTO
442  m_keypath = rhs.m_keypath;
443  m_ivloaded = rhs.m_ivloaded;
444  memcpy(m_aesIv, rhs.m_aesIv, sizeof(m_aesIv));
445 #endif
446  return *this;
447  }
448 
449  static bool IsGreater(const HLSStream *s1, const HLSStream *s2)
450  {
451  return s1->Bitrate() > s2->Bitrate();
452  }
453 
454  bool operator<(const HLSStream &b) const
455  {
456  return this->Bitrate() < b.Bitrate();
457  }
458 
459  bool operator>(const HLSStream &b) const
460  {
461  return this->Bitrate() > b.Bitrate();
462  }
463 
469  uint64_t Size(bool force = false)
470  {
471  if (m_size > 0 && !force)
472  return m_size;
473  QMutexLocker lock(&m_lock);
474 
475  int64_t size = 0;
476  int count = NumSegments();
477 
478  for (int i = 0; i < count; i++)
479  {
480  HLSSegment *segment = GetSegment(i);
481  segment->Lock();
482  if (segment->Size() > 0)
483  {
484  size += (int64_t)segment->Size();
485  }
486  else
487  {
488  size += segment->Duration() * Bitrate() / 8;
489  }
490  segment->Unlock();
491  }
492  m_size = size;
493  return m_size;
494  }
495 
496  int64_t Duration(void)
497  {
498  QMutexLocker lock(&m_lock);
499  return m_duration;
500  }
501 
502  void Clear(void)
503  {
504  m_segments.clear();
505  }
506 
507  int NumSegments(void) const
508  {
509  return m_segments.size();
510  }
511 
512  void AppendSegment(HLSSegment *segment)
513  {
514  // must own lock
515  m_segments.append(segment);
516  }
517 
518  HLSSegment *GetSegment(const int wanted) const
519  {
520  int count = NumSegments();
521  if (count <= 0)
522  return nullptr;
523  if ((wanted < 0) || (wanted >= count))
524  return nullptr;
525  return m_segments[wanted];
526  }
527 
528  HLSSegment *FindSegment(const int id, int *segnum = nullptr) const
529  {
530  int count = NumSegments();
531  if (count <= 0)
532  return nullptr;
533  for (int n = 0; n < count; n++)
534  {
535  HLSSegment *segment = GetSegment(n);
536  if (segment == nullptr)
537  break;
538  if (segment->Id() == id)
539  {
540  if (segnum != nullptr)
541  {
542  *segnum = n;
543  }
544  return segment;
545  }
546  }
547  return nullptr;
548  }
549 
550  void AddSegment(const int duration, const QString &title, const QString &uri)
551  {
552  QMutexLocker lock(&m_lock);
553  QString psz_uri = relative_URI(m_url, uri);
554  int id = NumSegments() + m_startsequence;
555 #ifndef USING_LIBCRYPTO
556  QString m_keypath;
557 #endif
558  auto *segment = new HLSSegment(duration, id, title, psz_uri, m_keypath);
559  AppendSegment(segment);
560  m_duration += duration;
561  }
562 
563  void RemoveSegment(HLSSegment *segment, bool willdelete = true)
564  {
565  QMutexLocker lock(&m_lock);
566  m_duration -= segment->Duration();
567  if (willdelete)
568  {
569  delete segment;
570  }
571  int count = NumSegments();
572  if (count <= 0)
573  return;
574  for (int n = 0; n < count; n++)
575  {
576  HLSSegment *old = GetSegment(n);
577  if (old == segment)
578  {
579  m_segments.removeAt(n);
580  break;
581  }
582  }
583  }
584 
585  void RemoveSegment(int segnum, bool willdelete = true)
586  {
587  QMutexLocker lock(&m_lock);
588  HLSSegment *segment = GetSegment(segnum);
589  m_duration -= segment->Duration();
590  if (willdelete)
591  {
592  delete segment;
593  }
594  m_segments.removeAt(segnum);
595  }
596 
597  void RemoveListSegments(QMap<HLSSegment*,bool> &table)
598  {
599  QMap<HLSSegment*,bool>::iterator it;
600  for (it = table.begin(); it != table.end(); ++it)
601  {
602  bool todelete = *it;
603  HLSSegment *p = it.key();
604  RemoveSegment(p, todelete);
605  }
606  }
607 
608  int DownloadSegmentData(int segnum, uint64_t &bandwidth, int stream)
609  {
610  HLSSegment *segment = GetSegment(segnum);
611  if (segment == nullptr)
612  return RET_ERROR;
613 
614  segment->Lock();
615  if (!segment->IsEmpty())
616  {
617  /* Segment already downloaded */
618  segment->Unlock();
619  return RET_OK;
620  }
621 
622  LOG(VB_PLAYBACK, LOG_DEBUG, LOC +
623  QString("started download of segment %1 [%2/%3] using stream %4")
624  .arg(segnum).arg(segment->Id()).arg(NumSegments()+m_startsequence)
625  .arg(stream));
626 
627  /* sanity check - can we download this segment on time? */
628  if ((bandwidth > 0) && (m_bitrate > 0))
629  {
630  uint64_t size = (segment->Duration() * m_bitrate); /* bits */
631  int estimated = (int)(size / bandwidth);
632  if (estimated > segment->Duration())
633  {
634  LOG(VB_PLAYBACK, LOG_INFO, LOC +
635  QString("downloading of segment %1 [id:%2] will take %3s, "
636  "which is longer than its playback (%4s) at %5bit/s")
637  .arg(segnum)
638  .arg(segment->Id())
639  .arg(estimated)
640  .arg(segment->Duration())
641  .arg(bandwidth));
642  }
643  }
644 
645  uint64_t start = mdate();
646  if (segment->Download() != RET_OK)
647  {
648  LOG(VB_PLAYBACK, LOG_ERR, LOC +
649  QString("downloaded segment %1 [id:%2] from stream %3 failed")
650  .arg(segnum).arg(segment->Id()).arg(m_id));
651  segment->Unlock();
652  return RET_ERROR;
653  }
654 
655  uint64_t downloadduration = mdate() - start;
656  if (m_bitrate == 0 && segment->Duration() > 0)
657  {
658  /* Try to estimate the bandwidth for this stream */
659  m_bitrate = (uint64_t)(((double)segment->Size() * 8) /
660  ((double)segment->Duration()));
661  }
662 
663 #ifdef USING_LIBCRYPTO
664  /* If the segment is encrypted, decode it */
665  if (segment->HasKeyPath())
666  {
667  /* Do we have loaded the key ? */
668  if (!segment->KeyLoaded())
669  {
670  if (ManageSegmentKeys() != RET_OK)
671  {
672  LOG(VB_PLAYBACK, LOG_ERR, LOC +
673  "couldn't retrieve segment AES-128 key");
674  segment->Unlock();
675  return RET_OK;
676  }
677  }
678  if (segment->DecodeData(m_ivloaded ? m_aesIv : nullptr) != RET_OK)
679  {
680  segment->Unlock();
681  return RET_ERROR;
682  }
683  }
684 #endif
685  segment->Unlock();
686 
687  downloadduration = downloadduration < 1 ? 1 : downloadduration;
688  bandwidth = segment->Size() * 8 * 1000000ULL / downloadduration; /* bits / s */
689  LOG(VB_PLAYBACK, LOG_DEBUG, LOC +
690  QString("downloaded segment %1 [id:%2] took %3ms for %4 bytes: bandwidth:%5kiB/s")
691  .arg(segnum)
692  .arg(segment->Id())
693  .arg(downloadduration / 1000)
694  .arg(segment->Size())
695  .arg(bandwidth / 8192.0));
696 
697  return RET_OK;
698  }
699  int Id(void) const
700  {
701  return m_id;
702  }
703  int Version(void) const
704  {
705  return m_version;
706  }
707  void SetVersion(int x)
708  {
709  m_version = x;
710  }
711  int StartSequence(void) const
712  {
713  return m_startsequence;
714  }
715  void SetStartSequence(int x)
716  {
717  m_startsequence = x;
718  }
719  int TargetDuration(void) const
720  {
721  return m_targetduration;
722  }
723  void SetTargetDuration(int x)
724  {
725  m_targetduration = x;
726  }
727  uint64_t Bitrate(void) const
728  {
729  return m_bitrate;
730  }
731  bool Cache(void) const
732  {
733  return m_cache;
734  }
735  void SetCache(bool x)
736  {
737  m_cache = x;
738  }
739  bool Live(void) const
740  {
741  return m_live;
742  }
743  void SetLive(bool x)
744  {
745  m_live = x;
746  }
747  void Lock(void)
748  {
749  m_lock.lock();
750  }
751  void Unlock(void)
752  {
753  m_lock.unlock();
754  }
755  QString Url(void) const
756  {
757  return m_url;
758  }
759  void UpdateWith(const HLSStream &upd)
760  {
761  QMutexLocker lock(&m_lock);
764  m_cache = upd.m_cache;
765  }
766  void Cancel(void)
767  {
768  QMutexLocker lock(&m_lock);
769  foreach (auto & segment, m_segments)
770  {
771  if (segment)
772  {
773  segment->CancelDownload();
774  }
775  }
776  }
777 
778 #ifdef USING_LIBCRYPTO
779 
783  int ManageSegmentKeys()
784  {
785  HLSSegment *seg = nullptr;
786  HLSSegment *prev_seg = nullptr;
787  int count = NumSegments();
788 
789  for (int i = 0; i < count; i++)
790  {
791  prev_seg = seg;
792  seg = GetSegment(i);
793  if (seg == nullptr )
794  continue;
795  if (!seg->HasKeyPath())
796  continue; /* No key to load ? continue */
797  if (seg->KeyLoaded())
798  continue; /* The key is already loaded */
799 
800  /* if the key has not changed, and already available from previous segment,
801  * try to copy it, and don't load the key */
802  if (prev_seg && prev_seg->KeyLoaded() &&
803  (seg->KeyPath() == prev_seg->KeyPath()))
804  {
805  seg->CopyAESKey(*prev_seg);
806  continue;
807  }
808  if (seg->DownloadKey() != RET_OK)
809  return RET_ERROR;
810  }
811  return RET_OK;
812  }
813  bool SetAESIV(QString line)
814  {
815  /*
816  * If the EXT-X-KEY tag has the IV attribute, implementations MUST use
817  * the attribute value as the IV when encrypting or decrypting with that
818  * key. The value MUST be interpreted as a 128-bit hexadecimal number
819  * and MUST be prefixed with 0x or 0X.
820  */
821  if (!line.startsWith(QLatin1String("0x"), Qt::CaseInsensitive))
822  return false;
823  if (line.size() % 2)
824  {
825  // not even size, pad with front 0
826  line.insert(2, QLatin1String("0"));
827  }
828  int padding = max(0, AES_BLOCK_SIZE - (line.size() - 2));
829  QByteArray ba = QByteArray(padding, 0x0);
830  ba.append(QByteArray::fromHex(QByteArray(line.toLatin1().constData() + 2)));
831  memcpy(m_aesIv, ba.constData(), ba.size());
832  m_ivloaded = true;
833  return true;
834  }
835  uint8_t *AESIV(void)
836  {
837  return m_aesIv;
838  }
839  void SetKeyPath(const QString &x)
840  {
841  m_keypath = x;
842  }
843 
844 private:
845  QString m_keypath; // URL path of the encrypted key
846  bool m_ivloaded {false};
847  uint8_t m_aesIv[AES_BLOCK_SIZE]{0};// IV used when decypher the block
848 #endif
849 
850 private:
851  int m_id {0}; // program id
852  int m_version {1}; // protocol version should be 1
853  int m_startsequence {0}; // media starting sequence number
854  int m_targetduration {-1}; // maximum duration per segment (s)
855  uint64_t m_bitrate {0LL}; // bitrate of stream content (bits per second)
856  uint64_t m_size {0LL}; // stream length is calculated by taking the sum
857  // foreach segment of (segment->duration * hls->bitrate/8)
858  int64_t m_duration {0LL}; // duration of the stream in seconds
859  bool m_live {true};
860 
861  QList<HLSSegment*> m_segments; // list of segments
862  QString m_url; // uri to m3u8
863  QMutex m_lock;
864  bool m_cache {true};// allow caching
865 };
866 
867 // Playback Stream Information
869 {
870 public:
871  HLSPlayback(void) = default;
872 
873  /* offset is only used from main thread, no need for locking */
874  uint64_t Offset(void) const
875  {
876  return m_offset;
877  }
878  void SetOffset(uint64_t val)
879  {
880  m_offset = val;
881  }
882  void AddOffset(uint64_t val)
883  {
884  m_offset += val;
885  }
886  int Stream(void)
887  {
888  QMutexLocker lock(&m_lock);
889  return m_stream;
890  }
891  void SetStream(int val)
892  {
893  QMutexLocker lock(&m_lock);
894  m_stream = val;
895  }
896  int Segment(void)
897  {
898  QMutexLocker lock(&m_lock);
899  return m_segment;
900  }
901  void SetSegment(int val)
902  {
903  QMutexLocker lock(&m_lock);
904  m_segment = val;
905  }
906  int IncrSegment(void)
907  {
908  QMutexLocker lock(&m_lock);
909  return ++m_segment;
910  }
911 
912 private:
913  uint64_t m_offset {0}; // current offset in media
914  int m_stream {0}; // current HLSStream
915  int m_segment {0}; // current segment for playback
916  QMutex m_lock;
917 };
918 
919 // Stream Download Thread
920 class StreamWorker : public MThread
921 {
922 public:
923  StreamWorker(HLSRingBuffer *parent, int startup, int buffer) : MThread("HLSStream"),
924  m_parent(parent), m_segment(startup), m_buffer(buffer)
925  {
926  }
927  void Cancel(void)
928  {
929  m_interrupted = true;
930  Wakeup();
931  m_lock.lock();
932  // Interrupt on-going downloads of all segments
933  int streams = m_parent->NumStreams();
934  for (int i = 0; i < streams; i++)
935  {
936  HLSStream *hls = m_parent->GetStream(i);
937  if (hls)
938  {
939  hls->Cancel();
940  }
941  }
942  m_lock.unlock();
943  wait();
944  }
945  int CurrentStream(void)
946  {
947  QMutexLocker lock(&m_lock);
948  return m_stream;
949  }
950  int Segment(void)
951  {
952  QMutexLocker lock(&m_lock);
953  return m_segment;
954  }
955  void Seek(int val)
956  {
957  m_lock.lock();
958  m_segment = val;
959  m_lock.unlock();
960  Wakeup();
961  }
962  bool IsAtEnd(bool lock = false)
963  {
964  if (lock)
965  {
966  m_lock.lock();
967  }
968  int count = m_parent->NumSegments();
969  bool ret = m_segment >= count;
970  if (lock)
971  {
972  m_lock.unlock();
973  }
974  return ret;
975  }
976 
980  bool GotBufferedSegments(int from, int count) const
981  {
982  if (from + count > m_parent->NumSegments())
983  return false;
984 
985  for (int i = from; i < from + count; i++)
986  {
987  if (StreamForSegment(i, false) < 0)
988  {
989  return false;
990  }
991  }
992  return true;
993  }
994 
995  int CurrentPlaybackBuffer(bool lock = true)
996  {
997  if (lock)
998  {
999  m_lock.lock();
1000  }
1001  int ret = m_segment - m_parent->m_playback->Segment();
1002  if (lock)
1003  {
1004  m_lock.unlock();
1005  }
1006  return ret;
1007  }
1009  {
1010  return m_parent->NumSegments() - m_segment;
1011  }
1012  void SetBuffer(int val)
1013  {
1014  QMutexLocker lock(&m_lock);
1015  m_buffer = val;
1016  }
1017  void AddSegmentToStream(int segnum, int stream)
1018  {
1019  if (m_interrupted)
1020  return;
1021  QMutexLocker lock(&m_lock);
1022  m_segmap.insert(segnum, stream);
1023  }
1024  void RemoveSegmentFromStream(int segnum)
1025  {
1026  QMutexLocker lock(&m_lock);
1027  m_segmap.remove(segnum);
1028  }
1029 
1034  int StreamForSegment(int segmentid, bool lock = true) const
1035  {
1036  if (lock)
1037  {
1038  m_lock.lock();
1039  }
1040  int ret = 0;
1041  if (!m_segmap.contains(segmentid))
1042  {
1043  ret = -1; // we never downloaded that segment on any streams
1044  }
1045  else
1046  {
1047  ret = m_segmap[segmentid];
1048  }
1049  if (lock)
1050  {
1051  m_lock.unlock();
1052  }
1053  return ret;
1054  }
1055 
1056  void Wakeup(void)
1057  {
1058  // send a wake signal
1059  m_waitcond.wakeAll();
1060  }
1061  void WaitForSignal(unsigned long time = ULONG_MAX)
1062  {
1063  // must own lock
1064  m_waitcond.wait(&m_lock, time);
1065  }
1066  void Lock(void)
1067  {
1068  m_lock.lock();
1069  }
1070  void Unlock(void)
1071  {
1072  m_lock.unlock();
1073  }
1074  int64_t Bandwidth(void) const
1075  {
1076  return m_bandwidth;
1077  }
1078  double AverageNewBandwidth(int64_t bandwidth)
1079  {
1080  m_sumbandwidth += bandwidth;
1081  m_countbandwidth++;
1083  return m_bandwidth;
1084  }
1085 
1086 protected:
1087  void run(void) override // MThread
1088  {
1089  RunProlog();
1090 
1091  int retries = 0;
1092  while (!m_interrupted)
1093  {
1094  /*
1095  * we can go into waiting if:
1096  * - not live and download is more than 3 segments ahead of playback
1097  * - we are at the end of the stream
1098  */
1099  Lock();
1101  int dnldsegment = m_segment;
1102  int playsegment = m_parent->m_playback->Segment();
1103  if ((!hls->Live() && (playsegment < dnldsegment - m_buffer)) ||
1104  IsAtEnd())
1105  {
1106  /* wait until
1107  * 1- got interrupted
1108  * 2- we are less than 6 segments ahead of playback
1109  * 3- got asked to seek to a particular segment */
1110  while (!m_interrupted && (m_segment == dnldsegment) &&
1111  (((m_segment - playsegment) > m_buffer) || IsAtEnd()))
1112  {
1113  WaitForSignal();
1114  // do we have new segments available added by PlaylistWork?
1115  if (hls->Live() && !IsAtEnd())
1116  break;
1117  playsegment = m_parent->m_playback->Segment();
1118  }
1119  dnldsegment = m_segment;
1120  }
1121  Unlock();
1122 
1123  if (m_interrupted)
1124  {
1125  Wakeup();
1126  break;
1127  }
1128  // have we already downloaded the required segment?
1129  if (StreamForSegment(dnldsegment) < 0)
1130  {
1131  uint64_t bw = m_bandwidth;
1132  int err = hls->DownloadSegmentData(dnldsegment, bw, m_stream);
1133  if (m_interrupted)
1134  {
1135  // interrupt early
1136  Wakeup();
1137  break;
1138  }
1139  bw = AverageNewBandwidth(bw);
1140  if (err != RET_OK)
1141  {
1142  retries++;
1143  LOG(VB_PLAYBACK, LOG_DEBUG, LOC +
1144  QString("download failed, retry #%1").arg(retries));
1145  if (retries == 1) // first error
1146  continue; // will retry immediately
1147  usleep(500000); // sleep 0.5s
1148  if (retries == 2) // and retry once again
1149  continue;
1150  if (!m_parent->m_meta) // NOLINT(bugprone-branch-clone)
1151  {
1152  // no other stream to default to, skip packet
1153  retries = 0;
1154  }
1155  else
1156  {
1157  // TODO: should switch to another stream
1158  retries = 0;
1159  }
1160  }
1161  else
1162  {
1163  LOG(VB_PLAYBACK, LOG_DEBUG, LOC +
1164  QString("download completed, %1 segments ahead")
1165  .arg(CurrentLiveBuffer()));
1166  AddSegmentToStream(dnldsegment, m_stream);
1167  if (m_parent->m_meta && hls->Bitrate() != bw)
1168  {
1169  int newstream = BandwidthAdaptation(hls->Id(), bw);
1170 
1171  if (newstream >= 0 && newstream != m_stream)
1172  {
1173  LOG(VB_PLAYBACK, LOG_INFO, LOC +
1174  QString("switching to %1 bitrate %2 stream; changing "
1175  "from stream %3 to stream %4")
1176  .arg(bw >= hls->Bitrate() ? "faster" : "lower")
1177  .arg(bw).arg(m_stream).arg(newstream));
1178  m_stream = newstream;
1179  }
1180  }
1181  }
1182  }
1183  Lock();
1184  if (dnldsegment == m_segment) // false if seek was called
1185  {
1186  m_segment++;
1187  }
1188  Unlock();
1189  // Signal we're done
1190  Wakeup();
1191  }
1192 
1193  RunEpilog();
1194  }
1195 
1196  int BandwidthAdaptation(int progid, uint64_t &bandwidth) const
1197  {
1198  int candidate = -1;
1199  uint64_t bw = bandwidth;
1200  uint64_t bw_candidate = 0;
1201 
1202  int count = m_parent->NumStreams();
1203  for (int n = 0; n < count; n++)
1204  {
1205  /* Select best bandwidth match */
1206  HLSStream *hls = m_parent->GetStream(n);
1207  if (hls == nullptr)
1208  break;
1209 
1210  /* only consider streams with the same PROGRAM-ID */
1211  if (hls->Id() == progid)
1212  {
1213  if ((bw >= hls->Bitrate()) &&
1214  (bw_candidate < hls->Bitrate()))
1215  {
1216  LOG(VB_PLAYBACK, LOG_DEBUG, LOC +
1217  QString("candidate stream %1 bitrate %2 >= %3")
1218  .arg(n).arg(bw).arg(hls->Bitrate()));
1219  bw_candidate = hls->Bitrate();
1220  candidate = n; /* possible candidate */
1221  }
1222  }
1223  }
1224  bandwidth = bw_candidate;
1225  return candidate;
1226  }
1227 
1228 private:
1230  bool m_interrupted {false};
1231  // measured average download bandwidth (bits per second)
1232  int64_t m_bandwidth {0};
1233  int m_stream {0};// current HLSStream
1234  int m_segment; // current segment for downloading
1235  int m_buffer; // buffer kept between download and playback
1236  QMap<int,int> m_segmap; // segment with streamid used for download
1237  mutable QMutex m_lock;
1238  QWaitCondition m_waitcond;
1239  double m_sumbandwidth {0.0};
1241 };
1242 
1243 // Playlist Refresh Thread
1244 class PlaylistWorker : public MThread
1245 {
1246 public:
1247  PlaylistWorker(HLSRingBuffer *parent, int64_t wait) : MThread("HLSStream"),
1248  m_parent(parent), m_wakeup(wait) {}
1249  void Cancel()
1250  {
1251  m_interrupted = true;
1252  Wakeup();
1253  m_lock.lock();
1254  // Interrupt on-going downloads of all stream playlists
1255  int streams = m_parent->NumStreams();
1256  QStringList listurls;
1257  for (int i = 0; i < streams; i++)
1258  {
1259  HLSStream *hls = m_parent->GetStream(i);
1260  if (hls)
1261  {
1262  listurls.append(hls->Url());
1263  }
1264  }
1265  m_lock.unlock();
1266  cancelURL(listurls);
1267  wait();
1268  }
1269 
1270  void Wakeup(void)
1271  {
1272  m_lock.lock();
1273  m_wokenup = true;
1274  m_lock.unlock();
1275  // send a wake signal
1276  m_waitcond.wakeAll();
1277  }
1278  void WaitForSignal(unsigned long time = ULONG_MAX)
1279  {
1280  // must own lock
1281  m_waitcond.wait(&m_lock, time);
1282  }
1283  void Lock(void)
1284  {
1285  m_lock.lock();
1286  }
1287  void Unlock(void)
1288  {
1289  m_lock.unlock();
1290  }
1291 
1292 protected:
1293  void run(void) override // MThread
1294  {
1295  RunProlog();
1296 
1297  double wait = 0.5;
1298  double factor = m_parent->GetCurrentStream()->Live() ? 1.0 : 2.0;
1299 
1300  QWaitCondition mcond;
1301 
1302  while (!m_interrupted)
1303  {
1304  if (m_parent->m_streamworker == nullptr)
1305  {
1306  // streamworker not running
1307  LOG(VB_PLAYBACK, LOG_ERR, LOC +
1308  "StreamWorker not running, aborting live playback");
1309  m_interrupted = true;
1310  break;
1311  }
1312 
1313  Lock();
1314  if (!m_wokenup)
1315  {
1316  unsigned long waittime = m_wakeup < 100 ? 100 : m_wakeup;
1317  LOG(VB_PLAYBACK, LOG_DEBUG, LOC +
1318  QString("PlayListWorker refreshing in %1s")
1319  .arg(waittime / 1000));
1320  WaitForSignal(waittime);
1321  }
1322  m_wokenup = false;
1323  Unlock();
1324 
1325  /* reload the m3u8 */
1326  if (ReloadPlaylist() != RET_OK)
1327  {
1328  /* No change in playlist, then backoff */
1329  m_retries++;
1330  if (m_retries == 1) wait = 0.5;
1331  else if (m_retries == 2) wait = 1;
1332  else if (m_retries >= 3) wait = 2;
1333 
1334  // If we haven't been able to reload the playlist after x times
1335  // it probably means the stream got deleted, so abort
1337  {
1338  LOG(VB_PLAYBACK, LOG_ERR, LOC +
1339  QString("reloading the playlist failed after %1 attempts."
1340  "aborting.").arg(PLAYLIST_FAILURE));
1341  m_parent->m_error = true;
1342  }
1343 
1344  /* Can we afford to backoff? */
1346  {
1347  if (m_retries == 1)
1348  continue; // restart immediately if it's the first try
1349  m_retries = 0;
1350  wait = 0.5;
1351  }
1352  }
1353  else
1354  {
1355  // make streamworker process things
1357  m_retries = 0;
1358  wait = 0.5;
1359  }
1360 
1362  if (hls == nullptr)
1363  {
1364  // an irrevocable error has occured. exit
1365  LOG(VB_PLAYBACK, LOG_ERR, LOC +
1366  "unable to retrieve current stream, aborting live playback");
1367  m_interrupted = true;
1368  break;
1369  }
1370 
1371  /* determine next time to update playlist */
1372  m_wakeup = ((int64_t)(hls->TargetDuration() * wait * factor)
1373  * (int64_t)1000);
1374  }
1375 
1376  RunEpilog();
1377  }
1378 
1379 private:
1383  int ReloadPlaylist(void)
1384  {
1385  auto *streams = new StreamsList;
1386 
1387  LOG(VB_PLAYBACK, LOG_INFO, LOC + "reloading HLS live meta playlist");
1388 
1389  if (GetHTTPLiveMetaPlaylist(streams) != RET_OK)
1390  {
1391  LOG(VB_PLAYBACK, LOG_ERR, LOC + "reloading playlist failed");
1392  m_parent->FreeStreamsList(streams);
1393  return RET_ERROR;
1394  }
1395 
1396  /* merge playlists */
1397  int count = streams->size();
1398  for (int n = 0; n < count; n++)
1399  {
1400  HLSStream *hls_new = m_parent->GetStream(n, streams);
1401  if (hls_new == nullptr)
1402  continue;
1403 
1404  HLSStream *hls_old = m_parent->FindStream(hls_new);
1405  if (hls_old == nullptr)
1406  { /* new hls stream - append */
1407  m_parent->m_streams.append(hls_new);
1408  LOG(VB_PLAYBACK, LOG_INFO, LOC +
1409  QString("new HLS stream appended (id=%1, bitrate=%2)")
1410  .arg(hls_new->Id()).arg(hls_new->Bitrate()));
1411  }
1412  else if (UpdatePlaylist(hls_new, hls_old) != RET_OK)
1413  {
1414  LOG(VB_PLAYBACK, LOG_ERR, LOC +
1415  QString("failed updating HLS stream (id=%1, bandwidth=%2)")
1416  .arg(hls_new->Id()).arg(hls_new->Bitrate()));
1417  m_parent->FreeStreamsList(streams);
1418  return RET_ERROR;
1419  }
1420  }
1421  delete streams;
1422  return RET_OK;
1423  }
1424 
1425  static int UpdatePlaylist(HLSStream *hls_new, HLSStream *hls)
1426  {
1427  int count = hls_new->NumSegments();
1428 
1429  LOG(VB_PLAYBACK, LOG_INFO, LOC +
1430  QString("updated hls stream (program-id=%1, bitrate=%2) has %3 segments")
1431  .arg(hls_new->Id()).arg(hls_new->Bitrate()).arg(count));
1432  QMap<HLSSegment*,bool> table;
1433 
1434  for (int n = 0; n < count; n++)
1435  {
1436  HLSSegment *p = hls_new->GetSegment(n);
1437  if (p == nullptr)
1438  return RET_ERROR;
1439 
1440  hls->Lock();
1441  HLSSegment *segment = hls->FindSegment(p->Id());
1442  if (segment)
1443  {
1444  segment->Lock();
1445  /* they should be the same */
1446  if ((p->Id() != segment->Id()) ||
1447  (p->Duration() != segment->Duration()) ||
1448  (p->Url() != segment->Url()))
1449  {
1450  LOG(VB_PLAYBACK, LOG_WARNING, LOC +
1451  QString("existing segment found with different content - resetting"));
1452  LOG(VB_PLAYBACK, LOG_WARNING, LOC +
1453  QString("- id: new=%1, old=%2")
1454  .arg(p->Id()).arg(segment->Id()));
1455  LOG(VB_PLAYBACK, LOG_WARNING, LOC +
1456  QString("- duration: new=%1, old=%2")
1457  .arg(p->Duration()).arg(segment->Duration()));
1458  LOG(VB_PLAYBACK, LOG_WARNING, LOC +
1459  QString("- file: new=%1 old=%2")
1460  .arg(p->Url()).arg(segment->Url()));
1461 
1462  /* Resetting content */
1463  *segment = *p;
1464  }
1465  // mark segment to be removed from new stream, and deleted
1466  table.insert(p, true);
1467  segment->Unlock();
1468  }
1469  else
1470  {
1471  int last = hls->NumSegments() - 1;
1472  HLSSegment *l = hls->GetSegment(last);
1473  if (l == nullptr)
1474  {
1475  hls->Unlock();
1476  return RET_ERROR;
1477  }
1478 
1479  if ((l->Id() + 1) != p->Id())
1480  {
1481  LOG(VB_PLAYBACK, LOG_ERR, LOC +
1482  QString("gap in id numbers found: new=%1 expected %2")
1483  .arg(p->Id()).arg(l->Id()+1));
1484  }
1485  hls->AppendSegment(p);
1486  LOG(VB_PLAYBACK, LOG_INFO, LOC +
1487  QString("- segment %1 appended")
1488  .arg(p->Id()));
1489  // segment was moved to another stream, so do not delete it
1490  table.insert(p, false);
1491  }
1492  hls->Unlock();
1493  }
1494  hls_new->RemoveListSegments(table);
1495 
1496  /* update meta information */
1497  hls->UpdateWith(*hls_new);
1498  return RET_OK;
1499  }
1500 
1502  {
1503  int err = RET_ERROR;
1504 
1505  /* Duplicate HLS stream META information */
1506  for (int i = 0; i < m_parent->m_streams.size() && !m_interrupted; i++)
1507  {
1508  auto *src = m_parent->GetStream(i);
1509  if (src == nullptr)
1510  return RET_ERROR;
1511 
1512  auto *dst = new HLSStream(*src);
1513  streams->append(dst);
1514 
1515  /* Download playlist file from server */
1516  QByteArray buffer;
1517  if (!downloadURL(dst->Url(), &buffer) || m_interrupted)
1518  {
1519  return RET_ERROR;
1520  }
1521  /* Parse HLS m3u8 content. */
1522  err = m_parent->ParseM3U8(&buffer, streams);
1523  }
1524  m_parent->SanitizeStreams(streams);
1525  return err;
1526  }
1527 
1528  // private variable members
1530  bool m_interrupted {false};
1531  int64_t m_wakeup; // next reload time
1532  int m_retries {0}; // number of consecutive failures
1533  bool m_wokenup {false};
1534  QMutex m_lock;
1535  QWaitCondition m_waitcond;
1536 };
1537 
1538 HLSRingBuffer::HLSRingBuffer(const QString &lfilename) :
1540  m_playback(new HLSPlayback())
1541 {
1542  m_startReadAhead = false;
1543  HLSRingBuffer::OpenFile(lfilename);
1544 }
1545 
1546 HLSRingBuffer::HLSRingBuffer(const QString &lfilename, bool open) :
1548  m_playback(new HLSPlayback())
1549 {
1550  m_startReadAhead = false;
1551  if (open)
1552  {
1553  HLSRingBuffer::OpenFile(lfilename);
1554  }
1555 }
1556 
1558 {
1560 
1561  QWriteLocker lock(&m_rwLock);
1562 
1563  m_killed = true;
1564 
1565  if (m_playlistworker)
1566  {
1568  delete m_playlistworker;
1569  }
1570  // stream worker must be deleted after playlist worker
1571  if (m_streamworker)
1572  {
1574  delete m_streamworker;
1575  }
1577  delete m_playback;
1578  if (m_fd)
1579  {
1580  fclose(m_fd);
1581  }
1582 }
1583 
1585 {
1586  /* Free hls streams */
1587  for (int i = 0; i < streams->size(); i++)
1588  {
1589  HLSStream *hls = GetStream(i, streams);
1590  delete hls;
1591  }
1592  if (streams != &m_streams)
1593  {
1594  delete streams;
1595  }
1596 }
1597 
1599 {
1600  int stream = m_streamworker->StreamForSegment(segnum);
1601  if (stream < 0)
1602  {
1603  return GetCurrentStream();
1604  }
1605  return GetStream(stream);
1606 }
1607 
1608 HLSStream *HLSRingBuffer::GetStream(const int wanted, const StreamsList *streams) const
1609 {
1610  if (streams == nullptr)
1611  {
1612  streams = &m_streams;
1613  }
1614  int count = streams->size();
1615  if (count <= 0)
1616  return nullptr;
1617  if ((wanted < 0) || (wanted >= count))
1618  return nullptr;
1619  return streams->at(wanted);
1620 }
1621 
1623 {
1624  return GetStream(0, streams);
1625 }
1626 
1628 {
1629  if (streams == nullptr)
1630  {
1631  streams = &m_streams;
1632  }
1633  int count = streams->size();
1634  if (count <= 0)
1635  return nullptr;
1636  count--;
1637  return GetStream(count, streams);
1638 }
1639 
1641  const StreamsList *streams) const
1642 {
1643  if (streams == nullptr)
1644  {
1645  streams = &m_streams;
1646  }
1647  int count = streams->size();
1648  for (int n = 0; n < count; n++)
1649  {
1650  HLSStream *hls = GetStream(n, streams);
1651  if (hls)
1652  {
1653  /* compare */
1654  if ((hls->Id() == hls_new->Id()) &&
1655  ((hls->Bitrate() == hls_new->Bitrate()) ||
1656  (hls_new->Bitrate() == 0)))
1657  {
1658  return hls;
1659  }
1660  }
1661  }
1662  return nullptr;
1663 }
1664 
1669 {
1670  if (!m_streamworker)
1671  {
1672  return nullptr;
1673  }
1675 }
1676 
1678 {
1679  if (!s || s->size() < 7)
1680  return false;
1681 
1682  if (!s->startsWith((const char*)"#EXTM3U"))
1683  return false;
1684 
1685  QTextStream stream(s);
1686  /* Parse stream and search for
1687  * EXT-X-TARGETDURATION or EXT-X-STREAM-INF tag, see
1688  * http://tools.ietf.org/html/draft-pantos-http-live-streaming-04#page-8 */
1689  while (true)
1690  {
1691  QString line = stream.readLine();
1692  if (line.isNull())
1693  break;
1694  LOG(VB_PLAYBACK, LOG_DEBUG, LOC +
1695  QString("IsHTTPLiveStreaming: %1").arg(line));
1696  if (line.startsWith(QLatin1String("#EXT-X-TARGETDURATION")) ||
1697  line.startsWith(QLatin1String("#EXT-X-MEDIA-SEQUENCE")) ||
1698  line.startsWith(QLatin1String("#EXT-X-KEY")) ||
1699  line.startsWith(QLatin1String("#EXT-X-ALLOW-CACHE")) ||
1700  line.startsWith(QLatin1String("#EXT-X-ENDLIST")) ||
1701  line.startsWith(QLatin1String("#EXT-X-STREAM-INF")) ||
1702  line.startsWith(QLatin1String("#EXT-X-DISCONTINUITY")) ||
1703  line.startsWith(QLatin1String("#EXT-X-VERSION")))
1704  {
1705  return true;
1706  }
1707  }
1708  return false;
1709 }
1710 
1712 {
1713  bool isHLS = false;
1714  URLContext *context = nullptr;
1715 
1716  // Do a peek on the URL to test the format
1718  int ret = ffurl_open(&context, filename.toLatin1(),
1719  AVIO_FLAG_READ, nullptr, nullptr);
1720  if (ret >= 0)
1721  {
1722  unsigned char buffer[1024];
1723  ret = ffurl_read(context, buffer, sizeof(buffer));
1724  if (ret > 0)
1725  {
1726  QByteArray ba((const char*)buffer, ret);
1727  isHLS = IsHTTPLiveStreaming(&ba);
1728  }
1729  ffurl_close(context);
1730  }
1731  else
1732  {
1733  // couldn't peek, rely on URL analysis
1734  QUrl url = filename;
1735  isHLS =
1736  url.path().endsWith(QLatin1String("m3u8"), Qt::CaseInsensitive) ||
1737  url.query( QUrl::FullyEncoded ).contains(QLatin1String("m3u8"), Qt::CaseInsensitive);
1738  }
1739  return isHLS;
1740 }
1741 
1742 /* Parsing */
1743 QString HLSRingBuffer::ParseAttributes(const QString &line, const char *attr)
1744 {
1745  int p = line.indexOf(QLatin1String(":"));
1746  if (p < 0)
1747  return QString();
1748 
1749  QStringList list = line.mid(p+1).split(',');
1750  foreach (auto & it, list)
1751  {
1752  QString arg = it.trimmed();
1753  if (arg.startsWith(attr))
1754  {
1755  int pos = arg.indexOf(QLatin1String("="));
1756  if (pos < 0)
1757  continue;
1758  return arg.mid(pos+1);
1759  }
1760  }
1761  return QString();
1762 }
1763 
1768 int HLSRingBuffer::ParseDecimalValue(const QString &line, int &target)
1769 {
1770  int p = line.indexOf(QLatin1String(":"));
1771  if (p < 0)
1772  return RET_ERROR;
1773  int i = p;
1774  while (++i < line.size() && line[i].isNumber());
1775  if (i == p + 1)
1776  return RET_ERROR;
1777  target = line.mid(p+1, i - p - 1).toInt();
1778  return RET_OK;
1779 }
1780 
1781 int HLSRingBuffer::ParseSegmentInformation(const HLSStream *hls, const QString &line,
1782  int &duration, QString &title)
1783 {
1784  /*
1785  * #EXTINF:<duration>,<title>
1786  *
1787  * "duration" is an integer that specifies the duration of the media
1788  * file in seconds. Durations SHOULD be rounded to the nearest integer.
1789  * The remainder of the line following the comma is the title of the
1790  * media file, which is an optional human-readable informative title of
1791  * the media segment
1792  */
1793  int p = line.indexOf(QLatin1String(":"));
1794  if (p < 0)
1795  return RET_ERROR;
1796 
1797  QStringList list = line.mid(p+1).split(',');
1798 
1799  /* read duration */
1800  if (list.isEmpty())
1801  {
1802  return RET_ERROR;
1803  }
1804  QString val = list[0];
1805 
1806  if (hls->Version() < 3)
1807  {
1808  bool ok = false;
1809  duration = val.toInt(&ok);
1810  if (!ok)
1811  {
1812  duration = -1;
1813  return RET_ERROR;
1814  }
1815  }
1816  else
1817  {
1818  bool ok = false;
1819  double d = val.toDouble(&ok);
1820  if (!ok)
1821  {
1822  duration = -1;
1823  return RET_ERROR;
1824  }
1825  if ((d) - ((int)d) >= 0.5)
1826  duration = ((int)d) + 1;
1827  else
1828  duration = ((int)d);
1829  }
1830 
1831  if (list.size() >= 2)
1832  {
1833  title = list[1];
1834  }
1835 
1836  /* Ignore the rest of the line */
1837  return RET_OK;
1838 }
1839 
1840 int HLSRingBuffer::ParseTargetDuration(HLSStream *hls, const QString &line)
1841 {
1842  /*
1843  * #EXT-X-TARGETDURATION:<s>
1844  *
1845  * where s is an integer indicating the target duration in seconds.
1846  */
1847  int duration = -1;
1848 
1849  if (ParseDecimalValue(line, duration) != RET_OK)
1850  {
1851  LOG(VB_PLAYBACK, LOG_ERR, LOC + "expected #EXT-X-TARGETDURATION:<s>");
1852  return RET_ERROR;
1853  }
1854  hls->SetTargetDuration(duration); /* seconds */
1855  return RET_OK;
1856 }
1857 
1858 HLSStream *HLSRingBuffer::ParseStreamInformation(const QString &line, const QString &uri) const
1859 {
1860  /*
1861  * #EXT-X-STREAM-INF:[attribute=value][,attribute=value]*
1862  * <URI>
1863  */
1864  int id = 0;
1865  QString attr;
1866 
1867  attr = ParseAttributes(line, "PROGRAM-ID");
1868  if (attr.isNull())
1869  {
1870  LOG(VB_PLAYBACK, LOG_INFO, LOC + "#EXT-X-STREAM-INF: expected PROGRAM-ID=<value>, using -1");
1871  id = -1;
1872  }
1873  else
1874  {
1875  id = attr.toInt();
1876  }
1877 
1878  attr = ParseAttributes(line, "BANDWIDTH");
1879  if (attr.isNull())
1880  {
1881  LOG(VB_PLAYBACK, LOG_ERR, LOC + "#EXT-X-STREAM-INF: expected BANDWIDTH=<value>");
1882  return nullptr;
1883  }
1884  uint64_t bw = attr.toInt();
1885 
1886  if (bw == 0)
1887  {
1888  LOG(VB_PLAYBACK, LOG_ERR, LOC + "#EXT-X-STREAM-INF: bandwidth cannot be 0");
1889  return nullptr;
1890  }
1891 
1892  LOG(VB_PLAYBACK, LOG_INFO, LOC +
1893  QString("bandwidth adaptation detected (program-id=%1, bandwidth=%2")
1894  .arg(id).arg(bw));
1895 
1896  QString psz_uri = relative_URI(m_m3u8, uri);
1897 
1898  return new HLSStream(id, bw, psz_uri);
1899 }
1900 
1901 int HLSRingBuffer::ParseMediaSequence(HLSStream *hls, const QString &line)
1902 {
1903  /*
1904  * #EXT-X-MEDIA-SEQUENCE:<number>
1905  *
1906  * A Playlist file MUST NOT contain more than one EXT-X-MEDIA-SEQUENCE
1907  * tag. If the Playlist file does not contain an EXT-X-MEDIA-SEQUENCE
1908  * tag then the sequence number of the first URI in the playlist SHALL
1909  * be considered to be 0.
1910  */
1911  int sequence = 0;
1912 
1913  if (ParseDecimalValue(line, sequence) != RET_OK)
1914  {
1915  LOG(VB_PLAYBACK, LOG_ERR, LOC + "expected #EXT-X-MEDIA-SEQUENCE:<s>");
1916  return RET_ERROR;
1917  }
1918 
1919  if (hls->StartSequence() > 0 && !hls->Live())
1920  {
1921  LOG(VB_PLAYBACK, LOG_ERR, LOC +
1922  QString("EXT-X-MEDIA-SEQUENCE already present in playlist (new=%1, old=%2)")
1923  .arg(sequence).arg(hls->StartSequence()));
1924  }
1925  hls->SetStartSequence(sequence);
1926  return RET_OK;
1927 }
1928 
1929 
1930 int HLSRingBuffer::ParseKey(HLSStream *hls, const QString &line)
1931 {
1932  /*
1933  * #EXT-X-KEY:METHOD=<method>[,URI="<URI>"][,IV=<IV>]
1934  *
1935  * The METHOD attribute specifies the encryption method. Two encryption
1936  * methods are defined: NONE and AES-128.
1937  */
1938  int err = 0;
1939  QString attr = ParseAttributes(line, "METHOD");
1940  if (attr.isNull())
1941  {
1942  LOG(VB_PLAYBACK, LOG_ERR, LOC + "#EXT-X-KEY: expected METHOD=<value>");
1943  return RET_ERROR;
1944  }
1945 
1946  if (attr.startsWith(QLatin1String("NONE")))
1947  {
1948  QString uri = ParseAttributes(line, "URI");
1949  if (!uri.isNull())
1950  {
1951  LOG(VB_PLAYBACK, LOG_ERR, LOC + "#EXT-X-KEY: URI not expected");
1952  err = RET_ERROR;
1953  }
1954  /* IV is only supported in version 2 and above */
1955  if (hls->Version() >= 2)
1956  {
1957  QString iv = ParseAttributes(line, "IV");
1958  if (!iv.isNull())
1959  {
1960  LOG(VB_PLAYBACK, LOG_ERR, LOC + "#EXT-X-KEY: IV not expected");
1961  err = RET_ERROR;
1962  }
1963  }
1964  }
1965 #ifdef USING_LIBCRYPTO
1966  else if (attr.startsWith(QLatin1String("AES-128")))
1967  {
1968  QString uri;
1969  QString iv;
1970  if (!m_aesmsg)
1971  {
1972  LOG(VB_PLAYBACK, LOG_INFO, LOC +
1973  "playback of AES-128 encrypted HTTP Live media detected.");
1974  m_aesmsg = true;
1975  }
1976  uri = ParseAttributes(line, "URI");
1977  if (uri.isNull())
1978  {
1979  LOG(VB_PLAYBACK, LOG_ERR, LOC +
1980  "#EXT-X-KEY: URI not found for encrypted HTTP Live media in AES-128");
1981  return RET_ERROR;
1982  }
1983 
1984  /* Url is between quotes, remove them */
1985  hls->SetKeyPath(decoded_URI(uri.remove(QChar(QLatin1Char('"')))));
1986 
1987  iv = ParseAttributes(line, "IV");
1988  if (!iv.isNull() && !hls->SetAESIV(iv))
1989  {
1990  LOG(VB_PLAYBACK, LOG_ERR, LOC + "invalid IV");
1991  err = RET_ERROR;
1992  }
1993  }
1994 #endif
1995  else
1996  {
1997 #ifndef _MSC_VER
1998  LOG(VB_PLAYBACK, LOG_ERR, LOC +
1999  "invalid encryption type, only NONE "
2000 #ifdef USING_LIBCRYPTO
2001  "and AES-128 are supported"
2002 #else
2003  "is supported."
2004 #endif
2005  );
2006 #else
2007 // msvc doesn't like #ifdef in the middle of the LOG macro.
2008 // Errors with '#':invalid character: possibly the result of a macro expansion
2009 #endif
2010  err = RET_ERROR;
2011  }
2012  return err;
2013 }
2014 
2015 int HLSRingBuffer::ParseProgramDateTime(HLSStream */*hls*/, const QString &line)
2016 {
2017  /*
2018  * #EXT-X-PROGRAM-DATE-TIME:<YYYY-MM-DDThh:mm:ssZ>
2019  */
2020  LOG(VB_PLAYBACK, LOG_WARNING, LOC +
2021  QString("tag not supported: #EXT-X-PROGRAM-DATE-TIME %1")
2022  .arg(line));
2023  return RET_OK;
2024 }
2025 
2026 int HLSRingBuffer::ParseAllowCache(HLSStream *hls, const QString &line)
2027 {
2028  /*
2029  * The EXT-X-ALLOW-CACHE tag indicates whether the client MAY or MUST
2030  * NOT cache downloaded media files for later replay. It MAY occur
2031  * anywhere in the Playlist file; it MUST NOT occur more than once. The
2032  * EXT-X-ALLOW-CACHE tag applies to all segments in the playlist. Its
2033  * format is:
2034  *
2035  * #EXT-X-ALLOW-CACHE:<YES|NO>
2036  */
2037  int pos = line.indexOf(QLatin1String(":"));
2038  if (pos < 0)
2039  return RET_ERROR;
2040  QString answer = line.mid(pos+1, 3);
2041  if (answer.size() < 2)
2042  {
2043  LOG(VB_PLAYBACK, LOG_ERR, LOC + "#EXT-X-ALLOW-CACHE, ignoring ...");
2044  return RET_ERROR;
2045  }
2046  hls->SetCache(!answer.startsWith(QLatin1String("NO")));
2047  return RET_OK;
2048 }
2049 
2050 int HLSRingBuffer::ParseVersion(const QString &line, int &version)
2051 {
2052  /*
2053  * The EXT-X-VERSION tag indicates the compatibility version of the
2054  * Playlist file. The Playlist file, its associated media, and its
2055  * server MUST comply with all provisions of the most-recent version of
2056  * this document describing the protocol version indicated by the tag
2057  * value.
2058  *
2059  * Its format is:
2060  *
2061  * #EXT-X-VERSION:<n>
2062  */
2063 
2064  if (ParseDecimalValue(line, version) != RET_OK)
2065  {
2066  LOG(VB_PLAYBACK, LOG_ERR, LOC +
2067  "#EXT-X-VERSION: no protocol version found, should be version 1.");
2068  return RET_ERROR;
2069  }
2070 
2071  if (version <= 0 || version > 3)
2072  {
2073  LOG(VB_PLAYBACK, LOG_ERR, LOC +
2074  QString("#EXT-X-VERSION should be version 1, 2 or 3 iso %1")
2075  .arg(version));
2076  return RET_ERROR;
2077  }
2078  return RET_OK;
2079 }
2080 
2082 {
2083  /*
2084  * The EXT-X-ENDLIST tag indicates that no more media files will be
2085  * added to the Playlist file. It MAY occur anywhere in the Playlist
2086  * file; it MUST NOT occur more than once. Its format is:
2087  */
2088  hls->SetLive(false);
2089  LOG(VB_PLAYBACK, LOG_INFO, LOC + "video on demand (vod) mode");
2090  return RET_OK;
2091 }
2092 
2093 int HLSRingBuffer::ParseDiscontinuity(HLSStream */*hls*/, const QString &line)
2094 {
2095  /* Not handled, never seen so far */
2096  LOG(VB_PLAYBACK, LOG_DEBUG, LOC + QString("#EXT-X-DISCONTINUITY %1").arg(line));
2097  return RET_OK;
2098 }
2099 
2100 int HLSRingBuffer::ParseM3U8(const QByteArray *buffer, StreamsList *streams)
2101 {
2109  if (streams == nullptr)
2110  {
2111  streams = &m_streams;
2112  }
2113  QTextStream stream(*buffer); stream.setCodec("UTF-8");
2114 
2115  QString line = stream.readLine();
2116  if (line.isNull())
2117  return RET_ERROR;
2118 
2119  if (!line.startsWith(QLatin1String("#EXTM3U")))
2120  {
2121  LOG(VB_PLAYBACK, LOG_ERR, LOC + "missing #EXTM3U tag .. aborting");
2122  return RET_ERROR;
2123  }
2124 
2125  /* What is the version ? */
2126  int version = 1;
2127  int p = buffer->indexOf("#EXT-X-VERSION:");
2128  if (p >= 0)
2129  {
2130  stream.seek(p);
2131  QString psz_version = stream.readLine();
2132  if (psz_version.isNull())
2133  return RET_ERROR;
2134  int ret = ParseVersion(psz_version, version);
2135  if (ret != RET_OK)
2136  {
2137  LOG(VB_GENERAL, LOG_WARNING, LOC +
2138  "#EXT-X-VERSION: no protocol version found, assuming version 1.");
2139  version = 1;
2140  }
2141  }
2142 
2143  /* Is it a meta index file ? */
2144  bool meta = buffer->indexOf("#EXT-X-STREAM-INF") >= 0;
2145 
2146  int err = RET_OK;
2147 
2148  if (meta)
2149  {
2150  LOG(VB_PLAYBACK, LOG_INFO, LOC + "Meta playlist");
2151 
2152  /* M3U8 Meta Index file */
2153  stream.seek(0); // rewind
2154  while (!m_killed)
2155  {
2156  line = stream.readLine();
2157  if (line.isNull())
2158  break;
2159 
2160  if (line.startsWith(QLatin1String("#EXT-X-STREAM-INF")))
2161  {
2162  m_meta = true;
2163  QString uri = stream.readLine();
2164  if (uri.isNull())
2165  {
2166  err = RET_ERROR;
2167  break;
2168  }
2169  if (uri.startsWith(QLatin1String("#")))
2170  {
2171  LOG(VB_GENERAL, LOG_INFO, LOC +
2172  QString("Skipping invalid stream-inf: %1")
2173  .arg(uri));
2174  }
2175  else
2176  {
2177  HLSStream *hls = ParseStreamInformation(line, decoded_URI(uri));
2178  if (hls)
2179  {
2180  /* Download playlist file from server */
2181  QByteArray buf;
2182  bool ret = downloadURL(hls->Url(), &buf);
2183  if (!ret)
2184  {
2185  LOG(VB_GENERAL, LOG_INFO, LOC +
2186  QString("Skipping invalid stream, couldn't download: %1")
2187  .arg(hls->Url()));
2188  delete hls;
2189  continue;
2190  }
2191  streams->append(hls);
2192  // One last chance to abort early
2193  if (m_killed)
2194  {
2195  err = RET_ERROR;
2196  break;
2197  }
2198  /* Parse HLS m3u8 content. */
2199  err = ParseM3U8(&buf, streams);
2200  if (err != RET_OK)
2201  break;
2202  hls->SetVersion(version);
2203  }
2204  }
2205  }
2206  }
2207  }
2208  else
2209  {
2210  HLSStream *hls = nullptr;
2211  if (m_meta)
2212  hls = GetLastStream(streams);
2213  else
2214  {
2215  /* No Meta playlist used */
2216  hls = new HLSStream(0, 0, m_m3u8);
2217  streams->append(hls);
2218  /* Get TARGET-DURATION first */
2219  p = buffer->indexOf("#EXT-X-TARGETDURATION:");
2220  if (p >= 0)
2221  {
2222  stream.seek(p);
2223  QString psz_duration = stream.readLine();
2224  if (psz_duration.isNull())
2225  return RET_ERROR;
2226  err = ParseTargetDuration(hls, psz_duration);
2227  }
2228  /* Store version */
2229  hls->SetVersion(version);
2230  }
2231  LOG(VB_PLAYBACK, LOG_INFO, LOC +
2232  QString("%1 Playlist HLS protocol version: %2")
2233  .arg(hls->Live() ? "Live": "VOD").arg(version));
2234 
2235  // rewind
2236  stream.seek(0);
2237  /* */
2238  int segment_duration = -1;
2239  QString title;
2240  do
2241  {
2242  /* Next line */
2243  line = stream.readLine();
2244  if (line.isNull())
2245  break;
2246  LOG(VB_PLAYBACK, LOG_DEBUG, LOC + QString("ParseM3U8: %1")
2247  .arg(line));
2248 
2249  if (line.startsWith(QLatin1String("#EXTINF")))
2250  err = ParseSegmentInformation(hls, line, segment_duration, title);
2251  else if (line.startsWith(QLatin1String("#EXT-X-TARGETDURATION")))
2252  err = ParseTargetDuration(hls, line);
2253  else if (line.startsWith(QLatin1String("#EXT-X-MEDIA-SEQUENCE")))
2254  err = ParseMediaSequence(hls, line);
2255  else if (line.startsWith(QLatin1String("#EXT-X-KEY")))
2256  err = ParseKey(hls, line);
2257  else if (line.startsWith(QLatin1String("#EXT-X-PROGRAM-DATE-TIME")))
2258  err = ParseProgramDateTime(hls, line);
2259  else if (line.startsWith(QLatin1String("#EXT-X-ALLOW-CACHE")))
2260  err = ParseAllowCache(hls, line);
2261  else if (line.startsWith(QLatin1String("#EXT-X-DISCONTINUITY")))
2262  err = ParseDiscontinuity(hls, line);
2263  else if (line.startsWith(QLatin1String("#EXT-X-VERSION")))
2264  {
2265  int version2 = 0;
2266  err = ParseVersion(line, version2);
2267  hls->SetVersion(version2);
2268  }
2269  else if (line.startsWith(QLatin1String("#EXT-X-ENDLIST")))
2270  err = ParseEndList(hls);
2271  else if (!line.startsWith(QLatin1String("#")) && !line.isEmpty())
2272  {
2273  hls->AddSegment(segment_duration, title, decoded_URI(line));
2274  segment_duration = -1; /* reset duration */
2275  title = "";
2276  }
2277  }
2278  while (err == RET_OK);
2279  }
2280  return err;
2281 }
2282 
2283 // stream content functions
2288 {
2289  int retries = 0;
2290  int64_t starttime = mdate();
2291  LOG(VB_PLAYBACK, LOG_INFO, LOC +
2292  QString("Starting Prefetch for %2 segments")
2293  .arg(count));
2294  m_streamworker->Lock();
2296  while (!m_error && !m_killed && (retries < 20) &&
2297  (m_streamworker->CurrentPlaybackBuffer(false) < count) &&
2298  !m_streamworker->IsAtEnd())
2299  {
2301  retries++;
2302  }
2304  LOG(VB_PLAYBACK, LOG_INFO, LOC +
2305  QString("Finished Prefetch (%1s)")
2306  .arg((mdate() - starttime) / 1000000.0));
2307  // we waited more than 10s abort
2308  if (retries >= 10)
2309  return RET_ERROR;
2310  return RET_OK;
2311 }
2312 
2314 {
2315  bool live = hls->Live();
2316  /* sanity check */
2317  if ((m_streamworker->CurrentPlaybackBuffer() == 0) &&
2318  (!m_streamworker->IsAtEnd(true) || live))
2319  {
2320  LOG(VB_PLAYBACK, LOG_WARNING, LOC + "playback will stall");
2321  }
2323  (!m_streamworker->IsAtEnd(true) || live))
2324  {
2325  LOG(VB_PLAYBACK, LOG_WARNING, LOC + "playback in danger of stalling");
2326  }
2327  else if (live && m_streamworker->IsAtEnd(true) &&
2329  {
2330  LOG(VB_PLAYBACK, LOG_WARNING, LOC + "playback will exit soon, starving for data");
2331  }
2332 }
2333 
2340 {
2341  HLSSegment *segment = nullptr;
2342  int stream = m_streamworker->StreamForSegment(segnum);
2343  if (stream < 0)
2344  {
2345  // we haven't downloaded that segment, request it
2346  // we should never be into this condition for normal playback
2347  m_streamworker->Seek(segnum);
2348  m_streamworker->Lock();
2349  /* Wait for download to be finished */
2350  LOG(VB_PLAYBACK, LOG_WARNING, LOC +
2351  LOC + QString("waiting to get segment %1")
2352  .arg(segnum));
2353  int retries = 0;
2354  while (!m_error && (stream < 0) && (retries < 10))
2355  {
2357  stream = m_streamworker->StreamForSegment(segnum, false);
2358  retries++;
2359  }
2361  if (stream < 0)
2362  return nullptr;
2363  }
2364  HLSStream *hls = GetStream(stream);
2365  hls->Lock();
2366  segment = hls->GetSegment(segnum);
2367  hls->Unlock();
2368  LOG(VB_PLAYBACK, LOG_DEBUG, LOC +
2369  QString("GetSegment %1 [%2] stream[%3] (bitrate:%4)")
2370  .arg(segnum).arg(segment->Id()).arg(stream).arg(hls->Bitrate()));
2371  SanityCheck(hls);
2372  return segment;
2373 }
2374 
2376 {
2377  return m_streams.size();
2378 }
2379 
2381 {
2382  HLSStream *hls = GetStream(0);
2383  if (hls == nullptr)
2384  return 0;
2385  hls->Lock();
2386  int count = hls->NumSegments();
2387  hls->Unlock();
2388  return count;
2389 }
2390 
2391 int HLSRingBuffer::ChooseSegment(int stream) const
2392 {
2393  /* Choose a segment to start which is no closer than
2394  * 3 times the target duration from the end of the playlist.
2395  */
2396  int wanted = 0;
2397  int segid = 0;
2398  int wanted_duration = 0;
2399  int count = NumSegments();
2400  int i = count - 1;
2401 
2402  HLSStream *hls = GetStream(stream);
2403  while(i >= 0)
2404  {
2405  HLSSegment *segment = hls->GetSegment(i);
2406 
2407  if (segment->Duration() > hls->TargetDuration())
2408  {
2409  LOG(VB_PLAYBACK, LOG_WARNING, LOC +
2410  QString("EXTINF:%1 duration is larger than EXT-X-TARGETDURATION:%2")
2411  .arg(segment->Duration()).arg(hls->TargetDuration()));
2412  }
2413 
2414  wanted_duration += segment->Duration();
2415  if (wanted_duration >= 3 * hls->TargetDuration())
2416  {
2417  /* Start point found */
2418  wanted = i;
2419  segid = segment->Id();
2420  break;
2421  }
2422  i-- ;
2423  }
2424 
2425  LOG(VB_PLAYBACK, LOG_DEBUG, LOC +
2426  QString("Choose segment %1/%2 [%3]")
2427  .arg(wanted).arg(count).arg(segid));
2428  return wanted;
2429 }
2430 
2436 {
2437  // no lock is required as, at this stage, no threads have either been started
2438  // or we are working on a stream list unique to PlaylistWorker
2439  if (streams == nullptr)
2440  {
2441  streams = &m_streams;
2442  }
2443  QMap<int,int> idstart;
2444  // Find the highest starting sequence for each stream
2445  for (int n = streams->size() - 1 ; n >= 0; n--)
2446  {
2447  HLSStream *hls = GetStream(n, streams);
2448  if (hls->NumSegments() == 0)
2449  {
2450  streams->removeAt(n);
2451  continue; // remove it
2452  }
2453 
2454  int id = hls->Id();
2455  int start = hls->StartSequence();
2456  if (!idstart.contains(id))
2457  {
2458  idstart.insert(id, start);
2459  }
2460  int start2 = idstart.value(id);
2461  if (start > start2)
2462  {
2463  idstart.insert(id, start);
2464  }
2465  }
2466  // Find the highest starting sequence for each stream
2467  for (int n = 0; n < streams->size(); n++)
2468  {
2469  HLSStream *hls = GetStream(n, streams);
2470  int id = hls->Id();
2471  int seq = hls->StartSequence();
2472  int newstart= idstart.value(id);
2473  int todrop = newstart - seq;
2474  if (todrop == 0)
2475  {
2476  // perfect, leave it alone
2477  continue;
2478  }
2479  if (todrop >= hls->NumSegments() || todrop < 0)
2480  {
2481  LOG(VB_PLAYBACK, LOG_ERR, LOC +
2482  QString("stream %1 [id=%2] can't be properly adjusted, ignoring")
2483  .arg(n).arg(hls->Id()));
2484  continue;
2485  }
2486  for (int i = 0; i < todrop; i++)
2487  {
2488  hls->RemoveSegment(0);
2489  }
2490  hls->SetStartSequence(newstart);
2491  }
2492 }
2493 
2502 bool HLSRingBuffer::OpenFile(const QString &lfilename, uint /*retry_ms*/)
2503 {
2504  QWriteLocker lock(&m_rwLock);
2505 
2506  m_safeFilename = lfilename;
2507  m_filename = lfilename;
2508 
2509  QByteArray buffer;
2510  if (!downloadURL(m_filename, &buffer))
2511  {
2512  LOG(VB_PLAYBACK, LOG_ERR, LOC +
2513  QString("Couldn't open URL %1").arg(m_filename));
2514  return false; // can't download file
2515  }
2516  if (!IsHTTPLiveStreaming(&buffer))
2517  {
2518  LOG(VB_PLAYBACK, LOG_ERR, LOC +
2519  QString("%1 isn't a HTTP Live Streaming URL").arg(m_filename));
2520  return false;
2521  }
2522  // let's go
2523  m_m3u8 = m_filename;
2524  LOG(VB_PLAYBACK, LOG_INFO, LOC + QString("HTTP Live Streaming (%1)")
2525  .arg(m_m3u8));
2526 
2527  /* Parse HLS m3u8 content. */
2528  if (ParseM3U8(&buffer, &m_streams) != RET_OK || m_streams.isEmpty())
2529  {
2530  LOG(VB_PLAYBACK, LOG_ERR, LOC +
2531  QString("An error occurred reading M3U8 playlist (%1)").arg(m_filename));
2532  m_error = true;
2533  return false;
2534  }
2535 
2536  SanitizeStreams();
2537 
2538  /* HLS standard doesn't provide any guaranty about streams
2539  being sorted by bitrate, so we sort them, higher bitrate being first */
2540  std::sort(m_streams.begin(), m_streams.end(), HLSStream::IsGreater);
2541 
2542  // if we want as close to live. We should be selecting a further segment
2543  // m_live ? ChooseSegment(0) : 0;
2544 // if (m_live && m_startup < 0)
2545 // {
2546 // LOG(VB_PLAYBACK, LOG_WARNING, LOC +
2547 // "less data than 3 times 'target duration' available for "
2548 // "live playback, playback may stall");
2549 // m_startup = 0;
2550 // }
2551  m_startup = 0;
2553 
2555  m_streamworker->start();
2556 
2558  {
2559  LOG(VB_PLAYBACK, LOG_ERR, LOC +
2560  "fetching first segment failed or didn't complete within 10s.");
2561  m_error = true;
2562  return false;
2563  }
2564 
2565  // set bitrate value used to calculate the size of the stream
2566  HLSStream *hls = GetCurrentStream();
2567  m_bitrate = hls->Bitrate();
2568 
2569  // Set initial seek position (relative to m_startup)
2570  m_playback->SetOffset(0);
2571 
2572  /* Initialize HLS live stream thread */
2573  //if (m_live) // commented out as some streams are marked as VOD, yet
2574  // aren't, they are updated over time
2575  {
2576  m_playlistworker = new PlaylistWorker(this, 0);
2578  }
2579 
2580  return true;
2581 }
2582 
2583 bool HLSRingBuffer::SaveToDisk(const QString &filename, int segstart, int segend)
2584 {
2585  // download it all
2586  FILE *fp = fopen(filename.toLatin1().constData(), "w");
2587  if (fp == nullptr)
2588  return false;
2589  int count = NumSegments();
2590  if (segend < 0)
2591  {
2592  segend = count;
2593  }
2594  for (int i = segstart; i < segend; i++)
2595  {
2596  HLSSegment *segment = GetSegment(i);
2597  if (segment == nullptr)
2598  {
2599  LOG(VB_PLAYBACK, LOG_ERR, LOC +
2600  QString("downloading %1 failed").arg(i));
2601  }
2602  else
2603  {
2604  LOG(VB_PLAYBACK, LOG_INFO, LOC +
2605  QString("download of %1 succeeded")
2606  .arg(i));
2607  fwrite(segment->Data(), segment->Size(), 1, fp);
2608  fflush(fp);
2609  }
2610  }
2611  fclose(fp);
2612  return true;
2613 }
2614 
2615 int64_t HLSRingBuffer::SizeMedia(void) const
2616 {
2617  if (m_error)
2618  return -1;
2619 
2620  HLSStream *hls = GetCurrentStream();
2621  int64_t size = hls->Duration() * m_bitrate / 8;
2622 
2623  return size;
2624 }
2625 
2631 {
2632  bool live = GetCurrentStream()->Live();
2633 
2634  // last seek was to end of media, we are just in seek mode so do not wait
2635  if (m_seektoend)
2636  return;
2637 
2639  (!live && m_streamworker->IsAtEnd()))
2640  {
2641  return;
2642  }
2643 
2644  // danger of getting to the end... pause until we have some more
2645  LOG(VB_PLAYBACK, LOG_INFO, LOC +
2646  QString("pausing until we get sufficient data buffered"));
2648  m_streamworker->Lock();
2649  int retries = 0;
2650  while (!m_error && !m_interrupted &&
2651  (m_streamworker->CurrentPlaybackBuffer(false) < 2) &&
2652  (live || !m_streamworker->IsAtEnd()))
2653  {
2655  retries++;
2656  }
2658 }
2659 
2660 int HLSRingBuffer::safe_read(void *data, uint sz)
2661 {
2662  if (m_error)
2663  return -1;
2664 
2665  int used = 0;
2666  int i_read = sz;
2667 
2669  if (m_interrupted)
2670  {
2671  LOG(VB_PLAYBACK, LOG_DEBUG, LOC + QString("interrupted"));
2672  return 0;
2673  }
2674 
2675  do
2676  {
2677  int segnum = m_playback->Segment();
2678  if (segnum >= NumSegments())
2679  {
2680  m_playback->AddOffset(used);
2681  return used;
2682  }
2683  int stream = m_streamworker->StreamForSegment(segnum);
2684  if (stream < 0)
2685  {
2686  // we haven't downloaded this segment yet, likely that it was
2687  // dropped (livetv?)
2689  continue;
2690  }
2691  HLSStream *hls = GetStream(stream);
2692  if (hls == nullptr)
2693  break;
2694  HLSSegment *segment = hls->GetSegment(segnum);
2695  if (segment == nullptr)
2696  break;
2697 
2698  segment->Lock();
2699  if (segment->SizePlayed() == segment->Size())
2700  {
2701  if (!hls->Cache() || hls->Live())
2702  {
2703  segment->Clear();
2705  }
2706  else
2707  {
2708  segment->Reset();
2709  }
2710 
2712  segment->Unlock();
2713 
2714  /* signal download thread we're about to use a new segment */
2716  continue;
2717  }
2718 
2719  if (segment->SizePlayed() == 0)
2720  {
2721  LOG(VB_PLAYBACK, LOG_INFO, LOC +
2722  QString("started reading segment %1 [id:%2] from stream %3 (%4 buffered)")
2723  .arg(segnum).arg(segment->Id()).arg(stream)
2725  }
2726 
2727  int32_t len = segment->Read((uint8_t*)data + used, i_read, m_fd);
2728  used += len;
2729  i_read -= len;
2730  segment->Unlock();
2731  }
2732  while (i_read > 0 && !m_interrupted);
2733 
2734  if (m_interrupted)
2735  LOG(VB_PLAYBACK, LOG_DEBUG, LOC + QString("interrupted"));
2736 
2737  m_playback->AddOffset(used);
2738  return used;
2739 }
2740 
2746 {
2747  int segnum = m_playback->Segment();
2748  int stream = m_streamworker->StreamForSegment(segnum);
2749  if (stream < 0)
2750  {
2751  return 0;
2752  }
2753  HLSStream *hls = GetStream(stream);
2754  if (hls == nullptr)
2755  {
2756  return 0;
2757  }
2758  HLSSegment *segment = hls->GetSegment(segnum);
2759  if (segment == nullptr)
2760  {
2761  return 0;
2762  }
2763  auto byterate = (uint64_t)(((double)segment->Size()) /
2764  ((double)segment->Duration()));
2765 
2766  return (int)((size * 1000.0) / byterate);
2767 }
2768 
2770 {
2771  QReadLocker lock(&m_rwLock);
2772  return SizeMedia();
2773 }
2774 
2775 long long HLSRingBuffer::SeekInternal(long long pos, int whence)
2776 {
2777  if (m_error)
2778  return -1;
2779 
2780  if (!IsSeekingAllowed())
2781  {
2782  return m_playback->Offset();
2783  }
2784 
2785  int64_t starting = mdate();
2786 
2787  QWriteLocker lock(&m_posLock);
2788 
2789  int totalsize = SizeMedia();
2790  int64_t where = 0;
2791  switch (whence)
2792  {
2793  case SEEK_CUR:
2794  // return current location, nothing to do
2795  if (pos == 0)
2796  {
2797  return m_playback->Offset();
2798  }
2799  where = m_playback->Offset() + pos;
2800  break;
2801  case SEEK_END:
2802  where = SizeMedia() - pos;
2803  break;
2804  case SEEK_SET:
2805  default:
2806  where = pos;
2807  break;
2808  }
2809 
2810  // We determine the duration at which it was really attempting to seek to
2811  int64_t postime = (where * 8.0) / m_bitrate;
2812  int count = NumSegments();
2813  int segnum = m_playback->Segment();
2814  HLSStream *hls = GetStreamForSegment(segnum);
2815 
2816  /* restore current segment's file position indicator to 0 */
2817  HLSSegment *segment = hls->GetSegment(segnum);
2818  if (segment != nullptr)
2819  {
2820  segment->Lock();
2821  segment->Reset();
2822  segment->Unlock();
2823  }
2824 
2825  if (where > totalsize)
2826  {
2827  // we're at the end, never let seek after last 3 segments
2828  postime -= hls->TargetDuration() * 3;
2829  if (postime < 0)
2830  {
2831  postime = 0;
2832  }
2833  }
2834 
2835  // Find segment containing position
2836  int64_t starttime = 0LL;
2837  int64_t endtime = 0LL;
2838  for (int n = m_startup; n < count; n++)
2839  {
2840  hls = GetStreamForSegment(n);
2841  if (hls == nullptr)
2842  {
2843  // error, should never happen, irrecoverable error
2844  return -1;
2845  }
2846  segment = hls->GetSegment(n);
2847  if (segment == nullptr)
2848  {
2849  // stream doesn't contain segment error can't continue,
2850  // unknown error
2851  return -1;
2852  }
2853  endtime += segment->Duration();
2854  if (postime < endtime)
2855  {
2856  segnum = n;
2857  break;
2858  }
2859  starttime = endtime;
2860  }
2861 
2862  /*
2863  * Live Mode exception:
2864  * FFmpeg seek to the last segment in order to determine the size of the video
2865  * so do not allow seeking to the last segment if in live mode as we don't care
2866  * about the size
2867  * Also do not allow to seek before the current playback segment as segment
2868  * has been cleared from memory
2869  * We only let determine the size if the bandwidth would allow fetching the
2870  * the segments in less than 5s
2871  */
2872  if (hls->Live() && (segnum >= count - 1 || segnum < m_playback->Segment()) &&
2873  ((hls->TargetDuration() * hls->Bitrate() / m_streamworker->Bandwidth()) > 5))
2874  {
2875  return m_playback->Offset();
2876  }
2877  m_seektoend = segnum >= count - 1;
2878 
2879  m_playback->SetSegment(segnum);
2880 
2881  m_streamworker->Seek(segnum);
2882  m_playback->SetOffset(postime * m_bitrate / 8);
2883 
2884  m_streamworker->Lock();
2885 
2886  /* Wait for download to be finished and to buffer 3 segment */
2887  LOG(VB_PLAYBACK, LOG_INFO, LOC +
2888  QString("seek to segment %1").arg(segnum));
2889  int retries = 0;
2890 
2891  // see if we've already got the segment, and at least 2 buffered after
2892  // then no need to wait for streamworker
2893  while (!m_error && !m_interrupted &&
2894  (!m_streamworker->GotBufferedSegments(segnum, 2) &&
2895  (m_streamworker->CurrentPlaybackBuffer(false) < 2) &&
2896  !m_streamworker->IsAtEnd()))
2897  {
2899  retries++;
2900  }
2901  if (m_interrupted)
2902  LOG(VB_PLAYBACK, LOG_DEBUG, LOC + QString("interrupted"));
2903 
2905 
2906  // now seek within found segment
2907  int stream = m_streamworker->StreamForSegment(segnum);
2908  if (stream < 0)
2909  {
2910  // segment didn't get downloaded (timeout?)
2911  LOG(VB_PLAYBACK, LOG_ERR, LOC +
2912  QString("seek error: segment %1 should have been downloaded, but didn't."
2913  " Playback will stall")
2914  .arg(segnum));
2915  }
2916  else
2917  {
2918  if (segment == nullptr) // can never happen, make coverity happy
2919  {
2920  // stream doesn't contain segment error can't continue,
2921  // unknown error
2922  return -1;
2923  }
2924  int32_t skip = ((postime - starttime) * segment->Size()) / segment->Duration();
2925  segment->Read(nullptr, skip);
2926  }
2927  LOG(VB_PLAYBACK, LOG_INFO, LOC +
2928  QString("seek completed in %1s").arg((mdate() - starting) / 1000000.0));
2929 
2930  return m_playback->Offset();
2931 }
2932 
2933 long long HLSRingBuffer::GetReadPosition(void) const
2934 {
2935  if (m_error)
2936  return 0;
2937  return m_playback->Offset();
2938 }
2939 
2940 bool HLSRingBuffer::IsOpen(void) const
2941 {
2942  return !m_error && !m_streams.isEmpty() && NumSegments() > 0;
2943 }
2944 
2946 {
2947  QMutexLocker lock(&m_lock);
2948 
2949  // segment didn't get downloaded (timeout?)
2950  LOG(VB_PLAYBACK, LOG_DEBUG, LOC + QString("requesting interrupt"));
2951  m_interrupted = true;
2952 }
2953 
2955 {
2956  QMutexLocker lock(&m_lock);
2957 
2958  // segment didn't get downloaded (timeout?)
2959  LOG(VB_PLAYBACK, LOG_DEBUG, LOC + QString("requesting restart"));
2960  m_interrupted = false;
2961 }
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
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)
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)
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)
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 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)
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)