Skip to content

Commit

Permalink
fix: AgoraVideoView crash when dispose after RtcEngine.release
Browse files Browse the repository at this point in the history
  • Loading branch information
littleGnAl committed Mar 4, 2024
1 parent 1c747f9 commit de7f3ce
Show file tree
Hide file tree
Showing 9 changed files with 111 additions and 33 deletions.
2 changes: 1 addition & 1 deletion android/src/main/cpp/iris_rtc_rendering_android.cc
Original file line number Diff line number Diff line change
Expand Up @@ -711,7 +711,7 @@ class NativeTextureRenderer final
}
}

~NativeTextureRenderer() final { Dispose(); }
~NativeTextureRenderer() final {}

void OnVideoFrameReceived(const void *videoFrame,
const IrisRtcVideoFrameConfig &config,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ void releaseRef() {
class PlatformRenderPool {

private final Map<Integer, SimpleRef> renders = new HashMap<>();

SimpleRef createView(int platformViewId,
Context context,
AgoraPlatformViewFactory.PlatformViewProvider viewProvider) {
Expand Down Expand Up @@ -182,10 +183,8 @@ public void onMethodCall(@NonNull MethodCall call, @NonNull MethodChannel.Result
case "createTextureRender": {
final Map<?, ?> args = (Map<?, ?>) call.arguments;

@SuppressWarnings("ConstantConditions")
final long irisRtcRenderingHandle = getLong(args.get("irisRtcRenderingHandle"));
@SuppressWarnings("ConstantConditions")
final long uid = getLong(args.get("uid"));
@SuppressWarnings("ConstantConditions") final long irisRtcRenderingHandle = getLong(args.get("irisRtcRenderingHandle"));
@SuppressWarnings("ConstantConditions") final long uid = getLong(args.get("uid"));
final String channelId = (String) args.get("channelId");
final int videoSourceType = (int) args.get("videoSourceType");
final int videoViewSetupMode = (int) args.get("videoViewSetupMode");
Expand All @@ -205,13 +204,25 @@ public void onMethodCall(@NonNull MethodCall call, @NonNull MethodChannel.Result
result.success(success);
break;
}
case "dispose": {
disposeAllRenderers();
result.success(true);
break;
}
case "updateTextureRenderData":
default:
result.notImplemented();
break;
}
}

private void disposeAllRenderers() {
for (final TextureRenderer textureRenderer : textureRendererMap.values()) {
textureRenderer.dispose();
}
textureRendererMap.clear();
}

/**
* Flutter may convert a long to int type in java, we force parse a long value via this function
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,6 @@ class GlobalVideoViewControllerIO extends GlobalVideoViewControllerPlatfrom {
@override
int get irisRtcRenderingHandle => _irisRtcRenderingHandle;

final Map<int, Completer<void>> _destroyTextureRenderCompleters = {};
bool _isDetachVFBMing = false;

void _hotRestartListener(Object? message) {
assert(() {
// Free `IrisRtcRendering` when hot restart
Expand Down Expand Up @@ -64,34 +61,28 @@ class GlobalVideoViewControllerIO extends GlobalVideoViewControllerPlatfrom {
return;
}

final irisRtcRenderingHandle = _irisRtcRenderingHandle;
_irisRtcRenderingHandle = 0;

irisMethodChannel.removeHotRestartListener(_hotRestartListener);

_isDetachVFBMing = true;

// Need wait for all `destroyTextureRender` functions are called completed before
// `FreeIrisVideoFrameBufferManager`, if not, the `destroyTextureRender`(call
// `IrisVideoFrameBufferManager.DisableVideoFrameBuffer` in native side) and
// `FreeIrisVideoFrameBufferManager` will be called parallelly, which will cause crash.
for (final completer in _destroyTextureRenderCompleters.values) {
if (!completer.isCompleted) {
await completer.future;
}
}
_destroyTextureRenderCompleters.clear();
await methodChannel.invokeMethod('dispose');

await irisMethodChannel.invokeMethod(IrisMethodCall(
'FreeIrisRtcRendering',
jsonEncode({
'irisRtcEngineNativeHandle': irisRtcEngineIntPtr,
'irisRtcRenderingHandle': _irisRtcRenderingHandle,
'irisRtcRenderingHandle': irisRtcRenderingHandle,
}),
));
_irisRtcRenderingHandle = 0;
}

@override
Future<int> createTextureRender(int uid, String channelId,
int videoSourceType, int videoViewSetupMode) async {
if (_irisRtcRenderingHandle == 0) {
return kTextureNotInit;
}
final textureId =
await methodChannel.invokeMethod<int>('createTextureRender', {
'irisRtcRenderingHandle': _irisRtcRenderingHandle,
Expand All @@ -106,16 +97,11 @@ class GlobalVideoViewControllerIO extends GlobalVideoViewControllerPlatfrom {
/// Call `IrisVideoFrameBufferManager.DisableVideoFrameBuffer` in the native side
@override
Future<void> destroyTextureRender(int textureId) async {
_destroyTextureRenderCompleters.putIfAbsent(
textureId, () => Completer<void>());
if (_irisRtcRenderingHandle == 0) {
return;
}

await methodChannel.invokeMethod('destroyTextureRender', textureId);

_destroyTextureRenderCompleters[textureId]?.complete(null);

if (!_isDetachVFBMing) {
_destroyTextureRenderCompleters.remove(textureId);
}
}

/// Decrease the ref count of the native view(`UIView` in iOS) of the `platformViewId`.
Expand Down
12 changes: 12 additions & 0 deletions shared/darwin/VideoViewController.mm
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,8 @@ @interface VideoViewController ()
@property(nonatomic) PlatformRenderPool* platformRenderPool;

@property(nonatomic, strong) FlutterMethodChannel *methodChannel;

- (void)dispose;
@end

@implementation VideoViewController
Expand Down Expand Up @@ -185,6 +187,9 @@ - (void)onMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result {
int64_t platformViewId = [platformViewIdValue longLongValue];
[self dePlatformRenderRef:platformViewId];

result(@(YES));
} else if ([@"dispose" isEqualToString:call.method]) {
[self dispose];
result(@(YES));
}
}
Expand Down Expand Up @@ -231,4 +236,11 @@ - (BOOL)destroyTextureRender:(int64_t)textureId {
return NO;
}

- (void)dispose {
for (TextureRender * textureRender in self.textureRenders.allValues) {
[textureRender dispose];
}
[self.textureRenders removeAllObjects];
}

@end
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ class FakeIrisMethodChannelConfig {
this.isFakeRemoveHotRestartListener = true,
this.isFakeDispose = true,
this.delayInvokeMethod = const {},
this.fakeInvokeMethods = const {},
});

final bool isFakeInitilize;
Expand All @@ -19,6 +20,7 @@ class FakeIrisMethodChannelConfig {
final bool isFakeRemoveHotRestartListener;
final bool isFakeDispose;
final Map<String, int> delayInvokeMethod;
final Map<String, CallApiResult> fakeInvokeMethods;

FakeIrisMethodChannelConfig copyWith({
bool? isFakeInitilize,
Expand All @@ -28,6 +30,7 @@ class FakeIrisMethodChannelConfig {
bool? isFakeRemoveHotRestartListener,
bool? isFakeDispose,
Map<String, int>? delayInvokeMethod,
Map<String, CallApiResult>? fakeInvokeMethods
}) {
return FakeIrisMethodChannelConfig(
isFakeInitilize: isFakeInitilize ?? this.isFakeInitilize,
Expand All @@ -40,6 +43,7 @@ class FakeIrisMethodChannelConfig {
isFakeRemoveHotRestartListener ?? this.isFakeRemoveHotRestartListener,
isFakeDispose: isFakeDispose ?? this.isFakeDispose,
delayInvokeMethod: delayInvokeMethod ?? this.delayInvokeMethod,
fakeInvokeMethods: fakeInvokeMethods ?? this.fakeInvokeMethods,
);
}
}
Expand Down Expand Up @@ -77,6 +81,9 @@ class FakeIrisMethodChannel extends IrisMethodChannel {

if (_config.isFakeInvokeMethod) {
await __maybeDelay();
if (_config.fakeInvokeMethods.containsKey(methodCall.funcName)) {
return _config.fakeInvokeMethods[methodCall.funcName]!;
}
return CallApiResult(data: {'result': 0}, irisReturnCode: 0);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'dart:math';

import 'package:agora_rtc_engine/agora_rtc_engine.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:agora_rtc_engine/src/impl/agora_rtc_engine_impl.dart';
import '../fake/fake_iris_method_channel.dart';

class _RenderViewWidget extends StatefulWidget {
const _RenderViewWidget({
Expand Down Expand Up @@ -325,4 +322,47 @@ void testCases() {

expect(find.byType(AgoraVideoView), findsNothing);
});

testWidgets('Dispose AgoraVideoView after RtcEngine.release',
(WidgetTester tester) async {
final videoViewCreatedCompleter = Completer<bool>();
final key = GlobalKey<_RenderViewWidgetState>();

await tester.pumpWidget(_RenderViewWidget(
key: key,
builder: (context, engine) {
return SizedBox(
height: 100,
width: 100,
child: AgoraVideoView(
controller: VideoViewController(
rtcEngine: engine,
canvas: const VideoCanvas(uid: 0),
useFlutterTexture: true,
),
onAgoraVideoViewCreated: (viewId) {
if (!videoViewCreatedCompleter.isCompleted) {
videoViewCreatedCompleter.complete(true);
}
},
),
);
},
));

await tester.pumpAndSettle(const Duration(milliseconds: 5000));
// pumpAndSettle again to ensure the `AgoraVideoView` shown
await tester.pumpAndSettle(const Duration(milliseconds: 5000));

await videoViewCreatedCompleter.future;

// Call `RtcEngine.release` before `AgoraVideoView` dispose
await key.currentState?._dispose();

await tester.pumpWidget(Container());
await tester.pumpAndSettle(const Duration(milliseconds: 5000));
await Future.delayed(const Duration(seconds: 5));

expect(find.byType(AgoraVideoView), findsNothing);
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import 'package:agora_rtc_engine/src/impl/agora_rtc_engine_impl.dart';
import '../fake/fake_iris_method_channel.dart';
import 'package:agora_rtc_engine/src/impl/platform/io/global_video_view_controller_platform_io.dart';
import 'package:agora_rtc_engine/src/impl/platform/io/native_iris_api_engine_binding_delegate.dart';
import 'package:iris_method_channel/iris_method_channel.dart';

class _RenderViewWidget extends StatefulWidget {
const _RenderViewWidget({
Expand Down Expand Up @@ -191,6 +192,11 @@ void testCases() {

setUp(() {
irisMethodChannel.reset();
irisMethodChannel.config =
FakeIrisMethodChannelConfig(fakeInvokeMethods: {
'CreateIrisRtcRendering': CallApiResult(
data: {'irisRtcRenderingHandle': 100}, irisReturnCode: 0)
});
});

group(
Expand Down
2 changes: 2 additions & 0 deletions windows/include/agora_rtc_engine/video_view_controller.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ class VideoViewController

bool DestroyTextureRender(int64_t textureId);

void Dispose();

public:
VideoViewController(flutter::TextureRegistrar *texture_registrar, flutter::BinaryMessenger *messenger_);
virtual ~VideoViewController();
Expand Down
14 changes: 14 additions & 0 deletions windows/video_view_controller.cc
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,11 @@ void VideoViewController::HandleMethodCall(

result->Success(flutter::EncodableValue(true));
}
else if (method.compare("dispose") == 0)
{
Dispose();
result->Success(flutter::EncodableValue(true));
}
else if (method.compare("updateTextureRenderData") == 0)
{
}
Expand Down Expand Up @@ -171,4 +176,13 @@ bool VideoViewController::DestroyTextureRender(int64_t textureId)
return true;
}
return false;
}

void VideoViewController::Dispose()
{
for (const auto &entry : renderers_)
{
entry.second->Dispose();
}
renderers_.clear();
}

0 comments on commit de7f3ce

Please sign in to comment.