video record

Signed-off-by: 21pages <pages21@163.com>
This commit is contained in:
21pages
2022-09-15 17:31:28 +08:00
parent f5b7c34c81
commit 9489877c78
48 changed files with 1186 additions and 398 deletions

View File

@@ -1,6 +1,7 @@
import 'dart:convert';
import 'dart:io';
import 'package:file_picker/file_picker.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_hbb/common.dart';
@@ -9,6 +10,7 @@ import 'package:flutter_hbb/models/platform_model.dart';
import 'package:flutter_hbb/models/server_model.dart';
import 'package:get/get.dart';
import 'package:provider/provider.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:url_launcher/url_launcher_string.dart';
import 'package:flutter_hbb/desktop/widgets/scroll_wrapper.dart';
@@ -199,6 +201,7 @@ class _GeneralState extends State<_General> {
abr(),
hwcodec(),
audio(context),
record(context),
_Card(title: 'Language', children: [language()]),
],
).marginOnly(bottom: _kListViewBottomMargin));
@@ -290,6 +293,59 @@ class _GeneralState extends State<_General> {
});
}
Widget record(BuildContext context) {
return _futureBuilder(future: () async {
String customDirectory =
await bind.mainGetOption(key: 'video-save-directory');
String defaultDirectory = await bind.mainDefaultVideoSaveDirectory();
String dir;
if (customDirectory.isNotEmpty) {
dir = customDirectory;
} else {
dir = defaultDirectory;
}
final canlaunch = await canLaunchUrl(Uri.file(dir));
return {'dir': dir, 'canlaunch': canlaunch};
}(), hasData: (data) {
Map<String, dynamic> map = data as Map<String, dynamic>;
String dir = map['dir']!;
bool canlaunch = map['canlaunch']! as bool;
return _Card(title: 'Recording', children: [
_OptionCheckBox(context, 'Automatically record incoming sessions',
'allow-auto-record-incoming'),
Row(
children: [
Text('${translate('Directory')}:'),
Expanded(
child: GestureDetector(
onTap: canlaunch ? () => launchUrl(Uri.file(dir)) : null,
child: Text(
dir,
softWrap: true,
style:
const TextStyle(decoration: TextDecoration.underline),
)).marginOnly(left: 10),
),
ElevatedButton(
onPressed: () async {
String? selectedDirectory = await FilePicker.platform
.getDirectoryPath(initialDirectory: dir);
if (selectedDirectory != null) {
await bind.mainSetOption(
key: 'video-save-directory',
value: selectedDirectory);
setState(() {});
}
},
child: Text(translate('Change')))
.marginOnly(left: 5),
],
).marginOnly(left: _kContentHMargin),
]);
});
}
Widget language() {
return _futureBuilder(future: () async {
String langs = await bind.mainGetLangs();

View File

@@ -166,6 +166,7 @@ class _RemotePageState extends State<RemotePage>
ChangeNotifierProvider.value(value: _ffi.imageModel),
ChangeNotifierProvider.value(value: _ffi.cursorModel),
ChangeNotifierProvider.value(value: _ffi.canvasModel),
ChangeNotifierProvider.value(value: _ffi.recordingModel),
], child: buildBody(context)));
}

View File

