diff --git a/src/output/output.h b/src/output/output.h index cf5731a7..f4210a5f 100644 --- a/src/output/output.h +++ b/src/output/output.h @@ -121,7 +121,6 @@ namespace Mist{ virtual std::string getConnectedHost(); virtual std::string getConnectedBinHost(); virtual std::string getStatsName(); - virtual bool hasSessionIDs(){return false;} virtual void connStats(uint64_t now, Comms::Connections &statComm); diff --git a/src/output/output_hls.cpp b/src/output/output_hls.cpp index b0165b5f..a3d2058a 100644 --- a/src/output/output_hls.cpp +++ b/src/output/output_hls.cpp @@ -1,13 +1,8 @@ #include "output_hls.h" -#include -// #include /*LTS*/ -// #include -// #include -// #include - -int64_t bootMsOffset; // boot time in ms -uint64_t systemBoot; // time since boot in ms -const std::string hlsMediaFormat = ".ts"; +#include /*LTS*/ +#include +#include +#include namespace Mist{ bool OutHLS::isReadyForPlay(){ @@ -17,17 +12,161 @@ namespace Mist{ uint32_t mainTrack = M.mainTrack(); if (mainTrack == INVALID_TRACK_ID){return false;} DTSC::Fragments fragments(M.fragments(mainTrack)); - return fragments.getValidCount() > 6; + return fragments.getValidCount() > 4; + } + + ///\brief Builds an index file for HTTP Live streaming. + ///\return The index file for HTTP Live Streaming. + std::string OutHLS::liveIndex(){ + std::stringstream result; + selectDefaultTracks(); + result << "#EXTM3U\r\n"; + size_t audioId = INVALID_TRACK_ID; + size_t vidTracks = 0; + bool hasSubs = false; + for (std::map::iterator it = userSelect.begin(); it != userSelect.end(); ++it){ + if (audioId == INVALID_TRACK_ID && M.getType(it->first) == "audio"){audioId = it->first;} + if (!hasSubs && M.getCodec(it->first) == "subtitle"){hasSubs = true;} + } + std::string tknStr; + if (tkn.size() && Comms::tknMode & 0x04){tknStr = "?tkn=" + tkn;} + for (std::map::iterator it = userSelect.begin(); it != userSelect.end(); ++it){ + if (M.getType(it->first) == "video"){ + ++vidTracks; + int bWidth = M.getBps(it->first); + if (bWidth < 5){bWidth = 5;} + if (audioId != INVALID_TRACK_ID){bWidth += M.getBps(audioId);} + result << "#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=" << (bWidth * 8); + result << ",RESOLUTION=" << M.getWidth(it->first) << "x" << M.getHeight(it->first); + if (M.getFpks(it->first)){ + result << ",FRAME-RATE=" << (float)M.getFpks(it->first) / 1000; + } + if (hasSubs){result << ",SUBTITLES=\"sub1\"";} + result << ",CODECS=\""; + result << Util::codecString(M.getCodec(it->first), M.getInit(it->first)); + if (audioId != INVALID_TRACK_ID){ + result << "," << Util::codecString(M.getCodec(audioId), M.getInit(audioId)); + } + result << "\"\r\n" << it->first; + if (audioId != INVALID_TRACK_ID){result << "_" << audioId;} + result << "/index.m3u8" << tknStr << "\r\n"; + }else if (M.getCodec(it->first) == "subtitle"){ + + if (M.getLang(it->first).empty()){meta.setLang(it->first, "und");} + + result << "#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID=\"sub1\",LANGUAGE=\"" << M.getLang(it->first) + << "\",NAME=\"" << Encodings::ISO639::decode(M.getLang(it->first)) + << "\",AUTOSELECT=NO,DEFAULT=NO,FORCED=NO,URI=\"" << it->first << "/index.m3u8" << tknStr << "\"" + << "\r\n"; + } + } + if (!vidTracks && audioId != INVALID_TRACK_ID){ + result << "#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=" << (M.getBps(audioId) * 8); + result << ",CODECS=\"" << Util::codecString(M.getCodec(audioId), M.getInit(audioId)) << "\""; + result << "\r\n"; + result << audioId << "/index.m3u8" << tknStr << "\r\n"; + } + return result.str(); + } + + std::string OutHLS::liveIndex(size_t tid, const std::string &tknStr, const std::string &urlPrefix){ + //Timing track is current track, unless non-video, then time by video track + size_t timingTid = tid; + if (M.getType(timingTid) != "video"){timingTid = M.mainTrack();} + if (timingTid == INVALID_TRACK_ID){timingTid = tid;} + + std::stringstream result; + // parse single track + uint32_t targetDuration = (M.biggestFragment(timingTid) / 1000) + 1; + result << "#EXTM3U\r\n#EXT-X-VERSION:"; + + result << (M.getEncryption(tid) == "" ? "3" : "5"); + + result << "\r\n#EXT-X-TARGETDURATION:" << targetDuration << "\r\n"; + + if (M.getEncryption(tid) != ""){ + result << "#EXT-X-KEY:METHOD=SAMPLE-AES,URI=\""; + result << "urlHere"; + result << "\",KEYFORMAT=\"com.apple.streamingkeydelivery" << std::endl; + } + + std::deque lines; + std::deque durations; + uint32_t totalDuration = 0; + DTSC::Keys keys(M.keys(timingTid)); + DTSC::Fragments fragments(M.fragments(timingTid)); + uint32_t firstFragment = fragments.getFirstValid(); + uint32_t endFragment = fragments.getEndValid(); + for (int i = firstFragment; i < endFragment; i++){ + uint64_t duration = fragments.getDuration(i); + size_t keyNumber = fragments.getFirstKey(i); + uint64_t startTime = keys.getTime(keyNumber); + if (!duration){duration = M.getLastms(timingTid) - startTime;} + double floatDur = (double)duration / 1000; + char lineBuf[400]; + + if (M.getCodec(tid) == "subtitle"){ + snprintf(lineBuf, 400, "#EXTINF:%f,\r\n../../../%s.webvtt?meta=%zu&from=%" PRIu64 "&to=%" PRIu64 "\r\n", + (double)duration / 1000, streamName.c_str(), tid, startTime, startTime + duration); + }else{ + snprintf(lineBuf, 400, "#EXTINF:%f,\r\n%s%" PRIu64 "_%" PRIu64 ".ts%s\r\n", floatDur, urlPrefix.c_str(), + startTime, startTime + duration, tknStr.c_str()); + } + totalDuration += duration; + durations.push_back(duration); + lines.push_back(lineBuf); + } + size_t skippedLines = 0; + if (M.getLive() && lines.size()){ + // only print the last segment when non-live + lines.pop_back(); + totalDuration -= durations.back(); + durations.pop_back(); + // skip the first two segments when live, unless that brings us under 4 target durations + while ((totalDuration - durations.front()) > (targetDuration * 4000) && skippedLines < 2){ + lines.pop_front(); + totalDuration -= durations.front(); + durations.pop_front(); + ++skippedLines; + } + /*LTS-START*/ + // remove lines to reduce size towards listlimit setting - but keep at least 4X target + // duration available + uint64_t listlimit = config->getInteger("listlimit"); + if (listlimit){ + while (lines.size() > listlimit && (totalDuration - durations.front()) > (targetDuration * 4000)){ + lines.pop_front(); + totalDuration -= durations.front(); + durations.pop_front(); + ++skippedLines; + } + } + /*LTS-END*/ + } + + result << "#EXT-X-MEDIA-SEQUENCE:" << firstFragment + skippedLines << "\r\n"; + + for (std::deque::iterator it = lines.begin(); it != lines.end(); it++){ + result << *it; + } + if (!M.getLive() || !totalDuration){result << "#EXT-X-ENDLIST\r\n";} + HIGH_MSG("Sending this index: %s", result.str().c_str()); + return result.str(); } OutHLS::OutHLS(Socket::Connection &conn) : TSOutput(conn){ - // load from global config - systemBoot = Util::getGlobalConfig("systemBoot").asInt(); - // fall back to local calculation if loading from global config fails - if (!systemBoot){systemBoot = (Util::unixMS() - Util::bootMS());} uaDelay = 0; realTime = 0; - targetTime = 0xFFFFFFFFFFFFFFFFull; + until = 0xFFFFFFFFFFFFFFFFull; + // If this connection is a socket and not already connected to stdio, connect it to stdio. + if (myConn.getPureSocket() != -1 && myConn.getSocket() != STDIN_FILENO && myConn.getSocket() != STDOUT_FILENO){ + std::string host = getConnectedHost(); + dup2(myConn.getSocket(), STDIN_FILENO); + dup2(myConn.getSocket(), STDOUT_FILENO); + myConn.open(STDOUT_FILENO, STDIN_FILENO); + myConn.setHost(host); + } + } OutHLS::~OutHLS(){} @@ -79,22 +218,13 @@ namespace Mist{ "significantly, but increases compatibility somewhat."; capa["optional"]["nonchunked"]["option"] = "--nonchunked"; - cfg->addOption("mergesessions", - JSON::fromString("{\"short\":\"M\",\"long\":\"mergesessions\",\"help\":\"Merge " - "together sessions from one user into a single session.\"}")); - capa["optional"]["mergesessions"]["name"] = "Merge sessions"; - capa["optional"]["mergesessions"]["help"] = - "If enabled, merges together all views from a single user into a single combined session. " - "If disabled, each view (main playlist request) is a separate session."; - capa["optional"]["mergesessions"]["option"] = "--mergesessions"; - cfg->addOption("chunkpath", JSON::fromString("{\"arg\":\"string\",\"default\":\"\",\"short\":\"e\",\"long\":" "\"chunkpath\",\"help\":\"Alternate URL path to " "prepend to chunk paths, for serving through e.g. a CDN\"}")); capa["optional"]["chunkpath"]["name"] = "Prepend path for chunks"; capa["optional"]["chunkpath"]["help"] = - "Chunks will be served from this path. This also disables sessions IDs for chunks."; + "Chunks will be served from this path."; capa["optional"]["chunkpath"]["default"] = ""; capa["optional"]["chunkpath"]["type"] = "str"; capa["optional"]["chunkpath"]["option"] = "--chunkpath"; @@ -102,133 +232,24 @@ namespace Mist{ capa["optional"]["chunkpath"]["default"] = ""; } - /******************************/ - /* HLS Manifest Generation */ - /******************************/ - - /// \brief Builds master playlist for (LL)HLS. - ///\return The master playlist file for (LL)HLS. - void OutHLS::sendHlsMasterManifest(){ - selectDefaultTracks(); - // check for forced "no low latency" parameter - bool noLLHLS = H.GetVar("llhls").size() ? H.GetVar("llhls") == "0" : false; - - // Populate the struct that will help generate the master playlist - const HLS::MasterData masterData ={ - false,//hasSessionIDs, unused - noLLHLS, - hlsMediaFormat == ".ts", - getMainSelectedTrack(), - H.GetHeader("User-Agent"), - (Comms::tknMode & 0x04)?tkn:"", - systemBoot, - bootMsOffset, - }; - - std::stringstream result; - HLS::addMasterManifest(result, M, userSelect, masterData); - - H.SetBody(result.str()); - H.SendResponse("200", "OK", myConn); - } - - /// \brief Builds media playlist to (LL)HLS - ///\return The media playlist file to (LL)HLS - void OutHLS::sendHlsMediaManifest(const size_t requestTid){ - const HLS::HlsSpecData hlsSpec ={H.GetVar("_HLS_skip"), H.GetVar("_HLS_msn"), - H.GetVar("_HLS_part")}; - - size_t timingTid = HLS::getTimingTrackId(M, H.GetVar("mTrack"), getMainSelectedTrack()); - - // Chunkpath & Session ID logic - std::string urlPrefix = ""; - if (config->getString("chunkpath").size()){ - urlPrefix = HTTP::URL(config->getString("chunkpath")).link("./" + H.url).link("./").getUrl(); - } - - // check for forced "no low latency" parameter - bool noLLHLS = H.GetVar("llhls").size() ? H.GetVar("llhls") == "0" : false; - // override if valid header forces "no low latency" - noLLHLS = H.GetHeader("X-Mist-LLHLS").size() ? H.GetHeader("X-Mist-LLHLS") == "0" : noLLHLS; - - const HLS::TrackData trackData ={ - M.getLive(), - M.getType(requestTid) == "video", - noLLHLS, - hlsMediaFormat, - M.getEncryption(requestTid), - (Comms::tknMode & 0x04)?tkn:"", - timingTid, - requestTid, - M.biggestFragment(timingTid) / 1000, - (uint64_t)atol(H.GetVar("iMsn").c_str()), - (uint64_t)(M.getLive() ? config->getInteger("listlimit") : 0), - urlPrefix, - systemBoot, - bootMsOffset, - }; - - // Fragment & Key handlers - DTSC::Fragments fragments(M.fragments(trackData.timingTrackId)); - DTSC::Keys keys(M.keys(trackData.timingTrackId)); - - uint32_t bprErrCode = HLS::blockPlaylistReload(M, userSelect, trackData, hlsSpec, fragments, keys); - if (bprErrCode == 400){ - H.SendResponse("400", "Bad Request: Invalid LLHLS parameter", myConn); - return; - }else if (bprErrCode == 503){ - H.SendResponse("503", "Service Unavailable", myConn); - return; - } - - HLS::FragmentData fragData; - HLS::populateFragmentData(M, userSelect, fragData, trackData, fragments, keys); - - std::stringstream result; - HLS::addStartingMetaTags(result, fragData, trackData, hlsSpec); - HLS::addMediaFragments(result, M, fragData, trackData, fragments, keys); - HLS::addEndingTags(result, M, userSelect, fragData, trackData); - - H.SetBody(result.str()); - H.SendResponse("200", "OK", myConn); - } - - void OutHLS::sendHlsManifest(const std::string url){ - H.setCORSHeaders(); - H.SetHeader("Content-Type", "application/vnd.apple.mpegurl"); // for .m3u8 - H.SetHeader("Cache-Control", "no-store"); - if (H.method == "OPTIONS" || H.method == "HEAD"){ - H.SetBody(""); - H.SendResponse("200", "OK", myConn); - return; - } - - if (url.find("/") == std::string::npos){ - sendHlsMasterManifest(); - }else{ - sendHlsMediaManifest(atoll(url.c_str())); - } - } - void OutHLS::onHTTP(){ initialize(); - bootMsOffset = 0; - if (M.getLive()){bootMsOffset = M.getBootMsOffset();} if (tkn.size()){ if (Comms::tknMode & 0x08){ - const std::string koekjes = H.GetHeader("Cookie"); std::stringstream cookieHeader; cookieHeader << "tkn=" << tkn << "; Max-Age=" << SESS_TIMEOUT; H.SetHeader("Set-Cookie", cookieHeader.str()); } } + std::string method = H.method; if (H.url == "/crossdomain.xml"){ + H.Clean(); H.SetHeader("Content-Type", "text/xml"); H.SetHeader("Server", APPIDENT); H.setCORSHeaders(); - if (H.method == "OPTIONS" || H.method == "HEAD"){ + if (method == "OPTIONS" || method == "HEAD"){ H.SendResponse("200", "OK", myConn); responded = true; return; @@ -250,7 +271,7 @@ namespace Mist{ }else{ H.SetHeader("Content-Type", "application/vnd.apple.mpegurl"); } - if (isTS && (!hasSessionIDs() || config->getOption("chunkpath"))){ + if (isTS && (!(Comms::tknMode & 0x04) || config->getOption("chunkpath"))){ H.SetHeader("Cache-Control", "public, max-age=" + JSON::Value(M.getDuration(getMainSelectedTrack()) / 1000).asString() + @@ -286,101 +307,41 @@ namespace Mist{ if (HTTP::URL(H.url).getExt().substr(0, 3) != "m3u"){ std::string tmpStr = H.getUrl().substr(5 + streamName.size()); - std::string url = H.url.substr(H.url.find('/', 5) + 1); // Strip /hls// from url - const uint64_t msn = atoll(H.GetVar("msn").c_str()); - const uint64_t dur = atoll(H.GetVar("dur").c_str()); - const uint64_t mTrack = atoll(H.GetVar("mTrack").c_str()); - size_t idx = atoll(url.c_str()); - - if (url.find(hlsMediaFormat) == std::string::npos){ - H.SendResponse("404", "File not found", myConn); + uint64_t from; + if (sscanf(tmpStr.c_str(), "/%zu_%zu/%" PRIu64 "_%" PRIu64 ".ts", &vidTrack, &audTrack, &from, &until) != 4){ + if (sscanf(tmpStr.c_str(), "/%zu/%" PRIu64 "_%" PRIu64 ".ts", &vidTrack, &from, &until) != 3){ + MEDIUM_MSG("Could not parse URL: %s", H.getUrl().c_str()); + H.Clean(); + H.setCORSHeaders(); + H.SetBody("The HLS URL wasn't understood - what did you want, exactly?\n"); + myConn.SendNow(H.BuildResponse("404", "URL mismatch")); return; - } - if (!M.getValidTracks().count(idx)){ - H.SendResponse("404", "Track not found", myConn); - return; - } - - uint64_t fragmentIndex; - uint64_t startTime; - uint32_t part; - - // set targetTime - if (sscanf(url.c_str(), "%*d/chunk_%" PRIu64 ".%" PRIu32 ".*", &startTime, &part) == - 2){ - // Logic: calculate targetTime for partial segments - targetTime = HLS::getPartTargetTime(M, idx, mTrack, startTime, msn, part); - if (!targetTime){ - H.SendResponse("404", "Partial fragment does not exist", myConn); - responded = true; - return; } - startTime += part * HLS::partDurationMaxMs; - fragmentIndex = M.getFragmentIndexForTime(mTrack, startTime); - DEBUG_MSG(5, "partial segment requested: %s st %" PRIu64 " et %" PRIu64, url.c_str(), - startTime, targetTime); - }else if (sscanf(url.c_str(), "%*d_%*d/chunk_%" PRIu64 ".%" PRIu32 ".*", &startTime, &part) == 2){ - // Logic: calculate targetTime for partial segments for TS based media with 1 audio track - targetTime = HLS::getPartTargetTime(M, idx, mTrack, startTime, msn, part); - if (!targetTime){ - H.SendResponse("404", "Partial fragment does not exist", myConn); - return; - } - startTime += part * HLS::partDurationMaxMs; - fragmentIndex = M.getFragmentIndexForTime(mTrack, startTime); - DEBUG_MSG(5, "partial segment requested: %s st %" PRIu64 " et %" PRIu64, url.c_str(), - startTime, targetTime); - }else if (sscanf(url.c_str(), "%*d/chunk_%" PRIu64 ".*", &startTime) == 1){ - // Logic: calculate targetTime for full segments - if (M.getVod()){startTime += M.getFirstms(idx);} - fragmentIndex = M.getFragmentIndexForTime(mTrack, startTime); - targetTime = dur ? startTime + dur : M.getTimeForFragmentIndex(mTrack, fragmentIndex + 1); - DEBUG_MSG(5, "full segment requested: %s st %" PRIu64 " et %" PRIu64 " asd", url.c_str(), - startTime, targetTime); - }else if (sscanf(url.c_str(), "%*d_%*d/chunk_%" PRIu64 ".*", &startTime) == 1){ - // Logic: calculate targetTime for full segments - if (M.getVod()){startTime += M.getFirstms(idx);} - fragmentIndex = M.getFragmentIndexForTime(mTrack, startTime); - targetTime = dur ? startTime + dur : M.getTimeForFragmentIndex(mTrack, fragmentIndex + 1); - DEBUG_MSG(5, "full segment requested: %s st %" PRIu64 " et %" PRIu64 " asd", url.c_str(), - startTime, targetTime); - }else{ - WARN_MSG("Undetected media request. Please report to MistServer."); - } - - std::map::const_iterator it = userSelect.begin(); - std::set aTracks; - for (; it != userSelect.end(); it++){ - if (M.getType(it->first) == "audio"){aTracks.insert(it->first);} - } - // Select the right track - if (aTracks.size() == 1){ userSelect.clear(); - userSelect[idx].reload(streamName, idx); - userSelect[*aTracks.begin()].reload(streamName, *aTracks.begin()); + userSelect[vidTrack].reload(streamName, vidTrack); }else{ userSelect.clear(); - userSelect[idx].reload(streamName, idx); + userSelect[vidTrack].reload(streamName, vidTrack); + userSelect[audTrack].reload(streamName, audTrack); } - std::set validTracks = getSupportedTracks(); for (std::set::iterator it = validTracks.begin(); it != validTracks.end(); ++it){ if (M.getCodec(*it) == "ID3"){userSelect[*it].reload(streamName, *it);} } - if (M.getLive() && startTime < M.getFirstms(idx)){ + if (M.getLive() && from < M.getFirstms(vidTrack)){ + H.Clean(); H.setCORSHeaders(); H.SetBody("The requested fragment is no longer kept in memory on the server and cannot be " "served.\n"); myConn.SendNow(H.BuildResponse("404", "Fragment out of range")); - WARN_MSG("Fragment @ %" PRIu64 " too old", startTime); - responded = true; + WARN_MSG("Fragment @ %" PRIu64 " too old", from); return; } H.SetHeader("Content-Type", "video/mp2t"); H.setCORSHeaders(); - if (hasSessionIDs() && !config->getOption("chunkpath")){ + if (!(Comms::tknMode & 0x04) || config->getOption("chunkpath")){ H.SetHeader("Cache-Control", "no-store"); }else{ H.SetHeader("Cache-Control", @@ -390,7 +351,7 @@ namespace Mist{ H.SetHeader("Pragma", ""); H.SetHeader("Expires", ""); } - if (H.method == "OPTIONS" || H.method == "HEAD"){ + if (method == "OPTIONS" || method == "HEAD"){ H.SendResponse("200", "OK", myConn); responded = true; return; @@ -399,44 +360,59 @@ namespace Mist{ H.StartResponse(H, myConn, VLCworkaround || config->getBool("nonchunked")); responded = true; // we assume whole fragments - but timestamps may be altered at will - contPAT = fragmentIndex; // PAT continuity counter - contPMT = fragmentIndex; // PMT continuity counter - contSDT = fragmentIndex; // SDT continuity counter + uint32_t fragIndice = M.getFragmentIndexForTime(vidTrack, from); + contPAT = fragIndice; // PAT continuity counter + contPMT = fragIndice; // PMT continuity counter + contSDT = fragIndice; // SDT continuity counter packCounter = 0; parseData = true; wantRequest = false; - seek(startTime); - ts_from = startTime; + seek(from); + ts_from = from; }else{ initialize(); - + std::string request = H.url.substr(H.url.find("/", 5) + 1); H.setCORSHeaders(); H.SetHeader("Content-Type", "application/vnd.apple.mpegurl"); if (!M.getValidTracks().size()){ H.SendResponse("404", "Not online or found", myConn); return; } - if (H.method == "OPTIONS" || H.method == "HEAD"){ + if (method == "OPTIONS" || method == "HEAD"){ H.SendResponse("200", "OK", myConn); return; } - - // Strip /hls// from url - std::string url = H.url.substr(H.url.find('/', 5) + 1); - sendHlsManifest(url); - responded = true; + std::string manifest; + if (request.find("/") == std::string::npos){ + manifest = liveIndex(); + }else{ + size_t idx = atoi(request.substr(0, request.find("/")).c_str()); + if (!M.getValidTracks().count(idx)){ + H.SendResponse("404", "No corresponding track found", myConn); + return; + } + if (config->getString("chunkpath").size()){ + manifest = liveIndex(idx, "", HTTP::URL(config->getString("chunkpath")).link(reqUrl).link("./").getUrl()); + }else{ + std::string tknStr; + if (tkn.size() && Comms::tknMode & 0x04){tknStr = "?tkn=" + tkn;} + manifest = liveIndex(idx, tknStr); + } + } + H.SetBody(manifest); + H.SendResponse("200", "OK", myConn); } } void OutHLS::sendNext(){ // First check if we need to stop. - if (thisPacket.getTime() >= targetTime){ + if (thisPacket.getTime() >= until){ stop(); wantRequest = true; + parseData = false; // Ensure alignment of contCounters, to prevent discontinuities. - for (std::map::iterator it = contCounters.begin(); it != contCounters.end(); - it++){ + for (std::map::iterator it = contCounters.begin(); it != contCounters.end(); it++){ if (it->second % 16 != 0){ packData.clear(); packData.setPID(it->first); diff --git a/src/output/output_hls.h b/src/output/output_hls.h index 2ed1961a..129873f9 100644 --- a/src/output/output_hls.h +++ b/src/output/output_hls.h @@ -17,14 +17,12 @@ namespace Mist{ protected: std::string h264init(const std::string &initData); std::string h265init(const std::string &initData); + std::string liveIndex(); + std::string liveIndex(size_t tid, const std::string &sessId, const std::string &urlPrefix = ""); - bool hasSessionIDs(){return !config->getBool("mergesessions");} - - void sendHlsManifest(const std::string url); - void sendHlsMasterManifest(); - void sendHlsMediaManifest(const size_t requestTid); - - uint64_t targetTime; + size_t vidTrack; + size_t audTrack; + uint64_t until; }; }// namespace Mist diff --git a/src/output/output_webrtc.h b/src/output/output_webrtc.h index a999fce3..477b07ef 100644 --- a/src/output/output_webrtc.h +++ b/src/output/output_webrtc.h @@ -127,7 +127,6 @@ namespace Mist{ public: OutWebRTC(Socket::Connection &myConn); ~OutWebRTC(); - bool hasSessionIDs(){return !config->getBool("mergesessions");} static void init(Util::Config *cfg); virtual void sendHeader(); virtual void sendNext();