Skip to content

Commit

Permalink
fix: lazy initialize AgoraVideoView (#905)
Browse files Browse the repository at this point in the history
fix: lazy initialize AgoraVideoView
  • Loading branch information
littleGnAl committed Feb 21, 2023
1 parent 1d9fa0b commit d370c67
Show file tree
Hide file tree
Showing 11 changed files with 255 additions and 118 deletions.
35 changes: 34 additions & 1 deletion lib/src/impl/agora_rtc_engine_impl.dart
Expand Up @@ -35,7 +35,8 @@ import 'package:agora_rtc_engine/src/impl/audio_device_manager_impl.dart'
as audio_device_manager_impl;
import 'package:agora_rtc_engine/src/binding/impl_forward_export.dart';
import 'package:agora_rtc_engine/src/impl/native_iris_api_engine_binding_delegate.dart';
import 'package:flutter/foundation.dart' show defaultTargetPlatform;
import 'package:flutter/foundation.dart'
show ChangeNotifier, defaultTargetPlatform;
import 'package:flutter/services.dart' show MethodChannel;
import 'package:flutter/widgets.dart'
show
Expand Down Expand Up @@ -168,6 +169,20 @@ class _Lifecycle with WidgetsBindingObserver {
}
}

@internal
class InitializationState extends ChangeNotifier {
bool _isInitialzed = false;
bool get isInitialzed => _isInitialzed;
set isInitialzed(bool value) {
if (_isInitialzed == value) {
return;
}

_isInitialzed = value;
notifyListeners();
}
}

class RtcEngineImpl extends rtc_engine_ex_binding.RtcEngineExImpl
implements RtcEngineEx {
RtcEngineImpl._(IrisMethodChannel irisMethodChannel)
Expand All @@ -177,6 +192,10 @@ class RtcEngineImpl extends rtc_engine_ex_binding.RtcEngineExImpl

static RtcEngineImpl? _instance;

final InitializationState _rtcEngineState = InitializationState();
@internal
bool get isInitialzed => _rtcEngineState.isInitialzed;

final _rtcEngineImplScopedKey = const TypedScopedKey(RtcEngineImpl);

late final GlobalVideoViewController _globalVideoViewController;
Expand Down Expand Up @@ -214,6 +233,8 @@ class RtcEngineImpl extends rtc_engine_ex_binding.RtcEngineExImpl
.attachVideoFrameBufferManager(irisMethodChannel.getNativeHandle());

irisMethodChannel.addHotRestartListener(_hotRestartListener);

_rtcEngineState.isInitialzed = true;
}

void _hotRestartListener(Object? message) {
Expand Down Expand Up @@ -262,6 +283,16 @@ class RtcEngineImpl extends rtc_engine_ex_binding.RtcEngineExImpl
await _initializeInternal(context);
}

@internal
void addInitializedCompletedListener(VoidCallback listener) {
_rtcEngineState.addListener(listener);
}

@internal
void removeInitializedCompletedListener(VoidCallback listener) {
_rtcEngineState.removeListener(listener);
}

@override
Future<void> release({bool sync = false}) async {
if (_instance == null) return;
Expand All @@ -273,6 +304,8 @@ class RtcEngineImpl extends rtc_engine_ex_binding.RtcEngineExImpl
_lifecycle = null;
}

_rtcEngineState.dispose();

await _objectPool.clear();

await _globalVideoViewController
Expand Down
51 changes: 50 additions & 1 deletion lib/src/impl/agora_video_view_impl.dart
@@ -1,5 +1,6 @@
import 'package:agora_rtc_engine/src/agora_base.dart';
import 'package:agora_rtc_engine/src/agora_media_base.dart';

import 'package:agora_rtc_engine/src/impl/video_view_controller_impl.dart';
import 'package:agora_rtc_engine/src/render/agora_video_view.dart';
import 'package:agora_rtc_engine/src/render/video_view_controller.dart';
Expand All @@ -11,9 +12,57 @@ import 'agora_rtc_renderer.dart';

// ignore_for_file: public_member_api_docs