@@ -6,6 +6,7 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_hbb/models/chat_model.dart';
import 'package:get/get.dart';
import 'package:provider/provider.dart';
import 'package:rxdart/rxdart.dart' as rxdart;
import '../../common.dart';
@@ -134,6 +135,7 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
if (!isWeb) {
menubarItems.add(_buildChat(context));
}
menubarItems.add(_buildRecording(context));
menubarItems.add(_buildClose(context));
return PopupMenuTheme(
data: const PopupMenuThemeData(
@@ -351,6 +353,24 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
);
}
Widget _buildRecording(BuildContext context) {
return Consumer<RecordingModel>(
builder: (context, value, child) => IconButton(
tooltip: value.start
? translate('Stop session recording')
: translate('Start session recording'),
onPressed: () async {
await value.toggle();
},
icon: Icon(
value.start
? Icons.pause_circle_filled
: Icons.videocam_outlined,
color: _MenubarTheme.commonColor,
),
));
}
Widget _buildClose(BuildContext context) {
return IconButton(
tooltip: translate('Close'),

View File

@@ -197,6 +197,7 @@ class FfiModel with ChangeNotifier {
_display.height = int.parse(evt['height']);
if (old != _pi.currentDisplay) {
parent.target?.cursorModel.updateDisplayOrigin(_display.x, _display.y);
parent.target?.recordingModel.switchDisplay();
}
// remote is mobile, and orientation changed
@@ -972,6 +973,41 @@ class QualityMonitorModel with ChangeNotifier {
}
}
class RecordingModel with ChangeNotifier {
WeakReference<FFI> parent;
RecordingModel(this.parent);
bool _start = false;
get start => _start;
switchDisplay() {
if (!isDesktop || !_start) return;
var id = parent.target?.id;
int? width = parent.target?.canvasModel.getDisplayWidth();
int? height = parent.target?.canvasModel.getDisplayWidth();
if (id == null || width == null || height == null) return;
bind.sessionRecordScreen(
id: id, start: _start, width: width, height: height);
}
Future<void> toggle() async {
if (!isDesktop) return;
var id = parent.target?.id;
int? width = parent.target?.canvasModel.getDisplayWidth();
int? height = parent.target?.canvasModel.getDisplayWidth();
if (id == null || width == null || height == null) return;
await bind.sessionRecordScreen(
id: id, start: !_start, width: width, height: height);
_start = !_start;
notifyListeners();
if (_start) {
Future.delayed(const Duration(milliseconds: 100), () {
bind.sessionRefresh(id: id);
});
}
}
}
/// Mouse button enum.
enum MouseButtons { left, right, wheel }
@@ -1013,6 +1049,7 @@ class FFI {
late final AbModel abModel; // global
late final UserModel userModel; // global
late final QualityMonitorModel qualityMonitorModel; // session
late final RecordingModel recordingModel; // recording
FFI() {
imageModel = ImageModel(WeakReference(this));
@@ -1025,6 +1062,7 @@ class FFI {
abModel = AbModel(WeakReference(this));
userModel = UserModel(WeakReference(this));
qualityMonitorModel = QualityMonitorModel(WeakReference(this));
recordingModel = RecordingModel(WeakReference(this));
}
/// Send a mouse tap event(down and up).

View File

@@ -140,7 +140,7 @@ packages:
name: characters
url: "https://pub.dartlang.org"
source: hosted
version: "1.2.0"
version: "1.2.1"
charcode:
dependency: transitive
description:
@@ -161,7 +161,7 @@ packages:
name: clock
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.0"
version: "1.1.1"
code_builder:
dependency: transitive
description:
@@ -325,6 +325,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "6.1.4"
file_picker:
dependency: "direct main"
description:
name: file_picker
url: "https://pub.dartlang.org"
source: hosted
version: "5.1.0"
fixnum:
dependency: transitive
description:
@@ -588,7 +595,7 @@ packages:
name: material_color_utilities
url: "https://pub.dartlang.org"
source: hosted
version: "0.1.4"
version: "0.1.5"
menu_base:
dependency: transitive
description:
@@ -602,7 +609,7 @@ packages:
name: meta
url: "https://pub.dartlang.org"
source: hosted
version: "1.7.0"
version: "1.8.0"
mime:
dependency: transitive
description:
@@ -679,7 +686,7 @@ packages:
name: path
url: "https://pub.dartlang.org"
source: hosted
version: "1.8.1"
version: "1.8.2"
path_provider:
dependency: "direct main"
description:

View File

@@ -80,6 +80,7 @@ dependencies:
desktop_drop: ^0.3.3
scroll_pos: ^0.3.0
rxdart: ^0.27.5
file_picker: ^5.1.0
flutter_improved_scrolling: ^0.0.3
# currently, we use flutter 3.0.5 for windows build, latest for other builds.
#