Flutter 中的 Agora - 多次导航到视频聊天屏幕使本地视频永远加载

Agora in Flutter- navigating to the video chat screen more than one time keeps local video loading forever

我正在使用 Agora for a one-to-one video chat purpose in Flutter。 User1有一个应用要上线,user2有另一个应用要上线。两人上网后,就可以进行视频聊天了。 这两个应用程序的代码库几乎相似

我有一个屏幕或 activity(例如屏幕 1),其中在点击按钮(例如按钮 1)时会显示一个警告对话框。点击警告对话框中的 Continue 按钮,对话框消失,用户被带到进行视频聊天的屏幕(比如屏幕 2)。但是在成功进入视频聊天屏幕后,如果用户点击移动设备上的后退按钮,则 s/he 将被带到屏幕 1,并且在点击按钮 1 后,如果用户点击 Continue 按钮在弹出的警告对话框中,用户再次被带到屏幕 2,但这次本地视频(即用户使用应用程序的视频)一直在加载。显然我希望本地视频像第一次一样加载。

我要把我的代码放在这里,这样你就可以很容易地运行。

以下代码适用于用户 1。对于 user2,应用程序中没有警告框。来自 user1 的相同代码用于 user2 应用程序,不同之处在于 remoteUid 的值对于 user2 设置为 2,而该值对于 user1 设置为 1。这些只是标识 2 个用户的两个值。

用户 1:

main.dart:

import 'dart:async';
import 'dart:convert';
import 'package:flutter/material.dart';
import 'livesession1to1.dart';

void main()  {

  runApp(MessagingExampleApp());
}

class NavigationService {
  static GlobalKey<NavigatorState> navigatorKey =
  GlobalKey<NavigatorState>();
}


/// Entry point for the example application.
class MessagingExampleApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Messaging Example App',
      navigatorKey: NavigationService.navigatorKey, // set property
      theme: ThemeData.dark(),
      routes: {
        '/': (context) => Application(),
        '/liveSession1to1': (context) =>LiveSession1to1(),
      },
    );
  }
}

int _messageCount = 0;

/// The API endpoint here accepts a raw FCM payload for demonstration purposes.
String constructFCMPayload(String? token, String server_key) {
  _messageCount++;
  return jsonEncode({
    'token': token,
    'to':token,
    'data': {
      'via': 'FlutterFire Cloud Messaging!!!',
      'count': _messageCount.toString(),
    },
    'notification': {
      'title': 'Hello FlutterFire!',
      'body': 'This notification (#$_messageCount) was created via FCM! =============',
    },
    "delay_while_idle" : false,
    "priority" : "high",
    "content_available" : true

  });
}

/// Renders the example application.
class Application extends StatefulWidget {
  @override
  State<StatefulWidget> createState() => _Application();
}

class _Application extends State<Application> {
  String? _token;

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



  }


  showAlertDialog() {
    BuildContext context=NavigationService.navigatorKey.currentContext!;
    // set up the buttons
    Widget cancelButton = TextButton(
      child: Text("Cancel"),
      onPressed:  () {},
    );
    Widget continueButton = TextButton(
      child: Text("Continue"),
      onPressed:  () {

        Navigator.of(context, rootNavigator: true).pop();

        Navigator.of(context).pushNamed('/liveSession1to1');
      },
    );





    Timer? timer = Timer(Duration(milliseconds: 5000), (){

      Navigator.of(context, rootNavigator: true).pop();
    });

    showDialog(
        context: context,
        builder: (BuildContext builderContext) {

          return AlertDialog(
            backgroundColor: Colors.black26,
            title: Text('One to one live session'),
            content: SingleChildScrollView(
              child: Text('Do you want to connect for a live session ?'),
            ),

            actions: [
              cancelButton,
              continueButton,
            ],
          );
        }
    ).then((value){
      // dispose the timer in case something else has triggered the dismiss.
      timer?.cancel();
      timer = null;
    });


  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('My App'),

      ),
      floatingActionButton: Builder(
        builder: (context) => FloatingActionButton(
          onPressed: showAlertDialog,

          backgroundColor: Colors.white,
          child: const Icon(Icons.send),
        ),
      ),
      body: SingleChildScrollView(
        child: Text(
            'Trigger Alert'

        ),
      ),
    );
  }


}

livesession1to1.dart:

import 'dart:async';
import 'package:agora_rtc_engine/rtc_engine.dart';
import 'package:agora_rtc_engine/rtc_local_view.dart' as RtcLocalView;
import 'package:agora_rtc_engine/rtc_remote_view.dart' as RtcRemoteView;
import 'package:flutter/material.dart';
import 'package:permission_handler/permission_handler.dart';

// const appId = "<-- Insert App Id -->";
// const token = "<-- Insert Token -->";


const appId = "......";// Put Agora App ID from Agora site here
const token = "....";// Put token ( temporary token avilable from Agora site)


void main() => runApp(MaterialApp(home: LiveSession1to1()));

class LiveSession1to1 extends StatefulWidget {
  @override
  _LiveSession1to1State createState() => _LiveSession1to1State();
}

class _LiveSession1to1State extends State<LiveSession1to1> {

  int _remoteUid=1;
  bool _localUserJoined = false;
  late RtcEngine _engine;

  @override
  void initState() {
    super.initState();
    setState(() {});
    initAgora();
  }





