HLS TS updated to use hls_support library for (LL)HLS manifest generation.
This commit is contained in:
		
							parent
							
								
									7848a78fbd
								
							
						
					
					
						commit
						5147d77b02
					
				
					 2 changed files with 262 additions and 209 deletions
				
			
		| 
						 | 
					@ -1,8 +1,13 @@
 | 
				
			||||||
#include "output_hls.h"
 | 
					#include "output_hls.h"
 | 
				
			||||||
#include <mist/langcodes.h> /*LTS*/
 | 
					#include <mist/hls_support.h>
 | 
				
			||||||
#include <mist/stream.h>
 | 
					// #include <mist/langcodes.h> /*LTS*/
 | 
				
			||||||
#include <mist/url.h>
 | 
					// #include <mist/stream.h>
 | 
				
			||||||
#include <unistd.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";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace Mist{
 | 
					namespace Mist{
 | 
				
			||||||
  bool OutHLS::isReadyForPlay(){
 | 
					  bool OutHLS::isReadyForPlay(){
 | 
				
			||||||
| 
						 | 
					@ -12,159 +17,17 @@ namespace Mist{
 | 
				
			||||||
    uint32_t mainTrack = M.mainTrack();
 | 
					    uint32_t mainTrack = M.mainTrack();
 | 
				
			||||||
    if (mainTrack == INVALID_TRACK_ID){return false;}
 | 
					    if (mainTrack == INVALID_TRACK_ID){return false;}
 | 
				
			||||||
    DTSC::Fragments fragments(M.fragments(mainTrack));
 | 
					    DTSC::Fragments fragments(M.fragments(mainTrack));
 | 
				
			||||||
    return fragments.getValidCount() > 4;
 | 
					    return fragments.getValidCount() > 6;
 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  ///\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;}
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    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;}
 | 
					 | 
				
			||||||
        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<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?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<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){
 | 
					  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;
 | 
					    uaDelay = 0;
 | 
				
			||||||
    realTime = 0;
 | 
					    realTime = 0;
 | 
				
			||||||
    until = 0xFFFFFFFFFFFFFFFFull;
 | 
					    targetTime = 0xFFFFFFFFFFFFFFFFull;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  OutHLS::~OutHLS(){}
 | 
					  OutHLS::~OutHLS(){}
 | 
				
			||||||
| 
						 | 
					@ -184,7 +47,7 @@ namespace Mist{
 | 
				
			||||||
    capa["codecs"][0u][4u].append("+MP3");
 | 
					    capa["codecs"][0u][4u].append("+MP3");
 | 
				
			||||||
    capa["codecs"][0u][5u].append("+AC3");
 | 
					    capa["codecs"][0u][5u].append("+AC3");
 | 
				
			||||||
    capa["codecs"][0u][6u].append("+MP2");
 | 
					    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]["handler"] = "http";
 | 
				
			||||||
    capa["methods"][0u]["type"] = "html5/application/vnd.apple.mpegurl";
 | 
					    capa["methods"][0u]["type"] = "html5/application/vnd.apple.mpegurl";
 | 
				
			||||||
    capa["methods"][0u]["hrn"] = "HLS (TS)";
 | 
					    capa["methods"][0u]["hrn"] = "HLS (TS)";
 | 
				
			||||||
| 
						 | 
					@ -194,11 +57,12 @@ namespace Mist{
 | 
				
			||||||
        "[[\"blacklist\",[\"Mozilla/"
 | 
					        "[[\"blacklist\",[\"Mozilla/"
 | 
				
			||||||
        "\"]],[\"whitelist\",[\"iPad\",\"iPhone\",\"iPod\",\"MacIntel\",\"Edge\"]]]");
 | 
					        "\"]],[\"whitelist\",[\"iPad\",\"iPhone\",\"iPod\",\"MacIntel\",\"Edge\"]]]");
 | 
				
			||||||
    capa["exceptions"]["codec:HEVC"] = JSON::fromString("[[\"blacklist\"]]");
 | 
					    capa["exceptions"]["codec:HEVC"] = JSON::fromString("[[\"blacklist\"]]");
 | 
				
			||||||
    /*LTS-START*/
 | 
					
 | 
				
			||||||
    cfg->addOption("listlimit",
 | 
					    cfg->addOption(
 | 
				
			||||||
                   JSON::fromString(
 | 
					        "listlimit",
 | 
				
			||||||
                       "{\"arg\":\"integer\",\"default\":0,\"short\":\"y\",\"long\":\"list-limit\","
 | 
					        JSON::fromString(
 | 
				
			||||||
                       "\"help\":\"Maximum number of parts in live playlists (0 = infinite).\"}"));
 | 
					            "{\"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"]["name"] = "Live playlist limit";
 | 
				
			||||||
    capa["optional"]["listlimit"]["help"] =
 | 
					    capa["optional"]["listlimit"]["help"] =
 | 
				
			||||||
        "Maximum number of parts in live playlists. (0 = infinite)";
 | 
					        "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 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.";
 | 
					        "If disabled, each view (main playlist request) is a separate session.";
 | 
				
			||||||
    capa["optional"]["mergesessions"]["option"] = "--mergesessions";
 | 
					    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(){
 | 
					  void OutHLS::onHTTP(){
 | 
				
			||||||
    std::string method = H.method;
 | 
					    initialize();
 | 
				
			||||||
    std::string sessId = H.GetVar("sessId");
 | 
					    bootMsOffset = 0;
 | 
				
			||||||
 | 
					    if (M.getLive()){bootMsOffset = M.getBootMsOffset();}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (H.url == "/crossdomain.xml"){
 | 
					    if (H.url == "/crossdomain.xml"){
 | 
				
			||||||
      H.SetHeader("Content-Type", "text/xml");
 | 
					      H.SetHeader("Content-Type", "text/xml");
 | 
				
			||||||
      H.SetHeader("Server", APPIDENT);
 | 
					      H.SetHeader("Server", APPIDENT);
 | 
				
			||||||
      H.setCORSHeaders();
 | 
					      H.setCORSHeaders();
 | 
				
			||||||
      if (method == "OPTIONS" || method == "HEAD"){
 | 
					      if (H.method == "OPTIONS" || H.method == "HEAD"){
 | 
				
			||||||
        H.SendResponse("200", "OK", myConn);
 | 
					        H.SendResponse("200", "OK", myConn);
 | 
				
			||||||
        return;
 | 
					        return;
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
| 
						 | 
					@ -254,12 +250,15 @@ namespace Mist{
 | 
				
			||||||
      }else{
 | 
					      }else{
 | 
				
			||||||
        H.SetHeader("Content-Type", "application/vnd.apple.mpegurl");
 | 
					        H.SetHeader("Content-Type", "application/vnd.apple.mpegurl");
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      if (isTS && !hasSessionIDs()){
 | 
					      if (isTS && (!hasSessionIDs() || config->getOption("chunkpath"))){
 | 
				
			||||||
        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("Pragma", "");
 | 
				
			||||||
        H.SetHeader("Expires", "");
 | 
					        H.SetHeader("Expires", "");
 | 
				
			||||||
      }else{
 | 
					      }else{
 | 
				
			||||||
        H.SetHeader("Cache-Control", "no-cache");
 | 
					        H.SetHeader("Cache-Control", "no-store");
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      H.SetBody("");
 | 
					      H.SetBody("");
 | 
				
			||||||
      H.SendResponse("200", "OK", myConn);
 | 
					      H.SendResponse("200", "OK", myConn);
 | 
				
			||||||
| 
						 | 
					@ -286,99 +285,152 @@ namespace Mist{
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (HTTP::URL(H.url).getExt().substr(0, 3) != "m3u"){
 | 
					    if (HTTP::URL(H.url).getExt().substr(0, 3) != "m3u"){
 | 
				
			||||||
      std::string tmpStr = H.getUrl().substr(5 + streamName.size());
 | 
					      std::string tmpStr = H.getUrl().substr(5 + streamName.size());
 | 
				
			||||||
      uint64_t from;
 | 
					      std::string url = H.url.substr(H.url.find('/', 5) + 1); // Strip /hls/<streamname>/ from url
 | 
				
			||||||
      if (sscanf(tmpStr.c_str(), "/%zu_%zu/%" PRIu64 "_%" PRIu64 ".ts", &vidTrack, &audTrack, &from, &until) != 4){
 | 
					      const uint64_t msn = atoll(H.GetVar("msn").c_str());
 | 
				
			||||||
        if (sscanf(tmpStr.c_str(), "/%zu/%" PRIu64 "_%" PRIu64 ".ts", &vidTrack, &from, &until) != 3){
 | 
					      const uint64_t dur = atoll(H.GetVar("dur").c_str());
 | 
				
			||||||
          MEDIUM_MSG("Could not parse URL: %s", H.getUrl().c_str());
 | 
					      const uint64_t mTrack = atoll(H.GetVar("mTrack").c_str());
 | 
				
			||||||
          H.setCORSHeaders();
 | 
					      size_t idx = atoll(url.c_str());
 | 
				
			||||||
          H.SetBody("The HLS URL wasn't understood - what did you want, exactly?\n");
 | 
					
 | 
				
			||||||
          myConn.SendNow(H.BuildResponse("404", "URL mismatch"));
 | 
					      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;
 | 
					          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.clear();
 | 
				
			||||||
        userSelect[vidTrack].reload(streamName, vidTrack);
 | 
					        userSelect[idx].reload(streamName, idx);
 | 
				
			||||||
 | 
					        userSelect[*aTracks.begin()].reload(streamName, *aTracks.begin());
 | 
				
			||||||
      }else{
 | 
					      }else{
 | 
				
			||||||
        userSelect.clear();
 | 
					        userSelect.clear();
 | 
				
			||||||
        userSelect[vidTrack].reload(streamName, vidTrack);
 | 
					        userSelect[idx].reload(streamName, idx);
 | 
				
			||||||
        userSelect[audTrack].reload(streamName, audTrack);
 | 
					 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      std::set<size_t> validTracks = getSupportedTracks();
 | 
					      std::set<size_t> validTracks = getSupportedTracks();
 | 
				
			||||||
      for (std::set<size_t>::iterator it = validTracks.begin(); it != validTracks.end(); ++it){
 | 
					      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.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.setCORSHeaders();
 | 
				
			||||||
        H.SetBody("The requested fragment is no longer kept in memory on the server and cannot be "
 | 
					        H.SetBody("The requested fragment is no longer kept in memory on the server and cannot be "
 | 
				
			||||||
                  "served.\n");
 | 
					                  "served.\n");
 | 
				
			||||||
        myConn.SendNow(H.BuildResponse("404", "Fragment out of range"));
 | 
					        myConn.SendNow(H.BuildResponse("404", "Fragment out of range"));
 | 
				
			||||||
        WARN_MSG("Fragment @ %" PRIu64 " too old", from);
 | 
					        WARN_MSG("Fragment @ %" PRIu64 " too old", startTime);
 | 
				
			||||||
        return;
 | 
					        return;
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      H.SetHeader("Content-Type", "video/mp2t");
 | 
					      H.SetHeader("Content-Type", "video/mp2t");
 | 
				
			||||||
      H.setCORSHeaders();
 | 
					      H.setCORSHeaders();
 | 
				
			||||||
      if (hasSessionIDs()){
 | 
					      if (hasSessionIDs() && !config->getOption("chunkpath")){
 | 
				
			||||||
        H.SetHeader("Cache-Control", "no-cache");
 | 
					        H.SetHeader("Cache-Control", "no-store");
 | 
				
			||||||
      }else{
 | 
					      }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("Pragma", "");
 | 
				
			||||||
        H.SetHeader("Expires", "");
 | 
					        H.SetHeader("Expires", "");
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      if (method == "OPTIONS" || method == "HEAD"){
 | 
					      if (H.method == "OPTIONS" || H.method == "HEAD"){
 | 
				
			||||||
        H.SendResponse("200", "OK", myConn);
 | 
					        H.SendResponse("200", "OK", myConn);
 | 
				
			||||||
        return;
 | 
					        return;
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      H.StartResponse(H, myConn, VLCworkaround || config->getBool("nonchunked"));
 | 
					      H.StartResponse(H, myConn, VLCworkaround || config->getBool("nonchunked"));
 | 
				
			||||||
      // we assume whole fragments - but timestamps may be altered at will
 | 
					      // we assume whole fragments - but timestamps may be altered at will
 | 
				
			||||||
      uint32_t fragIndice = M.getFragmentIndexForTime(vidTrack, from);
 | 
					      contPAT = fragmentIndex; // PAT continuity counter
 | 
				
			||||||
      contPAT = fragIndice; // PAT continuity counter
 | 
					      contPMT = fragmentIndex; // PMT continuity counter
 | 
				
			||||||
      contPMT = fragIndice; // PMT continuity counter
 | 
					      contSDT = fragmentIndex; // SDT continuity counter
 | 
				
			||||||
      contSDT = fragIndice; // SDT continuity counter
 | 
					 | 
				
			||||||
      packCounter = 0;
 | 
					      packCounter = 0;
 | 
				
			||||||
      parseData = true;
 | 
					      parseData = true;
 | 
				
			||||||
      wantRequest = false;
 | 
					      wantRequest = false;
 | 
				
			||||||
      seek(from);
 | 
					      seek(startTime);
 | 
				
			||||||
      ts_from = from;
 | 
					      ts_from = startTime;
 | 
				
			||||||
    }else{
 | 
					    }else{
 | 
				
			||||||
      initialize();
 | 
					      initialize();
 | 
				
			||||||
      std::string request = H.url.substr(H.url.find("/", 5) + 1);
 | 
					
 | 
				
			||||||
      H.setCORSHeaders();
 | 
					      H.setCORSHeaders();
 | 
				
			||||||
      H.SetHeader("Content-Type", "application/vnd.apple.mpegurl");
 | 
					      H.SetHeader("Content-Type", "application/vnd.apple.mpegurl");
 | 
				
			||||||
      if (!M.getValidTracks().size()){
 | 
					      if (!M.getValidTracks().size()){
 | 
				
			||||||
        H.SendResponse("404", "Not online or found", myConn);
 | 
					        H.SendResponse("404", "Not online or found", myConn);
 | 
				
			||||||
        return;
 | 
					        return;
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      if (method == "OPTIONS" || method == "HEAD"){
 | 
					      if (H.method == "OPTIONS" || H.method == "HEAD"){
 | 
				
			||||||
        H.SendResponse("200", "OK", myConn);
 | 
					        H.SendResponse("200", "OK", myConn);
 | 
				
			||||||
        return;
 | 
					        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);
 | 
					      // Strip /hls/<streamname>/ from url
 | 
				
			||||||
      }
 | 
					      std::string url = H.url.substr(H.url.find('/', 5) + 1);
 | 
				
			||||||
      H.SetBody(manifest);
 | 
					      sendHlsManifest(url);
 | 
				
			||||||
      H.SendResponse("200", "OK", myConn);
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  void OutHLS::sendNext(){
 | 
					  void OutHLS::sendNext(){
 | 
				
			||||||
    // First check if we need to stop.
 | 
					    // First check if we need to stop.
 | 
				
			||||||
    if (thisPacket.getTime() >= until){
 | 
					    if (thisPacket.getTime() >= targetTime){
 | 
				
			||||||
      stop();
 | 
					      stop();
 | 
				
			||||||
      wantRequest = true;
 | 
					      wantRequest = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      // Ensure alignment of contCounters, to prevent discontinuities.
 | 
					      // 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){
 | 
					        if (it->second % 16 != 0){
 | 
				
			||||||
          packData.clear();
 | 
					          packData.clear();
 | 
				
			||||||
          packData.setPID(it->first);
 | 
					          packData.setPID(it->first);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -13,17 +13,18 @@ namespace Mist{
 | 
				
			||||||
    bool isReadyForPlay();
 | 
					    bool isReadyForPlay();
 | 
				
			||||||
    virtual void onFail(const std::string &msg, bool critical = false);
 | 
					    virtual void onFail(const std::string &msg, bool critical = false);
 | 
				
			||||||
    virtual std::string getStatsName(){return Output::getStatsName();}
 | 
					    virtual std::string getStatsName(){return Output::getStatsName();}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  protected:
 | 
					  protected:
 | 
				
			||||||
    std::string h264init(const std::string &initData);
 | 
					    std::string h264init(const std::string &initData);
 | 
				
			||||||
    std::string h265init(const std::string &initData);
 | 
					    std::string h265init(const std::string &initData);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    bool hasSessionIDs(){return !config->getBool("mergesessions");}
 | 
					    bool hasSessionIDs(){return !config->getBool("mergesessions");}
 | 
				
			||||||
    std::string liveIndex();
 | 
					 | 
				
			||||||
    std::string liveIndex(size_t tid, const std::string &sessId);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    size_t vidTrack;
 | 
					    void sendHlsManifest(const std::string url);
 | 
				
			||||||
    size_t audTrack;
 | 
					    void sendHlsMasterManifest();
 | 
				
			||||||
    uint64_t until;
 | 
					    void sendHlsMediaManifest(const size_t requestTid);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    uint64_t targetTime;
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
}// namespace Mist
 | 
					}// namespace Mist
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue