mirror of
https://github.com/rustdesk/rustdesk.git
synced 2026-08-24 15:16:42 +00:00
* feat(web): zero-readback WebCodecs video path Decoded VideoFrames from js/src/webcodecs.js are handed to Flutter via window.onVideoFrame and imported GPU-side with createImageFromTextureSource; any failure unregisters the hook so the JS side falls back to RGBA readback. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(web): load bundled terminal font when Google CDNs are unreachable In air-gapped deployments GoogleFonts.robotoMono() cannot download the terminal font; when index.html signals offline mode, load the copy bundled with the web app under the family name google_fonts registers. Part of the fix for rustdesk/rustdesk-server-pro#996. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * ci: bump windows arm64 to Flutter 3.44.8, add web build patch script apply_flutter_3.44_web_patches.sh prepares a 3.44.x web build on top of the shared source patches: qr_code_scanner's web impl needs dart:ui_web for the removed platformViewRegistry, and flutter/web/fonts is refreshed to the font paths the 3.44 engine requests. The disabled build-rustdesk-web job runs it automatically once FLUTTER_VERSION moves to 3.44.x, and version-guarded 'Patch flutter' steps no longer fail when the guard does not match. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(web): prevent stale WebCodecs frames across sessions Signed-off-by: fufesou <linlong1266@gmail.com> * fix(web): harden WebCodecs reconnect and Flutter 3.44 patches Signed-off-by: fufesou <linlong1266@gmail.com> * fix(ci): harden Flutter 3.44 patch input validation Validate required files before checking patch state, parameterize the theme-range validator, and prevent missing inputs from satisfying NO_MATCHES checks. Signed-off-by: fufesou <linlong1266@gmail.com> * Remove unused code Signed-off-by: fufesou <linlong1266@gmail.com> * fix(web): retry font loading and dispose stale decoded images Signed-off-by: fufesou <linlong1266@gmail.com> * remove unused code Signed-off-by: fufesou <linlong1266@gmail.com> * fix(web): Bad state: RenderBox was not laid out Signed-off-by: fufesou <linlong1266@gmail.com> --------- Signed-off-by: fufesou <linlong1266@gmail.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com> Co-authored-by: fufesou <linlong1266@gmail.com>
134 lines
3.7 KiB
Dart
134 lines
3.7 KiB
Dart
import 'dart:async';
|
|
|
|
typedef VideoFrameImporter<Frame, Image> = Future<Image> Function(Frame frame);
|
|
typedef VideoFrameCloser<Frame> = void Function(Frame frame);
|
|
typedef VideoImageDisposer<Image> = void Function(Image image);
|
|
typedef VideoSessionValidator = bool Function();
|
|
typedef VideoImageCallback<Image> = Future<void> Function(
|
|
int display, Image image, VideoSessionValidator isCurrentSession);
|
|
typedef VideoQueueErrorCallback = void Function(
|
|
Object error, StackTrace stackTrace);
|
|
|
|
class WebVideoFrameQueue<Frame, Image> {
|
|
WebVideoFrameQueue({
|
|
required VideoFrameImporter<Frame, Image> importFrame,
|
|
required VideoFrameCloser<Frame> closeFrame,
|
|
required VideoImageDisposer<Image> disposeImage,
|
|
required VideoQueueErrorCallback onImportError,
|
|
required VideoQueueErrorCallback onCallbackError,
|
|
}) : _importFrame = importFrame,
|
|
_closeFrame = closeFrame,
|
|
_disposeImage = disposeImage,
|
|
_onImportError = onImportError,
|
|
_onCallbackError = onCallbackError;
|
|
|
|
final VideoFrameImporter<Frame, Image> _importFrame;
|
|
final VideoFrameCloser<Frame> _closeFrame;
|
|
final VideoImageDisposer<Image> _disposeImage;
|
|
final VideoQueueErrorCallback _onImportError;
|
|
final VideoQueueErrorCallback _onCallbackError;
|
|
final Map<int, _QueuedFrame<Frame>> _pending = {};
|
|
|
|
VideoImageCallback<Image>? _callback;
|
|
int _generation = 0;
|
|
bool _processing = false;
|
|
bool _enabled = true;
|
|
|
|
bool get isEnabled => _enabled;
|
|
|
|
void beginSession(VideoImageCallback<Image> callback) {
|
|
_invalidateSession();
|
|
_enabled = true;
|
|
_callback = callback;
|
|
}
|
|
|
|
void endSession() {
|
|
_invalidateSession();
|
|
_callback = null;
|
|
}
|
|
|
|
void _invalidateSession() {
|
|
_generation++;
|
|
for (final queued in _pending.values) {
|
|
_closeFrame(queued.frame);
|
|
}
|
|
_pending.clear();
|
|
}
|
|
|
|
bool submit(int display, Frame frame) {
|
|
if (!_enabled || _callback == null) {
|
|
_closeFrame(frame);
|
|
return false;
|
|
}
|
|
final replaced = _pending.remove(display);
|
|
if (replaced != null) {
|
|
_closeFrame(replaced.frame);
|
|
}
|
|
_pending[display] = _QueuedFrame(display, frame, _generation);
|
|
_startProcessing();
|
|
return true;
|
|
}
|
|
|
|
void _startProcessing() {
|
|
if (_processing) return;
|
|
_processing = true;
|
|
unawaited(Future<void>(_process));
|
|
}
|
|
|
|
Future<void> _process() async {
|
|
while (_pending.isNotEmpty) {
|
|
final display = _pending.keys.first;
|
|
final queued = _pending.remove(display)!;
|
|
if (!_enabled || queued.generation != _generation) {
|
|
_closeFrame(queued.frame);
|
|
continue;
|
|
}
|
|
await _importAndDeliver(queued);
|
|
}
|
|
_processing = false;
|
|
}
|
|
|
|
Future<void> _importAndDeliver(_QueuedFrame<Frame> queued) async {
|
|
Image? image;
|
|
try {
|
|
image = await _importFrame(queued.frame);
|
|
} catch (error, stackTrace) {
|
|
if (queued.generation == _generation) {
|
|
_enabled = false;
|
|
_onImportError(error, stackTrace);
|
|
}
|
|
} finally {
|
|
_closeFrame(queued.frame);
|
|
}
|
|
if (image != null) {
|
|
await _deliver(queued, image);
|
|
}
|
|
}
|
|
|
|
Future<void> _deliver(_QueuedFrame<Frame> queued, Image image) async {
|
|
final callback = _callback;
|
|
bool isCurrentSession() =>
|
|
_enabled &&
|
|
queued.generation == _generation &&
|
|
identical(callback, _callback);
|
|
if (!isCurrentSession() || callback == null) {
|
|
_disposeImage(image);
|
|
return;
|
|
}
|
|
try {
|
|
await callback(queued.display, image, isCurrentSession);
|
|
} catch (error, stackTrace) {
|
|
_disposeImage(image);
|
|
_onCallbackError(error, stackTrace);
|
|
}
|
|
}
|
|
}
|
|
|
|
class _QueuedFrame<Frame> {
|
|
const _QueuedFrame(this.display, this.frame, this.generation);
|
|
|
|
final int display;
|
|
final Frame frame;
|
|
final int generation;
|
|
}
|