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 "mythcorecontext.h"
37 #include "mythdate.h"
38 #include "mythdirs.h"
39 #include "mythtimer.h"
40 #include "mthreadpool.h"
41 #include "mythsystemlegacy.h"
42 #include "exitcodes.h"
43 #include "mythlogging.h"
44 #include "storagegroup.h"
45 #include "httplivestream.h"
46 
47 #define LOC QString("HLS(%1): ").arg(m_sourceFile)
48 #define LOC_ERR QString("HLS(%1) Error: ").arg(m_sourceFile)
49 #define SLOC QString("HLS(): ")
50 #define SLOC_ERR QString("HLS() Error: ")
51 
58 class HTTPLiveStreamThread : public QRunnable
59 {
60  public:
65  explicit HTTPLiveStreamThread(int streamid)
66  : m_streamID(streamid) {}
67 
73  void run(void) override // QRunnable
74  {
76 
77  QString command = GetAppBinDir() +
78  QString("mythtranscode --hls --hlsstreamid %1")
80 
81  uint result = myth_system(command, flags);
82 
83  if (result != GENERIC_EXIT_OK)
84  {
85  LOG(VB_GENERAL, LOG_WARNING, SLOC +
86  QString("Command '%1' returned %2")
87  .arg(command).arg(result));
88  }
89  }
90 
91  private:
93 };
94 
95 
96 HTTPLiveStream::HTTPLiveStream(QString srcFile, uint16_t width, uint16_t height,
97  uint32_t bitrate, uint32_t abitrate,
98  uint16_t maxSegments, uint16_t segmentSize,
99  uint32_t aobitrate, int32_t srate)
100  : m_sourceFile(std::move(srcFile)),
101  m_segmentSize(segmentSize), m_maxSegments(maxSegments),
102  m_height(height), m_width(width),
103  m_bitrate(bitrate),
104  m_audioBitrate(abitrate), m_audioOnlyBitrate(aobitrate),
105  m_sampleRate(srate),
106  m_created(MythDate::current()),
107  m_lastModified(MythDate::current())
108 {
109  if ((m_width == 0) && (m_height == 0))
110  m_width = 640;
111 
112  if (m_bitrate == 0)
113  m_bitrate = 800000;
114 
115  if (m_audioBitrate == 0)
116  m_audioBitrate = 64000;
117 
118  if (m_segmentSize == 0)
119  m_segmentSize = 4;
120 
121  if (m_audioOnlyBitrate == 0)
122  m_audioOnlyBitrate = 64000;
123 
125 
126  QFileInfo finfo(m_sourceFile);
127  m_outBase = finfo.fileName() +
128  QString(".%1x%2_%3kV_%4kA").arg(m_width).arg(m_height)
129  .arg(m_bitrate/1000).arg(m_audioBitrate/1000);
130 
131  SetOutputVars();
132 
133  m_fullURL = m_httpPrefix + m_outBase + ".m3u8";
135 
136  StorageGroup sgroup("Streaming", gCoreContext->GetHostName());
137  m_outDir = sgroup.GetFirstDir();
138  QDir outDir(m_outDir);
139 
140  if (!outDir.exists() && !outDir.mkdir(m_outDir))
141  {
142  LOG(VB_RECORD, LOG_ERR, "Unable to create HTTP Live Stream output "
143  "directory, Live Stream will not be created");
144  return;
145  }
146 
147  AddStream();
148 }
149 
151  : m_streamid(streamid)
152 {
153  LoadFromDB();
154 }
155 
157 {
158  if (m_writing)
159  {
160  WritePlaylist(false, true);
161  if (m_audioOnlyBitrate)
162  WritePlaylist(true, true);
163  }
164 }
165 
167 {
168  if ((m_streamid == -1) ||
169  (!WriteHTML()) ||
170  (!WriteMetaPlaylist()) ||
172  (!UpdateStatusMessage("Transcode Starting")))
173  return false;
174 
175  m_writing = true;
176 
177  return true;
178 }
179 
180 QString HTTPLiveStream::GetFilename(uint16_t segmentNumber, bool fileOnly,
181  bool audioOnly, bool encoded) const
182 {
183  QString filename;
184 
185  if (encoded)
187  else
188  filename = audioOnly ? m_audioOutFile : m_outFile;
189 
190  filename += ".%1.ts";
191 
192  if (!fileOnly)
193  filename = m_outDir + "/" + filename;
194 
195  if (segmentNumber)
196  return filename.arg(segmentNumber, 6, 10, QChar('0'));
197 
198  return filename.arg(1, 6, 10, QChar('0'));
199 }
200 
201 QString HTTPLiveStream::GetCurrentFilename(bool audioOnly, bool encoded) const
202 {
203  return GetFilename(m_curSegment, false, audioOnly, encoded);
204 }
205 
207 {
209 
210  QString tmpBase = QString("");
211  QString tmpFullURL = QString("");
212  QString tmpRelURL = QString("");
213 
214  if (m_width && m_height)
215  {
216  tmpBase = m_outBase;
217  tmpFullURL = m_fullURL;
218  tmpRelURL = m_relativeURL;
219  }
220 
221  // Check that this stream has not already been created.
222  // We want to avoid creating multiple identical streams and transcode
223  // jobs
225  query.prepare(
226  "SELECT id FROM livestream "
227  "WHERE "
228  "(width = :WIDTH OR height = :HEIGHT) AND bitrate = :BITRATE AND "
229  "audioonlybitrate = :AUDIOONLYBITRATE AND samplerate = :SAMPLERATE AND "
230  "audiobitrate = :AUDIOBITRATE AND segmentsize = :SEGMENTSIZE AND "
231  "sourcefile = :SOURCEFILE AND status <= :STATUS ");
232  query.bindValue(":WIDTH", m_width);
233  query.bindValue(":HEIGHT", m_height);
234  query.bindValue(":BITRATE", m_bitrate);
235  query.bindValue(":AUDIOBITRATE", m_audioBitrate);
236  query.bindValue(":SEGMENTSIZE", m_segmentSize);
237  query.bindValue(":STATUS", (int)kHLSStatusCompleted);
238  query.bindValue(":SOURCEFILE", m_sourceFile);
239  query.bindValue(":AUDIOONLYBITRATE", m_audioOnlyBitrate);
240  query.bindValue(":SAMPLERATE", (m_sampleRate == -1) ? 0 : m_sampleRate); // samplerate column is unsigned, -1 becomes 0
241 
242  if (!query.exec())
243  {
244  LOG(VB_GENERAL, LOG_ERR, LOC + "LiveStream existing stream check failed.");
245  return -1;
246  }
247 
248  if (!query.next())
249  {
250  query.prepare(
251  "INSERT INTO livestream "
252  " ( width, height, bitrate, audiobitrate, segmentsize, "
253  " maxsegments, startsegment, currentsegment, segmentcount, "
254  " percentcomplete, created, lastmodified, relativeurl, "
255  " fullurl, status, statusmessage, sourcefile, sourcehost, "
256  " sourcewidth, sourceheight, outdir, outbase, "
257  " audioonlybitrate, samplerate ) "
258  "VALUES "
259  " ( :WIDTH, :HEIGHT, :BITRATE, :AUDIOBITRATE, :SEGMENTSIZE, "
260  " :MAXSEGMENTS, 0, 0, 0, "
261  " 0, :CREATED, :LASTMODIFIED, :RELATIVEURL, "
262  " :FULLURL, :STATUS, :STATUSMESSAGE, :SOURCEFILE, :SOURCEHOST, "
263  " :SOURCEWIDTH, :SOURCEHEIGHT, :OUTDIR, :OUTBASE, "
264  " :AUDIOONLYBITRATE, :SAMPLERATE ) ");
265  query.bindValue(":WIDTH", m_width);
266  query.bindValue(":HEIGHT", m_height);
267  query.bindValue(":BITRATE", m_bitrate);
268  query.bindValue(":AUDIOBITRATE", m_audioBitrate);
269  query.bindValue(":SEGMENTSIZE", m_segmentSize);
270  query.bindValue(":MAXSEGMENTS", m_maxSegments);
271  query.bindValue(":CREATED", m_created);
272  query.bindValue(":LASTMODIFIED", m_lastModified);
273  query.bindValue(":RELATIVEURL", tmpRelURL);
274  query.bindValue(":FULLURL", tmpFullURL);
275  query.bindValue(":STATUS", (int)m_status);
276  query.bindValue(":STATUSMESSAGE",
277  QString("Waiting for mythtranscode startup."));
278  query.bindValue(":SOURCEFILE", m_sourceFile);
279  query.bindValue(":SOURCEHOST", gCoreContext->GetHostName());
280  query.bindValue(":SOURCEWIDTH", 0);
281  query.bindValue(":SOURCEHEIGHT", 0);
282  query.bindValue(":OUTDIR", m_outDir);
283  query.bindValue(":OUTBASE", tmpBase);
284  query.bindValue(":AUDIOONLYBITRATE", m_audioOnlyBitrate);
285  query.bindValue(":SAMPLERATE", (m_sampleRate == -1) ? 0 : m_sampleRate); // samplerate column is unsigned, -1 becomes 0
286 
287  if (!query.exec())
288  {
289  LOG(VB_GENERAL, LOG_ERR, LOC + "LiveStream insert failed.");
290  return -1;
291  }
292 
293  if (!query.exec("SELECT LAST_INSERT_ID()") || !query.next())
294  {
295  LOG(VB_GENERAL, LOG_ERR, LOC + "Unable to query LiveStream streamid.");
296  return -1;
297  }
298  }
299 
300  m_streamid = query.value(0).toUInt();
301 
302  return m_streamid;
303 }
304 
306 {
307  if (m_streamid == -1)
308  return false;
309 
311 
312  ++m_curSegment;
313  ++m_segmentCount;
314 
315  if (!m_startSegment)
317 
318  if ((m_maxSegments) &&
320  {
321  QString thisFile = GetFilename(m_startSegment);
322 
323  if (!QFile::remove(thisFile))
324  LOG(VB_GENERAL, LOG_ERR, LOC +
325  QString("Unable to delete %1.").arg(thisFile));
326 
327  ++m_startSegment;
328  --m_segmentCount;
329  }
330 
331  SaveSegmentInfo();
332  WritePlaylist(false);
333 
334  if (m_audioOnlyBitrate)
335  WritePlaylist(true);
336 
337  return true;
338 }
339 
341 {
342  if (m_streamid == -1)
343  return QString();
344 
345  QString outFile = m_outDir + "/" + m_outBase + ".html";
346  return outFile;
347 }
348 
350 {
351  if (m_streamid == -1)
352  return false;
353 
354  QString outFile = m_outDir + "/" + m_outBase + ".html";
355  QFile file(outFile);
356 
357  if (!file.open(QIODevice::WriteOnly))
358  {
359  LOG(VB_RECORD, LOG_ERR, QString("Error opening %1").arg(outFile));
360  return false;
361  }
362 
363  file.write(QString(
364  "<html>\n"
365  " <head>\n"
366  " <title>%1</title>\n"
367  " </head>\n"
368  " <body style='background-color:#FFFFFF;'>\n"
369  " <center>\n"
370  " <video controls>\n"
371  " <source src='%2.m3u8' />\n"
372  " </video>\n"
373  " </center>\n"
374  " </body>\n"
375  "</html>\n"
377  .toLatin1());
378 
379  file.close();
380 
381  return true;
382 }
383 
385 {
386  if (m_streamid == -1)
387  return QString();
388 
389  QString outFile = m_outDir + "/" + m_outBase + ".m3u8";
390  return outFile;
391 }
392 
394 {
395  if (m_streamid == -1)
396  return false;
397 
398  QString outFile = GetMetaPlaylistName();
399  QFile file(outFile);
400 
401  if (!file.open(QIODevice::WriteOnly))
402  {
403  LOG(VB_RECORD, LOG_ERR, QString("Error opening %1").arg(outFile));
404  return false;
405  }
406 
407  file.write(QString(
408  "#EXTM3U\n"
409  "#EXT-X-VERSION:4\n"
410  "#EXT-X-MEDIA:TYPE=VIDEO,GROUP-ID=\"AV\",NAME=\"Main\",DEFAULT=YES,URI=\"%2.m3u8\"\n"
411  "#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=%1\n"
412  "%2.m3u8\n"
413  ).arg((int)((m_bitrate + m_audioBitrate) * 1.1))
414  .arg(m_outFileEncoded).toLatin1());
415 
416  if (m_audioOnlyBitrate)
417  {
418  file.write(QString(
419  "#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID=\"AO\",NAME=\"Main\",DEFAULT=NO,URI=\"%2.m3u8\"\n"
420  "#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=%1\n"
421  "%2.m3u8\n"
422  ).arg((int)((m_audioOnlyBitrate) * 1.1))
423  .arg(m_audioOutFileEncoded).toLatin1());
424  }
425 
426  file.close();
427 
428  return true;
429 }
430 
431 QString HTTPLiveStream::GetPlaylistName(bool audioOnly) const
432 {
433  if (m_streamid == -1)
434  return QString();
435 
436  if (audioOnly && m_audioOutFile.isEmpty())
437  return QString();
438 
439  QString base = audioOnly ? m_audioOutFile : m_outFile;
440  QString outFile = m_outDir + "/" + base + ".m3u8";
441  return outFile;
442 }
443 
444 bool HTTPLiveStream::WritePlaylist(bool audioOnly, bool writeEndTag)
445 {
446  if (m_streamid == -1)
447  return false;
448 
449  QString outFile = GetPlaylistName(audioOnly);
450  QString tmpFile = outFile + ".tmp";
451 
452  QFile file(tmpFile);
453 
454  if (!file.open(QIODevice::WriteOnly))
455  {
456  LOG(VB_RECORD, LOG_ERR, QString("Error opening %1").arg(tmpFile));
457  return false;
458  }
459 
460  file.write(QString(
461  "#EXTM3U\n"
462  "#EXT-X-ALLOW-CACHE:YES\n"
463  "#EXT-X-TARGETDURATION:%1\n"
464  "#EXT-X-MEDIA-SEQUENCE:%2\n"
465  ).arg(m_segmentSize).arg(m_startSegment).toLatin1());
466 
467  if (writeEndTag)
468  file.write("#EXT-X-ENDLIST\n");
469 
470  // Don't write out the current segment until the end
471  unsigned int tmpSegCount = m_segmentCount - 1;
472  unsigned int i = 0;
473  unsigned int segmentid = m_startSegment;
474 
475  if (writeEndTag)
476  ++tmpSegCount;
477 
478  while (i < tmpSegCount)
479  {
480  file.write(QString(
481  "#EXTINF:%1,\n"
482  "%2\n"
483  ).arg(m_segmentSize)
484  .arg(GetFilename(segmentid + i, true, audioOnly, true)).toLatin1());
485 
486  ++i;
487  }
488 
489  file.close();
490 
491  if(rename(tmpFile.toLatin1().constData(),
492  outFile.toLatin1().constData()) == -1)
493  {
494  LOG(VB_RECORD, LOG_ERR, LOC +
495  QString("Error renaming %1 to %2").arg(tmpFile).arg(outFile) + ENO);
496  return false;
497  }
498 
499  return true;
500 }
501 
503 {
504  if (m_streamid == -1)
505  return false;
506 
508  query.prepare(
509  "UPDATE livestream "
510  "SET startsegment = :START, currentsegment = :CURRENT, "
511  " segmentcount = :COUNT "
512  "WHERE id = :STREAMID; ");
513  query.bindValue(":START", m_startSegment);
514  query.bindValue(":CURRENT", m_curSegment);
515  query.bindValue(":COUNT", m_segmentCount);
516  query.bindValue(":STREAMID", m_streamid);
517 
518  if (query.exec())
519  return true;
520 
521  LOG(VB_GENERAL, LOG_ERR, LOC +
522  QString("Unable to update segment info for streamid %1")
523  .arg(m_streamid));
524  return false;
525 }
526 
528  uint16_t srcwidth, uint16_t srcheight)
529 {
530  if (m_streamid == -1)
531  return false;
532 
533  QFileInfo finfo(m_sourceFile);
534  QString newOutBase = finfo.fileName() +
535  QString(".%1x%2_%3kV_%4kA").arg(width).arg(height)
536  .arg(m_bitrate/1000).arg(m_audioBitrate/1000);
537  QString newFullURL = m_httpPrefix + newOutBase + ".m3u8";
538  QString newRelativeURL = m_httpPrefixRel + newOutBase + ".m3u8";
539 
541  query.prepare(
542  "UPDATE livestream "
543  "SET width = :WIDTH, height = :HEIGHT, "
544  " sourcewidth = :SRCWIDTH, sourceheight = :SRCHEIGHT, "
545  " fullurl = :FULLURL, relativeurl = :RELATIVEURL, "
546  " outbase = :OUTBASE "
547  "WHERE id = :STREAMID; ");
548  query.bindValue(":WIDTH", width);
549  query.bindValue(":HEIGHT", height);
550  query.bindValue(":SRCWIDTH", srcwidth);
551  query.bindValue(":SRCHEIGHT", srcheight);
552  query.bindValue(":FULLURL", newFullURL);
553  query.bindValue(":RELATIVEURL", newRelativeURL);
554  query.bindValue(":OUTBASE", newOutBase);
555  query.bindValue(":STREAMID", m_streamid);
556 
557  if (!query.exec())
558  {
559  LOG(VB_GENERAL, LOG_ERR, LOC +
560  QString("Unable to update segment info for streamid %1")
561  .arg(m_streamid));
562  return false;
563  }
564 
565  m_width = width;
566  m_height = height;
567  m_sourceWidth = srcwidth;
568  m_sourceHeight = srcheight;
569  m_outBase = newOutBase;
570  m_fullURL = newFullURL;
571  m_relativeURL = newRelativeURL;
572 
573  SetOutputVars();
574 
575  return true;
576 }
577 
579 {
580  if (m_streamid == -1)
581  return false;
582 
583  if ((m_status == kHLSStatusStopping) &&
584  (status == kHLSStatusRunning))
585  {
586  LOG(VB_RECORD, LOG_DEBUG, LOC +
587  QString("Attempted to switch streamid %1 from "
588  "Stopping to Running State").arg(m_streamid));
589  return false;
590  }
591 
592  QString mStatusStr = StatusToString(m_status);
593  QString statusStr = StatusToString(status);
594  LOG(VB_RECORD, LOG_DEBUG, LOC +
595  QString("Switch streamid %1 from %2 to %3")
596  .arg(m_streamid).arg(mStatusStr).arg(statusStr));
597 
598  m_status = status;
599 
601  query.prepare(
602  "UPDATE livestream "
603  "SET status = :STATUS "
604  "WHERE id = :STREAMID; ");
605  query.bindValue(":STATUS", (int)status);
606  query.bindValue(":STREAMID", m_streamid);
607 
608  if (query.exec())
609  return true;
610 
611  LOG(VB_GENERAL, LOG_ERR, LOC +
612  QString("Unable to update status for streamid %1").arg(m_streamid));
613  return false;
614 }
615 
616 bool HTTPLiveStream::UpdateStatusMessage(const QString& message)
617 {
618  if (m_streamid == -1)
619  return false;
620 
622  query.prepare(
623  "UPDATE livestream "
624  "SET statusmessage = :MESSAGE "
625  "WHERE id = :STREAMID; ");
626  query.bindValue(":MESSAGE", message);
627  query.bindValue(":STREAMID", m_streamid);
628 
629  if (query.exec())
630  {
631  m_statusMessage = message;
632  return true;
633  }
634 
635  LOG(VB_GENERAL, LOG_ERR, LOC +
636  QString("Unable to update status message for streamid %1")
637  .arg(m_streamid));
638  return false;
639 }
640 
642 {
643  if (m_streamid == -1)
644  return false;
645 
647  query.prepare(
648  "UPDATE livestream "
649  "SET percentcomplete = :PERCENT "
650  "WHERE id = :STREAMID; ");
651  query.bindValue(":PERCENT", percent);
652  query.bindValue(":STREAMID", m_streamid);
653 
654  if (query.exec())
655  {
656  m_percentComplete = percent;
657  return true;
658  }
659 
660  LOG(VB_GENERAL, LOG_ERR, LOC +
661  QString("Unable to update percent complete for streamid %1")
662  .arg(m_streamid));
663  return false;
664 }
665 
667 {
668  switch (status) {
669  case kHLSStatusUndefined : return QString("Undefined");
670  case kHLSStatusQueued : return QString("Queued");
671  case kHLSStatusStarting : return QString("Starting");
672  case kHLSStatusRunning : return QString("Running");
673  case kHLSStatusCompleted : return QString("Completed");
674  case kHLSStatusErrored : return QString("Errored");
675  case kHLSStatusStopping : return QString("Stopping");
676  case kHLSStatusStopped : return QString("Stopped");
677  };
678 
679  return QString("Unknown status value");
680 }
681 
683 {
684  if (m_streamid == -1)
685  return false;
686 
688  query.prepare(
689  "SELECT width, height, bitrate, audiobitrate, segmentsize, "
690  " maxsegments, startsegment, currentsegment, segmentcount, "
691  " percentcomplete, created, lastmodified, relativeurl, "
692  " fullurl, status, statusmessage, sourcefile, sourcehost, "
693  " sourcewidth, sourceheight, outdir, outbase, audioonlybitrate, "
694  " samplerate "
695  "FROM livestream "
696  "WHERE id = :STREAMID; ");
697  query.bindValue(":STREAMID", m_streamid);
698 
699  if (!query.exec() || !query.next())
700  {
701  LOG(VB_GENERAL, LOG_ERR, LOC +
702  QString("Unable to query DB info for stream %1")
703  .arg(m_streamid));
704  return false;
705  }
706 
707  m_width = query.value(0).toUInt();
708  m_height = query.value(1).toUInt();
709  m_bitrate = query.value(2).toUInt();
710  m_audioBitrate = query.value(3).toUInt();
711  m_segmentSize = query.value(4).toUInt();
712  m_maxSegments = query.value(5).toUInt();
713  m_startSegment = query.value(6).toUInt();
714  m_curSegment = query.value(7).toUInt();
715  m_segmentCount = query.value(8).toUInt();
716  m_percentComplete = query.value(9).toUInt();
717  m_created = MythDate::as_utc(query.value(10).toDateTime());
718  m_lastModified = MythDate::as_utc(query.value(11).toDateTime());
719  m_relativeURL = query.value(12).toString();
720  m_fullURL = query.value(13).toString();
721  m_status = (HTTPLiveStreamStatus)(query.value(14).toInt());
722  m_statusMessage = query.value(15).toString();
723  m_sourceFile = query.value(16).toString();
724  m_sourceHost = query.value(17).toString();
725  m_sourceWidth = query.value(18).toUInt();
726  m_sourceHeight = query.value(19).toUInt();
727  m_outDir = query.value(20).toString();
728  m_outBase = query.value(21).toString();
729  m_audioOnlyBitrate = query.value(22).toUInt();
730  m_sampleRate = query.value(23).toUInt();
731 
732  SetOutputVars();
733 
734  return true;
735 }
736 
738 {
739  m_outBaseEncoded = QString(QUrl::toPercentEncoding(m_outBase, "", " "));
740 
741  m_outFile = m_outBase + ".av";
743 
744  if (m_audioOnlyBitrate)
745  {
747  QString(".ao_%1kA").arg(m_audioOnlyBitrate/1000);
749  QString(".ao_%1kA").arg(m_audioOnlyBitrate/1000);
750  }
751 
752  m_httpPrefix = gCoreContext->GetSetting("HTTPLiveStreamPrefix", QString(
753  "http://%1:%2/StorageGroup/Streaming/")
756 
757  if (!m_httpPrefix.endsWith("/"))
758  m_httpPrefix.append("/");
759 
760  if (!gCoreContext->GetSetting("HTTPLiveStreamPrefixRel").isEmpty())
761  {
762  m_httpPrefixRel = gCoreContext->GetSetting("HTTPLiveStreamPrefixRel");
763  if (!m_httpPrefix.endsWith("/"))
764  m_httpPrefix.append("/");
765  }
766  else if (m_httpPrefix.contains("/StorageGroup/Streaming/"))
767  m_httpPrefixRel = "/StorageGroup/Streaming/";
768  else
769  m_httpPrefixRel = "";
770 }
771 
773 {
774  if (m_streamid == -1)
775  return kHLSStatusUndefined;
776 
778  query.prepare(
779  "SELECT status FROM livestream "
780  "WHERE id = :STREAMID; ");
781  query.bindValue(":STREAMID", m_streamid);
782 
783  if (!query.exec() || !query.next())
784  {
785  LOG(VB_GENERAL, LOG_ERR, LOC +
786  QString("Unable to check stop status for stream %1")
787  .arg(m_streamid));
788  return kHLSStatusUndefined;
789  }
790 
791  return (HTTPLiveStreamStatus)query.value(0).toInt();
792 }
793 
795 {
796  if (m_streamid == -1)
797  return false;
798 
800  query.prepare(
801  "SELECT status FROM livestream "
802  "WHERE id = :STREAMID; ");
803  query.bindValue(":STREAMID", m_streamid);
804 
805  if (!query.exec() || !query.next())
806  {
807  LOG(VB_GENERAL, LOG_ERR, LOC +
808  QString("Unable to check stop status for stream %1")
809  .arg(m_streamid));
810  return false;
811  }
812 
813  return query.value(0).toInt() == (int)kHLSStatusStopping;
814 }
815 
817 {
818  if (GetDBStatus() != kHLSStatusQueued)
819  return GetLiveStreamInfo();
820 
821  auto *streamThread = new HTTPLiveStreamThread(GetStreamID());
823  "HTTPLiveStream");
824  MythTimer statusTimer;
825  int delay = 250000;
826  statusTimer.start();
827 
829  while ((status == kHLSStatusQueued) &&
830  ((statusTimer.elapsed() / 1000) < 30))
831  {
832  delay = (int)(delay * 1.5);
833  usleep(delay);
834 
835  status = GetDBStatus();
836  }
837 
838  return GetLiveStreamInfo();
839 }
840 
842 {
844  query.prepare(
845  "SELECT startSegment, segmentCount "
846  "FROM livestream "
847  "WHERE id = :STREAMID; ");
848  query.bindValue(":STREAMID", id);
849 
850  if (!query.exec() || !query.next())
851  {
852  LOG(VB_RECORD, LOG_ERR, "Error selecting stream info in RemoveStream");
853  return false;
854  }
855 
856  auto *hls = new HTTPLiveStream(id);
857 
858  if (hls->GetDBStatus() == kHLSStatusRunning) {
860  }
861 
862  QString thisFile;
863  int startSegment = query.value(0).toInt();
864  int segmentCount = query.value(1).toInt();
865 
866  for (int x = 0; x < segmentCount; ++x)
867  {
868  thisFile = hls->GetFilename(startSegment + x);
869 
870  if (!thisFile.isEmpty() && !QFile::remove(thisFile))
871  LOG(VB_GENERAL, LOG_ERR, SLOC +
872  QString("Unable to delete %1.").arg(thisFile));
873 
874  thisFile = hls->GetFilename(startSegment + x, false, true);
875 
876  if (!thisFile.isEmpty() && !QFile::remove(thisFile))
877  LOG(VB_GENERAL, LOG_ERR, SLOC +
878  QString("Unable to delete %1.").arg(thisFile));
879  }
880 
881  thisFile = hls->GetMetaPlaylistName();
882  if (!thisFile.isEmpty() && !QFile::remove(thisFile))
883  LOG(VB_GENERAL, LOG_ERR, SLOC +
884  QString("Unable to delete %1.").arg(thisFile));
885 
886  thisFile = hls->GetPlaylistName();
887  if (!thisFile.isEmpty() && !QFile::remove(thisFile))
888  LOG(VB_GENERAL, LOG_ERR, SLOC +
889  QString("Unable to delete %1.").arg(thisFile));
890 
891  thisFile = hls->GetPlaylistName(true);
892  if (!thisFile.isEmpty() && !QFile::remove(thisFile))
893  LOG(VB_GENERAL, LOG_ERR, SLOC +
894  QString("Unable to delete %1.").arg(thisFile));
895 
896  thisFile = hls->GetHTMLPageName();
897  if (!thisFile.isEmpty() && !QFile::remove(thisFile))
898  LOG(VB_GENERAL, LOG_ERR, SLOC +
899  QString("Unable to delete %1.").arg(thisFile));
900 
901  query.prepare(
902  "DELETE FROM livestream "
903  "WHERE id = :STREAMID; ");
904  query.bindValue(":STREAMID", id);
905 
906  if (!query.exec())
907  LOG(VB_RECORD, LOG_ERR, "Error deleting stream info in RemoveStream");
908 
909  delete hls;
910  return true;
911 }
912 
914 {
916  query.prepare(
917  "UPDATE livestream "
918  "SET status = :STATUS "
919  "WHERE id = :STREAMID; ");
920  query.bindValue(":STATUS", (int)kHLSStatusStopping);
921  query.bindValue(":STREAMID", id);
922 
923  if (!query.exec())
924  {
925  LOG(VB_GENERAL, LOG_ERR, SLOC +
926  QString("Unable to remove mark stream stopped for stream %1.")
927  .arg(id));
928  return nullptr;
929  }
930 
931  auto *hls = new HTTPLiveStream(id);
932  if (!hls)
933  return nullptr;
934 
935  MythTimer statusTimer;
936  int delay = 250000;
937  statusTimer.start();
938 
939  HTTPLiveStreamStatus status = hls->GetDBStatus();
940  while ((status != kHLSStatusStopped) &&
941  (status != kHLSStatusCompleted) &&
942  (status != kHLSStatusErrored) &&
943  ((statusTimer.elapsed() / 1000) < 30))
944  {
945  delay = (int)(delay * 1.5);
946  usleep(delay);
947 
948  status = hls->GetDBStatus();
949  }
950 
951  hls->LoadFromDB();
952  DTC::LiveStreamInfo *pLiveStreamInfo = hls->GetLiveStreamInfo();
953 
954  delete hls;
955  return pLiveStreamInfo;
956 }
957 
959 // Content Service API helpers
961 
963  DTC::LiveStreamInfo *info)
964 {
965  if (!info)
966  info = new DTC::LiveStreamInfo();
967 
968  info->setId(m_streamid);
969  info->setWidth((int)m_width);
970  info->setHeight((int)m_height);
971  info->setBitrate((int)m_bitrate);
972  info->setAudioBitrate((int)m_audioBitrate);
973  info->setSegmentSize((int)m_segmentSize);
974  info->setMaxSegments((int)m_maxSegments);
975  info->setStartSegment((int)m_startSegment);
976  info->setCurrentSegment((int)m_curSegment);
977  info->setSegmentCount((int)m_segmentCount);
978  info->setPercentComplete((int)m_percentComplete);
979  info->setCreated(m_created);
980  info->setLastModified(m_lastModified);
981  info->setStatusStr(StatusToString(m_status));
982  info->setStatusInt((int)m_status);
983  info->setStatusMessage(m_statusMessage);
984  info->setSourceFile(m_sourceFile);
985  info->setSourceHost(m_sourceHost);
986  info->setAudioOnlyBitrate((int)m_audioOnlyBitrate);
987 
988  if (m_width && m_height) {
989  info->setRelativeURL(m_relativeURL);
990  info->setFullURL(m_fullURL);
991  info->setSourceWidth(m_sourceWidth);
992  info->setSourceHeight(m_sourceHeight);
993  }
994 
995  return info;
996 }
997 
999 {
1000  auto *infoList = new DTC::LiveStreamInfoList();
1001 
1002  QString sql = "SELECT id FROM livestream ";
1003 
1004  if (!FileName.isEmpty())
1005  sql += "WHERE sourcefile LIKE :FILENAME ";
1006 
1007  sql += "ORDER BY lastmodified DESC;";
1008 
1010  query.prepare(sql);
1011  if (!FileName.isEmpty())
1012  query.bindValue(":FILENAME", QString("%%1%").arg(FileName));
1013 
1014  if (!query.exec())
1015  {
1016  LOG(VB_GENERAL, LOG_ERR, SLOC + "Unable to get list of Live Streams");
1017  return infoList;
1018  }
1019 
1020  DTC::LiveStreamInfo *info = nullptr;
1021  HTTPLiveStream *hls = nullptr;
1022  while (query.next())
1023  {
1024  hls = new HTTPLiveStream(query.value(0).toUInt());
1025  info = infoList->AddNewLiveStreamInfo();
1026  hls->GetLiveStreamInfo(info);
1027  delete hls;
1028  }
1029 
1030  return infoList;
1031 }
1032 
1033 /* 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:783
MSqlQuery
QSqlQuery wrapper that fetches a DB connection from the connection pool.
Definition: mythdbcon.h:126
HTTPLiveStream::m_outBaseEncoded
QString m_outBaseEncoded
Definition: httplivestream.h:91
HTTPLiveStream::CheckStop
bool CheckStop(void)
Definition: httplivestream.cpp:794
ENO
#define ENO
This can be appended to the LOG args with "+".
Definition: mythlogging.h:72
myth_system
uint myth_system(const QString &command, uint flags, uint timeout)
Definition: mythsystemlegacy.cpp:501
GENERIC_EXIT_OK
#define GENERIC_EXIT_OK
Exited with no error.
Definition: exitcodes.h:10
HTTPLiveStream::GetCurrentFilename
QString GetCurrentFilename(bool audioOnly=false, bool encoded=false) const
Definition: httplivestream.cpp:201
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:34
HTTPLiveStream::WritePlaylist
bool WritePlaylist(bool audioOnly=false, bool writeEndTag=false)
Definition: httplivestream.cpp:444
DTC::LiveStreamInfo
Definition: liveStreamInfo.h:16
HTTPLiveStream::UpdateSizeInfo
bool UpdateSizeInfo(uint16_t width, uint16_t height, uint16_t srcwidth, uint16_t srcheight)
Definition: httplivestream.cpp:527
MythDate::as_utc
QDateTime as_utc(const QDateTime &old_dt)
Returns copy of QDateTime with TimeSpec set to UTC.
Definition: mythdate.cpp:23
MythTimer
A QElapsedTimer based timer to replace use of QTime as a timer.
Definition: mythtimer.h:14
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:340
HTTPLiveStream::m_height
uint16_t m_height
Definition: httplivestream.h:103
MSqlQuery::value
QVariant value(int i) const
Definition: mythdbcon.h:198
arg
arg(title).arg(filename).arg(doDelete))
DTC::LiveStreamInfoList
Definition: liveStreamInfoList.h:15
MSqlQuery::exec
bool exec(void)
Wrap QSqlQuery::exec() so we can display SQL.
Definition: mythdbcon.cpp:603
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:23
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:96
httplivestream.h
HTTPLiveStream::m_startSegment
uint16_t m_startSegment
Definition: httplivestream.h:99
HTTPLiveStream::UpdateStatus
bool UpdateStatus(HTTPLiveStreamStatus status)
Definition: httplivestream.cpp:578
build_compdb.file
file
Definition: build_compdb.py:55
mythdirs.h
HTTPLiveStream::m_outFile
QString m_outFile
Definition: httplivestream.h:92
MythDate::current
QDateTime current(bool stripped)
Returns current Date and Time in UTC.
Definition: mythdate.cpp:10
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:1010
HTTPLiveStream::GetDBStatus
HTTPLiveStreamStatus GetDBStatus(void) const
Definition: httplivestream.cpp:772
HTTPLiveStream::StatusToString
static QString StatusToString(HTTPLiveStreamStatus status)
Definition: httplivestream.cpp:666
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:180
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:502
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:166
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:983
HTTPLiveStreamThread::HTTPLiveStreamThread
HTTPLiveStreamThread(int streamid)
Constructor for creating a SystemEventThread.
Definition: httplivestream.cpp:65
SLOC
#define SLOC
Definition: httplivestream.cpp:49
MSqlQuery::InitCon
static MSqlQueryInfo InitCon(ConnectionReuse _reuse=kNormalConnection)
Only use this in combination with MSqlQuery constructor.
Definition: mythdbcon.cpp:535
HTTPLiveStream::GetLiveStreamInfo
DTC::LiveStreamInfo * GetLiveStreamInfo(DTC::LiveStreamInfo *info=nullptr)
Definition: httplivestream.cpp:962
HTTPLiveStream::WriteMetaPlaylist
bool WriteMetaPlaylist(void)
Definition: httplivestream.cpp:393
HTTPLiveStream::AddStream
int AddStream(void)
Definition: httplivestream.cpp:206
HTTPLiveStream::UpdateStatusMessage
bool UpdateStatusMessage(const QString &message)
Definition: httplivestream.cpp:616
HTTPLiveStreamStatus
HTTPLiveStreamStatus
Definition: httplivestream.h:10
HTTPLiveStream::StartStream
DTC::LiveStreamInfo * StartStream(void)
Definition: httplivestream.cpp:816
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:998
HTTPLiveStream::SetOutputVars
void SetOutputVars(void)
Definition: httplivestream.cpp:737
filename
QString filename
Definition: mythplugins/mytharchive/mytharchivehelper/main.cpp:637
StorageGroup::GetFirstDir
QString GetFirstDir(bool appendSlash=false) const
Definition: storagegroup.cpp:189
HTTPLiveStream::GetMetaPlaylistName
QString GetMetaPlaylistName(void) const
Definition: httplivestream.cpp:384
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:431
HTTPLiveStream::LoadFromDB
bool LoadFromDB(void)
Definition: httplivestream.cpp:682
uint
unsigned int uint
Definition: compat.h:140
HTTPLiveStream::AddSegment
bool AddSegment(void)
Definition: httplivestream.cpp:305
gCoreContext
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
Definition: mythcorecontext.cpp:56
HTTPLiveStream::StopStream
static DTC::LiveStreamInfo * StopStream(int id)
Definition: httplivestream.cpp:913
kHLSStatusStopping
@ kHLSStatusStopping
Definition: httplivestream.h:17
HTTPLiveStream::UpdatePercentComplete
bool UpdatePercentComplete(int percent)
Definition: httplivestream.cpp:641
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:841
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
MythTimer::elapsed
int elapsed(void)
Returns milliseconds elapsed since last start() or restart()
Definition: mythtimer.cpp:90
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:23
kHLSStatusErrored
@ kHLSStatusErrored
Definition: httplivestream.h:16
mythcorecontext.h
MythDate
Definition: mythdate.cpp:8
MSqlQuery::bindValue
void bindValue(const QString &placeholder, const QVariant &val)
Add a single binding.
Definition: mythdbcon.cpp:864
LOC
#define LOC
Definition: httplivestream.cpp:47
HTTPLiveStream::m_maxSegments
uint16_t m_maxSegments
Definition: httplivestream.h:97
HTTPLiveStream::WriteHTML
bool WriteHTML(void)
Definition: httplivestream.cpp:349
GetAppBinDir
QString GetAppBinDir(void)
Definition: mythdirs.cpp:221
StorageGroup
Definition: storagegroup.h:12
HTTPLiveStream::~HTTPLiveStream
~HTTPLiveStream()
Definition: httplivestream.cpp:156
mythtimer.h
HTTPLiveStream::m_sourceFile
QString m_sourceFile
Definition: httplivestream.h:85
MythCoreContext::GetHostName
QString GetHostName(void)
Definition: mythcorecontext.cpp:855
uint16_t
unsigned short uint16_t
Definition: iso6937tables.h:3
logPropagateArgs
QString logPropagateArgs
Definition: logging.cpp:87
exitcodes.h
HTTPLiveStream::m_created
QDateTime m_created
Definition: httplivestream.h:110
HTTPLiveStream::m_statusMessage
QString m_statusMessage
Definition: httplivestream.h:115
MThreadPool::globalInstance
static MThreadPool * globalInstance(void)
Definition: mthreadpool.cpp:306
kHLSStatusRunning
@ kHLSStatusRunning
Definition: httplivestream.h:14
query
MSqlQuery query(MSqlQuery::InitCon())
MThreadPool::startReserved
void startReserved(QRunnable *runnable, const QString &debugName, int waitForAvailMS=0)
Definition: mthreadpool.cpp:360
HTTPLiveStreamThread::m_streamID
int m_streamID
Definition: httplivestream.cpp:92
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:73
HTTPLiveStream::m_width
uint16_t m_width
Definition: httplivestream.h:104
MythCoreContext::GetSetting
QString GetSetting(const QString &key, const QString &defaultval="")
Definition: mythcorecontext.cpp:915
MSqlQuery::prepare
bool prepare(const QString &query)
QSqlQuery::prepare() is not thread safe in Qt <= 3.3.2.
Definition: mythdbcon.cpp:808