5 #if QT_VERSION >= QT_VERSION_CHECK(6,0,0)
6 #include <QStringConverter>
12 #define LOC QString("%1: ").arg(m_curstream ? m_curstream->M3U8Url() : "HLSReader")
19 static QUrl
RelativeURI (
const QString& baseString,
const QString& uriString)
21 QUrl base(baseString);
22 QUrl uri(QUrl::fromEncoded(uriString.toLatin1()));
24 return base.resolved(uri);
29 LOG(VB_RECORD, LOG_INFO,
LOC +
"dtor -- start");
31 LOG(VB_RECORD, LOG_INFO,
LOC +
"dtor -- end");
36 LOG(VB_RECORD, LOG_INFO,
LOC + QString(
"Opening '%1'").arg(m3u));
42 LOG(VB_RECORD, LOG_ERR,
LOC +
"Already open");
50 #ifdef HLS_USE_MYTHDOWNLOADMANAGER // MythDownloadManager leaks memory
51 if (!DownloadURL(m3u, &buffer))
53 LOG(VB_RECORD, LOG_ERR,
LOC +
"Open failed.");
59 if (!downloader.
DownloadURL(m3u, &buffer, 30s, 0, 0, &redir))
61 LOG(VB_GENERAL, LOG_ERR,
67 QTextStream text(&buffer);
68 #if QT_VERSION < QT_VERSION_CHECK(6,0,0)
69 text.setCodec(
"UTF-8");
71 text.setEncoding(QStringConverter::Utf8);
76 LOG(VB_RECORD, LOG_ERR,
LOC +
77 QString(
"Open '%1': not a valid playlist").arg(m3u));
94 StreamContainer::iterator Istream;
106 LOG(VB_RECORD, LOG_ERR,
LOC +
107 QString(
"Open '%1': Only %2 bitrates, %3 is not a valid index")
123 LOG(VB_RECORD, LOG_ERR,
LOC + QString(
"No stream selected"));
127 LOG(VB_RECORD, LOG_INFO,
LOC +
128 QString(
"Selected stream with %3 bitrate")
139 LOG(VB_RECORD, LOG_INFO,
LOC +
"Open -- end");
145 LOG(VB_RECORD, (
quiet ? LOG_DEBUG : LOG_INFO),
LOC +
"Close -- start");
152 StreamContainer::iterator Istream;
164 LOG(VB_RECORD, (
quiet ? LOG_DEBUG : LOG_INFO),
LOC +
"Close -- end");
169 LOG(VB_RECORD, (
quiet ? LOG_DEBUG : LOG_INFO),
LOC +
"Cancel -- start");
180 LOG(VB_RECORD, LOG_INFO,
LOC +
"Cancel");
188 #ifdef HLS_USE_MYTHDOWNLOADMANAGER // MythDownloadManager leaks memory
189 if (!m_sements.empty())
193 LOG(VB_RECORD, (
quiet ? LOG_DEBUG : LOG_INFO),
LOC +
"Cancel -- done");
198 LOG(VB_RECORD, LOG_INFO,
LOC + QString(
"Throttle(%1)")
199 .arg(val ?
"true" :
"false"));
214 LOG(VB_RECORD, LOG_ERR,
LOC +
"Read: no stream selected");
219 LOG(VB_RECORD, LOG_DEBUG,
LOC + QString(
"Read: canceled"));
226 LOG(VB_RECORD, LOG_DEBUG,
LOC + QString(
"Reading %1 of %2 bytes")
229 memcpy(buffer,
m_buffer.constData(), len);
240 #ifdef HLS_USE_MYTHDOWNLOADMANAGER // MythDownloadManager leaks memory
241 bool HLSReader::DownloadURL(
const QString &url, QByteArray *buffer)
266 QString line = text.readLine();
267 if (!line.startsWith((
const char*)
"#EXTM3U"))
272 line = text.readLine();
275 LOG(VB_RECORD, LOG_DEBUG,
276 QString(
"IsValidPlaylist: |'%1'").arg(line));
277 if (line.startsWith(QLatin1String(
"#EXT-X-TARGETDURATION")) ||
278 line.startsWith(QLatin1String(
"#EXT-X-MEDIA-SEQUENCE")) ||
279 line.startsWith(QLatin1String(
"#EXT-X-KEY")) ||
280 line.startsWith(QLatin1String(
"#EXT-X-ALLOW-CACHE")) ||
281 line.startsWith(QLatin1String(
"#EXT-X-ENDLIST")) ||
282 line.startsWith(QLatin1String(
"#EXT-X-STREAM-INF")) ||
283 line.startsWith(QLatin1String(
"#EXT-X-DISCONTINUITY")) ||
284 line.startsWith(QLatin1String(
"#EXT-X-VERSION")))
290 LOG(VB_RECORD, LOG_ERR, QString(
"IsValidPlaylist: false"));
304 LOG(VB_RECORD, LOG_DEBUG,
LOC +
"ParseM3U8 -- begin");
306 QTextStream text(buffer);
308 QString line = text.readLine();
311 LOG(VB_RECORD, LOG_ERR,
LOC +
"ParseM3U8: empty line");
315 if (!line.startsWith(QLatin1String(
"#EXTM3U")))
317 LOG(VB_RECORD, LOG_ERR,
LOC +
318 "ParseM3U8: missing #EXTM3U tag .. aborting");
324 int p = buffer.indexOf(
"#EXT-X-VERSION:");
332 if (buffer.indexOf(
"#EXT-X-STREAM-INF") >= 0)
335 LOG(VB_RECORD, LOG_INFO,
LOC +
"Meta index file");
341 line = text.readLine();
345 LOG(VB_RECORD, LOG_INFO,
LOC + QString(
"|%1").arg(line));
346 if (line.startsWith(QLatin1String(
"#EXT-X-STREAM-INF")))
348 QString uri = text.readLine();
349 if (uri.isNull() || uri.startsWith(QLatin1String(
"#")))
351 LOG(VB_RECORD, LOG_INFO,
LOC +
352 QString(
"ParseM3U8: Invalid EXT-X-STREAM-INF data '%1'")
357 StreamContainer::iterator Istream;
363 uint64_t bandwidth = 0;
371 LOG(VB_RECORD, LOG_INFO,
LOC +
372 QString(
"Adding stream %1")
373 .arg(hls->toString()));
378 LOG(VB_RECORD, LOG_INFO,
LOC +
379 QString(
"Already have stream '%1'").arg(url));
386 LOG(VB_RECORD, LOG_DEBUG,
LOC +
"Meta playlist");
389 if (stream ==
nullptr)
392 StreamContainer::iterator Istream;
400 LOG(VB_RECORD, LOG_INFO,
LOC +
401 QString(
"Adding new stream '%1'").arg(
m_m3u8));
408 LOG(VB_RECORD, LOG_INFO,
LOC +
409 QString(
"Updating stream '%1'").arg(hls->
toString()));
413 p = buffer.indexOf(
"#EXT-X-TARGETDURATION:");
427 LOG(VB_RECORD, LOG_DEBUG,
LOC +
428 QString(
"%1 Playlist HLS protocol version: %2")
435 std::chrono::seconds segment_duration = -1s;
436 int64_t first_sequence = -1;
437 int64_t sequence_num = 0;
446 line = text.readLine();
449 LOG(VB_RECORD, (
m_debug ? LOG_INFO : LOG_DEBUG),
450 LOC + QString(
"|%1").arg(line));
452 if (line.startsWith(QLatin1String(
"#EXTINF")))
454 uint tmp_duration = -1;
459 segment_duration = std::chrono::seconds(tmp_duration);
461 else if (line.startsWith(QLatin1String(
"#EXT-X-TARGETDURATION")))
468 else if (line.startsWith(QLatin1String(
"#EXT-X-MEDIA-SEQUENCE")))
472 if (first_sequence < 0)
473 first_sequence = sequence_num;
475 else if (line.startsWith(QLatin1String(
"#EXT-X-KEY")))
477 #ifdef USING_LIBCRYPTO
484 hls->SetKeyPath(path);
486 if (!iv.isNull() && !hls->SetAESIV(iv))
488 LOG(VB_RECORD, LOG_ERR,
LOC +
"invalid IV");
492 LOG(VB_RECORD, LOG_ERR,
LOC +
"#EXT-X-KEY needs libcrypto");
496 else if (line.startsWith(QLatin1String(
"#EXT-X-PROGRAM-DATE-TIME")))
502 else if (line.startsWith(QLatin1String(
"#EXT-X-ALLOW-CACHE")))
504 bool do_cache =
false;
509 else if (line.startsWith(QLatin1String(
"#EXT-X-DISCONTINUITY")))
515 else if (line.startsWith(QLatin1String(
"#EXT-X-VERSION")))
522 else if (line.startsWith(QLatin1String(
"#EXT-X-ENDLIST")))
529 else if (!line.startsWith(QLatin1String(
"#")) && !line.isEmpty())
531 if (m_curSeq < 0 || sequence_num >
m_curSeq)
533 new_segments.push_back
541 segment_duration = -1s;
549 LOG(VB_RECORD, LOG_WARNING,
LOC +
550 QString(
"Sequence number has been reset from %1 to %2")
551 .arg(
m_curSeq).arg(first_sequence));
556 SegmentContainer::iterator Inew = new_segments.begin();
557 SegmentContainer::iterator Iseg =
m_segments.end() - 1;
560 if (!
m_segments.empty() && !new_segments.empty())
562 if ((*Iseg).Sequence() >= first_sequence &&
563 (*Iseg).Sequence() < sequence_num)
567 (*Iseg).Sequence() > first_sequence &&
568 (*Iseg).Sequence() < sequence_num)
571 int64_t diff = (*Iseg).Sequence() - (*Inew).Sequence();
572 if (diff >= 0 && new_segments.size() > diff)
577 for ( ; Iseg !=
m_segments.end(); ++Iseg, ++Inew)
579 if (Inew == new_segments.end())
581 LOG(VB_RECORD, LOG_ERR,
LOC +
582 QString(
"Went off the end with %1 left")
586 if ((*Iseg).Sequence() != (*Inew).Sequence())
588 LOG(VB_RECORD, LOG_ERR,
LOC +
589 QString(
"Sequence non-sequential? %1 != %2")
590 .arg((*Iseg).Sequence())
591 .arg((*Inew).Sequence()));
595 (*Iseg).m_duration = (*Inew).Duration();
596 (*Iseg).m_title = (*Inew).Title();
597 (*Iseg).m_url = (*Inew).Url();
603 for ( ; Inew != new_segments.end(); ++Inew)
609 if (behind > max_behind)
611 LOG(VB_RECORD, LOG_WARNING,
LOC +
612 QString(
"Not downloading fast enough! "
613 "%1 segments behind, skipping %2 segments. "
614 "playlist size: %3, queued: %4")
615 .arg(behind).arg(behind - max_behind)
623 Iseg =
m_segments.begin() + (behind - max_behind);
633 LOG(VB_RECORD, LOG_DEBUG,
LOC +
"ParseM3U8 -- end");
642 LOG(VB_RECORD, LOG_DEBUG,
LOC +
643 QString(
"LoadMetaPlaylists stream %1")
646 StreamContainer::iterator Istream;
656 LOG(VB_RECORD, LOG_WARNING,
LOC +
657 QString(
"Falling behind: only %1% buffered").arg(buffered));
658 LOG(VB_RECORD, LOG_DEBUG,
LOC +
659 QString(
"playlist size %1, queued %2")
665 else if (buffered > 85)
668 LOG(VB_RECORD, LOG_DEBUG,
LOC +
669 QString(
"Plenty of bandwidth, downloading %1 of %2")
672 LOG(VB_RECORD, LOG_DEBUG,
LOC +
673 QString(
"playlist size %1, queued %2")
683 #ifdef HLS_USE_MYTHDOWNLOADMANAGER // MythDownloadManager leaks memory
690 LOG(VB_GENERAL, LOG_WARNING,
715 uint64_t candidate = 0;
719 if ((*Istream)->Id() != progid)
721 if (bitrate > (*Istream)->Bitrate() &&
722 candidate < (*Istream)->Bitrate())
724 LOG(VB_RECORD, LOG_DEBUG,
LOC +
725 QString(
"candidate stream '%1' bitrate %2 >= %3")
726 .arg(Istream.key()).arg(bitrate).arg((*Istream)->Bitrate()));
734 LOG(VB_RECORD, LOG_INFO,
LOC +
735 QString(
"Switching to a lower bitrate stream %1 -> %2")
736 .arg(bitrate).arg(candidate));
741 LOG(VB_RECORD, LOG_DEBUG,
LOC +
742 QString(
"Already at lowest bitrate %1").arg(bitrate));
750 uint64_t candidate = INT_MAX;
754 if ((*Istream)->Id() != progid)
756 if (bitrate < (*Istream)->Bitrate() &&
757 candidate > (*Istream)->Bitrate())
759 LOG(VB_RECORD, LOG_DEBUG,
LOC +
760 QString(
"candidate stream '%1' bitrate %2 >= %3")
761 .arg(Istream.key()).arg(bitrate).arg((*Istream)->Bitrate()));
769 LOG(VB_RECORD, LOG_INFO,
LOC +
770 QString(
"Switching to a higher bitrate stream %1 -> %2")
771 .arg(bitrate).arg(candidate));
776 LOG(VB_RECORD, LOG_DEBUG,
LOC +
777 QString(
"Already at highest bitrate %1").arg(bitrate));
783 LOG(VB_RECORD, LOG_DEBUG,
LOC +
"LoadSegment -- start");
787 LOG(VB_RECORD, LOG_ERR,
LOC +
"LoadSegment: current stream not set.");
806 QString(
"Downloading segment %1 (1 of %2) with %3 behind")
814 QString(
"Downloading segment %1 (%2 of %3)")
826 LOG(VB_RECORD, LOG_DEBUG,
LOC +
"LoadSegment -- no current stream");
837 SegmentContainer::iterator Iseg =
m_segments.begin() +
852 else if (throttle > 8)
856 LOG(VB_RECORD, LOG_INFO,
LOC +
857 QString(
"Throttling -- sleeping %1 secs.")
862 LOG(VB_RECORD, LOG_INFO,
LOC +
"Throttle aborted");
864 LOG(VB_RECORD, LOG_INFO,
LOC +
"Throttle done");
878 LOG(VB_RECORD, LOG_DEBUG,
LOC +
"LoadSegment -- end");
896 LOG(VB_RECORD, LOG_DEBUG,
LOC +
897 QString(
"Downloading %1 bandwidth %2 bitrate %3")
901 if ((bandwidth > 0) && (hls->
Bitrate() > 0))
904 auto estimated_time = std::chrono::seconds(size / bandwidth);
905 if (estimated_time > segment.
Duration())
907 LOG(VB_RECORD, LOG_WARNING,
LOC +
908 QString(
"downloading of %1 will take %2s, "
909 "which is longer than its playback (%3s) at %4kiB/s")
911 .arg(estimated_time.count())
913 .arg(bandwidth / 8192));
918 auto start = nowAsDuration<std::chrono::milliseconds>();
920 #ifdef HLS_USE_MYTHDOWNLOADMANAGER // MythDownloadManager leaks memory
922 if (!HLSReader::DownloadURL(segment.
Url(), &buffer))
924 LOG(VB_RECORD, LOG_ERR,
LOC +
925 QString(
"%1 failed").arg(segment.
Sequence()));
926 if (estimated_time * 2 < segment.
Duration())
928 if (!HLSReader::DownloadURL(segment.
Url(), &buffer))
930 LOG(VB_RECORD, LOG_ERR,
LOC + QString(
"%1 failed")
941 LOG(VB_RECORD, LOG_ERR,
LOC + QString(
"%1 failed: %2")
947 auto downloadduration = nowAsDuration<std::chrono::milliseconds>() - start;
949 #ifdef USING_LIBCRYPTO
951 if (segment.HasKeyPath())
953 if (!hls->DecodeData(downloader, hls->IVLoaded() ? hls->AESIV() : QByteArray(),
960 int segment_len = buffer.size();
963 if (
m_buffer.size() > segment_len * playlist_size)
965 LOG(VB_RECORD, LOG_WARNING,
LOC +
966 QString(
"streambuffer is not reading fast enough. "
967 "buffer size %1").arg(
m_buffer.size()));
979 if (
m_buffer.size() >= segment_len * playlist_size * 2)
981 LOG(VB_RECORD, LOG_WARNING,
LOC +
982 QString(
"streambuffer is not reading fast enough. "
983 "buffer size %1. Dropping %2 bytes")
984 .arg(
m_buffer.size()).arg(segment_len));
994 hls->
SetBitrate((uint64_t)(((
double)segment_len * 8) /
995 ((
double)segment.
Duration().count())));
998 if (downloadduration < 1ms)
999 downloadduration = 1ms;
1002 bandwidth = 8 * 1000ULL * segment_len / downloadduration.count();
1005 ((
static_cast<double>(segment_len) /
1006 static_cast<double>(segment.
Duration().count()))));
1009 QString(
"%1 took %3ms for %4 bytes: "
1010 "bandwidth:%5kiB/s")
1012 .arg(downloadduration.count())
1014 .arg(bandwidth / 8192.0));
1045 LOG(VB_RECORD, LOG_INFO,
LOC +
"Debugging enabled");