diff --git a/lib/src/impl/agora_rtc_engine_impl.dart b/lib/src/impl/agora_rtc_engine_impl.dart index 09e4ed41d..535cb1d23 100644 --- a/lib/src/impl/agora_rtc_engine_impl.dart +++ b/lib/src/impl/agora_rtc_engine_impl.dart @@ -207,10 +207,10 @@ class RtcEngineImpl extends rtc_engine_ex_binding.RtcEngineExImpl @internal final MethodChannel engineMethodChannel = const MethodChannel('agora_rtc_ng'); - static RtcEngineEx create() { + static RtcEngineEx create({IrisMethodChannel? irisMethodChannel}) { if (_instance != null) return _instance!; - _instance = RtcEngineImpl._(IrisMethodChannel()); + _instance = RtcEngineImpl._(irisMethodChannel ?? IrisMethodChannel()); return _instance!; } diff --git a/lib/src/impl/video_view_controller_impl.dart b/lib/src/impl/video_view_controller_impl.dart index b25596b93..939fe6500 100644 --- a/lib/src/impl/video_view_controller_impl.dart +++ b/lib/src/impl/video_view_controller_impl.dart @@ -92,7 +92,12 @@ mixin VideoViewControllerBaseMixin implements VideoViewControllerBase { mediaPlayerId: canvas.mediaPlayerId, ); if (canvas.uid != 0) { - await rtcEngine.setupRemoteVideo(videoCanvas); + if (connection != null && rtcEngine is RtcEngineEx) { + await (rtcEngine as RtcEngineEx) + .setupRemoteVideoEx(canvas: videoCanvas, connection: connection!); + } else { + await rtcEngine.setupRemoteVideo(videoCanvas); + } } else { await rtcEngine.setupLocalVideo(videoCanvas); } diff --git a/lib/src/render/video_view_controller.dart b/lib/src/render/video_view_controller.dart index 8ac020a48..05004759f 100644 --- a/lib/src/render/video_view_controller.dart +++ b/lib/src/render/video_view_controller.dart @@ -71,7 +71,9 @@ class VideoViewController required this.connection, this.useFlutterTexture = false, this.useAndroidSurfaceView = false}) - : assert(connection.channelId != null); + : assert(canvas.uid != null && canvas.uid != 0, + 'Remote uid can not be null or 0'), + assert(connection.channelId != null); @override final RtcEngine rtcEngine; diff --git a/test_shard/integration_test_app/integration_test/fake/fake_iris_method_channel.dart b/test_shard/integration_test_app/integration_test/fake/fake_iris_method_channel.dart new file mode 100644 index 000000000..88b5ff26b --- /dev/null +++ b/test_shard/integration_test_app/integration_test/fake/fake_iris_method_channel.dart @@ -0,0 +1,35 @@ +import 'package:flutter/foundation.dart'; +import 'package:iris_method_channel/iris_method_channel.dart'; + +class FakeIrisMethodChannel extends IrisMethodChannel { + final List methodCallQueue = []; + + @override + Future initilize(NativeBindingsProvider provider) async {} + + @override + Future invokeMethod(IrisMethodCall methodCall) async { + methodCallQueue.add(methodCall); + return CallApiResult(data: {'result': 0}, irisReturnCode: 0); + } + + @override + int getNativeHandle() { + return 100; + } + + @override + VoidCallback addHotRestartListener(HotRestartListener listener) { + return () {}; + } + + @override + void removeHotRestartListener(HotRestartListener listener) {} + + @override + Future dispose() async {} + + void reset() { + methodCallQueue.clear(); + } +} diff --git a/test_shard/integration_test_app/integration_test/testcases/agora_video_view_testcases.dart b/test_shard/integration_test_app/integration_test/testcases/agora_video_view_testcases.dart index 1c1a34dc5..6de0fb9a3 100644 --- a/test_shard/integration_test_app/integration_test/testcases/agora_video_view_testcases.dart +++ b/test_shard/integration_test_app/integration_test/testcases/agora_video_view_testcases.dart @@ -1,18 +1,24 @@ +import 'dart:convert'; import 'dart:io'; 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({ Key? key, required this.builder, + required this.rtcEngine, }) : super(key: key); final Function(BuildContext context, RtcEngine engine) builder; + final RtcEngine rtcEngine; + @override State<_RenderViewWidget> createState() => _RenderViewWidgetState(); } @@ -41,7 +47,7 @@ class _RenderViewWidgetState extends State<_RenderViewWidget> { String engineAppId = const String.fromEnvironment('TEST_APP_ID', defaultValue: ''); - _engine = createAgoraRtcEngine(); + _engine = widget.rtcEngine; await _engine.initialize(RtcEngineContext( appId: engineAppId, areaCode: AreaCode.areaCodeGlob.value(), @@ -75,7 +81,12 @@ void testCases() { testWidgets( 'Show local AgoraVideoView after RtcEngine.initialize', (WidgetTester tester) async { + final irisMethodChannel = FakeIrisMethodChannel(); + final rtcEngine = + RtcEngineImpl.create(irisMethodChannel: irisMethodChannel); + await tester.pumpWidget(_RenderViewWidget( + rtcEngine: rtcEngine, builder: (context, engine) { return SizedBox( height: 100, @@ -90,6 +101,8 @@ void testCases() { }, )); + await tester.pumpAndSettle(const Duration(milliseconds: 5000)); + // pumpAndSettle again to ensure the `AgoraVideoView` shown await tester.pumpAndSettle(const Duration(milliseconds: 5000)); if (defaultTargetPlatform == TargetPlatform.android) { @@ -100,6 +113,13 @@ void testCases() { expect(find.byType(UiKitView), findsOneWidget); } + final setupLocalVideoCalls = irisMethodChannel.methodCallQueue + .where((e) => e.funcName == 'RtcEngine_setupLocalVideo') + .toList(); + + final jsonMap2 = jsonDecode(setupLocalVideoCalls[0].params); + expect(jsonMap2['canvas']['view'] != 0, isTrue); + await tester.pumpWidget(Container()); await tester.pumpAndSettle(const Duration(milliseconds: 5000)); @@ -107,4 +127,118 @@ void testCases() { }, skip: !(Platform.isAndroid || Platform.isIOS), ); + + testWidgets( + 'Switch local/remote AgoraVideoView with RtcConnection', + (WidgetTester tester) async { + final irisMethodChannel = FakeIrisMethodChannel(); + final rtcEngine = + RtcEngineImpl.create(irisMethodChannel: irisMethodChannel); + + await tester.pumpWidget(_RenderViewWidget( + rtcEngine: rtcEngine, + builder: (context, engine) { + return Column( + children: [ + SizedBox( + height: 100, + width: 100, + child: AgoraVideoView( + controller: VideoViewController( + rtcEngine: engine, + canvas: const VideoCanvas(uid: 0), + ), + ), + ), + SizedBox( + height: 100, + width: 100, + child: AgoraVideoView( + controller: VideoViewController.remote( + rtcEngine: engine, + canvas: const VideoCanvas(uid: 1000), + connection: const RtcConnection( + channelId: 'switch_video_view', + localUid: 1000, + ), + ), + ), + ) + ], + ); + }, + )); + + await tester.pumpAndSettle(const Duration(milliseconds: 5000)); + // pumpAndSettle again to ensure the `AgoraVideoView` shown + await tester.pumpAndSettle(const Duration(milliseconds: 5000)); + + if (defaultTargetPlatform == TargetPlatform.android) { + expect(find.byType(AndroidView), findsNWidgets(2)); + } + + if (defaultTargetPlatform == TargetPlatform.iOS) { + expect(find.byType(UiKitView), findsNWidgets(2)); + } + + // Clear the methodCall records + irisMethodChannel.reset(); + + await tester.pumpWidget(_RenderViewWidget( + rtcEngine: rtcEngine, + builder: (context, engine) { + return Column( + children: [ + SizedBox( + height: 100, + width: 100, + child: AgoraVideoView( + controller: VideoViewController.remote( + rtcEngine: engine, + canvas: const VideoCanvas(uid: 1000), + connection: const RtcConnection( + channelId: 'switch_video_view', + localUid: 1000, + ), + ), + ), + ), + SizedBox( + height: 100, + width: 100, + child: AgoraVideoView( + controller: VideoViewController( + rtcEngine: engine, + canvas: const VideoCanvas(uid: 0), + ), + ), + ) + ], + ); + }, + )); + + await tester.pumpAndSettle(const Duration(milliseconds: 5000)); + if (defaultTargetPlatform == TargetPlatform.android) { + expect(find.byType(AndroidView), findsNWidgets(2)); + } + if (defaultTargetPlatform == TargetPlatform.iOS) { + expect(find.byType(UiKitView), findsNWidgets(2)); + } + + final setupRemoteVideoExCalls = irisMethodChannel.methodCallQueue + .where((e) => e.funcName == 'RtcEngineEx_setupRemoteVideoEx') + .toList(); + + final jsonMap1 = jsonDecode(setupRemoteVideoExCalls[0].params); + expect(jsonMap1['canvas']['view'] == 0, isTrue); + + final jsonMap2 = jsonDecode(setupRemoteVideoExCalls[1].params); + expect(jsonMap2['canvas']['view'] != 0, isTrue); + + await tester.pumpWidget(Container()); + await tester.pumpAndSettle(const Duration(milliseconds: 5000)); + }, + skip: !(Platform.isAndroid || Platform.isIOS), + ); }