class AgoraVideoViewState extends State<AgoraVideoView> with RtcRenderMixin {
class AgoraVideoViewState extends State<AgoraVideoView> {
AgoraVideoViewState() {
_listener = () {
bool isInitialzed = _controller(widget.controller).isInitialzed;
if (isInitialzed != _isInitialzed) {
setState(() {
_isInitialzed = isInitialzed;
});
}
};
}

late VoidCallback _listener;
late bool _isInitialzed;

VideoViewControllerBaseMixin _controller(VideoViewControllerBase controller) {
return controller as VideoViewControllerBaseMixin;
}

@override
void initState() {
super.initState();

_isInitialzed = _controller(widget.controller).isInitialzed;
_controller(widget.controller).addInitializedCompletedListener(_listener);
}

@override
void didUpdateWidget(covariant AgoraVideoView oldWidget) {
super.didUpdateWidget(oldWidget);

_controller(oldWidget.controller)
.removeInitializedCompletedListener(_listener);
// Refresh the `_isInitialzed` to the current widget.controller `isInitialzed`
_isInitialzed = _controller(widget.controller).isInitialzed;
_controller(widget.controller).addInitializedCompletedListener(_listener);
}

@override
void deactivate() {
super.deactivate();
_controller(widget.controller)
.removeInitializedCompletedListener(_listener);
}

@override
Widget build(BuildContext context) {
if (!_isInitialzed) {
return Container();
}

if (defaultTargetPlatform == TargetPlatform.macOS ||
defaultTargetPlatform == TargetPlatform.windows) {
return AgoraRtcRenderTexture(
Expand Down
17 changes: 17 additions & 0 deletions lib/src/impl/media_player_controller_impl.dart
Expand Up @@ -9,6 +9,7 @@ import 'package:agora_rtc_engine/src/impl/agora_rtc_engine_impl.dart';
import 'package:agora_rtc_engine/src/impl/media_player_impl.dart';
import 'package:agora_rtc_engine/src/impl/video_view_controller_impl.dart';
import 'package:agora_rtc_engine/src/render/media_player_controller.dart';
import 'package:flutter/foundation.dart';

class MediaPlayerControllerImpl
with VideoViewControllerBaseMixin
Expand All @@ -20,6 +21,10 @@ class MediaPlayerControllerImpl

final RtcEngine _rtcEngine;

final InitializationState _initState = InitializationState();
@override
bool get isInitialzed => _initState.isInitialzed;

@override
RtcEngine get rtcEngine => _rtcEngine;

Expand Down Expand Up @@ -329,6 +334,17 @@ class MediaPlayerControllerImpl
@override
Future<void> initialize() async {
_mediaPlayer = await rtcEngine.createMediaPlayer();
_initState.isInitialzed = true;
}

@override
void addInitializedCompletedListener(VoidCallback listener) {
_initState.addListener(listener);
}

@override
void removeInitializedCompletedListener(VoidCallback listener) {
_initState.removeListener(listener);
}

@override
Expand All @@ -348,6 +364,7 @@ class MediaPlayerControllerImpl

@override
Future<void> dispose() async {
_initState.dispose();
await (_mediaPlayer as MediaPlayerImpl).destroy();
await super.dispose();
await rtcEngine.destroyMediaPlayer(this);
Expand Down
15 changes: 15 additions & 0 deletions lib/src/impl/video_view_controller_impl.dart
Expand Up @@ -45,6 +45,21 @@ extension VideoViewControllerBaseExt on VideoViewControllerBase {
mixin VideoViewControllerBaseMixin implements VideoViewControllerBase {
int _textureId = kTextureNotInit;

@internal
bool get isInitialzed => (rtcEngine as RtcEngineImpl).isInitialzed;

@internal
void addInitializedCompletedListener(VoidCallback listener) {
final engine = rtcEngine as RtcEngineImpl;
engine.addInitializedCompletedListener(listener);
}

@internal
void removeInitializedCompletedListener(VoidCallback listener) {
final engine = rtcEngine as RtcEngineImpl;
engine.removeInitializedCompletedListener(listener);
}

@override
int getTextureId() => _textureId;

Expand Down
@@ -1,60 +1,81 @@
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:integration_test/integration_test.dart';
import 'package:integration_test_app/main.dart' as app;

class _RenderViewWidget extends StatefulWidget {
const _RenderViewWidget({
Key? key,
required this.rtcEngine,
required this.builder,
}) : super(key: key);

final Function(BuildContext context, RtcEngine engine) builder;

final RtcEngine rtcEngine;

@override
State<_RenderViewWidget> createState() => _RenderViewWidgetState();
}

class _RenderViewWidgetState extends State<_RenderViewWidget> {
late final RtcEngine _engine;

@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: widget.builder(context, widget.rtcEngine),
),
);
void initState() {
super.initState();
_initEngine();
}
}

void testCases() {
testWidgets('Show Local AgoraVideoView pressure test',
(WidgetTester tester) async {
app.main();
await tester.pumpAndSettle();
@override
void dispose() {
super.dispose();
_dispose();
}

Future<void> _dispose() async {
await _engine.leaveChannel();
await _engine.release();
}

Future<void> _initEngine() async {
String engineAppId = const String.fromEnvironment('TEST_APP_ID',
defaultValue: '<YOUR_APP_ID>');

final rtcEngine = createAgoraRtcEngine();
await rtcEngine.initialize(RtcEngineContext(
_engine = createAgoraRtcEngine();
await _engine.initialize(RtcEngineContext(
appId: engineAppId,
areaCode: AreaCode.areaCodeGlob.value(),
));

try {
await rtcEngine.enableVideo();
await rtcEngine.startPreview();
await _engine.enableVideo();
await _engine.startPreview();
} catch (e) {
debugPrint(e.toString());
}

for (int i = 0; i < 5; i++) {
await _engine.setClientRole(role: ClientRoleType.clientRoleBroadcaster);
await _engine.setAudioProfile(
profile: AudioProfileType.audioProfileDefault,
scenario: AudioScenarioType.audioScenarioGameStreaming,
);
}

@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: widget.builder(context, _engine),
),
);
}
}

void testCases() {
testWidgets(
'Show local AgoraVideoView after RtcEngine.initialize',
(WidgetTester tester) async {
await tester.pumpWidget(_RenderViewWidget(
rtcEngine: rtcEngine,
builder: (context, engine) {
return SizedBox(
height: 100,
Expand All @@ -71,12 +92,19 @@ void testCases() {

await tester.pumpAndSettle(const Duration(milliseconds: 5000));

if (defaultTargetPlatform == TargetPlatform.android) {
expect(find.byType(AndroidView), findsOneWidget);
}

if (defaultTargetPlatform == TargetPlatform.iOS) {
expect(find.byType(UiKitView), findsOneWidget);
}

await tester.pumpWidget(Container());
await tester.pumpAndSettle(const Duration(milliseconds: 5000));
}

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

await rtcEngine.release(sync: true);
});
expect(find.byType(AgoraVideoView), findsNothing);
},
skip: !(Platform.isAndroid || Platform.isIOS),
);
}

0 comments on commit d370c67

Please sign in to comment.