5 #include <QRegularExpression>
6 #if QT_VERSION >= QT_VERSION_CHECK(6,0,0)
7 #include <QStringConverter>
15 #define LOC QString("HLSReader[%1]: ").arg(m_inputId)
22 static QUrl
RelativeURI (
const QString& baseString,
const QString& uriString)
24 QUrl base(baseString);
25 QUrl uri(QUrl::fromEncoded(uriString.toLatin1()));
27 return base.resolved(uri);
32 LOG(VB_RECORD, LOG_INFO,
LOC +
"dtor -- start");
34 LOG(VB_RECORD, LOG_INFO,
LOC +
"dtor -- end");
39 LOG(VB_RECORD, LOG_INFO,
LOC + QString(
"Opening '%1'").arg(m3u));
45 LOG(VB_RECORD, LOG_ERR,
LOC +
"Already open");
53 #ifdef HLS_USE_MYTHDOWNLOADMANAGER // MythDownloadManager leaks memory
54 if (!DownloadURL(m3u, &buffer))
56 LOG(VB_RECORD, LOG_ERR,
LOC +
"Open failed.");
62 if (!downloader.
DownloadURL(m3u, &buffer, 30s, 0, 0, &redir))
64 LOG(VB_GENERAL, LOG_ERR,
70 QTextStream text(&buffer);
71 #if QT_VERSION < QT_VERSION_CHECK(6,0,0)
72 text.setCodec(
"UTF-8");
74 text.setEncoding(QStringConverter::Utf8);
79 LOG(VB_RECORD, LOG_ERR,
LOC +
80 QString(
"Open '%1': not a valid playlist").arg(m3u));
97 StreamContainer::iterator Istream;
109 LOG(VB_RECORD, LOG_ERR,
LOC +
110 QString(
"Open '%1': Only %2 bitrates, %3 is not a valid index")
126 LOG(VB_RECORD, LOG_ERR,
LOC + QString(
"No stream selected"));
130 LOG(VB_RECORD, LOG_INFO,
LOC +
131 QString(
"Selected stream with %3 bitrate")
142 LOG(VB_RECORD, LOG_INFO,
LOC +
"Open -- end");
148 LOG(VB_RECORD, (
quiet ? LOG_DEBUG : LOG_INFO),
LOC +
"Close -- start");
155 StreamContainer::iterator Istream;
167 LOG(VB_RECORD, (
quiet ? LOG_DEBUG : LOG_INFO),
LOC +
"Close -- end");
172 LOG(VB_RECORD, (
quiet ? LOG_DEBUG : LOG_INFO),
LOC +
"Cancel -- start");
183 LOG(VB_RECORD, LOG_INFO,
LOC +
"Cancel");
191 #ifdef HLS_USE_MYTHDOWNLOADMANAGER // MythDownloadManager leaks memory
192 if (!m_sements.empty())
196 LOG(VB_RECORD, (
quiet ? LOG_DEBUG : LOG_INFO),
LOC +
"Cancel -- done");
201 LOG(VB_RECORD, LOG_INFO,
LOC + QString(
"Throttle(%1)")
202 .arg(val ?
"true" :
"false"));
217 LOG(VB_RECORD, LOG_ERR,
LOC +
"Read: no stream selected");
222 LOG(VB_RECORD, LOG_DEBUG,
LOC + QString(
"Read: canceled"));
229 LOG(VB_RECORD, LOG_DEBUG,
LOC + QString(
"Reading %1 of %2 bytes")
232 memcpy(buffer,
m_buffer.constData(), len);
245 #ifdef HLS_USE_MYTHDOWNLOADMANAGER // MythDownloadManager leaks memory
246 bool HLSReader::DownloadURL(
const QString &url, QByteArray *buffer)
275 QString line = text.readLine();
276 if (!line.startsWith((
const char*)
"#EXTM3U"))
281 line = text.readLine();
284 LOG(VB_RECORD, LOG_DEBUG,
285 QString(
"IsValidPlaylist: |'%1'").arg(line));
286 if (line.startsWith(QLatin1String(
"#EXT-X-TARGETDURATION")) ||
287 line.startsWith(QLatin1String(
"#EXT-X-STREAM-INF")) ||
288 line.startsWith(QLatin1String(
"#EXT-X-MEDIA")) ||
289 line.startsWith(QLatin1String(
"#EXT-X-KEY")) ||
290 line.startsWith(QLatin1String(
"#EXT-X-ALLOW-CACHE")) ||
291 line.startsWith(QLatin1String(
"#EXT-X-ENDLIST")) ||
292 line.startsWith(QLatin1String(
"#EXT-X-DISCONTINUITY")) ||
293 line.startsWith(QLatin1String(
"#EXT-X-VERSION")))
299 LOG(VB_RECORD, LOG_ERR, QString(
"IsValidPlaylist: false"));
313 LOG(VB_RECORD, LOG_DEBUG,
LOC +
"ParseM3U8 -- begin");
315 QTextStream text(buffer);
317 QString line = text.readLine();
320 LOG(VB_RECORD, LOG_ERR,
LOC +
"ParseM3U8: empty line");
324 if (!line.startsWith(QLatin1String(
"#EXTM3U")))
326 LOG(VB_RECORD, LOG_ERR,
LOC +
327 "ParseM3U8: missing #EXTM3U tag .. aborting");
333 int p = buffer.indexOf(
"#EXT-X-VERSION:");
341 if (buffer.indexOf(
"#EXT-X-STREAM-INF") >= 0)
344 LOG(VB_RECORD, LOG_DEBUG,
LOC +
"Master Playlist");
350 line = text.readLine();
354 LOG(VB_RECORD, LOG_INFO,
LOC + QString(
"|%1").arg(line));
357 if (line.startsWith(QLatin1String(
"#EXT-X-STREAM-INF")))
359 QString uri = text.readLine();
360 if (uri.isNull() || uri.startsWith(QLatin1String(
"#")))
362 LOG(VB_RECORD, LOG_INFO,
LOC +
363 QString(
"ParseM3U8: Invalid EXT-X-STREAM-INF data '%1'")
370 StreamContainer::iterator Istream =
m_streams.find(url);
374 uint64_t bandwidth = 0;
378 id, bandwidth, audio, video))
384 LOG(VB_RECORD, LOG_INFO,
LOC +
385 QString(
"Adding stream %1")
386 .arg(hls->toString()));
392 LOG(VB_RECORD, LOG_INFO,
LOC +
393 QString(
"Already have stream '%1'").arg(url));
401 LOG(VB_RECORD, LOG_DEBUG,
LOC +
"Media Playlist");
404 if (stream ==
nullptr)
407 StreamContainer::iterator Istream =
414 LOG(VB_RECORD, LOG_INFO,
LOC +
415 QString(
"Adding new stream '%1'").arg(
m_m3u8));
422 LOG(VB_RECORD, LOG_INFO,
LOC +
423 QString(
"Updating stream '%1'").arg(hls->
toString()));
427 p = buffer.indexOf(
"#EXT-X-TARGETDURATION:");
441 LOG(VB_RECORD, LOG_INFO,
LOC +
442 QString(
"%1 Media Playlist HLS protocol version: %2")
449 std::chrono::milliseconds segment_duration = 0s;
450 int64_t first_sequence = -1;
451 int64_t sequence_num = 0;
453 #ifdef USING_LIBCRYPTO
463 line = text.readLine();
466 LOG(VB_RECORD, (
m_debug ? LOG_INFO : LOG_DEBUG),
467 LOC + QString(
"|%1").arg(line));
469 if (line.startsWith(QLatin1String(
"#EXTINF")))
471 int tmp_duration = 0;
476 segment_duration = std::chrono::milliseconds(tmp_duration);
478 else if (line.startsWith(QLatin1String(
"#EXT-X-TARGETDURATION")))
485 else if (line.startsWith(QLatin1String(
"#EXT-X-MEDIA-SEQUENCE")))
489 if (first_sequence < 0)
490 first_sequence = sequence_num;
492 else if (line.startsWith(QLatin1String(
"#EXT-X-MEDIA")))
496 else if (line.startsWith(QLatin1String(
"#EXT-X-KEY")))
498 #ifdef USING_LIBCRYPTO
507 #else // USING_LIBCRYPTO
508 LOG(VB_RECORD, LOG_ERR,
LOC +
"#EXT-X-KEY needs libcrypto");
510 #endif // USING_LIBCRYPTO
512 else if (line.startsWith(QLatin1String(
"#EXT-X-MAP")))
519 else if (line.startsWith(QLatin1String(
"#EXT-X-PROGRAM-DATE-TIME")))
526 else if (line.startsWith(QLatin1String(
"#EXT-X-ALLOW-CACHE")))
528 bool do_cache =
false;
533 else if (line.startsWith(QLatin1String(
"#EXT-X-DISCONTINUITY-SEQUENCE")))
540 else if (line.startsWith(QLatin1String(
"#EXT-X-DISCONTINUITY")))
546 else if (line.startsWith(QLatin1String(
"#EXT-X-INDEPENDENT-SEGMENTS")))
552 else if (line.startsWith(QLatin1String(
"#EXT-X-VERSION")))
559 else if (line.startsWith(QLatin1String(
"#EXT-X-ENDLIST")))
566 else if (!line.startsWith(QLatin1String(
"#")) && !line.isEmpty())
568 if (m_curSeq < 0 || sequence_num >
m_curSeq)
573 #ifdef USING_LIBCRYPTO
574 if (!aes_iv.isEmpty() || !aes_keypath.isEmpty())
576 LOG(VB_RECORD, LOG_DEBUG,
LOC +
" aes_iv:" + aes_iv +
" aes_keypath:" + aes_keypath);
579 segment.SetKeyPath(aes_keypath);
580 if (!aes_iv.isEmpty() && !segment.SetAESIV(aes_iv))
582 LOG(VB_RECORD, LOG_ERR,
LOC +
"invalid AES IV:" + aes_iv);
587 #endif // USING_LIBCRYPTO
588 new_segments.push_back(segment);
596 segment_duration = -1s;
601 LOG(VB_RECORD, LOG_DEBUG,
LOC +
602 QString(
"first_sequence:%1").arg(first_sequence) +
603 QString(
" sequence_num:%1").arg(sequence_num) +
604 QString(
" m_curSeq:%1").arg(
m_curSeq) +
605 QString(
" skipped:%1").arg(skipped));
610 LOG(VB_RECORD, LOG_WARNING,
LOC +
611 QString(
"Sequence number has been reset from %1 to %2")
612 .arg(
m_curSeq).arg(first_sequence));
622 int numseg = new_segments.size();
623 numseg = std::min(numseg, 3);
631 if (new_segments.size() > numseg)
633 int size_before = new_segments.size();
634 SegmentContainer::iterator it = new_segments.begin() + (new_segments.size() - numseg);
635 new_segments.erase(new_segments.begin(), it);
636 LOG(VB_RECORD, LOG_INFO,
LOC +
637 QString(
"Read last %1 segments instead of %2 for near-live")
638 .arg(new_segments.size()).arg(size_before));
641 first_sequence += size_before - new_segments.size();
645 SegmentContainer::iterator Inew = new_segments.begin();
646 SegmentContainer::iterator Iseg =
m_segments.end() - 1;
649 if (!
m_segments.empty() && !new_segments.empty())
651 if ((*Iseg).Sequence() >= first_sequence &&
652 (*Iseg).Sequence() < sequence_num)
656 (*Iseg).Sequence() > first_sequence &&
657 (*Iseg).Sequence() < sequence_num)
660 int64_t diff = (*Iseg).Sequence() - (*Inew).Sequence();
661 if (diff >= 0 && new_segments.size() > diff)
666 for ( ; Iseg !=
m_segments.end(); ++Iseg, ++Inew)
668 if (Inew == new_segments.end())
670 LOG(VB_RECORD, LOG_ERR,
LOC +
671 QString(
"Went off the end with %1 left")
675 if ((*Iseg).Sequence() != (*Inew).Sequence())
677 LOG(VB_RECORD, LOG_ERR,
LOC +
678 QString(
"Sequence non-sequential? %1 != %2")
679 .arg((*Iseg).Sequence())
680 .arg((*Inew).Sequence()));
684 (*Iseg).m_duration = (*Inew).Duration();
685 (*Iseg).m_title = (*Inew).Title();
686 (*Iseg).m_url = (*Inew).Url();
692 for ( ; Inew != new_segments.end(); ++Inew)
699 LOG(VB_RECORD, LOG_INFO,
LOC +
700 QString(
"new_segments.size():%1 ").arg(new_segments.size()) +
702 QString(
"behind:%1 ").arg(behind) +
703 QString(
"max_behind:%1").arg(max_behind));
705 if (behind > max_behind)
707 LOG(VB_RECORD, LOG_WARNING,
LOC +
708 QString(
"Not downloading fast enough! "
709 "%1 segments behind, skipping %2 segments. "
710 "playlist size: %3, queued: %4")
711 .arg(behind).arg(behind - max_behind)
719 Iseg =
m_segments.begin() + (behind - max_behind);
733 LOG(VB_RECORD, LOG_DEBUG,
LOC +
"ParseM3U8 -- end");
742 LOG(VB_RECORD, LOG_DEBUG,
LOC +
743 QString(
"LoadMetaPlaylists stream %1")
746 StreamContainer::iterator Istream;
756 LOG(VB_RECORD, LOG_WARNING,
LOC +
757 QString(
"Falling behind: only %1% buffered").arg(buffered));
758 LOG(VB_RECORD, LOG_DEBUG,
LOC +
759 QString(
"playlist size %1, queued %2")
765 else if (buffered > 85)
768 LOG(VB_RECORD, LOG_DEBUG,
LOC +
769 QString(
"Plenty of bandwidth, downloading %1 of %2")
772 LOG(VB_RECORD, LOG_DEBUG,
LOC +
773 QString(
"playlist size %1, queued %2")
783 #ifdef HLS_USE_MYTHDOWNLOADMANAGER // MythDownloadManager leaks memory
790 LOG(VB_GENERAL, LOG_WARNING,
815 uint64_t candidate = 0;
819 if ((*Istream)->Id() != progid)
821 if (bitrate > (*Istream)->Bitrate() &&
822 candidate < (*Istream)->Bitrate())
824 LOG(VB_RECORD, LOG_DEBUG,
LOC +
825 QString(
"candidate stream '%1' bitrate %2 >= %3")
826 .arg(Istream.key()).arg(bitrate).arg((*Istream)->Bitrate()));
834 LOG(VB_RECORD, LOG_INFO,
LOC +
835 QString(
"Switching to a lower bitrate stream %1 -> %2")
836 .arg(bitrate).arg(candidate));
841 LOG(VB_RECORD, LOG_DEBUG,
LOC +
842 QString(
"Already at lowest bitrate %1").arg(bitrate));
850 uint64_t candidate = INT_MAX;
854 if ((*Istream)->Id() != progid)
856 if (bitrate < (*Istream)->Bitrate() &&
857 candidate > (*Istream)->Bitrate())
859 LOG(VB_RECORD, LOG_DEBUG,
LOC +
860 QString(
"candidate stream '%1' bitrate %2 >= %3")
861 .arg(Istream.key()).arg(bitrate).arg((*Istream)->Bitrate()));
869 LOG(VB_RECORD, LOG_INFO,
LOC +
870 QString(
"Switching to a higher bitrate stream %1 -> %2")
871 .arg(bitrate).arg(candidate));
876 LOG(VB_RECORD, LOG_DEBUG,
LOC +
877 QString(
"Already at highest bitrate %1").arg(bitrate));
883 LOG(VB_RECORD, LOG_DEBUG,
LOC +
"LoadSegment -- start");
887 LOG(VB_RECORD, LOG_ERR,
LOC +
"LoadSegment: current stream not set.");
906 QString(
"Downloading segment %1 (1 of %2) with %3 behind")
914 QString(
"Downloading segment %1 (%2 of %3)")
926 LOG(VB_RECORD, LOG_DEBUG,
LOC +
"LoadSegment -- no current stream");
937 SegmentContainer::iterator Iseg =
m_segments.begin() +
952 else if (throttle > 8)
956 LOG(VB_RECORD, LOG_INFO,
LOC +
957 QString(
"Throttling -- sleeping %1 secs.")
962 LOG(VB_RECORD, LOG_INFO,
LOC +
"Throttle aborted");
964 LOG(VB_RECORD, LOG_INFO,
LOC +
"Throttle done");
982 LOG(VB_RECORD, LOG_DEBUG,
LOC +
"LoadSegment -- end");
1000 LOG(VB_RECORD, LOG_DEBUG,
LOC +
1001 QString(
"Downloading seq#%1 av.bandwidth:%2 bitrate:%3")
1005 if ((bandwidth > 0) && (hls->
Bitrate() > 0) && (segment.
Duration().count() > 0))
1008 auto estimated_time = std::chrono::milliseconds(size / bandwidth);
1009 if (estimated_time > segment.
Duration())
1011 LOG(VB_RECORD, LOG_WARNING,
LOC +
1012 QString(
"downloading of %1 will take %2ms, "
1013 "which is longer than its playback (%3ms) at %4kB/s")
1015 .arg(estimated_time.count())
1017 .arg(bandwidth / 8000));
1019 LOG(VB_RECORD, LOG_DEBUG,
LOC +
1020 QString(
" sequence:%1").arg(segment.
Sequence()) +
1021 QString(
" bandwidth:%1").arg(bandwidth) +
1022 QString(
" hls->Bitrate:%1").arg(hls->
Bitrate()) +
1023 QString(
" seg.Dur.cnt:%1").arg(segment.
Duration().count()) +
1024 QString(
" est_time:%1").arg(estimated_time.count()));
1028 auto start = nowAsDuration<std::chrono::milliseconds>();
1030 #ifdef HLS_USE_MYTHDOWNLOADMANAGER // MythDownloadManager leaks memory
1032 if (!HLSReader::DownloadURL(segment.
Url(), &buffer))
1034 LOG(VB_RECORD, LOG_ERR,
LOC +
1035 QString(
"%1 failed").arg(segment.
Sequence()));
1036 if (estimated_time * 2 < segment.
Duration())
1038 if (!HLSReader::DownloadURL(segment.
Url(), &buffer))
1040 LOG(VB_RECORD, LOG_ERR,
LOC + QString(
"%1 failed")
1051 LOG(VB_RECORD, LOG_ERR,
LOC + QString(
"%1 failed: %2")
1057 auto downloadduration = nowAsDuration<std::chrono::milliseconds>() - start;
1059 LOG(VB_RECORD, LOG_DEBUG,
LOC +
1060 QString(
"Downloaded segment %1 %2").arg(segment.
Sequence()).arg(segment.
Url().toString()));
1062 #ifdef USING_LIBCRYPTO
1064 if (segment.HasKeyPath())
1066 if (!hls->DecodeData(downloader,
1067 segment.IVLoaded() ? segment.AESIV() : QByteArray(),
1073 LOG(VB_RECORD, LOG_DEBUG,
LOC +
1074 QString(
"Decoded segment sequence %1").arg(segment.
Sequence()));
1076 #endif // USING_LIBCRYPTO
1077 int64_t segment_len = buffer.size();
1080 if (
m_buffer.size() > segment_len * playlist_size)
1082 LOG(VB_RECORD, LOG_WARNING,
LOC +
1083 QString(
"streambuffer is not reading fast enough. "
1084 "buffer size %1").arg(
m_buffer.size()));
1098 if (
m_buffer.size() >= segment_len * playlist_size * 2)
1100 LOG(VB_RECORD, LOG_WARNING,
LOC +
1101 QString(
"streambuffer is not reading fast enough. "
1102 "buffer size %1. Dropping %2 bytes")
1103 .arg(
m_buffer.size()).arg(segment_len));
1113 hls->
SetBitrate((uint64_t)(((
double)segment_len * 8 * 1000) /
1114 ((
double)segment.
Duration().count())));
1117 if (downloadduration < 1ms)
1118 downloadduration = 1ms;
1121 bandwidth = 8ULL * 1000 * segment_len / downloadduration.count();
1129 QString(
"Sequence %1 took %2ms for %3 bytes, bandwidth:%4kB/s byterate:%5kB/s")
1131 .arg(downloadduration.count())
1133 .arg(bandwidth / 8000)
1165 LOG(VB_RECORD, LOG_INFO,
LOC +
"Debugging enabled");