30 #include <QStringList>
31 #include <QtAlgorithms>
32 #if QT_VERSION >= QT_VERSION_CHECK(6,0,0)
33 #include <QStringConverter>
49 #ifdef USING_LIBCRYPTO
51 #include <openssl/aes.h>
52 using aesiv_array = std::array<uint8_t,AES_BLOCK_SIZE>;
55 #define LOC QString("HLSBuffer: ")
72 QByteArray ba = uri.toLatin1();
73 QUrl url = QUrl::fromEncoded(ba);
74 return url.toString();
77 static QString
relative_URI(
const QString &surl,
const QString &spath)
79 QUrl url = QUrl(surl);
80 QUrl path = QUrl(spath);
82 if (!path.isRelative())
86 return url.resolved(path).toString();
89 static std::chrono::microseconds
mdate(
void)
91 return nowAsDuration<std::chrono::microseconds>();
94 static bool downloadURL(
const QString &url, QByteArray *buffer, QString &finalURL)
97 return mdm->
download(url, buffer,
false, &finalURL);
123 HLSSegment(
const std::chrono::seconds mduration,
const int id, QString title,
124 QString uri, QString current_key_path)
130 #ifdef USING_LIBCRYPTO
131 m_pszKeyPath = std::move(current_key_path);
133 Q_UNUSED(current_key_path);
156 #ifdef USING_LIBCRYPTO
157 m_pszKeyPath = rhs.m_pszKeyPath;
158 memcpy(&m_aeskey, &(rhs.m_aeskey),
sizeof(m_aeskey));
159 m_keyloaded = rhs.m_keyloaded;
215 QMutexLocker lock(&
m_lock);
230 uint32_t
Read(uint8_t *buffer, int32_t length,
FILE *fd =
nullptr)
237 if (buffer !=
nullptr)
274 return m_data.constData();
277 #ifdef USING_LIBCRYPTO
278 int DownloadKey(
void)
285 if (!ret || key.size() != AES_BLOCK_SIZE)
289 LOG(VB_PLAYBACK, LOG_ERR,
LOC +
290 QString(
"The AES key loaded doesn't have the right size (%1)")
295 LOG(VB_PLAYBACK, LOG_ERR,
LOC +
"Failed to download AES key");
299 AES_set_decrypt_key((
const unsigned char*)key.constData(), 128, &m_aeskey);
304 int DecodeData(
const aesiv_array IV,
bool iv_valid)
307 int aeslen =
m_data.size() & ~0xf;
309 auto *decrypted_data =
new uint8_t[
m_data.size()];
319 iv[15] =
m_id & 0xff;
320 iv[14] = (
m_id >> 8) & 0xff;
321 iv[13] = (
m_id >> 16) & 0xff;
322 iv[12] = (
m_id >> 24) & 0xff;
326 std::copy(IV.cbegin(), IV.cend(), iv.begin());
328 AES_cbc_encrypt((
unsigned char*)
m_data.constData(),
329 decrypted_data, aeslen,
330 &m_aeskey, iv.data(), AES_DECRYPT);
331 memcpy(decrypted_data + aeslen,
m_data.constData() + aeslen,
335 int pad = decrypted_data[
m_data.size()-1];
336 if (pad <= 0 || pad > AES_BLOCK_SIZE)
338 LOG(VB_PLAYBACK, LOG_ERR,
LOC +
339 QString(
"bad padding character (0x%1)").arg(pad, 0, 16, QLatin1Char(
'0')));
340 delete[] decrypted_data;
343 aeslen =
m_data.size() - pad;
344 m_data = QByteArray(
reinterpret_cast<char*
>(decrypted_data), aeslen);
345 delete[] decrypted_data;
350 bool HasKeyPath(
void)
const
352 return !m_pszKeyPath.isEmpty();
355 bool KeyLoaded(
void)
const
360 QString KeyPath(
void)
const
365 void SetKeyPath(
const QString &path)
372 memcpy(&m_aeskey, &(segment.m_aeskey),
sizeof(m_aeskey));
373 m_keyloaded = segment.m_keyloaded;
377 bool m_keyloaded {
false};
378 QString m_pszKeyPath;
399 HLSStream(
const int mid,
const uint64_t bitrate, QString uri)
404 #ifdef USING_LIBCRYPTO
424 for (
const auto & segment : qAsConst(
m_segments))
443 #ifdef USING_LIBCRYPTO
444 m_keypath = rhs.m_keypath;
445 m_ivloaded = rhs.m_ivloaded;
446 m_aesIv = rhs.m_aesIv;
475 QMutexLocker lock(&
m_lock);
480 for (
int i = 0; i < count; i++)
484 if (segment->
Size() > 0)
486 size += (int64_t)segment->
Size();
500 QMutexLocker lock(&
m_lock);
525 if ((wanted < 0) || (wanted >= count))
535 for (
int n = 0; n < count; n++)
538 if (segment ==
nullptr)
540 if (segment->
Id() ==
id)
542 if (segnum !=
nullptr)
552 void AddSegment(
const std::chrono::seconds duration,
const QString &title,
const QString &uri)
554 QMutexLocker lock(&
m_lock);
557 #ifndef USING_LIBCRYPTO
560 auto *segment =
new HLSSegment(duration,
id, title, psz_uri, m_keypath);
567 QMutexLocker lock(&
m_lock);
576 for (
int n = 0; n < count; n++)
589 QMutexLocker lock(&
m_lock);
591 if (segment !=
nullptr)
602 for (
auto it = table.begin(); it != table.end(); ++it)
613 if (segment ==
nullptr)
624 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC +
625 QString(
"started download of segment %1 [%2/%3] using stream %4")
633 auto estimated = std::chrono::seconds(size / bandwidth);
634 if (estimated > segment->
Duration())
636 LOG(VB_PLAYBACK, LOG_INFO,
LOC +
637 QString(
"downloading of segment %1 [id:%2] will take %3s, "
638 "which is longer than its playback (%4s) at %5bit/s")
641 .arg(estimated.count())
647 std::chrono::microseconds start =
mdate();
650 LOG(VB_PLAYBACK, LOG_ERR,
LOC +
651 QString(
"downloaded segment %1 [id:%2] from stream %3 failed")
652 .arg(segnum).arg(segment->
Id()).arg(
m_id));
657 std::chrono::microseconds downloadduration =
mdate() - start;
662 ((double)segment->
Duration().count()));
665 #ifdef USING_LIBCRYPTO
667 if (segment->HasKeyPath())
670 if (!segment->KeyLoaded())
672 if (ManageSegmentKeys() !=
RET_OK)
674 LOG(VB_PLAYBACK, LOG_ERR,
LOC +
675 "couldn't retrieve segment AES-128 key");
680 if (segment->DecodeData(m_aesIv, m_ivloaded) !=
RET_OK)
689 downloadduration = std::max(1us, downloadduration);
690 bandwidth = segment->
Size() * 8ULL * 1000000ULL / downloadduration.count();
691 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC +
692 QString(
"downloaded segment %1 [id:%2] took %3ms for %4 bytes: bandwidth:%5kiB/s")
695 .arg(duration_cast<std::chrono::milliseconds>(downloadduration).count())
696 .arg(segment->
Size())
697 .arg(bandwidth / 8192.0));
763 QMutexLocker lock(&
m_lock);
770 QMutexLocker lock(&
m_lock);
771 for (
const auto & segment : qAsConst(
m_segments))
775 segment->CancelDownload();
780 #ifdef USING_LIBCRYPTO
785 int ManageSegmentKeys()
const
791 for (
int i = 0; i < count; i++)
797 if (!seg->HasKeyPath())
799 if (seg->KeyLoaded())
804 if (prev_seg && prev_seg->KeyLoaded() &&
805 (seg->KeyPath() == prev_seg->KeyPath()))
807 seg->CopyAESKey(*prev_seg);
810 if (seg->DownloadKey() !=
RET_OK)
815 bool SetAESIV(QString line)
823 if (!line.startsWith(QLatin1String(
"0x"), Qt::CaseInsensitive))
828 line.insert(2, QLatin1String(
"0"));
830 int padding = std::max(0, AES_BLOCK_SIZE - (
static_cast<int>(line.size()) - 2));
831 QByteArray ba = QByteArray(padding, 0
x0);
832 ba.append(QByteArray::fromHex(QByteArray(line.toLatin1().constData() + 2)));
833 std::copy(ba.cbegin(), ba.cend(), m_aesIv.begin());
837 aesiv_array AESIV(
void)
841 void SetKeyPath(
const QString &x)
848 bool m_ivloaded {
false};
849 aesiv_array m_aesIv {0};
890 QMutexLocker lock(&
m_lock);
895 QMutexLocker lock(&
m_lock);
900 QMutexLocker lock(&
m_lock);
905 QMutexLocker lock(&
m_lock);
910 QMutexLocker lock(&
m_lock);
936 for (
int i = 0; i < streams; i++)
949 QMutexLocker lock(&
m_lock);
954 QMutexLocker lock(&
m_lock);
987 for (
int i = from; i < from + count; i++)
1016 QMutexLocker lock(&
m_lock);
1023 QMutexLocker lock(&
m_lock);
1028 QMutexLocker lock(&
m_lock);
1063 void WaitForSignal(std::chrono::milliseconds time = std::chrono::milliseconds::max())
1105 if ((!hls->
Live() && (playsegment < dnldsegment -
m_buffer)) ||
1145 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC +
1146 QString(
"download failed, retry #%1").arg(retries));
1165 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC +
1166 QString(
"download completed, %1 segments ahead")
1173 if (newstream >= 0 && newstream !=
m_stream)
1175 LOG(VB_PLAYBACK, LOG_INFO,
LOC +
1176 QString(
"switching to %1 bitrate %2 stream; changing "
1177 "from stream %3 to stream %4")
1178 .arg(bw >= hls->
Bitrate() ?
"faster" :
"lower")
1179 .arg(bw).arg(
m_stream).arg(newstream));
1201 uint64_t bw = bandwidth;
1202 uint64_t bw_candidate = 0;
1205 for (
int n = 0; n < count; n++)
1213 if (hls->
Id() == progid)
1216 (bw_candidate < hls->Bitrate()))
1218 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC +
1219 QString(
"candidate stream %1 bitrate %2 >= %3")
1220 .arg(n).arg(bw).arg(hls->
Bitrate()));
1221 bw_candidate = hls->
Bitrate();
1226 bandwidth = bw_candidate;
1258 QStringList listurls;
1259 for (
int i = 0; i < streams; i++)
1264 listurls.append(hls->
Url());
1280 void WaitForSignal(std::chrono::milliseconds time = std::chrono::milliseconds::max())
1302 QWaitCondition mcond;
1309 LOG(VB_PLAYBACK, LOG_ERR,
LOC +
1310 "StreamWorker not running, aborting live playback");
1318 std::chrono::milliseconds waittime = std::max(100ms,
m_wakeup);
1319 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC +
1320 QString(
"PlayListWorker refreshing in %1s")
1321 .arg(duration_cast<std::chrono::seconds>(waittime).count()));
1340 LOG(VB_PLAYBACK, LOG_ERR,
LOC +
1341 QString(
"reloading the playlist failed after %1 attempts."
1367 LOG(VB_PLAYBACK, LOG_ERR,
LOC +
1368 "unable to retrieve current stream, aborting live playback");
1374 m_wakeup = duration_cast<std::chrono::milliseconds>(
1389 LOG(VB_PLAYBACK, LOG_INFO,
LOC +
"reloading HLS live meta playlist");
1393 LOG(VB_PLAYBACK, LOG_ERR,
LOC +
"reloading playlist failed");
1399 int count = streams->size();
1400 for (
int n = 0; n < count; n++)
1403 if (hls_new ==
nullptr)
1407 if (hls_old ==
nullptr)
1410 LOG(VB_PLAYBACK, LOG_INFO,
LOC +
1411 QString(
"new HLS stream appended (id=%1, bitrate=%2)")
1412 .arg(hls_new->
Id()).arg(hls_new->
Bitrate()));
1416 LOG(VB_PLAYBACK, LOG_ERR,
LOC +
1417 QString(
"failed updating HLS stream (id=%1, bandwidth=%2)")
1418 .arg(hls_new->
Id()).arg(hls_new->
Bitrate()));
1431 LOG(VB_PLAYBACK, LOG_INFO,
LOC +
1432 QString(
"updated hls stream (program-id=%1, bitrate=%2) has %3 segments")
1433 .arg(hls_new->
Id()).arg(hls_new->
Bitrate()).arg(count));
1434 QHash<HLSSegment*,bool> table;
1436 for (
int n = 0; n < count; n++)
1448 if ((
p->Id() != segment->
Id()) ||
1449 (
p->Duration() != segment->
Duration()) ||
1450 (
p->Url() != segment->
Url()))
1452 LOG(VB_PLAYBACK, LOG_WARNING,
LOC +
1453 QString(
"existing segment found with different content - resetting"));
1454 LOG(VB_PLAYBACK, LOG_WARNING,
LOC +
1455 QString(
"- id: new=%1, old=%2")
1456 .arg(
p->Id()).arg(segment->
Id()));
1457 LOG(VB_PLAYBACK, LOG_WARNING,
LOC +
1458 QString(
"- duration: new=%1, old=%2")
1459 .arg(
p->Duration().count()).arg(segment->
Duration().count()));
1460 LOG(VB_PLAYBACK, LOG_WARNING,
LOC +
1461 QString(
"- file: new=%1 old=%2")
1462 .arg(
p->Url(), segment->
Url()));
1468 table.insert(
p,
true);
1481 if ((l->
Id() + 1) !=
p->Id())
1483 LOG(VB_PLAYBACK, LOG_ERR,
LOC +
1484 QString(
"gap in id numbers found: new=%1 expected %2")
1485 .arg(
p->Id()).arg(l->
Id()+1));
1488 LOG(VB_PLAYBACK, LOG_INFO,
LOC +
1489 QString(
"- segment %1 appended")
1492 table.insert(
p,
false);
1515 streams->append(dst);
1589 for (
int i = 0; i < streams->size(); i++)
1612 if (streams ==
nullptr)
1616 int count = streams->size();
1619 if ((wanted < 0) || (wanted >= count))
1621 return streams->at(wanted);
1631 if (streams ==
nullptr)
1635 int count = streams->size();
1645 if (streams ==
nullptr)
1649 int count = streams->size();
1650 for (
int n = 0; n < count; n++)
1656 if ((hls->
Id() == hls_new->
Id()) &&
1681 if (!s || s->size() < 7)
1684 if (!s->startsWith((
const char*)
"#EXTM3U"))
1687 QTextStream stream(s);
1693 QString line = stream.readLine();
1696 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC +
1697 QString(
"IsHTTPLiveStreaming: %1").arg(line));
1698 if (line.startsWith(QLatin1String(
"#EXT-X-TARGETDURATION")) ||
1699 line.startsWith(QLatin1String(
"#EXT-X-MEDIA-SEQUENCE")) ||
1700 line.startsWith(QLatin1String(
"#EXT-X-KEY")) ||
1701 line.startsWith(QLatin1String(
"#EXT-X-ALLOW-CACHE")) ||
1702 line.startsWith(QLatin1String(
"#EXT-X-ENDLIST")) ||
1703 line.startsWith(QLatin1String(
"#EXT-X-STREAM-INF")) ||
1704 line.startsWith(QLatin1String(
"#EXT-X-DISCONTINUITY")) ||
1705 line.startsWith(QLatin1String(
"#EXT-X-VERSION")))
1716 URLContext *context =
nullptr;
1720 int ret = ffurl_open_whitelist(&context,
filename.toLatin1(),
1721 AVIO_FLAG_READ,
nullptr,
nullptr,
nullptr,
nullptr,
nullptr);
1724 std::array<uint8_t,1024> buffer {};
1725 ret = ffurl_read(context, buffer.data(), buffer.size());
1728 QByteArray ba((
const char*)buffer.data(), ret);
1731 ffurl_close(context);
1738 url.path().endsWith(QLatin1String(
"m3u8"), Qt::CaseInsensitive) ||
1739 url.query( QUrl::FullyEncoded ).contains(QLatin1String(
"m3u8"), Qt::CaseInsensitive);
1747 int p = line.indexOf(QLatin1String(
":"));
1751 QStringList list = line.mid(
p+1).split(
',');
1752 for (
const auto& it : qAsConst(list))
1754 QString arg = it.trimmed();
1755 if (arg.startsWith(attr))
1757 int pos = arg.indexOf(QLatin1String(
"="));
1760 return arg.mid(pos+1);
1772 int p = line.indexOf(QLatin1String(
":"));
1776 while (++i < line.size() && line[i].isNumber());
1779 target = line.mid(
p+1, i -
p - 1).toInt();
1784 int &duration, QString &title)
1795 int p = line.indexOf(QLatin1String(
":"));
1799 QStringList list = line.mid(
p+1).split(
',');
1806 QString val = list[0];
1811 duration = val.toInt(&ok);
1821 double d = val.toDouble(&ok);
1827 if ((
d) - ((
int)
d) >= 0.5)
1828 duration = ((int)
d) + 1;
1830 duration = ((int)
d);
1833 if (list.size() >= 2)
1853 LOG(VB_PLAYBACK, LOG_ERR,
LOC +
"expected #EXT-X-TARGETDURATION:<s>");
1872 LOG(VB_PLAYBACK, LOG_INFO,
LOC +
"#EXT-X-STREAM-INF: expected PROGRAM-ID=<value>, using -1");
1883 LOG(VB_PLAYBACK, LOG_ERR,
LOC +
"#EXT-X-STREAM-INF: expected BANDWIDTH=<value>");
1886 uint64_t bw = attr.toInt();
1890 LOG(VB_PLAYBACK, LOG_ERR,
LOC +
"#EXT-X-STREAM-INF: bandwidth cannot be 0");
1894 LOG(VB_PLAYBACK, LOG_INFO,
LOC +
1895 QString(
"bandwidth adaptation detected (program-id=%1, bandwidth=%2")
1917 LOG(VB_PLAYBACK, LOG_ERR,
LOC +
"expected #EXT-X-MEDIA-SEQUENCE:<s>");
1923 LOG(VB_PLAYBACK, LOG_ERR,
LOC +
1924 QString(
"EXT-X-MEDIA-SEQUENCE already present in playlist (new=%1, old=%2)")
1944 LOG(VB_PLAYBACK, LOG_ERR,
LOC +
"#EXT-X-KEY: expected METHOD=<value>");
1948 if (attr.startsWith(QLatin1String(
"NONE")))
1953 LOG(VB_PLAYBACK, LOG_ERR,
LOC +
"#EXT-X-KEY: URI not expected");
1962 LOG(VB_PLAYBACK, LOG_ERR,
LOC +
"#EXT-X-KEY: IV not expected");
1967 #ifdef USING_LIBCRYPTO
1968 else if (attr.startsWith(QLatin1String(
"AES-128")))
1974 LOG(VB_PLAYBACK, LOG_INFO,
LOC +
1975 "playback of AES-128 encrypted HTTP Live media detected.");
1981 LOG(VB_PLAYBACK, LOG_ERR,
LOC +
1982 "#EXT-X-KEY: URI not found for encrypted HTTP Live media in AES-128");
1987 hls->SetKeyPath(
decoded_URI(uri.remove(QChar(QLatin1Char(
'"')))));
1990 if (!iv.isNull() && !hls->SetAESIV(iv))
1992 LOG(VB_PLAYBACK, LOG_ERR,
LOC +
"invalid IV");
2000 LOG(VB_PLAYBACK, LOG_ERR,
LOC +
2001 "invalid encryption type, only NONE "
2002 #ifdef USING_LIBCRYPTO
2003 "and AES-128 are supported"
2022 LOG(VB_PLAYBACK, LOG_WARNING,
LOC +
2023 QString(
"tag not supported: #EXT-X-PROGRAM-DATE-TIME %1")
2039 int pos = line.indexOf(QLatin1String(
":"));
2042 QString answer = line.mid(pos+1, 3);
2043 if (answer.size() < 2)
2045 LOG(VB_PLAYBACK, LOG_ERR,
LOC +
"#EXT-X-ALLOW-CACHE, ignoring ...");
2048 hls->
SetCache(!answer.startsWith(QLatin1String(
"NO")));
2068 LOG(VB_PLAYBACK, LOG_ERR,
LOC +
2069 "#EXT-X-VERSION: no protocol version found, should be version 1.");
2073 if (version <= 0 || version > 3)
2075 LOG(VB_PLAYBACK, LOG_ERR,
LOC +
2076 QString(
"#EXT-X-VERSION should be version 1, 2 or 3 iso %1")
2091 LOG(VB_PLAYBACK, LOG_INFO,
LOC +
"video on demand (vod) mode");
2098 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC + QString(
"#EXT-X-DISCONTINUITY %1").arg(line));
2111 if (streams ==
nullptr)
2115 QTextStream stream(*buffer);
2116 #if QT_VERSION < QT_VERSION_CHECK(6,0,0)
2117 stream.setCodec(
"UTF-8");
2119 stream.setEncoding(QStringConverter::Utf8);
2122 QString line = stream.readLine();
2126 if (!line.startsWith(QLatin1String(
"#EXTM3U")))
2128 LOG(VB_PLAYBACK, LOG_ERR,
LOC +
"missing #EXTM3U tag .. aborting");
2134 int p = buffer->indexOf(
"#EXT-X-VERSION:");
2138 QString psz_version = stream.readLine();
2139 if (psz_version.isNull())
2144 LOG(VB_GENERAL, LOG_WARNING,
LOC +
2145 "#EXT-X-VERSION: no protocol version found, assuming version 1.");
2151 bool meta = buffer->indexOf(
"#EXT-X-STREAM-INF") >= 0;
2157 LOG(VB_PLAYBACK, LOG_INFO,
LOC +
"Meta playlist");
2163 line = stream.readLine();
2167 if (line.startsWith(QLatin1String(
"#EXT-X-STREAM-INF")))
2170 QString uri = stream.readLine();
2176 if (uri.startsWith(QLatin1String(
"#")))
2178 LOG(VB_GENERAL, LOG_INFO,
LOC +
2179 QString(
"Skipping invalid stream-inf: %1")
2192 LOG(VB_GENERAL, LOG_INFO,
LOC +
2193 QString(
"Skipping invalid stream, couldn't download: %1")
2198 streams->append(hls);
2224 streams->append(hls);
2226 p = buffer->indexOf(
"#EXT-X-TARGETDURATION:");
2230 QString psz_duration = stream.readLine();
2231 if (psz_duration.isNull())
2238 LOG(VB_PLAYBACK, LOG_INFO,
LOC +
2239 QString(
"%1 Playlist HLS protocol version: %2")
2245 std::chrono::seconds segment_duration = -1s;
2250 line = stream.readLine();
2253 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC + QString(
"ParseM3U8: %1")
2256 if (line.startsWith(QLatin1String(
"#EXTINF")))
2260 segment_duration = std::chrono::seconds(
tmp);
2262 else if (line.startsWith(QLatin1String(
"#EXT-X-TARGETDURATION")))
2264 else if (line.startsWith(QLatin1String(
"#EXT-X-MEDIA-SEQUENCE")))
2266 else if (line.startsWith(QLatin1String(
"#EXT-X-KEY")))
2268 else if (line.startsWith(QLatin1String(
"#EXT-X-PROGRAM-DATE-TIME")))
2270 else if (line.startsWith(QLatin1String(
"#EXT-X-ALLOW-CACHE")))
2272 else if (line.startsWith(QLatin1String(
"#EXT-X-DISCONTINUITY")))
2274 else if (line.startsWith(QLatin1String(
"#EXT-X-VERSION")))
2280 else if (line.startsWith(QLatin1String(
"#EXT-X-ENDLIST")))
2282 else if (!line.startsWith(QLatin1String(
"#")) && !line.isEmpty())
2285 segment_duration = -1s;
2301 std::chrono::microseconds starttime =
mdate();
2302 LOG(VB_PLAYBACK, LOG_INFO,
LOC +
2303 QString(
"Starting Prefetch for %2 segments")
2315 LOG(VB_PLAYBACK, LOG_INFO,
LOC + QString(
"Finished Prefetch (%1s)")
2316 .arg(duration_cast<std::chrono::seconds>(
mdate() - starttime).count()));
2325 bool live = hls->
Live();
2330 LOG(VB_PLAYBACK, LOG_WARNING,
LOC +
"playback will stall");
2335 LOG(VB_PLAYBACK, LOG_WARNING,
LOC +
"playback in danger of stalling");
2340 LOG(VB_PLAYBACK, LOG_WARNING,
LOC +
"playback will exit soon, starving for data");
2360 LOG(VB_PLAYBACK, LOG_WARNING,
LOC +
2361 LOC + QString(
"waiting to get segment %1")
2364 while (!
m_error && (stream < 0) && (retries < 10))
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()));
2408 std::chrono::seconds wanted_duration = 0s;
2416 if (segment ==
nullptr)
2421 LOG(VB_PLAYBACK, LOG_WARNING,
LOC +
2422 QString(
"EXTINF:%1 duration is larger than EXT-X-TARGETDURATION:%2")
2426 wanted_duration += segment->
Duration();
2431 segid = segment->
Id();
2437 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC +
2438 QString(
"Choose segment %1/%2 [%3]")
2439 .arg(wanted).arg(count).arg(segid));
2451 if (streams ==
nullptr)
2455 QMap<int,int> idstart;
2457 for (
int n = streams->size() - 1 ; n >= 0; n--)
2462 streams->removeAt(n);
2468 if (!idstart.contains(
id))
2470 idstart.insert(
id,
start);
2472 int start2 = idstart.value(
id);
2475 idstart.insert(
id,
start);
2479 for (
int n = 0; n < streams->size(); n++)
2484 int newstart= idstart.value(
id);
2485 int todrop = newstart - seq;
2493 LOG(VB_PLAYBACK, LOG_ERR,
LOC +
2494 QString(
"stream %1 [id=%2] can't be properly adjusted, ignoring")
2495 .arg(n).arg(hls->
Id()));
2498 for (
int i = 0; i < todrop; i++)
2525 LOG(VB_PLAYBACK, LOG_ERR,
LOC +
2526 QString(
"Couldn't open URL %1").arg(
m_filename));
2531 LOG(VB_PLAYBACK, LOG_INFO,
LOC +
2532 QString(
"Redirected %1 -> %2 ").arg(
m_filename, finalURL));
2537 LOG(VB_PLAYBACK, LOG_ERR,
LOC +
2538 QString(
"%1 isn't a HTTP Live Streaming URL").arg(
m_filename));
2543 LOG(VB_PLAYBACK, LOG_INFO,
LOC + QString(
"HTTP Live Streaming (%1)")
2549 LOG(VB_PLAYBACK, LOG_ERR,
LOC +
2550 QString(
"An error occurred reading M3U8 playlist (%1)").arg(
m_filename));
2578 LOG(VB_PLAYBACK, LOG_ERR,
LOC +
2579 "fetching first segment failed or didn't complete within 10s.");
2605 FILE *fp = fopen(
filename.toLatin1().constData(),
"w");
2613 for (
int i = segstart; i < segend; i++)
2616 if (segment ==
nullptr)
2618 LOG(VB_PLAYBACK, LOG_ERR,
LOC +
2619 QString(
"downloading %1 failed").arg(i));
2623 LOG(VB_PLAYBACK, LOG_INFO,
LOC +
2624 QString(
"download of %1 succeeded")
2626 fwrite(segment->
Data(), segment->
Size(), 1, fp);
2664 LOG(VB_PLAYBACK, LOG_INFO,
LOC +
2665 QString(
"pausing until we get sufficient data buffered"));
2688 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC + QString(
"interrupted"));
2712 if (segment ==
nullptr)
2738 LOG(VB_PLAYBACK, LOG_INFO,
LOC +
2739 QString(
"started reading segment %1 [id:%2] from stream %3 (%4 buffered)")
2740 .arg(segnum).arg(segment->
Id()).arg(stream)
2744 int32_t len = segment->
Read((uint8_t*)data + used, i_read,
m_fd);
2752 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC + QString(
"interrupted"));
2776 if (segment ==
nullptr)
2780 auto byterate = (uint64_t)(((
double)segment->
Size()) /
2781 ((double)segment->
Duration().count()));
2783 return (
int)((size * 1000.0) / byterate);
2802 std::chrono::microseconds starting =
mdate();
2835 if (segment !=
nullptr)
2842 if (where > totalsize)
2853 std::chrono::seconds starttime = 0s;
2854 std::chrono::seconds endtime = 0s;
2864 if (segment ==
nullptr)
2871 if (postime < endtime)
2876 starttime = endtime;
2889 if (hls->
Live() && (segnum >= count - 1 || segnum < m_playback->Segment()) &&
2904 LOG(VB_PLAYBACK, LOG_INFO,
LOC +
2905 QString(
"seek to segment %1").arg(segnum));
2917 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC + QString(
"interrupted"));
2926 LOG(VB_PLAYBACK, LOG_ERR,
LOC +
2927 QString(
"seek error: segment %1 should have been downloaded, but didn't."
2928 " Playback will stall")
2933 if (segment ==
nullptr)
2939 int32_t skip = ((postime - starttime) * segment->
Size()) / segment->
Duration();
2940 segment->
Read(
nullptr, skip);
2942 LOG(VB_PLAYBACK, LOG_INFO,
LOC + QString(
"seek completed in %1s")
2943 .arg(duration_cast<std::chrono::seconds>(
mdate() - starting).count()));
2962 QMutexLocker lock(&
m_lock);
2965 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC + QString(
"requesting interrupt"));
2971 QMutexLocker lock(&
m_lock);
2974 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC + QString(
"requesting restart"));