mirror of
https://github.com/kunkundi/crossdesk.git
synced 2026-10-23 21:23:12 +08:00
[feat] add DeskHub stream controls and network status
This commit is contained in:
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* @Author: DI JUNKUN
|
||||
* @Date: 2026-10-12
|
||||
* Copyright (c) 2026 by DI JUNKUN, All Rights Reserved.
|
||||
*/
|
||||
|
||||
#ifndef _GALLERY_STREAM_SETTINGS_H_
|
||||
#define _GALLERY_STREAM_SETTINGS_H_
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <optional>
|
||||
#include <preview_subscription.h>
|
||||
|
||||
#include "preview_resolution.h"
|
||||
|
||||
namespace crossdesk::gallery {
|
||||
struct StreamSettings {
|
||||
// Persisted preset order: automatic, fixed bounds, then original size.
|
||||
static constexpr std::array<PreviewResolution, 8> kResolutions{{
|
||||
{}, {320, 180}, {640, 360}, {1280, 720}, {1920, 1080},
|
||||
{2560, 1440}, {3840, 2160}, {}}};
|
||||
static constexpr std::array<int, 4> kFrameRates{0, 15, 30, 60};
|
||||
int resolution = 0;
|
||||
int frame_rate = 0; // 0 follows the session's existing 30/60 FPS preference.
|
||||
|
||||
static std::optional<StreamSettings> FromIndices(int resolution, int fps) {
|
||||
if (resolution < 0 || resolution >= int(kResolutions.size()) ||
|
||||
fps < 0 || fps >= int(kFrameRates.size()))
|
||||
return {};
|
||||
return StreamSettings{resolution, kFrameRates[fps]};
|
||||
}
|
||||
int FrameRateIndex() const {
|
||||
return int(std::find(kFrameRates.begin(), kFrameRates.end(), frame_rate) -
|
||||
kFrameRates.begin());
|
||||
}
|
||||
bool Valid() const {
|
||||
return resolution >= 0 && resolution < int(kResolutions.size()) &&
|
||||
FrameRateIndex() < int(kFrameRates.size());
|
||||
}
|
||||
static StreamSettings FromJson(const nlohmann::json& json) {
|
||||
try {
|
||||
if (!json.at("resolution").is_number_integer() ||
|
||||
!json.at("frame_rate").is_number_integer()) return {};
|
||||
const auto resolution = json.at("resolution").get<int64_t>();
|
||||
const auto fps = json.at("frame_rate").get<int64_t>();
|
||||
if (resolution < 0 || resolution >= int(kResolutions.size()) ||
|
||||
fps < 0 || fps > 60) return {};
|
||||
// Retired sparse presets migrate to the lowest current setting.
|
||||
StreamSettings result{int(resolution), fps == 1 || fps == 8 ? 15 : int(fps)};
|
||||
return result.Valid() ? result : StreamSettings{};
|
||||
} catch (...) {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
nlohmann::json ToJson() const {
|
||||
return {{"resolution", resolution}, {"frame_rate", frame_rate}};
|
||||
}
|
||||
PreviewResolution Resolution(bool wall_managed, PreviewResolution wall_view,
|
||||
PreviewResolution stream_view) const {
|
||||
if (!wall_managed) return {};
|
||||
if (resolution != 0 && Valid()) return kResolutions[resolution];
|
||||
// A square bound limits the longest edge of an unexpanded wall tile.
|
||||
if (!wall_view.width && !stream_view.width) return {320, 320};
|
||||
return {std::max(wall_view.width, stream_view.width),
|
||||
std::max(wall_view.height, stream_view.height)};
|
||||
}
|
||||
void ApplyFrameRate(PreviewSubscription& demand, bool supports_fps15) const {
|
||||
if (!Valid() || !frame_rate) return;
|
||||
// Older peers reject fps=15. Keep their existing interactive wire format.
|
||||
demand.fps = frame_rate == 15 && supports_fps15 ? 15 : 60;
|
||||
demand.frame_rate = frame_rate <= 30 ? 30 : 60;
|
||||
}
|
||||
};
|
||||
} // namespace crossdesk::gallery
|
||||
#endif
|
||||
@@ -17,6 +17,7 @@
|
||||
#include <vector>
|
||||
|
||||
#include "gallery_storage.h"
|
||||
#include "gallery_stream_settings.h"
|
||||
|
||||
namespace crossdesk::gallery {
|
||||
struct Member {
|
||||
@@ -33,6 +34,7 @@ struct Workspace {
|
||||
std::set<std::string> selected;
|
||||
int page = 0;
|
||||
int capacity = 6;
|
||||
StreamSettings stream_settings;
|
||||
|
||||
static std::string NormalizeId(const std::string &input) {
|
||||
std::string result;
|
||||
@@ -181,6 +183,8 @@ struct Workspace {
|
||||
if (version == 2 && !doc.at("wall_ids").is_array())
|
||||
return false;
|
||||
Workspace loaded;
|
||||
if (doc.contains("stream_settings"))
|
||||
loaded.stream_settings = StreamSettings::FromJson(doc["stream_settings"]);
|
||||
for (const auto &member : doc.at("members")) {
|
||||
const auto id = member.at("id").get<std::string>();
|
||||
loaded.AddAssignment(id, member.value("group", ""));
|
||||
@@ -197,6 +201,7 @@ struct Workspace {
|
||||
members = std::move(loaded.members);
|
||||
groups = std::move(loaded.groups);
|
||||
wall_ids_ = std::move(loaded.wall_ids_);
|
||||
stream_settings = loaded.stream_settings;
|
||||
return true;
|
||||
} catch (...) {
|
||||
return false;
|
||||
@@ -206,7 +211,8 @@ struct Workspace {
|
||||
try {
|
||||
nlohmann::json doc{{"version", 2},
|
||||
{"members", nlohmann::json::array()},
|
||||
{"wall_ids", wall_ids_}};
|
||||
{"wall_ids", wall_ids_},
|
||||
{"stream_settings", stream_settings.ToJson()}};
|
||||
for (const auto &m : members)
|
||||
doc["members"].push_back({{"id", m.id}, {"group", m.group}});
|
||||
return WriteAtomically(path, doc.dump(2));
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* @Author: DI JUNKUN
|
||||
* @Date: 2026-10-12
|
||||
* Copyright (c) 2026 by DI JUNKUN, All Rights Reserved.
|
||||
*/
|
||||
|
||||
#ifndef _PREVIEW_RESOLUTION_H_
|
||||
#define _PREVIEW_RESOLUTION_H_
|
||||
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <cmath>
|
||||
|
||||
namespace crossdesk::gallery {
|
||||
struct PreviewResolution {
|
||||
int width = 0;
|
||||
int height = 0;
|
||||
|
||||
bool operator==(const PreviewResolution& other) const {
|
||||
return width == other.width && height == other.height;
|
||||
}
|
||||
static PreviewResolution Viewport(float width, float height, float scale) {
|
||||
if (!std::isfinite(width) || !std::isfinite(height) ||
|
||||
!std::isfinite(scale) || width <= 0 || height <= 0 || scale <= 0)
|
||||
return {};
|
||||
return {int(std::clamp(std::ceil(double(width) * scale), 2.0, 16384.0)),
|
||||
int(std::clamp(std::ceil(double(height) * scale), 2.0, 16384.0))};
|
||||
}
|
||||
};
|
||||
|
||||
class PreviewResolutionDebouncer {
|
||||
public:
|
||||
using Clock = std::chrono::steady_clock;
|
||||
PreviewResolution Update(PreviewResolution desired, Clock::time_point now,
|
||||
bool reset = false) {
|
||||
if (!initialized_ || reset) {
|
||||
initialized_ = true;
|
||||
applied_ = candidate_ = desired;
|
||||
changed_at_ = now;
|
||||
} else if (!(candidate_ == desired)) {
|
||||
candidate_ = desired;
|
||||
changed_at_ = now;
|
||||
} else if (now - changed_at_ >= std::chrono::milliseconds(200)) {
|
||||
applied_ = candidate_;
|
||||
}
|
||||
return applied_;
|
||||
}
|
||||
|
||||
private:
|
||||
bool initialized_ = false;
|
||||
PreviewResolution applied_, candidate_;
|
||||
Clock::time_point changed_at_{};
|
||||
};
|
||||
} // namespace crossdesk::gallery
|
||||
#endif
|
||||
Reference in New Issue
Block a user