feat: play resolved Miruro HLS with external VTT and stable episode progress

This commit is contained in:
2026-10-12 19:17:22 +02:00
parent 1f12660936
commit 470d8feec2
23 changed files with 1528 additions and 38 deletions
+36 -6
View File
@@ -69,11 +69,31 @@ bool ProgressReporter::validMediaURL(std::string_view url) noexcept {
return true;
}
bool ProgressReporter::validMediaIdentity(std::string_view identity) noexcept {
if (validMediaURL(identity)) return true;
constexpr std::string_view prefix = "miruro:";
if (identity.substr(0, prefix.size()) != prefix) return false;
const auto separator = identity.find(':', prefix.size());
if (separator == std::string_view::npos) return false;
const auto id = identity.substr(prefix.size(), separator - prefix.size());
if (id.empty() || id.size() > 512) return false;
for (char c : id) {
if (!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') ||
(c >= '0' && c <= '9') || c == '-' || c == '_')) return false;
}
const auto episode = identity.substr(separator + 1);
if (episode.empty() || episode.front() < '1' || episode.front() > '9') return false;
for (char c : episode) if (c < '0' || c > '9') return false;
int value = 0;
const auto parsed = std::from_chars(episode.data(), episode.data() + episode.size(), value);
return parsed.ec == std::errc{} && parsed.ptr == episode.data() + episode.size() && value > 0;
}
void ProgressReporter::begin(const std::string &url, bool isLive) noexcept {
if (finished || disabled.load(std::memory_order_acquire)) return;
if (url.empty()) { closeMedia(); return; }
if (!validMediaURL(url)) {
disable("Progress media URL is not a canonical YouTube watch URL");
if (!validMediaIdentity(url)) {
disable("Progress media identity is invalid");
return;
}
try {
@@ -144,11 +164,21 @@ bool ProgressReporter::emit(const char *event) noexcept {
try {
std::string record;
record.reserve(256);
record += "{\"version\":1,\"event\":\"";
const bool miruro = mediaURL.compare(0, 7, "miruro:") == 0;
record += miruro ? "{\"version\":2,\"event\":\"" : "{\"version\":1,\"event\":\"";
record += event;
record += "\",\"media_url\":\"";
record += mediaURL;
record += "\",\"position_seconds\":";
if (miruro) {
const auto separator = mediaURL.find(':', 7);
record += "\",\"media_identity\":{\"provider\":\"miruro\",\"media_id\":\"";
record.append(mediaURL, 7, separator - 7);
record += "\",\"episode\":";
record.append(mediaURL, separator + 1, std::string::npos);
record += "},\"position_seconds\":";
} else {
record += "\",\"media_url\":\"";
record += mediaURL;
record += "\",\"position_seconds\":";
}
appendNumber(record, snapshot.position);
record += ",\"duration_seconds\":";
if (snapshot.duration > 0) appendNumber(record, snapshot.duration);