fix: voice call, select audio input device (#7922)

Signed-off-by: fufesou <shuanglongchen@yeah.net>
This commit is contained in:
fufesou 2024-05-07 16:18:48 +08:00 committed by GitHub
parent f08933f93c
commit 2c1595d0d5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
50 changed files with 305 additions and 79 deletions

View File

@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="200" height="200" class="icon" viewBox="0 0 1024 1024"><path d="M608 160c141.16 0 256 114.84 256 256 0 17.67 14.33 32 32 32s32-14.33 32-32c0-85.48-33.29-165.83-93.73-226.27C773.83 129.29 693.47 96 608 96c-17.67 0-32 14.33-32 32s14.33 32 32 32zm-24 168c61.76 0 112 50.24 112 112 0 17.67 14.33 32 32 32s32-14.33 32-32c0-97.05-78.95-176-176-176-17.67 0-32 14.33-32 32s14.33 32 32 32z"/><path d="M808.3 561.21c-12.76-3.83-25.7-6.2-38.46-7.03-60.3-4.5-116.45 18.9-146.55 61.08-22.6 31.67-45.66 50.01-68.52 54.5-17.71 3.48-33.12-1.7-45.49-5.85-2.66-.9-5.18-1.74-7.68-2.49-93.84-28.17-156.49-108.42-155.9-199.7.16-24.14 16.38-45.98 42.34-56.99 43.75-18.56 77.35-54 92.17-97.22 7.02-20.48 9.65-41.57 7.8-62.68-2.66-31.78-15.1-61.85-35.96-86.96-21.1-25.39-49.51-44-82.16-53.8-4.07-1.22-8.22-2.31-12.35-3.23-30.63-6.87-62.7-4.49-92.73 6.88-29.24 11.07-54.56 29.86-73.23 54.33a476.073 476.073 0 0 0-36.42 55.34 477.675 477.675 0 0 0-17.24 33.81C109.84 312.17 95.73 376.76 96 443.15c.26 63.78 13.7 126.26 39.95 185.7 27.55 62.39 69.3 119.84 120.74 166.11 54.14 48.71 117.6 84.85 188.63 107.4C499.02 919.41 554.33 928 610.21 928c10.99 0 22.01-.33 33.03-1 17.64-1.07 31.08-16.23 30.01-33.87-1.07-17.64-16.22-31.08-33.87-30.01-59.19 3.57-117.96-3.75-174.69-21.76C342.78 802.66 244.31 715.78 194.5 603c-46.76-105.9-46.21-221.33 1.55-325.03 4.55-9.87 9.57-19.72 14.92-29.26 9.29-16.54 19.89-32.64 31.5-47.86 23.47-30.77 64.09-45.87 101.07-37.58 2.66.6 5.33 1.3 7.95 2.08 40.93 12.29 69.48 45.6 72.75 84.86 0 .05.01.1.01.15 1.07 12.15-.47 24.39-4.58 36.37-8.94 26.06-29.58 47.59-56.63 59.07-23.58 10.01-43.63 25.72-57.99 45.45-15.12 20.78-23.2 45-23.36 70.05-.37 57.15 19 114.29 54.53 160.91 36.46 47.83 87.28 82.58 146.96 100.49 1.5.45 3.44 1.1 5.69 1.86 29.79 10.01 108.9 36.59 186.49-72.13 16.95-23.75 52.2-37.26 89.81-34.42l.36.03c7.97.51 16.17 2.02 24.34 4.47 22.12 6.64 42.04 25.38 56.11 52.77 16.97 33.04 21.71 72.53 12.1 100.56l-.16.47c-5.54 16.05-17.78 29.48-34.47 37.8-15.82 7.89-22.24 27.1-14.36 42.92s27.1 22.24 42.92 14.36c31.78-15.85 55.36-42.19 66.41-74.2l.18-.53c15.23-44.4 9.22-102.11-15.68-150.61-22.07-43.02-55.68-73.15-94.62-84.84z"/></svg> <svg xmlns="http://www.w3.org/2000/svg" width="200" height="200" class="icon" viewBox="-186 -186 1365 1365"><path d="M608 160c141.16 0 256 114.84 256 256 0 17.67 14.33 32 32 32s32-14.33 32-32c0-85.48-33.29-165.83-93.73-226.27C773.83 129.29 693.47 96 608 96c-17.67 0-32 14.33-32 32s14.33 32 32 32zm-24 168c61.76 0 112 50.24 112 112 0 17.67 14.33 32 32 32s32-14.33 32-32c0-97.05-78.95-176-176-176-17.67 0-32 14.33-32 32s14.33 32 32 32z"/><path d="M808.3 561.21c-12.76-3.83-25.7-6.2-38.46-7.03-60.3-4.5-116.45 18.9-146.55 61.08-22.6 31.67-45.66 50.01-68.52 54.5-17.71 3.48-33.12-1.7-45.49-5.85-2.66-.9-5.18-1.74-7.68-2.49-93.84-28.17-156.49-108.42-155.9-199.7.16-24.14 16.38-45.98 42.34-56.99 43.75-18.56 77.35-54 92.17-97.22 7.02-20.48 9.65-41.57 7.8-62.68-2.66-31.78-15.1-61.85-35.96-86.96-21.1-25.39-49.51-44-82.16-53.8-4.07-1.22-8.22-2.31-12.35-3.23-30.63-6.87-62.7-4.49-92.73 6.88-29.24 11.07-54.56 29.86-73.23 54.33a476.073 476.073 0 0 0-36.42 55.34 477.675 477.675 0 0 0-17.24 33.81C109.84 312.17 95.73 376.76 96 443.15c.26 63.78 13.7 126.26 39.95 185.7 27.55 62.39 69.3 119.84 120.74 166.11 54.14 48.71 117.6 84.85 188.63 107.4C499.02 919.41 554.33 928 610.21 928c10.99 0 22.01-.33 33.03-1 17.64-1.07 31.08-16.23 30.01-33.87-1.07-17.64-16.22-31.08-33.87-30.01-59.19 3.57-117.96-3.75-174.69-21.76C342.78 802.66 244.31 715.78 194.5 603c-46.76-105.9-46.21-221.33 1.55-325.03 4.55-9.87 9.57-19.72 14.92-29.26 9.29-16.54 19.89-32.64 31.5-47.86 23.47-30.77 64.09-45.87 101.07-37.58 2.66.6 5.33 1.3 7.95 2.08 40.93 12.29 69.48 45.6 72.75 84.86 0 .05.01.1.01.15 1.07 12.15-.47 24.39-4.58 36.37-8.94 26.06-29.58 47.59-56.63 59.07-23.58 10.01-43.63 25.72-57.99 45.45-15.12 20.78-23.2 45-23.36 70.05-.37 57.15 19 114.29 54.53 160.91 36.46 47.83 87.28 82.58 146.96 100.49 1.5.45 3.44 1.1 5.69 1.86 29.79 10.01 108.9 36.59 186.49-72.13 16.95-23.75 52.2-37.26 89.81-34.42l.36.03c7.97.51 16.17 2.02 24.34 4.47 22.12 6.64 42.04 25.38 56.11 52.77 16.97 33.04 21.71 72.53 12.1 100.56l-.16.47c-5.54 16.05-17.78 29.48-34.47 37.8-15.82 7.89-22.24 27.1-14.36 42.92s27.1 22.24 42.92 14.36c31.78-15.85 55.36-42.19 66.41-74.2l.18-.53c15.23-44.4 9.22-102.11-15.68-150.61-22.07-43.02-55.68-73.15-94.62-84.84z"/></svg>

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@ -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<String> 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<String> getValue() async {
String device = await bind.mainGetOption(key: 'audio-input');
if (device.isNotEmpty) {
return device;
} else {
return getDefault();
}
}
static Future<void> setDevice(String device) async {
if (device == getDefault()) device = '';
await bind.mainSetOption(key: 'audio-input', value: device);
}
static Future<Map<String, Object>> getDevicesInfo() async {
List<String> 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<String> devices = data['devices'] as List<String>;
if (devices.isEmpty) {
return const Offstage();
}
return builder(devices, currentDevice, setDevice);
},
);
}
}

