From 266ab3a65462ed35a74e2bbe5e891e51567953d5 Mon Sep 17 00:00:00 2001 From: Thulinma Date: Sun, 18 Mar 2018 17:24:54 +0100 Subject: [PATCH] Implemented browser detection and handling of output-specific browser exceptions. --- src/output/output_ebml.cpp | 15 +++++++ src/output/output_hls.cpp | 2 + src/output/output_http_internal.cpp | 64 ++++++++++++++++++++++++--- src/output/output_http_internal.h | 2 +- src/output/output_progressive_mp4.cpp | 5 +-- 5 files changed, 77 insertions(+), 11 deletions(-) diff --git a/src/output/output_ebml.cpp b/src/output/output_ebml.cpp index 4dd845a4..63542a34 100644 --- a/src/output/output_ebml.cpp +++ b/src/output/output_ebml.cpp @@ -68,6 +68,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; capa["push_urls"].append("/*.mkv"); capa["push_urls"].append("/*.webm"); diff --git a/src/output/output_hls.cpp b/src/output/output_hls.cpp index f0fedd23..a472639c 100644 --- a/src/output/output_hls.cpp +++ b/src/output/output_hls.cpp @@ -306,6 +306,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\"]]]"); /*LTS-START*/ cfg->addOption("listlimit", JSON::fromString("{\"arg\":\"integer\",\"default\":0,\"short\":\"y\",\"long\":\"list-limit\",\"help\":\"Maximum number of parts in live playlists (0 = infinite).\"}")); capa["optional"]["listlimit"]["name"] = "Live playlist limit"; diff --git a/src/output/output_http_internal.cpp b/src/output/output_http_internal.cpp index e70abbd6..636fc9b0 100644 --- a/src/output/output_http_internal.cpp +++ b/src/output/output_http_internal.cpp @@ -170,9 +170,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; @@ -188,6 +219,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; + } + } + } } } } @@ -262,7 +304,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; if (config->getString("nostreamtext") != ""){ json_resp["on_error"] = config->getString("nostreamtext"); @@ -383,7 +425,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")){ @@ -396,7 +438,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); } } } @@ -536,6 +578,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(); @@ -553,7 +599,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{ @@ -764,6 +810,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;} @@ -788,7 +838,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; diff --git a/src/output/output_progressive_mp4.cpp b/src/output/output_progressive_mp4.cpp index ae3ae8b8..70d37c30 100644 --- a/src/output/output_progressive_mp4.cpp +++ b/src/output/output_progressive_mp4.cpp @@ -26,9 +26,8 @@ namespace Mist{ capa["methods"][0u]["handler"] = "http"; capa["methods"][0u]["type"] = "html5/video/mp4"; capa["methods"][0u]["priority"] = 8ll; - ///\todo uncomment when we actually start implementing mp4 recording - //capa["canRecord"].append("mp4"); - //capa["canRecord"].append("m3u"); + //MP4 live is broken on Apple + capa["exceptions"]["live"] = JSON::fromString("[[\"blacklist\",[\"iPad\",\"iPhone\",\"iPod\",\"Safari\"]], [\"whitelist\",[\"Chrome\",\"Chromium\"]]]"); } uint64_t OutProgressiveMP4::estimateFileSize(){