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 
36 #include "libmythbase/exitcodes.h"
39 #include "libmythbase/mythdate.h"
40 #include "libmythbase/mythdirs.h"
43 #include "libmythbase/mythtimer.h"
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 
59 class 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 
97 HTTPLiveStream::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 
132  SetOutputVars();
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);
162  if (m_audioOnlyBitrate)
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 
181 QString 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 
202 QString 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
225  MSqlQuery query(MSqlQuery::InitCon());
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 
311  MSqlQuery query(MSqlQuery::InitCon());
312 
313  ++m_curSegment;
314  ++m_segmentCount;
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 
328  ++m_startSegment;
329  --m_segmentCount;
330  }
331 
332  SaveSegmentInfo();
333  WritePlaylist(false);
334 
335  if (m_audioOnlyBitrate)
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 
417  if (m_audioOnlyBitrate)
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 
432 QString 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 
445 bool 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 
508  MSqlQuery query(MSqlQuery::InitCon());
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 
541  MSqlQuery query(MSqlQuery::InitCon());
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 
574  SetOutputVars();
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 
601  MSqlQuery query(MSqlQuery::InitCon());
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 
617 bool HTTPLiveStream::UpdateStatusMessage(const QString& message)
618 {
619  if (m_streamid == -1)
620  return false;
621 
622  MSqlQuery query(MSqlQuery::InitCon());
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 
647  MSqlQuery query(MSqlQuery::InitCon());
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 
688  MSqlQuery query(MSqlQuery::InitCon());
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 
733  SetOutputVars();
734 
735  return true;
736 }
737 
739 {
740  m_outBaseEncoded = QString(QUrl::toPercentEncoding(m_outBase, "", " "));
741 
742  m_outFile = m_outBase + ".av";
744 
745  if (m_audioOnlyBitrate)
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  m_httpPrefixRel = "/StorageGroup/Streaming/";
769  else
770  m_httpPrefixRel = "";
771 }
772 
774 {
775  if (m_streamid == -1)
776  return kHLSStatusUndefined;
777 
778  MSqlQuery query(MSqlQuery::InitCon());
779  query.prepare(
780  "SELECT status FROM livestream "
781  "WHERE id = :STREAMID; ");
782  query.bindValue(":STREAMID", m_streamid);
783 
784  if (!query.exec() || !query.next())
785  {
786  LOG(VB_GENERAL, LOG_ERR, LOC +
787  QString("Unable to check stop status for stream %1")
788  .arg(m_streamid));
789  return kHLSStatusUndefined;
790  }
791 
792  return (HTTPLiveStreamStatus)query.value(0).toInt();
793 }
794 
796 {
797  if (m_streamid == -1)
798  return false;
799 
800  MSqlQuery query(MSqlQuery::InitCon());
801  query.prepare(
802  "SELECT status FROM livestream "
803  "WHERE id = :STREAMID; ");
804  query.bindValue(":STREAMID", m_streamid);
805 
806  if (!query.exec() || !query.next())
807  {
808  LOG(VB_GENERAL, LOG_ERR, LOC +
809  QString("Unable to check stop status for stream %1")
810  .arg(m_streamid));
811  return false;
812  }
813 
814  return query.value(0).toInt() == (int)kHLSStatusStopping;
815 }
816 
818 {
819  if (GetDBStatus() != kHLSStatusQueued)
820  return GetLiveStreamInfo();
821 
822  auto *streamThread = new HTTPLiveStreamThread(GetStreamID());
824  "HTTPLiveStream");
825  MythTimer statusTimer;
826  int delay = 250000;
827  statusTimer.start();
828 
830  while ((status == kHLSStatusQueued) &&
831  (statusTimer.elapsed() < 30s))
832  {
833  delay = (int)(delay * 1.5);
834  usleep(delay);
835 
836  status = GetDBStatus();
837  }
838 
839  return GetLiveStreamInfo();
840 }
841 
843 {
844  MSqlQuery query(MSqlQuery::InitCon());
845  query.prepare(
846  "SELECT startSegment, segmentCount "
847  "FROM livestream "
848  "WHERE id = :STREAMID; ");
849  query.bindValue(":STREAMID", id);
850 
851  if (!query.exec() || !query.next())
852  {
853  LOG(VB_RECORD, LOG_ERR, "Error selecting stream info in RemoveStream");
854  return false;
855  }
856 
857  auto *hls = new HTTPLiveStream(id);
858 
859  if (hls->GetDBStatus() == kHLSStatusRunning) {
861  }
862 
863  QString thisFile;
864  int startSegment = query.value(0).toInt();
865  int segmentCount = query.value(1).toInt();
866 
867  for (int x = 0; x < segmentCount; ++x)
868  {
869  thisFile = hls->GetFilename(startSegment + x);
870 
871  if (!thisFile.isEmpty() && !QFile::remove(thisFile))
872  LOG(VB_GENERAL, LOG_ERR, SLOC +
873  QString("Unable to delete %1.").arg(thisFile));
874 
875  thisFile = hls->GetFilename(startSegment + x, false, true);
876 
877  if (!thisFile.isEmpty() && !QFile::remove(thisFile))
878  LOG(VB_GENERAL, LOG_ERR, SLOC +
879  QString("Unable to delete %1.").arg(thisFile));
880  }
881 
882  thisFile = hls->GetMetaPlaylistName();
883  if (!thisFile.isEmpty() && !QFile::remove(thisFile))
884  LOG(VB_GENERAL, LOG_ERR, SLOC +
885  QString("Unable to delete %1.").arg(thisFile));
886 
887  thisFile = hls->GetPlaylistName();
888  if (!thisFile.isEmpty() && !QFile::remove(thisFile))
889  LOG(VB_GENERAL, LOG_ERR, SLOC +
890  QString("Unable to delete %1.").arg(thisFile));
891 
892  thisFile = hls->GetPlaylistName(true);
893  if (!thisFile.isEmpty() && !QFile::remove(thisFile))
894  LOG(VB_GENERAL, LOG_ERR, SLOC +
895  QString("Unable to delete %1.").arg(thisFile));
896 
897  thisFile = hls->GetHTMLPageName();
898  if (!thisFile.isEmpty() && !QFile::remove(thisFile))
899  LOG(VB_GENERAL, LOG_ERR, SLOC +
900  QString("Unable to delete %1.").arg(thisFile));
901 
902  query.prepare(
903  "DELETE FROM livestream "
904  "WHERE id = :STREAMID; ");
905  query.bindValue(":STREAMID", id);
906 
907  if (!query.exec())
908  LOG(VB_RECORD, LOG_ERR, "Error deleting stream info in RemoveStream");
909 
910  delete hls;
911  return true;
912 }
913 
915 {
916  MSqlQuery query(MSqlQuery::InitCon());
917  query.prepare(
918  "UPDATE livestream "
919  "SET status = :STATUS "
920  "WHERE id = :STREAMID; ");
921  query.bindValue(":STATUS", (int)kHLSStatusStopping);
922  query.bindValue(":STREAMID", id);
923 
924  if (!query.exec())
925  {
926  LOG(VB_GENERAL, LOG_ERR, SLOC +
927  QString("Unable to remove mark stream stopped for stream %1.")
928  .arg(id));
929  return nullptr;
930  }
931 
932  auto *hls = new HTTPLiveStream(id);
933  if (!hls)
934  return nullptr;
935 
936  MythTimer statusTimer;
937  int delay = 250000;
938  statusTimer.start();
939 
940  HTTPLiveStreamStatus status = hls->GetDBStatus();
941  while ((status != kHLSStatusStopped) &&
942  (status != kHLSStatusCompleted) &&
943  (status != kHLSStatusErrored) &&
944  (statusTimer.elapsed() < 30s))
945  {
946  delay = (int)(delay * 1.5);
947  usleep(delay);
948 
949  status = hls->GetDBStatus();
950  }
951 
952  hls->LoadFromDB();
953  DTC::LiveStreamInfo *pLiveStreamInfo = hls->GetLiveStreamInfo();
954 
955  delete hls;
956  return pLiveStreamInfo;
957 }
958 
960 // Content Service API helpers
962 
964  DTC::LiveStreamInfo *info)
965 {
966  if (!info)
967  info = new DTC::LiveStreamInfo();
968 
969  info->setId(m_streamid);
970  info->setWidth((int)m_width);
971  info->setHeight((int)m_height);
972  info->setBitrate((int)m_bitrate);
973  info->setAudioBitrate((int)m_audioBitrate);
974  info->setSegmentSize((int)m_segmentSize);
975  info->setMaxSegments((int)m_maxSegments);
976  info->setStartSegment((int)m_startSegment);
977  info->setCurrentSegment((int)m_curSegment);
978  info->setSegmentCount((int)m_segmentCount);
979  info->setPercentComplete((int)m_percentComplete);
980  info->setCreated(m_created);
981  info->setLastModified(m_lastModified);
982  info->setStatusStr(StatusToString(m_status));
983  info->setStatusInt((int)m_status);
984  info->setStatusMessage(m_statusMessage);
985  info->setSourceFile(m_sourceFile);
986  info->setSourceHost(m_sourceHost);
987  info->setAudioOnlyBitrate((int)m_audioOnlyBitrate);
988 
989  if (m_width && m_height) {
990  info->setRelativeURL(m_relativeURL);
991  info->setFullURL(m_fullURL);
992  info->setSourceWidth(m_sourceWidth);
993  info->setSourceHeight(m_sourceHeight);
994  }
995 
996  return info;
997 }
998 
1000 {
1001  auto *infoList = new DTC::LiveStreamInfoList();
1002 
1003  QString sql = "SELECT id FROM livestream ";
1004 
1005  if (!FileName.isEmpty())
1006  sql += "WHERE sourcefile LIKE :FILENAME ";
1007 
1008  sql += "ORDER BY lastmodified DESC;";
1009 
1010  MSqlQuery query(MSqlQuery::InitCon());
1011  query.prepare(sql);
1012  if (!FileName.isEmpty())
1013  query.bindValue(":FILENAME", QString("%%1%").arg(FileName));
1014 
1015  if (!query.exec())
1016  {
1017  LOG(VB_GENERAL, LOG_ERR, SLOC + "Unable to get list of Live Streams");
1018  return infoList;
1019  }
1020 
1021  DTC::LiveStreamInfo *info = nullptr;
1022  HTTPLiveStream *hls = nullptr;
1023  while (query.next())
1024  {
1025  hls = new HTTPLiveStream(query.value(0).toUInt());
1026  info = infoList->AddNewLiveStreamInfo();
1027  hls->GetLiveStreamInfo(info);
1028  delete hls;
1029  }
1030 
1031  return infoList;
1032 }
1033 
1034 /* vim: set expandtab tabstop=4 shiftwidth=4: */
MSqlQuery::next
bool next(void)
Wrap QSqlQuery::next() so we can display the query results.
Definition: mythdbcon.cpp:813
MSqlQuery
QSqlQuery wrapper that fetches a DB connection from the connection pool.
Definition: mythdbcon.h:127
MythTimer::elapsed
std::chrono::milliseconds elapsed(void)
Returns milliseconds elapsed since last start() or restart()
Definition: mythtimer.cpp:91
HTTPLiveStream::m_outBaseEncoded
QString m_outBaseEncoded
Definition: httplivestream.h:91
HTTPLiveStream::CheckStop
bool CheckStop(void)
Definition: httplivestream.cpp:795
ENO
#define ENO
This can be appended to the LOG args with "+".
Definition: mythlogging.h:73
HTTPLiveStream::GetCurrentFilename
QString GetCurrentFilename(bool audioOnly=false, bool encoded=false) const
Definition: httplivestream.cpp:202
HTTPLiveStream::m_outBase
QString m_outBase
Definition: httplivestream.h:90
HTTPLiveStream::m_bitrate
uint32_t m_bitrate
Definition: httplivestream.h:105
HTTPLiveStream::m_fullURL
QString m_fullURL
Definition: httplivestream.h:114
kMSDontBlockInputDevs
@ kMSDontBlockInputDevs
avoid blocking LIRC & Joystick Menu
Definition: mythsystem.h:36
HTTPLiveStream::WritePlaylist
bool WritePlaylist(bool audioOnly=false, bool writeEndTag=false)
Definition: httplivestream.cpp:445
DTC::LiveStreamInfo
Definition: liveStreamInfo.h:15
HTTPLiveStream::UpdateSizeInfo
bool UpdateSizeInfo(uint16_t width, uint16_t height, uint16_t srcwidth, uint16_t srcheight)
Definition: httplivestream.cpp:528
MythDate::as_utc
QDateTime as_utc(const QDateTime &old_dt)
Returns copy of QDateTime with TimeSpec set to UTC.
Definition: mythdate.cpp:27
MythTimer
A QElapsedTimer based timer to replace use of QTime as a timer.
Definition: mythtimer.h:13
HTTPLiveStream::m_relativeURL
QString m_relativeURL
Definition: httplivestream.h:113
HTTPLiveStream::m_httpPrefixRel
QString m_httpPrefixRel
Definition: httplivestream.h:102
HTTPLiveStream::GetHTMLPageName
QString GetHTMLPageName(void) const
Definition: httplivestream.cpp:341
HTTPLiveStream::m_height
uint16_t m_height
Definition: httplivestream.h:103
MSqlQuery::value
QVariant value(int i) const
Definition: mythdbcon.h:204
DTC::LiveStreamInfoList
Definition: liveStreamInfoList.h:14
MSqlQuery::exec
bool exec(void)
Wrap QSqlQuery::exec() so we can display SQL.
Definition: mythdbcon.cpp:619
HTTPLiveStream::m_lastModified
QDateTime m_lastModified
Definition: httplivestream.h:111
MythTimer::start
void start(void)
starts measuring elapsed time.
Definition: mythtimer.cpp:47
LOG
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:39
HTTPLiveStream::HTTPLiveStream
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)
Definition: httplivestream.cpp:97
httplivestream.h
HTTPLiveStream::m_startSegment
uint16_t m_startSegment
Definition: httplivestream.h:99
HTTPLiveStream::UpdateStatus
bool UpdateStatus(HTTPLiveStreamStatus status)
Definition: httplivestream.cpp:579
build_compdb.file
file
Definition: build_compdb.py:55
mythdirs.h
HTTPLiveStream::m_outFile
QString m_outFile
Definition: httplivestream.h:92
myth_system
uint myth_system(const QString &command, uint flags, std::chrono::seconds timeout)
Definition: mythsystemlegacy.cpp:506
GENERIC_EXIT_OK
@ GENERIC_EXIT_OK
Exited with no error.
Definition: exitcodes.h:11
MThreadPool::startReserved
void startReserved(QRunnable *runnable, const QString &debugName, std::chrono::milliseconds waitForAvailMS=0ms)
Definition: mthreadpool.cpp:361
MythDate::current
QDateTime current(bool stripped)
Returns current Date and Time in UTC.
Definition: mythdate.cpp:14
MythCoreContext::GetMasterServerStatusPort
int GetMasterServerStatusPort(void)
Returns the Master Backend status port If no master server status port has been defined in the databa...
Definition: mythcorecontext.cpp:993
HTTPLiveStream::GetDBStatus
HTTPLiveStreamStatus GetDBStatus(void) const
Definition: httplivestream.cpp:773
HTTPLiveStream::StatusToString
static QString StatusToString(HTTPLiveStreamStatus status)
Definition: httplivestream.cpp:667
HTTPLiveStream::m_audioOutFile
QString m_audioOutFile
Definition: httplivestream.h:94
mythsystemlegacy.h
HTTPLiveStream::GetFilename
QString GetFilename(uint16_t segmentNumber=0, bool fileOnly=false, bool audioOnly=false, bool encoded=false) const
Definition: httplivestream.cpp:181
HTTPLiveStreamThread
QRunnable class for running mythtranscode for HTTP Live Streams.
Definition: httplivestream.cpp:59
kHLSStatusStarting
@ kHLSStatusStarting
Definition: httplivestream.h:13
mythdate.h
HTTPLiveStream::SaveSegmentInfo
bool SaveSegmentInfo(void)
Definition: httplivestream.cpp:503
kHLSStatusStopped
@ kHLSStatusStopped
Definition: httplivestream.h:18
HTTPLiveStream::m_writing
bool m_writing
Definition: httplivestream.h:83
mythlogging.h
HTTPLiveStream::m_outFileEncoded
QString m_outFileEncoded
Definition: httplivestream.h:93
kHLSStatusCompleted
@ kHLSStatusCompleted
Definition: httplivestream.h:15
HTTPLiveStream::m_segmentCount
uint16_t m_segmentCount
Definition: httplivestream.h:98
HTTPLiveStream::m_sampleRate
int32_t m_sampleRate
Definition: httplivestream.h:108
HTTPLiveStream::m_sourceHost
QString m_sourceHost
Definition: httplivestream.h:86
HTTPLiveStream::InitForWrite
bool InitForWrite(void)
Definition: httplivestream.cpp:167
MythCoreContext::GetMasterServerIP
QString GetMasterServerIP(void)
Returns the Master Backend IP address If the address is an IPv6 address, the scope Id is removed.
Definition: mythcorecontext.cpp:966
HTTPLiveStreamThread::HTTPLiveStreamThread
HTTPLiveStreamThread(int streamid)
Constructor for creating a SystemEventThread.
Definition: httplivestream.cpp:66
SLOC
#define SLOC
Definition: httplivestream.cpp:50
MSqlQuery::InitCon
static MSqlQueryInfo InitCon(ConnectionReuse _reuse=kNormalConnection)
Only use this in combination with MSqlQuery constructor.
Definition: mythdbcon.cpp:551
HTTPLiveStream::GetLiveStreamInfo
DTC::LiveStreamInfo * GetLiveStreamInfo(DTC::LiveStreamInfo *info=nullptr)
Definition: httplivestream.cpp:963
HTTPLiveStream::WriteMetaPlaylist
bool WriteMetaPlaylist(void)
Definition: httplivestream.cpp:394
HTTPLiveStream::AddStream
int AddStream(void)
Definition: httplivestream.cpp:207
HTTPLiveStream::UpdateStatusMessage
bool UpdateStatusMessage(const QString &message)
Definition: httplivestream.cpp:617
HTTPLiveStreamStatus
HTTPLiveStreamStatus
Definition: httplivestream.h:10
HTTPLiveStream::StartStream
DTC::LiveStreamInfo * StartStream(void)
Definition: httplivestream.cpp:817
HTTPLiveStream::m_status
HTTPLiveStreamStatus m_status
Definition: httplivestream.h:117
HTTPLiveStream::m_curSegment
uint16_t m_curSegment
Definition: httplivestream.h:100
HTTPLiveStream::GetLiveStreamInfoList
static DTC::LiveStreamInfoList * GetLiveStreamInfoList(const QString &FileName="")
Definition: httplivestream.cpp:999
HTTPLiveStream::SetOutputVars
void SetOutputVars(void)
Definition: httplivestream.cpp:738
StorageGroup::GetFirstDir
QString GetFirstDir(bool appendSlash=false) const
Definition: storagegroup.cpp:189
HTTPLiveStream::GetMetaPlaylistName
QString GetMetaPlaylistName(void) const
Definition: httplivestream.cpp:385
HTTPLiveStream::m_outDir
QString m_outDir
Definition: httplivestream.h:89
storagegroup.h
kHLSStatusUndefined
@ kHLSStatusUndefined
Definition: httplivestream.h:11
kHLSStatusQueued
@ kHLSStatusQueued
Definition: httplivestream.h:12
HTTPLiveStream::m_audioOutFileEncoded
QString m_audioOutFileEncoded
Definition: httplivestream.h:95
HTTPLiveStream::GetPlaylistName
QString GetPlaylistName(bool audioOnly=false) const
Definition: httplivestream.cpp:432
HTTPLiveStream::LoadFromDB
bool LoadFromDB(void)
Definition: httplivestream.cpp:683
uint
unsigned int uint
Definition: compat.h:81
HTTPLiveStream::AddSegment
bool AddSegment(void)
Definition: httplivestream.cpp:306
gCoreContext
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
Definition: mythcorecontext.cpp:55
HTTPLiveStream::StopStream
static DTC::LiveStreamInfo * StopStream(int id)
Definition: httplivestream.cpp:914
kHLSStatusStopping
@ kHLSStatusStopping
Definition: httplivestream.h:17
HTTPLiveStream::UpdatePercentComplete
bool UpdatePercentComplete(int percent)
Definition: httplivestream.cpp:642
HTTPLiveStream::m_sourceHeight
uint16_t m_sourceHeight
Definition: httplivestream.h:88
HTTPLiveStream::m_segmentSize
uint16_t m_segmentSize
Definition: httplivestream.h:96
HTTPLiveStream::RemoveStream
static bool RemoveStream(int id)
Definition: httplivestream.cpp:842
HTTPLiveStream::m_audioOnlyBitrate
uint32_t m_audioOnlyBitrate
Definition: httplivestream.h:107
mthreadpool.h
HTTPLiveStream::m_percentComplete
uint16_t m_percentComplete
Definition: httplivestream.h:112
HTTPLiveStream::GetStreamID
int GetStreamID(void) const
Definition: httplivestream.h:35
HTTPLiveStream::m_sourceWidth
uint16_t m_sourceWidth
Definition: httplivestream.h:87
HTTPLiveStream::m_streamid
int m_streamid
Definition: httplivestream.h:84
HTTPLiveStream
Definition: httplivestream.h:22
kHLSStatusErrored
@ kHLSStatusErrored
Definition: httplivestream.h:16
mythcorecontext.h
MythDate
Definition: mythdate.cpp:11
MSqlQuery::bindValue
void bindValue(const QString &placeholder, const QVariant &val)
Add a single binding.
Definition: mythdbcon.cpp:889
std
Definition: mythchrono.h:23
LOC
#define LOC
Definition: httplivestream.cpp:48
HTTPLiveStream::m_maxSegments
uint16_t m_maxSegments
Definition: httplivestream.h:97
HTTPLiveStream::WriteHTML
bool WriteHTML(void)
Definition: httplivestream.cpp:350
GetAppBinDir
QString GetAppBinDir(void)
Definition: mythdirs.cpp:253
StorageGroup
Definition: storagegroup.h:11
HTTPLiveStream::~HTTPLiveStream
~HTTPLiveStream()
Definition: httplivestream.cpp:157
mythtimer.h
HTTPLiveStream::m_sourceFile
QString m_sourceFile
Definition: httplivestream.h:85
MythCoreContext::GetHostName
QString GetHostName(void)
Definition: mythcorecontext.cpp:838
uint16_t
unsigned short uint16_t
Definition: iso6937tables.h:3
logPropagateArgs
QString logPropagateArgs
Definition: logging.cpp:82
exitcodes.h
HTTPLiveStream::m_created
QDateTime m_created
Definition: httplivestream.h:110
build_compdb.filename
filename
Definition: build_compdb.py:21
HTTPLiveStream::m_statusMessage
QString m_statusMessage
Definition: httplivestream.h:115
MThreadPool::globalInstance
static MThreadPool * globalInstance(void)
Definition: mthreadpool.cpp:307
kHLSStatusRunning
@ kHLSStatusRunning
Definition: httplivestream.h:14
HTTPLiveStreamThread::m_streamID
int m_streamID
Definition: httplivestream.cpp:93
HTTPLiveStream::m_audioBitrate
uint32_t m_audioBitrate
Definition: httplivestream.h:106
HTTPLiveStream::m_httpPrefix
QString m_httpPrefix
Definition: httplivestream.h:101
HTTPLiveStreamThread::run
void run(void) override
Runs mythtranscode for the given HTTP Live Stream ID.
Definition: httplivestream.cpp:74
HTTPLiveStream::m_width
uint16_t m_width
Definition: httplivestream.h:104
MythCoreContext::GetSetting
QString GetSetting(const QString &key, const QString &defaultval="")
Definition: mythcorecontext.cpp:898
MSqlQuery::prepare
bool prepare(const QString &query)
QSqlQuery::prepare() is not thread safe in Qt <= 3.3.2.
Definition: mythdbcon.cpp:838