diff --git a/apps/desktop/lib/main.dart b/apps/desktop/lib/main.dart index 7958e6c..1e8fc55 100644 --- a/apps/desktop/lib/main.dart +++ b/apps/desktop/lib/main.dart @@ -49,8 +49,6 @@ Future main(List args) async { await AppSettings.instance.ensureLoaded(); // The composition root: every system, made once. final graph = AppGraph.build(); - // Sounds stay off after an audio-device crash (see SoundEngine.crashMark). - graph.sfx.engine.crashMark = '${dirs.support}${Platform.pathSeparator}audio-init.pending'; final ports = graph.ports; // Close button / Alt+F4: the orderly close ([shutDownApp]), so others // see "left", your server shuts down and no player outlives the engine. diff --git a/apps/desktop/lib/src/platform/audio/sound_engine.dart b/apps/desktop/lib/src/platform/audio/sound_engine.dart index d987e24..b0cd922 100644 --- a/apps/desktop/lib/src/platform/audio/sound_engine.dart +++ b/apps/desktop/lib/src/platform/audio/sound_engine.dart @@ -1,5 +1,4 @@ import 'dart:async'; -import 'dart:io'; import 'dart:typed_data'; import 'package:flutter_soloud/flutter_soloud.dart'; @@ -51,13 +50,6 @@ class SoundEngine { Future? _ready; var _disposed = false; - - /// A file that exists only while the audio device is opening (set by the - /// app). Found at the next start, the last open took the app down (a - /// native crash in the driver or miniaudio, seen on Windows): sounds stay - /// off rather than crash every start, until [retry] (Settings' preview). - String? crashMark; - var _skipped = false; /// Per slot: the loaded key and its sound (null: failed to load). final Map)> _slots = {}; /// Load names are unique: SoLoud hands back the old sound for a name it @@ -69,43 +61,15 @@ class SoundEngine { Future _init() async { if (_disposed) return false; - final mark = crashMark == null ? null : File(crashMark!); - try { - if (mark != null && mark.existsSync()) { - _skipped = true; - _log.warning('no sounds: opening the audio device crashed the app last time ' - '(${mark.path}); a sound preview in Settings tries again'); - return false; - } - mark?.writeAsStringSync('opening the audio device'); - } catch (_) {} - _log.info('opening the audio device'); try { await _backend.init(); - _log.info('audio device open'); return true; } catch (e) { _log.warning('no sounds: audio init failed: $e'); return false; - } finally { - try { - if (mark != null && mark.existsSync()) mark.deleteSync(); - } catch (_) {} } } - /// Opens the device again after a crash mark kept sounds off (the user - /// asked for a sound). A real crash leaves the mark again. - void retry() { - if (!_skipped) return; - _skipped = false; - try { - final m = crashMark; - if (m != null) File(m).deleteSync(); - } catch (_) {} - _ready = null; - } - /// Plays the sound of [slot] at [volume] (linear gain). [key] names what /// [wav] renders: the same key reuses the loaded sound; another key, or /// [fresh], renders and loads it again. diff --git a/apps/desktop/lib/src/sfx/synth_sfx.dart b/apps/desktop/lib/src/sfx/synth_sfx.dart index 90cffc3..b7146f3 100644 --- a/apps/desktop/lib/src/sfx/synth_sfx.dart +++ b/apps/desktop/lib/src/sfx/synth_sfx.dart @@ -37,11 +37,8 @@ class SynthSfx implements SfxService { /// Plays [kind] even when it is switched off (Settings' preview), always /// freshly rendered: an edited spec plays after a hot reload. - Future preview(SfxKind kind, {int readyIndex = 1, SoundPreset? preset}) { - // Asked for: try the device again even after a crash kept it closed. - engine.retry(); - return _play(kind, readyIndex, preset ?? presetFor(kind), fresh: true); - } + Future preview(SfxKind kind, {int readyIndex = 1, SoundPreset? preset}) => + _play(kind, readyIndex, preset ?? presetFor(kind), fresh: true); /// One slot per sound and ready step: a new preset replaces it. Future _play(SfxKind kind, int readyIndex, SoundPreset preset, {bool fresh = false}) { diff --git a/apps/desktop/test/sound_engine_test.dart b/apps/desktop/test/sound_engine_test.dart index a678683..90c1130 100644 --- a/apps/desktop/test/sound_engine_test.dart +++ b/apps/desktop/test/sound_engine_test.dart @@ -1,4 +1,3 @@ -import 'dart:io'; import 'dart:typed_data'; import 'package:flutter_test/flutter_test.dart'; @@ -148,46 +147,4 @@ void main() { expect(b.deinits, 1); }); }); - - group('crash mark', () { - late Directory dir; - setUp(() => dir = Directory.systemTemp.createTempSync('sound_mark')); - tearDown(() => dir.deleteSync(recursive: true)); - - test('exists only while the device opens', () async { - final mark = '${dir.path}/audio-init.pending'; - var seen = false; - final b = _MarkBackend(() => seen = File(mark).existsSync()); - final e = SoundEngine(backend: b)..crashMark = mark; - expect(await e.warmUp(), isTrue); - expect(seen, isTrue, reason: 'there while opening'); - expect(File(mark).existsSync(), isFalse, reason: 'gone once open'); - }); - - test('left by a crash: sounds stay off, a preview tries again', () async { - final mark = '${dir.path}/audio-init.pending'; - File(mark).writeAsStringSync('opening the audio device'); - final b = _FakeBackend(); - final e = SoundEngine(backend: b)..crashMark = mark; - await e.play('chat', 'k', _bytes, volume: 1); - expect(b.inits, 0, reason: 'never opened after a crash'); - expect(b.plays, isEmpty); - e.retry(); - await e.play('chat', 'k', _bytes, volume: 1); - expect(b.inits, 1); - expect(b.plays, hasLength(1)); - expect(File(mark).existsSync(), isFalse); - }); - }); -} - -class _MarkBackend extends _FakeBackend { - final void Function() onInit; - _MarkBackend(this.onInit); - - @override - Future init() async { - onInit(); - await super.init(); - } } diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index bbf3515..59746bf 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -1946,12 +1946,7 @@ panel) and chat messages (Copy message, and a link in it). switched off in `soundsOff`. The device opens 2 s after startup (`warmUp`), so neither startup nor the first sound waits on it; if it can't open, the engine logs `no sounds: audio init failed` once and drops - every play. Opening it is logged (`opening the audio device`, - `audio device open`) and leaves `/audio-init.pending` until it - returns: a native crash there (a driver, miniaudio) leaves the file, and - the next start skips sounds (`no sounds: opening the audio device - crashed the app last time`) instead of crashing again; a sound preview - in Settings deletes it and tries once more. App close disposes the engine (deinit, 500 ms bound) + every play. App close disposes the engine (deinit, 500 ms bound) before leaving the server. Settings → Notifications & sounds has the volume, the overall sound set (`soundPreset`), and per sound a preview, its own set (`soundPresetFor`, else the overall one) and a switch; a preview always