00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
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
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
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
00989