mirror of
https://github.com/weyne85/rustdesk.git
synced 2025-10-29 17:00:05 +00:00
commit
4a03b3d7d9
10
Cargo.lock
generated
10
Cargo.lock
generated
@ -6712,18 +6712,16 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "webm"
|
name = "webm"
|
||||||
version = "1.0.2"
|
version = "1.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "git+https://github.com/21pages/rust-webm#d2c4d3ac133c7b0e4c0f656da710b48391981e64"
|
||||||
checksum = "ecb047148a12ef1fd8ab26302bca7e82036f005c3073b48e17cc1b44ec577136"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"webm-sys",
|
"webm-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "webm-sys"
|
name = "webm-sys"
|
||||||
version = "1.0.3"
|
version = "1.0.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "git+https://github.com/21pages/rust-webm#d2c4d3ac133c7b0e4c0f656da710b48391981e64"
|
||||||
checksum = "0ded6ec82ccf51fe265b0b2b1579cac839574ed910c17baac58e807f8a9de7f3"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cc",
|
"cc",
|
||||||
]
|
]
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -478,7 +478,7 @@ class _RemoteToolbarState extends State<RemoteToolbar> {
|
|||||||
toolbarItems.add(_ChatMenu(id: widget.id, ffi: widget.ffi));
|
toolbarItems.add(_ChatMenu(id: widget.id, ffi: widget.ffi));
|
||||||
toolbarItems.add(_VoiceCallMenu(id: widget.id, ffi: widget.ffi));
|
toolbarItems.add(_VoiceCallMenu(id: widget.id, ffi: widget.ffi));
|
||||||
}
|
}
|
||||||
toolbarItems.add(_RecordMenu(ffi: widget.ffi));
|
toolbarItems.add(_RecordMenu());
|
||||||
toolbarItems.add(_CloseMenu(id: widget.id, ffi: widget.ffi));
|
toolbarItems.add(_CloseMenu(id: widget.id, ffi: widget.ffi));
|
||||||
return Column(
|
return Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
@ -1646,17 +1646,17 @@ class _VoiceCallMenu extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _RecordMenu extends StatelessWidget {
|
class _RecordMenu extends StatelessWidget {
|
||||||
final FFI ffi;
|
const _RecordMenu({Key? key}) : super(key: key);
|
||||||
const _RecordMenu({Key? key, required this.ffi}) : super(key: key);
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
var ffiModel = 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 || ffiModel.permissions['recording'] != false;
|
(recordingModel.start || ffi.permissions['recording'] != false) &&
|
||||||
|
ffi.pi.currentDisplay != kAllDisplayValue;
|
||||||
if (!visible) return Offstage();
|
if (!visible) return Offstage();
|
||||||
final menuButton = _IconMenuButton(
|
return _IconMenuButton(
|
||||||
assetName: 'assets/rec.svg',
|
assetName: 'assets/rec.svg',
|
||||||
tooltip: recordingModel.start
|
tooltip: recordingModel.start
|
||||||
? 'Stop session recording'
|
? 'Stop session recording'
|
||||||
@ -1669,14 +1669,6 @@ class _RecordMenu extends StatelessWidget {
|
|||||||
? _ToolbarTheme.hoverRedColor
|
? _ToolbarTheme.hoverRedColor
|
||||||
: _ToolbarTheme.hoverBlueColor,
|
: _ToolbarTheme.hoverBlueColor,
|
||||||
);
|
);
|
||||||
return ChangeNotifierProvider.value(
|
|
||||||
value: ffi.qualityMonitorModel,
|
|
||||||
child: Consumer<QualityMonitorModel>(
|
|
||||||
builder: (context, model, child) => Offstage(
|
|
||||||
// If already started, AV1->Hidden/Stop, Other->Start, same as actual
|
|
||||||
offstage: model.data.codecFormat == 'AV1',
|
|
||||||
child: menuButton,
|
|
||||||
)));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -19,7 +19,7 @@ cfg-if = "1.0"
|
|||||||
num_cpus = "1.15"
|
num_cpus = "1.15"
|
||||||
lazy_static = "1.4"
|
lazy_static = "1.4"
|
||||||
hbb_common = { path = "../hbb_common" }
|
hbb_common = { path = "../hbb_common" }
|
||||||
webm = "1.0"
|
webm = { git = "https://github.com/21pages/rust-webm" }
|
||||||
|
|
||||||
[dependencies.winapi]
|
[dependencies.winapi]
|
||||||
version = "0.3"
|
version = "0.3"
|
||||||
|
|||||||
@ -49,9 +49,12 @@ 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 || self.format == CodecFormat::VP8 {
|
+ if self.format == CodecFormat::VP9
|
||||||
|
|| self.format == CodecFormat::VP8
|
||||||
|
|| self.format == CodecFormat::AV1
|
||||||
|
{
|
||||||
".webm"
|
".webm"
|
||||||
} else {
|
} else {
|
||||||
".mp4"
|
".mp4"
|
||||||
@ -83,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 {
|
||||||
@ -101,19 +105,18 @@ impl DerefMut for Recorder {
|
|||||||
|
|
||||||
impl Recorder {
|
impl Recorder {
|
||||||
pub fn new(mut ctx: RecorderContext) -> ResultType<Self> {
|
pub fn new(mut ctx: RecorderContext) -> ResultType<Self> {
|
||||||
if ctx.format == CodecFormat::AV1 {
|
|
||||||
bail!("not support av1 recording");
|
|
||||||
}
|
|
||||||
ctx.set_filename()?;
|
ctx.set_filename()?;
|
||||||
let recorder = match ctx.format {
|
let recorder = match ctx.format {
|
||||||
CodecFormat::VP8 | CodecFormat::VP9 => 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"),
|
||||||
@ -125,13 +128,16 @@ impl Recorder {
|
|||||||
fn change(&mut self, mut ctx: RecorderContext) -> ResultType<()> {
|
fn change(&mut self, mut ctx: RecorderContext) -> ResultType<()> {
|
||||||
ctx.set_filename()?;
|
ctx.set_filename()?;
|
||||||
self.inner = match ctx.format {
|
self.inner = match ctx.format {
|
||||||
CodecFormat::VP8 | CodecFormat::VP9 => Box::new(WebmRecorder::new(ctx.clone())?),
|
CodecFormat::VP8 | CodecFormat::VP9 | CodecFormat::AV1 => {
|
||||||
|
Box::new(WebmRecorder::new(ctx.clone())?)
|
||||||
|
}
|
||||||
#[cfg(feature = "hwcodec")]
|
#[cfg(feature = "hwcodec")]
|
||||||
_ => Box::new(HwRecorder::new(ctx.clone())?),
|
_ => Box::new(HwRecorder::new(ctx.clone())?),
|
||||||
#[cfg(not(feature = "hwcodec"))]
|
#[cfg(not(feature = "hwcodec"))]
|
||||||
_ => 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(())
|
||||||
}
|
}
|
||||||
@ -153,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 {
|
||||||
@ -162,7 +171,22 @@ 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) => {
|
||||||
|
if self.ctx.format != CodecFormat::AV1 {
|
||||||
|
self.change(RecorderContext {
|
||||||
|
format: CodecFormat::AV1,
|
||||||
|
..self.ctx.clone()
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
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) => {
|
||||||
@ -172,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")]
|
||||||
@ -184,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"),
|
||||||
@ -194,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));
|
||||||
}
|
}
|
||||||
@ -230,10 +267,19 @@ impl RecorderApi for WebmRecorder {
|
|||||||
None,
|
None,
|
||||||
if ctx.format == CodecFormat::VP9 {
|
if ctx.format == CodecFormat::VP9 {
|
||||||
mux::VideoCodecId::VP9
|
mux::VideoCodecId::VP9
|
||||||
} else {
|
} else if ctx.format == CodecFormat::VP8 {
|
||||||
mux::VideoCodecId::VP8
|
mux::VideoCodecId::VP8
|
||||||
|
} else {
|
||||||
|
mux::VideoCodecId::AV1
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
if ctx.format == CodecFormat::AV1 {
|
||||||
|
// [129, 8, 12, 0] in 3.6.0, but zero works
|
||||||
|
let codec_private = vec![0, 0, 0, 0];
|
||||||
|
if !webm.set_codec_private(vt.track_number(), &codec_private) {
|
||||||
|
bail!("Failed to set codec private");
|
||||||
|
}
|
||||||
|
}
|
||||||
Ok(WebmRecorder {
|
Ok(WebmRecorder {
|
||||||
vt,
|
vt,
|
||||||
webm: Some(webm),
|
webm: Some(webm),
|
||||||
|
|||||||
@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -436,8 +436,7 @@ fn run(vs: VideoService) -> ResultType<()> {
|
|||||||
log::info!("init quality={:?}, abr enabled:{}", quality, abr);
|
log::info!("init quality={:?}, abr enabled:{}", quality, abr);
|
||||||
let codec_name = Encoder::negotiated_codec();
|
let codec_name = Encoder::negotiated_codec();
|
||||||
let recorder = get_recorder(c.width, c.height, &codec_name);
|
let recorder = get_recorder(c.width, c.height, &codec_name);
|
||||||
let last_recording =
|
let last_recording = recorder.lock().unwrap().is_some() || video_qos.record();
|
||||||
(recorder.lock().unwrap().is_some() || video_qos.record()) && codec_name != CodecName::AV1;
|
|
||||||
drop(video_qos);
|
drop(video_qos);
|
||||||
let encoder_cfg = get_encoder_config(&c, quality, last_recording);
|
let encoder_cfg = get_encoder_config(&c, quality, last_recording);
|
||||||
|
|
||||||
@ -479,8 +478,7 @@ fn run(vs: VideoService) -> ResultType<()> {
|
|||||||
allow_err!(encoder.set_quality(quality));
|
allow_err!(encoder.set_quality(quality));
|
||||||
video_qos.store_bitrate(encoder.bitrate());
|
video_qos.store_bitrate(encoder.bitrate());
|
||||||
}
|
}
|
||||||
let recording = (recorder.lock().unwrap().is_some() || video_qos.record())
|
let recording = recorder.lock().unwrap().is_some() || video_qos.record();
|
||||||
&& codec_name != CodecName::AV1;
|
|
||||||
if recording != last_recording {
|
if recording != last_recording {
|
||||||
bail!("SWITCH");
|
bail!("SWITCH");
|
||||||
}
|
}
|
||||||
|
|||||||
@ -144,7 +144,7 @@ class Header: Reactor.Component {
|
|||||||
<span #action>{svg_action}</span>
|
<span #action>{svg_action}</span>
|
||||||
<span #display>{svg_display}</span>
|
<span #display>{svg_display}</span>
|
||||||
<span #keyboard>{svg_keyboard}</span>
|
<span #keyboard>{svg_keyboard}</span>
|
||||||
{recording_enabled && qualityMonitorData[4] != "AV1" ? <span #recording>{recording ? svg_recording_on : svg_recording_off}</span> : ""}
|
{recording_enabled ? <span #recording>{recording ? svg_recording_on : svg_recording_off}</span> : ""}
|
||||||
{this.renderKeyboardPop()}
|
{this.renderKeyboardPop()}
|
||||||
{this.renderDisplayPop()}
|
{this.renderDisplayPop()}
|
||||||
{this.renderActionPop()}
|
{this.renderActionPop()}
|
||||||
@ -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 {
|
||||||
|
|||||||
@ -522,7 +522,6 @@ handler.updateQualityStatus = function(speed, fps, delay, bitrate, codec_format)
|
|||||||
bitrate ? qualityMonitorData[3] = bitrate:null;
|
bitrate ? qualityMonitorData[3] = bitrate:null;
|
||||||
codec_format ? qualityMonitorData[4] = codec_format:null;
|
codec_format ? qualityMonitorData[4] = codec_format:null;
|
||||||
qualityMonitor.update();
|
qualityMonitor.update();
|
||||||
if (codec_format) header.update();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
handler.setPermission = function(name, enabled) {
|
handler.setPermission = function(name, enabled) {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user