diff --git a/changelog.md b/changelog.md
index e7de162d..3ce8364d 100644
--- a/changelog.md
+++ b/changelog.md
@@ -4,8 +4,8 @@
### Features
-- Client: Add support for (secure-) websockets (Issue #1325)
-- Server: Add client authentication (Issue #1334)
+- Client: Add support for (secure-) websockets (Issue #1325, PR #1340)
+- Server: Add client authentication (Issue #1334, PR #1340)
### Bugfixes
diff --git a/common/message/client_info.hpp b/common/message/client_info.hpp
index 06331841..2e388743 100644
--- a/common/message/client_info.hpp
+++ b/common/message/client_info.hpp
@@ -1,6 +1,6 @@
/***
This file is part of snapcast
- Copyright (C) 2014-2022 Johannes Pohl
+ Copyright (C) 2014-2025 Johannes Pohl
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -16,8 +16,7 @@
along with this program. If not, see .
***/
-#ifndef MESSAGE_CLIENT_INFO_HPP
-#define MESSAGE_CLIENT_INFO_HPP
+#pragma once
// local headers
#include "json_message.hpp"
@@ -27,39 +26,47 @@ namespace msg
{
/// Client information sent from client to server
-/// Might also be used for sync stats and latency estimations
+/// Might also be used for other things in future, like
+/// - sync stats
+/// - latency estimations
+/// - Battery status
+/// - ...
class ClientInfo : public JsonMessage
{
public:
+ /// c'tor
ClientInfo() : JsonMessage(message_type::kClientInfo)
{
setVolume(100);
setMuted(false);
}
+ /// d'tor
~ClientInfo() override = default;
+ /// @return the volume in percent
uint16_t getVolume()
{
return get("volume", static_cast(100));
}
+ /// @return if muted or not
bool isMuted()
{
return get("muted", false);
}
+ /// Set the volume to @p volume percent
void setVolume(uint16_t volume)
{
msg["volume"] = volume;
}
+ /// Set muted to @p muted
void setMuted(bool muted)
{
msg["muted"] = muted;
}
};
+
} // namespace msg
-
-
-#endif
diff --git a/common/message/codec_header.hpp b/common/message/codec_header.hpp
index 3f91c971..743760c0 100644
--- a/common/message/codec_header.hpp
+++ b/common/message/codec_header.hpp
@@ -1,6 +1,6 @@
/***
This file is part of snapcast
- Copyright (C) 2014-2022 Johannes Pohl
+ Copyright (C) 2014-2025 Johannes Pohl
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -16,8 +16,8 @@
along with this program. If not, see .
***/
-#ifndef MESSAGE_CODEC_HEADER_HPP
-#define MESSAGE_CODEC_HEADER_HPP
+#pragma once
+
// local headers
#include "message.hpp"
@@ -31,13 +31,15 @@ namespace msg
class CodecHeader : public BaseMessage
{
public:
- explicit CodecHeader(const std::string& codecName = "", uint32_t size = 0)
- : BaseMessage(message_type::kCodecHeader), payloadSize(size), payload(nullptr), codec(codecName)
+ /// c'tor taking the @p codec_name and @p site of the payload
+ explicit CodecHeader(const std::string& codec_name = "", uint32_t size = 0)
+ : BaseMessage(message_type::kCodecHeader), payloadSize(size), payload(nullptr), codec(codec_name)
{
if (size > 0)
payload = static_cast(malloc(size * sizeof(char)));
}
+ /// d'tor
~CodecHeader() override
{
free(payload);
@@ -54,8 +56,11 @@ public:
return static_cast(sizeof(uint32_t) + codec.size() + sizeof(uint32_t) + payloadSize);
}
+ /// payload size
uint32_t payloadSize;
+ /// the payload
char* payload;
+ /// name of the codec
std::string codec;
protected:
@@ -65,7 +70,5 @@ protected:
writeVal(stream, payload, payloadSize);
}
};
+
} // namespace msg
-
-
-#endif
diff --git a/common/message/factory.hpp b/common/message/factory.hpp
index ac236ed6..767fe945 100644
--- a/common/message/factory.hpp
+++ b/common/message/factory.hpp
@@ -1,6 +1,6 @@
/***
This file is part of snapcast
- Copyright (C) 2014-2024 Johannes Pohl
+ Copyright (C) 2014-2025 Johannes Pohl
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -30,15 +30,16 @@
namespace msg
{
+/// Cast a BaseMessage @message to type "ToType"
+/// @return castest message or nullptr, if the cast failed
template
static std::unique_ptr message_cast(std::unique_ptr message)
{
- ToType* tmp = dynamic_cast(message.get());
- std::unique_ptr result;
+ auto* tmp = dynamic_cast(message.get());
if (tmp != nullptr)
{
message.release();
- result.reset(tmp);
+ std::unique_ptr result(tmp);
return result;
}
return nullptr;
@@ -47,6 +48,7 @@ static std::unique_ptr message_cast(std::unique_ptr me
namespace factory
{
+/// Create a message of type T from @p base_message beaser and payload @p buffer
template
static std::unique_ptr createMessage(const BaseMessage& base_message, char* buffer)
{
@@ -57,6 +59,7 @@ static std::unique_ptr createMessage(const BaseMessage& base_message, char* b
return result;
}
+/// Create a BaseMessage from @p base_message header and payload @p buffer
static std::unique_ptr createMessage(const BaseMessage& base_message, char* buffer)
{
std::unique_ptr result;
diff --git a/common/message/hello.hpp b/common/message/hello.hpp
index 3deb161e..5e768319 100644
--- a/common/message/hello.hpp
+++ b/common/message/hello.hpp
@@ -1,6 +1,6 @@
/***
This file is part of snapcast
- Copyright (C) 2014-2022 Johannes Pohl
+ Copyright (C) 2014-2025 Johannes Pohl
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -16,8 +16,7 @@
along with this program. If not, see .
***/
-#ifndef MESSAGE_HELLO_HPP
-#define MESSAGE_HELLO_HPP
+#pragma once
// local headers
#include "common/str_compat.hpp"
@@ -31,16 +30,20 @@
namespace msg
{
+/// Hello message
+/// Initial message, sent from client to server
class Hello : public JsonMessage
{
public:
+ /// c'tor
Hello() : JsonMessage(message_type::kHello)
{
}
- Hello(const std::string& macAddress, const std::string& id, size_t instance) : JsonMessage(message_type::kHello)
+ /// c'tor taking @p macAddress, @p id and @p instance
+ Hello(const std::string& mac_address, const std::string& id, size_t instance) : JsonMessage(message_type::kHello)
{
- msg["MAC"] = macAddress;
+ msg["MAC"] = mac_address;
msg["HostName"] = ::getHostName();
msg["Version"] = VERSION;
msg["ClientName"] = "Snapclient";
@@ -51,53 +54,64 @@ public:
msg["SnapStreamProtocolVersion"] = 2;
}
+ /// d'tor
~Hello() override = default;
+ /// @return the MAC address
std::string getMacAddress() const
{
return msg["MAC"];
}
+ /// @return the host name
std::string getHostName() const
{
return msg["HostName"];
}
+ /// @return the client version
std::string getVersion() const
{
return msg["Version"];
}
+ /// @return the client name (e.g. "Snapclient")
std::string getClientName() const
{
return msg["ClientName"];
}
+ /// @return the OS name
std::string getOS() const
{
return msg["OS"];
}
+ /// @return the CPU architecture
std::string getArch() const
{
return msg["Arch"];
}
+ /// @return the instance id
int getInstance() const
{
return get("Instance", 1);
}
+ /// @return the protocol version
int getProtocolVersion() const
{
return get("SnapStreamProtocolVersion", 1);
}
+ /// @return a unqiue machine ID
std::string getId() const
{
return get("ID", getMacAddress());
}
+ /// @return a unqiue client ID
std::string getUniqueId() const
{
std::string id = getId();
@@ -109,7 +123,5 @@ public:
return id;
}
};
+
} // namespace msg
-
-
-#endif
diff --git a/common/message/json_message.hpp b/common/message/json_message.hpp
index 127a1d33..a9829ff2 100644
--- a/common/message/json_message.hpp
+++ b/common/message/json_message.hpp
@@ -1,6 +1,6 @@
/***
This file is part of snapcast
- Copyright (C) 2014-2022 Johannes Pohl
+ Copyright (C) 2014-2025 Johannes Pohl
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -16,8 +16,8 @@
along with this program. If not, see .
***/
-#ifndef MESSAGE_JSON_HPP
-#define MESSAGE_JSON_HPP
+#pragma once
+
// local headers
#include "common/json.hpp"
@@ -30,13 +30,16 @@ using json = nlohmann::json;
namespace msg
{
+/// Base class of a message with json payload
class JsonMessage : public BaseMessage
{
public:
- JsonMessage(message_type msgType) : BaseMessage(msgType)
+ /// c'tor taking the @p msg_type
+ explicit JsonMessage(message_type msg_type) : BaseMessage(msg_type)
{
}
+ /// d'tor
~JsonMessage() override = default;
void read(std::istream& stream) override
@@ -60,6 +63,7 @@ protected:
writeVal(stream, msg.dump());
}
+ /// @return value for key @p what or @p def, if not found
template
T get(const std::string& what, const T& def) const
{
@@ -75,7 +79,5 @@ protected:
}
}
};
+
} // namespace msg
-
-
-#endif
diff --git a/common/message/pcm_chunk.hpp b/common/message/pcm_chunk.hpp
index df4e66aa..5454d5c2 100644
--- a/common/message/pcm_chunk.hpp
+++ b/common/message/pcm_chunk.hpp
@@ -38,15 +38,17 @@ namespace msg
class PcmChunk : public WireChunk
{
public:
- PcmChunk(const SampleFormat& sampleFormat, uint32_t ms)
- : WireChunk((sampleFormat.rate() * ms / 1000) * sampleFormat.frameSize()), format(sampleFormat), idx_(0)
+ /// c'tor, construct from @p sample_format with duration @p ms
+ PcmChunk(const SampleFormat& sample_format, uint32_t ms) : WireChunk((sample_format.rate() * ms / 1000) * sample_format.frameSize()), format(sample_format)
{
}
- PcmChunk() : WireChunk(), idx_(0)
+ /// c'tor
+ PcmChunk() : WireChunk()
{
}
+ /// d'tor
~PcmChunk() override = default;
#if 0
@@ -73,16 +75,18 @@ public:
// return result;
// }
- int readFrames(void* outputBuffer, uint32_t frameCount)
+ /// Read the next @p frame_count frames into @p output_buffer, update the internal read position
+ /// @return number of frames copied to @p output_buffer
+ int readFrames(void* output_buffer, uint32_t frame_count)
{
// logd << "read: " << frameCount << ", total: " << (wireChunk->length / format.frameSize()) << ", idx: " << idx;// << "\n";
- int result = frameCount;
- if (idx_ + frameCount > (payloadSize / format.frameSize()))
+ int result = frame_count;
+ if (idx_ + frame_count > (payloadSize / format.frameSize()))
result = (payloadSize / format.frameSize()) - idx_;
// logd << ", from: " << format.frameSize()*idx << ", to: " << format.frameSize()*idx + format.frameSize()*result;
- if (outputBuffer != nullptr)
- memcpy(static_cast(outputBuffer), static_cast(payload) + format.frameSize() * idx_, format.frameSize() * result);
+ if (output_buffer != nullptr)
+ memcpy(static_cast(output_buffer), static_cast(payload) + format.frameSize() * idx_, format.frameSize() * result);
idx_ += result;
// logd << ", new idx: " << idx << ", result: " << result << ", wireChunk->length: " << wireChunk->length << ", format.frameSize(): " <<
@@ -90,6 +94,8 @@ public:
return result;
}
+ /// seek @p frames forward or backward
+ /// @return the new read position
int seek(int frames)
{
if ((frames < 0) && (-frames > static_cast(idx_)))
@@ -102,17 +108,20 @@ public:
return idx_;
}
+ /// @return start time of the current frame
chronos::time_point_clk start() const override
{
return chronos::time_point_clk(chronos::sec(timestamp.sec) + chronos::usec(timestamp.usec) +
chronos::usec(static_cast(1000000. * ((double)idx_ / (double)format.rate()))));
}
+ /// @return time of the last frame
inline chronos::time_point_clk end() const
{
return start() + durationLeft();
}
+ /// @return duration of this chunk
template
inline T duration() const
{
@@ -127,42 +136,51 @@ public:
// payloadSize = newSize;
// }
- void setFrameCount(int frameCount)
+ /// Set the @p frame_count, reserve memory
+ void setFrameCount(int frame_count)
{
- auto newSize = format.frameSize() * frameCount;
- payload = static_cast(realloc(payload, newSize));
- payloadSize = newSize;
+ auto new_size = format.frameSize() * frame_count;
+ payload = static_cast(realloc(payload, new_size));
+ payloadSize = new_size;
}
+ /// @return duration of this chunk in [ms]
double durationMs() const
{
return static_cast(getFrameCount()) / format.msRate();
}
+ /// @return time left, starting from the read pointer
template
inline T durationLeft() const
{
return std::chrono::duration_cast(chronos::nsec(static_cast(1000000 * (getFrameCount() - idx_) / format.msRate())));
}
+ /// @return true if the read pointer is at the end
inline bool isEndOfChunk() const
{
return idx_ >= getFrameCount();
}
+ /// @return number of frames
inline uint32_t getFrameCount() const
{
return (payloadSize / format.frameSize());
}
+ /// @return number of samples
inline uint32_t getSampleCount() const
{
return (payloadSize / format.sampleSize());
}
+ /// Sample format of this chunk
SampleFormat format;
private:
+ /// current read position (frame idx)
uint32_t idx_ = 0;
};
+
} // namespace msg
diff --git a/common/message/server_settings.hpp b/common/message/server_settings.hpp
index 0979118a..cff96977 100644
--- a/common/message/server_settings.hpp
+++ b/common/message/server_settings.hpp
@@ -1,6 +1,6 @@
/***
This file is part of snapcast
- Copyright (C) 2014-2022 Johannes Pohl
+ Copyright (C) 2014-2025 Johannes Pohl
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -16,8 +16,7 @@
along with this program. If not, see .
***/
-#ifndef MESSAGE_SERVER_SETTINGS_HPP
-#define MESSAGE_SERVER_SETTINGS_HPP
+#pragma once
// local headers
#include "json_message.hpp"
@@ -26,9 +25,11 @@
namespace msg
{
+/// Dynamic settings that affect the client
class ServerSettings : public JsonMessage
{
public:
+ /// c'tor
ServerSettings() : JsonMessage(message_type::kServerSettings)
{
setBufferMs(0);
@@ -37,51 +38,57 @@ public:
setMuted(false);
}
+ /// d'tor
~ServerSettings() override = default;
+ /// @return the end to end delay in [ms]
int32_t getBufferMs()
{
return get("bufferMs", 0);
}
+ /// @return client specific additional latency in [ms]
int32_t getLatency()
{
return get("latency", 0);
}
+ /// @return the volume in [%]
uint16_t getVolume()
{
return get("volume", static_cast(100));
}
+ /// @return if muted
bool isMuted()
{
return get("muted", false);
}
-
- void setBufferMs(int32_t bufferMs)
+ /// Set the end to end delay to @p buffer_ms [ms]
+ void setBufferMs(int32_t buffer_ms)
{
- msg["bufferMs"] = bufferMs;
+ msg["bufferMs"] = buffer_ms;
}
+ /// Set the additional client specific @p latency [ms]
void setLatency(int32_t latency)
{
msg["latency"] = latency;
}
+ /// Set the @p volume [%]
void setVolume(uint16_t volume)
{
msg["volume"] = volume;
}
+ /// Set client to @p muted
void setMuted(bool muted)
{
msg["muted"] = muted;
}
};
+
} // namespace msg
-
-
-#endif
diff --git a/common/message/time.hpp b/common/message/time.hpp
index 3abc18bb..52cf8c7b 100644
--- a/common/message/time.hpp
+++ b/common/message/time.hpp
@@ -1,6 +1,6 @@
/***
This file is part of snapcast
- Copyright (C) 2014-2022 Johannes Pohl
+ Copyright (C) 2014-2025 Johannes Pohl
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -16,8 +16,7 @@
along with this program. If not, see .
***/
-#ifndef MESSAGE_TIME_HPP
-#define MESSAGE_TIME_HPP
+#pragma once
// local headers
#include "message.hpp"
@@ -25,13 +24,16 @@
namespace msg
{
+/// Time sync message, send from client to server and back
class Time : public BaseMessage
{
public:
+ /// c'tor
Time() : BaseMessage(message_type::kTime)
{
}
+ /// d'tor
~Time() override = default;
void read(std::istream& stream) override
@@ -45,6 +47,7 @@ public:
return sizeof(tv);
}
+ /// The latency after round trip "client => server => client"
tv latency;
protected:
@@ -54,7 +57,5 @@ protected:
writeVal(stream, latency.usec);
}
};
+
} // namespace msg
-
-
-#endif