Ctrl K
ComunidadGuías…

sonos-status.cpp

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

Archivo original ↗Preguntar a KiBot
// sonos-status.cpp -- poll Sonos transport/track state and report changes
//
// Copyright (c) 2026 Jaap van Vliet
//
// Original implementation for the YeneY project.
// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0
// Licensed under the PolyForm Noncommercial License 1.0.0. See LICENSE.
//
// THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.

#include "sonos-status.h"
#include "speaker_uri.h"
#include "stream_session.h"
#include "stream_metrics.h"
#include "stream_display.h"

#include <cstdio>
#include <cstdlib>

using namespace bridge;

namespace {
constexpr char kNoPosition[] = "-:--:--";
}

bool Status::Snapshot::operator==(const Snapshot& other) const
{
    return title == other.title
        && album == other.album
        && artist == other.artist
        && transportStatus == other.transportStatus
        && transportState == other.transportState
        && relativeTime == other.relativeTime
        && trackDuration == other.trackDuration
        && volume == other.volume;
}

Status::Status(std::shared_ptr<upnp::SpeakerControl> player)
    : m_player(player)
{
    if (m_player) {
        m_zoneUuid = m_player->speaker().uuid;
        m_zoneName = m_player->speaker().name;
    }
    m_current.relativeTime = kNoPosition;
    m_current.trackDuration = kNoPosition;
}

Status::Snapshot Status::poll() const
{
    Snapshot s;
    s.relativeTime = kNoPosition;
    s.trackDuration = kNoPosition;

    if (!m_player) return s;
    if (!m_player->transportInfo().available) return s;
    s.volume = m_player->displayVolume();
    uint32_t ms;
    m_player->positionInfo(ms, &s.relativeTime);
    const auto transport = m_player->transportInfo();
    s.title = transport.title; s.album = transport.album; s.artist = transport.artist;
    s.transportStatus = transport.status; s.transportState = transport.state;
    s.trackDuration = transport.duration;
    bool queueDisplay = false;
    if (auto *mode = roomMode()) {
        const auto value = mode->status();
        if (value.active == "queue" && value.path == "relay") { s.title = value.title; queueDisplay = true; }
    }
    auto &display = streamDisplay();
    std::lock_guard<std::mutex> lock(display.mutex);
    if (display.active && !queueDisplay) {
        s.title = display.title; s.artist = display.artist; s.album = display.album;
        s.trackDuration = display.duration;
    }
    return s;
}

void Status::update()
{
    if (m_player) m_player->poll();
    m_current = poll();
    if (m_player) {
        const auto info = m_player->transportInfo();
        if (info.uriKnown && (!m_haveUri || info.uri != m_lastUri)) {
            m_haveUri = true; m_lastUri = info.uri;
            printf("speaker URI: %s\n", speakerUriDescription(info.uri, streamSessionToken()).c_str());
        }
    }
}

bool Status::changed()
{
    if (m_haveReported && m_current == m_lastReported)
        return false;
    m_lastReported = m_current;
    m_haveReported = true;
    return true;
}

size_t Status::hash()
{
    // No longer used internally (changed() compares snapshots directly);
    // kept so callers built against the earlier interface still link.
    size_t h = std::hash<std::string>{}(m_current.title);
    h ^= std::hash<std::string>{}(m_current.album) << 1;
    h ^= std::hash<std::string>{}(m_current.artist) << 2;
    return h;
}

void Status::print()
{
    streamMetrics().print(streamMetrics().sample());
    if (auto* mode = roomMode())
        printf("Playback: %s\n", RoomStatus::json(mode->status()).c_str());
    const std::string border(97, '-');
    int nameField = static_cast<int>(border.length()) - static_cast<int>(m_zoneName.length()) - 5;
    if (nameField < 0)
        nameField = 0;
    printf("\n+%s %s ---+\n", border.substr(0, static_cast<size_t>(nameField)).c_str(), m_zoneName.c_str());
    printf("| Title  %-84s |\n", m_current.title.c_str());
    if (!m_current.album.empty())
        printf("| Album  %-84s |\n", m_current.album.c_str());
    if (!m_current.artist.empty())
        printf("| Artist %-84s |\n", m_current.artist.c_str());
    printf("+%s+\n", border.c_str());
    printf("| %-26s | %-24s | vol %3u / 100 | %8s / %8s |\n",
        m_current.transportStatus.c_str(),
        m_current.transportState.c_str(),
        static_cast<unsigned>(m_current.volume),
        m_current.relativeTime.c_str(),
        m_current.trackDuration.c_str());
    printf("+%s+\n", border.c_str());
}

void Status::get_mac(uint8_t* mac) const
{
    // Sonos zone UUIDs take the form "RINCON_" followed by the 12 hex
    // digits of the player's MAC address (e.g. "RINCON_5CAAFDXXXXXX01400").
    static constexpr size_t kPrefixLen = 7;   // strlen("RINCON_")
    static constexpr size_t kMacHexLen = 12;  // 6 bytes, 2 hex chars each
    if (m_zoneUuid.length() < kPrefixLen + kMacHexLen)
        return;
    for (size_t i = 0; i < 6; ++i) {
        char byteHex[3] = {
            m_zoneUuid[kPrefixLen + 2 * i],
            m_zoneUuid[kPrefixLen + 2 * i + 1],
            '\0',
        };
        mac[i] = static_cast<uint8_t>(std::strtoul(byteHex, nullptr, 16));
    }
}