Implement Cast Streaming mirroring, DLNA casting, daemon+GUI, and breadd integration
Some checks failed
dev release / build (push) Failing after 12s
Some checks failed
dev release / build (push) Failing after 12s
Builds out the full v1 scope: a vendored+patched openscreen subset for low-latency Cast Streaming (Mirroring receiver 0F5096E8) alongside the existing Cast V2/HLS and new DLNA/AVTransport casting paths, breadcastd's Idle/Casting state machine with a private IPC socket, the breadcast GTK4 popup as a thin IPC client, and bread.cast.*/bread.command.cast.* breadd integration (device discovery, start/stop, mirroring lifecycle events). Also adds bakery/systemd/Forgejo CI packaging. Validated end-to-end against a real Chromecast/Google TV: negotiated Cast Streaming session, live pipeline playback, and daemon+GUI click-to-cast/ stop through the actual popup.
This commit is contained in:
parent
887c29002f
commit
8c745d18e0
283 changed files with 36788 additions and 0 deletions
300
breadcast-caststream-sys/vendor/openscreen/cast/streaming/public/receiver_message.cc
vendored
Normal file
300
breadcast-caststream-sys/vendor/openscreen/cast/streaming/public/receiver_message.cc
vendored
Normal file
|
|
@ -0,0 +1,300 @@
|
|||
// Copyright 2020 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "cast/streaming/public/receiver_message.h"
|
||||
|
||||
#include <utility>
|
||||
#include <variant>
|
||||
|
||||
#include "cast/streaming/message_fields.h"
|
||||
#include "json/reader.h"
|
||||
#include "json/writer.h"
|
||||
#include "platform/base/error.h"
|
||||
#include "util/base64.h"
|
||||
#include "util/enum_name_table.h"
|
||||
#include "util/json/json_helpers.h"
|
||||
#include "util/json/json_serialization.h"
|
||||
#include "util/osp_logging.h"
|
||||
#include "util/string_util.h"
|
||||
#include "util/stringprintf.h"
|
||||
|
||||
namespace openscreen::cast {
|
||||
|
||||
namespace {
|
||||
|
||||
EnumNameTable<ReceiverMessage::Type, 4> kMessageTypeNames{
|
||||
{{kMessageTypeAnswer, ReceiverMessage::Type::kAnswer},
|
||||
{"CAPABILITIES_RESPONSE", ReceiverMessage::Type::kCapabilitiesResponse},
|
||||
{"RPC", ReceiverMessage::Type::kRpc},
|
||||
{"INPUT", ReceiverMessage::Type::kInput}}};
|
||||
|
||||
EnumNameTable<MediaCapability, 10> kMediaCapabilityNames{
|
||||
{{"audio", MediaCapability::kAudio},
|
||||
{"aac", MediaCapability::kAac},
|
||||
{"opus", MediaCapability::kOpus},
|
||||
{"video", MediaCapability::kVideo},
|
||||
{"4k", MediaCapability::k4k},
|
||||
{"h264", MediaCapability::kH264},
|
||||
{"vp8", MediaCapability::kVp8},
|
||||
{"vp9", MediaCapability::kVp9},
|
||||
{"hevc", MediaCapability::kHevc},
|
||||
{"av1", MediaCapability::kAv1}}};
|
||||
|
||||
ReceiverMessage::Type GetMessageType(const Json::Value& root) {
|
||||
std::string type;
|
||||
if (!json::TryParseString(root[kMessageType], &type)) {
|
||||
return ReceiverMessage::Type::kUnknown;
|
||||
}
|
||||
string_util::AsciiStrToUpper(type);
|
||||
|
||||
ErrorOr<ReceiverMessage::Type> parsed = GetEnum(kMessageTypeNames, type);
|
||||
return parsed.value(ReceiverMessage::Type::kUnknown);
|
||||
}
|
||||
|
||||
bool TryParseCapability(const Json::Value& value, MediaCapability* out) {
|
||||
std::string c;
|
||||
if (!json::TryParseString(value, &c)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const ErrorOr<MediaCapability> capability = GetEnum(kMediaCapabilityNames, c);
|
||||
if (capability.is_error()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
*out = capability.value();
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
ReceiverError::ReceiverError(int code, std::string_view description)
|
||||
: code(code), description(description) {
|
||||
if (code >= kOpenscreenErrorOffset) {
|
||||
openscreen_code = static_cast<Error::Code>(code - kOpenscreenErrorOffset);
|
||||
}
|
||||
}
|
||||
|
||||
ReceiverError::ReceiverError(Error::Code code, std::string_view description)
|
||||
: code(static_cast<int>(code) + kOpenscreenErrorOffset),
|
||||
openscreen_code(code),
|
||||
description(description) {}
|
||||
|
||||
ReceiverError::ReceiverError(const Error& error)
|
||||
: code(static_cast<int>(error.code()) + kOpenscreenErrorOffset),
|
||||
openscreen_code(error.code()),
|
||||
description(error.message()) {}
|
||||
|
||||
ReceiverError::ReceiverError(const ReceiverError&) = default;
|
||||
ReceiverError::ReceiverError(ReceiverError&&) noexcept = default;
|
||||
ReceiverError& ReceiverError::operator=(const ReceiverError&) = default;
|
||||
ReceiverError& ReceiverError::operator=(ReceiverError&&) = default;
|
||||
ReceiverError::~ReceiverError() = default;
|
||||
|
||||
// static
|
||||
ErrorOr<ReceiverError> ReceiverError::Parse(const Json::Value& value) {
|
||||
if (!value.isObject()) {
|
||||
return Error(Error::Code::kParameterInvalid,
|
||||
"Empty JSON in receiver error parsing");
|
||||
}
|
||||
|
||||
int code;
|
||||
std::string description;
|
||||
if (!json::TryParseInt(value[kErrorCode], &code) ||
|
||||
!json::TryParseString(value[kErrorDescription], &description)) {
|
||||
return Error::Code::kJsonParseError;
|
||||
}
|
||||
|
||||
return ReceiverError(code, description);
|
||||
}
|
||||
|
||||
Json::Value ReceiverError::ToJson() const {
|
||||
Json::Value root;
|
||||
root[kErrorCode] = openscreen_code ? static_cast<int>(*openscreen_code) +
|
||||
kOpenscreenErrorOffset
|
||||
: code;
|
||||
root[kErrorDescription] = description;
|
||||
return root;
|
||||
}
|
||||
|
||||
Error ReceiverError::ToError() const {
|
||||
if (openscreen_code) {
|
||||
return Error(*openscreen_code, description);
|
||||
}
|
||||
|
||||
std::string full_description = StringFormat("Error code: {}, description: {}",
|
||||
code, description.c_str());
|
||||
return Error(Error::Code::kUnknownError, std::move(full_description));
|
||||
}
|
||||
|
||||
// static
|
||||
ErrorOr<ReceiverCapability> ReceiverCapability::Parse(
|
||||
const Json::Value& value) {
|
||||
if (!value.isObject()) {
|
||||
return Error(Error::Code::kParameterInvalid,
|
||||
"Empty JSON in capabilities parsing");
|
||||
}
|
||||
|
||||
int remoting_version;
|
||||
if (!json::TryParseInt(value["remoting"], &remoting_version)) {
|
||||
remoting_version = ReceiverCapability::kRemotingVersionUnknown;
|
||||
}
|
||||
|
||||
std::vector<MediaCapability> capabilities;
|
||||
if (!json::TryParseArray<MediaCapability>(
|
||||
value["mediaCaps"], TryParseCapability, &capabilities)) {
|
||||
return Error(Error::Code::kJsonParseError,
|
||||
"Failed to parse media capabilities");
|
||||
}
|
||||
|
||||
return ReceiverCapability{remoting_version, std::move(capabilities)};
|
||||
}
|
||||
|
||||
Json::Value ReceiverCapability::ToJson() const {
|
||||
Json::Value root;
|
||||
root["remoting"] = remoting_version;
|
||||
Json::Value capabilities(Json::ValueType::arrayValue);
|
||||
for (const auto& capability : media_capabilities) {
|
||||
capabilities.append(GetEnumName(kMediaCapabilityNames, capability).value());
|
||||
}
|
||||
root["mediaCaps"] = std::move(capabilities);
|
||||
return root;
|
||||
}
|
||||
|
||||
// static
|
||||
ErrorOr<ReceiverMessage> ReceiverMessage::Parse(const Json::Value& value) {
|
||||
ReceiverMessage message;
|
||||
if (!value.isObject()) {
|
||||
return Error(Error::Code::kJsonParseError, "Invalid message body");
|
||||
}
|
||||
|
||||
std::string result;
|
||||
if (!json::TryParseString(value[kResult], &result)) {
|
||||
result = kResultError;
|
||||
}
|
||||
|
||||
message.type = GetMessageType(value);
|
||||
message.valid =
|
||||
(result == kResultOk || message.type == ReceiverMessage::Type::kRpc ||
|
||||
message.type == ReceiverMessage::Type::kInput);
|
||||
|
||||
if (message.type != ReceiverMessage::Type::kRpc &&
|
||||
message.type != ReceiverMessage::Type::kInput) {
|
||||
if (!json::TryParseInt(value[kSequenceNumber],
|
||||
&(message.sequence_number))) {
|
||||
message.sequence_number = -1;
|
||||
}
|
||||
|
||||
// Sequence numbers must be non-negative.
|
||||
if (message.sequence_number < 0) {
|
||||
message.valid = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!message.valid) {
|
||||
ErrorOr<ReceiverError> error =
|
||||
ReceiverError::Parse(value[kErrorMessageBody]);
|
||||
if (error.is_value()) {
|
||||
message.body = std::move(error.value());
|
||||
}
|
||||
return message;
|
||||
}
|
||||
|
||||
switch (message.type) {
|
||||
case Type::kAnswer: {
|
||||
auto answer_or_error =
|
||||
openscreen::cast::Answer::TryParse(value[kAnswerMessageBody]);
|
||||
if (answer_or_error.is_value()) {
|
||||
message.body = std::move(answer_or_error.value());
|
||||
message.valid = true;
|
||||
}
|
||||
} break;
|
||||
|
||||
case Type::kCapabilitiesResponse: {
|
||||
ErrorOr<ReceiverCapability> capability =
|
||||
ReceiverCapability::Parse(value[kCapabilitiesMessageBody]);
|
||||
if (capability.is_value()) {
|
||||
message.body = std::move(capability.value());
|
||||
message.valid = true;
|
||||
}
|
||||
} break;
|
||||
|
||||
case Type::kRpc: {
|
||||
std::string encoded_rpc;
|
||||
std::vector<uint8_t> rpc;
|
||||
if (json::TryParseString(value[kRpcMessageBody], &encoded_rpc) &&
|
||||
base64::Decode(encoded_rpc, &rpc)) {
|
||||
message.body = std::move(rpc);
|
||||
message.valid = true;
|
||||
}
|
||||
} break;
|
||||
|
||||
case Type::kInput: {
|
||||
std::string encoded_input;
|
||||
std::vector<uint8_t> input;
|
||||
if (json::TryParseString(value[kInputMessageBody], &encoded_input) &&
|
||||
base64::Decode(encoded_input, &input)) {
|
||||
message.body = std::move(input);
|
||||
message.valid = true;
|
||||
}
|
||||
} break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return message;
|
||||
}
|
||||
|
||||
ErrorOr<Json::Value> ReceiverMessage::ToJson() const {
|
||||
OSP_CHECK(type != ReceiverMessage::Type::kUnknown)
|
||||
<< "Trying to send an unknown message is a developer error";
|
||||
|
||||
Json::Value root;
|
||||
root[kMessageType] = GetEnumName(kMessageTypeNames, type).value();
|
||||
if (sequence_number >= 0) {
|
||||
root[kSequenceNumber] = sequence_number;
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
case ReceiverMessage::Type::kAnswer:
|
||||
if (valid) {
|
||||
root[kResult] = kResultOk;
|
||||
root[kAnswerMessageBody] = std::get<Answer>(body).ToJson();
|
||||
} else {
|
||||
root[kResult] = kResultError;
|
||||
root[kErrorMessageBody] = std::get<ReceiverError>(body).ToJson();
|
||||
}
|
||||
break;
|
||||
|
||||
case ReceiverMessage::Type::kCapabilitiesResponse:
|
||||
if (valid) {
|
||||
root[kResult] = kResultOk;
|
||||
root[kCapabilitiesMessageBody] =
|
||||
std::get<ReceiverCapability>(body).ToJson();
|
||||
} else {
|
||||
root[kResult] = kResultError;
|
||||
root[kErrorMessageBody] = std::get<ReceiverError>(body).ToJson();
|
||||
}
|
||||
break;
|
||||
|
||||
// NOTE: RPC messages do NOT have a result field.
|
||||
case ReceiverMessage::Type::kRpc:
|
||||
root[kRpcMessageBody] =
|
||||
base64::Encode(std::get<std::vector<uint8_t>>(body));
|
||||
break;
|
||||
|
||||
case ReceiverMessage::Type::kInput:
|
||||
root[kInputMessageBody] =
|
||||
base64::Encode(std::get<std::vector<uint8_t>>(body));
|
||||
break;
|
||||
|
||||
default:
|
||||
OSP_NOTREACHED();
|
||||
}
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
} // namespace openscreen::cast
|
||||
Loading…
Add table
Add a link
Reference in a new issue