MythTV  0.26-pre
httplivestream.cpp
Go to the documentation of this file.
00001 /*  -*- Mode: c++ -*-
00002  *
00003  *   Class HTTPLiveStream
00004  *
00005  *   Copyright (C) Chris Pinkham 2011
00006  *
00007  *   This program is free software; you can redistribute it and/or modify
00008  *   it under the terms of the GNU General Public License as published by
00009  *   the Free Software Foundation; either version 2 of the License, or
00010  *   (at your option) any later version.
00011  *
00012  *   This program is distributed in the hope that it will be useful,
00013  *   but WITHOUT ANY WARRANTY; without even the implied warranty of
00014  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00015  *   GNU General Public License for more details.
00016  *
00017  *   You should have received a copy of the GNU General Public License
00018  *   along with this program; if not, write to the Free Software
00019  *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
00020  */
00021 
00022 #include <stdio.h>
00023 
00024 #include <QDir>
00025 #include <QFile>
00026 #include <QFileInfo>
00027 #include <QIODevice>
00028 #include <QRunnable>
00029 #include <QUrl>
00030 
00031 #include "mythcorecontext.h"
00032 #include "mythdirs.h"
00033 #include "mythtimer.h"
00034 #include "mthreadpool.h"
00035 #include "mythsystem.h"
00036 #include "exitcodes.h"
00037 #include "mythlogging.h"
00038 #include "storagegroup.h"
00039 #include "httplivestream.h"
00040 
00041 #define LOC QString("HLS(%1): ").arg(m_sourceFile)
00042 #define LOC_ERR QString("HLS(%1) Error: ").arg(m_sourceFile)
00043 #define SLOC QString("HLS(): ")
00044 #define SLOC_ERR QString("HLS() Error: ")
00045 
00052 class HTTPLiveStreamThread : public QRunnable
00053 {
00054   public:
00061     HTTPLiveStreamThread(int streamid)
00062       : m_streamID(streamid) {}
00063 
00069     void run(void)
00070     {
00071         uint flags = kMSDontBlockInputDevs;
00072 
00073         QString command = GetInstallPrefix() +
00074             QString("/bin/mythtranscode --hls --hlsstreamid %1")
00075                     .arg(m_streamID) + logPropagateArgs;
00076 
00077         uint result = myth_system(command, flags);
00078 
00079         if (result != GENERIC_EXIT_OK)
00080             LOG(VB_GENERAL, LOG_WARNING, SLOC +
00081                 QString("Command '%1' returned %2")
00082                     .arg(command).arg(result));
00083     }
00084 
00085   private:
00086     int m_streamID;
00087 };
00088 
00089 
00090 HTTPLiveStream::HTTPLiveStream(QString srcFile, uint16_t width, uint16_t height,
00091                                uint32_t bitrate, uint32_t abitrate,
00092                                uint16_t maxSegments, uint16_t segmentSize,
00093                                uint32_t aobitrate, uint16_t srate)
00094   : m_writing(false),
00095     m_streamid(-1),              m_sourceFile(srcFile),
00096     m_sourceWidth(0),            m_sourceHeight(0),
00097     m_segmentSize(segmentSize),  m_maxSegments(maxSegments),
00098     m_segmentCount(0),           m_startSegment(0),
00099     m_curSegment(0),
00100     m_height(height),            m_width(width),
00101     m_bitrate(bitrate),
00102     m_audioBitrate(abitrate),    m_audioOnlyBitrate(aobitrate),
00103     m_sampleRate(srate),
00104     m_created(QDateTime::currentDateTime()),
00105     m_lastModified(QDateTime::currentDateTime()),
00106     m_percentComplete(0),
00107     m_status(kHLSStatusUndefined)
00108 {
00109     if ((m_width == 0) && (m_height == 0))
00110         m_width = 640;
00111 
00112     if (m_bitrate == 0)
00113         m_bitrate = 800000;
00114 
00115     if (m_audioBitrate == 0)
00116         m_audioBitrate = 64000;
00117 
00118     if (m_segmentSize == 0)
00119         m_segmentSize = 10;
00120 
00121     if (m_audioOnlyBitrate == 0)
00122         m_audioOnlyBitrate = 32000;
00123 
00124     m_sourceHost = gCoreContext->GetHostName();
00125 
00126     QFileInfo finfo(m_sourceFile);
00127     m_outBase = finfo.fileName() +
00128         QString(".%1x%2_%3kV_%4kA").arg(m_width).arg(m_height)
00129                 .arg(m_bitrate/1000).arg(m_audioBitrate/1000);
00130 
00131     SetOutputVars();
00132 
00133     m_fullURL     = m_httpPrefix + m_outBase + ".m3u8";
00134     m_relativeURL = m_httpPrefixRel + m_outBase + ".m3u8";
00135 
00136     StorageGroup sgroup("Streaming", gCoreContext->GetHostName());
00137     m_outDir = sgroup.GetFirstDir();
00138     QDir outDir(m_outDir);
00139 
00140     if (!outDir.exists() && !outDir.mkdir(m_outDir))
00141     {
00142         LOG(VB_RECORD, LOG_ERR, "Unable to create HTTP Live Stream output "
00143             "directory, Live Stream will not be created");
00144         return;
00145     }
00146 
00147     AddStream();
00148 }
00149 
00150 HTTPLiveStream::HTTPLiveStream(int streamid)
00151   : m_writing(false),
00152     m_streamid(streamid)
00153 {
00154     LoadFromDB();
00155 }
00156 
00157 HTTPLiveStream::~HTTPLiveStream()
00158 {
00159     if (m_writing)
00160     {
00161         WritePlaylist(false, true);
00162         if (m_audioOnlyBitrate)
00163             WritePlaylist(true, true);
00164     }
00165 }
00166 
00167 bool HTTPLiveStream::InitForWrite(void)
00168 {
00169     if ((m_streamid == -1) ||
00170         (!WriteHTML()) ||
00171         (!WriteMetaPlaylist()) ||
00172         (!UpdateStatus(kHLSStatusStarting)) ||
00173         (!UpdateStatusMessage("Transcode Starting")))
00174         return false;
00175 
00176     m_writing = true;
00177 
00178     return true;
00179 }
00180 
00181 QString HTTPLiveStream::GetFilename(uint16_t segmentNumber, bool fileOnly,
00182                                     bool audioOnly, bool encoded) const
00183 {
00184     QString filename;
00185 
00186     if (encoded)
00187         filename = audioOnly ? m_audioOutFileEncoded : m_outFileEncoded;
00188     else
00189         filename = audioOnly ? m_audioOutFile : m_outFile;
00190 
00191     filename += ".%1.ts";
00192 
00193     if (!fileOnly)
00194         filename = m_outDir + "/" + filename;
00195 
00196     if (segmentNumber)
00197         return filename.arg(segmentNumber, 6, 10, QChar('0'));
00198 
00199     return filename.arg(1, 6, 10, QChar('0'));
00200 }
00201 
00202 QString HTTPLiveStream::GetCurrentFilename(bool audioOnly, bool encoded) const
00203 {
00204     return GetFilename(m_curSegment, false, audioOnly, encoded);
00205 }
00206 
00207 int HTTPLiveStream::AddStream(void)
00208 {
00209     m_status = kHLSStatusQueued;
00210 
00211     QString tmpBase = QString("");
00212     QString tmpFullURL = QString("");
00213     QString tmpRelURL = QString("");
00214 
00215     if (m_width && m_height)
00216     {
00217         tmpBase = m_outBase;
00218         tmpFullURL = m_fullURL;
00219         tmpRelURL = m_relativeURL;
00220     }
00221 
00222     MSqlQuery query(MSqlQuery::InitCon());
00223     query.prepare(
00224         "INSERT INTO livestream "
00225         "    ( width, height, bitrate, audiobitrate, segmentsize, "
00226         "      maxsegments, startsegment, currentsegment, segmentcount, "
00227         "      percentcomplete, created, lastmodified, relativeurl, "
00228         "      fullurl, status, statusmessage, sourcefile, sourcehost, "
00229         "      sourcewidth, sourceheight, outdir, outbase, "
00230         "      audioonlybitrate, samplerate ) "
00231         "VALUES "
00232         "    ( :WIDTH, :HEIGHT, :BITRATE, :AUDIOBITRATE, :SEGMENTSIZE, "
00233         "      :MAXSEGMENTS, 0, 0, 0, "
00234         "      0, :CREATED, :LASTMODIFIED, :RELATIVEURL, "
00235         "      :FULLURL, :STATUS, :STATUSMESSAGE, :SOURCEFILE, :SOURCEHOST, "
00236         "      :SOURCEWIDTH, :SOURCEHEIGHT, :OUTDIR, :OUTBASE, "
00237         "      :AUDIOONLYBITRATE, :SAMPLERATE ) ");
00238     query.bindValue(":WIDTH", m_width);
00239     query.bindValue(":HEIGHT", m_height);
00240     query.bindValue(":BITRATE", m_bitrate);
00241     query.bindValue(":AUDIOBITRATE", m_audioBitrate);
00242     query.bindValue(":SEGMENTSIZE", m_segmentSize);
00243     query.bindValue(":MAXSEGMENTS", m_maxSegments);
00244     query.bindValue(":CREATED", m_created);
00245     query.bindValue(":LASTMODIFIED", m_lastModified);
00246     query.bindValue(":RELATIVEURL", tmpRelURL);
00247     query.bindValue(":FULLURL", tmpFullURL);
00248     query.bindValue(":STATUS", (int)m_status);
00249     query.bindValue(":STATUSMESSAGE",
00250         QString("Waiting for mythtranscode startup."));
00251     query.bindValue(":SOURCEFILE", m_sourceFile);
00252     query.bindValue(":SOURCEHOST", gCoreContext->GetHostName());
00253     query.bindValue(":SOURCEWIDTH", 0);
00254     query.bindValue(":SOURCEHEIGHT", 0);
00255     query.bindValue(":OUTDIR", m_outDir);
00256     query.bindValue(":OUTBASE", tmpBase);
00257     query.bindValue(":AUDIOONLYBITRATE", m_audioOnlyBitrate);
00258     query.bindValue(":SAMPLERATE", m_sampleRate);
00259 
00260     if (!query.exec())
00261     {
00262         LOG(VB_GENERAL, LOG_ERR, LOC + "LiveStream insert failed.");
00263         return -1;
00264     }
00265 
00266     if (!query.exec("SELECT LAST_INSERT_ID()") || !query.next())
00267     {
00268         LOG(VB_GENERAL, LOG_ERR, LOC + "Unable to query LiveStream streamid.");
00269         return -1;
00270     }
00271 
00272     m_streamid = query.value(0).toUInt();
00273 
00274     return m_streamid;
00275 }
00276 
00277 bool HTTPLiveStream::AddSegment(void)
00278 {
00279     if (m_streamid == -1)
00280         return false;
00281 
00282     MSqlQuery query(MSqlQuery::InitCon());
00283 
00284     ++m_curSegment;
00285     ++m_segmentCount;
00286 
00287     if (!m_startSegment)
00288         m_startSegment = m_curSegment;
00289 
00290     if ((m_maxSegments) &&
00291         (m_segmentCount > (uint16_t)(m_maxSegments + 1)))
00292     {
00293         QString thisFile = GetFilename(m_startSegment);
00294 
00295         if (!QFile::remove(thisFile))
00296             LOG(VB_GENERAL, LOG_ERR, LOC +
00297                 QString("Unable to delete %1.").arg(thisFile));
00298 
00299         ++m_startSegment;
00300         --m_segmentCount;
00301     }
00302 
00303     SaveSegmentInfo();
00304     WritePlaylist(false);
00305 
00306     if (m_audioOnlyBitrate)
00307         WritePlaylist(true);
00308 
00309     return true;
00310 }
00311 
00312 QString HTTPLiveStream::GetHTMLPageName(void) const
00313 {
00314     if (m_streamid == -1)
00315         return QString();
00316 
00317     QString outFile = m_outDir + "/" + m_outBase + ".html";
00318     return outFile;
00319 }
00320 
00321 bool HTTPLiveStream::WriteHTML(void)
00322 {
00323     if (m_streamid == -1)
00324         return false;
00325 
00326     QString outFile = m_outDir + "/" + m_outBase + ".html";
00327     QFile file(outFile);
00328 
00329     if (!file.open(QIODevice::WriteOnly))
00330     {
00331         LOG(VB_RECORD, LOG_ERR, QString("Error opening %1").arg(outFile));
00332         return false;
00333     }
00334 
00335     file.write(QString(
00336         "<html>\n"
00337         "  <head>\n"
00338         "    <title>%1</title>\n"
00339         "  </head>\n"
00340         "  <body style='background-color:#FFFFFF;'>\n"
00341         "    <center>\n"
00342         "      <video controls>\n"
00343         "        <source src='%2.m3u8' />\n"
00344         "      </video>\n"
00345         "    </center>\n"
00346         "  </body>\n"
00347         "</html>\n"
00348         ).arg(m_sourceFile).arg(m_outBaseEncoded)
00349          .toAscii());
00350 
00351     file.close();
00352 
00353     return true;
00354 }
00355 
00356 QString HTTPLiveStream::GetMetaPlaylistName(void) const
00357 {
00358     if (m_streamid == -1)
00359         return QString();
00360 
00361     QString outFile = m_outDir + "/" + m_outBase + ".m3u8";
00362     return outFile;
00363 }
00364 
00365 bool HTTPLiveStream::WriteMetaPlaylist(void)
00366 {
00367     if (m_streamid == -1)
00368         return false;
00369 
00370     QString outFile = GetMetaPlaylistName();
00371     QFile file(outFile);
00372 
00373     if (!file.open(QIODevice::WriteOnly))
00374     {
00375         LOG(VB_RECORD, LOG_ERR, QString("Error opening %1").arg(outFile));
00376         return false;
00377     }
00378 
00379     file.write(QString(
00380         "#EXTM3U\n"
00381         "#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=%1\n"
00382         "%2.m3u8\n"
00383         ).arg((int)((m_bitrate + m_audioBitrate) * 1.1))
00384          .arg(m_outFileEncoded).toAscii());
00385 
00386     if (m_audioOnlyBitrate)
00387     {
00388         file.write(QString(
00389             "#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=%1\n"
00390             "%2.m3u8\n"
00391             ).arg((int)((m_audioOnlyBitrate) * 1.1))
00392              .arg(m_audioOutFileEncoded).toAscii());
00393     }
00394 
00395     file.close();
00396 
00397     return true;
00398 }
00399 
00400 QString HTTPLiveStream::GetPlaylistName(bool audioOnly) const
00401 {
00402     if (m_streamid == -1)
00403         return QString();
00404 
00405     if (audioOnly && m_audioOutFile.isEmpty())
00406         return QString();
00407 
00408     QString base = audioOnly ? m_audioOutFile : m_outFile;
00409     QString outFile = m_outDir + "/" + base + ".m3u8";
00410     return outFile;
00411 }
00412 
00413 bool HTTPLiveStream::WritePlaylist(bool audioOnly, bool writeEndTag)
00414 {
00415     if (m_streamid == -1)
00416         return false;
00417 
00418     QString outFile = GetPlaylistName(audioOnly);
00419     QString tmpFile = outFile + ".tmp";
00420 
00421     QFile file(tmpFile);
00422 
00423     if (!file.open(QIODevice::WriteOnly))
00424     {
00425         LOG(VB_RECORD, LOG_ERR, QString("Error opening %1").arg(tmpFile));
00426         return false;
00427     }
00428 
00429     file.write(QString(
00430         "#EXTM3U\n"
00431         "#EXT-X-TARGETDURATION:%1\n"
00432         "#EXT-X-MEDIA-SEQUENCE:%2\n"
00433         ).arg(m_segmentSize).arg(m_startSegment).toAscii());
00434 
00435     if (writeEndTag)
00436         file.write("#EXT-X-ENDLIST\n");
00437 
00438     // Don't write out the current segment until the end
00439     unsigned int tmpSegCount = m_segmentCount - 1;
00440     unsigned int i = 0;
00441     unsigned int segmentid = m_startSegment;
00442 
00443     if (writeEndTag)
00444         ++tmpSegCount;
00445 
00446     while (i < tmpSegCount)
00447     {
00448         file.write(QString(
00449             "#EXTINF:%1,\n"
00450             "%2\n"
00451             ).arg(m_segmentSize)
00452              .arg(GetFilename(segmentid + i, true, audioOnly, true)).toAscii());
00453 
00454         ++i;
00455     }
00456 
00457     file.close();
00458 
00459     rename(tmpFile.toAscii().constData(), outFile.toAscii().constData());
00460 
00461     return true;
00462 }
00463 
00464 bool HTTPLiveStream::SaveSegmentInfo(void)
00465 {
00466     if (m_streamid == -1)
00467         return false;
00468 
00469     MSqlQuery query(MSqlQuery::InitCon());
00470     query.prepare(
00471         "UPDATE livestream "
00472         "SET startsegment = :START, currentsegment = :CURRENT, "
00473         "    segmentcount = :COUNT "
00474         "WHERE id = :STREAMID; ");
00475     query.bindValue(":START", m_startSegment);
00476     query.bindValue(":CURRENT", m_curSegment);
00477     query.bindValue(":COUNT", m_segmentCount);
00478     query.bindValue(":STREAMID", m_streamid);
00479 
00480     if (query.exec())
00481         return true;
00482 
00483     LOG(VB_GENERAL, LOG_ERR, LOC +
00484         QString("Unable to update segment info for streamid %1")
00485                 .arg(m_streamid));
00486     return false;
00487 }
00488 
00489 bool HTTPLiveStream::UpdateSizeInfo(uint16_t width, uint16_t height,
00490                                     uint16_t srcwidth, uint16_t srcheight)
00491 {
00492     if (m_streamid == -1)
00493         return false;
00494 
00495     QFileInfo finfo(m_sourceFile);
00496     QString newOutBase = finfo.fileName() +
00497         QString(".%1x%2_%3kV_%4kA").arg(width).arg(height)
00498                 .arg(m_bitrate/1000).arg(m_audioBitrate/1000);
00499     QString newFullURL = m_httpPrefix + newOutBase + ".m3u8";
00500     QString newRelativeURL = m_httpPrefixRel + newOutBase + ".m3u8";
00501 
00502     MSqlQuery query(MSqlQuery::InitCon());
00503     query.prepare(
00504         "UPDATE livestream "
00505         "SET width = :WIDTH, height = :HEIGHT, "
00506         "    sourcewidth = :SRCWIDTH, sourceheight = :SRCHEIGHT, "
00507         "    fullurl = :FULLURL, relativeurl = :RELATIVEURL, "
00508         "    outbase = :OUTBASE "
00509         "WHERE id = :STREAMID; ");
00510     query.bindValue(":WIDTH", width);
00511     query.bindValue(":HEIGHT", height);
00512     query.bindValue(":SRCWIDTH", srcwidth);
00513     query.bindValue(":SRCHEIGHT", srcheight);
00514     query.bindValue(":FULLURL", newFullURL);
00515     query.bindValue(":RELATIVEURL", newRelativeURL);
00516     query.bindValue(":OUTBASE", newOutBase);
00517     query.bindValue(":STREAMID", m_streamid);
00518 
00519     if (!query.exec())
00520     {
00521         LOG(VB_GENERAL, LOG_ERR, LOC +
00522             QString("Unable to update segment info for streamid %1")
00523                     .arg(m_streamid));
00524         return false;
00525     }
00526 
00527     m_width = width;
00528     m_height = height;
00529     m_sourceWidth = srcwidth;
00530     m_sourceHeight = srcheight;
00531     m_outBase = newOutBase;
00532     m_fullURL = newFullURL;
00533     m_relativeURL = newRelativeURL;
00534 
00535     SetOutputVars();
00536 
00537     return true;
00538 }
00539 
00540 bool HTTPLiveStream::UpdateStatus(HTTPLiveStreamStatus status)
00541 {
00542     if (m_streamid == -1)
00543         return false;
00544 
00545     if ((m_status == kHLSStatusStopping) &&
00546         (status == kHLSStatusRunning))
00547     {
00548         LOG(VB_RECORD, LOG_DEBUG, LOC + "Attempted to switch from "
00549             "Stopping to Running State");
00550         return false;
00551     }
00552 
00553     QString statusStr = StatusToString(status);
00554 
00555     m_status = status;
00556 
00557     MSqlQuery query(MSqlQuery::InitCon());
00558     query.prepare(
00559         "UPDATE livestream "
00560         "SET status = :STATUS "
00561         "WHERE id = :STREAMID; ");
00562     query.bindValue(":STATUS", (int)status);
00563     query.bindValue(":STREAMID", m_streamid);
00564 
00565     if (query.exec())
00566         return true;
00567 
00568     LOG(VB_GENERAL, LOG_ERR, LOC +
00569         QString("Unable to update status for streamid %1").arg(m_streamid));
00570     return false;
00571 }
00572 
00573 bool HTTPLiveStream::UpdateStatusMessage(QString message)
00574 {
00575     if (m_streamid == -1)
00576         return false;
00577 
00578     MSqlQuery query(MSqlQuery::InitCon());
00579     query.prepare(
00580         "UPDATE livestream "
00581         "SET statusmessage = :MESSAGE "
00582         "WHERE id = :STREAMID; ");
00583     query.bindValue(":MESSAGE", message);
00584     query.bindValue(":STREAMID", m_streamid);
00585 
00586     if (query.exec())
00587     {
00588         m_statusMessage = message;
00589         return true;
00590     }
00591 
00592     LOG(VB_GENERAL, LOG_ERR, LOC +
00593         QString("Unable to update status message for streamid %1")
00594                 .arg(m_streamid));
00595     return false;
00596 }
00597 
00598 bool HTTPLiveStream::UpdatePercentComplete(int percent)
00599 {
00600     if (m_streamid == -1)
00601         return false;
00602 
00603     MSqlQuery query(MSqlQuery::InitCon());
00604     query.prepare(
00605         "UPDATE livestream "
00606         "SET percentcomplete = :PERCENT "
00607         "WHERE id = :STREAMID; ");
00608     query.bindValue(":PERCENT", percent);
00609     query.bindValue(":STREAMID", m_streamid);
00610 
00611     if (query.exec())
00612     {
00613         m_percentComplete = percent;
00614         return true;
00615     }
00616 
00617     LOG(VB_GENERAL, LOG_ERR, LOC +
00618         QString("Unable to update percent complete for streamid %1")
00619                 .arg(m_streamid));
00620     return false;
00621 }
00622 
00623 QString HTTPLiveStream::StatusToString(HTTPLiveStreamStatus status)
00624 {
00625     switch (m_status) {
00626         case kHLSStatusUndefined : return QString("Undefined");
00627         case kHLSStatusQueued    : return QString("Queued");
00628         case kHLSStatusStarting  : return QString("Starting");
00629         case kHLSStatusRunning   : return QString("Running");
00630         case kHLSStatusCompleted : return QString("Completed");
00631         case kHLSStatusErrored   : return QString("Errored");
00632         case kHLSStatusStopping  : return QString("Stopping");
00633         case kHLSStatusStopped   : return QString("Stopped");
00634     };
00635 
00636     return QString("Unknown status value");
00637 }
00638 
00639 bool HTTPLiveStream::LoadFromDB(void)
00640 {
00641     if (m_streamid == -1)
00642         return false;
00643 
00644     MSqlQuery query(MSqlQuery::InitCon());
00645     query.prepare(
00646         "SELECT width, height, bitrate, audiobitrate, segmentsize, "
00647         "   maxsegments, startsegment, currentsegment, segmentcount, "
00648         "   percentcomplete, created, lastmodified, relativeurl, "
00649         "   fullurl, status, statusmessage, sourcefile, sourcehost, "
00650         "   sourcewidth, sourceheight, outdir, outbase, audioonlybitrate, "
00651         "   samplerate "
00652         "FROM livestream "
00653         "WHERE id = :STREAMID; ");
00654     query.bindValue(":STREAMID", m_streamid);
00655 
00656     if (!query.exec() || !query.next())
00657     {
00658         LOG(VB_GENERAL, LOG_ERR, LOC +
00659             QString("Unable to query DB info for stream %1")
00660                     .arg(m_streamid));
00661         return false;
00662     }
00663 
00664     m_width              = query.value(0).toUInt();
00665     m_height             = query.value(1).toUInt();
00666     m_bitrate            = query.value(2).toUInt();
00667     m_audioBitrate       = query.value(3).toUInt();
00668     m_segmentSize        = query.value(4).toUInt();
00669     m_maxSegments        = query.value(5).toUInt();
00670     m_startSegment       = query.value(6).toUInt();
00671     m_curSegment         = query.value(7).toUInt();
00672     m_segmentCount       = query.value(8).toUInt();
00673     m_percentComplete    = query.value(9).toUInt();
00674     m_created            = query.value(10).toDateTime();
00675     m_lastModified       = query.value(11).toDateTime();
00676     m_relativeURL        = query.value(12).toString();
00677     m_fullURL            = query.value(13).toString();
00678     m_status             = (HTTPLiveStreamStatus)(query.value(14).toInt());
00679     m_statusMessage      = query.value(15).toString();
00680     m_sourceFile         = query.value(16).toString();
00681     m_sourceHost         = query.value(17).toString();
00682     m_sourceWidth        = query.value(18).toUInt();
00683     m_sourceHeight       = query.value(19).toUInt();
00684     m_outDir             = query.value(20).toString();
00685     m_outBase            = query.value(21).toString();
00686     m_audioOnlyBitrate   = query.value(22).toUInt();
00687     m_sampleRate         = query.value(23).toUInt();
00688 
00689     SetOutputVars();
00690 
00691     return true;
00692 }
00693 
00694 void HTTPLiveStream::SetOutputVars(void)
00695 {
00696     m_outBaseEncoded = QString(QUrl::toPercentEncoding(m_outBase, "", " "));
00697 
00698     m_outFile        = m_outBase + ".av";
00699     m_outFileEncoded = m_outBaseEncoded + ".av";
00700 
00701     if (m_audioOnlyBitrate)
00702     {
00703         m_audioOutFile = m_outBase +
00704             QString(".ao_%1kA").arg(m_audioOnlyBitrate/1000);
00705         m_audioOutFileEncoded = m_outBaseEncoded +
00706             QString(".ao_%1kA").arg(m_audioOnlyBitrate/1000);
00707     }
00708 
00709     m_httpPrefix = gCoreContext->GetSetting("HTTPLiveStreamPrefix", QString(
00710         "http://%1:%2/StorageGroup/Streaming/")
00711         .arg(gCoreContext->GetSetting("MasterServerIP"))
00712         .arg(gCoreContext->GetSetting("BackendStatusPort")));
00713 
00714     if (!m_httpPrefix.endsWith("/"))
00715         m_httpPrefix.append("/");
00716 
00717     if (!gCoreContext->GetSetting("HTTPLiveStreamPrefixRel").isEmpty())
00718     {
00719         m_httpPrefixRel = gCoreContext->GetSetting("HTTPLiveStreamPrefixRel");
00720         if (!m_httpPrefix.endsWith("/"))
00721             m_httpPrefix.append("/");
00722     }
00723     else if (m_httpPrefix.contains("/StorageGroup/Streaming/"))
00724         m_httpPrefixRel = "/StorageGroup/Streaming/";
00725     else
00726         m_httpPrefixRel = "";
00727 }
00728 
00729 HTTPLiveStreamStatus HTTPLiveStream::GetDBStatus(void) const
00730 {
00731     if (m_streamid == -1)
00732         return kHLSStatusUndefined;
00733 
00734     MSqlQuery query(MSqlQuery::InitCon());
00735     query.prepare(
00736         "SELECT status FROM livestream "
00737         "WHERE id = :STREAMID; ");
00738     query.bindValue(":STREAMID", m_streamid);
00739 
00740     if (!query.exec() || !query.next())
00741     {
00742         LOG(VB_GENERAL, LOG_ERR, LOC +
00743             QString("Unable to check stop status for stream %1")
00744                     .arg(m_streamid));
00745         return kHLSStatusUndefined;
00746     }
00747 
00748     return (HTTPLiveStreamStatus)query.value(0).toInt();
00749 }
00750 
00751 bool HTTPLiveStream::CheckStop(void)
00752 {
00753     if (m_streamid == -1)
00754         return false;
00755 
00756     MSqlQuery query(MSqlQuery::InitCon());
00757     query.prepare(
00758         "SELECT status FROM livestream "
00759         "WHERE id = :STREAMID; ");
00760     query.bindValue(":STREAMID", m_streamid);
00761 
00762     if (!query.exec() || !query.next())
00763     {
00764         LOG(VB_GENERAL, LOG_ERR, LOC +
00765             QString("Unable to check stop status for stream %1")
00766                     .arg(m_streamid));
00767         return false;
00768     }
00769 
00770     if (query.value(0).toInt() == (int)kHLSStatusStopping)
00771         return true;
00772 
00773     return false;
00774 }
00775 
00776 DTC::LiveStreamInfo *HTTPLiveStream::StartStream(void)
00777 {
00778     HTTPLiveStreamThread *streamThread =
00779         new HTTPLiveStreamThread(GetStreamID());
00780     MThreadPool::globalInstance()->startReserved(streamThread,
00781                                                  "HTTPLiveStream");
00782     MythTimer statusTimer;
00783     int       delay = 250000;
00784     statusTimer.start();
00785 
00786     HTTPLiveStreamStatus status = GetDBStatus();
00787     while ((status == kHLSStatusQueued) &&
00788            ((statusTimer.elapsed() / 1000) < 30))
00789     {
00790         delay = (int)(delay * 1.5);
00791         usleep(delay);
00792 
00793         status = GetDBStatus();
00794     }
00795 
00796     return GetLiveStreamInfo();
00797 }
00798 
00799 bool HTTPLiveStream::RemoveStream(int id)
00800 {
00801     MSqlQuery query(MSqlQuery::InitCon());
00802     query.prepare(
00803         "SELECT startSegment, segmentCount "
00804         "FROM livestream "
00805         "WHERE id = :STREAMID; ");
00806     query.bindValue(":STREAMID", id);
00807 
00808     if (!query.exec() || !query.next())
00809     {
00810         LOG(VB_RECORD, LOG_ERR, "Error selecting stream info in RemoveStream");
00811         return false;
00812     }
00813 
00814     HTTPLiveStream *hls = new HTTPLiveStream(id);
00815 
00816     if (hls->GetDBStatus() == kHLSStatusRunning) {
00817         HTTPLiveStream::StopStream(id);
00818     }
00819 
00820     QString thisFile;
00821     int startSegment = query.value(0).toInt();
00822     int segmentCount = query.value(1).toInt();
00823 
00824     for (int x = 0; x < segmentCount; ++x)
00825     {
00826         thisFile = hls->GetFilename(startSegment + x);
00827 
00828         if (!thisFile.isEmpty() && !QFile::remove(thisFile))
00829             LOG(VB_GENERAL, LOG_ERR, SLOC +
00830                 QString("Unable to delete %1.").arg(thisFile));
00831 
00832         thisFile = hls->GetFilename(startSegment + x, false, true);
00833 
00834         if (!thisFile.isEmpty() && !QFile::remove(thisFile))
00835             LOG(VB_GENERAL, LOG_ERR, SLOC +
00836                 QString("Unable to delete %1.").arg(thisFile));
00837     }
00838 
00839     thisFile = hls->GetMetaPlaylistName();
00840     if (!thisFile.isEmpty() && !QFile::remove(thisFile))
00841         LOG(VB_GENERAL, LOG_ERR, SLOC +
00842             QString("Unable to delete %1.").arg(thisFile));
00843 
00844     thisFile = hls->GetPlaylistName();
00845     if (!thisFile.isEmpty() && !QFile::remove(thisFile))
00846         LOG(VB_GENERAL, LOG_ERR, SLOC +
00847             QString("Unable to delete %1.").arg(thisFile));
00848 
00849     thisFile = hls->GetPlaylistName(true);
00850     if (!thisFile.isEmpty() && !QFile::remove(thisFile))
00851         LOG(VB_GENERAL, LOG_ERR, SLOC +
00852             QString("Unable to delete %1.").arg(thisFile));
00853 
00854     thisFile = hls->GetHTMLPageName();
00855     if (!thisFile.isEmpty() && !QFile::remove(thisFile))
00856         LOG(VB_GENERAL, LOG_ERR, SLOC +
00857             QString("Unable to delete %1.").arg(thisFile));
00858 
00859     query.prepare(
00860         "DELETE FROM livestream "
00861         "WHERE id = :STREAMID; ");
00862     query.bindValue(":STREAMID", id);
00863 
00864     if (!query.exec())
00865         LOG(VB_RECORD, LOG_ERR, "Error deleting stream info in RemoveStream");
00866 
00867     delete hls;
00868     return true;
00869 }
00870 
00871 DTC::LiveStreamInfo *HTTPLiveStream::StopStream(int id)
00872 {
00873     MSqlQuery query(MSqlQuery::InitCon());
00874     query.prepare(
00875         "UPDATE livestream "
00876         "SET status = :STATUS "
00877         "WHERE id = :STREAMID; ");
00878     query.bindValue(":STATUS", (int)kHLSStatusStopping);
00879     query.bindValue(":STREAMID", id);
00880 
00881     if (!query.exec())
00882     {
00883         LOG(VB_GENERAL, LOG_ERR, SLOC +
00884             QString("Unable to remove mark stream stopped for stream %1.")
00885                     .arg(id));
00886         return NULL;
00887     }
00888 
00889     HTTPLiveStream *hls = new HTTPLiveStream(id);
00890     if (!hls)
00891         return NULL;
00892 
00893     MythTimer statusTimer;
00894     int       delay = 250000;
00895     statusTimer.start();
00896 
00897     HTTPLiveStreamStatus status = hls->GetDBStatus();
00898     while ((status != kHLSStatusStopped) &&
00899            (status != kHLSStatusCompleted) &&
00900            (status != kHLSStatusErrored) &&
00901            ((statusTimer.elapsed() / 1000) < 30))
00902     {
00903         delay = (int)(delay * 1.5);
00904         usleep(delay);
00905 
00906         status = hls->GetDBStatus();
00907     }
00908 
00909     hls->LoadFromDB();
00910     DTC::LiveStreamInfo *pLiveStreamInfo = hls->GetLiveStreamInfo();
00911 
00912     delete hls;
00913     return pLiveStreamInfo;
00914 }
00915 
00917 // Content Service API helpers
00919 
00920 DTC::LiveStreamInfo *HTTPLiveStream::GetLiveStreamInfo(
00921     DTC::LiveStreamInfo *info)
00922 {
00923     if (!info)
00924         info = new DTC::LiveStreamInfo();
00925 
00926     info->setId((int)m_streamid);
00927     info->setWidth((int)m_width);
00928     info->setHeight((int)m_height);
00929     info->setBitrate((int)m_bitrate);
00930     info->setAudioBitrate((int)m_audioBitrate);
00931     info->setSegmentSize((int)m_segmentSize);
00932     info->setMaxSegments((int)m_maxSegments);
00933     info->setStartSegment((int)m_startSegment);
00934     info->setCurrentSegment((int)m_curSegment);
00935     info->setSegmentCount((int)m_segmentCount);
00936     info->setPercentComplete((int)m_percentComplete);
00937     info->setCreated(m_created);
00938     info->setLastModified(m_lastModified);
00939     info->setRelativeURL(m_relativeURL);
00940     info->setFullURL(m_fullURL);
00941     info->setStatusStr(StatusToString(m_status));
00942     info->setStatusInt((int)m_status);
00943     info->setStatusMessage(m_statusMessage);
00944     info->setSourceFile(m_sourceFile);
00945     info->setSourceHost(m_sourceHost);
00946     info->setSourceWidth(m_sourceWidth);
00947     info->setSourceHeight(m_sourceHeight);
00948     info->setAudioOnlyBitrate((int)m_audioOnlyBitrate);
00949 
00950     return info;
00951 }
00952 
00953 DTC::LiveStreamInfoList *HTTPLiveStream::GetLiveStreamInfoList(const QString &FileName)
00954 {
00955     DTC::LiveStreamInfoList *infoList = new DTC::LiveStreamInfoList();
00956 
00957     QString sql = "SELECT id FROM livestream ";
00958 
00959     if (!FileName.isEmpty())
00960         sql += "WHERE sourcefile LIKE :FILENAME ";
00961 
00962     sql += "ORDER BY lastmodified DESC;";
00963 
00964     MSqlQuery query(MSqlQuery::InitCon());
00965     query.prepare(sql);
00966     if (!FileName.isEmpty())
00967         query.bindValue(":FILENAME", QString("%%1%").arg(FileName));
00968 
00969     if (!query.exec())
00970     {
00971         LOG(VB_GENERAL, LOG_ERR, SLOC + "Unable to get list of Live Streams");
00972         return infoList;
00973     }
00974 
00975     DTC::LiveStreamInfo *info = NULL;
00976     HTTPLiveStream *hls = NULL;
00977     while (query.next())
00978     {
00979         hls = new HTTPLiveStream(query.value(0).toUInt());
00980         info = infoList->AddNewLiveStreamInfo();
00981         hls->GetLiveStreamInfo(info);
00982         delete hls;
00983     }
00984 
00985     return infoList;
00986 }
00987 
00988 /* vim: set expandtab tabstop=4 shiftwidth=4: */
00989 
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends