WebRTC flutter 的一侧流调用

问题描述 投票:0回答:1

我目前正在向我的应用程序添加视频通话(前端使用 FLutter,后端使用 Asp.NET Core Web + SignalR),我正在使用 flutter_webrtc 库。无论我多么努力,我都成功地使发起呼叫的客户端接收远程流,而接听呼叫的客户端看不到发起呼叫的客户端的流

现在基本上对于想要拨打电话的人来说,我将单击另一个页面上的按钮,该按钮将在没有 roomId 的情况下实例化此页面,并且该过程在设置功能中继续,最后通过中心。 对于接收方,他将从另一个集线器(应用程序始终连接到该集线器)接收回调,该集线器将推送带有 roomId 的 PhoneCallPage,并且他必须按下一个按钮来触发 answerCall 函数,该函数将尝试获取报价等等

我的扑动页面:

import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_webrtc/flutter_webrtc.dart';
import 'package:getwidget/components/appbar/gf_appbar.dart';
import 'package:scobius/runtime_data.dart';
import 'package:signalr_netcore/signalr_client.dart';

class PhoneCallPage extends StatefulWidget{

  final String interlocutor;
  final String? roomId;

  const PhoneCallPage({required this.interlocutor, this.roomId, super.key});

  call(){
    
  }

  @override
  // ignore: no_logic_in_create_state
  State<PhoneCallPage> createState() => PhoneCallPageState(interlocutor, roomId);
}


class PhoneCallPageState extends State<PhoneCallPage>{
  String? interlocutor;
  String? roomId;
  bool callStart = false;
  bool isCaller = false;
  bool requestSended = false;
  PhoneCallPageState(this.interlocutor, this.roomId);

  HubConnection commHub = HubConnectionBuilder()
  .withUrl('${RuntimeData.serverBaseAddress}/call',
      options: HttpConnectionOptions(
        accessTokenFactory: () async => await Future.value(RuntimeData.sessionInformations?.token),
        transport: HttpTransportType.WebSockets,
      ))
  .withAutomaticReconnect(retryDelays: [2000, 5000, 10000, 20000])
  .build();
  
  RTCPeerConnection? pc;

  final RTCVideoRenderer _localRenderer = RTCVideoRenderer();
  final RTCVideoRenderer _remoteRenderer = RTCVideoRenderer();

  MediaStream? localStream;
  MediaStream? remoteStream;

  late RTCSessionDescription _offer;
  

  final Map<String, dynamic> _configuration = {
    'iceServers': [
      {
        'urls': [
          'stun:stun1.l.google.com:19302',
          'stun:stun2.l.google.com:19302',
        ],
      },
    ],
  };


  @override
  void initState() {
    setup();

    super.initState();
  }

  @override
  void dispose() {
    _localRenderer.dispose();
    _remoteRenderer.dispose();
    localStream!.dispose();
    remoteStream!.dispose();

    pc?.dispose();
    super.dispose();
  }

  void setup() async{
    setState(() {
      isCaller = roomId == null;
    });
    await _localRenderer.initialize();
      await _remoteRenderer.initialize();

    await _initializeHubConnection();

    var stream = await navigator.mediaDevices.getUserMedia(
        {'video': true, 'audio': true},
      );
    
    setState((){

      localStream = stream;
      _localRenderer.srcObject = localStream;
    });

    if(isCaller) await _createRoom(); 
  }

  Future<void> _initializeHubConnection() async{

    commHub.on('ReceiveOffer', _handleReceiveOffer);
    commHub.on('ReceiveAnswer', _handleReceiveAnswer);
    commHub.on('ReceiveIceCandidate', _handleReceiveIceCandidate);
    commHub.on('ReceiveIceCandidates', _handleReceiveIceCandidates);
    commHub.on('ReceiveRoomId', _handleReceiveRoomId);

    await commHub.start();
  }

  Future<void> _createRoom() async{
    pc = await createPeerConnection(_configuration);
    var offer = await pc?.createOffer();
    setState(() {
      _offer = offer!;
    });
    

    await commHub.invoke('CreateRoom', args : [offer!.sdp!]);
  }

  void _registerPeerConnectionListeners() {

    localStream!.getTracks().forEach((track) async{
      await pc?.addTrack(track, localStream!);
    });


    pc?.onIceCandidate = (candidate) async{
      await commHub.invoke('SendIceCandidate', args: [roomId! , candidate.toMap()]);
    };

    pc?.onAddStream = (stream) {
      remoteStream = stream;
    };

    pc?.onTrack = (event) {
      event.streams[0].getTracks().forEach(
        (track) => remoteStream?.addTrack(track)
      );
    
    };
   
  }


  void answerCall() async{

    await commHub.invoke('GetOffer', args: [roomId!]);
  }

  void endCall() async{

    Navigator.pop(context);
  }

  @override
  Widget build(BuildContext context) {

    if (localStream != null || _localRenderer.srcObject != localStream) {
      _localRenderer.srcObject = localStream!;
    }
    if (remoteStream != null || _remoteRenderer.srcObject != remoteStream) {
      _remoteRenderer.srcObject = remoteStream!;
    }
    

    return Scaffold(
      appBar: GFAppBar(
        leading: Container(),
      
        title: Text(interlocutor!),
      ),
      body: Column(
        children: [
          (
          Expanded(
            child: callStart ? 
                    Stack(
                      alignment: Alignment.bottomRight,
                      children: [
                        Container(
                            decoration: const BoxDecoration(color: Colors.black),
                            child: RTCVideoView(_remoteRenderer)
                          ),
                        Container(
                          width: 100,
                          height: 250,
                            decoration: const BoxDecoration(color: Colors.black),
                            child: RTCVideoView(_localRenderer, objectFit: RTCVideoViewObjectFit.RTCVideoViewObjectFitContain,)
                          ),
                        
                      ])
                    : const Center(
              child: CircleAvatar(
                    backgroundColor: Colors.grey,
                    foregroundColor: Colors.white, 
                    radius: 50,
                    child: Icon(Icons.person, size: 40),
                  ),
            )
          )),
          Padding(
              padding: const EdgeInsets.all(12),

              child: callStart ? Flex(
                direction: Axis.horizontal,
                crossAxisAlignment: CrossAxisAlignment.end,
                mainAxisAlignment: MainAxisAlignment.spaceBetween,
                children: [
                  
                  IconButton(
                    onPressed: (){}, 
                    icon: const Icon(Icons.video_call)
                  ),
                  IconButton(
                    onPressed: (){}, 
                    icon: const Icon(Icons.mic_off)
                  ),
                  IconButton(
                    onPressed: endCall, 
                    icon: const Icon(Icons.call_end,  color: Colors.red,)
                  ),
                ],
              ) : Flex(
                direction: Axis.horizontal,
                crossAxisAlignment: CrossAxisAlignment.center,
                mainAxisAlignment: MainAxisAlignment.spaceBetween,
                children: [
                  
                  IconButton(
                    onPressed: answerCall, 
                    icon: const Icon(Icons.call)
                  ),
                  IconButton(
                    onPressed: endCall, 
                    icon: const Icon(Icons.call_end,  color: Colors.red,)
                  ),
                ],
              ),
            ),
        ],
      ),
    );
  }
  
  

  void _handleReceiveRoomId(List<Object?>? arguments) async{
    roomId = arguments![0] as String;

    _registerPeerConnectionListeners();

    await pc?.setLocalDescription(_offer);
    
  }

  Future<void> _handleReceiveIceCandidate(List<Object?>? arguments) async {
    var candidate = arguments![0] as dynamic;

    await pc?.addCandidate(RTCIceCandidate(
      candidate['candidate'] as String,
      candidate['sdpMid'] as String,
      candidate['sdpMLineIndex'] as int,
    ));

    if(isCaller && !requestSended) {
      
      setState(() {
        requestSended = true;
      });
      await commHub.invoke('SendOffer', args: [interlocutor!,roomId!]);
    }
  }

