forked from Foxyz/sync_replay_project
Stabilisation quick fixes: orderly close, visible version and update band, chat pop-ups on, Windows shaders and hwdec, render logs
- Close: hide the window, dispose every player, thumbnail job and the sound engine, leave, then close normally; Linux runner _exit (the NVIDIA EGL exit handler crashed 10/10 closes, 0/41 now); Windows tears down inside the message loop, then TerminateProcess; watchdogs. - Version under the Settings tab; an update band instead of a dot, never over a video; checks at start and every 4 hours. - Chat pop-ups default to All again. - Shader read-back compared as a list (old mpv joins with ','), so Windows stops turning shaders off; Windows hwdec auto-copy. - Render diagnostics (`mpv: render` lines); RAM-only demuxer cache pin silences the file-cache warning; a volume readout test. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
e537f9d5f8
commit
ea607c1a52
29 files changed
+873
-168
No files matched your search
@@ -0,0 +1,78 @@
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:window_manager/window_manager.dart';
|
||||
|
||||
import '../files/thumbnails.dart';
|
||||
import '../state/session_state.dart';
|
||||
import 'window_memory.dart';
|
||||
|
||||
// The one way out of the app (close button, Alt+F4, Update and restart).
|
||||
// Order matters: every mpv player goes before the engine does (a live one
|
||||
// calls a dead Dart callback), then the network goodbye, then the window
|
||||
// closes through the platform's normal path so the engine is torn down
|
||||
// inside the message loop. The runners end the process right after
|
||||
// (`linux/runner/main.cc`, `windows/runner/main.cpp`).
|
||||
|
||||
/// Saved on the way out; set by main once known.
|
||||
WindowMemory? shutdownWindowMemory;
|
||||
|
||||
bool _shuttingDown = false;
|
||||
|
||||
/// True once [shutDownApp] started.
|
||||
bool get shuttingDown => _shuttingDown;
|
||||
|
||||
/// Hides the window, releases every player and the sound device, leaves
|
||||
/// (others see "left", a hosted server stops), runs [beforeQuit], then
|
||||
/// closes the window. Every step is bounded; a second call does nothing.
|
||||
Future<void> shutDownApp({Future<void> Function()? beforeQuit}) async {
|
||||
if (_shuttingDown) return;
|
||||
_shuttingDown = true;
|
||||
final session = SessionState.instance;
|
||||
final total = Stopwatch()..start();
|
||||
await _step('window save', shutdownWindowMemory?.save(), const Duration(milliseconds: 500));
|
||||
await _step('hide', windowManager.hide(), const Duration(milliseconds: 500));
|
||||
await Future.wait([
|
||||
_step('players', session.coordinator.releasePlayers(), const Duration(seconds: 3)),
|
||||
_step('thumbnails', cancelAllThumbnails(), const Duration(milliseconds: 1500)),
|
||||
_step('sounds', session.sfx.dispose(), const Duration(milliseconds: 500)),
|
||||
]);
|
||||
// Windows: media_kit_video frees a player's video output on a thread of
|
||||
// its own after dispose returns; it must be done before the engine
|
||||
// stops, or the plugin's teardown waits on it for good.
|
||||
if (Platform.isWindows) await Future.delayed(const Duration(milliseconds: 300));
|
||||
// Jellyfin stop report, last diagnostics, the goodbye, then the hosted
|
||||
// server: 3 s in all.
|
||||
await _step('leave', session.leave(), const Duration(milliseconds: 2500));
|
||||
await _step('server stop', session.stopServer(), const Duration(milliseconds: 500));
|
||||
if (beforeQuit != null) await _step('before quit', beforeQuit(), const Duration(seconds: 3));
|
||||
debugPrint('close: ready to quit after ${total.elapsedMilliseconds} ms');
|
||||
try {
|
||||
await windowManager.setPreventClose(false);
|
||||
await windowManager.close();
|
||||
} catch (e) {
|
||||
debugPrint('window close failed: $e');
|
||||
await windowManager.destroy();
|
||||
}
|
||||
// The engine stops with the window, so this only fires when the close
|
||||
// did not go through. A kill, not exit(): no exit handlers (NVIDIA's
|
||||
// EGL one segfaults, Windows DLL detach can hang).
|
||||
Timer(const Duration(seconds: 3), () {
|
||||
debugPrint('close: the window did not close; ending the process');
|
||||
Process.killPid(pid, ProcessSignal.sigkill);
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _step(String what, Future<void>? f, Duration limit) async {
|
||||
if (f == null) return;
|
||||
final sw = Stopwatch()..start();
|
||||
try {
|
||||
await f.timeout(limit);
|
||||
} on TimeoutException {
|
||||
debugPrint('close: $what did not finish in ${limit.inMilliseconds} ms');
|
||||
} catch (e) {
|
||||
debugPrint('close: $what failed: $e');
|
||||
}
|
||||
if (sw.elapsedMilliseconds > 200) debugPrint('close: $what took ${sw.elapsedMilliseconds} ms');
|
||||
}
|
||||
Reference in new issue
Block a user