From 2c1595d0d517fd3e377a12fd81f4ccee31f14f4f Mon Sep 17 00:00:00 2001 From: fufesou Date: Tue, 7 May 2024 16:18:48 +0800 Subject: [PATCH] fix: voice call, select audio input device (#7922) Signed-off-by: fufesou --- flutter/assets/voice_call.svg | 2 +- flutter/lib/common/widgets/audio_input.dart | 56 +++++++++ .../desktop/pages/desktop_setting_page.dart | 34 +----- flutter/lib/desktop/pages/server_page.dart | 111 +++++++++++++++--- .../lib/desktop/widgets/remote_toolbar.dart | 105 ++++++++++++----- src/ipc.rs | 5 + src/lang/ar.rs | 1 + src/lang/bg.rs | 1 + src/lang/ca.rs | 1 + src/lang/cn.rs | 1 + src/lang/cs.rs | 1 + src/lang/da.rs | 1 + src/lang/de.rs | 1 + src/lang/el.rs | 1 + src/lang/en.rs | 1 + src/lang/eo.rs | 1 + src/lang/es.rs | 1 + src/lang/et.rs | 1 + src/lang/fa.rs | 1 + src/lang/fr.rs | 1 + src/lang/he.rs | 1 + src/lang/hr.rs | 1 + src/lang/hu.rs | 1 + src/lang/id.rs | 1 + src/lang/it.rs | 1 + src/lang/ja.rs | 1 + src/lang/ko.rs | 1 + src/lang/kz.rs | 1 + src/lang/lt.rs | 1 + src/lang/lv.rs | 1 + src/lang/nb.rs | 1 + src/lang/nl.rs | 1 + src/lang/pl.rs | 1 + src/lang/pt_PT.rs | 1 + src/lang/ptbr.rs | 1 + src/lang/ro.rs | 1 + src/lang/ru.rs | 1 + src/lang/sk.rs | 1 + src/lang/sl.rs | 1 + src/lang/sq.rs | 1 + src/lang/sr.rs | 1 + src/lang/sv.rs | 1 + src/lang/template.rs | 1 + src/lang/th.rs | 1 + src/lang/tr.rs | 1 + src/lang/tw.rs | 1 + src/lang/ua.rs | 1 + src/lang/vn.rs | 1 + src/server/audio_service.rs | 27 ++++- src/ui_interface.rs | 2 + 50 files changed, 305 insertions(+), 79 deletions(-) create mode 100644 flutter/lib/common/widgets/audio_input.dart diff --git a/flutter/assets/voice_call.svg b/flutter/assets/voice_call.svg index bf90ec958..98ebd4bc8 100644 --- a/flutter/assets/voice_call.svg +++ b/flutter/assets/voice_call.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/flutter/lib/common/widgets/audio_input.dart b/flutter/lib/common/widgets/audio_input.dart new file mode 100644 index 000000000..36a0e4972 --- /dev/null +++ b/flutter/lib/common/widgets/audio_input.dart @@ -0,0 +1,56 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_hbb/common.dart'; +import 'package:flutter_hbb/models/platform_model.dart'; + +typedef AudioINputSetDevice = void Function(String device); +typedef AudioInputBuilder = Widget Function( + List devices, String currentDevice, AudioINputSetDevice setDevice); + +class AudioInput extends StatelessWidget { + final AudioInputBuilder builder; + + const AudioInput({Key? key, required this.builder}) : super(key: key); + + static String getDefault() { + if (isWindows) return translate('System Sound'); + return ''; + } + + static Future getValue() async { + String device = await bind.mainGetOption(key: 'audio-input'); + if (device.isNotEmpty) { + return device; + } else { + return getDefault(); + } + } + + static Future setDevice(String device) async { + if (device == getDefault()) device = ''; + await bind.mainSetOption(key: 'audio-input', value: device); + } + + static Future> getDevicesInfo() async { + List devices = (await bind.mainGetSoundInputs()).toList(); + if (isWindows) { + devices.insert(0, translate('System Sound')); + } + String current = await getValue(); + return {'devices': devices, 'current': current}; + } + + @override + Widget build(BuildContext context) { + return futureBuilder( + future: getDevicesInfo(), + hasData: (data) { + String currentDevice = data['current']; + List devices = data['devices'] as List; + if (devices.isEmpty) { + return const Offstage(); + } + return builder(devices, currentDevice, setDevice); + }, + ); + } +} diff --git a/flutter/lib/desktop/pages/desktop_setting_page.dart b/flutter/lib/desktop/pages/desktop_setting_page.dart index c5f437263..f65a6e267 100644 --- a/flutter/lib/desktop/pages/desktop_setting_page.dart +++ b/flutter/lib/desktop/pages/desktop_setting_page.dart @@ -6,6 +6,7 @@ import 'package:file_picker/file_picker.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_hbb/common.dart'; +import 'package:flutter_hbb/common/widgets/audio_input.dart'; import 'package:flutter_hbb/common/widgets/setting_widgets.dart'; import 'package:flutter_hbb/consts.dart'; import 'package:flutter_hbb/desktop/pages/desktop_home_page.dart'; @@ -469,38 +470,7 @@ class _GeneralState extends State<_General> { return const Offstage(); } - String getDefault() { - if (isWindows) return translate('System Sound'); - return ''; - } - - Future getValue() async { - String device = await bind.mainGetOption(key: 'audio-input'); - if (device.isNotEmpty) { - return device; - } else { - return getDefault(); - } - } - - setDevice(String device) { - if (device == getDefault()) device = ''; - bind.mainSetOption(key: 'audio-input', value: device); - } - - return futureBuilder(future: () async { - List devices = (await bind.mainGetSoundInputs()).toList(); - if (isWindows) { - devices.insert(0, translate('System Sound')); - } - String current = await getValue(); - return {'devices': devices, 'current': current}; - }(), hasData: (data) { - String currentDevice = data['current']; - List devices = data['devices'] as List; - if (devices.isEmpty) { - return const Offstage(); - } + return AudioInput(builder: (devices, currentDevice, setDevice) { return _Card(title: 'Audio Input Device', children: [ ...devices.map((device) => _Radio(context, value: device, diff --git a/flutter/lib/desktop/pages/server_page.dart b/flutter/lib/desktop/pages/server_page.dart index 961a42477..6d5466add 100644 --- a/flutter/lib/desktop/pages/server_page.dart +++ b/flutter/lib/desktop/pages/server_page.dart @@ -4,6 +4,7 @@ import 'dart:async'; import 'dart:math'; import 'package:flutter/material.dart'; +import 'package:flutter_hbb/common/widgets/audio_input.dart'; import 'package:flutter_hbb/consts.dart'; import 'package:flutter_hbb/desktop/widgets/tabbar_widget.dart'; import 'package:flutter_hbb/models/chat_model.dart'; @@ -701,17 +702,86 @@ class _CmControlPanel extends StatelessWidget { children: [ Offstage( offstage: !client.inVoiceCall, - child: buildButton( - context, - color: Colors.red, - onClick: () => closeVoiceCall(), - icon: Icon( - Icons.call_end_rounded, - color: Colors.white, - size: 14, - ), - text: "Stop voice call", - textColor: Colors.white, + child: Row( + children: [ + Expanded( + child: buildButton(context, + color: MyTheme.accent, + onClick: null, onTapDown: (details) async { + final devicesInfo = await AudioInput.getDevicesInfo(); + List devices = devicesInfo['devices'] as List; + if (devices.isEmpty) { + msgBox( + gFFI.sessionId, + 'custom-nocancel-info', + 'Prompt', + 'no_audio_input_device_tip', + '', + gFFI.dialogManager, + ); + return; + } + + String currentDevice = devicesInfo['current'] as String; + final x = details.globalPosition.dx; + final y = details.globalPosition.dy; + final position = RelativeRect.fromLTRB(x, y, x, y); + showMenu( + context: context, + position: position, + items: devices + .map((d) => PopupMenuItem( + value: d, + height: 18, + padding: EdgeInsets.zero, + onTap: () => AudioInput.setDevice(d), + child: IgnorePointer( + child: RadioMenuButton( + value: d, + groupValue: currentDevice, + onChanged: (v) { + if (v != null) AudioInput.setDevice(v); + }, + child: Container( + child: Text( + d, + overflow: TextOverflow.ellipsis, + maxLines: 1, + ), + constraints: BoxConstraints( + maxWidth: + kConnectionManagerWindowSizeClosedChat + .width - + 80), + ), + )), + )) + .toList(), + ); + }, + icon: Icon( + Icons.call_rounded, + color: Colors.white, + size: 14, + ), + text: "Audio input", + textColor: Colors.white), + ), + Expanded( + child: buildButton( + context, + color: Colors.red, + onClick: () => closeVoiceCall(), + icon: Icon( + Icons.call_end_rounded, + color: Colors.white, + size: 14, + ), + text: "Stop voice call", + textColor: Colors.white, + ), + ) + ], ), ), Offstage( @@ -872,12 +942,14 @@ class _CmControlPanel extends StatelessWidget { Widget buildButton(BuildContext context, {required Color? color, - required Function() onClick, - Icon? icon, + GestureTapCallback? onClick, + Widget? icon, BoxBorder? border, required String text, required Color? textColor, - String? tooltip}) { + String? tooltip, + GestureTapDownCallback? onTapDown}) { + assert(!(onClick == null && onTapDown == null)); Widget textWidget; if (icon != null) { textWidget = Text( @@ -901,7 +973,16 @@ class _CmControlPanel extends StatelessWidget { color: color, borderRadius: borderRadius, border: border), child: InkWell( borderRadius: borderRadius, - onTap: () => checkClickTime(client.id, onClick), + onTap: () { + if (onClick == null) return; + checkClickTime(client.id, onClick); + }, + onTapDown: (details) { + if (onTapDown == null) return; + checkClickTime(client.id, () { + onTapDown.call(details); + }); + }, child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ diff --git a/flutter/lib/desktop/widgets/remote_toolbar.dart b/flutter/lib/desktop/widgets/remote_toolbar.dart index aa1b346fa..4ef62e493 100644 --- a/flutter/lib/desktop/widgets/remote_toolbar.dart +++ b/flutter/lib/desktop/widgets/remote_toolbar.dart @@ -3,6 +3,7 @@ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; +import 'package:flutter_hbb/common/widgets/audio_input.dart'; import 'package:flutter_hbb/common/widgets/toolbar.dart'; import 'package:flutter_hbb/models/chat_model.dart'; import 'package:flutter_hbb/models/state_model.dart'; @@ -1953,34 +1954,71 @@ class _VoiceCallMenu extends StatelessWidget { @override Widget build(BuildContext context) { + menuChildrenGetter() { + final audioInput = + AudioInput(builder: (devices, currentDevice, setDevice) { + return Column( + children: devices + .map((d) => RdoMenuButton( + child: Container( + child: Text( + d, + overflow: TextOverflow.ellipsis, + ), + constraints: BoxConstraints(maxWidth: 250), + ), + value: d, + groupValue: currentDevice, + onChanged: (v) { + if (v != null) setDevice(v); + }, + ffi: ffi, + )) + .toList(), + ); + }); + return [ + audioInput, + Divider(), + MenuButton( + child: Text(translate('End call')), + onPressed: () => bind.sessionCloseVoiceCall(sessionId: ffi.sessionId), + ffi: ffi, + ), + ]; + } + return Obx( () { - final String tooltip; - final String icon; switch (ffi.chatModel.voiceCallStatus.value) { case VoiceCallStatus.waitingForResponse: - tooltip = "Waiting"; - icon = "assets/call_wait.svg"; - break; + return buildCallWaiting(context); case VoiceCallStatus.connected: - tooltip = "Disconnect"; - icon = "assets/call_end.svg"; - break; + return _IconSubmenuButton( + tooltip: 'Voice call', + svg: 'assets/voice_call.svg', + color: _ToolbarTheme.blueColor, + hoverColor: _ToolbarTheme.hoverBlueColor, + menuChildrenGetter: menuChildrenGetter, + ffi: ffi, + ); default: return Offstage(); } - return _IconMenuButton( - assetName: icon, - tooltip: tooltip, - onPressed: () => - bind.sessionCloseVoiceCall(sessionId: ffi.sessionId), - color: _ToolbarTheme.redColor, - hoverColor: _ToolbarTheme.hoverRedColor); }, ); } -} + Widget buildCallWaiting(BuildContext context) { + return _IconMenuButton( + assetName: "assets/call_wait.svg", + tooltip: "Waiting", + onPressed: () => bind.sessionCloseVoiceCall(sessionId: ffi.sessionId), + color: _ToolbarTheme.redColor, + hoverColor: _ToolbarTheme.hoverRedColor, + ); + } +} class _RecordMenu extends StatelessWidget { const _RecordMenu({Key? key}) : super(key: key); @@ -2115,7 +2153,7 @@ class _IconSubmenuButton extends StatefulWidget { final Color hoverColor; final List Function() menuChildrenGetter; final MenuStyle? menuStyle; - final FFI ffi; + final FFI? ffi; final double? width; _IconSubmenuButton({ @@ -2126,7 +2164,7 @@ class _IconSubmenuButton extends StatefulWidget { required this.color, required this.hoverColor, required this.menuChildrenGetter, - required this.ffi, + this.ffi, this.menuStyle, this.width, }) : super(key: key); @@ -2208,13 +2246,13 @@ class MenuButton extends StatelessWidget { final VoidCallback? onPressed; final Widget? trailingIcon; final Widget? child; - final FFI ffi; + final FFI? ffi; MenuButton( {Key? key, this.onPressed, this.trailingIcon, required this.child, - required this.ffi}) + this.ffi}) : super(key: key); @override @@ -2223,7 +2261,9 @@ class MenuButton extends StatelessWidget { key: key, onPressed: onPressed != null ? () { - _menuDismissCallback(ffi); + if (ffi != null) { + _menuDismissCallback(ffi!); + } onPressed?.call(); } : null, @@ -2236,13 +2276,13 @@ class CkbMenuButton extends StatelessWidget { final bool? value; final ValueChanged? onChanged; final Widget? child; - final FFI ffi; + final FFI? ffi; const CkbMenuButton( {Key? key, required this.value, required this.onChanged, required this.child, - required this.ffi}) + this.ffi}) : super(key: key); @override @@ -2253,7 +2293,9 @@ class CkbMenuButton extends StatelessWidget { child: child, onChanged: onChanged != null ? (bool? value) { - _menuDismissCallback(ffi); + if (ffi != null) { + _menuDismissCallback(ffi!); + } onChanged?.call(value); } : null, @@ -2266,13 +2308,13 @@ class RdoMenuButton extends StatelessWidget { final T? groupValue; final ValueChanged? onChanged; final Widget? child; - final FFI ffi; + final FFI? ffi; const RdoMenuButton({ Key? key, required this.value, required this.groupValue, required this.child, - required this.ffi, + this.ffi, this.onChanged, }) : super(key: key); @@ -2284,7 +2326,9 @@ class RdoMenuButton extends StatelessWidget { child: child, onChanged: onChanged != null ? (T? value) { - _menuDismissCallback(ffi); + if (ffi != null) { + _menuDismissCallback(ffi!); + } onChanged?.call(value); } : null, @@ -2471,10 +2515,11 @@ class InputModeMenu { _menuDismissCallback(FFI ffi) => ffi.inputModel.refreshMousePos(); -Widget _buildPointerTrackWidget(Widget child, FFI ffi) { +Widget _buildPointerTrackWidget(Widget child, FFI? ffi) { return Listener( - onPointerHover: (PointerHoverEvent e) => - ffi.inputModel.lastMousePos = e.position, + onPointerHover: (PointerHoverEvent e) => { + if (ffi != null) {ffi.inputModel.lastMousePos = e.position} + }, child: MouseRegion( child: child, ), diff --git a/src/ipc.rs b/src/ipc.rs index 7957b8db6..1118f7ff8 100644 --- a/src/ipc.rs +++ b/src/ipc.rs @@ -454,7 +454,12 @@ async fn handle(data: Data, stream: &mut Connection) { if let Some(v) = value.get("privacy-mode-impl-key") { crate::privacy_mode::switch(v); } + let pre_opts = Config::get_options(); + let new_audio_input = pre_opts.get("audio-input"); Config::set_options(value); + if new_audio_input != pre_opts.get("audio-input") { + crate::audio_service::restart(); + } allow_err!(stream.send(&Data::Options(None)).await); } }, diff --git a/src/lang/ar.rs b/src/lang/ar.rs index 649b9f8eb..8eaa1c837 100644 --- a/src/lang/ar.rs +++ b/src/lang/ar.rs @@ -606,5 +606,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Follow remote cursor", ""), ("Follow remote window focus", ""), ("default_proxy_tip", ""), + ("no_audio_input_device_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/bg.rs b/src/lang/bg.rs index 99fda4fd8..e85efb711 100644 --- a/src/lang/bg.rs +++ b/src/lang/bg.rs @@ -606,5 +606,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Follow remote cursor", ""), ("Follow remote window focus", ""), ("default_proxy_tip", ""), + ("no_audio_input_device_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ca.rs b/src/lang/ca.rs index 77153f754..d23f79e29 100644 --- a/src/lang/ca.rs +++ b/src/lang/ca.rs @@ -606,5 +606,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Follow remote cursor", ""), ("Follow remote window focus", ""), ("default_proxy_tip", ""), + ("no_audio_input_device_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/cn.rs b/src/lang/cn.rs index a25115f0b..530522643 100644 --- a/src/lang/cn.rs +++ b/src/lang/cn.rs @@ -606,5 +606,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Follow remote cursor", "跟随远程光标"), ("Follow remote window focus", "跟随远程窗口焦点"), ("default_proxy_tip", "默认代理协议及端口为 Socks5 和 1080"), + ("no_audio_input_device_tip", "未找到音频输入设备"), ].iter().cloned().collect(); } diff --git a/src/lang/cs.rs b/src/lang/cs.rs index 0761d2564..f39b16b4d 100644 --- a/src/lang/cs.rs +++ b/src/lang/cs.rs @@ -606,5 +606,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Follow remote cursor", "Sledovat dálkový kurzor"), ("Follow remote window focus", "Sledovat zaměření vzdáleného okna"), ("default_proxy_tip", ""), + ("no_audio_input_device_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/da.rs b/src/lang/da.rs index ad5384143..2aac97595 100644 --- a/src/lang/da.rs +++ b/src/lang/da.rs @@ -606,5 +606,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Follow remote cursor", ""), ("Follow remote window focus", ""), ("default_proxy_tip", ""), + ("no_audio_input_device_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/de.rs b/src/lang/de.rs index 9eb93d26a..abdda6ff3 100644 --- a/src/lang/de.rs +++ b/src/lang/de.rs @@ -606,5 +606,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Follow remote cursor", "Dem entfernten Cursor folgen"), ("Follow remote window focus", "Dem Fokus des entfernten Fensters folgen"), ("default_proxy_tip", ""), + ("no_audio_input_device_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/el.rs b/src/lang/el.rs index 02a889bc3..61300e92f 100644 --- a/src/lang/el.rs +++ b/src/lang/el.rs @@ -606,5 +606,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Follow remote cursor", ""), ("Follow remote window focus", ""), ("default_proxy_tip", ""), + ("no_audio_input_device_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/en.rs b/src/lang/en.rs index bdd1d6206..4439d9b25 100644 --- a/src/lang/en.rs +++ b/src/lang/en.rs @@ -224,5 +224,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Follow remote cursor", ""), ("Follow remote window focus", ""), ("default_proxy_tip", "Default protocol and port are Socks5 and 1080"), + ("no_audio_input_device_tip", "No audio input device found."), ].iter().cloned().collect(); } diff --git a/src/lang/eo.rs b/src/lang/eo.rs index 4ed6a2658..30282bdbd 100644 --- a/src/lang/eo.rs +++ b/src/lang/eo.rs @@ -606,5 +606,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Follow remote cursor", ""), ("Follow remote window focus", ""), ("default_proxy_tip", ""), + ("no_audio_input_device_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/es.rs b/src/lang/es.rs index e4fa9984c..c51dc0c97 100644 --- a/src/lang/es.rs +++ b/src/lang/es.rs @@ -606,5 +606,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Follow remote cursor", "Seguir cursor remoto"), ("Follow remote window focus", "Seguir ventana remota activa"), ("default_proxy_tip", ""), + ("no_audio_input_device_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/et.rs b/src/lang/et.rs index 434a4ade9..09783fd90 100644 --- a/src/lang/et.rs +++ b/src/lang/et.rs @@ -606,5 +606,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Follow remote cursor", ""), ("Follow remote window focus", ""), ("default_proxy_tip", ""), + ("no_audio_input_device_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/fa.rs b/src/lang/fa.rs index 134494a47..27a27dd5b 100644 --- a/src/lang/fa.rs +++ b/src/lang/fa.rs @@ -606,5 +606,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Follow remote cursor", ""), ("Follow remote window focus", ""), ("default_proxy_tip", ""), + ("no_audio_input_device_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/fr.rs b/src/lang/fr.rs index 521d65012..8b118908b 100644 --- a/src/lang/fr.rs +++ b/src/lang/fr.rs @@ -606,5 +606,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Follow remote cursor", ""), ("Follow remote window focus", ""), ("default_proxy_tip", ""), + ("no_audio_input_device_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/he.rs b/src/lang/he.rs index 94cd44c45..f231ed5ac 100644 --- a/src/lang/he.rs +++ b/src/lang/he.rs @@ -606,5 +606,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Follow remote cursor", ""), ("Follow remote window focus", ""), ("default_proxy_tip", ""), + ("no_audio_input_device_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/hr.rs b/src/lang/hr.rs index 6b0f372d8..ecd5ab52a 100644 --- a/src/lang/hr.rs +++ b/src/lang/hr.rs @@ -606,5 +606,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Follow remote cursor", ""), ("Follow remote window focus", ""), ("default_proxy_tip", ""), + ("no_audio_input_device_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/hu.rs b/src/lang/hu.rs index 5ddb6e335..b76c4f34d 100644 --- a/src/lang/hu.rs +++ b/src/lang/hu.rs @@ -606,5 +606,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Follow remote cursor", ""), ("Follow remote window focus", ""), ("default_proxy_tip", ""), + ("no_audio_input_device_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/id.rs b/src/lang/id.rs index 389ec8677..874e64547 100644 --- a/src/lang/id.rs +++ b/src/lang/id.rs @@ -606,5 +606,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Follow remote cursor", ""), ("Follow remote window focus", ""), ("default_proxy_tip", ""), + ("no_audio_input_device_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/it.rs b/src/lang/it.rs index 03b4462d2..3ee0df552 100644 --- a/src/lang/it.rs +++ b/src/lang/it.rs @@ -606,5 +606,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Follow remote cursor", "Segui cursore remoto"), ("Follow remote window focus", "Segui focus finestra remota"), ("default_proxy_tip", ""), + ("no_audio_input_device_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ja.rs b/src/lang/ja.rs index 7df3fc665..ecdeaca6f 100644 --- a/src/lang/ja.rs +++ b/src/lang/ja.rs @@ -606,5 +606,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Follow remote cursor", ""), ("Follow remote window focus", ""), ("default_proxy_tip", ""), + ("no_audio_input_device_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ko.rs b/src/lang/ko.rs index 7cc226b56..43e8d5983 100644 --- a/src/lang/ko.rs +++ b/src/lang/ko.rs @@ -606,5 +606,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Follow remote cursor", ""), ("Follow remote window focus", ""), ("default_proxy_tip", ""), + ("no_audio_input_device_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/kz.rs b/src/lang/kz.rs index 7ac4fa836..f2c7874b1 100644 --- a/src/lang/kz.rs +++ b/src/lang/kz.rs @@ -606,5 +606,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Follow remote cursor", ""), ("Follow remote window focus", ""), ("default_proxy_tip", ""), + ("no_audio_input_device_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/lt.rs b/src/lang/lt.rs index 9218cbbd6..87895873a 100644 --- a/src/lang/lt.rs +++ b/src/lang/lt.rs @@ -606,5 +606,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Follow remote cursor", ""), ("Follow remote window focus", ""), ("default_proxy_tip", ""), + ("no_audio_input_device_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/lv.rs b/src/lang/lv.rs index 4bf006e4f..fccac8de1 100644 --- a/src/lang/lv.rs +++ b/src/lang/lv.rs @@ -606,5 +606,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Follow remote cursor", "Sekot attālajam kursoram"), ("Follow remote window focus", "Sekot attālā loga fokusam"), ("default_proxy_tip", ""), + ("no_audio_input_device_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/nb.rs b/src/lang/nb.rs index dc12a41d6..fa79d639a 100644 --- a/src/lang/nb.rs +++ b/src/lang/nb.rs @@ -606,5 +606,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Follow remote cursor", ""), ("Follow remote window focus", ""), ("default_proxy_tip", ""), + ("no_audio_input_device_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/nl.rs b/src/lang/nl.rs index 86d9c1479..c5f9c58f7 100644 --- a/src/lang/nl.rs +++ b/src/lang/nl.rs @@ -606,5 +606,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Follow remote cursor", "Volg de cursor op afstand"), ("Follow remote window focus", "Volg de focus van het venster op afstand"), ("default_proxy_tip", ""), + ("no_audio_input_device_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/pl.rs b/src/lang/pl.rs index b27f7ce08..ca0b208dd 100644 --- a/src/lang/pl.rs +++ b/src/lang/pl.rs @@ -606,5 +606,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Follow remote cursor", "Podążaj za zdalnym kursorem"), ("Follow remote window focus", "Podążaj za aktywnością zdalnych okien"), ("default_proxy_tip", ""), + ("no_audio_input_device_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/pt_PT.rs b/src/lang/pt_PT.rs index 21786001f..e1a385213 100644 --- a/src/lang/pt_PT.rs +++ b/src/lang/pt_PT.rs @@ -606,5 +606,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Follow remote cursor", ""), ("Follow remote window focus", ""), ("default_proxy_tip", ""), + ("no_audio_input_device_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ptbr.rs b/src/lang/ptbr.rs index 18e13fefd..838f3f873 100644 --- a/src/lang/ptbr.rs +++ b/src/lang/ptbr.rs @@ -606,5 +606,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Follow remote cursor", ""), ("Follow remote window focus", ""), ("default_proxy_tip", ""), + ("no_audio_input_device_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ro.rs b/src/lang/ro.rs index 1296b1305..46df78d1c 100644 --- a/src/lang/ro.rs +++ b/src/lang/ro.rs @@ -606,5 +606,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Follow remote cursor", ""), ("Follow remote window focus", ""), ("default_proxy_tip", ""), + ("no_audio_input_device_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ru.rs b/src/lang/ru.rs index 015776710..3b7782209 100644 --- a/src/lang/ru.rs +++ b/src/lang/ru.rs @@ -606,5 +606,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Follow remote cursor", "Следовать за удалённым курсором"), ("Follow remote window focus", "Следовать за фокусом удалённого окна"), ("default_proxy_tip", ""), + ("no_audio_input_device_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/sk.rs b/src/lang/sk.rs index 9206ebdf5..2a67c459c 100644 --- a/src/lang/sk.rs +++ b/src/lang/sk.rs @@ -606,5 +606,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Follow remote cursor", "Nasledovať vzdialený kurzor"), ("Follow remote window focus", "Nasledovať vzdialené zameranie okna"), ("default_proxy_tip", ""), + ("no_audio_input_device_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/sl.rs b/src/lang/sl.rs index 381e42461..cbc9f0121 100755 --- a/src/lang/sl.rs +++ b/src/lang/sl.rs @@ -606,5 +606,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Follow remote cursor", ""), ("Follow remote window focus", ""), ("default_proxy_tip", ""), + ("no_audio_input_device_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/sq.rs b/src/lang/sq.rs index 4928d7fe6..213594e63 100644 --- a/src/lang/sq.rs +++ b/src/lang/sq.rs @@ -606,5 +606,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Follow remote cursor", ""), ("Follow remote window focus", ""), ("default_proxy_tip", ""), + ("no_audio_input_device_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/sr.rs b/src/lang/sr.rs index fe956ebfc..7937c0c46 100644 --- a/src/lang/sr.rs +++ b/src/lang/sr.rs @@ -606,5 +606,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Follow remote cursor", ""), ("Follow remote window focus", ""), ("default_proxy_tip", ""), + ("no_audio_input_device_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/sv.rs b/src/lang/sv.rs index c0e53aaa6..d607087a6 100644 --- a/src/lang/sv.rs +++ b/src/lang/sv.rs @@ -606,5 +606,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Follow remote cursor", ""), ("Follow remote window focus", ""), ("default_proxy_tip", ""), + ("no_audio_input_device_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/template.rs b/src/lang/template.rs index 8a791f3af..9eb0a147f 100644 --- a/src/lang/template.rs +++ b/src/lang/template.rs @@ -606,5 +606,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Follow remote cursor", ""), ("Follow remote window focus", ""), ("default_proxy_tip", ""), + ("no_audio_input_device_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/th.rs b/src/lang/th.rs index 40b77d102..1c256ffb7 100644 --- a/src/lang/th.rs +++ b/src/lang/th.rs @@ -606,5 +606,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Follow remote cursor", ""), ("Follow remote window focus", ""), ("default_proxy_tip", ""), + ("no_audio_input_device_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/tr.rs b/src/lang/tr.rs index 23c6638e4..e42aedfcc 100644 --- a/src/lang/tr.rs +++ b/src/lang/tr.rs @@ -606,5 +606,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Follow remote cursor", ""), ("Follow remote window focus", ""), ("default_proxy_tip", ""), + ("no_audio_input_device_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/tw.rs b/src/lang/tw.rs index 4a54ea2df..157a76187 100644 --- a/src/lang/tw.rs +++ b/src/lang/tw.rs @@ -606,5 +606,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Follow remote cursor", "跟隨遠端游標"), ("Follow remote window focus", "跟隨遠端視窗焦點"), ("default_proxy_tip", ""), + ("no_audio_input_device_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ua.rs b/src/lang/ua.rs index 0e63a333e..01aee813a 100644 --- a/src/lang/ua.rs +++ b/src/lang/ua.rs @@ -606,5 +606,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Follow remote cursor", ""), ("Follow remote window focus", ""), ("default_proxy_tip", ""), + ("no_audio_input_device_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/vn.rs b/src/lang/vn.rs index 5e5da1e52..103bf3dce 100644 --- a/src/lang/vn.rs +++ b/src/lang/vn.rs @@ -606,5 +606,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Follow remote cursor", ""), ("Follow remote window focus", ""), ("default_proxy_tip", ""), + ("no_audio_input_device_tip", ""), ].iter().cloned().collect(); } diff --git a/src/server/audio_service.rs b/src/server/audio_service.rs index 98aa7fa31..7b4feeb4f 100644 --- a/src/server/audio_service.rs +++ b/src/server/audio_service.rs @@ -103,6 +103,7 @@ mod pa_impl { #[cfg(not(any(target_os = "linux", target_os = "android")))] mod cpal_impl { + use self::service::{Reset, ServiceSwap}; use super::*; use cpal::{ traits::{DeviceTrait, HostTrait, StreamTrait}, @@ -125,7 +126,23 @@ mod cpal_impl { } } - pub fn run(sp: EmptyExtraFieldService, state: &mut State) -> ResultType<()> { + fn run_restart(sp: EmptyExtraFieldService, state: &mut State) -> ResultType<()> { + state.reset(); + sp.snapshot(|_sps: ServiceSwap<_>| Ok(()))?; + match &state.stream { + None => { + state.stream = Some(play(&sp)?); + } + _ => {} + } + if let Some((_, format)) = &state.stream { + sp.send_shared(format.clone()); + } + RESTARTING.store(false, Ordering::SeqCst); + Ok(()) + } + + fn run_serv_snapshot(sp: EmptyExtraFieldService, state: &mut State) -> ResultType<()> { sp.snapshot(|sps| { match &state.stream { None => { @@ -141,6 +158,14 @@ mod cpal_impl { Ok(()) } + pub fn run(sp: EmptyExtraFieldService, state: &mut State) -> ResultType<()> { + if !RESTARTING.load(Ordering::SeqCst) { + run_serv_snapshot(sp, state) + } else { + run_restart(sp, state) + } + } + fn send( data: Vec, sample_rate0: u32, diff --git a/src/ui_interface.rs b/src/ui_interface.rs index eb36cdae9..644f208b6 100644 --- a/src/ui_interface.rs +++ b/src/ui_interface.rs @@ -369,6 +369,8 @@ pub fn set_option(key: String, value: String) { return; } } + } else if &key == "audio-input" { + crate::audio_service::restart(); } #[cfg(not(any(target_os = "android", target_os = "ios")))] {