MythTV  0.27pre
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Groups Pages
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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
20  */
21 
22 #include <stdio.h>
23 
24 #include <QDir>
25 #include <QFile>
26 #include <QFileInfo>
27 #include <QIODevice>
28 #include <QRunnable>
29 #include <QUrl>
30 
31 #include "mythcorecontext.h"
32 #include "mythdate.h"
33 #include "mythdirs.h"
34 #include "mythtimer.h"
35 #include "mthreadpool.h"
36 #include "mythsystem.h"
37 #include "exitcodes.h"
38 #include "mythlogging.h"
39 #include "storagegroup.h"
40 #include "httplivestream.h"
41 
42 #define LOC QString("HLS(%1): ").arg(m_sourceFile)
43 #define LOC_ERR QString("HLS(%1) Error: ").arg(m_sourceFile)
44 #define SLOC QString("HLS(): ")
45 #define SLOC_ERR QString("HLS() Error: ")
46 
53 class HTTPLiveStreamThread : public QRunnable
54 {
55  public:
62  HTTPLiveStreamThread(int streamid)
63  : m_streamID(streamid) {}
64 
70  void run(void)
71  {
73 
74  QString command = GetInstallPrefix() +
75  QString("/bin/mythtranscode --hls --hlsstreamid %1")
77 
78  uint result = myth_system(command, flags);
79 
80  if (result != GENERIC_EXIT_OK)
81  LOG(VB_GENERAL, LOG_WARNING, SLOC +
82  QString("Command '%1' returned %2")
83  .arg(command).arg(result));
84  }
85 
86  private:
88 };
89 
90 
92  uint32_t bitrate, uint32_t abitrate,
93  uint16_t maxSegments, uint16_t segmentSize,
94  uint32_t aobitrate, int32_t srate)
95  : m_writing(false),
96  m_streamid(-1), m_sourceFile(srcFile),
97  m_sourceWidth(0), m_sourceHeight(0),
98  m_segmentSize(segmentSize), m_maxSegments(maxSegments),
99  m_segmentCount(0), m_startSegment(0),
100  m_curSegment(0),
101  m_height(height), m_width(width),
102  m_bitrate(bitrate),
103  m_audioBitrate(abitrate), m_audioOnlyBitrate(aobitrate),
104  m_sampleRate(srate),
105  m_created(MythDate::current()),
106  m_lastModified(MythDate::current()),
107  m_percentComplete(0),
108  m_status(kHLSStatusUndefined)
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 = 10;
121 
122  if (m_audioOnlyBitrate == 0)
123  m_audioOnlyBitrate = 32000;
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";
135  m_relativeURL = m_httpPrefixRel + 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_writing(false),
153  m_streamid(streamid)
154 {
155  LoadFromDB();
156 }
157 
159 {
160  if (m_writing)
161  {
162  WritePlaylist(false, true);
163  if (m_audioOnlyBitrate)
164  WritePlaylist(true, true);
165  }
166 }
167 
169 {
170  if ((m_streamid == -1) ||
171  (!WriteHTML()) ||
172  (!WriteMetaPlaylist()) ||
174  (!UpdateStatusMessage("Transcode Starting")))
175  return false;
176 
177  m_writing = true;
178 
179  return true;
180 }
181 
182 QString HTTPLiveStream::GetFilename(uint16_t segmentNumber, bool fileOnly,
183  bool audioOnly, bool encoded) const
184 {
185  QString filename;
186 
187  if (encoded)
188  filename = audioOnly ? m_audioOutFileEncoded : m_outFileEncoded;
189  else
190  filename = audioOnly ? m_audioOutFile : m_outFile;
191 
192  filename += ".%1.ts";
193 
194  if (!fileOnly)
195  filename = m_outDir + "/" + filename;
196 
197  if (segmentNumber)
198  return filename.arg(segmentNumber, 6, 10, QChar('0'));
199 
200  return filename.arg(1, 6, 10, QChar('0'));
201 }
202 
203 QString HTTPLiveStream::GetCurrentFilename(bool audioOnly, bool encoded) const
204 {
205  return GetFilename(m_curSegment, false, audioOnly, encoded);
206 }
207 
209 {
211 
212  QString tmpBase = QString("");
213  QString tmpFullURL = QString("");
214  QString tmpRelURL = QString("");
215 
216  if (m_width && m_height)
217  {
218  tmpBase = m_outBase;
219  tmpFullURL = m_fullURL;
220  tmpRelURL = m_relativeURL;
221  }
222 
223  MSqlQuery query(MSqlQuery::InitCon());
224  query.prepare(
225  "INSERT INTO livestream "
226  " ( width, height, bitrate, audiobitrate, segmentsize, "
227  " maxsegments, startsegment, currentsegment, segmentcount, "
228  " percentcomplete, created, lastmodified, relativeurl, "
229  " fullurl, status, statusmessage, sourcefile, sourcehost, "
230  " sourcewidth, sourceheight, outdir, outbase, "
231  " audioonlybitrate, samplerate ) "
232  "VALUES "
233  " ( :WIDTH, :HEIGHT, :BITRATE, :AUDIOBITRATE, :SEGMENTSIZE, "
234  " :MAXSEGMENTS, 0, 0, 0, "
235  " 0, :CREATED, :LASTMODIFIED, :RELATIVEURL, "
236  " :FULLURL, :STATUS, :STATUSMESSAGE, :SOURCEFILE, :SOURCEHOST, "
237  " :SOURCEWIDTH, :SOURCEHEIGHT, :OUTDIR, :OUTBASE, "
238  " :AUDIOONLYBITRATE, :SAMPLERATE ) ");
239  query.bindValue(":WIDTH", m_width);
240  query.bindValue(":HEIGHT", m_height);
241  query.bindValue(":BITRATE", m_bitrate);
242  query.bindValue(":AUDIOBITRATE", m_audioBitrate);
243  query.bindValue(":SEGMENTSIZE", m_segmentSize);
244  query.bindValue(":MAXSEGMENTS", m_maxSegments);
245  query.bindValue(":CREATED", m_created);
246  query.bindValue(":LASTMODIFIED", m_lastModified);
247  query.bindValue(":RELATIVEURL", tmpRelURL);
248  query.bindValue(":FULLURL", tmpFullURL);
249  query.bindValue(":STATUS", (int)m_status);
250  query.bindValue(":STATUSMESSAGE",
251  QString("Waiting for mythtranscode startup."));
252  query.bindValue(":SOURCEFILE", m_sourceFile);
253  query.bindValue(":SOURCEHOST", gCoreContext->GetHostName());
254  query.bindValue(":SOURCEWIDTH", 0);
255  query.bindValue(":SOURCEHEIGHT", 0);
256  query.bindValue(":OUTDIR", m_outDir);
257  query.bindValue(":OUTBASE", tmpBase);
258  query.bindValue(":AUDIOONLYBITRATE", m_audioOnlyBitrate);
259  query.bindValue(":SAMPLERATE", m_sampleRate);
260 
261  if (!query.exec())
262  {
263  LOG(VB_GENERAL, LOG_ERR, LOC + "LiveStream insert failed.");
264  return -1;
265  }
266 
267  if (!query.exec("SELECT LAST_INSERT_ID()") || !query.next())
268  {
269  LOG(VB_GENERAL, LOG_ERR, LOC + "Unable to query LiveStream streamid.");
270  return -1;
271  }
272 
273  m_streamid = query.value(0).toUInt();
274 
275  return m_streamid;
276 }
277 
279 {
280  if (m_streamid == -1)
281  return false;
282 
283  MSqlQuery query(MSqlQuery::InitCon());
284 
285  ++m_curSegment;
286  ++m_segmentCount;
287 
288  if (!m_startSegment)
290 
291  if ((m_maxSegments) &&
293  {
294  QString thisFile = GetFilename(m_startSegment);
295 
296  if (!QFile::remove(thisFile))
297  LOG(VB_GENERAL, LOG_ERR, LOC +
298  QString("Unable to delete %1.").arg(thisFile));
299 
300  ++m_startSegment;
301  --m_segmentCount;
302  }
303 
304  SaveSegmentInfo();
305  WritePlaylist(false);
306 
307  if (m_audioOnlyBitrate)
308  WritePlaylist(true);
309 
310  return true;
311 }
312 
314 {
315  if (m_streamid == -1)
316  return QString();
317 
318  QString outFile = m_outDir + "/" + m_outBase + ".html";
319  return outFile;
320 }
321 
323 {
324  if (m_streamid == -1)
325  return false;
326 
327  QString outFile = m_outDir + "/" + m_outBase + ".html";
328  QFile file(outFile);
329 
330  if (!file.open(QIODevice::WriteOnly))
331  {
332  LOG(VB_RECORD, LOG_ERR, QString("Error opening %1").arg(outFile));
333  return false;
334  }
335 
336  file.write(QString(
337  "<html>\n"
338  " <head>\n"
339  " <title>%1</title>\n"
340  " </head>\n"
341  " <body style='background-color:#FFFFFF;'>\n"
342  " <center>\n"
343  " <video controls>\n"
344  " <source src='%2.m3u8' />\n"
345  " </video>\n"
346  " </center>\n"
347  " </body>\n"
348  "</html>\n"
349  ).arg(m_sourceFile).arg(m_outBaseEncoded)
350  .toLatin1());
351 
352  file.close();
353 
354  return true;
355 }
356 
358 {
359  if (m_streamid == -1)
360  return QString();
361 
362  QString outFile = m_outDir + "/" + m_outBase + ".m3u8";
363  return outFile;
364 }
365 
367 {
368  if (m_streamid == -1)
369  return false;
370 
371  QString outFile = GetMetaPlaylistName();
372  QFile file(outFile);
373 
374  if (!file.open(QIODevice::WriteOnly))
375  {
376  LOG(VB_RECORD, LOG_ERR, QString("Error opening %1").arg(outFile));
377  return false;
378  }
379 
380  file.write(QString(
381  "#EXTM3U\n"
382  "#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=%1\n"
383  "%2.m3u8\n"
384  ).arg((int)((m_bitrate + m_audioBitrate) * 1.1))
385  .arg(m_outFileEncoded).toLatin1());
386 
387  if (m_audioOnlyBitrate)
388  {
389  file.write(QString(
390  "#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=%1\n"
391  "%2.m3u8\n"
392  ).arg((int)((m_audioOnlyBitrate) * 1.1))
393  .arg(m_audioOutFileEncoded).toLatin1());
394  }
395 
396  file.close();
397 
398  return true;
399 }
400 
401 QString HTTPLiveStream::GetPlaylistName(bool audioOnly) const
402 {
403  if (m_streamid == -1)
404  return QString();
405 
406  if (audioOnly && m_audioOutFile.isEmpty())
407  return QString();
408 
409  QString base = audioOnly ? m_audioOutFile : m_outFile;
410  QString outFile = m_outDir + "/" + base + ".m3u8";
411  return outFile;
412 }
413 
414 bool HTTPLiveStream::WritePlaylist(bool audioOnly, bool writeEndTag)
415 {
416  if (m_streamid == -1)
417  return false;
418 
419  QString outFile = GetPlaylistName(audioOnly);
420  QString tmpFile = outFile + ".tmp";
421 
422  QFile file(tmpFile);
423 
424  if (!file.open(QIODevice::WriteOnly))
425  {
426  LOG(VB_RECORD, LOG_ERR, QString("Error opening %1").arg(tmpFile));
427  return false;
428  }
429 
430  file.write(QString(
431  "#EXTM3U\n"
432  "#EXT-X-TARGETDURATION:%1\n"
433  "#EXT-X-MEDIA-SEQUENCE:%2\n"
434  ).arg(m_segmentSize).arg(m_startSegment).toLatin1());
435 
436  if (writeEndTag)
437  file.write("#EXT-X-ENDLIST\n");
438 
439  // Don't write out the current segment until the end
440  unsigned int tmpSegCount = m_segmentCount - 1;
441  unsigned int i = 0;
442  unsigned int segmentid = m_startSegment;
443 
444  if (writeEndTag)
445  ++tmpSegCount;
446 
447  while (i < tmpSegCount)
448  {
449  file.write(QString(
450  "#EXTINF:%1,\n"
451  "%2\n"
452  ).arg(m_segmentSize)
453  .arg(GetFilename(segmentid + i, true, audioOnly, true)).toLatin1());
454 
455  ++i;
456  }
457 
458  file.close();
459 
460  rename(tmpFile.toLatin1().constData(), outFile.toLatin1().constData());
461 
462  return true;
463 }
464 
466 {
467  if (m_streamid == -1)
468  return false;
469 
470  MSqlQuery query(MSqlQuery::InitCon());
471  query.prepare(
472  "UPDATE livestream "
473  "SET startsegment = :START, currentsegment = :CURRENT, "
474  " segmentcount = :COUNT "
475  "WHERE id = :STREAMID; ");
476  query.bindValue(":START", m_startSegment);
477  query.bindValue(":CURRENT", m_curSegment);
478  query.bindValue(":COUNT", m_segmentCount);
479  query.bindValue(":STREAMID", m_streamid);
480 
481  if (query.exec())
482  return true;
483 
484  LOG(VB_GENERAL, LOG_ERR, LOC +
485  QString("Unable to update segment info for streamid %1")
486  .arg(m_streamid));
487  return false;
488 }
489 
491  uint16_t srcwidth, uint16_t srcheight)
492 {
493  if (m_streamid == -1)
494  return false;
495 
496  QFileInfo finfo(m_sourceFile);
497  QString newOutBase = finfo.fileName() +
498  QString(".%1x%2_%3kV_%4kA").arg(width).arg(height)
499  .arg(m_bitrate/1000).arg(m_audioBitrate/1000);
500  QString newFullURL = m_httpPrefix + newOutBase + ".m3u8";
501  QString newRelativeURL = m_httpPrefixRel + newOutBase + ".m3u8";
502 
503  MSqlQuery query(MSqlQuery::InitCon());
504  query.prepare(
505  "UPDATE livestream "
506  "SET width = :WIDTH, height = :HEIGHT, "
507  " sourcewidth = :SRCWIDTH, sourceheight = :SRCHEIGHT, "
508  " fullurl = :FULLURL, relativeurl = :RELATIVEURL, "
509  " outbase = :OUTBASE "
510  "WHERE id = :STREAMID; ");
511  query.bindValue(":WIDTH", width);
512  query.bindValue(":HEIGHT", height);
513  query.bindValue(":SRCWIDTH", srcwidth);
514  query.bindValue(":SRCHEIGHT", srcheight);
515  query.bindValue(":FULLURL", newFullURL);
516  query.bindValue(":RELATIVEURL", newRelativeURL);
517  query.bindValue(":OUTBASE", newOutBase);
518  query.bindValue(":STREAMID", m_streamid);
519 
520  if (!query.exec())
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 
528  m_width = width;
529  m_height = height;
530  m_sourceWidth = srcwidth;
531  m_sourceHeight = srcheight;
532  m_outBase = newOutBase;
533  m_fullURL = newFullURL;
534  m_relativeURL = newRelativeURL;
535 
536  SetOutputVars();
537 
538  return true;
539 }
540 
542 {
543  if (m_streamid == -1)
544  return false;
545 
546  if ((m_status == kHLSStatusStopping) &&
547  (status == kHLSStatusRunning))
548  {
549  LOG(VB_RECORD, LOG_DEBUG, LOC + "Attempted to switch from "
550  "Stopping to Running State");
551  return false;
552  }
553 
554  QString statusStr = StatusToString(status);
555 
556  m_status = status;
557 
558  MSqlQuery query(MSqlQuery::InitCon());
559  query.prepare(
560  "UPDATE livestream "
561  "SET status = :STATUS "
562  "WHERE id = :STREAMID; ");
563  query.bindValue(":STATUS", (int)status);
564  query.bindValue(":STREAMID", m_streamid);
565 
566  if (query.exec())
567  return true;
568 
569  LOG(VB_GENERAL, LOG_ERR, LOC +
570  QString("Unable to update status for streamid %1").arg(m_streamid));
571  return false;
572 }
573 
575 {
576  if (m_streamid == -1)
577  return false;
578 
579  MSqlQuery query(MSqlQuery::InitCon());
580  query.prepare(
581  "UPDATE livestream "
582  "SET statusmessage = :MESSAGE "
583  "WHERE id = :STREAMID; ");
584  query.bindValue(":MESSAGE", message);
585  query.bindValue(":STREAMID", m_streamid);
586 
587  if (query.exec())
588  {
589  m_statusMessage = message;
590  return true;
591  }
592 
593  LOG(VB_GENERAL, LOG_ERR, LOC +
594  QString("Unable to update status message for streamid %1")
595  .arg(m_streamid));
596  return false;
597 }
598 
600 {
601  if (m_streamid == -1)
602  return false;
603 
604  MSqlQuery query(MSqlQuery::InitCon());
605  query.prepare(
606  "UPDATE livestream "
607  "SET percentcomplete = :PERCENT "
608  "WHERE id = :STREAMID; ");
609  query.bindValue(":PERCENT", percent);
610  query.bindValue(":STREAMID", m_streamid);
611 
612  if (query.exec())
613  {
614  m_percentComplete = percent;
615  return true;
616  }
617 
618  LOG(VB_GENERAL, LOG_ERR, LOC +
619  QString("Unable to update percent complete for streamid %1")
620  .arg(m_streamid));
621  return false;
622 }
623 
625 {
626  switch (m_status) {
627  case kHLSStatusUndefined : return QString("Undefined");
628  case kHLSStatusQueued : return QString("Queued");
629  case kHLSStatusStarting : return QString("Starting");
630  case kHLSStatusRunning : return QString("Running");
631  case kHLSStatusCompleted : return QString("Completed");
632  case kHLSStatusErrored : return QString("Errored");
633  case kHLSStatusStopping : return QString("Stopping");
634  case kHLSStatusStopped : return QString("Stopped");
635  };
636 
637  return QString("Unknown status value");
638 }
639 
641 {
642  if (m_streamid == -1)
643  return false;
644 
645  MSqlQuery query(MSqlQuery::InitCon());
646  query.prepare(
647  "SELECT width, height, bitrate, audiobitrate, segmentsize, "
648  " maxsegments, startsegment, currentsegment, segmentcount, "
649  " percentcomplete, created, lastmodified, relativeurl, "
650  " fullurl, status, statusmessage, sourcefile, sourcehost, "
651  " sourcewidth, sourceheight, outdir, outbase, audioonlybitrate, "
652  " samplerate "
653  "FROM livestream "
654  "WHERE id = :STREAMID; ");
655  query.bindValue(":STREAMID", m_streamid);
656 
657  if (!query.exec() || !query.next())
658  {
659  LOG(VB_GENERAL, LOG_ERR, LOC +
660  QString("Unable to query DB info for stream %1")
661  .arg(m_streamid));
662  return false;
663  }
664 
665  m_width = query.value(0).toUInt();
666  m_height = query.value(1).toUInt();
667  m_bitrate = query.value(2).toUInt();
668  m_audioBitrate = query.value(3).toUInt();
669  m_segmentSize = query.value(4).toUInt();
670  m_maxSegments = query.value(5).toUInt();
671  m_startSegment = query.value(6).toUInt();
672  m_curSegment = query.value(7).toUInt();
673  m_segmentCount = query.value(8).toUInt();
674  m_percentComplete = query.value(9).toUInt();
675  m_created = MythDate::as_utc(query.value(10).toDateTime());
676  m_lastModified = MythDate::as_utc(query.value(11).toDateTime());
677  m_relativeURL = query.value(12).toString();
678  m_fullURL = query.value(13).toString();
679  m_status = (HTTPLiveStreamStatus)(query.value(14).toInt());
680  m_statusMessage = query.value(15).toString();
681  m_sourceFile = query.value(16).toString();
682  m_sourceHost = query.value(17).toString();
683  m_sourceWidth = query.value(18).toUInt();
684  m_sourceHeight = query.value(19).toUInt();
685  m_outDir = query.value(20).toString();
686  m_outBase = query.value(21).toString();
687  m_audioOnlyBitrate = query.value(22).toUInt();
688  m_sampleRate = query.value(23).toUInt();
689 
690  SetOutputVars();
691 
692  return true;
693 }
694 
696 {
697  m_outBaseEncoded = QString(QUrl::toPercentEncoding(m_outBase, "", " "));
698 
699  m_outFile = m_outBase + ".av";
701 
702  if (m_audioOnlyBitrate)
703  {
705  QString(".ao_%1kA").arg(m_audioOnlyBitrate/1000);
707  QString(".ao_%1kA").arg(m_audioOnlyBitrate/1000);
708  }
709 
710  m_httpPrefix = gCoreContext->GetSetting("HTTPLiveStreamPrefix", QString(
711  "http://%1:%2/StorageGroup/Streaming/")
712  .arg(gCoreContext->GetSetting("MasterServerIP"))
713  .arg(gCoreContext->GetSetting("BackendStatusPort")));
714 
715  if (!m_httpPrefix.endsWith("/"))
716  m_httpPrefix.append("/");
717 
718  if (!gCoreContext->GetSetting("HTTPLiveStreamPrefixRel").isEmpty())
719  {
720  m_httpPrefixRel = gCoreContext->GetSetting("HTTPLiveStreamPrefixRel");
721  if (!m_httpPrefix.endsWith("/"))
722  m_httpPrefix.append("/");
723  }
724  else if (m_httpPrefix.contains("/StorageGroup/Streaming/"))
725  m_httpPrefixRel = "/StorageGroup/Streaming/";
726  else
727  m_httpPrefixRel = "";
728 }
729 
731 {
732  if (m_streamid == -1)
733  return kHLSStatusUndefined;
734 
735  MSqlQuery query(MSqlQuery::InitCon());
736  query.prepare(
737  "SELECT status FROM livestream "
738  "WHERE id = :STREAMID; ");
739  query.bindValue(":STREAMID", m_streamid);
740 
741  if (!query.exec() || !query.next())
742  {
743  LOG(VB_GENERAL, LOG_ERR, LOC +
744  QString("Unable to check stop status for stream %1")
745  .arg(m_streamid));
746  return kHLSStatusUndefined;
747  }
748 
749  return (HTTPLiveStreamStatus)query.value(0).toInt();
750 }
751 
753 {
754  if (m_streamid == -1)
755  return false;
756 
757  MSqlQuery query(MSqlQuery::InitCon());
758  query.prepare(
759  "SELECT status FROM livestream "
760  "WHERE id = :STREAMID; ");
761  query.bindValue(":STREAMID", m_streamid);
762 
763  if (!query.exec() || !query.next())
764  {
765  LOG(VB_GENERAL, LOG_ERR, LOC +
766  QString("Unable to check stop status for stream %1")
767  .arg(m_streamid));
768  return false;
769  }
770 
771  if (query.value(0).toInt() == (int)kHLSStatusStopping)
772  return true;
773 
774  return false;
775 }
776 
778 {
779  HTTPLiveStreamThread *streamThread =
782  "HTTPLiveStream");
783  MythTimer statusTimer;
784  int delay = 250000;
785  statusTimer.start();
786 
788  while ((status == kHLSStatusQueued) &&
789  ((statusTimer.elapsed() / 1000) < 30))
790  {
791  delay = (int)(delay * 1.5);
792  usleep(delay);
793 
794  status = GetDBStatus();
795  }
796 
797  return GetLiveStreamInfo();
798 }
799 
801 {
802  MSqlQuery query(MSqlQuery::InitCon());
803  query.prepare(
804  "SELECT startSegment, segmentCount "
805  "FROM livestream "
806  "WHERE id = :STREAMID; ");
807  query.bindValue(":STREAMID", id);
808 
809  if (!query.exec() || !query.next())
810  {
811  LOG(VB_RECORD, LOG_ERR, "Error selecting stream info in RemoveStream");
812  return false;
813  }
814 
815  HTTPLiveStream *hls = new HTTPLiveStream(id);
816 
817  if (hls->GetDBStatus() == kHLSStatusRunning) {
819  }
820 
821  QString thisFile;
822  int startSegment = query.value(0).toInt();
823  int segmentCount = query.value(1).toInt();
824 
825  for (int x = 0; x < segmentCount; ++x)
826  {
827  thisFile = hls->GetFilename(startSegment + x);
828 
829  if (!thisFile.isEmpty() && !QFile::remove(thisFile))
830  LOG(VB_GENERAL, LOG_ERR, SLOC +
831  QString("Unable to delete %1.").arg(thisFile));
832 
833  thisFile = hls->GetFilename(startSegment + x, false, true);
834 
835  if (!thisFile.isEmpty() && !QFile::remove(thisFile))
836  LOG(VB_GENERAL, LOG_ERR, SLOC +
837  QString("Unable to delete %1.").arg(thisFile));
838  }
839 
840  thisFile = hls->GetMetaPlaylistName();
841  if (!thisFile.isEmpty() && !QFile::remove(thisFile))
842  LOG(VB_GENERAL, LOG_ERR, SLOC +
843  QString("Unable to delete %1.").arg(thisFile));
844 
845  thisFile = hls->GetPlaylistName();
846  if (!thisFile.isEmpty() && !QFile::remove(thisFile))
847  LOG(VB_GENERAL, LOG_ERR, SLOC +
848  QString("Unable to delete %1.").arg(thisFile));
849 
850  thisFile = hls->GetPlaylistName(true);
851  if (!thisFile.isEmpty() && !QFile::remove(thisFile))
852  LOG(VB_GENERAL, LOG_ERR, SLOC +
853  QString("Unable to delete %1.").arg(thisFile));
854 
855  thisFile = hls->GetHTMLPageName();
856  if (!thisFile.isEmpty() && !QFile::remove(thisFile))
857  LOG(VB_GENERAL, LOG_ERR, SLOC +
858  QString("Unable to delete %1.").arg(thisFile));
859 
860  query.prepare(
861  "DELETE FROM livestream "
862  "WHERE id = :STREAMID; ");
863  query.bindValue(":STREAMID", id);
864 
865  if (!query.exec())
866  LOG(VB_RECORD, LOG_ERR, "Error deleting stream info in RemoveStream");
867 
868  delete hls;
869  return true;
870 }
871 
873 {
874  MSqlQuery query(MSqlQuery::InitCon());
875  query.prepare(
876  "UPDATE livestream "
877  "SET status = :STATUS "
878  "WHERE id = :STREAMID; ");
879  query.bindValue(":STATUS", (int)kHLSStatusStopping);
880  query.bindValue(":STREAMID", id);
881 
882  if (!query.exec())
883  {
884  LOG(VB_GENERAL, LOG_ERR, SLOC +
885  QString("Unable to remove mark stream stopped for stream %1.")
886  .arg(id));
887  return NULL;
888  }
889 
890  HTTPLiveStream *hls = new HTTPLiveStream(id);
891  if (!hls)
892  return NULL;
893 
894  MythTimer statusTimer;
895  int delay = 250000;
896  statusTimer.start();
897 
898  HTTPLiveStreamStatus status = hls->GetDBStatus();
899  while ((status != kHLSStatusStopped) &&
900  (status != kHLSStatusCompleted) &&
901  (status != kHLSStatusErrored) &&
902  ((statusTimer.elapsed() / 1000) < 30))
903  {
904  delay = (int)(delay * 1.5);
905  usleep(delay);
906 
907  status = hls->GetDBStatus();
908  }
909 
910  hls->LoadFromDB();
911  DTC::LiveStreamInfo *pLiveStreamInfo = hls->GetLiveStreamInfo();
912 
913  delete hls;
914  return pLiveStreamInfo;
915 }
916 
918 // Content Service API helpers
920 
922  DTC::LiveStreamInfo *info)
923 {
924  if (!info)
925  info = new DTC::LiveStreamInfo();
926 
927  info->setId((int)m_streamid);
928  info->setWidth((int)m_width);
929  info->setHeight((int)m_height);
930  info->setBitrate((int)m_bitrate);
931  info->setAudioBitrate((int)m_audioBitrate);
932  info->setSegmentSize((int)m_segmentSize);
933  info->setMaxSegments((int)m_maxSegments);
934  info->setStartSegment((int)m_startSegment);
935  info->setCurrentSegment((int)m_curSegment);
936  info->setSegmentCount((int)m_segmentCount);
937  info->setPercentComplete((int)m_percentComplete);
938  info->setCreated(m_created);
939  info->setLastModified(m_lastModified);
940  info->setRelativeURL(m_relativeURL);
941  info->setFullURL(m_fullURL);
942  info->setStatusStr(StatusToString(m_status));
943  info->setStatusInt((int)m_status);
944  info->setStatusMessage(m_statusMessage);
945  info->setSourceFile(m_sourceFile);
946  info->setSourceHost(m_sourceHost);
947  info->setSourceWidth(m_sourceWidth);
948  info->setSourceHeight(m_sourceHeight);
949  info->setAudioOnlyBitrate((int)m_audioOnlyBitrate);
950 
951  return info;
952 }
953 
955 {
957 
958  QString sql = "SELECT id FROM livestream ";
959 
960  if (!FileName.isEmpty())
961  sql += "WHERE sourcefile LIKE :FILENAME ";
962 
963  sql += "ORDER BY lastmodified DESC;";
964 
965  MSqlQuery query(MSqlQuery::InitCon());
966  query.prepare(sql);
967  if (!FileName.isEmpty())
968  query.bindValue(":FILENAME", QString("%%1%").arg(FileName));
969 
970  if (!query.exec())
971  {
972  LOG(VB_GENERAL, LOG_ERR, SLOC + "Unable to get list of Live Streams");
973  return infoList;
974  }
975 
976  DTC::LiveStreamInfo *info = NULL;
977  HTTPLiveStream *hls = NULL;
978  while (query.next())
979  {
980  hls = new HTTPLiveStream(query.value(0).toUInt());
981  info = infoList->AddNewLiveStreamInfo();
982  hls->GetLiveStreamInfo(info);
983  delete hls;
984  }
985 
986  return infoList;
987 }
988 
989 /* vim: set expandtab tabstop=4 shiftwidth=4: */
990