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'")
359 StreamContainer::iterator Istream =
m_streams.find(url);
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 =
399 LOG(VB_RECORD, LOG_INFO,
LOC +
400 QString(
"Adding new stream '%1'").arg(
m_m3u8));
407 LOG(VB_RECORD, LOG_INFO,
LOC +
408 QString(
"Updating stream '%1'").arg(hls->
toString()));
412 p = buffer.indexOf(
"#EXT-X-TARGETDURATION:");
426 LOG(VB_RECORD, LOG_DEBUG,
LOC +
427 QString(
"%1 Playlist HLS protocol version: %2")
434 std::chrono::seconds segment_duration = -1s;
435 int64_t first_sequence = -1;
436 int64_t sequence_num = 0;
445 line = text.readLine();
448 LOG(VB_RECORD, (
m_debug ? LOG_INFO : LOG_DEBUG),
449 LOC + QString(
"|%1").arg(line));
451 if (line.startsWith(QLatin1String(
"#EXTINF")))
453 uint tmp_duration = -1;
458 segment_duration = std::chrono::seconds(tmp_duration);
460 else if (line.startsWith(QLatin1String(
"#EXT-X-TARGETDURATION")))
467 else if (line.startsWith(QLatin1String(
"#EXT-X-MEDIA-SEQUENCE")))
471 if (first_sequence < 0)
472 first_sequence = sequence_num;
474 else if (line.startsWith(QLatin1String(
"#EXT-X-KEY")))
476 #ifdef USING_LIBCRYPTO
483 hls->SetKeyPath(path);
485 if (!iv.isNull() && !hls->SetAESIV(iv))
487 LOG(VB_RECORD, LOG_ERR,
LOC +
"invalid IV");
491 LOG(VB_RECORD, LOG_ERR,
LOC +
"#EXT-X-KEY needs libcrypto");
495 else if (line.startsWith(QLatin1String(
"#EXT-X-PROGRAM-DATE-TIME")))
501 else if (line.startsWith(QLatin1String(
"#EXT-X-ALLOW-CACHE")))
503 bool do_cache =
false;
508 else if (line.startsWith(QLatin1String(
"#EXT-X-DISCONTINUITY")))
514 else if (line.startsWith(QLatin1String(
"#EXT-X-VERSION")))
521 else if (line.startsWith(QLatin1String(
"#EXT-X-ENDLIST")))
528 else if (!line.startsWith(QLatin1String(
"#")) && !line.isEmpty())
530 if (m_curSeq < 0 || sequence_num >
m_curSeq)
532 new_segments.push_back
540 segment_duration = -1s;
548 LOG(VB_RECORD, LOG_WARNING,
LOC +
549 QString(
"Sequence number has been reset from %1 to %2")
550 .arg(
m_curSeq).arg(first_sequence));
555 SegmentContainer::iterator Inew = new_segments.begin();
556 SegmentContainer::iterator Iseg =
m_segments.end() - 1;
559 if (!
m_segments.empty() && !new_segments.empty())
561 if ((*Iseg).Sequence() >= first_sequence &&
562 (*Iseg).Sequence() < sequence_num)
566 (*Iseg).Sequence() > first_sequence &&
567 (*Iseg).Sequence() < sequence_num)
570 int64_t diff = (*Iseg).Sequence() - (*Inew).Sequence();
571 if (diff >= 0 && new_segments.size() > diff)
576 for ( ; Iseg !=
m_segments.end(); ++Iseg, ++Inew)
578 if (Inew == new_segments.end())
580 LOG(VB_RECORD, LOG_ERR,
LOC +
581 QString(
"Went off the end with %1 left")
585 if ((*Iseg).Sequence() != (*Inew).Sequence())
587 LOG(VB_RECORD, LOG_ERR,
LOC +
588 QString(
"Sequence non-sequential? %1 != %2")
589 .arg((*Iseg).Sequence())
590 .arg((*Inew).Sequence()));
594 (*Iseg).m_duration = (*Inew).Duration();
595 (*Iseg).m_title = (*Inew).Title();
596 (*Iseg).m_url = (*Inew).Url();
602 for ( ; Inew != new_segments.end(); ++Inew)
608 if (behind > max_behind)
610 LOG(VB_RECORD, LOG_WARNING,
LOC +
611 QString(
"Not downloading fast enough! "
612 "%1 segments behind, skipping %2 segments. "
613 "playlist size: %3, queued: %4")
614 .arg(behind).arg(behind - max_behind)
622 Iseg =
m_segments.begin() + (behind - max_behind);
632 LOG(VB_RECORD, LOG_DEBUG,
LOC +
"ParseM3U8 -- end");
641 LOG(VB_RECORD, LOG_DEBUG,
LOC +
642 QString(
"LoadMetaPlaylists stream %1")
645 StreamContainer::iterator Istream;
655 LOG(VB_RECORD, LOG_WARNING,
LOC +
656 QString(
"Falling behind: only %1% buffered").arg(buffered));
657 LOG(VB_RECORD, LOG_DEBUG,
LOC +
658 QString(
"playlist size %1, queued %2")
664 else if (buffered > 85)
667 LOG(VB_RECORD, LOG_DEBUG,
LOC +
668 QString(
"Plenty of bandwidth, downloading %1 of %2")
671 LOG(VB_RECORD, LOG_DEBUG,
LOC +
672 QString(
"playlist size %1, queued %2")
682 #ifdef HLS_USE_MYTHDOWNLOADMANAGER // MythDownloadManager leaks memory
689 LOG(VB_GENERAL, LOG_WARNING,
714 uint64_t candidate = 0;
718 if ((*Istream)->Id() != progid)
720 if (bitrate > (*Istream)->Bitrate() &&
721 candidate < (*Istream)->Bitrate())
723 LOG(VB_RECORD, LOG_DEBUG,
LOC +
724 QString(
"candidate stream '%1' bitrate %2 >= %3")
725 .arg(Istream.key()).arg(bitrate).arg((*Istream)->Bitrate()));
733 LOG(VB_RECORD, LOG_INFO,
LOC +
734 QString(
"Switching to a lower bitrate stream %1 -> %2")
735 .arg(bitrate).arg(candidate));
740 LOG(VB_RECORD, LOG_DEBUG,
LOC +
741 QString(
"Already at lowest bitrate %1").arg(bitrate));
749 uint64_t candidate = INT_MAX;
753 if ((*Istream)->Id() != progid)
755 if (bitrate < (*Istream)->Bitrate() &&
756 candidate > (*Istream)->Bitrate())
758 LOG(VB_RECORD, LOG_DEBUG,
LOC +
759 QString(
"candidate stream '%1' bitrate %2 >= %3")
760 .arg(Istream.key()).arg(bitrate).arg((*Istream)->Bitrate()));
768 LOG(VB_RECORD, LOG_INFO,
LOC +
769 QString(
"Switching to a higher bitrate stream %1 -> %2")
770 .arg(bitrate).arg(candidate));
775 LOG(VB_RECORD, LOG_DEBUG,
LOC +
776 QString(
"Already at highest bitrate %1").arg(bitrate));
782 LOG(VB_RECORD, LOG_DEBUG,
LOC +
"LoadSegment -- start");
786 LOG(VB_RECORD, LOG_ERR,
LOC +
"LoadSegment: current stream not set.");
805 QString(
"Downloading segment %1 (1 of %2) with %3 behind")
813 QString(
"Downloading segment %1 (%2 of %3)")
825 LOG(VB_RECORD, LOG_DEBUG,
LOC +
"LoadSegment -- no current stream");
836 SegmentContainer::iterator Iseg =
m_segments.begin() +
851 else if (throttle > 8)
855 LOG(VB_RECORD, LOG_INFO,
LOC +
856 QString(
"Throttling -- sleeping %1 secs.")
861 LOG(VB_RECORD, LOG_INFO,
LOC +
"Throttle aborted");
863 LOG(VB_RECORD, LOG_INFO,
LOC +
"Throttle done");
877 LOG(VB_RECORD, LOG_DEBUG,
LOC +
"LoadSegment -- end");
895 LOG(VB_RECORD, LOG_DEBUG,
LOC +
896 QString(
"Downloading %1 bandwidth %2 bitrate %3")
900 if ((bandwidth > 0) && (hls->
Bitrate() > 0))
903 auto estimated_time = std::chrono::seconds(size / bandwidth);
904 if (estimated_time > segment.
Duration())
906 LOG(VB_RECORD, LOG_WARNING,
LOC +
907 QString(
"downloading of %1 will take %2s, "
908 "which is longer than its playback (%3s) at %4kiB/s")
910 .arg(estimated_time.count())
912 .arg(bandwidth / 8192));
917 auto start = nowAsDuration<std::chrono::milliseconds>();
919 #ifdef HLS_USE_MYTHDOWNLOADMANAGER // MythDownloadManager leaks memory
921 if (!HLSReader::DownloadURL(segment.
Url(), &buffer))
923 LOG(VB_RECORD, LOG_ERR,
LOC +
924 QString(
"%1 failed").arg(segment.
Sequence()));
925 if (estimated_time * 2 < segment.
Duration())
927 if (!HLSReader::DownloadURL(segment.
Url(), &buffer))
929 LOG(VB_RECORD, LOG_ERR,
LOC + QString(
"%1 failed")
940 LOG(VB_RECORD, LOG_ERR,
LOC + QString(
"%1 failed: %2")
946 auto downloadduration = nowAsDuration<std::chrono::milliseconds>() - start;
948 #ifdef USING_LIBCRYPTO
950 if (segment.HasKeyPath())
952 if (!hls->DecodeData(downloader, hls->IVLoaded() ? hls->AESIV() : QByteArray(),
959 int segment_len = buffer.size();
962 if (
m_buffer.size() > segment_len * playlist_size)
964 LOG(VB_RECORD, LOG_WARNING,
LOC +
965 QString(
"streambuffer is not reading fast enough. "
966 "buffer size %1").arg(
m_buffer.size()));
978 if (
m_buffer.size() >= segment_len * playlist_size * 2)
980 LOG(VB_RECORD, LOG_WARNING,
LOC +
981 QString(
"streambuffer is not reading fast enough. "
982 "buffer size %1. Dropping %2 bytes")
983 .arg(
m_buffer.size()).arg(segment_len));
993 hls->
SetBitrate((uint64_t)(((
double)segment_len * 8) /
994 ((
double)segment.
Duration().count())));
997 if (downloadduration < 1ms)
998 downloadduration = 1ms;
1001 bandwidth = 8 * 1000ULL * segment_len / downloadduration.count();
1004 ((
static_cast<double>(segment_len) /
1005 static_cast<double>(segment.
Duration().count()))));
1008 QString(
"%1 took %3ms for %4 bytes: "
1009 "bandwidth:%5kiB/s")
1011 .arg(downloadduration.count())
1013 .arg(bandwidth / 8192.0));
1044 LOG(VB_RECORD, LOG_INFO,
LOC +
"Debugging enabled");