diff --git a/CHANGELOG.md b/CHANGELOG.md index cfdc505205..f319b025a0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,11 +1,12 @@ # Changelog -[0.13.2] - 2025-04-29 +[0.14.0] - 2025-05-06 * [iOS/Android]feat: Media Recorder implementation Android and iOS (#1810) * [Wndows] fix: Pickup registrar for plugin by plugin registrar manager (#1752) * [Linux] fix: add task runner for linux. (#1821) +* [iOS/macOS] fix: Fix deadlock when creating a frame cryptor on iOS/macOS. [0.13.1+hotfix.1] - 2025-04-07 diff --git a/android/src/main/java/com/cloudwebrtc/webrtc/record/VideoFileRenderer.java b/android/src/main/java/com/cloudwebrtc/webrtc/record/VideoFileRenderer.java index f2a0de795c..212a868e2a 100644 --- a/android/src/main/java/com/cloudwebrtc/webrtc/record/VideoFileRenderer.java +++ b/android/src/main/java/com/cloudwebrtc/webrtc/record/VideoFileRenderer.java @@ -1,3 +1,5 @@ +// Modifications by Signify, Copyright 2025, Signify Holding - SPDX-License-Identifier: MIT + package com.cloudwebrtc.webrtc.record; import android.media.MediaCodec; @@ -19,6 +21,7 @@ import java.io.IOException; import java.nio.ByteBuffer; +import java.util.concurrent.CountDownLatch; class VideoFileRenderer implements VideoSink, SamplesReadyCallback { private static final String TAG = "VideoFileRenderer"; @@ -127,27 +130,47 @@ private void renderFrameOnRenderThread(VideoFrame frame) { /** * Release all resources. All already posted frames will be rendered first. */ + // Start Signify modification void release() { isRunning = false; - if (audioThreadHandler != null) + CountDownLatch latch = new CountDownLatch(audioThreadHandler != null ? 2 : 1); + if (audioThreadHandler != null) { audioThreadHandler.post(() -> { - if (audioEncoder != null) { - audioEncoder.stop(); - audioEncoder.release(); + try{ + if (audioEncoder != null) { + audioEncoder.stop(); + audioEncoder.release(); + } + audioThread.quit(); + } finally { + latch.countDown(); } - audioThread.quit(); }); + } + renderThreadHandler.post(() -> { - if (encoder != null) { - encoder.stop(); - encoder.release(); + try { + if (encoder != null) { + encoder.stop(); + encoder.release(); + } + eglBase.release(); + mediaMuxer.stop(); + mediaMuxer.release(); + renderThread.quit(); + } finally { + latch.countDown(); } - eglBase.release(); - mediaMuxer.stop(); - mediaMuxer.release(); - renderThread.quit(); }); + + try { + latch.await(); + } catch (InterruptedException e) { + Log.e(TAG, "Release interrupted", e); + Thread.currentThread().interrupt(); + } } + // End Signify modification private boolean encoderStarted = false; private volatile boolean muxerStarted = false; diff --git a/common/darwin/Classes/FlutterWebRTCPlugin.m b/common/darwin/Classes/FlutterWebRTCPlugin.m index 6a4749d9db..73f01aebe1 100644 --- a/common/darwin/Classes/FlutterWebRTCPlugin.m +++ b/common/darwin/Classes/FlutterWebRTCPlugin.m @@ -6,9 +6,9 @@ #import "FlutterRTCMediaStream.h" #import "FlutterRTCPeerConnection.h" #import "FlutterRTCVideoRenderer.h" -#import "FlutterRTCMediaRecorder.h" #import "FlutterRTCFrameCryptor.h" #if TARGET_OS_IPHONE +#import "FlutterRTCMediaRecorder.h" #import "FlutterRTCVideoPlatformViewFactory.h" #import "FlutterRTCVideoPlatformViewController.h" #endif @@ -1507,8 +1507,9 @@ - (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result { message:[NSString stringWithFormat:@"Error: peerConnection not found!"] details:nil]); } +#if TARGET_OS_IOS } else if ([@"startRecordToFile" isEqualToString:call.method]){ - #if TARGET_OS_IOS + NSDictionary* argsMap = call.arguments; NSNumber* recorderId = argsMap[@"recorderId"]; NSString* path = argsMap[@"path"]; @@ -1527,10 +1528,7 @@ - (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result { ]; } result(nil); - #endif - } else if ([@"stopRecordToFile" isEqualToString:call.method]) { - #if TARGET_OS_IOS NSDictionary* argsMap = call.arguments; NSNumber* recorderId = argsMap[@"recorderId"]; FlutterRTCMediaRecorder* recorder = self.recorders[recorderId]; @@ -1542,7 +1540,7 @@ - (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result { message:[NSString stringWithFormat:@"Error: recorder with id %@ not found!",recorderId] details:nil]); } - #endif +#endif } else { [self handleFrameCryptorMethodCall:call result:result]; } diff --git a/example/lib/src/get_user_media_sample.dart b/example/lib/src/get_user_media_sample.dart index e02e65dd37..9c2dea81d0 100644 --- a/example/lib/src/get_user_media_sample.dart +++ b/example/lib/src/get_user_media_sample.dart @@ -125,7 +125,7 @@ class _GetUserMediaSampleState extends State { if (await file.exists()) { await file.delete(); } - _mediaRecorder = MediaRecorder(); + _mediaRecorder = MediaRecorder(albumName: 'FlutterWebRTC'); setState(() {}); final videoTrack = _localStream! @@ -145,7 +145,7 @@ class _GetUserMediaSampleState extends State { } // album name works only for android, for ios use gallerySaver - await _mediaRecorder?.stop(albumName: 'FlutterWebRTC'); + await _mediaRecorder?.stop(); setState(() { _mediaRecorder = null; }); diff --git a/ios/flutter_webrtc.podspec b/ios/flutter_webrtc.podspec index 379bae55dd..778368a8a0 100644 --- a/ios/flutter_webrtc.podspec +++ b/ios/flutter_webrtc.podspec @@ -3,7 +3,7 @@ # Pod::Spec.new do |s| s.name = 'flutter_webrtc' - s.version = '0.12.6' + s.version = '0.14.0' s.summary = 'Flutter WebRTC plugin for iOS.' s.description = <<-DESC A new flutter plugin project. @@ -15,7 +15,7 @@ A new flutter plugin project. s.source_files = 'Classes/**/*' s.public_header_files = 'Classes/**/*.h' s.dependency 'Flutter' - s.dependency 'WebRTC-SDK', '125.6422.06' + s.dependency 'WebRTC-SDK', '125.6422.07' s.ios.deployment_target = '13.0' s.static_framework = true s.pod_target_xcconfig = { diff --git a/lib/flutter_webrtc.dart b/lib/flutter_webrtc.dart index 7201f77efa..b7dd3a8fc6 100644 --- a/lib/flutter_webrtc.dart +++ b/lib/flutter_webrtc.dart @@ -7,6 +7,7 @@ export 'src/helper.dart'; export 'src/desktop_capturer.dart'; export 'src/media_devices.dart'; export 'src/media_recorder.dart'; +export 'src/video_renderer_extension.dart'; export 'src/native/factory_impl.dart' if (dart.library.js_interop) 'src/web/factory_impl.dart'; export 'src/native/rtc_video_renderer_impl.dart' diff --git a/lib/src/media_recorder.dart b/lib/src/media_recorder.dart index 2295dd353e..370cfa9c78 100644 --- a/lib/src/media_recorder.dart +++ b/lib/src/media_recorder.dart @@ -1,9 +1,17 @@ +import 'package:flutter/foundation.dart'; + import 'package:webrtc_interface/webrtc_interface.dart' as rtc; import '../flutter_webrtc.dart'; +import 'native/media_recorder_impl.dart' show MediaRecorderNative; class MediaRecorder extends rtc.MediaRecorder { - MediaRecorder() : _delegate = mediaRecorder(); + MediaRecorder({ + String? albumName, + }) : _delegate = (kIsWeb || kIsWasm) + ? mediaRecorder() + : MediaRecorderNative(albumName: albumName); + final rtc.MediaRecorder _delegate; @override @@ -21,8 +29,7 @@ class MediaRecorder extends rtc.MediaRecorder { } @override - Future stop({String? albumName}) => - _delegate.stop(albumName: albumName ?? "FlutterWebRtc"); + Future stop() => _delegate.stop(); @override void startWeb( diff --git a/lib/src/native/media_recorder_impl.dart b/lib/src/native/media_recorder_impl.dart index 795e4c2127..15f4ae22dd 100644 --- a/lib/src/native/media_recorder_impl.dart +++ b/lib/src/native/media_recorder_impl.dart @@ -7,9 +7,13 @@ import 'media_stream_track_impl.dart'; import 'utils.dart'; class MediaRecorderNative extends MediaRecorder { + MediaRecorderNative({ + String? albumName = 'FlutterWebRTC', + }) : _albumName = albumName; static final _random = Random(); final _recorderId = _random.nextInt(0x7FFFFFFF); var _isStarted = false; + final String? _albumName; @override Future start( @@ -42,13 +46,13 @@ class MediaRecorderNative extends MediaRecorder { } @override - Future stop({String? albumName}) async { + Future stop() async { if (!_isStarted) { throw "Media recorder not started!"; } return await WebRTC.invokeMethod('stopRecordToFile', { 'recorderId': _recorderId, - 'albumName': albumName, + 'albumName': _albumName, }); } } diff --git a/lib/src/native/rtc_video_platform_view_controller.dart b/lib/src/native/rtc_video_platform_view_controller.dart index 6cb2a33b87..e9eeb1d51c 100644 --- a/lib/src/native/rtc_video_platform_view_controller.dart +++ b/lib/src/native/rtc_video_platform_view_controller.dart @@ -32,9 +32,6 @@ class RTCVideoPlatformViewController extends ValueNotifier @override int get videoHeight => value.height.toInt(); - @override - RTCVideoValue get videoValue => value; - @override int? get textureId => _viewId; diff --git a/lib/src/native/rtc_video_renderer_impl.dart b/lib/src/native/rtc_video_renderer_impl.dart index ba050c6e80..c2a46cba75 100644 --- a/lib/src/native/rtc_video_renderer_impl.dart +++ b/lib/src/native/rtc_video_renderer_impl.dart @@ -38,9 +38,6 @@ class RTCVideoRenderer extends ValueNotifier @override int get videoHeight => value.height.toInt(); - @override - RTCVideoValue get videoValue => value; - @override int? get textureId => _textureId; diff --git a/lib/src/video_renderer_extension.dart b/lib/src/video_renderer_extension.dart new file mode 100644 index 0000000000..fa8b7ac78b --- /dev/null +++ b/lib/src/video_renderer_extension.dart @@ -0,0 +1,5 @@ +import 'package:flutter_webrtc/flutter_webrtc.dart'; + +extension VideoRendererExtension on RTCVideoRenderer { + RTCVideoValue get videoValue => value; +} diff --git a/lib/src/web/rtc_video_renderer_impl.dart b/lib/src/web/rtc_video_renderer_impl.dart index 70580fc717..69df097e0c 100644 --- a/lib/src/web/rtc_video_renderer_impl.dart +++ b/lib/src/web/rtc_video_renderer_impl.dart @@ -71,9 +71,6 @@ class RTCVideoRenderer extends ValueNotifier @override int get videoHeight => value.height.toInt(); - @override - RTCVideoValue get videoValue => value; - @override int get textureId => _textureId; diff --git a/macos/flutter_webrtc.podspec b/macos/flutter_webrtc.podspec index d8e96f3d2a..7baf7ea75f 100644 --- a/macos/flutter_webrtc.podspec +++ b/macos/flutter_webrtc.podspec @@ -3,7 +3,7 @@ # Pod::Spec.new do |s| s.name = 'flutter_webrtc' - s.version = '0.12.6' + s.version = '0.14.0' s.summary = 'Flutter WebRTC plugin for macOS.' s.description = <<-DESC A new flutter plugin project. @@ -15,6 +15,6 @@ A new flutter plugin project. s.source_files = ['Classes/**/*'] s.dependency 'FlutterMacOS' - s.dependency 'WebRTC-SDK', '125.6422.06' + s.dependency 'WebRTC-SDK', '125.6422.07' s.osx.deployment_target = '10.14' end diff --git a/pubspec.yaml b/pubspec.yaml index 62677b5477..e3e380b6f5 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: flutter_webrtc description: Flutter WebRTC plugin for iOS/Android/Destkop/Web, based on GoogleWebRTC. -version: 0.13.2 +version: 0.14.0 homepage: https://github.com/cloudwebrtc/flutter-webrtc environment: sdk: ">=3.3.0 <4.0.0" @@ -8,12 +8,12 @@ environment: dependencies: collection: ^1.17.0 - dart_webrtc: ^1.5.4 + dart_webrtc: ^1.5.3+hotfix.2 flutter: sdk: flutter path_provider: ^2.0.2 web: ^1.0.0 - webrtc_interface: ^1.2.3 + webrtc_interface: ^1.2.2+hotfix.2 dev_dependencies: flutter_test: