diff --git a/lib/stream.cpp b/lib/stream.cpp index 8c7ce702..5e6360b9 100644 --- a/lib/stream.cpp +++ b/lib/stream.cpp @@ -9,12 +9,30 @@ #include "procs.h" #include "shared_memory.h" #include "socket.h" +#include "mp4_generic.h" #include #include #include #include #include +std::string Util::codecString(const std::string & codec, const std::string & initData){ + if (codec == "H264"){ + std::stringstream r; + MP4::AVCC avccBox; + avccBox.setPayload(initData); + r << "avc1."; + r << std::hex << std::setw(2) << std::setfill('0') << (int)initData[1] << std::dec; + r << std::hex << std::setw(2) << std::setfill('0') << (int)initData[2] << std::dec; + r << std::hex << std::setw(2) << std::setfill('0') << (int)initData[3] << std::dec; + return r.str(); + } + if (codec == "AAC"){return "mp4a.40.2";} + if (codec == "MP3"){return "mp4a.40.34";} + if (codec == "AC3"){return "ec-3";} + return ""; +} + std::string Util::getTmpFolder(){ std::string dir; char *tmp_char = 0; @@ -351,6 +369,29 @@ uint8_t Util::getStreamStatus(const std::string &streamname){ return streamStatus.mapped[0]; } +/// Checks if a given user agent is allowed according to the given exception. +bool Util::checkException(const JSON::Value & ex, const std::string & useragent){ + //No user agent? Always allow everything. + if (!useragent.size()){return true;} + if (!ex.isArray() || !ex.size()){return true;} + bool ret = true; + jsonForEachConst(ex, e){ + if (!e->isArray() || !e->size()){continue;} + bool setTo = ((*e)[0u].asStringRef() == "whitelist"); + if (e->size() == 1){ + ret = setTo; + continue; + } + if (!(*e)[1].isArray()){continue;} + jsonForEachConst((*e)[1u], i){ + if (useragent.find(i->asStringRef()) != std::string::npos){ + ret = setTo; + } + } + } + return ret; +} + Util::DTSCShmReader::DTSCShmReader(const std::string &pageName){ rPage.init(pageName, 0, false, false); if (rPage){rAcc = Util::RelAccX(rPage.mapped);} diff --git a/lib/stream.h b/lib/stream.h index 81c8bd5e..a928a5b6 100644 --- a/lib/stream.h +++ b/lib/stream.h @@ -18,6 +18,8 @@ namespace Util { JSON::Value getInputBySource(const std::string & filename, bool isProvider = false); DTSC::Meta getStreamMeta(const std::string & streamname); uint8_t getStreamStatus(const std::string & streamname); + bool checkException(const JSON::Value & ex, const std::string & useragent); + std::string codecString(const std::string & codec, const std::string & initData = ""); class DTSCShmReader{ public: diff --git a/src/output/output.cpp b/src/output/output.cpp index 15e1d0e9..f82b915f 100644 --- a/src/output/output.cpp +++ b/src/output/output.cpp @@ -317,6 +317,17 @@ namespace Mist{ 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; @@ -372,6 +383,17 @@ namespace Mist{ 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;} + //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;} @@ -381,6 +403,17 @@ namespace Mist{ 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;} + //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;} diff --git a/src/output/output_hls.cpp b/src/output/output_hls.cpp index 8ba7c76a..3ce1fbf9 100644 --- a/src/output/output_hls.cpp +++ b/src/output/output_hls.cpp @@ -12,61 +12,46 @@ namespace Mist { return false; } - std::string OutHLS::h264init(const std::string & initData){ - std::stringstream r; - MP4::AVCC avccBox; - avccBox.setPayload(initData); - r << std::hex << std::setw(2) << std::setfill('0') << (int)initData[1] << std::dec; - r << std::hex << std::setw(2) << std::setfill('0') << (int)initData[2] << std::dec; - r << std::hex << std::setw(2) << std::setfill('0') << (int)initData[3] << std::dec; - return r.str(); - } - ///\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"; int audioId = -1; - for (std::map::iterator it = myMeta.tracks.begin(); it != myMeta.tracks.end(); it++){ - if (it->second.codec == "AAC" || it->second.codec == "MP3"){ - audioId = it->first; - break; - } - } unsigned int vidTracks = 0; - for (std::map::iterator it = myMeta.tracks.begin(); it != myMeta.tracks.end(); it++){ - if (it->second.codec == "H264"){ + bool hasSubs = false; + for (std::set::iterator it = selectedTracks.begin(); it != selectedTracks.end(); ++it){ + if (audioId == -1 && myMeta.tracks[*it].type == "audio"){audioId = *it;} + if (!hasSubs && myMeta.tracks[*it].codec == "subtitle"){hasSubs = true;} + } + for (std::set::iterator it = selectedTracks.begin(); it != selectedTracks.end(); ++it){ + if (myMeta.tracks[*it].type == "video") { vidTracks++; - int bWidth = it->second.bps; - if (bWidth < 5){ + int bWidth = myMeta.tracks[*it].bps; + if (bWidth < 5) { bWidth = 5; } if (audioId != -1){ bWidth += myMeta.tracks[audioId].bps; } result << "#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=" << (bWidth * 8); - result << ",RESOLUTION=" << it->second.width << "x" << it->second.height; - if (it->second.fpks){ - result << ",FRAME-RATE=" << (float)it->second.fpks / 1000; + result << ",RESOLUTION=" << myMeta.tracks[*it].width << "x" << myMeta.tracks[*it].height; + if (myMeta.tracks[*it].fpks){ + result << ",FRAME-RATE=" << (float)myMeta.tracks[*it].fpks / 1000; } - if (it->second.codec == "H264"){ - result << ",CODECS=\""; - if (it->second.codec == "H264"){ - result << "avc1." << h264init(it->second.init); - } - if (audioId != -1){ - if (myMeta.tracks[audioId].codec == "AAC"){ - result << ",mp4a.40.2"; - }else if (myMeta.tracks[audioId].codec == "MP3" ){ - result << ",mp4a.40.34"; - } - } - result << "\""; + if (hasSubs){ + result << ",SUBTITLES=\"sub1\""; } - result <<"\r\n"; - result << it->first; + result << ",CODECS=\""; + result << Util::codecString(myMeta.tracks[*it].codec, myMeta.tracks[*it].init); if (audioId != -1){ + result << "," << Util::codecString(myMeta.tracks[audioId].codec, myMeta.tracks[audioId].init); + } + result << "\""; + result <<"\r\n"; + result << *it; + if (audioId != -1) { result << "_" << audioId; } result << "/index.m3u8?sessId=" << getpid() << "\r\n"; @@ -74,11 +59,7 @@ namespace Mist { } if (!vidTracks && audioId) { result << "#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=" << (myMeta.tracks[audioId].bps * 8); - if (myMeta.tracks[audioId].codec == "AAC"){ - result << ",CODECS=\"mp4a.40.2\""; - }else if (myMeta.tracks[audioId].codec == "MP3" ){ - result << ",CODECS=\"mp4a.40.34\""; - } + result << ",CODECS=\"" << Util::codecString(myMeta.tracks[audioId].codec, myMeta.tracks[audioId].init) << "\""; result << "\r\n"; result << audioId << "/index.m3u8\r\n"; } @@ -156,14 +137,14 @@ namespace Mist { capa["desc"] = "Segmented streaming in Apple (TS-based) format over HTTP ( = HTTP Live Streaming)"; capa["url_rel"] = "/hls/$/index.m3u8"; capa["url_prefix"] = "/hls/$/"; - capa["codecs"][0u][0u].append("H264"); - capa["codecs"][0u][1u].append("AAC"); - capa["codecs"][0u][1u].append("MP3"); + capa["codecs"][0u][0u].append("+H264"); + capa["codecs"][0u][1u].append("+AAC"); + capa["codecs"][0u][2u].append("+MP3"); capa["methods"][0u]["handler"] = "http"; capa["methods"][0u]["type"] = "html5/application/vnd.apple.mpegurl"; capa["methods"][0u]["priority"] = 9; //MP3 only works on Edge/Apple - capa["exceptions"]["codec:MP3"] = JSON::fromString("[[\"blacklist\"],[\"whitelist\",[\"iPad\",\"iPhone\",\"iPod\",\"MacIntel\",\"Edge\"]]]"); + capa["exceptions"]["codec:MP3"] = JSON::fromString("[[\"blacklist\",[\"Mozilla/\"]],[\"whitelist\",[\"iPad\",\"iPhone\",\"iPod\",\"MacIntel\",\"Edge\"]]]"); } void OutHLS::onHTTP() { diff --git a/src/output/output_http_internal.cpp b/src/output/output_http_internal.cpp index 9aa77b16..ebd01cc2 100644 --- a/src/output/output_http_internal.cpp +++ b/src/output/output_http_internal.cpp @@ -188,34 +188,11 @@ namespace Mist { sources.insert(tmp); } - /// Checks if a given user agent is allowed according to the given exception. - bool checkException(const JSON::Value & ex, const std::string & useragent){ - //No user agent? Always allow everything. - if (!useragent.size()){return true;} - if (!ex.isArray() || !ex.size()){return true;} - bool ret = true; - jsonForEachConst(ex, e){ - if (!e->isArray() || !e->size()){continue;} - bool setTo = ((*e)[0u].asStringRef() == "whitelist"); - if (e->size() == 1){ - ret = setTo; - continue; - } - if (!(*e)[1].isArray()){continue;} - jsonForEachConst((*e)[1u], i){ - if (useragent.find(i->asStringRef()) != std::string::npos){ - ret = setTo; - } - } - } - return ret; - } - void addSources(std::string & streamname, std::set & sources, HTTP::URL url, JSON::Value & conncapa, JSON::Value & strmMeta, const std::string & useragent){ if (strmMeta.isMember("live") && conncapa.isMember("exceptions") && conncapa["exceptions"].isObject() && conncapa["exceptions"].size()){ jsonForEach(conncapa["exceptions"], ex){ if (ex.key() == "live"){ - if (!checkException(*ex, useragent)){ + if (!Util::checkException(*ex, useragent)){ return; } } @@ -232,14 +209,20 @@ namespace Mist { unsigned int matches = 0; 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;} jsonForEach(strmMeta["tracks"], trit) { - if ((*trit)["codec"].asStringRef() == (*itc).asStringRef()){ + if ((!byType && (*trit)["codec"].asStringRef() == strRef.substr(shift)) || (byType && (*trit)["type"].asStringRef() == strRef.substr(shift)) || strRef.substr(shift) == "*"){ matches++; total_matches++; if (conncapa.isMember("exceptions") && conncapa["exceptions"].isObject() && conncapa["exceptions"].size()){ jsonForEach(conncapa["exceptions"], ex){ - if (ex.key() == "codec:"+(*trit)["codec"].asStringRef()){ - if (!checkException(*ex, useragent)){ + if (ex.key() == "codec:"+strRef.substr(shift)){ + if (!Util::checkException(*ex, useragent)){ matches--; total_matches--; }