Feature(web): Add toolbar audio toggle button

This commit is contained in:
yuanyuanxiang
2026-06-02 20:23:07 +02:00
parent 9aca587654
commit 498c7d15b3
9 changed files with 166 additions and 2 deletions

View File

@@ -9651,6 +9651,25 @@ void CMy2015RemoteDlg::CloseRemoteDesktopByClientID(uint64_t clientID)
}
}
bool CMy2015RemoteDlg::PostWebAudioToggle(uint64_t clientID)
{
HWND hWnd = NULL;
EnterCriticalSection(&m_cs);
for (auto& pair : m_RemoteWnds) {
CScreenSpyDlg* dlg = dynamic_cast<CScreenSpyDlg*>(pair.second);
if (dlg && dlg->GetClientID() == clientID && dlg->IsWebSession()) {
hWnd = dlg->GetSafeHwnd();
break;
}
}
LeaveCriticalSection(&m_cs);
if (hWnd && ::IsWindow(hWnd)) {
// PostMessage 把活儿丢到对话框的 UI 线程,避免 WS 线程动 waveOut 句柄
return ::PostMessage(hWnd, WM_AUDIO_TOGGLE_FROM_WEB, 0, 0) != 0;
}
return false;
}
void CMy2015RemoteDlg::CloseWebRemoteDesktopByClientID(uint64_t clientID)
{
CScreenSpyDlg* targetDlg = nullptr;

View File

@@ -373,6 +373,7 @@ public:
void RemoveRemoteWindow(HWND wnd);
void CloseRemoteDesktopByClientID(uint64_t clientID);
void CloseWebRemoteDesktopByClientID(uint64_t clientID); // Only close Web session dialog
bool PostWebAudioToggle(uint64_t clientID); // 给 Web 会话 ScreenSpy 投递音频开关消息
CDialogBase* m_pActiveSession = nullptr; // 当前活动会话窗口指针 / NULL 表示无
void UpdateActiveRemoteSession(CDialogBase* sess);
CDialogBase* GetActiveRemoteSession();

View File

@@ -224,6 +224,8 @@ CScreenSpyDlg::CScreenSpyDlg(CMy2015RemoteDlg* Parent, Server* IOCPServer, CONTE
int width = m_BitmapInfor_Full->bmiHeader.biWidth;
int height = abs(m_BitmapInfor_Full->bmiHeader.biHeight);
WebService().NotifyResolutionChange(m_ClientID, width, height);
// 透传客户端初始的音频开/关状态给 web让前端按钮显示正确
WebService().NotifyAudioState(m_ClientID, m_Settings.AudioEnabled != 0);
}
}
@@ -568,6 +570,7 @@ BEGIN_MESSAGE_MAP(CScreenSpyDlg, CDialog)
ON_MESSAGE(MM_WOM_DONE, &CScreenSpyDlg::OnWaveOutDone)
ON_MESSAGE(WM_RECVFILEV2_CHUNK, &CScreenSpyDlg::OnRecvFileV2Chunk)
ON_MESSAGE(WM_RECVFILEV2_COMPLETE, &CScreenSpyDlg::OnRecvFileV2Complete)
ON_MESSAGE(WM_AUDIO_TOGGLE_FROM_WEB, &CScreenSpyDlg::OnAudioToggleFromWeb)
ON_WM_DROPFILES()
ON_WM_CAPTURECHANGED()
END_MESSAGE_MAP()
@@ -3521,6 +3524,11 @@ void CScreenSpyDlg::DisableAudio()
}
Mprintf("[Audio Web] 禁用音频(来自 web 命令)\n");
// 广播状态给所有正在观看本设备的 web 客户端
if (WebService().IsRunning()) {
WebService().NotifyAudioState(m_ClientID, false);
}
}
}
@@ -3539,9 +3547,24 @@ void CScreenSpyDlg::EnableAudio()
}
Mprintf("[Audio Web] 启用音频(来自 web 命令)\n");
if (WebService().IsRunning()) {
WebService().NotifyAudioState(m_ClientID, true);
}
}
}
// 由 PostMessage 从 WS 线程派发到 UI 线程;根据当前状态翻转
LRESULT CScreenSpyDlg::OnAudioToggleFromWeb(WPARAM /*wParam*/, LPARAM /*lParam*/)
{
if (m_Settings.AudioEnabled) {
DisableAudio();
} else {
EnableAudio();
}
return 0;
}
void CScreenSpyDlg::OnAudioData(BYTE* pData, UINT32 len)
{
if (len < 1) return;

View File

@@ -93,6 +93,9 @@ extern "C"
// 文件接收消息(用于将工作线程的文件数据转发到主线程处理)
#define WM_RECVFILEV2_CHUNK (WM_USER + 0x200)
#define WM_RECVFILEV2_COMPLETE (WM_USER + 0x201)
// 来自 web 命令的音频开关PostMessage 到对话框的 UI 线程,避免 WS 线程
// 直接动 waveOut 句柄
#define WM_AUDIO_TOGGLE_FROM_WEB (WM_USER + 0x202)
// ScreenSpyDlg 系统菜单命令 ID
enum {
@@ -363,6 +366,7 @@ public:
void StopAudioPlayback(); // 停止音频播放
void DisableAudio(); // 禁用音频(从网页命令)
void EnableAudio(); // 启用音频(从网页命令)
LRESULT OnAudioToggleFromWeb(WPARAM wParam, LPARAM lParam); // PostMessage 处理器
void SendAudioCtrl(BYTE enable, BYTE persist); // 发送音频控制命令
void FeedAudioBuffers(); // 填充音频缓冲区
void SendAudioToWeb(const BYTE* pAudioData, UINT32 len, const WAVEFORMATEX* pFormat, BYTE compression); // 发送音频到网页 (compression=AudioCompression)

View File

@@ -396,6 +396,8 @@ void CWebService::ServerThread(int port) {
HandleKey(ws_ptr, msg);
} else if (cmd == "rdp_reset") {
HandleRdpReset(ws_ptr, token);
} else if (cmd == "audio_toggle") {
HandleAudioToggle(ws_ptr, token);
} else if (cmd == "get_salt") {
HandleGetSalt(ws_ptr, msg);
} else if (cmd == "create_user") {
@@ -689,14 +691,16 @@ void CWebService::HandleConnect(void* ws_ptr, const std::string& token, uint64_t
}
}
// Get screen dimensions from device info cache (may not be available yet)
// Get screen dimensions + audio state from device info cache (may not be ready)
int width = 0, height = 0;
int audio_enabled = -1; // -1 = unknown yet (前端走 audio_state 事件兜底)
{
std::lock_guard<std::mutex> lock(m_DeviceCacheMutex);
auto it = m_DeviceCache.find(device_id);
if (it != m_DeviceCache.end()) {
width = it->second->screen_width;
height = it->second->screen_height;
audio_enabled = it->second->audio_enabled;
}
}
@@ -710,6 +714,9 @@ void CWebService::HandleConnect(void* ws_ptr, const std::string& token, uint64_t
res["width"] = width;
res["height"] = height;
}
if (audio_enabled >= 0) {
res["audio_enabled"] = (audio_enabled != 0);
}
res["algorithm"] = "h264";
Json::StreamWriterBuilder builder;
@@ -1002,6 +1009,33 @@ void CWebService::HandleRdpReset(void* ws_ptr, const std::string& token) {
}
}
void CWebService::HandleAudioToggle(void* ws_ptr, const std::string& token) {
std::string username, role;
if (!ValidateToken(token, username, role)) {
SendText(ws_ptr, BuildJsonResponse("audio_toggle_result", false, "Invalid token"));
return;
}
uint64_t device_id = 0;
{
std::lock_guard<std::mutex> lock(m_ClientsMutex);
auto it = m_Clients.find(ws_ptr);
if (it != m_Clients.end()) device_id = it->second.watch_device_id;
}
if (device_id == 0) {
SendText(ws_ptr, BuildJsonResponse("audio_toggle_result", false, "No device connected"));
return;
}
// 投递到 ScreenSpyDlg 的 UI 线程;那边会调用 Enable/DisableAudio 并通过
// NotifyAudioState 把新状态广播给所有 watching 的 web 客户端
if (!m_pParentDlg || !m_pParentDlg->PostWebAudioToggle(device_id)) {
SendText(ws_ptr, BuildJsonResponse("audio_toggle_result", false, "No active screen session"));
return;
}
SendText(ws_ptr, BuildJsonResponse("audio_toggle_result", true));
}
//////////////////////////////////////////////////////////////////////////
// User Management Handlers
//////////////////////////////////////////////////////////////////////////
@@ -1699,6 +1733,37 @@ void CWebService::NotifyResolutionChange(uint64_t device_id, int width, int heig
}
}
void CWebService::NotifyAudioState(uint64_t device_id, bool enabled) {
if (m_bStopping) return;
// 缓存最新状态,新加入的 web 客户端通过 connect_result 取到初值
{
std::lock_guard<std::mutex> lock(m_DeviceCacheMutex);
auto it = m_DeviceCache.find(device_id);
if (it == m_DeviceCache.end()) {
m_DeviceCache[device_id] = std::make_shared<WebDeviceInfo>();
it = m_DeviceCache.find(device_id);
}
it->second->audio_enabled = enabled ? 1 : 0;
}
Json::Value res;
res["cmd"] = "audio_state";
res["id"] = device_id;
res["enabled"] = enabled;
Json::StreamWriterBuilder builder;
builder["indentation"] = "";
std::string json = Json::writeString(builder, res);
std::lock_guard<std::mutex> lock(m_ClientsMutex);
for (auto& [ws_ptr, client] : m_Clients) {
if (client.watch_device_id == device_id) {
SendText(ws_ptr, json);
}
}
}
void CWebService::BroadcastCursor(uint64_t device_id, uint8_t cursor_index) {
if (m_bStopping) return;

View File

@@ -55,6 +55,8 @@ struct WebDeviceInfo {
int screen_width;
int screen_height;
bool online;
// 当前会话的音频开关。-1=未知(客户端 BITMAPINFO 还没回来0=关1=开
int audio_enabled = -1;
// Keyframe cache for new web clients
std::vector<uint8_t> keyframe_cache;
@@ -98,6 +100,10 @@ public:
// Resolution change notification
void NotifyResolutionChange(uint64_t device_id, int width, int height);
// Audio enable/disable notification — pushes current state to all web
// clients watching this device and caches it for newcomers.
void NotifyAudioState(uint64_t device_id, bool enabled);
// Cursor change notification (called from ScreenSpyDlg)
void BroadcastCursor(uint64_t device_id, uint8_t cursor_index);
@@ -129,6 +135,7 @@ private:
void HandleMouse(void* ws_ptr, const std::string& msg);
void HandleKey(void* ws_ptr, const std::string& msg);
void HandleRdpReset(void* ws_ptr, const std::string& token);
void HandleAudioToggle(void* ws_ptr, const std::string& token);
// Token management
std::string GenerateToken(const std::string& username, const std::string& role);