diff --git a/lib/defines.h b/lib/defines.h index 895e5d36..756d4aa1 100644 --- a/lib/defines.h +++ b/lib/defines.h @@ -175,3 +175,5 @@ static inline void show_stackframe(){} #define UDP_API_PORT 4242 #endif +#define INVALID_TRACK_ID 0 + diff --git a/lib/stream.cpp b/lib/stream.cpp index b27b871d..b0fb00b1 100644 --- a/lib/stream.cpp +++ b/lib/stream.cpp @@ -12,6 +12,8 @@ #include "triggers.h" //LTS #include "h265.h" #include "mp4_generic.h" +#include "langcodes.h" +#include "http_parser.h" #include #include #include @@ -364,8 +366,7 @@ bool Util::startInput(std::string streamname, std::string filename, bool forkFir str_args[opt] = overrides.at(opt); }else{ if (!stream_cfg.isMember(prm.key())){ - FAIL_MSG("Required parameter %s for stream %s missing", prm.key().c_str(), - streamname.c_str()); + FAIL_MSG("Required parameter %s for stream %s missing", prm.key().c_str(), streamname.c_str()); return false; } str_args[opt] = stream_cfg[opt].asStringRef(); @@ -402,8 +403,7 @@ bool Util::startInput(std::string streamname, std::string filename, bool forkFir argv[++argNum] = (char *)"--debug"; argv[++argNum] = (char *)debugLvl.c_str(); } - for (std::map::iterator it = str_args.begin(); it != str_args.end(); - ++it){ + for (std::map::iterator it = str_args.begin(); it != str_args.end(); ++it){ argv[++argNum] = (char *)it->first.c_str(); if (it->second.size()){argv[++argNum] = (char *)it->second.c_str();} } @@ -428,12 +428,14 @@ bool Util::startInput(std::string streamname, std::string filename, bool forkFir } if (pid == 0){ + for (std::set::iterator it = Util::Procs::socketList.begin(); it != Util::Procs::socketList.end(); ++it){ + close(*it); + } Socket::Connection io(0, 1); io.drop(); DONTEVEN_MSG("execvp"); execvp(argv[0], argv); - FAIL_MSG("Starting process %s for stream %s failed: %s", argv[0], streamname.c_str(), - strerror(errno)); + FAIL_MSG("Starting process %s for stream %s failed: %s", argv[0], streamname.c_str(), strerror(errno)); _exit(42); }else if (spawn_pid != NULL){ *spawn_pid = pid; @@ -629,9 +631,7 @@ bool Util::checkException(const JSON::Value & ex, const std::string & useragent) if (!(*e)[1].isArray()){continue;} bool match = false; jsonForEachConst((*e)[1u], i){ - if (useragent.find(i->asStringRef()) != std::string::npos){ - match = true; - } + if (useragent.find(i->asStringRef()) != std::string::npos){match = true;} } //set the (temp) return value if this was either a match in regular mode, or a non-match in except-mode. if (except != match){ret = setTo;} @@ -646,8 +646,7 @@ Util::DTSCShmReader::DTSCShmReader(const std::string &pageName){ DTSC::Scan Util::DTSCShmReader::getMember(const std::string &indice){ if (!rPage){return DTSC::Scan();} - return DTSC::Scan(rAcc.getPointer("dtsc_data"), rAcc.getSize("dtsc_data")) - .getMember(indice.c_str()); + return DTSC::Scan(rAcc.getPointer("dtsc_data"), rAcc.getSize("dtsc_data")).getMember(indice.c_str()); } DTSC::Scan Util::DTSCShmReader::getScan(){ @@ -655,3 +654,580 @@ DTSC::Scan Util::DTSCShmReader::getScan(){ return DTSC::Scan(rAcc.getPointer("dtsc_data"), rAcc.getSize("dtsc_data")); } +std::set Util::findTracks(const DTSC::Meta &M, const JSON::Value &capa, const std::string &trackType, const std::string &trackVal, const std::string &UA){ + std::set result; + if (!trackVal.size() || trackVal == "0" || trackVal == "-1" || trackVal == "none"){return result;}//don't select anything in particular + if (trackVal.find(',') != std::string::npos){ + // Comma-separated list, recurse. + std::stringstream ss(trackVal); + std::string item; + while (std::getline(ss, item, ',')){ + std::set items = findTracks(M, capa, trackType, item); + result.insert(items.begin(), items.end()); + } + return result; + } + { + size_t trackNo = JSON::Value(trackVal).asInt(); + if (trackVal == JSON::Value(trackNo).asString()){ + //It's an integer number + if (!M.tracks.count(trackNo)){ + INFO_MSG("Track %lld does not exist in stream, cannot select", trackNo); + return result; + } + const DTSC::Track & Trk = M.tracks.at(trackNo); + if (Trk.type != trackType && Trk.codec != trackType){ + INFO_MSG("Track %lld is not %s (%s/%s), cannot select", trackNo, trackType.c_str(), Trk.type.c_str(), Trk.codec.c_str()); + return result; + } + INFO_MSG("Selecting %s track %lld (%s/%s)", trackType.c_str(), trackNo, Trk.type.c_str(), Trk.codec.c_str()); + result.insert(trackNo); + return result; + } + } + std::string trackLow = trackVal; + Util::stringToLower(trackLow); + if (trackLow == "all" || trackLow == "*"){ + // select all tracks of this type + std::set validTracks = getSupportedTracks(M, capa); + for (std::set::iterator it = validTracks.begin(); it != validTracks.end(); it++){ + const DTSC::Track & Trk = M.tracks.at(*it); + if (!trackType.size() || Trk.type == trackType || Trk.codec == trackType){result.insert(*it);} + } + return result; + } + if (trackLow == "highbps" || trackLow == "bestbps" || trackLow == "maxbps"){ + // select highest bit rate track of this type + size_t currVal = INVALID_TRACK_ID; + uint32_t currRate = 0; + std::set validTracks = getSupportedTracks(M, capa); + for (std::set::iterator it = validTracks.begin(); it != validTracks.end(); it++){ + const DTSC::Track & Trk = M.tracks.at(*it); + if (!trackType.size() || Trk.type == trackType || Trk.codec == trackType){ + if (currRate < Trk.bps){ + currVal = *it; + currRate = Trk.bps; + } + } + } + if (currVal != INVALID_TRACK_ID){result.insert(currVal);} + return result; + } + if (trackLow == "lowbps" || trackLow == "worstbps" || trackLow == "minbps"){ + // select lowest bit rate track of this type + size_t currVal = INVALID_TRACK_ID; + uint32_t currRate = 0xFFFFFFFFul; + std::set validTracks = getSupportedTracks(M, capa); + for (std::set::iterator it = validTracks.begin(); it != validTracks.end(); it++){ + const DTSC::Track & Trk = M.tracks.at(*it); + if (!trackType.size() || Trk.type == trackType || Trk.codec == trackType){ + if (currRate > Trk.bps){ + currVal = *it; + currRate = Trk.bps; + } + } + } + if (currVal != INVALID_TRACK_ID){result.insert(currVal);} + return result; + } + //less-than or greater-than track matching on bit rate or resolution + if (trackLow[0] == '<' || trackLow[0] == '>'){ + unsigned int bpsVal; + uint64_t targetBps = 0; + if (trackLow.find("bps") != std::string::npos && sscanf(trackLow.c_str(), "<%ubps", &bpsVal) == 1){targetBps = bpsVal;} + if (trackLow.find("kbps") != std::string::npos && sscanf(trackLow.c_str(), "<%ukbps", &bpsVal) == 1){targetBps = bpsVal*1024;} + if (trackLow.find("mbps") != std::string::npos && sscanf(trackLow.c_str(), "<%umbps", &bpsVal) == 1){targetBps = bpsVal*1024*1024;} + if (trackLow.find("bps") != std::string::npos && sscanf(trackLow.c_str(), ">%ubps", &bpsVal) == 1){targetBps = bpsVal;} + if (trackLow.find("kbps") != std::string::npos && sscanf(trackLow.c_str(), ">%ukbps", &bpsVal) == 1){targetBps = bpsVal*1024;} + if (trackLow.find("mbps") != std::string::npos && sscanf(trackLow.c_str(), ">%umbps", &bpsVal) == 1){targetBps = bpsVal*1024*1024;} + if (targetBps){ + targetBps >>= 3; + // select all tracks of this type that match the requirements + std::set validTracks = getSupportedTracks(M, capa); + for (std::set::iterator it = validTracks.begin(); it != validTracks.end(); it++){ + const DTSC::Track & Trk = M.tracks.at(*it); + if (!trackType.size() || Trk.type == trackType || Trk.codec == trackType){ + if (trackLow[0] == '>' && Trk.bps > targetBps){result.insert(*it);} + if (trackLow[0] == '<' && Trk.bps < targetBps){result.insert(*it);} + } + } + return result; + } + unsigned int resX, resY; + uint64_t targetArea = 0; + if (sscanf(trackLow.c_str(), "<%ux%u", &resX, &resY) == 2){targetArea = resX*resY;} + if (sscanf(trackLow.c_str(), ">%ux%u", &resX, &resY) == 2){targetArea = resX*resY;} + if (targetArea){ + std::set validTracks = getSupportedTracks(M, capa); + for (std::set::iterator it = validTracks.begin(); it != validTracks.end(); it++){ + const DTSC::Track & Trk = M.tracks.at(*it); + if (!trackType.size() || Trk.type == trackType || Trk.codec == trackType){ + uint64_t trackArea = Trk.width*Trk.height; + if (trackLow[0] == '>' && trackArea > targetArea){result.insert(*it);} + if (trackLow[0] == '<' && trackArea < targetArea){result.insert(*it);} + } + } + return result; + } + } + //approx bitrate matching + { + unsigned int bpsVal; + uint64_t targetBps = 0; + if (trackLow.find("bps") != std::string::npos && sscanf(trackLow.c_str(), "%ubps", &bpsVal) == 1){targetBps = bpsVal;} + if (trackLow.find("kbps") != std::string::npos && sscanf(trackLow.c_str(), "%ukbps", &bpsVal) == 1){targetBps = bpsVal*1024;} + if (trackLow.find("mbps") != std::string::npos && sscanf(trackLow.c_str(), "%umbps", &bpsVal) == 1){targetBps = bpsVal*1024*1024;} + if (targetBps){ + targetBps >>= 3; + // select nearest bit rate track of this type + size_t currVal = INVALID_TRACK_ID; + uint32_t currDist = 0; + std::set validTracks = getSupportedTracks(M, capa); + for (std::set::iterator it = validTracks.begin(); it != validTracks.end(); it++){ + const DTSC::Track & Trk = M.tracks.at(*it); + if (!trackType.size() || Trk.type == trackType || Trk.codec == trackType){ + if (currVal == INVALID_TRACK_ID || (Trk.bps >= targetBps && currDist > (Trk.bps-targetBps)) || (Trk.bps < targetBps && currDist > (targetBps-Trk.bps))){ + currVal = *it; + currDist = (Trk.bps >= targetBps)?(Trk.bps-targetBps):(targetBps-Trk.bps); + } + } + } + if (currVal != INVALID_TRACK_ID){result.insert(currVal);} + return result; + } + } + //approx resolution matching + if (!trackType.size() || trackType == "video"){ + if (trackLow == "highres" || trackLow == "bestres" || trackLow == "maxres"){ + // select highest resolution track of this type + size_t currVal = INVALID_TRACK_ID; + uint64_t currRes = 0; + std::set validTracks = getSupportedTracks(M, capa); + for (std::set::iterator it = validTracks.begin(); it != validTracks.end(); it++){ + const DTSC::Track & Trk = M.tracks.at(*it); + if (!trackType.size() || Trk.type == trackType || Trk.codec == trackType){ + uint64_t trackRes = Trk.width*Trk.height; + if (currRes < trackRes){ + currVal = *it; + currRes = trackRes; + } + } + } + if (currVal != INVALID_TRACK_ID){result.insert(currVal);} + return result; + } + if (trackLow == "lowres" || trackLow == "worstres" || trackLow == "minres"){ + // select lowest resolution track of this type + size_t currVal = INVALID_TRACK_ID; + uint64_t currRes = 0xFFFFFFFFFFFFFFFFull; + std::set validTracks = getSupportedTracks(M, capa); + for (std::set::iterator it = validTracks.begin(); it != validTracks.end(); it++){ + const DTSC::Track & Trk = M.tracks.at(*it); + if (!trackType.size() || Trk.type == trackType || Trk.codec == trackType){ + uint64_t trackRes = Trk.width*Trk.height; + if (currRes > trackRes){ + currVal = *it; + currRes = trackRes; + } + } + } + if (currVal != INVALID_TRACK_ID){result.insert(currVal);} + return result; + } + { + unsigned int resX, resY; + if (sscanf(trackLow.c_str(), "~%ux%u", &resX, &resY) == 2){ + // select nearest resolution track of this type + size_t currVal = INVALID_TRACK_ID; + uint64_t currDist = 0; + uint64_t targetArea = resX*resY; + std::set validTracks = getSupportedTracks(M, capa); + for (std::set::iterator it = validTracks.begin(); it != validTracks.end(); it++){ + const DTSC::Track & Trk = M.tracks.at(*it); + if (!trackType.size() || Trk.type == trackType || Trk.codec == trackType){ + uint64_t trackArea = Trk.width*Trk.height; + if (currVal == INVALID_TRACK_ID || (trackArea >= targetArea && currDist > (trackArea-targetArea)) || (trackArea < targetArea && currDist > (targetArea-trackArea))){ + currVal = *it; + currDist = (trackArea >= targetArea)?(trackArea-targetArea):(targetArea-trackArea); + } + } + } + if (currVal != INVALID_TRACK_ID){result.insert(currVal);} + return result; + } + } + }//video track specific + // attempt to do language/codec matching + // convert 2-character language codes into 3-character language codes + if (trackLow.size() == 2){trackLow = Encodings::ISO639::twoToThree(trackLow);} + std::set validTracks = getSupportedTracks(M, capa); + for (std::set::iterator it = validTracks.begin(); it != validTracks.end(); it++){ + const DTSC::Track & Trk = M.tracks.at(*it); + if (!trackType.size() || Trk.type == trackType || Trk.codec == trackType){ + std::string codecLow = Trk.codec; + Util::stringToLower(codecLow); + if (Trk.lang == trackLow || trackLow == codecLow){result.insert(*it);} + if (!trackType.size() || trackType == "video"){ + unsigned int resX, resY; + if (trackLow == "720p" && Trk.width == 1280 && Trk.height == 720){result.insert(*it);} + if (trackLow == "1080p" && Trk.width == 1920 && Trk.height == 1080){result.insert(*it);} + if (trackLow == "1440p" && Trk.width == 2560 && Trk.height == 1440){result.insert(*it);} + if (trackLow == "2k" && Trk.width == 2048 && Trk.height == 1080){result.insert(*it);} + if (trackLow == "4k" && Trk.width == 3840 && Trk.height == 2160){result.insert(*it);} + if (trackLow == "5k" && Trk.width == 5120 && Trk.height == 2880){result.insert(*it);} + if (trackLow == "8k" && Trk.width == 7680 && Trk.height == 4320){result.insert(*it);} + //match "XxY" format + if (sscanf(trackLow.c_str(), "%ux%u", &resX, &resY) == 2){ + if (Trk.width == resX && Trk.height == resY){result.insert(*it);} + } + } + } + } + return result; +} + +std::set Util::wouldSelect(const DTSC::Meta &M, const std::string &trackSelector, + const JSON::Value &capa, const std::string &UA){ + std::map parsedVariables; + HTTP::parseVars(trackSelector, parsedVariables); + + return wouldSelect(M, parsedVariables, capa, UA); +} + +std::set Util::getSupportedTracks(const DTSC::Meta &M, const JSON::Value &capa, + const std::string &type, const std::string &UA){ + std::set validTracks; + for (std::map::const_iterator it = M.tracks.begin(); it != M.tracks.end(); it++){ + const DTSC::Track & Trk = it->second; + if (type != "" && type != Trk.type){ + continue; + } + // Remove tracks for which we don't have codec support + if (capa.isMember("codecs")){ + std::string codec = Trk.codec; + std::string type = Trk.type; + bool found = false; + jsonForEachConst(capa["codecs"], itb){ + jsonForEachConst(*itb, itc){ + jsonForEachConst(*itc, itd){ + const std::string &strRef = (*itd).asStringRef(); + bool byType = false; + uint8_t shift = 0; + if (strRef[shift] == '@'){ + byType = true; + ++shift; + } + if (strRef[shift] == '+'){ + ++shift; + }// Multiselect is ignored here, we only need to determine the types... + if ((!byType && codec == strRef.substr(shift)) || + (byType && type == strRef.substr(shift)) || strRef.substr(shift) == "*"){ + // user-agent-check + bool problems = false; + if (capa.isMember("exceptions") && capa["exceptions"].isObject() && capa["exceptions"].size()){ + jsonForEachConst(capa["exceptions"], ex){ + if (ex.key() == "codec:" + strRef.substr(shift)){ + problems = !Util::checkException(*ex, UA); + break; + } + } + } + if (problems){break;} + found = true; + break; + } + } + if (found){break;} + } + if (found){break;} + } + if (!found){ + HIGH_MSG("Track %zu with codec %s not supported!", *it, codec.c_str()); + continue; + } + } + validTracks.insert(it->first); + } + return validTracks; +} + +std::set Util::wouldSelect(const DTSC::Meta &M, const std::map &targetParams, + const JSON::Value &capa, const std::string &UA, uint64_t seekTarget){ + std::set result; + + /*LTS-START*/ + bool noSelAudio = false, noSelVideo = false, noSelSub = false; + // Then, select the tracks we've been asked to select. + if (targetParams.count("audio") && targetParams.at("audio").size()){ + if (targetParams.at("audio") != "-1" && targetParams.at("audio") != "none"){ + std::set tracks = Util::findTracks(M, capa, "audio", targetParams.at("audio")); + result.insert(tracks.begin(), tracks.end()); + } + noSelAudio = true; + } + if (targetParams.count("video") && targetParams.at("video").size()){ + if (targetParams.at("video") != "-1" && targetParams.at("video") != "none"){ + std::set tracks = Util::findTracks(M, capa, "video", targetParams.at("video")); + result.insert(tracks.begin(), tracks.end()); + } + noSelVideo = true; + } + if (targetParams.count("subtitle") && targetParams.at("subtitle").size()){ + std::set tracks = Util::findTracks(M, capa, "subtitle", targetParams.at("subtitle")); + result.insert(tracks.begin(), tracks.end()); + noSelSub = true; + } + /*LTS-END*/ + + std::set validTracks = getSupportedTracks(M, capa); + + // check which tracks don't actually exist + std::set toRemove; + for (std::set::iterator it = result.begin(); it != result.end(); it++){ + if (!validTracks.count(*it)){ + toRemove.insert(*it); + continue; + } + //autoSeeking and target not in bounds? Drop it too. + if (seekTarget && M.tracks.at(*it).lastms < std::max(seekTarget, (uint64_t)6000lu) - 6000){ + toRemove.insert(*it); + } + } + // remove those from selectedtracks + for (std::set::iterator it = toRemove.begin(); it != toRemove.end(); it++){ + result.erase(*it); + } + + // loop through all codec combinations, count max simultaneous active + unsigned int bestSoFar = 0; + unsigned int bestSoFarCount = 0; + unsigned int index = 0; + bool allowBFrames = true; + if (capa.isMember("methods")){ + jsonForEachConst(capa["methods"], mthd){ + if (mthd->isMember("nobframes") && (*mthd)["nobframes"]){ + allowBFrames = false; + break; + } + } + } + + /*LTS-START*/ + if (!capa.isMember("codecs")){ + for (std::set::iterator trit = validTracks.begin(); trit != validTracks.end(); trit++){ + const DTSC::Track & Trk = M.tracks.at(*trit); + bool problems = false; + if (capa.isMember("exceptions") && capa["exceptions"].isObject() && + capa["exceptions"].size()){ + jsonForEachConst(capa["exceptions"], ex){ + if (ex.key() == "codec:" + Trk.codec){ + problems = !Util::checkException(*ex, UA); + break; + } + } + } + //if (!allowBFrames && M.hasBFrames(*trit)){problems = true;} + if (problems){continue;} + if (noSelAudio && Trk.type == "audio"){continue;} + if (noSelVideo && Trk.type == "video"){continue;} + if (noSelSub && (Trk.type == "subtitle" || Trk.codec == "subtitle")){continue;} + result.insert(*trit); + } + return result; + } + /*LTS-END*/ + + + jsonForEachConst(capa["codecs"], it){ + unsigned int selCounter = 0; + if ((*it).size() > 0){ + jsonForEachConst((*it), itb){ + if ((*itb).size() > 0){ + jsonForEachConst(*itb, itc){ + const std::string &strRef = (*itc).asStringRef(); + bool byType = false; + bool multiSel = false; + uint8_t shift = 0; + if (strRef[shift] == '@'){ + byType = true; + ++shift; + } + if (strRef[shift] == '+'){ + multiSel = true; + ++shift; + } + for (std::set::iterator itd = result.begin(); itd != result.end(); itd++){ + const DTSC::Track & Trk = M.tracks.at(*itd); + if ((!byType && Trk.codec == strRef.substr(shift)) || + (byType && Trk.type == strRef.substr(shift)) || strRef.substr(shift) == "*"){ + // user-agent-check + bool problems = false; + if (capa.isMember("exceptions") && capa["exceptions"].isObject() && + capa["exceptions"].size()){ + jsonForEachConst(capa["exceptions"], ex){ + if (ex.key() == "codec:" + strRef.substr(shift)){ + problems = !Util::checkException(*ex, UA); + break; + } + } + } + if (problems){break;} + selCounter++; + if (!multiSel){break;} + } + } + } + } + } + if (selCounter == result.size()){ + if (selCounter > bestSoFarCount){ + bestSoFarCount = selCounter; + bestSoFar = index; + HIGH_MSG("Matched %u: %s", selCounter, (*it).toString().c_str()); + } + }else{ + VERYHIGH_MSG("Not a match for currently selected tracks: %s", it->toString().c_str()); + } + } + index++; + } + + HIGH_MSG("Trying to fill: %s", capa["codecs"][bestSoFar].toString().c_str()); + // try to fill as many codecs simultaneously as possible + if (capa["codecs"][bestSoFar].size() > 0){ + jsonForEachConst(capa["codecs"][bestSoFar], itb){ + if (itb->size() && validTracks.size()){ + bool found = false; + bool multiFind = false; + jsonForEachConst((*itb), itc){ + const std::string &strRef = (*itc).asStringRef(); + bool byType = false; + uint8_t shift = 0; + if (strRef[shift] == '@'){ + byType = true; + ++shift; + } + if (strRef[shift] == '+'){ + multiFind = true; + ++shift; + } + for (std::set::iterator itd = result.begin(); itd != result.end(); itd++){ + const DTSC::Track & Trk = M.tracks.at(*itd); + if ((!byType && Trk.codec == strRef.substr(shift)) || + (byType && Trk.type == strRef.substr(shift)) || strRef.substr(shift) == "*"){ + // user-agent-check + bool problems = false; + if (capa.isMember("exceptions") && capa["exceptions"].isObject() && capa["exceptions"].size()){ + jsonForEachConst(capa["exceptions"], ex){ + if (ex.key() == "codec:" + strRef.substr(shift)){ + problems = !Util::checkException(*ex, UA); + break; + } + } + } + if (problems){break;} + found = true; + break; + } + } + } + if (!found || multiFind){ + jsonForEachConst((*itb), itc){ + const std::string &strRef = (*itc).asStringRef(); + bool byType = false; + bool multiSel = false; + uint8_t shift = 0; + if (strRef[shift] == '@'){ + byType = true; + ++shift; + } + if (strRef[shift] == '+'){ + multiSel = true; + ++shift; + } + if (found && !multiSel){continue;} + if (M.live){ + for (std::set::reverse_iterator trit = validTracks.rbegin(); + trit != validTracks.rend(); trit++){ + const DTSC::Track & Trk = M.tracks.at(*trit); + if ((!byType && Trk.codec == strRef.substr(shift)) || + (byType && Trk.type == strRef.substr(shift)) || strRef.substr(shift) == "*"){ + // user-agent-check + bool problems = false; + if (capa.isMember("exceptions") && capa["exceptions"].isObject() && + capa["exceptions"].size()){ + jsonForEachConst(capa["exceptions"], ex){ + if (ex.key() == "codec:" + strRef.substr(shift)){ + problems = !Util::checkException(*ex, UA); + break; + } + } + } + //if (!allowBFrames && M.hasBFrames(*trit)){problems = true;} + if (problems){break;} + /*LTS-START*/ + if (noSelAudio && Trk.type == "audio"){continue;} + if (noSelVideo && Trk.type == "video"){continue;} + if (noSelSub && + (Trk.type == "subtitle" || Trk.codec == "subtitle")){ + continue; + } + /*LTS-END*/ + result.insert(*trit); + found = true; + if (!multiSel){break;} + } + } + }else{ + for (std::set::iterator trit = validTracks.begin(); trit != validTracks.end(); trit++){ + const DTSC::Track & Trk = M.tracks.at(*trit); + if ((!byType && Trk.codec == strRef.substr(shift)) || + (byType && Trk.type == strRef.substr(shift)) || strRef.substr(shift) == "*"){ + // user-agent-check + bool problems = false; + if (capa.isMember("exceptions") && capa["exceptions"].isObject() && + capa["exceptions"].size()){ + jsonForEachConst(capa["exceptions"], ex){ + if (ex.key() == "codec:" + strRef.substr(shift)){ + problems = !Util::checkException(*ex, UA); + break; + } + } + } + //if (!allowBFrames && M.hasBFrames(*trit)){problems = true;} + if (problems){break;} + /*LTS-START*/ + if (noSelAudio && Trk.type == "audio"){continue;} + if (noSelVideo && Trk.type == "video"){continue;} + if (noSelSub && + (Trk.type == "subtitle" || Trk.type == "subtitle")){ + continue; + } + /*LTS-END*/ + result.insert(*trit); + found = true; + if (!multiSel){break;} + } + } + } + } + } + } + } + } + + if (Util::Config::printDebugLevel >= DLVL_MEDIUM){ + // print the selected tracks + std::stringstream selected; + for (std::set::iterator it = result.begin(); it != result.end(); it++){ + if (it != result.begin()){selected << ", ";} + selected << *it; + } + MEDIUM_MSG("Selected tracks: %s (%zu)", selected.str().c_str(), result.size()); + } + + if (!result.size() && validTracks.size() && capa["codecs"][bestSoFar].size()){ + WARN_MSG("No tracks selected (%zu total) for stream!", validTracks.size()); + } + return result; +} diff --git a/lib/stream.h b/lib/stream.h index eab26acf..28b0c471 100644 --- a/lib/stream.h +++ b/lib/stream.h @@ -9,6 +9,8 @@ #include "shared_memory.h" #include "util.h" +const JSON::Value empty; + namespace Util { void streamVariables(std::string &str, const std::string & streamname, const std::string & source = ""); std::string getTmpFolder(); @@ -24,6 +26,11 @@ namespace Util { bool checkException(const JSON::Value & ex, const std::string & useragent); std::string codecString(const std::string & codec, const std::string & initData = ""); + std::set getSupportedTracks(const DTSC::Meta &M, const JSON::Value &capa = empty, const std::string &type = "", const std::string &UA = ""); + std::set findTracks(const DTSC::Meta &M, const JSON::Value &capa, const std::string &trackType, const std::string &trackVal, const std::string &UA = ""); + std::set wouldSelect(const DTSC::Meta &M, const std::string &trackSelector = "", const JSON::Value &capa = empty, const std::string &UA = ""); + std::set wouldSelect(const DTSC::Meta &M, const std::map &targetParams, const JSON::Value &capa = empty, const std::string &UA = "", uint64_t seekTarget = 0); + class DTSCShmReader{ public: DTSCShmReader(const std::string &pageName); diff --git a/src/output/output.cpp b/src/output/output.cpp index 66ce6253..222c1b73 100644 --- a/src/output/output.cpp +++ b/src/output/output.cpp @@ -505,207 +505,31 @@ namespace Mist{ if (!isInitialized){return false;} } - //First, back up and wipe the existing selections, if any. - std::set oldSel = selectedTracks; - selectedTracks.clear(); - bool autoSeek = buffer.size(); uint64_t seekTarget = currentTime(); + std::set newSelects = Util::wouldSelect(myMeta, targetParams, capa, UA, autoSeek?seekTarget:0); - /*LTS-START*/ - bool noSelAudio = false, noSelVideo = false, noSelSub = false; - //Then, select the tracks we've been asked to select. - if (targetParams.count("audio") && targetParams["audio"].size()){ - selectTrack("audio", targetParams["audio"]); - noSelAudio = true; - } - if (targetParams.count("video") && targetParams["video"].size()){ - selectTrack("video", targetParams["video"]); - noSelVideo = true; - } - if (targetParams.count("subtitle") && targetParams["subtitle"].size()){ - selectTrack("subtitle", targetParams["subtitle"]); - noSelSub = true; - } - /*LTS-END*/ - - //check which tracks don't actually exist - std::set toRemove; - for (std::set::iterator it = selectedTracks.begin(); it != selectedTracks.end(); it++){ - if (!myMeta.tracks.count(*it)){ - toRemove.insert(*it); - continue; - } - //autoSeeking and target not in bounds? Drop it too. - if (autoSeek && myMeta.tracks[*it].lastms < std::max(seekTarget, (uint64_t)6000lu) - 6000){ - toRemove.insert(*it); - } - } - //remove those from selectedtracks - for (std::set::iterator it = toRemove.begin(); it != toRemove.end(); it++){ - selectedTracks.erase(*it); - } - - //loop through all codec combinations, count max simultaneous active - unsigned int bestSoFar = 0; - unsigned int bestSoFarCount = 0; - unsigned int index = 0; - jsonForEach(capa["codecs"], it){ - unsigned int selCounter = 0; - if ((*it).size() > 0){ - jsonForEach((*it), itb){ - if ((*itb).size() > 0){ - jsonForEach(*itb, itc){ - const std::string & strRef = (*itc).asStringRef(); - bool byType = false; - bool multiSel = false; - uint8_t shift = 0; - if (strRef[shift] == '@'){byType = true; ++shift;} - if (strRef[shift] == '+'){multiSel = true; ++shift;} - for (std::set::iterator itd = selectedTracks.begin(); itd != selectedTracks.end(); itd++){ - if ((!byType && myMeta.tracks[*itd].codec == strRef.substr(shift)) || (byType && myMeta.tracks[*itd].type == strRef.substr(shift)) || strRef.substr(shift) == "*"){ - //user-agent-check - bool problems = false; - if (capa.isMember("exceptions") && capa["exceptions"].isObject() && capa["exceptions"].size()){ - jsonForEach(capa["exceptions"], ex){ - if (ex.key() == "codec:"+strRef.substr(shift)){ - problems = !Util::checkException(*ex, UA); - break; - } - } - } - if (problems){break;} - selCounter++; - if (!multiSel){ - break; - } - } - } - } - } - } - if (selCounter == selectedTracks.size()){ - if (selCounter > bestSoFarCount){ - bestSoFarCount = selCounter; - bestSoFar = index; - HIGH_MSG("Matched %u: %s", selCounter, (*it).toString().c_str()); - } - }else{ - VERYHIGH_MSG("Not a match for currently selected tracks: %s", (*it).toString().c_str()); - } - } - index++; - } - - MEDIUM_MSG("Trying to fill: %s", capa["codecs"][bestSoFar].toString().c_str()); - //try to fill as many codecs simultaneously as possible - if (capa["codecs"][bestSoFar].size() > 0){ - jsonForEach(capa["codecs"][bestSoFar], itb){ - if ((*itb).size() && myMeta.tracks.size()){ - bool found = false; - bool multiFind = false; - jsonForEach((*itb), itc){ - const std::string & strRef = (*itc).asStringRef(); - bool byType = false; - uint8_t shift = 0; - if (strRef[shift] == '@'){byType = true; ++shift;} - if (strRef[shift] == '+'){multiFind = true; ++shift;} - for (std::set::iterator itd = selectedTracks.begin(); itd != selectedTracks.end(); itd++){ - if ((!byType && myMeta.tracks[*itd].codec == strRef.substr(shift)) || (byType && myMeta.tracks[*itd].type == strRef.substr(shift)) || strRef.substr(shift) == "*"){ - found = true; - break; - } - } - } - if (!found || multiFind){ - jsonForEach((*itb), itc){ - const std::string & strRef = (*itc).asStringRef(); - bool byType = false; - bool multiSel = false; - uint8_t shift = 0; - if (strRef[shift] == '@'){byType = true; ++shift;} - if (strRef[shift] == '+'){multiSel = true; ++shift;} - if (found && !multiSel){continue;} - if (myMeta.live){ - for (std::map::reverse_iterator trit = myMeta.tracks.rbegin(); trit != myMeta.tracks.rend(); trit++){ - if ((!byType && trit->second.codec == strRef.substr(shift)) || (byType && trit->second.type == strRef.substr(shift)) || strRef.substr(shift) == "*"){ - if (autoSeek && trit->second.lastms < std::max(seekTarget, (uint64_t)6000lu) - 6000){continue;} - /*LTS-START*/ - if (noSelAudio && trit->second.type == "audio"){continue;} - if (noSelVideo && trit->second.type == "video"){continue;} - if (noSelSub && (trit->second.type == "subtitle" || trit->second.codec == "subtitle")){continue;} - /*LTS-END*/ - //user-agent-check - bool problems = false; - if (capa.isMember("exceptions") && capa["exceptions"].isObject() && capa["exceptions"].size()){ - jsonForEach(capa["exceptions"], ex){ - if (ex.key() == "codec:"+strRef.substr(shift)){ - problems = !Util::checkException(*ex, UA); - break; - } - } - } - if (problems){continue;} - selectedTracks.insert(trit->first); - found = true; - if (!multiSel){break;} - } - } - }else{ - for (std::map::iterator trit = myMeta.tracks.begin(); trit != myMeta.tracks.end(); trit++){ - if ((!byType && trit->second.codec == strRef.substr(shift)) || (byType && trit->second.type == strRef.substr(shift)) || strRef.substr(shift) == "*"){ - if (autoSeek && trit->second.lastms < std::max(seekTarget, (uint64_t)6000lu) - 6000){continue;} - /*LTS-START*/ - if (noSelAudio && trit->second.type == "audio"){continue;} - if (noSelVideo && trit->second.type == "video"){continue;} - if (noSelSub && (trit->second.type == "subtitle" || trit->second.codec == "subtitle")){continue;} - /*LTS-END*/ - //user-agent-check - bool problems = false; - if (capa.isMember("exceptions") && capa["exceptions"].isObject() && capa["exceptions"].size()){ - jsonForEach(capa["exceptions"], ex){ - if (ex.key() == "codec:"+strRef.substr(shift)){ - problems = !Util::checkException(*ex, UA); - break; - } - } - } - if (problems){continue;} - selectedTracks.insert(trit->first); - found = true; - if (!multiSel){break;} - } - } - } - } - } - } - } + std::set oldSel; + for (std::set::iterator selIt = selectedTracks.begin(); selIt != selectedTracks.end(); ++selIt){ + oldSel.insert(*selIt); } - if (Util::Config::printDebugLevel >= DLVL_MEDIUM){ - //print the selected tracks - std::stringstream selected; - if (selectedTracks.size()){ - for (std::set::iterator it = selectedTracks.begin(); it != selectedTracks.end(); it++){ - if (it != selectedTracks.begin()){ - selected << ", "; - } - selected << (*it); - } - } - MEDIUM_MSG("Selected tracks: %s (%lu)", selected.str().c_str(), selectedTracks.size()); + if (oldSel == newSelects){ + //No new selections? Do nothing, return no change. + return false; } - - if (!selectedTracks.size() && myMeta.tracks.size() && capa["codecs"][bestSoFar].size()){ - WARN_MSG("No tracks selected (%u total) for stream %s!", myMeta.tracks.size(), streamName.c_str()); + + //We changed the selection! Change to the new selection. + selectedTracks.clear(); + for (std::set::iterator reselIt = newSelects.begin(); reselIt != newSelects.end(); ++reselIt){ + selectedTracks.insert(*reselIt); } - bool madeChange = (oldSel != selectedTracks); - if (autoSeek && madeChange){ + + if (autoSeek){ INFO_MSG("Automatically seeking to position %llu to resume playback", seekTarget); seek(seekTarget); } - return madeChange; + return true; } /// Clears the buffer, sets parseData to false, and generally makes not very much happen at all.