// 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 #include 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 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 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{}(m_current.title); h ^= std::hash{}(m_current.album) << 1; h ^= std::hash{}(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(border.length()) - static_cast(m_zoneName.length()) - 5; if (nameField < 0) nameField = 0; printf("\n+%s %s ---+\n", border.substr(0, static_cast(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(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(std::strtoul(byteHex, nullptr, 16)); } }