I was using VideoCompress plugin with success. But since I moved my video processing to an isolate, I am getting this error:
Cannot set the method call handler before the binary messenger has been initialized. This happens when you call setMethodCallHandler() before the WidgetsFlutterBinding has been initialized. You can fix this by either calling WidgetsFlutterBinding.ensureInitialized() before this or by passing a custom BinaryMessenger instance to MethodChannel().
Though I don't see why being in an isolate could end up with an error.
So the question is, beyond video_compress
, can one use a Flutter plugin from within an isolate?
Anyway, I would be grateful to anyone that might help me on this.
My code's as follows:
late Subscription _subscription;
// === Compressing
try {
isolateSwitchToState(RecordingState.compressingVideo);
// == Subscribe to progress
_subscription = VideoCompress.compressProgress$.subscribe((progress) {
debugPrint('progress: $progress');
_send2main?.send(progress);
});
final videoDurationInSeconds = (videoMediaInfo.duration! / 1000).floor();
final trimDurationInSec =
min(purchasedDurationInSeconds, videoDurationInSeconds);
finalMediaInfo = await VideoCompress.compressVideo(videoLocalPath,
startTime: 0,
// Possibly truncate the video to the purchased time extension: but it cannot exceed the initial duration, hence the min
duration: videoDurationInSeconds - trimDurationInSec,
quality: //VideoQuality.DefaultQuality // 58MiB
//VideoQuality.Res640x480Quality // 17MiB
VideoQuality.Res960x540Quality // 34MiB
//VideoQuality.Res1280x720Quality // 58MiB
//VideoQuality.Res1920x1080Quality // 127MiB
//VideoQuality.HighestQuality,
);
isolateSwitchToState(RecordingState.videoCompressed);
// ==Now uploading
if (finalMediaInfo == null || finalMediaInfo.file == null) {
isolateSwitchToErrorState(
i18n_The_compressed_video_file_or_file_info_is_null.i18n);
return;
}
} catch (e) {
isolateSwitchToErrorState(
i18n_While_compressing_to_the_video_file_s.i18n.fill([e.toString()]));
return;
} finally {
// == Unsuscribe from progression
_subscription.unsubscribe();
}
[UDPATE]
The throwing is from this code:
/// Sets a callback for receiving method calls on this channel.
///
/// The given callback will replace the currently registered callback for this
/// channel, if any. To remove the handler, pass null as the
/// `handler` argument.
///
/// If the future returned by the handler completes with a result, that value
/// is sent back to the platform plugin caller wrapped in a success envelope
/// as defined by the [codec] of this channel. If the future completes with
/// a [PlatformException], the fields of that exception will be used to
/// populate an error envelope which is sent back instead. If the future
/// completes with a [MissingPluginException], an empty reply is sent
/// similarly to what happens if no method call handler has been set.
/// Any other exception results in an error envelope being sent.
void setMethodCallHandler(Future<dynamic> Function(MethodCall call)? handler) {
assert(
_binaryMessenger != null || BindingBase.debugBindingType() != null,
'Cannot set the method call handler before the binary messenger has been initialized. '
'This happens when you call setMethodCallHandler() before the WidgetsFlutterBinding '
'has been initialized. You can fix this by either calling WidgetsFlutterBinding.ensureInitialized() '
'before this or by passing a custom BinaryMessenger instance to MethodChannel().',
);
From this line in compress_mixin.dart
:
class CompressMixin {
final compressProgress$ = ObservableBuilder<double>();
final _channel = const MethodChannel('video_compress');
@protected
void initProcessCallback() {
_channel.setMethodCallHandler(_progressCallback); // <<===== From here
}
MethodChannel get channel => _channel;
bool _isCompressing = false;
bool get isCompressing => _isCompressing;
@protected
void setProcessingStatus(bool status) {
_isCompressing = status;
}
Future<void> _progressCallback(MethodCall call) async {
switch (call.method) {
case 'updateProgress':
final progress = double.tryParse(call.arguments.toString());
if (progress != null) compressProgress$.next(progress);
break;
}
}
}
Ok, here is the reason: plugins require special bindings and this is why I get the exception.
Someone wrote the flutter_isolate plugin that does just that: make the binding for isolates.
You simply replace you isolate.spawn
by FlutterIsolate.spawn
and add the following decoration to your global or static entry point @pragma('vm:entry-point')
as follows:
/// the code to run the compression from within the isolate
Future<void> runVideoCompressionFromIsolate () async {
final isolate = await FlutterIsolate.spawn<int>(
longTaskVideoCompressionForIsolate,
7,
//OPTIONS NOT AVAILABLE IN NEW PLUGIN
//errorsAreFatal: true, // uncaught errors will terminate the isolate
//debugName: 'longTaskVideoCompressionForIsolate', // name in debuggers and logging
);
await Future<void>.delayed(const Duration(seconds: 20));
isolate.kill(); // Kill the isolate.
}
/// The entry point of our isolate.
@pragma('vm:entry-point')
void longTaskVideoCompressionForIsolate(int payload) async {
try {
// == Subscribe to progress
final _subscription = VideoCompress.compressProgress$.subscribe((progress) {
debugPrint('progress: $progress');
});
// blah blah
}
catch(e) {
debugPrint(e.toString());
}
}
One important note though: flutter_isolate
does not allow passing objects thru ports: You must use Map<String, dynamic>
instead, so be prepared for some refactors.