Merge pull request #1759 from 21pages/fps
support adjust fps && fix statusbar ui
This commit is contained in:
commit
493126e328
2
build.py
2
build.py
@ -71,7 +71,7 @@ def make_parser():
|
||||
parser.add_argument(
|
||||
'--hwcodec',
|
||||
action='store_true',
|
||||
help='Enable feature hwcodec'
|
||||
help='Enable feature hwcodec' + ('' if windows or osx else ', need libva-dev, libvdpau-dev.')
|
||||
)
|
||||
parser.add_argument(
|
||||
'--portable',
|
||||
|
@ -39,6 +39,10 @@ class _ConnectionPageState extends State<ConnectionPage>
|
||||
final RxBool _idInputFocused = false.obs;
|
||||
final FocusNode _idFocusNode = FocusNode();
|
||||
|
||||
var svcStopped = false.obs;
|
||||
var svcStatusCode = 0.obs;
|
||||
var svcIsUsingPublicServer = true.obs;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
@ -55,6 +59,18 @@ class _ConnectionPageState extends State<ConnectionPage>
|
||||
_updateTimer = Timer.periodic(Duration(seconds: 1), (timer) {
|
||||
updateStatus();
|
||||
});
|
||||
_idFocusNode.addListener(() {
|
||||
_idInputFocused.value = _idFocusNode.hasFocus;
|
||||
});
|
||||
Get.put<RxBool>(svcStopped, tag: 'service-stop');
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_idController.dispose();
|
||||
_updateTimer?.cancel();
|
||||
Get.delete<RxBool>(tag: 'service-stop');
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
@ -107,9 +123,8 @@ class _ConnectionPageState extends State<ConnectionPage>
|
||||
).paddingOnly(left: 12.0),
|
||||
),
|
||||
),
|
||||
const Divider(),
|
||||
SizedBox(child: Obx(() => buildStatus()))
|
||||
.paddingOnly(bottom: 12, top: 6),
|
||||
const Divider(height: 1),
|
||||
buildStatus()
|
||||
],
|
||||
);
|
||||
}
|
||||
@ -124,9 +139,6 @@ class _ConnectionPageState extends State<ConnectionPage>
|
||||
/// UI for the remote ID TextField.
|
||||
/// Search for a peer and connect to it if the id exists.
|
||||
Widget _buildRemoteIDTextField(BuildContext context) {
|
||||
_idFocusNode.addListener(() {
|
||||
_idInputFocused.value = _idFocusNode.hasFocus;
|
||||
});
|
||||
var w = Container(
|
||||
width: 320 + 20 * 2,
|
||||
padding: const EdgeInsets.fromLTRB(20, 24, 20, 22),
|
||||
@ -223,91 +235,74 @@ class _ConnectionPageState extends State<ConnectionPage>
|
||||
constraints: const BoxConstraints(maxWidth: 600), child: w));
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_idController.dispose();
|
||||
_updateTimer?.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
var svcStopped = false.obs;
|
||||
var svcStatusCode = 0.obs;
|
||||
var svcIsUsingPublicServer = true.obs;
|
||||
|
||||
Widget buildStatus() {
|
||||
final fontSize = 14.0;
|
||||
final textStyle = TextStyle(fontSize: fontSize);
|
||||
final light = Container(
|
||||
height: 8,
|
||||
width: 8,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
color: svcStopped.value || svcStatusCode.value == 0
|
||||
? kColorWarn
|
||||
: (svcStatusCode.value == 1
|
||||
? Color.fromARGB(255, 50, 190, 166)
|
||||
: Color.fromARGB(255, 224, 79, 95)),
|
||||
),
|
||||
).paddingSymmetric(horizontal: 12.0);
|
||||
if (svcStopped.value) {
|
||||
return Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
light,
|
||||
Text(translate("Service is not running"), style: textStyle),
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
bool checked = await bind.mainCheckSuperUserPermission();
|
||||
if (checked) {
|
||||
bind.mainSetOption(key: "stop-service", value: "");
|
||||
bind.mainSetOption(key: "access-mode", value: "");
|
||||
}
|
||||
},
|
||||
child: Text(translate("Start Service"), style: textStyle))
|
||||
],
|
||||
);
|
||||
} else {
|
||||
if (svcStatusCode.value == 0) {
|
||||
return Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
light,
|
||||
Text(translate("connecting_status"), style: textStyle)
|
||||
],
|
||||
);
|
||||
} else if (svcStatusCode.value == -1) {
|
||||
return Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
light,
|
||||
Text(translate("not_ready_status"), style: textStyle)
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
return Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
light,
|
||||
Text(translate('Ready'), style: textStyle),
|
||||
Offstage(
|
||||
offstage: !svcIsUsingPublicServer.value,
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Text(', ', style: textStyle),
|
||||
InkWell(
|
||||
onTap: onUsePublicServerGuide,
|
||||
child: Text(
|
||||
translate('setup_server_tip'),
|
||||
style: TextStyle(
|
||||
decoration: TextDecoration.underline,
|
||||
fontSize: fontSize),
|
||||
),
|
||||
)
|
||||
],
|
||||
))
|
||||
],
|
||||
final em = 14.0;
|
||||
return ConstrainedBox(
|
||||
constraints: BoxConstraints.tightFor(height: 3 * em),
|
||||
child: Obx(() => Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Container(
|
||||
height: 8,
|
||||
width: 8,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
color: svcStopped.value || svcStatusCode.value == 0
|
||||
? kColorWarn
|
||||
: (svcStatusCode.value == 1
|
||||
? Color.fromARGB(255, 50, 190, 166)
|
||||
: Color.fromARGB(255, 224, 79, 95)),
|
||||
),
|
||||
).marginSymmetric(horizontal: em),
|
||||
Text(
|
||||
svcStopped.value
|
||||
? translate("Service is not running")
|
||||
: svcStatusCode.value == 0
|
||||
? translate("connecting_status")
|
||||
: svcStatusCode.value == -1
|
||||
? translate("not_ready_status")
|
||||
: translate('Ready'),
|
||||
style: TextStyle(fontSize: em)),
|
||||
// stop
|
||||
Offstage(
|
||||
offstage: !svcStopped.value,
|
||||
child: GestureDetector(
|
||||
onTap: () async {
|
||||
bool checked =
|
||||
await bind.mainCheckSuperUserPermission();
|
||||
if (checked) {
|
||||
bind.mainSetOption(key: "stop-service", value: "");
|
||||
bind.mainSetOption(key: "access-mode", value: "");
|
||||
}
|
||||
},
|
||||
child: Text(translate("Start Service"),
|
||||
style: TextStyle(
|
||||
decoration: TextDecoration.underline,
|
||||
fontSize: em)))
|
||||
.marginOnly(left: em),
|
||||
),
|
||||
// ready && public
|
||||
Offstage(
|
||||
offstage: !(!svcStopped.value &&
|
||||
svcStatusCode.value == 1 &&
|
||||
svcIsUsingPublicServer.value),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Text(', ', style: TextStyle(fontSize: em)),
|
||||
InkWell(
|
||||
onTap: onUsePublicServerGuide,
|
||||
child: Text(
|
||||
translate('setup_server_tip'),
|
||||
style: TextStyle(
|
||||
decoration: TextDecoration.underline, fontSize: em),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
)),
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -432,6 +432,7 @@ class _SafetyState extends State<_Safety> with AutomaticKeepAliveClientMixin {
|
||||
bool get wantKeepAlive => true;
|
||||
bool locked = bind.mainIsInstalled();
|
||||
final scrollController = ScrollController();
|
||||
final RxBool serviceStop = Get.find<RxBool>(tag: 'service-stop');
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@ -465,17 +466,15 @@ class _SafetyState extends State<_Safety> with AutomaticKeepAliveClientMixin {
|
||||
}
|
||||
|
||||
Widget permissions(context) {
|
||||
bool enabled = !locked;
|
||||
return Obx(() => _permissions(context, serviceStop.value));
|
||||
}
|
||||
|
||||
Widget _permissions(context, bool stopService) {
|
||||
bool enabled = !locked;
|
||||
return _futureBuilder(future: () async {
|
||||
bool stopService = option2bool(
|
||||
'stop-service', await bind.mainGetOption(key: 'stop-service'));
|
||||
final accessMode = await bind.mainGetOption(key: 'access-mode');
|
||||
return {'stopService': stopService, 'accessMode': accessMode};
|
||||
return await bind.mainGetOption(key: 'access-mode');
|
||||
}(), hasData: (data) {
|
||||
var map = data! as Map<String, dynamic>;
|
||||
bool stopService = map['stopService'] as bool;
|
||||
String accessMode = map['accessMode'] as String;
|
||||
String accessMode = data! as String;
|
||||
_AccessMode mode;
|
||||
if (stopService) {
|
||||
mode = _AccessMode.deny;
|
||||
|
@ -742,59 +742,147 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
|
||||
await bind.sessionSetImageQuality(id: widget.id, value: newValue);
|
||||
}
|
||||
|
||||
double qualityInitValue = 50;
|
||||
double fpsInitValue = 30;
|
||||
bool qualitySet = false;
|
||||
bool fpsSet = false;
|
||||
setCustomValues({double? quality, double? fps}) async {
|
||||
if (quality != null) {
|
||||
qualitySet = true;
|
||||
await bind.sessionSetCustomImageQuality(
|
||||
id: widget.id, value: quality.toInt());
|
||||
}
|
||||
if (fps != null) {
|
||||
fpsSet = true;
|
||||
await bind.sessionSetCustomFps(id: widget.id, fps: fps.toInt());
|
||||
}
|
||||
if (!qualitySet) {
|
||||
qualitySet = true;
|
||||
await bind.sessionSetCustomImageQuality(
|
||||
id: widget.id, value: qualityInitValue.toInt());
|
||||
}
|
||||
if (!fpsSet) {
|
||||
fpsSet = true;
|
||||
await bind.sessionSetCustomFps(
|
||||
id: widget.id, fps: fpsInitValue.toInt());
|
||||
}
|
||||
}
|
||||
|
||||
if (newValue == 'custom') {
|
||||
final btnCancel = msgBoxButton(translate('Close'), () {
|
||||
final btnClose = msgBoxButton(translate('Close'), () async {
|
||||
await setCustomValues();
|
||||
widget.ffi.dialogManager.dismissAll();
|
||||
});
|
||||
|
||||
// quality
|
||||
final quality =
|
||||
await bind.sessionGetCustomImageQuality(id: widget.id);
|
||||
double initValue = quality != null && quality.isNotEmpty
|
||||
qualityInitValue = quality != null && quality.isNotEmpty
|
||||
? quality[0].toDouble()
|
||||
: 50.0;
|
||||
const minValue = 10.0;
|
||||
const maxValue = 100.0;
|
||||
if (initValue < minValue) {
|
||||
initValue = minValue;
|
||||
const qualityMinValue = 10.0;
|
||||
const qualityMaxValue = 100.0;
|
||||
if (qualityInitValue < qualityMinValue) {
|
||||
qualityInitValue = qualityMinValue;
|
||||
}
|
||||
if (initValue > maxValue) {
|
||||
initValue = maxValue;
|
||||
if (qualityInitValue > qualityMaxValue) {
|
||||
qualityInitValue = qualityMaxValue;
|
||||
}
|
||||
final RxDouble sliderValue = RxDouble(initValue);
|
||||
final rxReplay = rxdart.ReplaySubject<double>();
|
||||
rxReplay
|
||||
final RxDouble qualitySliderValue = RxDouble(qualityInitValue);
|
||||
final qualityRxReplay = rxdart.ReplaySubject<double>();
|
||||
qualityRxReplay
|
||||
.throttleTime(const Duration(milliseconds: 1000),
|
||||
trailing: true, leading: false)
|
||||
.listen((double v) {
|
||||
() async {
|
||||
await bind.sessionSetCustomImageQuality(
|
||||
id: widget.id, value: v.toInt());
|
||||
await setCustomValues(quality: v);
|
||||
}();
|
||||
});
|
||||
final slider = Obx(() {
|
||||
return Slider(
|
||||
value: sliderValue.value,
|
||||
min: minValue,
|
||||
max: maxValue,
|
||||
divisions: 90,
|
||||
onChanged: (double value) {
|
||||
sliderValue.value = value;
|
||||
rxReplay.add(value);
|
||||
},
|
||||
);
|
||||
final qualitySlider = Obx(() => Row(
|
||||
children: [
|
||||
Slider(
|
||||
value: qualitySliderValue.value,
|
||||
min: qualityMinValue,
|
||||
max: qualityMaxValue,
|
||||
divisions: 90,
|
||||
onChanged: (double value) {
|
||||
qualitySliderValue.value = value;
|
||||
qualityRxReplay.add(value);
|
||||
},
|
||||
),
|
||||
SizedBox(
|
||||
width: 90,
|
||||
child: Obx(() => Text(
|
||||
'${qualitySliderValue.value.round()}% Bitrate',
|
||||
style: const TextStyle(fontSize: 15),
|
||||
)))
|
||||
],
|
||||
));
|
||||
// fps
|
||||
final fpsOption =
|
||||
await bind.sessionGetOption(id: widget.id, arg: 'custom-fps');
|
||||
fpsInitValue =
|
||||
fpsOption == null ? 30 : double.tryParse(fpsOption) ?? 30;
|
||||
if (fpsInitValue < 10 || fpsInitValue > 120) {
|
||||
fpsInitValue = 30;
|
||||
}
|
||||
final RxDouble fpsSliderValue = RxDouble(fpsInitValue);
|
||||
final fpsRxReplay = rxdart.ReplaySubject<double>();
|
||||
fpsRxReplay
|
||||
.throttleTime(const Duration(milliseconds: 1000),
|
||||
trailing: true, leading: false)
|
||||
.listen((double v) {
|
||||
() async {
|
||||
await setCustomValues(fps: v);
|
||||
}();
|
||||
});
|
||||
final content = Row(
|
||||
children: [
|
||||
slider,
|
||||
SizedBox(
|
||||
width: 90,
|
||||
child: Obx(() => Text(
|
||||
'${sliderValue.value.round()}% Bitrate',
|
||||
bool? direct;
|
||||
try {
|
||||
direct = ConnectionTypeState.find(widget.id).direct.value ==
|
||||
ConnectionType.strDirect;
|
||||
} catch (_) {}
|
||||
final fpsSlider = Offstage(
|
||||
offstage:
|
||||
(await bind.mainIsUsingPublicServer() && direct != true) ||
|
||||
(await bind.versionToNumber(
|
||||
v: widget.ffi.ffiModel.pi.version) <
|
||||
await bind.versionToNumber(v: '1.2.0')),
|
||||
child: Row(
|
||||
children: [
|
||||
Obx((() => Slider(
|
||||
value: fpsSliderValue.value,
|
||||
min: 10,
|
||||
max: 120,
|
||||
divisions: 22,
|
||||
onChanged: (double value) {
|
||||
fpsSliderValue.value = value;
|
||||
fpsRxReplay.add(value);
|
||||
},
|
||||
))),
|
||||
SizedBox(
|
||||
width: 90,
|
||||
child: Obx(() {
|
||||
final fps = fpsSliderValue.value.round();
|
||||
String text;
|
||||
if (fps < 100) {
|
||||
text = '$fps FPS';
|
||||
} else {
|
||||
text = '$fps FPS';
|
||||
}
|
||||
return Text(
|
||||
text,
|
||||
style: const TextStyle(fontSize: 15),
|
||||
)))
|
||||
],
|
||||
);
|
||||
}))
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
final content = Column(
|
||||
children: [qualitySlider, fpsSlider],
|
||||
);
|
||||
msgBoxCommon(widget.ffi.dialogManager, 'Custom Image Quality',
|
||||
content, [btnCancel]);
|
||||
content, [btnClose]);
|
||||
}
|
||||
},
|
||||
padding: padding,
|
||||
|
@ -480,6 +480,7 @@ message OptionMessage {
|
||||
BoolOption disable_clipboard = 8;
|
||||
BoolOption enable_file_transfer = 9;
|
||||
VideoCodecState video_codec_state = 10;
|
||||
int32 custom_fps = 11;
|
||||
}
|
||||
|
||||
message TestDelay {
|
||||
|
@ -1092,7 +1092,12 @@ impl LoginConfigHandler {
|
||||
n += 1;
|
||||
} else if q == "custom" {
|
||||
let config = PeerConfig::load(&self.id);
|
||||
msg.custom_image_quality = config.custom_image_quality[0] << 8;
|
||||
let quality = if config.custom_image_quality.is_empty() {
|
||||
50
|
||||
} else {
|
||||
config.custom_image_quality[0]
|
||||
};
|
||||
msg.custom_image_quality = quality << 8;
|
||||
n += 1;
|
||||
}
|
||||
if self.get_toggle_option("show-remote-cursor") {
|
||||
@ -1253,6 +1258,27 @@ impl LoginConfigHandler {
|
||||
res
|
||||
}
|
||||
|
||||
/// Create a [`Message`] for saving custom fps.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `fps` - The given fps.
|
||||
pub fn set_custom_fps(&mut self, fps: i32) -> Message {
|
||||
let mut misc = Misc::new();
|
||||
misc.set_option(OptionMessage {
|
||||
custom_fps: fps,
|
||||
..Default::default()
|
||||
});
|
||||
let mut msg_out = Message::new();
|
||||
msg_out.set_misc(misc);
|
||||
let mut config = self.load_config();
|
||||
config
|
||||
.options
|
||||
.insert("custom-fps".to_owned(), fps.to_string());
|
||||
self.save_config(config);
|
||||
msg_out
|
||||
}
|
||||
|
||||
pub fn get_option(&self, k: &str) -> String {
|
||||
if let Some(v) = self.config.options.get(k) {
|
||||
v.clone()
|
||||
|
@ -186,6 +186,12 @@ pub fn session_set_custom_image_quality(id: String, value: i32) {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn session_set_custom_fps(id: String, fps: i32) {
|
||||
if let Some(session) = SESSIONS.write().unwrap().get_mut(&id) {
|
||||
session.set_custom_fps(fps);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn session_lock_screen(id: String) {
|
||||
if let Some(session) = SESSIONS.read().unwrap().get(&id) {
|
||||
session.lock_screen();
|
||||
@ -1000,6 +1006,10 @@ pub fn query_onlines(ids: Vec<String>) {
|
||||
crate::rendezvous_mediator::query_online_states(ids, handle_query_onlines)
|
||||
}
|
||||
|
||||
pub fn version_to_number(v: String) -> i64 {
|
||||
hbb_common::get_version_number(&v)
|
||||
}
|
||||
|
||||
pub fn main_is_installed() -> SyncReturn<bool> {
|
||||
SyncReturn(is_installed())
|
||||
}
|
||||
|
@ -1316,16 +1316,25 @@ impl Connection {
|
||||
if o.custom_image_quality > 0 {
|
||||
image_quality = o.custom_image_quality;
|
||||
} else {
|
||||
image_quality = ImageQuality::Balanced.value();
|
||||
image_quality = -1;
|
||||
}
|
||||
} else {
|
||||
image_quality = q.value();
|
||||
}
|
||||
if image_quality > 0 {
|
||||
video_service::VIDEO_QOS
|
||||
.lock()
|
||||
.unwrap()
|
||||
.update_image_quality(image_quality);
|
||||
}
|
||||
}
|
||||
if o.custom_fps > 0 {
|
||||
video_service::VIDEO_QOS
|
||||
.lock()
|
||||
.unwrap()
|
||||
.update_image_quality(image_quality);
|
||||
.update_user_fps(o.custom_fps as _);
|
||||
}
|
||||
|
||||
if let Ok(q) = o.lock_after_session_end.enum_value() {
|
||||
if q != BoolOption::NotSet {
|
||||
self.lock_after_session_end = q == BoolOption::Yes;
|
||||
|
@ -1,6 +1,8 @@
|
||||
use super::*;
|
||||
use std::time::Duration;
|
||||
const FPS: u8 = 30;
|
||||
const MIN_FPS: u8 = 10;
|
||||
const MAX_FPS: u8 = 120;
|
||||
trait Percent {
|
||||
fn as_percent(&self) -> u32;
|
||||
}
|
||||
@ -23,7 +25,8 @@ pub struct VideoQoS {
|
||||
current_image_quality: u32,
|
||||
enable_abr: bool,
|
||||
pub current_delay: u32,
|
||||
pub fps: u8, // abr
|
||||
pub fps: u8, // abr
|
||||
pub user_fps: u8,
|
||||
pub target_bitrate: u32, // abr
|
||||
updated: bool,
|
||||
state: DelayState,
|
||||
@ -56,6 +59,7 @@ impl Default for VideoQoS {
|
||||
fn default() -> Self {
|
||||
VideoQoS {
|
||||
fps: FPS,
|
||||
user_fps: FPS,
|
||||
user_image_quality: ImageQuality::Balanced.as_percent(),
|
||||
current_image_quality: ImageQuality::Balanced.as_percent(),
|
||||
enable_abr: false,
|
||||
@ -80,12 +84,19 @@ impl VideoQoS {
|
||||
}
|
||||
|
||||
pub fn spf(&mut self) -> Duration {
|
||||
if self.fps <= 0 {
|
||||
self.fps = FPS;
|
||||
if self.fps < MIN_FPS || self.fps > MAX_FPS {
|
||||
self.fps = self.base_fps();
|
||||
}
|
||||
Duration::from_secs_f32(1. / (self.fps as f32))
|
||||
}
|
||||
|
||||
fn base_fps(&self) -> u8 {
|
||||
if self.user_fps >= MIN_FPS && self.user_fps <= MAX_FPS {
|
||||
return self.user_fps;
|
||||
}
|
||||
return FPS;
|
||||
}
|
||||
|
||||
// update_network_delay periodically
|
||||
// decrease the bitrate when the delay gets bigger
|
||||
pub fn update_network_delay(&mut self, delay: u32) {
|
||||
@ -124,19 +135,19 @@ impl VideoQoS {
|
||||
fn refresh_quality(&mut self) {
|
||||
match self.state {
|
||||
DelayState::Normal => {
|
||||
self.fps = FPS;
|
||||
self.fps = self.base_fps();
|
||||
self.current_image_quality = self.user_image_quality;
|
||||
}
|
||||
DelayState::LowDelay => {
|
||||
self.fps = FPS;
|
||||
self.fps = self.base_fps();
|
||||
self.current_image_quality = std::cmp::min(self.user_image_quality, 50);
|
||||
}
|
||||
DelayState::HighDelay => {
|
||||
self.fps = FPS / 2;
|
||||
self.fps = self.base_fps() / 2;
|
||||
self.current_image_quality = std::cmp::min(self.user_image_quality, 25);
|
||||
}
|
||||
DelayState::Broken => {
|
||||
self.fps = FPS / 4;
|
||||
self.fps = self.base_fps() / 4;
|
||||
self.current_image_quality = 10;
|
||||
}
|
||||
}
|
||||
@ -146,6 +157,14 @@ impl VideoQoS {
|
||||
|
||||
// handle image_quality change from peer
|
||||
pub fn update_image_quality(&mut self, image_quality: i32) {
|
||||
if image_quality == ImageQuality::Low.value()
|
||||
|| image_quality == ImageQuality::Balanced.value()
|
||||
|| image_quality == ImageQuality::Best.value()
|
||||
{
|
||||
// not custom
|
||||
self.user_fps = FPS;
|
||||
self.fps = FPS;
|
||||
}
|
||||
let image_quality = Self::convert_quality(image_quality) as _;
|
||||
if self.current_image_quality != image_quality {
|
||||
self.current_image_quality = image_quality;
|
||||
@ -156,6 +175,16 @@ impl VideoQoS {
|
||||
self.user_image_quality = self.current_image_quality;
|
||||
}
|
||||
|
||||
pub fn update_user_fps(&mut self, fps: u8) {
|
||||
if fps >= MIN_FPS && fps <= MAX_FPS {
|
||||
if self.user_fps != fps {
|
||||
self.user_fps = fps;
|
||||
self.fps = fps;
|
||||
self.updated = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn generate_bitrate(&mut self) -> ResultType<u32> {
|
||||
// https://www.nvidia.com/en-us/geforce/guides/broadcasting-guide/
|
||||
if self.width == 0 || self.height == 0 {
|
||||
|
@ -134,6 +134,11 @@ impl<T: InvokeUiSession> Session<T> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_custom_fps(&mut self, custom_fps: i32) {
|
||||
let msg = self.lc.write().unwrap().set_custom_fps(custom_fps);
|
||||
self.send(Data::Message(msg));
|
||||
}
|
||||
|
||||
pub fn get_remember(&self) -> bool {
|
||||
self.lc.read().unwrap().remember
|
||||
}
|
||||
@ -1181,7 +1186,12 @@ impl<T: InvokeUiSession> Interface for Session<T> {
|
||||
if self.is_file_transfer() {
|
||||
self.close_success();
|
||||
} else if !self.is_port_forward() {
|
||||
self.msgbox("success", "Successful", "Connected, waiting for image...", "");
|
||||
self.msgbox(
|
||||
"success",
|
||||
"Successful",
|
||||
"Connected, waiting for image...",
|
||||
"",
|
||||
);
|
||||
}
|
||||
#[cfg(windows)]
|
||||
{
|
||||
|
Loading…
x
Reference in New Issue
Block a user