From f65c759292e25301520ab8fcc15e86e03c08ac64 Mon Sep 17 00:00:00 2001 From: Thulinma Date: Tue, 27 Mar 2018 15:57:08 +0200 Subject: [PATCH] Implemented browser detection and handling of output-specific browser exceptions. --- src/output/output_ebml.cpp | 15 +++++++ src/output/output_hls.cpp | 10 ++--- src/output/output_http_internal.cpp | 64 +++++++++++++++++++++++++---- src/output/output_http_internal.h | 2 +- 4 files changed, 77 insertions(+), 14 deletions(-) diff --git a/src/output/output_ebml.cpp b/src/output/output_ebml.cpp index 36b62c8c..99c13a3d 100644 --- a/src/output/output_ebml.cpp +++ b/src/output/output_ebml.cpp @@ -40,6 +40,21 @@ namespace Mist{ capa["methods"][0u]["handler"] = "http"; capa["methods"][0u]["type"] = "html5/video/webm"; capa["methods"][0u]["priority"] = 8ll; + //EBML will only work with VP8/VP9/Opus except on Chrome + JSON::Value blacklistNonChrome = JSON::fromString("[[\"blacklist\"], [\"whitelist\",[\"Chrome\",\"Chromium\"]], [\"blacklist\",[\"Edge\",\"OPR/\"]]]"); + capa["exceptions"]["codec:H264"] = blacklistNonChrome; + capa["exceptions"]["codec:HEVC"] = blacklistNonChrome; + capa["exceptions"]["codec:theora"] = blacklistNonChrome; + capa["exceptions"]["codec:MPEG2"] = blacklistNonChrome; + capa["exceptions"]["codec:AAC"] = blacklistNonChrome; + capa["exceptions"]["codec:vorbis"] = blacklistNonChrome; + capa["exceptions"]["codec:PCM"] = blacklistNonChrome; + capa["exceptions"]["codec:ALAW"] = blacklistNonChrome; + capa["exceptions"]["codec:ULAW"] = blacklistNonChrome; + capa["exceptions"]["codec:MP2"] = blacklistNonChrome; + capa["exceptions"]["codec:MP3"] = blacklistNonChrome; + capa["exceptions"]["codec:FLOAT"] = blacklistNonChrome; + capa["exceptions"]["codec:AC3"] = blacklistNonChrome; } /// Calculates the size of a Cluster (contents only) and returns it. diff --git a/src/output/output_hls.cpp b/src/output/output_hls.cpp index 143ea543..e0afa6fc 100644 --- a/src/output/output_hls.cpp +++ b/src/output/output_hls.cpp @@ -19,7 +19,7 @@ namespace Mist { 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"){ + if (it->second.codec == "AAC" || it->second.codec == "MP3"){ audioId = it->first; break; } @@ -125,6 +125,8 @@ namespace Mist { capa["methods"][0u]["handler"] = "http"; capa["methods"][0u]["type"] = "html5/application/vnd.apple.mpegurl"; capa["methods"][0u]["priority"] = 9ll; + //MP3 only works on Edge/Apple + capa["exceptions"]["codec:MP3"] = JSON::fromString("[[\"blacklist\"],[\"whitelist\",[\"iPad\",\"iPhone\",\"iPod\",\"MacIntel\",\"Edge\"]]]"); } void OutHLS::onHTTP() { @@ -247,11 +249,7 @@ namespace Mist { initialize(); std::string request = H.url.substr(H.url.find("/", 5) + 1); H.Clean(); - if (H.url.find(".m3u8") != std::string::npos){ - H.SetHeader("Content-Type", "audio/x-mpegurl"); - }else{ - H.SetHeader("Content-Type", "audio/mpegurl"); - } + H.SetHeader("Content-Type", "application/vnd.apple.mpegurl"); H.SetHeader("Cache-Control", "no-cache"); H.setCORSHeaders(); if (!myMeta.tracks.size()){ diff --git a/src/output/output_http_internal.cpp b/src/output/output_http_internal.cpp index 9dcdb120..1cedc385 100644 --- a/src/output/output_http_internal.cpp +++ b/src/output/output_http_internal.cpp @@ -153,9 +153,40 @@ 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){ + 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)){ + return; + } + } + } + } const std::string & rel = conncapa["url_rel"].asStringRef(); unsigned int most_simul = 0; unsigned int total_matches = 0; @@ -171,6 +202,17 @@ namespace Mist { if ((*trit)["codec"].asStringRef() == (*itc).asStringRef()){ 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)){ + matches--; + total_matches--; + } + break; + } + } + } } } } @@ -236,7 +278,7 @@ namespace Mist { H.SendResponse("200", "OK", myConn); } - JSON::Value OutHTTP::getStatusJSON(std::string & reqHost){ + JSON::Value OutHTTP::getStatusJSON(std::string & reqHost, const std::string & useragent){ JSON::Value json_resp; uint8_t streamStatus = Util::getStreamStatus(streamName); if (streamStatus != STRMSTAT_READY){ @@ -344,7 +386,7 @@ namespace Mist { //and a URL - then list the URL JSON::Value capa_json = capa.asJSON(); if (capa.getMember("url_rel") || capa.getMember("methods")){ - addSources(streamName, sources, outURL, capa_json, json_resp["meta"]); + addSources(streamName, sources, outURL, capa_json, json_resp["meta"], useragent); } //Make note if this connector can be depended upon by other connectors if (capa.getMember("provides")){ @@ -357,7 +399,7 @@ namespace Mist { //if it depends on this connector and has a URL, list it if (conns.count(capa_lst.getIndiceName(j)) && capa_lst.getIndice(j).getMember("deps").asString() == cProv && capa_lst.getIndice(j).getMember("methods")){ JSON::Value subcapa_json = capa_lst.getIndice(j).asJSON(); - addSources(streamName, sources, outURL, subcapa_json, json_resp["meta"]); + addSources(streamName, sources, outURL, subcapa_json, json_resp["meta"], useragent); } } } @@ -497,6 +539,10 @@ namespace Mist { if ((H.url.length() > 9 && H.url.substr(0, 6) == "/info_" && H.url.substr(H.url.length() - 3, 3) == ".js") || (H.url.length() > 10 && H.url.substr(0, 7) == "/embed_" && H.url.substr(H.url.length() - 3, 3) == ".js") || (H.url.length() > 9 && H.url.substr(0, 6) == "/json_" && H.url.substr(H.url.length() - 3, 3) == ".js")){ if (websocketHandler()){return;} std::string reqHost = HTTP::URL(H.GetHeader("Host")).host; + std::string useragent = H.GetVar("ua"); + if (!useragent.size()){ + useragent = H.GetHeader("User-Agent"); + } std::string response; std::string rURL = H.url; H.Clean(); @@ -514,7 +560,7 @@ namespace Mist { } initialize(); response = "// Generating info code for stream " + streamName + "\n\nif (!mistvideo){var mistvideo = {};}\n"; - JSON::Value json_resp = getStatusJSON(reqHost); + JSON::Value json_resp = getStatusJSON(reqHost, useragent); if (rURL.substr(0, 6) != "/json_"){ response += "mistvideo['" + streamName + "'] = " + json_resp.toString() + ";\n"; }else{ @@ -700,6 +746,10 @@ namespace Mist { bool OutHTTP::websocketHandler(){ stayConnected = true; std::string reqHost = HTTP::URL(H.GetHeader("Host")).host; + std::string useragent = H.GetVar("ua"); + if (!useragent.size()){ + useragent = H.GetHeader("User-Agent"); + } if (H.GetHeader("Upgrade") != "websocket"){return false;} HTTP::Websocket ws(myConn, H); if (!ws){return false;} @@ -724,7 +774,7 @@ namespace Mist { }else{ disconnect(); } - JSON::Value resp = getStatusJSON(reqHost); + JSON::Value resp = getStatusJSON(reqHost, useragent); ws.sendFrame(resp.toString()); prevState = newState; }else{ diff --git a/src/output/output_http_internal.h b/src/output/output_http_internal.h index f2f9a0ba..49f37d72 100644 --- a/src/output/output_http_internal.h +++ b/src/output/output_http_internal.h @@ -15,7 +15,7 @@ namespace Mist { void onHTTP(); void sendIcon(); bool websocketHandler(); - JSON::Value getStatusJSON(std::string & reqHost); + JSON::Value getStatusJSON(std::string & reqHost, const std::string & useragent = ""); bool stayConnected; virtual bool onFinish(){ return stayConnected;