From 234c323907fbeea585f81b49a58e5f830a26a192 Mon Sep 17 00:00:00 2001 From: cloudwebrtc Date: Fri, 24 Jan 2025 14:31:37 +0800 Subject: [PATCH 1/3] chore: VideoRenderer improvements for iOS. --- example/lib/pages/connect.dart | 4 - example/lib/test_main.dart.text | 186 ++++++++++++++++++++++ lib/src/track/track.dart | 2 + lib/src/widgets/video_track_renderer.dart | 16 +- 4 files changed, 197 insertions(+), 11 deletions(-) create mode 100644 example/lib/test_main.dart.text diff --git a/example/lib/pages/connect.dart b/example/lib/pages/connect.dart index 5961c82ef..d88f6cfc1 100644 --- a/example/lib/pages/connect.dart +++ b/example/lib/pages/connect.dart @@ -47,10 +47,6 @@ class _ConnectPageState extends State { if (lkPlatformIs(PlatformType.android)) { _checkPermissions(); } - - if (lkPlatformIsMobile()) { - LiveKitClient.initialize(bypassVoiceProcessing: true); - } } @override diff --git a/example/lib/test_main.dart.text b/example/lib/test_main.dart.text new file mode 100644 index 000000000..2bbab5942 --- /dev/null +++ b/example/lib/test_main.dart.text @@ -0,0 +1,186 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:flutter_window_close/flutter_window_close.dart'; +import 'package:get/get.dart'; +import 'package:livekit_client/livekit_client.dart'; +import 'package:livekit_example/theme.dart'; +import 'package:logging/logging.dart'; +import 'package:intl/intl.dart'; +import 'package:uuid/uuid.dart'; +import 'pages/connect.dart'; +import 'utils.dart'; + +void main() async { + final format = DateFormat('HH:mm:ss'); + // configure logs for debugging + Logger.root.level = Level.FINE; + Logger.root.onRecord.listen((record) { + print('${format.format(record.time)}: ${record.message}'); + }); + + WidgetsFlutterBinding.ensureInitialized(); + + if (lkPlatformIsDesktop()) { + await FlutterWindowClose.setWindowShouldCloseHandler(() async { + await onWindowShouldClose?.call(); + return true; + }); + } + + const url = /*String.fromEnvironment('URL') ??*/ + 'wss://duansapp.livekit.cloud'; + const token = /*String.fromEnvironment('TOKEN') ??*/ + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3NTMxMTEwMjksImlzcyI6IkFQSVFaaFRwRVBNQ3NNRyIsIm5hbWUiOiJpcDE2IiwibmJmIjoxNzM1MTExMDI5LCJzdWIiOiJpcDE2IiwidmlkZW8iOnsicm9vbSI6ImxpdmUiLCJyb29tSm9pbiI6dHJ1ZX19.r-FsFl8cnk8qHZcAh-gq9TMcxOeWJyUmSRHYytDY9Vo'; + + runApp(const LiveKitTestPage( + url: url, + token: token, + )); +} + +class LiveKitTestPage extends StatefulWidget { + final String url; + final String token; + const LiveKitTestPage({super.key, required this.url, required this.token}); + + @override + State createState() => _LiveKitTestPageState(); +} + +class _LiveKitTestPageState extends State { + final Rx _roomState = Rx(null); + + Timer? _timer; + + final RxInt _seconds = 0.obs; + + @override + void initState() { + super.initState(); + _timer = Timer.periodic(const Duration(seconds: 1), (timer) { + _seconds.value++; + }); + } + + @override + void dispose() { + _disconnect(); + _timer?.cancel(); + super.dispose(); + } + + Future _connect() async { + _roomState.value = Room( + roomOptions: const RoomOptions( + enableVisualizer: true, adaptiveStream: true, dynacast: true)); + await _roomState.value!.connect(widget.url, widget.token); + await _roomState.value!.localParticipant?.setMicrophoneEnabled(true); + } + + Future _disconnect() async { + await _roomState.value?.disconnect(); + await _roomState.value?.dispose(); + } + + Future _reconnect() async { + await _disconnect(); + //await _requestToken(resume: true); + await _connect(); + } + + @override + Widget build(BuildContext context) { + return MaterialApp( + debugShowCheckedModeBanner: false, + home: Scaffold( + backgroundColor: Colors.black, + body: _mainView(), + )); + } + + Widget _mainView() { + return Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Obx(() { + return Text( + '${_seconds.value.toString()}s', + style: const TextStyle(color: Colors.white), + ); + }), + const SizedBox(height: 100), + Obx(() { + if (_roomState.value == null) { + return GestureContainer( + onTap: () { + showTipsToast('on tap connect'); + _connect(); + }, + color: Colors.greenAccent, + child: const Text( + 'connect', + ), + ); + } + return const LoadingView(); + }), + const SizedBox(height: 100), + GestureContainer( + onTap: () { + //showTipsToast("on tap reconnect"); + _reconnect(); + }, + color: Colors.greenAccent, + child: const Text( + 'reconnect', + ), + ) + ], + ), + ); + } + + void showTipsToast(String s) { + print(s); + } +} + +class GestureContainer extends StatelessWidget { + final Widget child; + final Color color; + final VoidCallback onTap; + const GestureContainer( + {super.key, + required this.child, + required this.color, + required this.onTap}); + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: onTap, + child: Container( + padding: const EdgeInsets.all(10), + decoration: BoxDecoration( + color: color, + borderRadius: BorderRadius.circular(10), + ), + child: child, + ), + ); + } +} + +class LoadingView extends StatelessWidget { + const LoadingView({super.key}); + + @override + Widget build(BuildContext context) { + return const Center( + child: CircularProgressIndicator(), + ); + } +} diff --git a/lib/src/track/track.dart b/lib/src/track/track.dart index cbddaabc0..c00edc91c 100644 --- a/lib/src/track/track.dart +++ b/lib/src/track/track.dart @@ -131,6 +131,8 @@ abstract class Track extends DisposableChangeNotifier await onStopped(); + await mediaStreamTrack.stop(); + logger.fine('$objectId.stop()'); _active = false; diff --git a/lib/src/widgets/video_track_renderer.dart b/lib/src/widgets/video_track_renderer.dart index 88f4b4796..a18ae2d19 100644 --- a/lib/src/widgets/video_track_renderer.dart +++ b/lib/src/widgets/video_track_renderer.dart @@ -70,11 +70,14 @@ class _VideoTrackRendererState extends State { late GlobalKey _internalKey; Future _initializeRenderer() async { - if (widget.renderMode == VideoRenderMode.platformView) { + if (lkPlatformIs(PlatformType.iOS) && + widget.renderMode == VideoRenderMode.platformView) { return Null as Future; } - _renderer ??= rtc.RTCVideoRenderer(); - await _renderer!.initialize(); + if (_renderer == null) { + _renderer = rtc.RTCVideoRenderer(); + await _renderer!.initialize(); + } await _attach(); return _renderer!; } @@ -181,8 +184,7 @@ class _VideoTrackRendererState extends State { Widget _videoRendererView() { if (lkPlatformIs(PlatformType.iOS) && - [VideoRenderMode.auto, VideoRenderMode.platformView] - .contains(widget.renderMode)) { + widget.renderMode == VideoRenderMode.platformView) { return rtc.RTCVideoPlatFormView( mirror: _shouldMirror(), objectFit: widget.fit, @@ -205,8 +207,8 @@ class _VideoTrackRendererState extends State { future: _initializeRenderer(), builder: (context, snapshot) { if ((snapshot.hasData && _renderer != null) || - [VideoRenderMode.auto, VideoRenderMode.platformView] - .contains(widget.renderMode)) { + (lkPlatformIs(PlatformType.iOS) && + widget.renderMode == VideoRenderMode.platformView)) { return Builder( key: _internalKey, builder: (ctx) { From a35cff7868943285861ceb4583be365393a99e6b Mon Sep 17 00:00:00 2001 From: cloudwebrtc Date: Fri, 24 Jan 2025 14:32:33 +0800 Subject: [PATCH 2/3] cleanup. --- example/lib/test_main.dart.text | 186 -------------------------------- 1 file changed, 186 deletions(-) delete mode 100644 example/lib/test_main.dart.text diff --git a/example/lib/test_main.dart.text b/example/lib/test_main.dart.text deleted file mode 100644 index 2bbab5942..000000000 --- a/example/lib/test_main.dart.text +++ /dev/null @@ -1,186 +0,0 @@ -import 'dart:async'; - -import 'package:flutter/material.dart'; -import 'package:flutter_window_close/flutter_window_close.dart'; -import 'package:get/get.dart'; -import 'package:livekit_client/livekit_client.dart'; -import 'package:livekit_example/theme.dart'; -import 'package:logging/logging.dart'; -import 'package:intl/intl.dart'; -import 'package:uuid/uuid.dart'; -import 'pages/connect.dart'; -import 'utils.dart'; - -void main() async { - final format = DateFormat('HH:mm:ss'); - // configure logs for debugging - Logger.root.level = Level.FINE; - Logger.root.onRecord.listen((record) { - print('${format.format(record.time)}: ${record.message}'); - }); - - WidgetsFlutterBinding.ensureInitialized(); - - if (lkPlatformIsDesktop()) { - await FlutterWindowClose.setWindowShouldCloseHandler(() async { - await onWindowShouldClose?.call(); - return true; - }); - } - - const url = /*String.fromEnvironment('URL') ??*/ - 'wss://duansapp.livekit.cloud'; - const token = /*String.fromEnvironment('TOKEN') ??*/ - 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3NTMxMTEwMjksImlzcyI6IkFQSVFaaFRwRVBNQ3NNRyIsIm5hbWUiOiJpcDE2IiwibmJmIjoxNzM1MTExMDI5LCJzdWIiOiJpcDE2IiwidmlkZW8iOnsicm9vbSI6ImxpdmUiLCJyb29tSm9pbiI6dHJ1ZX19.r-FsFl8cnk8qHZcAh-gq9TMcxOeWJyUmSRHYytDY9Vo'; - - runApp(const LiveKitTestPage( - url: url, - token: token, - )); -} - -class LiveKitTestPage extends StatefulWidget { - final String url; - final String token; - const LiveKitTestPage({super.key, required this.url, required this.token}); - - @override - State createState() => _LiveKitTestPageState(); -} - -class _LiveKitTestPageState extends State { - final Rx _roomState = Rx(null); - - Timer? _timer; - - final RxInt _seconds = 0.obs; - - @override - void initState() { - super.initState(); - _timer = Timer.periodic(const Duration(seconds: 1), (timer) { - _seconds.value++; - }); - } - - @override - void dispose() { - _disconnect(); - _timer?.cancel(); - super.dispose(); - } - - Future _connect() async { - _roomState.value = Room( - roomOptions: const RoomOptions( - enableVisualizer: true, adaptiveStream: true, dynacast: true)); - await _roomState.value!.connect(widget.url, widget.token); - await _roomState.value!.localParticipant?.setMicrophoneEnabled(true); - } - - Future _disconnect() async { - await _roomState.value?.disconnect(); - await _roomState.value?.dispose(); - } - - Future _reconnect() async { - await _disconnect(); - //await _requestToken(resume: true); - await _connect(); - } - - @override - Widget build(BuildContext context) { - return MaterialApp( - debugShowCheckedModeBanner: false, - home: Scaffold( - backgroundColor: Colors.black, - body: _mainView(), - )); - } - - Widget _mainView() { - return Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Obx(() { - return Text( - '${_seconds.value.toString()}s', - style: const TextStyle(color: Colors.white), - ); - }), - const SizedBox(height: 100), - Obx(() { - if (_roomState.value == null) { - return GestureContainer( - onTap: () { - showTipsToast('on tap connect'); - _connect(); - }, - color: Colors.greenAccent, - child: const Text( - 'connect', - ), - ); - } - return const LoadingView(); - }), - const SizedBox(height: 100), - GestureContainer( - onTap: () { - //showTipsToast("on tap reconnect"); - _reconnect(); - }, - color: Colors.greenAccent, - child: const Text( - 'reconnect', - ), - ) - ], - ), - ); - } - - void showTipsToast(String s) { - print(s); - } -} - -class GestureContainer extends StatelessWidget { - final Widget child; - final Color color; - final VoidCallback onTap; - const GestureContainer( - {super.key, - required this.child, - required this.color, - required this.onTap}); - - @override - Widget build(BuildContext context) { - return GestureDetector( - onTap: onTap, - child: Container( - padding: const EdgeInsets.all(10), - decoration: BoxDecoration( - color: color, - borderRadius: BorderRadius.circular(10), - ), - child: child, - ), - ); - } -} - -class LoadingView extends StatelessWidget { - const LoadingView({super.key}); - - @override - Widget build(BuildContext context) { - return const Center( - child: CircularProgressIndicator(), - ); - } -} From 8f11f77117b92873984ce4522023d376c625e75a Mon Sep 17 00:00:00 2001 From: cloudwebrtc Date: Fri, 24 Jan 2025 14:33:15 +0800 Subject: [PATCH 3/3] bump flutter-webrtc to 0.12.7. --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index 5ee94abe2..3b9a146c2 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -37,7 +37,7 @@ dependencies: uuid: '>=3.0.6' synchronized: ^3.0.0+3 protobuf: ^3.0.0 - flutter_webrtc: ^0.12.4 + flutter_webrtc: ^0.12.7 device_info_plus: ^11.1.1 js: '>=0.6.4' platform_detect: ^2.0.7