mistserver/src/analysers/analyser_hls.cpp
2021-11-04 18:49:27 +01:00

134 lines
4.1 KiB
C++

#include "analyser_hls.h"
#include <cstdio>
#include <cstdlib>
#include <iostream>
#include <mist/config.h>
#include <mist/defines.h>
#include <mist/http_parser.h>
#include <mist/timing.h>
#include <string.h>
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;
}