Ctrl K
ComunidadGuías…

queue_flac_metadata.h

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0
#pragma once
#include "queue_lms_cli.h"
#include "third_party/yeney-core/core/flac_header.h"
#include <cctype>
#include <functional>
#include <map>
#include <mutex>
#include <sstream>
namespace queue_audio {
// Metadata only. LMS IPv4 is resolved once before the player connects.
// A single absolute deadline covers CLI identity and HTTP metadata together.
class FlacMetadata {
    using Clock = std::chrono::steady_clock;
    std::string host;
    unsigned cliPort, httpPort;
    in_addr target{};
    bool haveAddress = false;
    std::function<void(const std::string &)> log;
    std::mutex mutex;
    std::map<std::string, yeney::FlacSourceInfo> cache;
    std::optional<uint64_t> baseIndex;
    uint64_t generation = 0;
    std::string exchange(unsigned port, const std::string &request, Clock::time_point deadline, bool http) {
        sockaddr_in address{};
        address.sin_family = AF_INET;
        address.sin_port = htons(port);
        if (!haveAddress)
            return {};
        address.sin_addr = target;
        int fd = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0);
        if (fd < 0)
            return {};
        connect(fd, reinterpret_cast<sockaddr *>(&address), sizeof(address));
        std::string response;
        size_t sent = 0;
        while (Clock::now() < deadline && response.size() < 16384) {
            pollfd p{fd, short(sent < request.size() ? POLLOUT : POLLIN), 0};
            auto left =
                std::chrono::duration_cast<std::chrono::milliseconds>(deadline - Clock::now()).count();
            if (poll(&p, 1, std::max<int64_t>(0, std::min<int64_t>(20, left))) <= 0)
                continue;
            if (sent < request.size()) {
                auto n = send(fd, request.data() + sent, request.size() - sent, MSG_NOSIGNAL);
                if (n <= 0)
                    break;
                sent += n;
            } else {
                char bytes[1024];
                auto n = recv(fd, bytes, sizeof(bytes), 0);
                if (n <= 0)
                    break;
                response.append(bytes, n);
                if (!http && response.find('\n') != std::string::npos)
                    break;
                auto end = response.find("\r\n\r\n");
                // STREAMINFO is the first mandatory block: 4+4+34 bytes.
                if (http && end != std::string::npos && response.size() >= end + 4 + 42)
                    break;
            }
        }
        close(fd);
        return response;
    }
    std::string query(const std::string &key, Clock::time_point deadline) {
        const std::string prefix = "02:59:51:54:00:01 " + key + " ";
        auto response = decodeLms(exchange(cliPort, prefix + "?\n", deadline, false));
        if (response.compare(0, prefix.size(), prefix))
            return {};
        response = response.substr(prefix.size());
        auto end = response.find_first_of("\r\n");
        return response.substr(0, end);
    }
    static bool digits(const std::string &s) {
        return !s.empty() && s.size() <= 18 && s.find_first_not_of("0123456789") == std::string::npos;
    }

public:
    FlacMetadata(std::string server, std::function<void(const std::string &)> logger, unsigned cli = 9090,
                 unsigned http = 9000)
        : host(std::move(server)), cliPort(cli), httpPort(http), log(std::move(logger)) {
        addrinfo hints{}, *addresses = nullptr;
        hints.ai_family = AF_INET;
        hints.ai_socktype = SOCK_STREAM;
        if (!getaddrinfo(host.c_str(), nullptr, &hints, &addresses)) {
            target = reinterpret_cast<sockaddr_in *>(addresses->ai_addr)->sin_addr;
            haveAddress = true;
            freeaddrinfo(addresses);
        }
    }
    void reset() {
        std::lock_guard<std::mutex> lock(mutex);
        baseIndex.reset();
        ++generation;
    }
    std::optional<yeney::FlacSourceInfo> resolve(uint64_t index, std::optional<yeney::FlacSourceInfo> info,
                                                 std::optional<uint64_t> audibleIndex = std::nullopt) {
        const auto deadline = Clock::now() + std::chrono::seconds(1);
        auto fail = [&](const std::string &why) {
            log(std::string(info ? "FLAC metadata cache unavailable: " : "FLAC metadata sealed fallback: ") +
                why);
            return info; // A stream's own validated STREAMINFO never needs a lookup.
        };
        uint64_t gen;
        std::optional<uint64_t> base;
        {
            std::lock_guard<std::mutex> lock(mutex);
            gen = generation;
            base = index ? baseIndex : std::nullopt;
        }
        if (!base) {
            // On reconnect/seek the first occurrence may be any playlist entry.
            // Subsequent strm occurrences follow it, ahead of the audible index.
            auto current = query("playlist index", deadline);
            if (!digits(current))
                return fail("CLI playlist index unavailable");
            const auto currentIndex = std::stoull(current);
            if (!index)
                base = currentIndex;
            else if (audibleIndex && currentIndex >= *audibleIndex)
                base = currentIndex - *audibleIndex;
            else
                return fail("playlist base unavailable");
            std::lock_guard<std::mutex> lock(mutex);
            if (generation != gen)
                return fail("flushed during identity lookup");
            baseIndex = base;
        }
        const std::string key = "status " + std::to_string(*base + index) + " 1 tags:";
        // status is an extended query, not a '?' query.
        const std::string prefix = "02:59:51:54:00:01 " + key;
        auto response = decodeLms(exchange(cliPort, prefix + "\n", deadline, false));
        if (response.compare(0, prefix.size(), prefix))
            return fail("CLI track id unavailable");
        std::istringstream tokens(response.substr(prefix.size()));
        std::string token, id;
        bool entry = false;
        while (tokens >> token) {
            if (token == "playlist_index:" + std::to_string(*base + index))
                entry = true;
            if (entry && token.rfind("id:", 0) == 0) {
                id = token.substr(3);
                break;
            }
        }
        if (!digits(id))
            return fail("CLI track id unavailable");
        if (Clock::now() >= deadline)
            return fail("identity deadline");
        {
            std::lock_guard<std::mutex> lock(mutex);
            if (generation != gen)
                return fail("flushed during identity lookup");
            if (info) {
                cache[id] = *info;
                log("FLAC STREAMINFO cached track_id=" + id);
                return info;
            }
            auto found = cache.find(id);
            if (found != cache.end()) {
                log("FLAC metadata cache track_id=" + id);
                return found->second;
            }
        }
        auto raw =
            exchange(httpPort,
                     "GET /music/" + id + "/download HTTP/1.1\r\nHost: " + host + ":" +
                         std::to_string(httpPort) + "\r\nRange: bytes=0-8191\r\nConnection: close\r\n\r\n",
                     deadline, true);
        if (Clock::now() >= deadline)
            return fail("metadata deadline");
        auto end = raw.find("\r\n\r\n");
        if (end == std::string::npos || end > 8192 || raw.size() < end + 46)
            return fail("HTTP STREAMINFO unavailable");
        auto headers = raw.substr(0, end);
        std::transform(headers.begin(), headers.end(), headers.begin(),
                       [](unsigned char c) { return std::tolower(c); });
        if ((headers.rfind("http/1.1 200 ", 0) && headers.rfind("http/1.0 200 ", 0) &&
             headers.rfind("http/1.1 206 ", 0) && headers.rfind("http/1.0 206 ", 0)) ||
            headers.find("transfer-encoding:") != std::string::npos ||
            headers.find("content-encoding:") != std::string::npos)
            return fail("unsupported metadata HTTP response");
        if (headers.substr(9, 3) == "206" && headers.find("content-range: bytes 0-") == std::string::npos)
            return fail("metadata range does not start at zero");
        auto p = reinterpret_cast<const uint8_t *>(raw.data() + end + 4);
        if (std::string(reinterpret_cast<const char *>(p), 4) != "fLaC" || (p[4] & 127) || p[5] || p[6] ||
            p[7] != 34)
            return fail("missing STREAMINFO");
        yeney::FlacSourceInfo fetched;
        fetched.fixedBlockSize = unsigned(p[10]) * 256 + p[11];
        fetched.totalSamples = p[21] & 15;
        for (unsigned i = 22; i < 26; ++i)
            fetched.totalSamples = (fetched.totalSamples << 8) | p[i];
        if (!fetched.totalSamples || fetched.fixedBlockSize < 16)
            return fail("unknown STREAMINFO count");
        {
            std::lock_guard<std::mutex> lock(mutex);
            if (generation != gen)
                return fail("flushed during metadata lookup");
            cache[id] = fetched;
        }
        log("FLAC metadata HTTP range track_id=" + id +
            " total_samples=" + std::to_string(fetched.totalSamples));
        return fetched;
    }
};
} // namespace queue_audio