View File

@ -6,6 +6,7 @@ import 'package:file_picker/file_picker.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_hbb/common.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/common/widgets/setting_widgets.dart';
import 'package:flutter_hbb/consts.dart'; import 'package:flutter_hbb/consts.dart';
import 'package:flutter_hbb/desktop/pages/desktop_home_page.dart'; import 'package:flutter_hbb/desktop/pages/desktop_home_page.dart';
@ -469,38 +470,7 @@ class _GeneralState extends State<_General> {
return const Offstage(); return const Offstage();
} }
String getDefault() { return AudioInput(builder: (devices, currentDevice, setDevice) {
if (isWindows) return translate('System Sound');
return '';
}
Future<String> 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<String> 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<String> devices = data['devices'] as List<String>;
if (devices.isEmpty) {
return const Offstage();
}
return _Card(title: 'Audio Input Device', children: [ return _Card(title: 'Audio Input Device', children: [
...devices.map((device) => _Radio<String>(context, ...devices.map((device) => _Radio<String>(context,
value: device, value: device,

View File

@ -4,6 +4,7 @@ import 'dart:async';
import 'dart:math'; import 'dart:math';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_hbb/common/widgets/audio_input.dart';
import 'package:flutter_hbb/consts.dart'; import 'package:flutter_hbb/consts.dart';
import 'package:flutter_hbb/desktop/widgets/tabbar_widget.dart'; import 'package:flutter_hbb/desktop/widgets/tabbar_widget.dart';
import 'package:flutter_hbb/models/chat_model.dart'; import 'package:flutter_hbb/models/chat_model.dart';
@ -701,17 +702,86 @@ class _CmControlPanel extends StatelessWidget {
children: [ children: [
Offstage( Offstage(
offstage: !client.inVoiceCall, offstage: !client.inVoiceCall,
child: buildButton( child: Row(
context, children: [
color: Colors.red, Expanded(
onClick: () => closeVoiceCall(), child: buildButton(context,
icon: Icon( color: MyTheme.accent,
Icons.call_end_rounded, onClick: null, onTapDown: (details) async {
color: Colors.white, final devicesInfo = await AudioInput.getDevicesInfo();
size: 14, List<String> devices = devicesInfo['devices'] as List<String>;
), if (devices.isEmpty) {
text: "Stop voice call", msgBox(
textColor: Colors.white, 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<String>(
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( Offstage(
@ -872,12 +942,14 @@ class _CmControlPanel extends StatelessWidget {
Widget buildButton(BuildContext context, Widget buildButton(BuildContext context,
{required Color? color, {required Color? color,
required Function() onClick, GestureTapCallback? onClick,
Icon? icon, Widget? icon,
BoxBorder? border, BoxBorder? border,
required String text, required String text,
required Color? textColor, required Color? textColor,
String? tooltip}) { String? tooltip,
GestureTapDownCallback? onTapDown}) {
assert(!(onClick == null && onTapDown == null));
Widget textWidget; Widget textWidget;
if (icon != null) { if (icon != null) {
textWidget = Text( textWidget = Text(
@ -901,7 +973,16 @@ class _CmControlPanel extends StatelessWidget {
color: color, borderRadius: borderRadius, border: border), color: color, borderRadius: borderRadius, border: border),
child: InkWell( child: InkWell(
borderRadius: borderRadius, 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( child: Row(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [

View File

@ -3,6 +3,7 @@ import 'dart:async';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.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/common/widgets/toolbar.dart';
import 'package:flutter_hbb/models/chat_model.dart'; import 'package:flutter_hbb/models/chat_model.dart';
import 'package:flutter_hbb/models/state_model.dart'; import 'package:flutter_hbb/models/state_model.dart';
@ -1953,34 +1954,71 @@ class _VoiceCallMenu extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
menuChildrenGetter() {
final audioInput =
AudioInput(builder: (devices, currentDevice, setDevice) {
return Column(
children: devices
.map((d) => RdoMenuButton<String>(
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( return Obx(
() { () {
final String tooltip;
final String icon;
switch (ffi.chatModel.voiceCallStatus.value) { switch (ffi.chatModel.voiceCallStatus.value) {
case VoiceCallStatus.waitingForResponse: case VoiceCallStatus.waitingForResponse:
tooltip = "Waiting"; return buildCallWaiting(context);
icon = "assets/call_wait.svg";
break;
case VoiceCallStatus.connected: case VoiceCallStatus.connected:
tooltip = "Disconnect"; return _IconSubmenuButton(
icon = "assets/call_end.svg"; tooltip: 'Voice call',
break; svg: 'assets/voice_call.svg',
color: _ToolbarTheme.blueColor,
hoverColor: _ToolbarTheme.hoverBlueColor,
menuChildrenGetter: menuChildrenGetter,
ffi: ffi,
);
default: default:
return Offstage(); 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 { class _RecordMenu extends StatelessWidget {
const _RecordMenu({Key? key}) : super(key: key); const _RecordMenu({Key? key}) : super(key: key);
@ -2115,7 +2153,7 @@ class _IconSubmenuButton extends StatefulWidget {
final Color hoverColor; final Color hoverColor;
final List<Widget> Function() menuChildrenGetter; final List<Widget> Function() menuChildrenGetter;
final MenuStyle? menuStyle; final MenuStyle? menuStyle;
final FFI ffi; final FFI? ffi;
final double? width; final double? width;
_IconSubmenuButton({ _IconSubmenuButton({
@ -2126,7 +2164,7 @@ class _IconSubmenuButton extends StatefulWidget {
required this.color, required this.color,
required this.hoverColor, required this.hoverColor,
required this.menuChildrenGetter, required this.menuChildrenGetter,
required this.ffi, this.ffi,
this.menuStyle, this.menuStyle,
this.width, this.width,
}) : super(key: key); }) : super(key: key);
@ -2208,13 +2246,13 @@ class MenuButton extends StatelessWidget {
final VoidCallback? onPressed; final VoidCallback? onPressed;
final Widget? trailingIcon; final Widget? trailingIcon;
final Widget? child; final Widget? child;
final FFI ffi; final FFI? ffi;
MenuButton( MenuButton(
{Key? key, {Key? key,
this.onPressed, this.onPressed,
this.trailingIcon, this.trailingIcon,
required this.child, required this.child,
required this.ffi}) this.ffi})
: super(key: key); : super(key: key);
@override @override
@ -2223,7 +2261,9 @@ class MenuButton extends StatelessWidget {
key: key, key: key,
onPressed: onPressed != null onPressed: onPressed != null
? () { ? () {
_menuDismissCallback(ffi); if (ffi != null) {
_menuDismissCallback(ffi!);
}
onPressed?.call(); onPressed?.call();
} }
: null, : null,
@ -2236,13 +2276,13 @@ class CkbMenuButton extends StatelessWidget {
final bool? value; final bool? value;
final ValueChanged<bool?>? onChanged; final ValueChanged<bool?>? onChanged;
final Widget? child; final Widget? child;
final FFI ffi; final FFI? ffi;
const CkbMenuButton( const CkbMenuButton(
{Key? key, {Key? key,
required this.value, required this.value,
required this.onChanged, required this.onChanged,
required this.child, required this.child,
required this.ffi}) this.ffi})
: super(key: key); : super(key: key);
@override @override
@ -2253,7 +2293,9 @@ class CkbMenuButton extends StatelessWidget {
child: child, child: child,
onChanged: onChanged != null onChanged: onChanged != null
? (bool? value) { ? (bool? value) {
_menuDismissCallback(ffi); if (ffi != null) {
_menuDismissCallback(ffi!);
}
onChanged?.call(value); onChanged?.call(value);
} }
: null, : null,
@ -2266,13 +2308,13 @@ class RdoMenuButton<T> extends StatelessWidget {
final T? groupValue; final T? groupValue;
final ValueChanged<T?>? onChanged; final ValueChanged<T?>? onChanged;
final Widget? child; final Widget? child;
final FFI ffi; final FFI? ffi;
const RdoMenuButton({ const RdoMenuButton({
Key? key, Key? key,
required this.value, required this.value,
required this.groupValue, required this.groupValue,
required this.child, required this.child,
required this.ffi, this.ffi,
this.onChanged, this.onChanged,
}) : super(key: key); }) : super(key: key);
@ -2284,7 +2326,9 @@ class RdoMenuButton<T> extends StatelessWidget {
child: child, child: child,
onChanged: onChanged != null onChanged: onChanged != null
? (T? value) { ? (T? value) {
_menuDismissCallback(ffi); if (ffi != null) {
_menuDismissCallback(ffi!);
}
onChanged?.call(value); onChanged?.call(value);
} }
: null, : null,
@ -2471,10 +2515,11 @@ class InputModeMenu {
_menuDismissCallback(FFI ffi) => ffi.inputModel.refreshMousePos(); _menuDismissCallback(FFI ffi) => ffi.inputModel.refreshMousePos();
Widget _buildPointerTrackWidget(Widget child, FFI ffi) { Widget _buildPointerTrackWidget(Widget child, FFI? ffi) {
return Listener( return Listener(
onPointerHover: (PointerHoverEvent e) => onPointerHover: (PointerHoverEvent e) => {
ffi.inputModel.lastMousePos = e.position, if (ffi != null) {ffi.inputModel.lastMousePos = e.position}
},
child: MouseRegion( child: MouseRegion(
child: child, child: child,
), ),

View File

@ -454,7 +454,12 @@ async fn handle(data: Data, stream: &mut Connection) {
if let Some(v) = value.get("privacy-mode-impl-key") { if let Some(v) = value.get("privacy-mode-impl-key") {
crate::privacy_mode::switch(v); crate::privacy_mode::switch(v);
} }
let pre_opts = Config::get_options();
let new_audio_input = pre_opts.get("audio-input");
Config::set_options(value); 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); allow_err!(stream.send(&Data::Options(None)).await);
} }
}, },

View File

@ -606,5 +606,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Follow remote cursor", ""), ("Follow remote cursor", ""),
("Follow remote window focus", ""), ("Follow remote window focus", ""),
("default_proxy_tip", ""), ("default_proxy_tip", ""),
("no_audio_input_device_tip", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -606,5 +606,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Follow remote cursor", ""), ("Follow remote cursor", ""),
("Follow remote window focus", ""), ("Follow remote window focus", ""),
("default_proxy_tip", ""), ("default_proxy_tip", ""),
("no_audio_input_device_tip", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -606,5 +606,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Follow remote cursor", ""), ("Follow remote cursor", ""),
("Follow remote window focus", ""), ("Follow remote window focus", ""),
("default_proxy_tip", ""), ("default_proxy_tip", ""),
("no_audio_input_device_tip", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -606,5 +606,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Follow remote cursor", "跟随远程光标"), ("Follow remote cursor", "跟随远程光标"),
("Follow remote window focus", "跟随远程窗口焦点"), ("Follow remote window focus", "跟随远程窗口焦点"),
("default_proxy_tip", "默认代理协议及端口为 Socks5 和 1080"), ("default_proxy_tip", "默认代理协议及端口为 Socks5 和 1080"),
("no_audio_input_device_tip", "未找到音频输入设备"),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -606,5 +606,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Follow remote cursor", "Sledovat dálkový kurzor"), ("Follow remote cursor", "Sledovat dálkový kurzor"),
("Follow remote window focus", "Sledovat zaměření vzdáleného okna"), ("Follow remote window focus", "Sledovat zaměření vzdáleného okna"),
("default_proxy_tip", ""), ("default_proxy_tip", ""),
("no_audio_input_device_tip", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -606,5 +606,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Follow remote cursor", ""), ("Follow remote cursor", ""),
("Follow remote window focus", ""), ("Follow remote window focus", ""),
("default_proxy_tip", ""), ("default_proxy_tip", ""),
("no_audio_input_device_tip", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -606,5 +606,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Follow remote cursor", "Dem entfernten Cursor folgen"), ("Follow remote cursor", "Dem entfernten Cursor folgen"),
("Follow remote window focus", "Dem Fokus des entfernten Fensters folgen"), ("Follow remote window focus", "Dem Fokus des entfernten Fensters folgen"),
("default_proxy_tip", ""), ("default_proxy_tip", ""),
("no_audio_input_device_tip", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -606,5 +606,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Follow remote cursor", ""), ("Follow remote cursor", ""),
("Follow remote window focus", ""), ("Follow remote window focus", ""),
("default_proxy_tip", ""), ("default_proxy_tip", ""),
("no_audio_input_device_tip", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -224,5 +224,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Follow remote cursor", ""), ("Follow remote cursor", ""),
("Follow remote window focus", ""), ("Follow remote window focus", ""),
("default_proxy_tip", "Default protocol and port are Socks5 and 1080"), ("default_proxy_tip", "Default protocol and port are Socks5 and 1080"),
("no_audio_input_device_tip", "No audio input device found."),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -606,5 +606,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Follow remote cursor", ""), ("Follow remote cursor", ""),
("Follow remote window focus", ""), ("Follow remote window focus", ""),
("default_proxy_tip", ""), ("default_proxy_tip", ""),
("no_audio_input_device_tip", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -606,5 +606,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Follow remote cursor", "Seguir cursor remoto"), ("Follow remote cursor", "Seguir cursor remoto"),
("Follow remote window focus", "Seguir ventana remota activa"), ("Follow remote window focus", "Seguir ventana remota activa"),
("default_proxy_tip", ""), ("default_proxy_tip", ""),
("no_audio_input_device_tip", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -606,5 +606,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Follow remote cursor", ""), ("Follow remote cursor", ""),
("Follow remote window focus", ""), ("Follow remote window focus", ""),
("default_proxy_tip", ""), ("default_proxy_tip", ""),
("no_audio_input_device_tip", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -606,5 +606,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Follow remote cursor", ""), ("Follow remote cursor", ""),
("Follow remote window focus", ""), ("Follow remote window focus", ""),
("default_proxy_tip", ""), ("default_proxy_tip", ""),
("no_audio_input_device_tip", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -606,5 +606,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Follow remote cursor", ""), ("Follow remote cursor", ""),
("Follow remote window focus", ""), ("Follow remote window focus", ""),
("default_proxy_tip", ""), ("default_proxy_tip", ""),
("no_audio_input_device_tip", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -606,5 +606,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Follow remote cursor", ""), ("Follow remote cursor", ""),
("Follow remote window focus", ""), ("Follow remote window focus", ""),
("default_proxy_tip", ""), ("default_proxy_tip", ""),
("no_audio_input_device_tip", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -606,5 +606,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Follow remote cursor", ""), ("Follow remote cursor", ""),
("Follow remote window focus", ""), ("Follow remote window focus", ""),
("default_proxy_tip", ""), ("default_proxy_tip", ""),
("no_audio_input_device_tip", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -606,5 +606,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Follow remote cursor", ""), ("Follow remote cursor", ""),
("Follow remote window focus", ""), ("Follow remote window focus", ""),
("default_proxy_tip", ""), ("default_proxy_tip", ""),
("no_audio_input_device_tip", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -606,5 +606,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Follow remote cursor", ""), ("Follow remote cursor", ""),
("Follow remote window focus", ""), ("Follow remote window focus", ""),
("default_proxy_tip", ""), ("default_proxy_tip", ""),
("no_audio_input_device_tip", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -606,5 +606,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Follow remote cursor", "Segui cursore remoto"), ("Follow remote cursor", "Segui cursore remoto"),
("Follow remote window focus", "Segui focus finestra remota"), ("Follow remote window focus", "Segui focus finestra remota"),
("default_proxy_tip", ""), ("default_proxy_tip", ""),
("no_audio_input_device_tip", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -606,5 +606,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Follow remote cursor", ""), ("Follow remote cursor", ""),
("Follow remote window focus", ""), ("Follow remote window focus", ""),
("default_proxy_tip", ""), ("default_proxy_tip", ""),
("no_audio_input_device_tip", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -606,5 +606,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Follow remote cursor", ""), ("Follow remote cursor", ""),
("Follow remote window focus", ""), ("Follow remote window focus", ""),
("default_proxy_tip", ""), ("default_proxy_tip", ""),
("no_audio_input_device_tip", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -606,5 +606,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Follow remote cursor", ""), ("Follow remote cursor", ""),
("Follow remote window focus", ""), ("Follow remote window focus", ""),
("default_proxy_tip", ""), ("default_proxy_tip", ""),
("no_audio_input_device_tip", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -606,5 +606,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Follow remote cursor", ""), ("Follow remote cursor", ""),
("Follow remote window focus", ""), ("Follow remote window focus", ""),
("default_proxy_tip", ""), ("default_proxy_tip", ""),
("no_audio_input_device_tip", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -606,5 +606,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Follow remote cursor", "Sekot attālajam kursoram"), ("Follow remote cursor", "Sekot attālajam kursoram"),
("Follow remote window focus", "Sekot attālā loga fokusam"), ("Follow remote window focus", "Sekot attālā loga fokusam"),
("default_proxy_tip", ""), ("default_proxy_tip", ""),
("no_audio_input_device_tip", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -606,5 +606,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Follow remote cursor", ""), ("Follow remote cursor", ""),
("Follow remote window focus", ""), ("Follow remote window focus", ""),
("default_proxy_tip", ""), ("default_proxy_tip", ""),
("no_audio_input_device_tip", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -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 cursor", "Volg de cursor op afstand"),
("Follow remote window focus", "Volg de focus van het venster op afstand"), ("Follow remote window focus", "Volg de focus van het venster op afstand"),
("default_proxy_tip", ""), ("default_proxy_tip", ""),
("no_audio_input_device_tip", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -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 cursor", "Podążaj za zdalnym kursorem"),
("Follow remote window focus", "Podążaj za aktywnością zdalnych okien"), ("Follow remote window focus", "Podążaj za aktywnością zdalnych okien"),
("default_proxy_tip", ""), ("default_proxy_tip", ""),
("no_audio_input_device_tip", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -606,5 +606,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Follow remote cursor", ""), ("Follow remote cursor", ""),
("Follow remote window focus", ""), ("Follow remote window focus", ""),
("default_proxy_tip", ""), ("default_proxy_tip", ""),
("no_audio_input_device_tip", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -606,5 +606,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Follow remote cursor", ""), ("Follow remote cursor", ""),
("Follow remote window focus", ""), ("Follow remote window focus", ""),
("default_proxy_tip", ""), ("default_proxy_tip", ""),
("no_audio_input_device_tip", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -606,5 +606,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Follow remote cursor", ""), ("Follow remote cursor", ""),
("Follow remote window focus", ""), ("Follow remote window focus", ""),
("default_proxy_tip", ""), ("default_proxy_tip", ""),
("no_audio_input_device_tip", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -606,5 +606,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Follow remote cursor", "Следовать за удалённым курсором"), ("Follow remote cursor", "Следовать за удалённым курсором"),
("Follow remote window focus", "Следовать за фокусом удалённого окна"), ("Follow remote window focus", "Следовать за фокусом удалённого окна"),
("default_proxy_tip", ""), ("default_proxy_tip", ""),
("no_audio_input_device_tip", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -606,5 +606,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Follow remote cursor", "Nasledovať vzdialený kurzor"), ("Follow remote cursor", "Nasledovať vzdialený kurzor"),
("Follow remote window focus", "Nasledovať vzdialené zameranie okna"), ("Follow remote window focus", "Nasledovať vzdialené zameranie okna"),
("default_proxy_tip", ""), ("default_proxy_tip", ""),
("no_audio_input_device_tip", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -606,5 +606,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Follow remote cursor", ""), ("Follow remote cursor", ""),
("Follow remote window focus", ""), ("Follow remote window focus", ""),
("default_proxy_tip", ""), ("default_proxy_tip", ""),
("no_audio_input_device_tip", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -606,5 +606,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Follow remote cursor", ""), ("Follow remote cursor", ""),
("Follow remote window focus", ""), ("Follow remote window focus", ""),
("default_proxy_tip", ""), ("default_proxy_tip", ""),
("no_audio_input_device_tip", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -606,5 +606,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Follow remote cursor", ""), ("Follow remote cursor", ""),
("Follow remote window focus", ""), ("Follow remote window focus", ""),
("default_proxy_tip", ""), ("default_proxy_tip", ""),
("no_audio_input_device_tip", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -606,5 +606,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Follow remote cursor", ""), ("Follow remote cursor", ""),
("Follow remote window focus", ""), ("Follow remote window focus", ""),
("default_proxy_tip", ""), ("default_proxy_tip", ""),
("no_audio_input_device_tip", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -606,5 +606,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Follow remote cursor", ""), ("Follow remote cursor", ""),
("Follow remote window focus", ""), ("Follow remote window focus", ""),
("default_proxy_tip", ""), ("default_proxy_tip", ""),
("no_audio_input_device_tip", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -606,5 +606,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Follow remote cursor", ""), ("Follow remote cursor", ""),
("Follow remote window focus", ""), ("Follow remote window focus", ""),
("default_proxy_tip", ""), ("default_proxy_tip", ""),
("no_audio_input_device_tip", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -606,5 +606,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Follow remote cursor", ""), ("Follow remote cursor", ""),
("Follow remote window focus", ""), ("Follow remote window focus", ""),
("default_proxy_tip", ""), ("default_proxy_tip", ""),
("no_audio_input_device_tip", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -606,5 +606,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Follow remote cursor", "跟隨遠端游標"), ("Follow remote cursor", "跟隨遠端游標"),
("Follow remote window focus", "跟隨遠端視窗焦點"), ("Follow remote window focus", "跟隨遠端視窗焦點"),
("default_proxy_tip", ""), ("default_proxy_tip", ""),
("no_audio_input_device_tip", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -606,5 +606,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Follow remote cursor", ""), ("Follow remote cursor", ""),
("Follow remote window focus", ""), ("Follow remote window focus", ""),
("default_proxy_tip", ""), ("default_proxy_tip", ""),
("no_audio_input_device_tip", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -606,5 +606,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Follow remote cursor", ""), ("Follow remote cursor", ""),
("Follow remote window focus", ""), ("Follow remote window focus", ""),
("default_proxy_tip", ""), ("default_proxy_tip", ""),
("no_audio_input_device_tip", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -103,6 +103,7 @@ mod pa_impl {
#[cfg(not(any(target_os = "linux", target_os = "android")))] #[cfg(not(any(target_os = "linux", target_os = "android")))]
mod cpal_impl { mod cpal_impl {
use self::service::{Reset, ServiceSwap};
use super::*; use super::*;
use cpal::{ use cpal::{
traits::{DeviceTrait, HostTrait, StreamTrait}, 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| { sp.snapshot(|sps| {
match &state.stream { match &state.stream {
None => { None => {
@ -141,6 +158,14 @@ mod cpal_impl {
Ok(()) 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( fn send(
data: Vec<f32>, data: Vec<f32>,
sample_rate0: u32, sample_rate0: u32,

View File

@ -369,6 +369,8 @@ pub fn set_option(key: String, value: String) {
return; return;
} }
} }
} else if &key == "audio-input" {
crate::audio_service::restart();
} }
#[cfg(not(any(target_os = "android", target_os = "ios")))] #[cfg(not(any(target_os = "android", target_os = "ios")))]
{ {