From 5147d77b0271cbd690fea72f0666beae7f4cf3be Mon Sep 17 00:00:00 2001 From: Siddarth Tegginamani Date: Tue, 1 Feb 2022 14:52:37 +0100 Subject: [PATCH] HLS TS updated to use hls_support library for (LL)HLS manifest generation. --- src/output/output_hls.cpp | 460 +++++++++++++++++++++----------------- src/output/output_hls.h | 11 +- 2 files changed, 262 insertions(+), 209 deletions(-) diff --git a/src/output/output_hls.cpp b/src/output/output_hls.cpp index 7e77dcd1..93410f54 100644 --- a/src/output/output_hls.cpp +++ b/src/output/output_hls.cpp @@ -1,8 +1,13 @@ #include "output_hls.h" -#include /*LTS*/ -#include -#include -#include +#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"; namespace Mist{ bool OutHLS::isReadyForPlay(){ @@ -12,159 +17,17 @@ namespace Mist{ uint32_t mainTrack = M.mainTrack(); if (mainTrack == INVALID_TRACK_ID){return false;} DTSC::Fragments fragments(M.fragments(mainTrack)); - 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;} - } - 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;} - if (hasSessionIDs()){ - result << "/index.m3u8?sessId=" << getpid() << "\r\n"; - }else{ - result << "/index.m3u8\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\"" - << "\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\r\n"; - } - return result.str(); - } - - std::string OutHLS::liveIndex(size_t tid, const std::string &sessId){ - //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?track=%zu&from=%" PRIu64 "&to=%" PRIu64 "\r\n", - (double)duration / 1000, streamName.c_str(), tid, startTime, startTime + duration); - }else{ - if (sessId.size()){ - snprintf(lineBuf, 400, "#EXTINF:%f,\r\n%" PRIu64 "_%" PRIu64 ".ts?sessId=%s\r\n", - floatDur, startTime, startTime + duration, sessId.c_str()); - }else{ - snprintf(lineBuf, 400, "#EXTINF:%f,\r\n%" PRIu64 "_%" PRIu64 ".ts\r\n", floatDur, - startTime, startTime + duration); - } - } - 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(); + return fragments.getValidCount() > 6; } 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; - until = 0xFFFFFFFFFFFFFFFFull; + targetTime = 0xFFFFFFFFFFFFFFFFull; } OutHLS::~OutHLS(){} @@ -184,7 +47,7 @@ namespace Mist{ capa["codecs"][0u][4u].append("+MP3"); capa["codecs"][0u][5u].append("+AC3"); capa["codecs"][0u][6u].append("+MP2"); - capa["codecs"][0u][6u].append("+subtitle"); + capa["codecs"][0u][7u].append("+subtitle"); capa["methods"][0u]["handler"] = "http"; capa["methods"][0u]["type"] = "html5/application/vnd.apple.mpegurl"; capa["methods"][0u]["hrn"] = "HLS (TS)"; @@ -194,11 +57,12 @@ namespace Mist{ "[[\"blacklist\",[\"Mozilla/" "\"]],[\"whitelist\",[\"iPad\",\"iPhone\",\"iPod\",\"MacIntel\",\"Edge\"]]]"); capa["exceptions"]["codec:HEVC"] = JSON::fromString("[[\"blacklist\"]]"); - /*LTS-START*/ - cfg->addOption("listlimit", - JSON::fromString( - "{\"arg\":\"integer\",\"default\":0,\"short\":\"y\",\"long\":\"list-limit\"," - "\"help\":\"Maximum number of parts in live playlists (0 = infinite).\"}")); + + cfg->addOption( + "listlimit", + JSON::fromString( + "{\"arg\":\"integer\",\"default\":0,\"short\":\"y\",\"long\":\"list-limit\"," + "\"help\":\"Maximum number of segments in live playlists (0 = infinite).\"}")); capa["optional"]["listlimit"]["name"] = "Live playlist limit"; capa["optional"]["listlimit"]["help"] = "Maximum number of parts in live playlists. (0 = infinite)"; @@ -223,18 +87,150 @@ namespace Mist{ "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"; - /*LTS-END*/ + + 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."; + capa["optional"]["chunkpath"]["default"] = ""; + capa["optional"]["chunkpath"]["type"] = "str"; + capa["optional"]["chunkpath"]["option"] = "--chunkpath"; + capa["optional"]["chunkpath"]["short"] = "e"; + 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(); + + std::string sessId = ""; + if (hasSessionIDs()){ + std::string ua = UA + JSON::Value(getpid()).asString(); + crc = checksum::crc32(0, ua.data(), ua.size()); + sessId = JSON::Value(crc).asString(); + } + + // 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 ={ + hasSessionIDs(), + noLLHLS, + hlsMediaFormat == ".ts", + getMainSelectedTrack(), + H.GetHeader("User-Agent"), + sessId, + 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 = ""; + std::string sessId = ""; + if (config->getString("chunkpath").size()){ + urlPrefix = HTTP::URL(config->getString("chunkpath")).link("./" + H.url).link("./").getUrl(); + }else{ + sessId = H.GetVar("sessId"); + } + + // 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), + sessId, + timingTid, + requestTid, + M.biggestFragment(timingTid) / 1000, + atol(H.GetVar("iMsn").c_str()), + config->getInteger("listlimit"), + 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(){ - std::string method = H.method; - std::string sessId = H.GetVar("sessId"); + initialize(); + bootMsOffset = 0; + if (M.getLive()){bootMsOffset = M.getBootMsOffset();} if (H.url == "/crossdomain.xml"){ H.SetHeader("Content-Type", "text/xml"); H.SetHeader("Server", APPIDENT); H.setCORSHeaders(); - if (method == "OPTIONS" || method == "HEAD"){ + if (H.method == "OPTIONS" || H.method == "HEAD"){ H.SendResponse("200", "OK", myConn); return; } @@ -254,12 +250,15 @@ namespace Mist{ }else{ H.SetHeader("Content-Type", "application/vnd.apple.mpegurl"); } - if (isTS && !hasSessionIDs()){ - H.SetHeader("Cache-Control", "public, max-age="+JSON::Value(M.getDuration(getMainSelectedTrack())/1000).asString()+", immutable"); + if (isTS && (!hasSessionIDs() || config->getOption("chunkpath"))){ + H.SetHeader("Cache-Control", + "public, max-age=" + + JSON::Value(M.getDuration(getMainSelectedTrack()) / 1000).asString() + + ", immutable"); H.SetHeader("Pragma", ""); H.SetHeader("Expires", ""); }else{ - H.SetHeader("Cache-Control", "no-cache"); + H.SetHeader("Cache-Control", "no-store"); } H.SetBody(""); H.SendResponse("200", "OK", myConn); @@ -286,99 +285,152 @@ namespace Mist{ if (HTTP::URL(H.url).getExt().substr(0, 3) != "m3u"){ std::string tmpStr = H.getUrl().substr(5 + streamName.size()); - 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.setCORSHeaders(); - H.SetBody("The HLS URL wasn't understood - what did you want, exactly?\n"); - myConn.SendNow(H.BuildResponse("404", "URL mismatch")); + 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); + 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); 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[vidTrack].reload(streamName, vidTrack); + userSelect[idx].reload(streamName, idx); + userSelect[*aTracks.begin()].reload(streamName, *aTracks.begin()); }else{ userSelect.clear(); - userSelect[vidTrack].reload(streamName, vidTrack); - userSelect[audTrack].reload(streamName, audTrack); + userSelect[idx].reload(streamName, idx); } + 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() && from < M.getFirstms(vidTrack)){ + if (M.getLive() && startTime < M.getFirstms(idx)){ 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", from); + WARN_MSG("Fragment @ %" PRIu64 " too old", startTime); return; } H.SetHeader("Content-Type", "video/mp2t"); H.setCORSHeaders(); - if (hasSessionIDs()){ - H.SetHeader("Cache-Control", "no-cache"); + if (hasSessionIDs() && !config->getOption("chunkpath")){ + H.SetHeader("Cache-Control", "no-store"); }else{ - H.SetHeader("Cache-Control", "public, max-age="+JSON::Value(M.getDuration(getMainSelectedTrack())/1000).asString()+", immutable"); + H.SetHeader("Cache-Control", + "public, max-age=" + + JSON::Value(M.getDuration(getMainSelectedTrack()) / 1000).asString() + + ", immutable"); H.SetHeader("Pragma", ""); H.SetHeader("Expires", ""); } - if (method == "OPTIONS" || method == "HEAD"){ + if (H.method == "OPTIONS" || H.method == "HEAD"){ H.SendResponse("200", "OK", myConn); return; } H.StartResponse(H, myConn, VLCworkaround || config->getBool("nonchunked")); // we assume whole fragments - but timestamps may be altered at will - uint32_t fragIndice = M.getFragmentIndexForTime(vidTrack, from); - contPAT = fragIndice; // PAT continuity counter - contPMT = fragIndice; // PMT continuity counter - contSDT = fragIndice; // SDT continuity counter + contPAT = fragmentIndex; // PAT continuity counter + contPMT = fragmentIndex; // PMT continuity counter + contSDT = fragmentIndex; // SDT continuity counter packCounter = 0; parseData = true; wantRequest = false; - seek(from); - ts_from = from; + seek(startTime); + ts_from = startTime; }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 (method == "OPTIONS" || method == "HEAD"){ + if (H.method == "OPTIONS" || H.method == "HEAD"){ H.SendResponse("200", "OK", myConn); return; } - 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; - } - manifest = liveIndex(idx, sessId); - } - H.SetBody(manifest); - H.SendResponse("200", "OK", myConn); + // Strip /hls// from url + std::string url = H.url.substr(H.url.find('/', 5) + 1); + sendHlsManifest(url); } } void OutHLS::sendNext(){ // First check if we need to stop. - if (thisPacket.getTime() >= until){ + if (thisPacket.getTime() >= targetTime){ stop(); wantRequest = true; // 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 243bc512..2ed1961a 100644 --- a/src/output/output_hls.h +++ b/src/output/output_hls.h @@ -13,17 +13,18 @@ namespace Mist{ bool isReadyForPlay(); virtual void onFail(const std::string &msg, bool critical = false); virtual std::string getStatsName(){return Output::getStatsName();} + protected: std::string h264init(const std::string &initData); std::string h265init(const std::string &initData); bool hasSessionIDs(){return !config->getBool("mergesessions");} - std::string liveIndex(); - std::string liveIndex(size_t tid, const std::string &sessId); - size_t vidTrack; - size_t audTrack; - uint64_t until; + void sendHlsManifest(const std::string url); + void sendHlsMasterManifest(); + void sendHlsMediaManifest(const size_t requestTid); + + uint64_t targetTime; }; }// namespace Mist