MythTV master
httplivestream.cpp
Go to the documentation of this file.
1/* -*- Mode: c++ -*-
2 *
3 * Class HTTPLiveStream
4 *
5 * Copyright (C) Chris Pinkham 2011
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
20 */
21
22// POSIX headers
23#include <unistd.h> // for usleep
24
25// C headers
26#include <cstdio>
27
28#include <QDir>
29#include <QFile>
30#include <QFileInfo>
31#include <QIODevice>
32#include <QRunnable>
33#include <QUrl>
34#include <utility>
35
45
46#include "httplivestream.h"
47
48#define LOC QString("HLS(%1): ").arg(m_sourceFile)
49#define LOC_ERR QString("HLS(%1) Error: ").arg(m_sourceFile)
50#define SLOC QString("HLS(): ")
51#define SLOC_ERR QString("HLS() Error: ")
52
59class HTTPLiveStreamThread : public QRunnable
60{
61 public:
66 explicit HTTPLiveStreamThread(int streamid)
67 : m_streamID(streamid) {}
68
74 void run(void) override // QRunnable
75 {
77
78 QString command = GetAppBinDir() +
79 QString("mythtranscode --hls --hlsstreamid %1")
81
82 uint result = myth_system(command, flags);
83
84 if (result != GENERIC_EXIT_OK)
85 {
86 LOG(VB_GENERAL, LOG_WARNING, SLOC +
87 QString("Command '%1' returned %2")
88 .arg(command).arg(result));
89 }
90 }
91
92 private:
94};
95
96
97HTTPLiveStream::HTTPLiveStream(QString srcFile, uint16_t width, uint16_t height,
98 uint32_t bitrate, uint32_t abitrate,
99 uint16_t maxSegments, uint16_t segmentSize,
100 uint32_t aobitrate, int32_t srate)
101 : m_sourceFile(std::move(srcFile)),
102 m_segmentSize(segmentSize), m_maxSegments(maxSegments),
103 m_height(height), m_width(width),
104 m_bitrate(bitrate),
105 m_audioBitrate(abitrate), m_audioOnlyBitrate(aobitrate),
106 m_sampleRate(srate),
107 m_created(MythDate::current()),
108 m_lastModified(MythDate::current())
109{
110 if ((m_width == 0) && (m_height == 0))
111 m_width = 640;
112
113 if (m_bitrate == 0)
114 m_bitrate = 800000;
115
116 if (m_audioBitrate == 0)
117 m_audioBitrate = 64000;
118
119 if (m_segmentSize == 0)
120 m_segmentSize = 4;
121
122 if (m_audioOnlyBitrate == 0)
123 m_audioOnlyBitrate = 64000;
124
126
127 QFileInfo finfo(m_sourceFile);
128 m_outBase = finfo.fileName() +
129 QString(".%1x%2_%3kV_%4kA").arg(m_width).arg(m_height)
130 .arg(m_bitrate/1000).arg(m_audioBitrate/1000);
131
133
134 m_fullURL = m_httpPrefix + m_outBase + ".m3u8";
136
137 StorageGroup sgroup("Streaming", gCoreContext->GetHostName());
138 m_outDir = sgroup.GetFirstDir();
139 QDir outDir(m_outDir);
140
141 if (!outDir.exists() && !outDir.mkdir(m_outDir))
142 {
143 LOG(VB_RECORD, LOG_ERR, "Unable to create HTTP Live Stream output "
144 "directory, Live Stream will not be created");
145 return;
146 }
147
148 AddStream();
149}
150
152 : m_streamid(streamid)
153{
154 LoadFromDB();
155}
156
158{
159 if (m_writing)
160 {
161 WritePlaylist(false, true);
163 WritePlaylist(true, true);
164 }
165}
166
168{
169 if ((m_streamid == -1) ||
170 (!WriteHTML()) ||
171 (!WriteMetaPlaylist()) ||
173 (!UpdateStatusMessage("Transcode Starting")))
174 return false;
175
176 m_writing = true;
177
178 return true;
179}
180
181QString HTTPLiveStream::GetFilename(uint16_t segmentNumber, bool fileOnly,
182 bool audioOnly, bool encoded) const
183{
184 QString filename;
185
186 if (encoded)
188 else
189 filename = audioOnly ? m_audioOutFile : m_outFile;
190
191 filename += ".%1.ts";
192
193 if (!fileOnly)
194 filename = m_outDir + "/" + filename;
195
196 if (segmentNumber)
197 return filename.arg(segmentNumber, 6, 10, QChar('0'));
198
199 return filename.arg(1, 6, 10, QChar('0'));
200}
201
202QString HTTPLiveStream::GetCurrentFilename(bool audioOnly, bool encoded) const
203{
204 return GetFilename(m_curSegment, false, audioOnly, encoded);
205}
206
208{
210
211 QString tmpBase = QString("");
212 QString tmpFullURL = QString("");
213 QString tmpRelURL = QString("");
214
215 if (m_width && m_height)
216 {
217 tmpBase = m_outBase;
218 tmpFullURL = m_fullURL;
219 tmpRelURL = m_relativeURL;
220 }
221
222 // Check that this stream has not already been created.
223 // We want to avoid creating multiple identical streams and transcode
224 // jobs
226 query.prepare(
227 "SELECT id FROM livestream "
228 "WHERE "
229 "(width = :WIDTH OR height = :HEIGHT) AND bitrate = :BITRATE AND "
230 "audioonlybitrate = :AUDIOONLYBITRATE AND samplerate = :SAMPLERATE AND "
231 "audiobitrate = :AUDIOBITRATE AND segmentsize = :SEGMENTSIZE AND "
232 "sourcefile = :SOURCEFILE AND status <= :STATUS ");
233 query.bindValue(":WIDTH", m_width);
234 query.bindValue(":HEIGHT", m_height);
235 query.bindValue(":BITRATE", m_bitrate);
236 query.bindValue(":AUDIOBITRATE", m_audioBitrate);
237 query.bindValue(":SEGMENTSIZE", m_segmentSize);
238 query.bindValue(":STATUS", (int)kHLSStatusCompleted);
239 query.bindValue(":SOURCEFILE", m_sourceFile);
240 query.bindValue(":AUDIOONLYBITRATE", m_audioOnlyBitrate);
241 query.bindValue(":SAMPLERATE", (m_sampleRate == -1) ? 0 : m_sampleRate); // samplerate column is unsigned, -1 becomes 0
242
243 if (!query.exec())
244 {
245 LOG(VB_GENERAL, LOG_ERR, LOC + "LiveStream existing stream check failed.");
246 return -1;
247 }
248
249 if (!query.next())
250 {
251 query.prepare(
252 "INSERT INTO livestream "
253 " ( width, height, bitrate, audiobitrate, segmentsize, "
254 " maxsegments, startsegment, currentsegment, segmentcount, "
255 " percentcomplete, created, lastmodified, relativeurl, "
256 " fullurl, status, statusmessage, sourcefile, sourcehost, "
257 " sourcewidth, sourceheight, outdir, outbase, "
258 " audioonlybitrate, samplerate ) "
259 "VALUES "
260 " ( :WIDTH, :HEIGHT, :BITRATE, :AUDIOBITRATE, :SEGMENTSIZE, "
261 " :MAXSEGMENTS, 0, 0, 0, "
262 " 0, :CREATED, :LASTMODIFIED, :RELATIVEURL, "
263 " :FULLURL, :STATUS, :STATUSMESSAGE, :SOURCEFILE, :SOURCEHOST, "
264 " :SOURCEWIDTH, :SOURCEHEIGHT, :OUTDIR, :OUTBASE, "
265 " :AUDIOONLYBITRATE, :SAMPLERATE ) ");
266 query.bindValue(":WIDTH", m_width);
267 query.bindValue(":HEIGHT", m_height);
268 query.bindValue(":BITRATE", m_bitrate);
269 query.bindValue(":AUDIOBITRATE", m_audioBitrate);
270 query.bindValue(":SEGMENTSIZE", m_segmentSize);
271 query.bindValue(":MAXSEGMENTS", m_maxSegments);
272 query.bindValue(":CREATED", m_created);
273 query.bindValue(":LASTMODIFIED", m_lastModified);
274 query.bindValue(":RELATIVEURL", tmpRelURL);
275 query.bindValue(":FULLURL", tmpFullURL);
276 query.bindValue(":STATUS", (int)m_status);
277 query.bindValue(":STATUSMESSAGE",
278 QString("Waiting for mythtranscode startup."));
279 query.bindValue(":SOURCEFILE", m_sourceFile);
280 query.bindValue(":SOURCEHOST", gCoreContext->GetHostName());
281 query.bindValue(":SOURCEWIDTH", 0);
282 query.bindValue(":SOURCEHEIGHT", 0);
283 query.bindValue(":OUTDIR", m_outDir);
284 query.bindValue(":OUTBASE", tmpBase);
285 query.bindValue(":AUDIOONLYBITRATE", m_audioOnlyBitrate);
286 query.bindValue(":SAMPLERATE", (m_sampleRate == -1) ? 0 : m_sampleRate); // samplerate column is unsigned, -1 becomes 0
287
288 if (!query.exec())
289 {
290 LOG(VB_GENERAL, LOG_ERR, LOC + "LiveStream insert failed.");
291 return -1;
292 }
293
294 if (!query.exec("SELECT LAST_INSERT_ID()") || !query.next())
295 {
296 LOG(VB_GENERAL, LOG_ERR, LOC + "Unable to query LiveStream streamid.");
297 return -1;
298 }
299 }
300
301 m_streamid = query.value(0).toUInt();
302
303 return m_streamid;
304}
305
307{
308 if (m_streamid == -1)
309 return false;
310
312
313 ++m_curSegment;
315
316 if (!m_startSegment)
318
319 if ((m_maxSegments) &&
321 {
322 QString thisFile = GetFilename(m_startSegment);
323
324 if (!QFile::remove(thisFile))
325 LOG(VB_GENERAL, LOG_ERR, LOC +
326 QString("Unable to delete %1.").arg(thisFile));
327
330 }
331
333 WritePlaylist(false);
334
336 WritePlaylist(true);
337
338 return true;
339}
340
342{
343 if (m_streamid == -1)
344 return {};
345
346 QString outFile = m_outDir + "/" + m_outBase + ".html";
347 return outFile;
348}
349
351{
352 if (m_streamid == -1)
353 return false;
354
355 QString outFile = m_outDir + "/" + m_outBase + ".html";
356 QFile file(outFile);
357
358 if (!file.open(QIODevice::WriteOnly))
359 {
360 LOG(VB_RECORD, LOG_ERR, QString("Error opening %1").arg(outFile));
361 return false;
362 }
363
364 file.write(QString(
365 "<html>\n"
366 " <head>\n"
367 " <title>%1</title>\n"
368 " </head>\n"
369 " <body style='background-color:#FFFFFF;'>\n"
370 " <center>\n"
371 " <video controls>\n"
372 " <source src='%2.m3u8' />\n"
373 " </video>\n"
374 " </center>\n"
375 " </body>\n"
376 "</html>\n"
378 .toLatin1());
379
380 file.close();
381
382 return true;
383}
384
386{
387 if (m_streamid == -1)
388 return {};
389
390 QString outFile = m_outDir + "/" + m_outBase + ".m3u8";
391 return outFile;
392}
393
395{
396 if (m_streamid == -1)
397 return false;
398
399 QString outFile = GetMetaPlaylistName();
400 QFile file(outFile);
401
402 if (!file.open(QIODevice::WriteOnly))
403 {
404 LOG(VB_RECORD, LOG_ERR, QString("Error opening %1").arg(outFile));
405 return false;
406 }
407
408 file.write(QString(
409 "#EXTM3U\n"
410 "#EXT-X-VERSION:4\n"
411 "#EXT-X-MEDIA:TYPE=VIDEO,GROUP-ID=\"AV\",NAME=\"Main\",DEFAULT=YES,URI=\"%2.m3u8\"\n"
412 "#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=%1\n"
413 "%2.m3u8\n"
414 ).arg((int)((m_bitrate + m_audioBitrate) * 1.1))
415 .arg(m_outFileEncoded).toLatin1());
416
418 {
419 file.write(QString(
420 "#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID=\"AO\",NAME=\"Main\",DEFAULT=NO,URI=\"%2.m3u8\"\n"
421 "#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=%1\n"
422 "%2.m3u8\n"
423 ).arg((int)((m_audioOnlyBitrate) * 1.1))
424 .arg(m_audioOutFileEncoded).toLatin1());
425 }
426
427 file.close();
428
429 return true;
430}
431
432QString HTTPLiveStream::GetPlaylistName(bool audioOnly) const
433{
434 if (m_streamid == -1)
435 return {};
436
437 if (audioOnly && m_audioOutFile.isEmpty())
438 return {};
439
440 QString base = audioOnly ? m_audioOutFile : m_outFile;
441 QString outFile = m_outDir + "/" + base + ".m3u8";
442 return outFile;
443}
444
445bool HTTPLiveStream::WritePlaylist(bool audioOnly, bool writeEndTag)
446{
447 if (m_streamid == -1)
448 return false;
449
450 QString outFile = GetPlaylistName(audioOnly);
451 QString tmpFile = outFile + ".tmp";
452
453 QFile file(tmpFile);
454
455 if (!file.open(QIODevice::WriteOnly))
456 {
457 LOG(VB_RECORD, LOG_ERR, QString("Error opening %1").arg(tmpFile));
458 return false;
459 }
460
461 file.write(QString(
462 "#EXTM3U\n"
463 "#EXT-X-ALLOW-CACHE:YES\n"
464 "#EXT-X-TARGETDURATION:%1\n"
465 "#EXT-X-MEDIA-SEQUENCE:%2\n"
466 ).arg(m_segmentSize).arg(m_startSegment).toLatin1());
467
468 if (writeEndTag)
469 file.write("#EXT-X-ENDLIST\n");
470
471 // Don't write out the current segment until the end
472 unsigned int tmpSegCount = m_segmentCount - 1;
473 unsigned int i = 0;
474 unsigned int segmentid = m_startSegment;
475
476 if (writeEndTag)
477 ++tmpSegCount;
478
479 while (i < tmpSegCount)
480 {
481 file.write(QString(
482 "#EXTINF:%1,\n"
483 "%2\n"
484 ).arg(m_segmentSize)
485 .arg(GetFilename(segmentid + i, true, audioOnly, true)).toLatin1());
486
487 ++i;
488 }
489
490 file.close();
491
492 if(rename(tmpFile.toLatin1().constData(),
493 outFile.toLatin1().constData()) == -1)
494 {
495 LOG(VB_RECORD, LOG_ERR, LOC +
496 QString("Error renaming %1 to %2").arg(tmpFile, outFile) + ENO);
497 return false;
498 }
499
500 return true;
501}
502
504{
505 if (m_streamid == -1)
506 return false;
507
509 query.prepare(
510 "UPDATE livestream "
511 "SET startsegment = :START, currentsegment = :CURRENT, "
512 " segmentcount = :COUNT "
513 "WHERE id = :STREAMID; ");
514 query.bindValue(":START", m_startSegment);
515 query.bindValue(":CURRENT", m_curSegment);
516 query.bindValue(":COUNT", m_segmentCount);
517 query.bindValue(":STREAMID", m_streamid);
518
519 if (query.exec())
520 return true;
521
522 LOG(VB_GENERAL, LOG_ERR, LOC +
523 QString("Unable to update segment info for streamid %1")
524 .arg(m_streamid));
525 return false;
526}
527
529 uint16_t srcwidth, uint16_t srcheight)
530{
531 if (m_streamid == -1)
532 return false;
533
534 QFileInfo finfo(m_sourceFile);
535 QString newOutBase = finfo.fileName() +
536 QString(".%1x%2_%3kV_%4kA").arg(width).arg(height)
537 .arg(m_bitrate/1000).arg(m_audioBitrate/1000);
538 QString newFullURL = m_httpPrefix + newOutBase + ".m3u8";
539 QString newRelativeURL = m_httpPrefixRel + newOutBase + ".m3u8";
540
542 query.prepare(
543 "UPDATE livestream "
544 "SET width = :WIDTH, height = :HEIGHT, "
545 " sourcewidth = :SRCWIDTH, sourceheight = :SRCHEIGHT, "
546 " fullurl = :FULLURL, relativeurl = :RELATIVEURL, "
547 " outbase = :OUTBASE "
548 "WHERE id = :STREAMID; ");
549 query.bindValue(":WIDTH", width);
550 query.bindValue(":HEIGHT", height);
551 query.bindValue(":SRCWIDTH", srcwidth);
552 query.bindValue(":SRCHEIGHT", srcheight);
553 query.bindValue(":FULLURL", newFullURL);
554 query.bindValue(":RELATIVEURL", newRelativeURL);
555 query.bindValue(":OUTBASE", newOutBase);
556 query.bindValue(":STREAMID", m_streamid);
557
558 if (!query.exec())
559 {
560 LOG(VB_GENERAL, LOG_ERR, LOC +
561 QString("Unable to update segment info for streamid %1")
562 .arg(m_streamid));
563 return false;
564 }
565
566 m_width = width;
567 m_height = height;
568 m_sourceWidth = srcwidth;
569 m_sourceHeight = srcheight;
570 m_outBase = newOutBase;
571 m_fullURL = newFullURL;
572 m_relativeURL = newRelativeURL;
573
575
576 return true;
577}
578
580{
581 if (m_streamid == -1)
582 return false;
583
584 if ((m_status == kHLSStatusStopping) &&
585 (status == kHLSStatusRunning))
586 {
587 LOG(VB_RECORD, LOG_DEBUG, LOC +
588 QString("Attempted to switch streamid %1 from "
589 "Stopping to Running State").arg(m_streamid));
590 return false;
591 }
592
593 QString mStatusStr = StatusToString(m_status);
594 QString statusStr = StatusToString(status);
595 LOG(VB_RECORD, LOG_DEBUG, LOC +
596 QString("Switch streamid %1 from %2 to %3")
597 .arg(QString::number(m_streamid), mStatusStr, statusStr));
598
599 m_status = status;
600
602 query.prepare(
603 "UPDATE livestream "
604 "SET status = :STATUS "
605 "WHERE id = :STREAMID; ");
606 query.bindValue(":STATUS", (int)status);
607 query.bindValue(":STREAMID", m_streamid);
608
609 if (query.exec())
610 return true;
611
612 LOG(VB_GENERAL, LOG_ERR, LOC +
613 QString("Unable to update status for streamid %1").arg(m_streamid));
614 return false;
615}
616
617bool HTTPLiveStream::UpdateStatusMessage(const QString& message)
618{
619 if (m_streamid == -1)
620 return false;
621
623 query.prepare(
624 "UPDATE livestream "
625 "SET statusmessage = :MESSAGE "
626 "WHERE id = :STREAMID; ");
627 query.bindValue(":MESSAGE", message);
628 query.bindValue(":STREAMID", m_streamid);
629
630 if (query.exec())
631 {
632 m_statusMessage = message;
633 return true;
634 }
635
636 LOG(VB_GENERAL, LOG_ERR, LOC +
637 QString("Unable to update status message for streamid %1")
638 .arg(m_streamid));
639 return false;
640}
641
643{
644 if (m_streamid == -1)
645 return false;
646
648 query.prepare(
649 "UPDATE livestream "
650 "SET percentcomplete = :PERCENT "
651 "WHERE id = :STREAMID; ");
652 query.bindValue(":PERCENT", percent);
653 query.bindValue(":STREAMID", m_streamid);
654
655 if (query.exec())
656 {
657 m_percentComplete = percent;
658 return true;
659 }
660
661 LOG(VB_GENERAL, LOG_ERR, LOC +
662 QString("Unable to update percent complete for streamid %1")
663 .arg(m_streamid));
664 return false;
665}
666
668{
669 switch (status) {
670 case kHLSStatusUndefined : return {"Undefined"};
671 case kHLSStatusQueued : return {"Queued"};
672 case kHLSStatusStarting : return {"Starting"};
673 case kHLSStatusRunning : return {"Running"};
674 case kHLSStatusCompleted : return {"Completed"};
675 case kHLSStatusErrored : return {"Errored"};
676 case kHLSStatusStopping : return {"Stopping"};
677 case kHLSStatusStopped : return {"Stopped"};
678 };
679
680 return {"Unknown status value"};
681}
682
684{
685 if (m_streamid == -1)
686 return false;
687
689 query.prepare(
690 "SELECT width, height, bitrate, audiobitrate, segmentsize, "
691 " maxsegments, startsegment, currentsegment, segmentcount, "
692 " percentcomplete, created, lastmodified, relativeurl, "
693 " fullurl, status, statusmessage, sourcefile, sourcehost, "
694 " sourcewidth, sourceheight, outdir, outbase, audioonlybitrate, "
695 " samplerate "
696 "FROM livestream "
697 "WHERE id = :STREAMID; ");
698 query.bindValue(":STREAMID", m_streamid);
699
700 if (!query.exec() || !query.next())
701 {
702 LOG(VB_GENERAL, LOG_ERR, LOC +
703 QString("Unable to query DB info for stream %1")
704 .arg(m_streamid));
705 return false;
706 }
707
708 m_width = query.value(0).toUInt();
709 m_height = query.value(1).toUInt();
710 m_bitrate = query.value(2).toUInt();
711 m_audioBitrate = query.value(3).toUInt();
712 m_segmentSize = query.value(4).toUInt();
713 m_maxSegments = query.value(5).toUInt();
714 m_startSegment = query.value(6).toUInt();
715 m_curSegment = query.value(7).toUInt();
716 m_segmentCount = query.value(8).toUInt();
717 m_percentComplete = query.value(9).toUInt();
718 m_created = MythDate::as_utc(query.value(10).toDateTime());
719 m_lastModified = MythDate::as_utc(query.value(11).toDateTime());
720 m_relativeURL = query.value(12).toString();
721 m_fullURL = query.value(13).toString();
722 m_status = (HTTPLiveStreamStatus)(query.value(14).toInt());
723 m_statusMessage = query.value(15).toString();
724 m_sourceFile = query.value(16).toString();
725 m_sourceHost = query.value(17).toString();
726 m_sourceWidth = query.value(18).toUInt();
727 m_sourceHeight = query.value(19).toUInt();
728 m_outDir = query.value(20).toString();
729 m_outBase = query.value(21).toString();
730 m_audioOnlyBitrate = query.value(22).toUInt();
731 m_sampleRate = query.value(23).toUInt();
732
734
735 return true;
736}
737
739{
740 m_outBaseEncoded = QString(QUrl::toPercentEncoding(m_outBase, "", " "));
741
742 m_outFile = m_outBase + ".av";
744
746 {
748 QString(".ao_%1kA").arg(m_audioOnlyBitrate/1000);
750 QString(".ao_%1kA").arg(m_audioOnlyBitrate/1000);
751 }
752
753 m_httpPrefix = gCoreContext->GetSetting("HTTPLiveStreamPrefix", QString(
754 "http://%1:%2/StorageGroup/Streaming/")
757
758 if (!m_httpPrefix.endsWith("/"))
759 m_httpPrefix.append("/");
760
761 if (!gCoreContext->GetSetting("HTTPLiveStreamPrefixRel").isEmpty())
762 {
763 m_httpPrefixRel = gCoreContext->GetSetting("HTTPLiveStreamPrefixRel");
764 if (!m_httpPrefix.endsWith("/"))
765 m_httpPrefix.append("/");
766 }
767 else if (m_httpPrefix.contains("/StorageGroup/Streaming/"))
768 {
769 m_httpPrefixRel = "/StorageGroup/Streaming/";
770 }
771 else
772 {
773 m_httpPrefixRel = "";
774 }
775}
776
778{
779 if (m_streamid == -1)
780 return kHLSStatusUndefined;
781
783 query.prepare(
784 "SELECT status FROM livestream "
785 "WHERE id = :STREAMID; ");
786 query.bindValue(":STREAMID", m_streamid);
787
788 if (!query.exec() || !query.next())
789 {
790 LOG(VB_GENERAL, LOG_ERR, LOC +
791 QString("Unable to check stop status for stream %1")
792 .arg(m_streamid));
793 return kHLSStatusUndefined;
794 }
795
796 return (HTTPLiveStreamStatus)query.value(0).toInt();
797}
798
800{
801 if (m_streamid == -1)
802 return false;
803
805 query.prepare(
806 "SELECT status FROM livestream "
807 "WHERE id = :STREAMID; ");
808 query.bindValue(":STREAMID", m_streamid);
809
810 if (!query.exec() || !query.next())
811 {
812 LOG(VB_GENERAL, LOG_ERR, LOC +
813 QString("Unable to check stop status for stream %1")
814 .arg(m_streamid));
815 return false;
816 }
817
818 return query.value(0).toInt() == (int)kHLSStatusStopping;
819}
820
821/* vim: set expandtab tabstop=4 shiftwidth=4: */
QRunnable class for running mythtranscode for HTTP Live Streams.
void run(void) override
Runs mythtranscode for the given HTTP Live Stream ID.
HTTPLiveStreamThread(int streamid)
Constructor for creating a SystemEventThread.
uint32_t m_bitrate
int32_t m_sampleRate
bool WriteHTML(void)
uint16_t m_percentComplete
QString GetHTMLPageName(void) const
QString GetCurrentFilename(bool audioOnly=false, bool encoded=false) const
QString m_outFileEncoded
QString GetPlaylistName(bool audioOnly=false) const
uint16_t m_sourceHeight
uint16_t m_width
HTTPLiveStreamStatus GetDBStatus(void) const
QString m_audioOutFileEncoded
HTTPLiveStream(QString srcFile, uint16_t width=640, uint16_t height=480, uint32_t bitrate=800000, uint32_t abitrate=64000, uint16_t maxSegments=0, uint16_t segmentSize=10, uint32_t aobitrate=32000, int32_t srate=-1)
bool WritePlaylist(bool audioOnly=false, bool writeEndTag=false)
uint16_t m_height
static QString StatusToString(HTTPLiveStreamStatus status)
QString m_sourceFile
bool SaveSegmentInfo(void)
bool AddSegment(void)
uint16_t m_maxSegments
uint32_t m_audioBitrate
bool CheckStop(void)
QString m_outBaseEncoded
uint16_t m_startSegment
uint16_t m_segmentSize
bool UpdatePercentComplete(int percent)
bool LoadFromDB(void)
uint16_t m_segmentCount
bool WriteMetaPlaylist(void)
QString m_httpPrefixRel
bool UpdateStatus(HTTPLiveStreamStatus status)
QString GetFilename(uint16_t segmentNumber=0, bool fileOnly=false, bool audioOnly=false, bool encoded=false) const
bool InitForWrite(void)
bool UpdateSizeInfo(uint16_t width, uint16_t height, uint16_t srcwidth, uint16_t srcheight)
bool UpdateStatusMessage(const QString &message)
uint16_t m_sourceWidth
QString m_sourceHost
QString m_httpPrefix
void SetOutputVars(void)
HTTPLiveStreamStatus m_status
QString m_relativeURL
QDateTime m_created
uint32_t m_audioOnlyBitrate
uint16_t m_curSegment
QString m_audioOutFile
QString m_statusMessage
QDateTime m_lastModified
QString GetMetaPlaylistName(void) const
QSqlQuery wrapper that fetches a DB connection from the connection pool.
Definition: mythdbcon.h:128
bool prepare(const QString &query)
QSqlQuery::prepare() is not thread safe in Qt <= 3.3.2.
Definition: mythdbcon.cpp:837
QVariant value(int i) const
Definition: mythdbcon.h:204
bool exec(void)
Wrap QSqlQuery::exec() so we can display SQL.
Definition: mythdbcon.cpp:618
void bindValue(const QString &placeholder, const QVariant &val)
Add a single binding.
Definition: mythdbcon.cpp:888
bool next(void)
Wrap QSqlQuery::next() so we can display the query results.
Definition: mythdbcon.cpp:812
static MSqlQueryInfo InitCon(ConnectionReuse _reuse=kNormalConnection)
Only use this in combination with MSqlQuery constructor.
Definition: mythdbcon.cpp:550
QString GetMasterServerIP(void)
Returns the Master Backend IP address If the address is an IPv6 address, the scope Id is removed.
QString GetHostName(void)
QString GetSetting(const QString &key, const QString &defaultval="")
int GetMasterServerStatusPort(void)
Returns the Master Backend status port If no master server status port has been defined in the databa...
QString GetFirstDir(bool appendSlash=false) const
@ GENERIC_EXIT_OK
Exited with no error.
Definition: exitcodes.h:13
unsigned int uint
Definition: freesurround.h:24
#define LOC
#define SLOC
HTTPLiveStreamStatus
Definition: httplivestream.h:8
@ kHLSStatusQueued
@ kHLSStatusStarting
@ kHLSStatusStopping
@ kHLSStatusStopped
@ kHLSStatusErrored
@ kHLSStatusCompleted
@ kHLSStatusRunning
@ kHLSStatusUndefined
Definition: httplivestream.h:9
unsigned short uint16_t
Definition: iso6937tables.h:3
QString logPropagateArgs
Definition: logging.cpp:82
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
QString GetAppBinDir(void)
Definition: mythdirs.cpp:260
#define ENO
This can be appended to the LOG args with "+".
Definition: mythlogging.h:74
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:39
@ kMSDontBlockInputDevs
avoid blocking LIRC & Joystick Menu
Definition: mythsystem.h:36
uint myth_system(const QString &command, uint flags, std::chrono::seconds timeout)
QDateTime as_utc(const QDateTime &old_dt)
Returns copy of QDateTime with TimeSpec set to UTC.
Definition: mythdate.cpp:28
QDateTime current(bool stripped)
Returns current Date and Time in UTC.
Definition: mythdate.cpp:15
STL namespace.