Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: lazy initialize AgoraVideoView #905

Merged
merged 10 commits into from
Feb 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 34 additions & 1 deletion lib/src/impl/agora_rtc_engine_impl.dart
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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),
);
}
Loading