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