|
MythTV
0.26-pre
|
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
1.7.6.1