-
-
Notifications
You must be signed in to change notification settings - Fork 28
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
On demand processing in background isolate #21
Open
ueman
wants to merge
2
commits into
main
Choose a base branch
from
on-demand-processing
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,63 +1,7 @@ | ||
import 'package:flutter/material.dart'; | ||
import 'package:image/image.dart' as image; | ||
import 'dart:ui' as ui show ImageByteFormat; | ||
import 'package:flutter/foundation.dart'; | ||
import 'package:screen_recorder/src/frame.dart'; | ||
|
||
abstract class Exporter { | ||
void onNewFrame(Frame frame); | ||
|
||
Future<List<int>?> export(); | ||
} | ||
|
||
class GifExporter implements Exporter { | ||
final List<Frame> _frames = []; | ||
|
||
@override | ||
Future<List<int>?> export() async { | ||
if (_frames.isEmpty) { | ||
return null; | ||
} | ||
List<RawFrame> bytes = []; | ||
for (final frame in _frames) { | ||
final i = await frame.image.toByteData(format: ui.ImageByteFormat.png); | ||
if (i != null) { | ||
bytes.add(RawFrame(16, i)); | ||
} else { | ||
print('Skipped frame while enconding'); | ||
} | ||
} | ||
final result = compute(_export, bytes); | ||
_frames.clear(); | ||
return result; | ||
} | ||
|
||
@override | ||
void onNewFrame(Frame frame) { | ||
_frames.add(frame); | ||
} | ||
|
||
static Future<List<int>?> _export(List<RawFrame> frames) async { | ||
final animation = image.Animation(); | ||
animation.backgroundColor = Colors.transparent.value; | ||
for (final frame in frames) { | ||
final iAsBytes = frame.image.buffer.asUint8List(); | ||
final decodedImage = image.decodePng(iAsBytes); | ||
|
||
if (decodedImage == null) { | ||
print('Skipped frame while enconding'); | ||
continue; | ||
} | ||
decodedImage.duration = frame.durationInMillis; | ||
animation.addFrame(decodedImage); | ||
} | ||
return image.encodeGifAnimation(animation); | ||
} | ||
} | ||
|
||
class RawFrame { | ||
RawFrame(this.durationInMillis, this.image); | ||
|
||
final int durationInMillis; | ||
final ByteData image; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
import 'package:screen_recorder/screen_recorder.dart'; | ||
|
||
import 'io_gif_exporter.dart' if (dart.library.html) 'web_gif_exporter.dart'; | ||
|
||
abstract class GifExporter implements Exporter { | ||
factory GifExporter() => gifExporter(); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
import 'dart:async'; | ||
import 'dart:isolate'; | ||
|
||
import 'package:flutter/material.dart'; | ||
import 'package:image/image.dart' as image; | ||
import 'dart:ui' as ui show ImageByteFormat; | ||
import 'package:flutter/foundation.dart'; | ||
import 'package:screen_recorder/src/frame.dart'; | ||
import 'package:screen_recorder/src/gif/gif_exporter.dart'; | ||
import 'package:stream_channel/isolate_channel.dart'; | ||
|
||
GifExporter gifExporter() => IoGifExporter(); | ||
|
||
class IoGifExporter implements GifExporter { | ||
IoGifExporter() { | ||
_controller.stream.listen((event) async { | ||
if (event is _InitIsolateMessage) { | ||
await _initIsolate(); | ||
} | ||
if (event is Frame) { | ||
final i = await event.image.toByteData(format: ui.ImageByteFormat.png); | ||
if (i != null) { | ||
channel!.sink.add(RawFrame(16, i)); | ||
} else { | ||
print('Skipped frame while enconding'); | ||
} | ||
} | ||
}); | ||
|
||
_controller.add(_InitIsolateMessage()); | ||
} | ||
|
||
StreamController _controller = StreamController(); | ||
|
||
ReceivePort receivePort = ReceivePort(); | ||
|
||
IsolateChannel? channel; | ||
|
||
Isolate? _isolate; | ||
|
||
Future<void> _initIsolate() async { | ||
channel = new IsolateChannel.connectReceive(receivePort); | ||
|
||
_isolate = await Isolate.spawn<SendPort>( | ||
_isolateEntryPoint, | ||
receivePort.sendPort, | ||
debugName: 'GifExporterIsolate', | ||
); | ||
} | ||
|
||
@override | ||
Future<List<int>?> export() async { | ||
channel!.sink.add(_ExportMessage()); | ||
return await channel!.stream.first; | ||
} | ||
|
||
@override | ||
void onNewFrame(Frame frame) { | ||
_controller.add(frame); | ||
} | ||
} | ||
|
||
class RawFrame { | ||
RawFrame(this.durationInMillis, this.image); | ||
|
||
final int durationInMillis; | ||
final ByteData image; | ||
} | ||
|
||
void _isolateEntryPoint(SendPort sendPort) { | ||
final exporter = _InternalExporter(); | ||
IsolateChannel channel = new IsolateChannel.connectSend(sendPort); | ||
channel.stream.listen((message) { | ||
if (message is RawFrame) { | ||
exporter.add(message); | ||
} | ||
if (message is _ExportMessage) { | ||
final gif = exporter.export(); | ||
channel.sink.add(gif); | ||
} | ||
}); | ||
} | ||
|
||
class _InternalExporter { | ||
_InternalExporter() { | ||
animation = image.Animation(); | ||
animation.backgroundColor = Colors.transparent.value; | ||
} | ||
|
||
late image.Animation animation; | ||
|
||
void add(RawFrame rawFrame) { | ||
final iAsBytes = rawFrame.image.buffer.asUint8List(); | ||
final decodedImage = image.decodePng(iAsBytes); | ||
|
||
if (decodedImage == null) { | ||
print('Skipped frame while enconding'); | ||
return; | ||
} | ||
decodedImage.duration = rawFrame.durationInMillis; | ||
animation.addFrame(decodedImage); | ||
} | ||
|
||
List<int>? export() => image.encodeGifAnimation(animation); | ||
} | ||
|
||
class _ExportMessage {} | ||
|
||
class _InitIsolateMessage {} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
import 'package:flutter/material.dart'; | ||
import 'package:image/image.dart' as image; | ||
import 'dart:ui' as ui show ImageByteFormat; | ||
import 'package:flutter/foundation.dart'; | ||
import 'package:screen_recorder/src/frame.dart'; | ||
import 'package:screen_recorder/src/gif/gif_exporter.dart'; | ||
|
||
GifExporter gifExporter() => WebGifExporter(); | ||
|
||
class WebGifExporter implements GifExporter { | ||
final List<Frame> _frames = []; | ||
|
||
@override | ||
Future<List<int>?> export() async { | ||
if (_frames.isEmpty) { | ||
return null; | ||
} | ||
List<RawFrame> bytes = []; | ||
for (final frame in _frames) { | ||
final i = await frame.image.toByteData(format: ui.ImageByteFormat.png); | ||
if (i != null) { | ||
bytes.add(RawFrame(16, i)); | ||
} else { | ||
print('Skipped frame while enconding'); | ||
} | ||
} | ||
final result = compute(_export, bytes); | ||
_frames.clear(); | ||
return result; | ||
} | ||
|
||
@override | ||
void onNewFrame(Frame frame) { | ||
_frames.add(frame); | ||
} | ||
|
||
static Future<List<int>?> _export(List<RawFrame> frames) async { | ||
final animation = image.Animation(); | ||
animation.backgroundColor = Colors.transparent.value; | ||
for (final frame in frames) { | ||
final iAsBytes = frame.image.buffer.asUint8List(); | ||
final decodedImage = image.decodePng(iAsBytes); | ||
|
||
if (decodedImage == null) { | ||
print('Skipped frame while enconding'); | ||
continue; | ||
} | ||
decodedImage.duration = frame.durationInMillis; | ||
animation.addFrame(decodedImage); | ||
} | ||
return image.encodeGifAnimation(animation); | ||
} | ||
} | ||
|
||
class RawFrame { | ||
RawFrame(this.durationInMillis, this.image); | ||
|
||
final int durationInMillis; | ||
final ByteData image; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This part still will freeze the screen on Web if you have many frames @ueman
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah I know. For now it's just an improvement for io platforms. For web it's just the old implementation. (This PR is a few commits behind)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The problem is that this operation(toByteData) we dont have any way to include it on a Web Worker, thats why my other proposal of processing the frames on the way. I dont like much my approach but honestly i dont find any other workaround :S
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So then, for me to get context about the PR, where is the improvement exactly? Capturing or just exporting?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This significantly improves the gif exporting on io platforms. Each frame is directly processed for a gif in a background isolate which means when the user actually requests the gif export, it's almost completely converted to a gif. So for a user this seems like a significant performance improvement.
But as said, it just works on IO platforms and only for gifs as dart:ui APIs can't be used in a background isolate.
But I'm guessing you're more interested in individual frames than a gif 😅
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok see what you mean now.
But yeah, this will still be a pain in my side haha Wondering what middle point we can have. For any reason i dont understand why the operation toByteData is freezing just on Web not in iPhone.