From 110a53915192ce4fe968e0115ecdc4eb14ab1fef Mon Sep 17 00:00:00 2001 From: Thulinma Date: Mon, 7 Aug 2023 11:35:17 +0200 Subject: [PATCH] Improvements and fixes to track wait code --- src/output/output.cpp | 79 +++++++++++++++++++++++++------------- src/output/output.h | 1 + src/output/output_http.cpp | 32 +++++++++------ 3 files changed, 74 insertions(+), 38 deletions(-) diff --git a/src/output/output.cpp b/src/output/output.cpp index 2853964a..a7da7280 100644 --- a/src/output/output.cpp +++ b/src/output/output.cpp @@ -118,6 +118,7 @@ namespace Mist{ Util::Config::binaryType = Util::OUTPUT; lastRecv = Util::bootSecs(); + outputStartMs = Util::bootMS(); if (myConn){ setBlocking(true); //Make sure that if the socket is a non-stdio socket, we close it when forking @@ -233,33 +234,59 @@ namespace Mist{ bool Output::isReadyForPlay(){ // If a protocol does not support any codecs, we assume you know what you're doing if (!capa.isMember("codecs") || !capa["codecs"].size() || !capa["codecs"].isArray() || !capa["codecs"][0u].size()){return true;} - if (!isInitialized){return false;} - meta.reloadReplacedPagesIfNeeded(); - if (getSupportedTracks().size()){ - size_t minTracks = 2; - size_t minMs = 5000; - if (targetParams.count("waittrackcount")){ - minTracks = JSON::Value(targetParams["waittrackcount"]).asInt(); - minMs = 120000; - } - if (targetParams.count("maxwaittrackms")){ - minMs = JSON::Value(targetParams["maxwaittrackms"]).asInt(); - } - if (!userSelect.size()){selectDefaultTracks();} - size_t mainTrack = getMainSelectedTrack(); - if (mainTrack != INVALID_TRACK_ID){ - DTSC::Keys keys(M.getKeys(mainTrack)); - if (keys.getValidCount() >= minTracks || M.getNowms(mainTrack) - M.getFirstms(mainTrack) > minMs){ - return true; - } - HIGH_MSG("NOT READY YET (%zu tracks, main track: %zu, with %zu keys)", - M.getValidTracks().size(), getMainSelectedTrack(), keys.getValidCount()); - }else{ - HIGH_MSG("NOT READY YET (%zu tracks)", getSupportedTracks().size()); - } - }else{ - HIGH_MSG("NOT READY (%zu tracks)", getSupportedTracks().size()); + + // Defaults for the below values are configured here, and may be overridden with query parameters: + size_t minTracks = 2; // Supported tracks to attempt to wait for + size_t minMs = 500; // Minimum time that must be buffered for a track to count as available + size_t minKeys = 2; // Minimum keyframes that must be buffered for a track to count as available + size_t maxWait = 5000; // Maximum time to wait for tracks + size_t maxMs = 20000; // If this much time is buffered in any track, don't wait on anything else anymore + + if (targetParams.count("waittrackcount")){ + minTracks = JSON::Value(targetParams["waittrackcount"]).asInt(); + // Non-default wait times get a 2 minute default timeout instead of the usually 5 seconds default. + // (May still be overridden) + maxWait = 120000; } + if (targetParams.count("mintrackms")){ + minMs = JSON::Value(targetParams["mintrackms"]).asInt(); + } + if (targetParams.count("maxtrackms")){ + maxMs = JSON::Value(targetParams["maxtrackms"]).asInt(); + } + if (targetParams.count("mintrackkeys")){ + minKeys = JSON::Value(targetParams["mintrackkeys"]).asInt(); + } + if (targetParams.count("maxwaittrackms")){ + maxWait = JSON::Value(targetParams["maxwaittrackms"]).asInt(); + } + + if (outputStartMs + maxWait < Util::bootMS()){ + HIGH_MSG("isReadyForPlay timed out waiting for tracks (%" PRId64 " > %zu); continuing with what we have", Util::bootMS()-outputStartMs, maxWait); + return true; + } + + //This logic is copied from selectDefaultTracks, and should be kept in sync with it + if (!isInitialized){initialize();} + meta.reloadReplacedPagesIfNeeded(); + bool autoSeek = buffer.size(); + uint64_t seekTarget = buffer.getSyncMode()?currentTime():0; + std::set trks = Util::wouldSelect(M, targetParams, capa, UA, autoSeek ? seekTarget : 0); + + size_t trkCount = 0; + for (std::set::iterator it = trks.begin(); it != trks.end(); ++it){ + DTSC::Keys keys(M.keys(*it)); + if (keys.getValidCount() >= minKeys || M.getLastms(*it) - M.getFirstms(*it) > minMs){ + ++trkCount; + if (trkCount >= minTracks){return true;} + } + if (M.getLastms(*it) - M.getFirstms(*it) > maxMs){ + HIGH_MSG("isReadyForPlay skipping wait because track %zu has %" PRId64 "ms buffered", *it, M.getLastms(*it) - M.getFirstms(*it)); + return true; + } + } + + HIGH_MSG("isReadyForPlay waiting for tracks: %zu/%zu/%zu tracks, maxWait %zu, minMs %zu, minKeys %zu", trkCount, trks.size(), minTracks, maxWait, minMs, minKeys); return false; } diff --git a/src/output/output.h b/src/output/output.h index ee1555a5..3ed4e286 100644 --- a/src/output/output.h +++ b/src/output/output.h @@ -106,6 +106,7 @@ namespace Mist{ uint32_t seekCount; bool firstData; uint64_t lastPushUpdate; + uint64_t outputStartMs; ///< bootMS() at time of output start (unrelated to media start) bool newUA; protected: // these are to be messed with by child classes diff --git a/src/output/output_http.cpp b/src/output/output_http.cpp index 83470c9e..8ab1ab7f 100644 --- a/src/output/output_http.cpp +++ b/src/output/output_http.cpp @@ -379,15 +379,23 @@ namespace Mist{ /*LTS-END*/ if (H.hasHeader("User-Agent")){UA = H.GetHeader("User-Agent");} - if (H.GetVar("audio") != ""){targetParams["audio"] = H.GetVar("audio");} - if (H.GetVar("video") != ""){targetParams["video"] = H.GetVar("video");} - if (H.GetVar("meta") != ""){targetParams["meta"] = H.GetVar("meta");} - if (H.GetVar("subtitle") != ""){targetParams["subtitle"] = H.GetVar("subtitle");} - if (H.GetVar("start") != ""){targetParams["start"] = H.GetVar("start");} - if (H.GetVar("stop") != ""){targetParams["stop"] = H.GetVar("stop");} - if (H.GetVar("startunix") != ""){targetParams["startunix"] = H.GetVar("startunix");} - if (H.GetVar("stopunix") != ""){targetParams["stopunix"] = H.GetVar("stopunix");} - if (H.GetVar("duration") != ""){targetParams["duration"] = H.GetVar("duration");} +#define HTTP_CONVERT(var) if (H.GetVar(var) != ""){targetParams[var] = H.GetVar(var);} + + HTTP_CONVERT("audio"); + HTTP_CONVERT("video"); + HTTP_CONVERT("meta"); + HTTP_CONVERT("subtitle"); + HTTP_CONVERT("start"); + HTTP_CONVERT("stop"); + HTTP_CONVERT("startunix"); + HTTP_CONVERT("stopunix"); + HTTP_CONVERT("duration"); + HTTP_CONVERT("waittrackcount"); + HTTP_CONVERT("mintrackms"); + HTTP_CONVERT("maxtrackms"); + HTTP_CONVERT("mintrackkeys"); + HTTP_CONVERT("maxwaittrackms"); + // allow setting of max lead time through buffer variable. // max lead time is set in MS, but the variable is in integer seconds for simplicity. if (H.GetVar("buffer") != ""){ @@ -461,7 +469,7 @@ namespace Mist{ //Parse JSON and check command type JSON::Value command = JSON::fromString(webSock->data, webSock->data.size()); if (!command || !command.isMember("type")){return false;} - + //Seek command, for changing playback position if (command["type"] == "seek") { handleWebsocketSeek(command); @@ -566,7 +574,7 @@ namespace Mist{ parseData = true; selectDefaultTracks(); } - + if (target_rate == 0.0){ r["data"]["play_rate_prev"] = "auto"; }else{ @@ -841,7 +849,7 @@ namespace Mist{ if (Comms::tknMode & 0x08){ std::stringstream cookieHeader; cookieHeader << "tkn=" << tkn << "; Max-Age=" << SESS_TIMEOUT; - H.SetHeader("Set-Cookie", cookieHeader.str()); + H.SetHeader("Set-Cookie", cookieHeader.str()); } } //Set attachment header to force download, if applicable