Rollback to 3.0 version of HLS

Change-Id: I12d2c48d3d4c57b93e827daffb7d38451e03dae9
This commit is contained in:
Thulinma 2023-01-17 01:05:28 +01:00
parent bf15ec6741
commit 0c716714df
4 changed files with 210 additions and 238 deletions

View file

@ -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);

View file

@ -1,13 +1,8 @@
#include "output_hls.h"
#include <mist/hls_support.h>
// #include <mist/langcodes.h> /*LTS*/
// #include <mist/stream.h>
// #include <mist/url.h>
// #include <unistd.h>
int64_t bootMsOffset; // boot time in ms
uint64_t systemBoot; // time since boot in ms
const std::string hlsMediaFormat = ".ts";
#include <mist/langcodes.h> /*LTS*/
#include <mist/stream.h>
#include <mist/url.h>
#include <unistd.h>
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<size_t, Comms::Users>::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<size_t, Comms::Users>::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<std::string> lines;
std::deque<uint16_t> 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<std::string>::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/<streamname>/ 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<size_t, Comms::Users>::const_iterator it = userSelect.begin();
std::set<size_t> 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<size_t> validTracks = getSupportedTracks();
for (std::set<size_t>::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/<streamname>/ 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<size_t, uint16_t>::iterator it = contCounters.begin(); it != contCounters.end();
it++){
for (std::map<size_t, uint16_t>::iterator it = contCounters.begin(); it != contCounters.end(); it++){
if (it->second % 16 != 0){
packData.clear();
packData.setPID(it->first);

View file

@ -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

View file

@ -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();