Backport of 3.0 stream selectors
This commit is contained in:
parent
6993133e62
commit
a2e2ddae1e
4 changed files with 611 additions and 202 deletions
|
@ -175,3 +175,5 @@ static inline void show_stackframe(){}
|
|||
#define UDP_API_PORT 4242
|
||||
#endif
|
||||
|
||||
#define INVALID_TRACK_ID 0
|
||||
|
||||
|
|
598
lib/stream.cpp
598
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 <semaphore.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/stat.h>
|
||||
|
@ -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<std::string, std::string>::iterator it = str_args.begin(); it != str_args.end();
|
||||
++it){
|
||||
for (std::map<std::string, std::string>::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<int>::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<size_t> Util::findTracks(const DTSC::Meta &M, const JSON::Value &capa, const std::string &trackType, const std::string &trackVal, const std::string &UA){
|
||||
std::set<size_t> 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<size_t> 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<size_t> validTracks = getSupportedTracks(M, capa);
|
||||
for (std::set<size_t>::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<size_t> validTracks = getSupportedTracks(M, capa);
|
||||
for (std::set<size_t>::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<size_t> validTracks = getSupportedTracks(M, capa);
|
||||
for (std::set<size_t>::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<size_t> validTracks = getSupportedTracks(M, capa);
|
||||
for (std::set<size_t>::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<size_t> validTracks = getSupportedTracks(M, capa);
|
||||
for (std::set<size_t>::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<size_t> validTracks = getSupportedTracks(M, capa);
|
||||
for (std::set<size_t>::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<size_t> validTracks = getSupportedTracks(M, capa);
|
||||
for (std::set<size_t>::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<size_t> validTracks = getSupportedTracks(M, capa);
|
||||
for (std::set<size_t>::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<size_t> validTracks = getSupportedTracks(M, capa);
|
||||
for (std::set<size_t>::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<size_t> validTracks = getSupportedTracks(M, capa);
|
||||
for (std::set<size_t>::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<size_t> Util::wouldSelect(const DTSC::Meta &M, const std::string &trackSelector,
|
||||
const JSON::Value &capa, const std::string &UA){
|
||||
std::map<std::string, std::string> parsedVariables;
|
||||
HTTP::parseVars(trackSelector, parsedVariables);
|
||||
|
||||
return wouldSelect(M, parsedVariables, capa, UA);
|
||||
}
|
||||
|
||||
std::set<size_t> Util::getSupportedTracks(const DTSC::Meta &M, const JSON::Value &capa,
|
||||
const std::string &type, const std::string &UA){
|
||||
std::set<size_t> validTracks;
|
||||
for (std::map<unsigned int, DTSC::Track>::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<size_t> Util::wouldSelect(const DTSC::Meta &M, const std::map<std::string, std::string> &targetParams,
|
||||
const JSON::Value &capa, const std::string &UA, uint64_t seekTarget){
|
||||
std::set<size_t> 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<size_t> 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<size_t> 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<size_t> tracks = Util::findTracks(M, capa, "subtitle", targetParams.at("subtitle"));
|
||||
result.insert(tracks.begin(), tracks.end());
|
||||
noSelSub = true;
|
||||
}
|
||||
/*LTS-END*/
|
||||
|
||||
std::set<size_t> validTracks = getSupportedTracks(M, capa);
|
||||
|
||||
// check which tracks don't actually exist
|
||||
std::set<size_t> toRemove;
|
||||
for (std::set<size_t>::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<size_t>::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<size_t>::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<size_t>::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<size_t>::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<size_t>::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<size_t>::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<size_t>::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;
|
||||
}
|
||||
|
|
|
@ -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<size_t> getSupportedTracks(const DTSC::Meta &M, const JSON::Value &capa = empty, const std::string &type = "", const std::string &UA = "");
|
||||
std::set<size_t> findTracks(const DTSC::Meta &M, const JSON::Value &capa, const std::string &trackType, const std::string &trackVal, const std::string &UA = "");
|
||||
std::set<size_t> wouldSelect(const DTSC::Meta &M, const std::string &trackSelector = "", const JSON::Value &capa = empty, const std::string &UA = "");
|
||||
std::set<size_t> wouldSelect(const DTSC::Meta &M, const std::map<std::string, std::string> &targetParams, const JSON::Value &capa = empty, const std::string &UA = "", uint64_t seekTarget = 0);
|
||||
|
||||
class DTSCShmReader{
|
||||
public:
|
||||
DTSCShmReader(const std::string &pageName);
|
||||
|
|
|
@ -505,207 +505,31 @@ namespace Mist{
|
|||
if (!isInitialized){return false;}
|
||||
}
|
||||
|
||||
//First, back up and wipe the existing selections, if any.
|
||||
std::set<unsigned long> oldSel = selectedTracks;
|
||||
selectedTracks.clear();
|
||||
|
||||
bool autoSeek = buffer.size();
|
||||
uint64_t seekTarget = currentTime();
|
||||
std::set<size_t> 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<unsigned long> toRemove;
|
||||
for (std::set<unsigned long>::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<unsigned long>::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<unsigned long>::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<unsigned long>::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<unsigned int, DTSC::Track>::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<unsigned int, DTSC::Track>::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<size_t> oldSel;
|
||||
for (std::set<unsigned long>::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<long unsigned int>::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<size_t>::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.
|
||||
|
|
Loading…
Add table
Reference in a new issue