From 1e9859b7fcc2b309ae0bfd9280aac6d3619bb52f Mon Sep 17 00:00:00 2001 From: polarby Date: Sun, 2 Apr 2023 13:48:26 +0800 Subject: [PATCH] closing application during active render session --- CHANGELOG.md | 7 +- README.md | 2 +- example/lib/main.dart | 5 +- lib/render.dart | 1 - lib/src/core.dart | 140 ++++++++++++++++++++++++--- lib/src/service/motion_recorder.dart | 61 ------------ lib/src/service/notifier.dart | 2 + lib/src/service/session.dart | 6 ++ pubspec.yaml | 2 +- 9 files changed, 143 insertions(+), 83 deletions(-) delete mode 100644 lib/src/service/motion_recorder.dart diff --git a/CHANGELOG.md b/CHANGELOG.md index ccc7ba7..66509c8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -43,6 +43,11 @@ ## 0.1.1 -* time capture bug resolved(laggy outcome) +* time capture bug resolved (laggy outcome) * proper session disposal +## 0.1.2 + +* error on closing application during active render session +* doc update + diff --git a/README.md b/README.md index 6835eb4..c286d78 100644 --- a/README.md +++ b/README.md @@ -269,7 +269,7 @@ stream.listen((event) { // listens to stream until it closes by itself (when res }); // result can then be displayed (see Motion rendering) -final result = await stream.firstWhere((element) => element.isResult); +final result = await stream.firstWhere((event) => event.isResult || event.isFatalError); ``` [comment]: # (@formatter:on) diff --git a/example/lib/main.dart b/example/lib/main.dart index a825961..8336bbc 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -97,8 +97,9 @@ class _MyHomePageState extends State setState(() { functionController.attach(stream); }); - final result = await stream - .firstWhere((element) => element.isResult); + final result = await stream.firstWhere( + (event) => event.isResult || event.isFatalError); + if (result.isFatalError) return; displayResult(result as RenderResult); }, exampleAnimationController: functionController, diff --git a/lib/render.dart b/lib/render.dart index 05b7290..430d605 100644 --- a/lib/render.dart +++ b/lib/render.dart @@ -1,7 +1,6 @@ library render; export 'src/core.dart'; -export 'src/service/motion_recorder.dart'; export 'src/service/exception.dart'; export 'src/service/settings.dart'; export 'src/service/notifier.dart'; diff --git a/lib/src/core.dart b/lib/src/core.dart index ce00910..a03ba70 100644 --- a/lib/src/core.dart +++ b/lib/src/core.dart @@ -1,5 +1,6 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; +import 'package:render/src/service/exception.dart'; import 'package:render/src/service/task_identifier.dart'; import 'package:rich_console/printRich.dart'; import 'package:uuid/uuid.dart'; @@ -10,7 +11,6 @@ import 'package:render/src/formats/abstract.dart'; import 'package:render/src/formats/image.dart'; import 'package:render/src/formats/motion.dart'; import 'package:render/src/process.dart'; -import 'package:render/src/service/motion_recorder.dart'; import 'package:render/src/service/session.dart'; import 'package:render/src/service/settings.dart'; import 'service/notifier.dart'; @@ -28,7 +28,10 @@ class RenderController { /// mode. final LogLevel logLevel; - //TODO: render controller doc (copy from read me) + /// All active render sessions + final List _activeSessions = []; + + /// The controller to initiate a render process. See doc for more info. RenderController({this.logLevel = LogLevel.debug}) : id = UuidValue(const Uuid().v4()); @@ -43,11 +46,14 @@ class RenderController { overwriteTask != null || _globalTask?.key.currentWidget != null, "RenderController must have a Render instance " "connected to create a session."); - return RenderSession.fromDetached( - detachedSession: detachedRenderSession, - notifier: notifier, - task: overwriteTask ?? _globalTask!, - ); + final session = RenderSession.fromDetached( + detachedSession: detachedRenderSession, + notifier: notifier, + task: overwriteTask ?? _globalTask!, + onDispose: () => _activeSessions.removeWhere( + (s) => s.sessionId == detachedRenderSession.sessionId)); + _activeSessions.add(session); + return session; } /// Listens to stream and prints out debug, if in debug mode. @@ -92,8 +98,10 @@ class RenderController { format: format, ); _debugPrintOnStream(stream, "Capturing image started"); - return await stream.firstWhere((element) => element.isResult) - as RenderResult; + final out = await stream + .firstWhere((event) => event.isResult || event.isFatalError); + if (out.isFatalError) throw (out as RenderError).exception; + return out as RenderResult; } /// Captures an image and returns the result as future. @@ -119,8 +127,10 @@ class RenderController { format: format, ); _debugPrintOnStream(stream, "Capturing image from widget started"); - return await stream.firstWhere((element) => element.isResult) - as RenderResult; + final out = await stream + .firstWhere((event) => event.isResult || event.isFatalError); + if (out.isFatalError) throw (out as RenderError).exception; + return out as RenderResult; } /// Captures the motion of a widget and returns a future of the result. @@ -146,8 +156,10 @@ class RenderController { format: format, ); _debugPrintOnStream(stream, "Capturing motion started"); - return await stream.firstWhere((element) => element.isResult) - as RenderResult; + final out = await stream + .firstWhere((event) => event.isResult || event.isFatalError); + if (out.isFatalError) throw (out as RenderError).exception; + return out as RenderResult; } /// Captures motion of a widget that is out of the widget tree @@ -179,8 +191,10 @@ class RenderController { format: format, ); _debugPrintOnStream(stream, "Capturing motion from widget started"); - return await stream.firstWhere((element) => element.isResult) - as RenderResult; + final out = await stream + .firstWhere((event) => event.isResult || event.isFatalError); + if (out.isFatalError) throw (out as RenderError).exception; + return out as RenderResult; } /// Captures an image and returns a stream of information of current @@ -339,6 +353,7 @@ class RenderController { LogLevel? logLevel, MotionSettings settings = const MotionSettings(), MotionFormat format = const MovFormat(), + bool logInConsole = false, }) { assert(!kIsWeb, "Render does not support Web yet"); assert( @@ -350,6 +365,8 @@ class RenderController { capturingSettings: settings, task: _globalTask!, logLevel: logLevel ?? this.logLevel, + controller: this, + logInConsole: logInConsole, ); } @@ -362,6 +379,7 @@ class RenderController { LogLevel? logLevel, MotionSettings settings = const MotionSettings(), MotionFormat format = const MovFormat(), + bool logInConsole = false, }) { assert(!kIsWeb, "Render does not support Web yet"); return MotionRecorder.start( @@ -369,10 +387,74 @@ class RenderController { capturingSettings: settings, task: WidgetIdentifier(controllerId: id, widget: widget), logLevel: logLevel ?? this.logLevel, + controller: this, + logInConsole: logInConsole, ); } } +class MotionRecorder { + final RenderController _controller; + + /// All related render recording settings + final MotionSettings capturingSettings; + + /// The output format of the recording + final T format; + + final bool logInConsole; + + /// What notifications should be displayed + final LogLevel logLevel; + late final StreamController _notifier; + late final RenderSession _session; + late final RenderCapturer _capturer; + + /// Starts a motion recording process + MotionRecorder.start({ + required RenderController controller, + required this.logLevel, + required this.format, + required this.capturingSettings, + required TaskIdentifier task, + required this.logInConsole, + }) : _controller = controller { + _notifier = StreamController.broadcast(); + DetachedRenderSession.create(format, capturingSettings, logLevel) + .then((detachedSession) { + _session = _controller._createRenderSessionFrom( + detachedSession, + _notifier, + task is WidgetIdentifier ? task : null, + ); + _capturer = RenderCapturer(_session); + _capturer.start(); + }); + if (logInConsole) { + _controller._debugPrintOnStream( + _notifier.stream, "Recording motion started"); + } + } + + /// It is highly recommended to make use of stream for capturing motion, + /// as the process usually takes longer and the user will likely wants to get + /// notified the process of rendering for longer operations. + Stream get stream => _notifier.stream; + + /// Stops the recording and returns the result of the recording. + Future stop() async { + final realSession = await _capturer.finish(); + final processor = MotionProcessor(realSession); + processor.process(); // wait for result instead of process + final out = await stream + .firstWhere((event) => event.isResult || event.isFatalError); + if (out.isFatalError) throw (out as RenderError).exception; + _notifier.close(); + await _session.dispose(); + return out as RenderResult; + } +} + class Render extends StatefulWidget { /// The render controller to initiate rendering final RenderController? controller; @@ -414,19 +496,45 @@ class Render extends StatefulWidget { State createState() => _RenderState(); } -class _RenderState extends State { +class _RenderState extends State with WidgetsBindingObserver { final GlobalKey renderKey = GlobalKey(); bool hasAttached = false; + @override + void didChangeAppLifecycleState(AppLifecycleState state) { + // Gets called when application has been closed/paused and is back to normal + // [Issue fix]: https://github.com/polarby/render/issues/11#issuecomment-1492948345 + if (state == AppLifecycleState.detached || + state == AppLifecycleState.paused || + state == AppLifecycleState.inactive) { + final numberOfSessions = widget.controller?._activeSessions.length ?? 0; + for (int i = 0; i < numberOfSessions; i++) { + widget.controller?._activeSessions.first.recordError( + const RenderException( + "Application was paused during an active render session.", + fatal: true, + ), + ); + } + } + } + @override void initState() { super.initState(); + WidgetsBinding.instance.addObserver(this); if (widget.controller != null) { attach(); hasAttached = true; } } + @override + void dispose() { + WidgetsBinding.instance.removeObserver(this); + super.dispose(); + } + @override Widget build(BuildContext context) { if (!hasAttached && widget.controller != null) { diff --git a/lib/src/service/motion_recorder.dart b/lib/src/service/motion_recorder.dart deleted file mode 100644 index f78efec..0000000 --- a/lib/src/service/motion_recorder.dart +++ /dev/null @@ -1,61 +0,0 @@ -import 'dart:async'; - -import 'package:render/src/formats/abstract.dart'; -import 'package:render/src/service/notifier.dart'; -import 'package:render/src/service/session.dart'; -import 'package:render/src/service/settings.dart'; -import 'package:render/src/service/task_identifier.dart'; - -import '../capturer.dart'; -import '../process.dart'; - -class MotionRecorder { - /// All related render recording settings - final MotionSettings capturingSettings; - - /// The output format of the recording - final T format; - - /// What notifications should be displayed - final LogLevel logLevel; - late final StreamController _notifier; - late final RenderSession _session; - late final RenderCapturer _capturer; - - /// Starts a motion recording process - MotionRecorder.start({ - required this.logLevel, - required this.format, - required this.capturingSettings, - required TaskIdentifier task, - }) { - _notifier = StreamController.broadcast(); - DetachedRenderSession.create(format, capturingSettings, logLevel) - .then((detachedSession) { - _session = RenderSession.fromDetached( - detachedSession: detachedSession, - notifier: _notifier, - task: task, - ); - _capturer = RenderCapturer(_session); - _capturer.start(); - }); - } - - /// It is highly recommended to make use of stream for capturing motion, - /// as the process usually takes longer and the user will likely wants to get - /// notified the process of rendering for longer operations. - Stream get stream => _notifier.stream; - - /// Stops the recording and returns the result of the recording. - Future stop() async { - final realSession = await _capturer.finish(); - final processor = MotionProcessor(realSession); - processor.process(); // wait for result instead of process - final result = - await stream.singleWhere((element) => element.isResult) as RenderResult; - _notifier.close(); - _session.dispose(); - return result; - } -} diff --git a/lib/src/service/notifier.dart b/lib/src/service/notifier.dart index 2429b36..feb907f 100644 --- a/lib/src/service/notifier.dart +++ b/lib/src/service/notifier.dart @@ -22,6 +22,8 @@ abstract class RenderNotifier { bool get isActivity => this is RenderActivity; bool get isLog => this is RenderLog; + + bool get isFatalError => isError && (this as RenderError).fatal; } /// Used to notify the user about an error that has occurred in the diff --git a/lib/src/service/session.dart b/lib/src/service/session.dart index f2c1ab6..0f1460e 100644 --- a/lib/src/service/session.dart +++ b/lib/src/service/session.dart @@ -124,6 +124,8 @@ class RenderSession /// remaining time calculation. final DateTime startTime; + final VoidCallback onDispose; + /// A class that holds all the information about the current session. /// used to pass information between the different parts of the rendering /// process. @@ -138,6 +140,7 @@ class RenderSession required super.format, required super.binding, required this.task, + required this.onDispose, required StreamController notifier, DateTime? startTime, }) : _notifier = notifier, @@ -151,6 +154,7 @@ class RenderSession required DetachedRenderSession detachedSession, required StreamController notifier, required this.task, + required this.onDispose, DateTime? startTime, }) : _notifier = notifier, startTime = DateTime.now(), @@ -176,6 +180,7 @@ class RenderSession capturingDuration: capturingDuration, frameAmount: frameAmount, ), + onDispose: onDispose, startTime: startTime, logLevel: logLevel, inputDirectory: inputDirectory, @@ -260,6 +265,7 @@ class RenderSession /// Disposing the current render session. Future dispose() async { + onDispose(); if (Directory(inputDirectory).existsSync()) { Directory(inputDirectory).deleteSync(recursive: true); } diff --git a/pubspec.yaml b/pubspec.yaml index 869c9cf..18fce29 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: render description: A package to render any native static and moving flutter widgets to exportable formats -version: 0.1.1 +version: 0.1.2 homepage: https://github.com/polarby/render environment: