fix client side record
Signed-off-by: 21pages <pages21@163.com>
This commit is contained in:
parent
510cffb305
commit
7a5bc864fa
@ -224,11 +224,8 @@ List<TTextMenu> toolbarControls(BuildContext context, String id, FFI ffi) {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
// record
|
// record
|
||||||
var codecFormat = ffi.qualityMonitorModel.data.codecFormat;
|
|
||||||
if (!isDesktop &&
|
if (!isDesktop &&
|
||||||
(ffi.recordingModel.start ||
|
(ffi.recordingModel.start || (perms["recording"] != false))) {
|
||||||
(perms["recording"] != false &&
|
|
||||||
(codecFormat == "VP8" || codecFormat == "VP9")))) {
|
|
||||||
v.add(TTextMenu(
|
v.add(TTextMenu(
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
|
|||||||
@ -677,7 +677,8 @@ class _ImagePaintState extends State<ImagePaint> {
|
|||||||
} else {
|
} else {
|
||||||
final key = cache.updateGetKey(scale);
|
final key = cache.updateGetKey(scale);
|
||||||
if (!cursor.cachedKeys.contains(key)) {
|
if (!cursor.cachedKeys.contains(key)) {
|
||||||
debugPrint("Register custom cursor with key $key (${cache.hotx},${cache.hoty})");
|
debugPrint(
|
||||||
|
"Register custom cursor with key $key (${cache.hotx},${cache.hoty})");
|
||||||
// [Safety]
|
// [Safety]
|
||||||
// It's ok to call async registerCursor in current synchronous context,
|
// It's ok to call async registerCursor in current synchronous context,
|
||||||
// because activating the cursor is also an async call and will always
|
// because activating the cursor is also an async call and will always
|
||||||
|
|||||||
@ -1370,7 +1370,8 @@ class _ResolutionsMenuState extends State<_ResolutionsMenu> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (final r in resolutions) {
|
for (final r in resolutions) {
|
||||||
if (r.width == _localResolution!.width && r.height == _localResolution!.height) {
|
if (r.width == _localResolution!.width &&
|
||||||
|
r.height == _localResolution!.height) {
|
||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1652,7 +1653,8 @@ class _RecordMenu extends StatelessWidget {
|
|||||||
var ffi = Provider.of<FfiModel>(context);
|
var ffi = Provider.of<FfiModel>(context);
|
||||||
var recordingModel = Provider.of<RecordingModel>(context);
|
var recordingModel = Provider.of<RecordingModel>(context);
|
||||||
final visible =
|
final visible =
|
||||||
recordingModel.start || ffi.permissions['recording'] != false;
|
(recordingModel.start || ffi.permissions['recording'] != false) &&
|
||||||
|
ffi.pi.currentDisplay != kAllDisplayValue;
|
||||||
if (!visible) return Offstage();
|
if (!visible) return Offstage();
|
||||||
return _IconMenuButton(
|
return _IconMenuButton(
|
||||||
assetName: 'assets/rec.svg',
|
assetName: 'assets/rec.svg',
|
||||||
|
|||||||
@ -763,7 +763,9 @@ void showOptions(
|
|||||||
children.add(InkWell(
|
children.add(InkWell(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
if (i == cur) return;
|
if (i == cur) return;
|
||||||
bind.sessionSwitchDisplay(sessionId: gFFI.sessionId, value: Int32List.fromList([i]));
|
gFFI.recordingModel.onClose();
|
||||||
|
bind.sessionSwitchDisplay(
|
||||||
|
sessionId: gFFI.sessionId, value: Int32List.fromList([i]));
|
||||||
gFFI.dialogManager.dismissAll();
|
gFFI.dialogManager.dismissAll();
|
||||||
},
|
},
|
||||||
child: Ink(
|
child: Ink(
|
||||||
|
|||||||
@ -860,6 +860,8 @@ class FfiModel with ChangeNotifier {
|
|||||||
|
|
||||||
// Directly switch to the new display without waiting for the response.
|
// Directly switch to the new display without waiting for the response.
|
||||||
switchToNewDisplay(int display, SessionID sessionId, String peerId) {
|
switchToNewDisplay(int display, SessionID sessionId, String peerId) {
|
||||||
|
// VideoHandler creation is upon when video frames are received, so either caching commands(don't know next width/height) or stopping recording when switching displays.
|
||||||
|
parent.target?.recordingModel.onClose();
|
||||||
// no need to wait for the response
|
// no need to wait for the response
|
||||||
pi.currentDisplay = display;
|
pi.currentDisplay = display;
|
||||||
updateCurDisplay(sessionId);
|
updateCurDisplay(sessionId);
|
||||||
@ -868,7 +870,6 @@ class FfiModel with ChangeNotifier {
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
//
|
//
|
||||||
}
|
}
|
||||||
parent.target?.recordingModel.onSwitchDisplay();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
updateBlockInputState(Map<String, dynamic> evt, String peerId) {
|
updateBlockInputState(Map<String, dynamic> evt, String peerId) {
|
||||||
@ -1850,56 +1851,66 @@ class RecordingModel with ChangeNotifier {
|
|||||||
int? width = parent.target?.canvasModel.getDisplayWidth();
|
int? width = parent.target?.canvasModel.getDisplayWidth();
|
||||||
int? height = parent.target?.canvasModel.getDisplayHeight();
|
int? height = parent.target?.canvasModel.getDisplayHeight();
|
||||||
if (sessionId == null || width == null || height == null) return;
|
if (sessionId == null || width == null || height == null) return;
|
||||||
final currentDisplay = parent.target?.ffiModel.pi.currentDisplay;
|
final pi = parent.target?.ffiModel.pi;
|
||||||
if (currentDisplay != kAllDisplayValue) {
|
if (pi == null) return;
|
||||||
|
final currentDisplay = pi.currentDisplay;
|
||||||
|
if (currentDisplay == kAllDisplayValue) return;
|
||||||
bind.sessionRecordScreen(
|
bind.sessionRecordScreen(
|
||||||
sessionId: sessionId,
|
sessionId: sessionId,
|
||||||
start: true,
|
start: true,
|
||||||
display: currentDisplay!,
|
display: currentDisplay,
|
||||||
width: width,
|
width: width,
|
||||||
height: height);
|
height: height);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
toggle() async {
|
toggle() async {
|
||||||
if (isIOS) return;
|
if (isIOS) return;
|
||||||
final sessionId = parent.target?.sessionId;
|
final sessionId = parent.target?.sessionId;
|
||||||
if (sessionId == null) return;
|
if (sessionId == null) return;
|
||||||
|
final pi = parent.target?.ffiModel.pi;
|
||||||
|
if (pi == null) return;
|
||||||
|
final currentDisplay = pi.currentDisplay;
|
||||||
|
if (currentDisplay == kAllDisplayValue) return;
|
||||||
_start = !_start;
|
_start = !_start;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
await bind.sessionRecordStatus(sessionId: sessionId, status: _start);
|
await _sendStatusMessage(sessionId, pi, _start);
|
||||||
if (_start) {
|
if (_start) {
|
||||||
final pi = parent.target?.ffiModel.pi;
|
|
||||||
if (pi != null) {
|
|
||||||
sessionRefreshVideo(sessionId, pi);
|
sessionRefreshVideo(sessionId, pi);
|
||||||
|
if (versionCmp(pi.version, '1.2.4') >= 0) {
|
||||||
|
// will not receive SwitchDisplay since 1.2.4
|
||||||
|
onSwitchDisplay();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
final currentDisplay = parent.target?.ffiModel.pi.currentDisplay;
|
|
||||||
if (currentDisplay != kAllDisplayValue) {
|
|
||||||
bind.sessionRecordScreen(
|
bind.sessionRecordScreen(
|
||||||
sessionId: sessionId,
|
sessionId: sessionId,
|
||||||
start: false,
|
start: false,
|
||||||
display: currentDisplay!,
|
display: currentDisplay,
|
||||||
width: 0,
|
width: 0,
|
||||||
height: 0);
|
height: 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
onClose() {
|
onClose() async {
|
||||||
if (isIOS) return;
|
if (isIOS) return;
|
||||||
final sessionId = parent.target?.sessionId;
|
final sessionId = parent.target?.sessionId;
|
||||||
if (sessionId == null) return;
|
if (sessionId == null) return;
|
||||||
|
if (!_start) return;
|
||||||
_start = false;
|
_start = false;
|
||||||
final currentDisplay = parent.target?.ffiModel.pi.currentDisplay;
|
final pi = parent.target?.ffiModel.pi;
|
||||||
if (currentDisplay != kAllDisplayValue) {
|
if (pi == null) return;
|
||||||
|
final currentDisplay = pi.currentDisplay;
|
||||||
|
if (currentDisplay == kAllDisplayValue) return;
|
||||||
|
await _sendStatusMessage(sessionId, pi, false);
|
||||||
bind.sessionRecordScreen(
|
bind.sessionRecordScreen(
|
||||||
sessionId: sessionId,
|
sessionId: sessionId,
|
||||||
start: false,
|
start: false,
|
||||||
display: currentDisplay!,
|
display: currentDisplay,
|
||||||
width: 0,
|
width: 0,
|
||||||
height: 0);
|
height: 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_sendStatusMessage(SessionID sessionId, PeerInfo pi, bool status) async {
|
||||||
|
await bind.sessionRecordStatus(sessionId: sessionId, status: status);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -49,8 +49,8 @@ impl RecorderContext {
|
|||||||
}
|
}
|
||||||
let file = if self.server { "s" } else { "c" }.to_string()
|
let file = if self.server { "s" } else { "c" }.to_string()
|
||||||
+ &self.id.clone()
|
+ &self.id.clone()
|
||||||
+ &chrono::Local::now().format("_%Y%m%d%H%M%S_").to_string()
|
+ &chrono::Local::now().format("_%Y%m%d%H%M%S%3f_").to_string()
|
||||||
+ &self.format.to_string()
|
+ &self.format.to_string().to_lowercase()
|
||||||
+ if self.format == CodecFormat::VP9
|
+ if self.format == CodecFormat::VP9
|
||||||
|| self.format == CodecFormat::VP8
|
|| self.format == CodecFormat::VP8
|
||||||
|| self.format == CodecFormat::AV1
|
|| self.format == CodecFormat::AV1
|
||||||
@ -86,6 +86,7 @@ pub enum RecordState {
|
|||||||
pub struct Recorder {
|
pub struct Recorder {
|
||||||
pub inner: Box<dyn RecorderApi>,
|
pub inner: Box<dyn RecorderApi>,
|
||||||
ctx: RecorderContext,
|
ctx: RecorderContext,
|
||||||
|
pts: Option<i64>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Deref for Recorder {
|
impl Deref for Recorder {
|
||||||
@ -109,11 +110,13 @@ impl Recorder {
|
|||||||
CodecFormat::VP8 | CodecFormat::VP9 | CodecFormat::AV1 => Recorder {
|
CodecFormat::VP8 | CodecFormat::VP9 | CodecFormat::AV1 => Recorder {
|
||||||
inner: Box::new(WebmRecorder::new(ctx.clone())?),
|
inner: Box::new(WebmRecorder::new(ctx.clone())?),
|
||||||
ctx,
|
ctx,
|
||||||
|
pts: None,
|
||||||
},
|
},
|
||||||
#[cfg(feature = "hwcodec")]
|
#[cfg(feature = "hwcodec")]
|
||||||
_ => Recorder {
|
_ => Recorder {
|
||||||
inner: Box::new(HwRecorder::new(ctx.clone())?),
|
inner: Box::new(HwRecorder::new(ctx.clone())?),
|
||||||
ctx,
|
ctx,
|
||||||
|
pts: None,
|
||||||
},
|
},
|
||||||
#[cfg(not(feature = "hwcodec"))]
|
#[cfg(not(feature = "hwcodec"))]
|
||||||
_ => bail!("unsupported codec type"),
|
_ => bail!("unsupported codec type"),
|
||||||
@ -134,6 +137,7 @@ impl Recorder {
|
|||||||
_ => bail!("unsupported codec type"),
|
_ => bail!("unsupported codec type"),
|
||||||
};
|
};
|
||||||
self.ctx = ctx;
|
self.ctx = ctx;
|
||||||
|
self.pts = None;
|
||||||
self.send_state(RecordState::NewFile(self.ctx.filename.clone()));
|
self.send_state(RecordState::NewFile(self.ctx.filename.clone()));
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -155,7 +159,10 @@ impl Recorder {
|
|||||||
..self.ctx.clone()
|
..self.ctx.clone()
|
||||||
})?;
|
})?;
|
||||||
}
|
}
|
||||||
vp8s.frames.iter().map(|f| self.write_video(f)).count();
|
for f in vp8s.frames.iter() {
|
||||||
|
self.check_pts(f.pts)?;
|
||||||
|
self.write_video(f);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
video_frame::Union::Vp9s(vp9s) => {
|
video_frame::Union::Vp9s(vp9s) => {
|
||||||
if self.ctx.format != CodecFormat::VP9 {
|
if self.ctx.format != CodecFormat::VP9 {
|
||||||
@ -164,7 +171,10 @@ impl Recorder {
|
|||||||
..self.ctx.clone()
|
..self.ctx.clone()
|
||||||
})?;
|
})?;
|
||||||
}
|
}
|
||||||
vp9s.frames.iter().map(|f| self.write_video(f)).count();
|
for f in vp9s.frames.iter() {
|
||||||
|
self.check_pts(f.pts)?;
|
||||||
|
self.write_video(f);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
video_frame::Union::Av1s(av1s) => {
|
video_frame::Union::Av1s(av1s) => {
|
||||||
if self.ctx.format != CodecFormat::AV1 {
|
if self.ctx.format != CodecFormat::AV1 {
|
||||||
@ -173,7 +183,10 @@ impl Recorder {
|
|||||||
..self.ctx.clone()
|
..self.ctx.clone()
|
||||||
})?;
|
})?;
|
||||||
}
|
}
|
||||||
av1s.frames.iter().map(|f| self.write_video(f)).count();
|
for f in av1s.frames.iter() {
|
||||||
|
self.check_pts(f.pts)?;
|
||||||
|
self.write_video(f);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
#[cfg(feature = "hwcodec")]
|
#[cfg(feature = "hwcodec")]
|
||||||
video_frame::Union::H264s(h264s) => {
|
video_frame::Union::H264s(h264s) => {
|
||||||
@ -183,8 +196,9 @@ impl Recorder {
|
|||||||
..self.ctx.clone()
|
..self.ctx.clone()
|
||||||
})?;
|
})?;
|
||||||
}
|
}
|
||||||
if self.ctx.format == CodecFormat::H264 {
|
for f in h264s.frames.iter() {
|
||||||
h264s.frames.iter().map(|f| self.write_video(f)).count();
|
self.check_pts(f.pts)?;
|
||||||
|
self.write_video(f);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#[cfg(feature = "hwcodec")]
|
#[cfg(feature = "hwcodec")]
|
||||||
@ -195,8 +209,9 @@ impl Recorder {
|
|||||||
..self.ctx.clone()
|
..self.ctx.clone()
|
||||||
})?;
|
})?;
|
||||||
}
|
}
|
||||||
if self.ctx.format == CodecFormat::H265 {
|
for f in h265s.frames.iter() {
|
||||||
h265s.frames.iter().map(|f| self.write_video(f)).count();
|
self.check_pts(f.pts)?;
|
||||||
|
self.write_video(f);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => bail!("unsupported frame type"),
|
_ => bail!("unsupported frame type"),
|
||||||
@ -205,6 +220,17 @@ impl Recorder {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn check_pts(&mut self, pts: i64) -> ResultType<()> {
|
||||||
|
// https://stackoverflow.com/questions/76379101/how-to-create-one-playable-webm-file-from-two-different-video-tracks-with-same-c
|
||||||
|
let old_pts = self.pts;
|
||||||
|
self.pts = Some(pts);
|
||||||
|
if old_pts.clone().unwrap_or_default() > pts {
|
||||||
|
log::info!("pts {:?}->{}, change record filename", old_pts, pts);
|
||||||
|
self.change(self.ctx.clone())?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
fn send_state(&self, state: RecordState) {
|
fn send_state(&self, state: RecordState) {
|
||||||
self.ctx.tx.as_ref().map(|tx| tx.send(state));
|
self.ctx.tx.as_ref().map(|tx| tx.send(state));
|
||||||
}
|
}
|
||||||
@ -250,7 +276,9 @@ impl RecorderApi for WebmRecorder {
|
|||||||
if ctx.format == CodecFormat::AV1 {
|
if ctx.format == CodecFormat::AV1 {
|
||||||
// [129, 8, 12, 0] in 3.6.0, but zero works
|
// [129, 8, 12, 0] in 3.6.0, but zero works
|
||||||
let codec_private = vec![0, 0, 0, 0];
|
let codec_private = vec![0, 0, 0, 0];
|
||||||
webm.set_codec_private(vt.track_number(), &codec_private);
|
if !webm.set_codec_private(vt.track_number(), &codec_private) {
|
||||||
|
bail!("Failed to set codec private");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Ok(WebmRecorder {
|
Ok(WebmRecorder {
|
||||||
vt,
|
vt,
|
||||||
|
|||||||
@ -999,16 +999,19 @@ pub struct VideoHandler {
|
|||||||
pub rgb: ImageRgb,
|
pub rgb: ImageRgb,
|
||||||
recorder: Arc<Mutex<Option<Recorder>>>,
|
recorder: Arc<Mutex<Option<Recorder>>>,
|
||||||
record: bool,
|
record: bool,
|
||||||
|
_display: usize, // useful for debug
|
||||||
}
|
}
|
||||||
|
|
||||||
impl VideoHandler {
|
impl VideoHandler {
|
||||||
/// Create a new video handler.
|
/// Create a new video handler.
|
||||||
pub fn new() -> Self {
|
pub fn new(_display: usize) -> Self {
|
||||||
|
log::info!("new video handler for display #{_display}");
|
||||||
VideoHandler {
|
VideoHandler {
|
||||||
decoder: Decoder::new(),
|
decoder: Decoder::new(),
|
||||||
rgb: ImageRgb::new(ImageFormat::ARGB, crate::DST_STRIDE_RGBA),
|
rgb: ImageRgb::new(ImageFormat::ARGB, crate::DST_STRIDE_RGBA),
|
||||||
recorder: Default::default(),
|
recorder: Default::default(),
|
||||||
record: false,
|
record: false,
|
||||||
|
_display,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1900,7 +1903,7 @@ where
|
|||||||
if handler_controller_map.len() <= display {
|
if handler_controller_map.len() <= display {
|
||||||
for _i in handler_controller_map.len()..=display {
|
for _i in handler_controller_map.len()..=display {
|
||||||
handler_controller_map.push(VideoHandlerController {
|
handler_controller_map.push(VideoHandlerController {
|
||||||
handler: VideoHandler::new(),
|
handler: VideoHandler::new(_i),
|
||||||
count: 0,
|
count: 0,
|
||||||
duration: std::time::Duration::ZERO,
|
duration: std::time::Duration::ZERO,
|
||||||
skip_beginning: 0,
|
skip_beginning: 0,
|
||||||
@ -1960,6 +1963,7 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
MediaData::RecordScreen(start, display, w, h, id) => {
|
MediaData::RecordScreen(start, display, w, h, id) => {
|
||||||
|
log::info!("record screen command: start:{start}, display:{display}");
|
||||||
if handler_controller_map.len() == 1 {
|
if handler_controller_map.len() == 1 {
|
||||||
// Compatible with the sciter version(single ui session).
|
// Compatible with the sciter version(single ui session).
|
||||||
// For the sciter version, there're no multi-ui-sessions for one connection.
|
// For the sciter version, there're no multi-ui-sessions for one connection.
|
||||||
|
|||||||
@ -2104,7 +2104,7 @@ impl Connection {
|
|||||||
display,
|
display,
|
||||||
video_service::OPTION_REFRESH,
|
video_service::OPTION_REFRESH,
|
||||||
super::service::SERVICE_OPTION_VALUE_TRUE,
|
super::service::SERVICE_OPTION_VALUE_TRUE,
|
||||||
)
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2145,6 +2145,7 @@ impl Connection {
|
|||||||
|
|
||||||
// Send display changed message.
|
// Send display changed message.
|
||||||
// For compatibility with old versions ( < 1.2.4 ).
|
// For compatibility with old versions ( < 1.2.4 ).
|
||||||
|
// sciter need it in new version
|
||||||
if let Some(msg_out) = video_service::make_display_changed_msg(self.display_idx, None) {
|
if let Some(msg_out) = video_service::make_display_changed_msg(self.display_idx, None) {
|
||||||
self.send(msg_out).await;
|
self.send(msg_out).await;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -299,14 +299,22 @@ class Header: Reactor.Component {
|
|||||||
header.update();
|
header.update();
|
||||||
handler.record_status(recording);
|
handler.record_status(recording);
|
||||||
// 0 is just a dummy value. It will be ignored by the handler.
|
// 0 is just a dummy value. It will be ignored by the handler.
|
||||||
if (recording)
|
if (recording) {
|
||||||
handler.refresh_video(0);
|
handler.refresh_video(0);
|
||||||
else
|
if (handler.version_cmp(pi.version, '1.2.4') >= 0) handler.record_screen(recording, pi.current_display, display_width, display_height);
|
||||||
handler.record_screen(false, 0, display_width, display_height);
|
}
|
||||||
|
else {
|
||||||
|
handler.record_screen(recording, pi.current_display, display_width, display_height);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
event click $(#screen) (_, me) {
|
event click $(#screen) (_, me) {
|
||||||
if (pi.current_display == me.index) return;
|
if (pi.current_display == me.index) return;
|
||||||
|
if (recording) {
|
||||||
|
recording = false;
|
||||||
|
handler.record_screen(false, pi.current_display, display_width, display_height);
|
||||||
|
handler.record_status(false);
|
||||||
|
}
|
||||||
handler.switch_display(me.index);
|
handler.switch_display(me.index);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -243,6 +243,7 @@ impl InvokeUiSession for SciterHandler {
|
|||||||
pi_sciter.set_item("sas_enabled", pi.sas_enabled);
|
pi_sciter.set_item("sas_enabled", pi.sas_enabled);
|
||||||
pi_sciter.set_item("displays", Self::make_displays_array(&pi.displays));
|
pi_sciter.set_item("displays", Self::make_displays_array(&pi.displays));
|
||||||
pi_sciter.set_item("current_display", pi.current_display);
|
pi_sciter.set_item("current_display", pi.current_display);
|
||||||
|
pi_sciter.set_item("version", pi.version.clone());
|
||||||
self.call("updatePi", &make_args!(pi_sciter));
|
self.call("updatePi", &make_args!(pi_sciter));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -469,6 +470,7 @@ impl sciter::EventHandler for SciterSession {
|
|||||||
fn restart_remote_device();
|
fn restart_remote_device();
|
||||||
fn request_voice_call();
|
fn request_voice_call();
|
||||||
fn close_voice_call();
|
fn close_voice_call();
|
||||||
|
fn version_cmp(String, String);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -757,6 +759,10 @@ impl SciterSession {
|
|||||||
log::error!("Failed to spawn IP tunneling: {}", err);
|
log::error!("Failed to spawn IP tunneling: {}", err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn version_cmp(&self, v1: String, v2: String) -> i32 {
|
||||||
|
(hbb_common::get_version_number(&v1) - hbb_common::get_version_number(&v2)) as i32
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn make_fd(id: i32, entries: &Vec<FileEntry>, only_count: bool) -> Value {
|
pub fn make_fd(id: i32, entries: &Vec<FileEntry>, only_count: bool) -> Value {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user