mirror of
https://github.com/kunkundi/crossdesk.git
synced 2026-10-15 00:09:39 +08:00
[refactor] use MiniRTC APIs for DeskHub media pause controls
This commit is contained in:
@@ -51,8 +51,7 @@ SessionDeviceManager::SessionDeviceManager(GuiRuntime &owner)
|
||||
frame.captured_timestamp = GetSystemTimeMicros(owner_.peer_);
|
||||
std::shared_lock lock(owner_.connection_status_mutex_);
|
||||
for (const auto& [id, status] : owner_.connection_status_) {
|
||||
if (status == ConnectionStatus::Connected &&
|
||||
owner_.preview_subscriptions_.ShouldSendAudio(id))
|
||||
if (status == ConnectionStatus::Connected)
|
||||
SendAudioFrameToPeer(owner_.peer_, &frame,
|
||||
owner_.audio_label_.c_str(), id.data(),
|
||||
id.size());
|
||||
|
||||
@@ -165,11 +165,11 @@ void PeerEventHandler::OnReceiveDataBuffer(
|
||||
return;
|
||||
const auto subscription = PreviewSubscription::Parse(preview_json);
|
||||
if (!subscription) return;
|
||||
const auto previous = runtime->preview_subscriptions_.Get(remote_id);
|
||||
bool accepted = true;
|
||||
// Apply encoder quality to this controller only; other controllers retain
|
||||
// their own demand. Frame gating happens before encoding and transmission.
|
||||
if (runtime->preview_subscriptions_.IsNewer(remote_id,
|
||||
subscription->request_id)) {
|
||||
// their own demand. MiniRTC owns per-peer video/audio pause state.
|
||||
if (!previous || subscription->IsNewerThan(*previous)) {
|
||||
if (subscription->fps > 0)
|
||||
accepted = UpdateVideoSettings(
|
||||
runtime->peer_, remote_id.data(), remote_id.size(),
|
||||
@@ -177,13 +177,22 @@ void PeerEventHandler::OnReceiveDataBuffer(
|
||||
subscription->frame_rate,
|
||||
static_cast<VideoDegradationPreference>(
|
||||
subscription->EncoderPreference())) == 0;
|
||||
if (accepted)
|
||||
// Audio-only updates must not force a keyframe. The pause API requests
|
||||
// its own keyframes on resume.
|
||||
if (accepted && (!previous || previous->width != subscription->width ||
|
||||
previous->height != subscription->height))
|
||||
accepted = UpdateVideoResolution(
|
||||
runtime->peer_, remote_id.data(), remote_id.size(),
|
||||
subscription->width, subscription->height) == 0;
|
||||
if (accepted)
|
||||
accepted = SetVideoSendPaused(runtime->peer_, remote_id.data(),
|
||||
remote_id.size(), subscription->fps == 0) == 0;
|
||||
if (accepted)
|
||||
accepted = SetAudioSendPaused(runtime->peer_, remote_id.data(),
|
||||
remote_id.size(), !subscription->audio) == 0;
|
||||
if (accepted && subscription->audio)
|
||||
runtime->devices_.StartSpeakerCapturer();
|
||||
}
|
||||
// The resolution API refreshes only this controller's keyframes, including
|
||||
// resume requests with unchanged dimensions. Do not disturb other viewers.
|
||||
if (accepted) runtime->preview_subscriptions_.Set(remote_id, *subscription);
|
||||
const auto reply = nlohmann::json{
|
||||
{"type", "deskhub_preview_status"},
|
||||
@@ -527,9 +536,16 @@ void PeerEventHandler::OnReceiveDataBuffer(
|
||||
runtime->devices_.SendMouseCommand(remote_action,
|
||||
runtime->selected_display_);
|
||||
} else if (remote_action.type == ControlType::audio_capture) {
|
||||
// A negotiated subscription remains authoritative if a legacy capture
|
||||
// command arrives after it (e.g. when unmuting an inactive window).
|
||||
const auto subscription = runtime->preview_subscriptions_.Get(remote_id);
|
||||
const bool paused = subscription ? !subscription->audio : !remote_action.a;
|
||||
const int ret = SetAudioSendPaused(runtime->peer_, remote_id.data(),
|
||||
remote_id.size(), paused);
|
||||
if (remote_action.a)
|
||||
runtime->devices_.StartSpeakerCapturer();
|
||||
else
|
||||
else if (ret != 0)
|
||||
// Legacy/web transports without the per-peer API retain their fallback.
|
||||
runtime->devices_.StopSpeakerCapturer();
|
||||
} else if (remote_action.type == ControlType::keyboard) {
|
||||
runtime->keyboard_.ApplyRemoteEvent(remote_id, remote_action);
|
||||
|
||||
@@ -20,23 +20,21 @@ namespace crossdesk {
|
||||
class PreviewSubscriptions {
|
||||
public:
|
||||
using Clock = std::chrono::steady_clock;
|
||||
bool IsNewer(const std::string &id, uint32_t request_id) {
|
||||
std::optional<PreviewSubscription> Get(const std::string &id) {
|
||||
std::lock_guard lock(mutex_);
|
||||
const auto found = states_.find(id);
|
||||
return found == states_.end() ||
|
||||
static_cast<int32_t>(request_id -
|
||||
found->second.request.request_id) > 0;
|
||||
return found == states_.end() ? std::nullopt
|
||||
: std::optional(found->second.request);
|
||||
}
|
||||
bool Set(const std::string &id, const PreviewSubscription &request) {
|
||||
std::lock_guard lock(mutex_);
|
||||
const auto found = states_.find(id);
|
||||
if (found != states_.end() &&
|
||||
static_cast<int32_t>(request.request_id -
|
||||
found->second.request.request_id) <= 0)
|
||||
!request.IsNewerThan(found->second.request))
|
||||
return false;
|
||||
auto &state = states_[id];
|
||||
if (state.request.fps != request.fps) state.next.clear();
|
||||
state.request = request;
|
||||
state.next.clear();
|
||||
return true;
|
||||
}
|
||||
bool ShouldSend(const std::string &id, Clock::time_point now = Clock::now(),
|
||||
@@ -46,8 +44,10 @@ public:
|
||||
if (found == states_.end())
|
||||
return true;
|
||||
auto &state = found->second;
|
||||
// MiniRTC owns pause/resume. This gate only limits preview cadence, so
|
||||
// paused input reaches its cheap send gate without dividing by zero here.
|
||||
if (!state.request.fps)
|
||||
return false;
|
||||
return true;
|
||||
// Interactive streams use the encoder's own 30/60 FPS cadence. A second
|
||||
// time gate here can drop valid frames because capture timing varies.
|
||||
if (state.request.fps == 60)
|
||||
@@ -67,11 +67,6 @@ public:
|
||||
next = now + std::chrono::milliseconds(1000 / state.request.fps);
|
||||
return true;
|
||||
}
|
||||
bool ShouldSendAudio(const std::string &id) {
|
||||
std::lock_guard lock(mutex_);
|
||||
const auto found = states_.find(id);
|
||||
return found == states_.end() || found->second.request.audio;
|
||||
}
|
||||
void Remove(const std::string &id) {
|
||||
std::lock_guard lock(mutex_);
|
||||
states_.erase(id);
|
||||
|
||||
Vendored
+1
-1
Submodule deps/submodules/minirtc updated: 851eb2ed1f...ac48aa4b70
@@ -45,6 +45,10 @@
|
||||
|
||||
MiniRTC 新增 `UpdateVideoResolution(peer, remote_id, remote_id_size, width, height)` 接口,返回 0 表示已接受,-1 表示参数、连接或传输类型不支持。它为指定控制端的所有显示流设置独立尺寸上限,切换时丢弃旧设置尚未发出的编码结果,并请求新关键帧;底层自适应也以这个上限作为有效源尺寸。
|
||||
|
||||
`SetVideoSendPaused(peer, remote_id, remote_id_size, paused)` 与 `SetAudioSendPaused(peer, remote_id, remote_id_size, paused)` 分别控制指定连接的所有视频/音频发送通道,`true` 暂停、`false` 恢复。重复设置不产生额外操作;返回 0 表示接受,-1 表示参数、连接或传输类型不支持。视频暂停阻止新帧进入编码,废弃旧编码结果,并在发送队列上清除待发视频/重传/纠错包,停止探测和填充;恢复请求新关键帧。音频暂停阻止 PCM 编码并清理待发音频,恢复时仅发送新音频。已交给网络的包无法撤回。API 保留连接、采集源、码率与画质设置以及其他媒体/数据通道,也不改变其他控制端的发送状态。
|
||||
|
||||
桌面墙将 `fps=0` 和 `audio=false` 分别映射到上述 API;应用层仅保留低帧率节流,静音在 MiniRTC 内部执行。接收端仍保留静音门控及播放队列清理,以立即停止播放网络中已在途的音频。旧版 `audio_capture` 命令在支持新 API 的传输中也按连接静音,已有订阅的音频状态优先;不支持 API 的旧/Web 传输沿用采集开关回退。
|
||||
|
||||
## 批量操作
|
||||
|
||||
连接和文件分发最多并行启动 4 个操作。尚未开始连接的设备卡片显示“排队中”,轮到后自动切换为实际连接状态。断开立即执行,同时取消该设备排队中的操作及重连请求,不等待文件传输完成。单台等待密码不会阻塞其他设备,可以从对应设备的放大视图填写密码。连接状态显示在设备卡片上,文件传输详情可在该设备的独立串流窗口中查看。
|
||||
|
||||
@@ -36,6 +36,10 @@ struct PreviewSubscription {
|
||||
return {0, 60, quality, frame_rate, preference, audio};
|
||||
}
|
||||
|
||||
bool IsNewerThan(const PreviewSubscription &other) const {
|
||||
return static_cast<int32_t>(request_id - other.request_id) > 0;
|
||||
}
|
||||
|
||||
bool SameSettings(const PreviewSubscription &other) const {
|
||||
return fps == other.fps && quality == other.quality &&
|
||||
frame_rate == other.frame_rate &&
|
||||
|
||||
Reference in New Issue
Block a user