  Future<void> initAgora() async {
    // retrieve permissions
    await [Permission.microphone, Permission.camera].request();




    // Create RTC client instance
    RtcEngineContext context = RtcEngineContext(appId);
    _engine = await RtcEngine.createWithContext(context);


    await _engine.enableVideo();

    _engine.setEventHandler(
      RtcEngineEventHandler(
        joinChannelSuccess: (String channel, int uid, int elapsed) {
          print("local user $uid joined");
          setState(() {
            _localUserJoined = true;
          });
        },
        userJoined: (int uid, int elapsed) {
          print("remote user $uid joined");
          setState(() {
            _remoteUid = uid;
          });
        },
        userOffline: (int uid, UserOfflineReason reason) {
          print("remote user $uid left channel");
          setState(() {
            // _remoteUid = null;
            _remoteUid = 0;
          });
        },
      ),
    );

    try {
      await _engine.joinChannel(token, "InstaClass", null, 0);

    } catch (e) {
      print("error with agora = ");
      print("$e");
      print("error printeddddd");
    }
  }

  // Create UI with local view and remote view
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Agora Video Call'),
      ),
      body: Stack(
        children: [
          Center(
            child: _remoteVideo(),
          ),


          Align(
            alignment: Alignment.topLeft,
            child: Container(
              width: 100,
              height: 150,
              child: Center(
                child: _localUserJoined
                    ? RtcLocalView.SurfaceView()
                    : CircularProgressIndicator(),
              ),
            ),
          ),


        ],
      ),
    );
  }

  // Display remote user's video
  Widget _remoteVideo() {

    if (_remoteUid != 0) {
      return RtcRemoteView.SurfaceView(
        uid: _remoteUid,
        channelId: "InstaClass",
      );
    }else {
      print("'Please wait for remote user to join',");
      return Text(
        'Please wait for remote user to join',
        textAlign: TextAlign.center,
      );
    }
  }
}

用户 2:

main.dart:

import 'dart:async';
import 'package:agora_rtc_engine/rtc_engine.dart';
import 'package:agora_rtc_engine/rtc_local_view.dart' as RtcLocalView;
import 'package:agora_rtc_engine/rtc_remote_view.dart' as RtcRemoteView;
import 'package:flutter/material.dart';
import 'package:permission_handler/permission_handler.dart';


const appId = "....."; // Same as user1 app
const token = "....."; // same as user1 app

void main() => runApp(MaterialApp(home: MyApp()));

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  // int? _remoteUid=1;

  int _remoteUid=2;
  bool _localUserJoined = false;
  late RtcEngine _engine;

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

  Future<void> initAgora() async {
    // retrieve permissions
    await [Permission.microphone, Permission.camera].request();

    //create the engine
    _engine = await RtcEngine.create(appId);
    await _engine.enableVideo();
    _engine.setEventHandler(
      RtcEngineEventHandler(
        joinChannelSuccess: (String channel, int uid, int elapsed) {
          print("local user $uid joined");
          setState(() {
            _localUserJoined = true;
          });
        },
        userJoined: (int uid, int elapsed) {
          print("remote user $uid joined");
          setState(() {
            _remoteUid = uid;
          });
        },
        userOffline: (int uid, UserOfflineReason reason) {
          print("remote user $uid left channel");
          setState(() {
            // _remoteUid = null;
            _remoteUid = 0;
          });
        },
      ),
    );

    // await _engine.joinChannel(token, "test", null, 0);
    await _engine.joinChannel(token, "InstaClass", null, 0);

  }

  // Create UI with local view and remote view
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Agora Video Call'),
      ),
      body: Stack(
        children: [
          Center(
            child: _remoteVideo(),
          ),
          Align(
            alignment: Alignment.topLeft,
            child: Container(
              width: 100,
              height: 150,
              child: Center(
                child: _localUserJoined
                    ? RtcLocalView.SurfaceView()
                    : CircularProgressIndicator(),
              ),
            ),
          ),
        ],
      ),
    );
  }

  // Display remote user's video
  Widget _remoteVideo() {
    /*if (_remoteUid != null) {
          return RtcRemoteView.SurfaceView(uid: _remoteUid!);
        }*/

    if (_remoteUid != 0) {
      return RtcRemoteView.SurfaceView(
        uid: _remoteUid,
        channelId: "InstaClass",
      );
    }else {
      return Text(
        'Please wait for remote user to join',
        textAlign: TextAlign.center,
      );
    }
  }
}

为了获取应用程序 ID 和令牌,请登录 Agora 站点。登录后,转到 'Project Management' 部分查看已在此处创建的项目。在 Functions 列下,单击密钥符号,您将被带到一个可以生成临时令牌的页面。在该页面上,为频道名称输入值 'InstaClass',因为我在我的代码中使用了这个名称。

如何让视频聊天第一次顺畅后顺畅?

我认为问题在于,当您按下 back 按钮时,您只是被带到上一个屏幕,而呼叫 session 并未结束。您可以按 leaving 后退按钮尝试频道,例如:

_engine.leaveChannel();

End Call 按钮示例

             ElevatedButton(
                    onPressed: () {
                      _rtcEngine.leaveChannel();
                      Navigator.pop(context);
                    },
                    style: ButtonStyle(
                      shape: MaterialStateProperty.all(CircleBorder()),
                      backgroundColor: MaterialStateProperty.all(Colors.red),
                      padding: MaterialStateProperty.all(
                          EdgeInsets.fromLTRB(15, 15, 15, 12)),
                    ),
                    child: Icon(
                      Icons.phone,
                      size: 30,
                    ),
                  )

Back Button 使用 WillPopScope

覆盖
return WillPopScope(
      onWillPop: () async {
        _rtcEngine.leaveChannel();
        return true;
      },
      child: Scaffold(
        body: Container(),
      ),
    );