#include "analyser_hls.h" #include #include #include #include #include #include #include #include void AnalyserHLS::init(Util::Config &conf){ Analyser::init(conf); JSON::Value opt; opt["long"] = "reconstruct"; opt["short"] = "R"; opt["arg"] = "string"; opt["default"] = ""; opt["help"] = "Reconstruct TS file from HLS to the given filename"; conf.addOption("reconstruct", opt); opt.null(); } void AnalyserHLS::getParts(const std::string &body){ parts.clear(); std::stringstream data(body); std::string line; uint64_t no = 0; float durat = 0; refreshAt = Util::bootSecs() + 10; while (data.good()){ std::getline(data, line); if (line.size() && *line.rbegin() == '\r'){line.resize(line.size() - 1);} if (!line.size()){continue;} if (line[0] != '#'){ if (line.find("m3u") != std::string::npos){ root = root.link(line); INFO_MSG("Found a sub-playlist, re-targeting %s", root.getUrl().c_str()); refreshAt = Util::bootSecs(); return; } if (!parsedPart || no > parsedPart){ HTTP::URL newURL = root.link(line); INFO_MSG("Discovered #%" PRIu64 ": %s", no, newURL.getUrl().c_str()); parts.push_back(HLSPart(newURL, no, durat)); } ++no; }else{ if (line.substr(0, 8) == "#EXTINF:"){durat = atof(line.c_str() + 8) * 1000;} if (line.substr(0, 22) == "#EXT-X-MEDIA-SEQUENCE:"){no = atoll(line.c_str() + 22);} if (line.substr(0, 14) == "#EXT-X-ENDLIST"){refreshAt = 0;} if (line.substr(0, 22) == "#EXT-X-TARGETDURATION:" && refreshAt){ refreshAt = Util::bootSecs() + atoll(line.c_str() + 22) / 2; } } } } /// Returns true if we either still have parts to download, or are still refreshing the playlist. bool AnalyserHLS::isOpen(){ return (*isActive) && (parts.size() || refreshAt); } void AnalyserHLS::stop(){ parts.clear(); refreshAt = 0; } bool AnalyserHLS::open(const std::string &url){ root = HTTP::URL(url); if (root.protocol != "http" && root.protocol != "https"){ FAIL_MSG("Only http(s) protocol is supported (%s not supported)", root.protocol.c_str()); return false; } return true; } AnalyserHLS::AnalyserHLS(Util::Config &conf) : Analyser(conf){ if (conf.getString("reconstruct") != ""){ reconstruct.open(conf.getString("reconstruct").c_str()); if (reconstruct.good()){ WARN_MSG("Will reconstruct to %s", conf.getString("reconstruct").c_str()); } } hlsTime = 0; parsedPart = 0; refreshAt = Util::bootSecs(); } bool AnalyserHLS::parsePacket(){ while (isOpen()){ // If needed, refresh the playlist if (refreshAt && Util::bootSecs() >= refreshAt){ if (DL.get(root)){ getParts(DL.data()); }else{ FAIL_MSG("Could not refresh playlist!"); return false; } } // If there are parts to download, get one. if (parts.size()){ HLSPart part = *parts.begin(); parts.pop_front(); if (!DL.get(part.uri)){return false;} if (DL.getHeader("Content-Length") != ""){ if (DL.data().size() != atoi(DL.getHeader("Content-Length").c_str())){ FAIL_MSG("Expected %s bytes of data, but only received %zu.", DL.getHeader("Content-Length").c_str(), DL.data().size()); return false; } } if (DL.data().size() % 188){ FAIL_MSG("Expected a multiple of 188 bytes, received %zu bytes", DL.data().size()); return false; } parsedPart = part.no; hlsTime += part.dur; mediaTime = (uint64_t)hlsTime; if (reconstruct.good()){reconstruct << DL.data();} return true; } // Hm. I guess we had no parts to get. if (refreshAt && refreshAt > Util::bootSecs()){ // We're getting a live stream. Let's wait and check again. uint64_t sleepSecs = (refreshAt - Util::bootSecs()); INFO_MSG("Sleeping for %" PRIu64 " seconds", sleepSecs); Util::sleep(sleepSecs * 1000); } // The non-live case is already handled in isOpen() } return false; }