  Future<void> _handleReceiveIceCandidates(List<Object?>? arguments) async {
    var candidates = arguments![0] as List<dynamic>;

    for (var candidate in candidates) {
      await pc?.addCandidate(RTCIceCandidate(
        candidate['candidate'] as String,
        candidate['sdpMid'] as String,
        candidate['sdpMLineIndex'] as int,
      )); 
    }
  }

  void _handleReceiveAnswer(List<Object?>? arguments) async{
    String sdp = arguments![0] as String;

    await pc?.setRemoteDescription(RTCSessionDescription(sdp, 'answer'));
    
    await commHub.invoke('GetIceCandiates', args: [roomId!]);
    setState(() {
      callStart = true;
    });
  }

  void _handleReceiveOffer(List<Object?>? arguments) async{
    String sdp = arguments![0] as String;

    pc = await createPeerConnection(_configuration);
    _registerPeerConnectionListeners();
    
    await pc?.setRemoteDescription(RTCSessionDescription(sdp, 'offer'));
    final answer = await pc?.createAnswer();
    await pc?.setLocalDescription(answer!);
    setState(() {
      callStart = true;
    });
    await commHub.invoke('GetIceCandiates', args: [roomId!]);
    await commHub.invoke('SendAnswer', args: [interlocutor!, answer!.sdp!]);
  }
}

以及管理通信的 SignalR 中心:

using Microsoft.AspNetCore.SignalR;
using System.Text.Json;
using ScobiusLibrary.DTOs;


namespace ScobiusServer.Hubs;

public class CommunicationHub(IHubContext<ServerHub> serverHub) : Hub
{

    public static List<CallRoom> CallRooms = new();

    public async Task CreateRoom(string sdp){
        string roomId = $"CallingRoom{CallRooms.Count}";
        CallRoom room = new CallRoom{
            RoomCreator = Context.UserIdentifier!,
            RoomId = roomId,
            Offer = sdp
        };

        CallRooms.Add(room);
        Console.WriteLine(JsonSerializer.Serialize(room));
        await Clients.Caller.SendAsync("ReceiveRoomId", roomId);
        await Groups.AddToGroupAsync(Context.ConnectionId, roomId);
    }

    public async Task GetOffer(string roomId){
        var room = CallRooms.FirstOrDefault(r => r.RoomId == roomId);
        if(room == null) return;
        await Clients.Caller.SendAsync("ReceiveOffer",  room.Offer, room.IceCandidates);
        await Groups.AddToGroupAsync(Context.ConnectionId, room.RoomId);
    }

    public async Task SendOffer(string receiverId, string roomId)
    {
        var room = CallRooms.First(r => r.RoomId == roomId);
        await serverHub.Clients.User(receiverId).SendAsync("ReceiveCallRequest", roomId, room.RoomCreator);
    }

    public async Task SendAnswer(string roomCreator, string answer)
    {
        await Clients.User(roomCreator).SendAsync("ReceiveAnswer", answer);
        
    }

#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously
    public async Task SendIceCandidate(string roomId, IceCandidate candidate)
#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously
    {
        CallRoom room = CallRooms.First(r => r.RoomId == roomId);
        room.IceCandidates.Add(candidate);
        Console.WriteLine(JsonSerializer.Serialize(room));

        await Clients.Group(roomId).SendAsync("ReceiveIceCandidate", candidate);
    }

    public async Task GetIceCandiates(string roomId){
        CallRoom room = CallRooms.First(r => r.RoomId == roomId);
        var candidates = room.IceCandidates;
        await Clients.Caller.SendAsync("ReceiveIceCandidates", candidates);
    }

    public override Task OnDisconnectedAsync(Exception? exception)
    {
        var room = CallRooms.FirstOrDefault(r => r.RoomCreator == Context.ConnectionId);
        if(room != null) CallRooms.Remove(room);
        return base.OnDisconnectedAsync(exception);
    }
}

我希望我已经提供了足够的信息。顺便说一句,我使用手机和模拟器作为设备

c# asp.net flutter signalr webrtc
1个回答
0
投票

您可以提供任何错误消息吗?

© www.soinside.com 2019 - 2024. All rights reserved.