【问题标题】:Multipeer connection Webrtc Flutter多点连接 Webrtc Flutter
【发布时间】:2021-05-10 06:11:09
【问题描述】:

我正在尝试在 Flutter 中使用 Webrtc 进行 3 对等视频聊天。 A 需要看到 B 和 C。B 需要看到 A 和 C。C 需要看到 A 和 B。我也使用 socket.io 作为信号服务器。我使用了 2 个对等连接。两个peer连接成功。当我尝试连接第三个并尝试传递报价时,出现错误。

在“RTCPeerConnection”上执行“createAnswer”:PeerConnection 无法在 have-remote-offer 或 have-local-pranswer 之外的状态下创建答案。

我的方法不适合多个对等连接吗?

    import 'dart:convert';

import 'package:flutter/material.dart';
import 'package:flutter_webrtc/flutter_webrtc.dart';
import 'package:sdp_transform/sdp_transform.dart';
import 'package:socket_io_client/socket_io_client.dart' as IO;

class Home extends StatefulWidget {
  final String title;
  const Home({Key key, this.title}) : super(key: key);

  @override
  _HomeState createState() => _HomeState();
}

class _HomeState extends State<Home> {
  bool _offer = false;
  bool firstTimeOffer = true;
  bool firstTimeRemoteDescription = true;
  bool firstTimeAnswer = true;
  bool firstTimeCandidate = true;
  RTCPeerConnection _peerConnection;
  RTCPeerConnection _peerConnection2;
  MediaStream _localStream;

  final _localVideoRenderer = new RTCVideoRenderer();
  final _remoteVideoRenderer = new RTCVideoRenderer();
  final _remoteVideoRenderer2 = RTCVideoRenderer();

  IO.Socket socket;
  var pcConfig = {};
  var candidates = [];

  final txtController = TextEditingController();

  @override
  void dispose() {
    txtController.dispose();
    _localVideoRenderer.dispose();
    super.dispose();
  }

  @override
  void initState() {
    initRenderers();
    _createPeerConnection().then((pc) {
      _peerConnection = pc;
    });

    _createPeerConnection2().then((pc) {
      _peerConnection2 = pc;
    });
    _getUserMedia();
    connectToServer();
    super.initState();
  }

  void connectToServer() {
    // connectToServer();
    socket = IO.io('http://localhost:3000', <String, dynamic>{
      'transports': ['websocket'],
      'autoConnect': false,
    });

    socket.connect();
    socket.onDisconnect((_) => print("disconnected"));

    socket.on('connection-success', (data) => print(data));
    socket.on('offerOrAnswer', (sdp) async {
      this.txtController.text = sdp;
    });
    socket.on('candidate', (candidate) {
      // print('From Peer.....');
      // print(json.encode(candidate));
      this.candidates = [...this.candidates, candidate];
    });
  }

  _getUserMedia() async {
    final Map<String, dynamic> mediaConstraints = {
      'audio': false,
      'video': {'facingMode': 'user'}
    };

    MediaStream stream =
        await navigator.mediaDevices.getUserMedia(mediaConstraints);
    _localVideoRenderer.srcObject = stream;
    //  _localVideoRenderer.mirror = true;
    return stream;
  }

  initRenderers() async {
    await _localVideoRenderer.initialize();
    await _remoteVideoRenderer.initialize();
  }

  _createPeerConnection() async {
    Map<String, dynamic> configuration = {
      "iceServers": [
        {"url": "stun:stun.l.google.com:19302"},
      ]
    };

    final Map<String, dynamic> offerSdpConstraints = {
      "mandatory": {
        "OfferToReceiveAudio": true,
        "OfferToReceiveVideo": true,
      },
      "optional": [],
    };

    _localStream = await _getUserMedia();

    RTCPeerConnection pc =
        await createPeerConnection(configuration, offerSdpConstraints);
    //  if (pc != null) print(pc);

    pc.addStream(_localStream);

    // triggered when a new candidate is returned
    pc.onIceCandidate = (e) {
      //send the candidates to the remote peer
      // see addCandidate below to be triggered
      if (e.candidate != null) {
        var candidate = {
          'candidate': e.candidate.toString(),
          'sdpMid': e.sdpMid.toString(),
          'sdpMlineIndex': e.sdpMlineIndex
        };
        print(e.candidate);
        this.sendToPeer('candidate', candidate);
      }
    };

    pc.onIceConnectionState = (e) {
      print(e);
    };

    pc.onAddStream = (stream) {
      print('addStream: ' + stream.id);
      _remoteVideoRenderer.srcObject = stream;
    };

    return pc;
  }

  _createPeerConnection2() async {
    Map<String, dynamic> configuration = {
      "iceServers": [
        {"url": "stun:stun.l.google.com:19302"},
      ]
    };

    final Map<String, dynamic> offerSdpConstraints = {
      "mandatory": {
        "OfferToReceiveAudio": true,
        "OfferToReceiveVideo": true,
      },
      "optional": [],
    };

    _localStream = await _getUserMedia();

    RTCPeerConnection pc =
        await createPeerConnection(configuration, offerSdpConstraints);
    //  if (pc != null) print(pc);
    pc.addStream(_localStream);

    // triggered when a new candidate is returned
    pc.onIceCandidate = (e) {
      //send the candidates to the remote peer
      // see addCandidate below to be triggered
      if (e.candidate != null) {
        var candidate = {
          'candidate': e.candidate.toString(),
          'sdpMid': e.sdpMid.toString(),
          'sdpMlineIndex': e.sdpMlineIndex
        };
        print(e.candidate);
        this.sendToPeer('candidate', candidate);
      }
    };

    pc.onIceConnectionState = (e) {
      print(e);
    };

    pc.onAddStream = (stream) {
      print('addStream: ' + stream.id);
      _remoteVideoRenderer2.srcObject = stream;
    };

    return pc;
  }

  sendToPeer(messageType, payload) {
    socket.emit(messageType, {'socketID': socket.id, 'payload': payload});
  }

  void _createOffer() async {
    if (firstTimeOffer) {
      print('if true firsttimeoffer');
      firstTimeOffer = false;
      RTCSessionDescription description =
          await _peerConnection.createOffer({'offerToReceiveVideo': 1});
      _offer = true;
      _peerConnection.setLocalDescription(description);
      this.sendToPeer('offerOrAnswer', description.sdp);
      //print(description.sdp);
    } else {
      print('if false offer');
      RTCSessionDescription description =
          await _peerConnection2.createOffer({'offerToReceiveVideo': 1});
      _offer = true;
      _peerConnection2.setLocalDescription(description);
      this.sendToPeer('offerOrAnswer', description.sdp);
    }
  }

  void _setRemoteDescription() async {
    if (firstTimeRemoteDescription) {
      print('if true remote');
      firstTimeRemoteDescription = false;
      RTCSessionDescription description = new RTCSessionDescription(
          txtController.text, _offer ? 'answer' : 'offer');
      await _peerConnection.setRemoteDescription(description);
    } else {
      print('if false remote');
      RTCSessionDescription description = new RTCSessionDescription(
          txtController.text, _offer ? 'answer' : 'offer');
      await _peerConnection2.setRemoteDescription(description);
    }
  }

  void _createAnswer() async {
    if (firstTimeAnswer) {
      print('if true createanswer');
      firstTimeAnswer = false;
      RTCSessionDescription description =
          await _peerConnection.createAnswer({'offerToReceiveVideo': 1});
      _offer = true;
      _peerConnection.setLocalDescription(description);
      this.sendToPeer('offerOrAnswer', description.sdp);
    } else {
      print('if false createanswer');
      RTCSessionDescription description =
          await _peerConnection2.createAnswer({'offerToReceiveVideo': 1});
      _offer = true;
      _peerConnection2.setLocalDescription(description);
      this.sendToPeer('offerOrAnswer', description.sdp);
    }
  }

  void _addCandidate() async {
   
    if (firstTimeCandidate) {
      print('if true addcandi');
      firstTimeCandidate = false;
      this.candidates.forEach((candidate) async {
        print(json.encode(candidate));
        dynamic session = new RTCIceCandidate(candidate['candidate'],
            candidate['sdpMid'], candidate['sdpMlineIndex']);
        await _peerConnection.addCandidate(session);
      });
    } else {
      print('if false addcandi');
      this.candidates.forEach((candidate) async {
        print(json.encode(candidate));
        dynamic session = new RTCIceCandidate(candidate['candidate'],
            candidate['sdpMid'], candidate['sdpMlineIndex']);
        await _peerConnection2.addCandidate(session);
      });
    }
  }

  SizedBox videoRenderers() => SizedBox(
      height: 210,
      child: Row(children: [
        Flexible(
          child: new Container(
              key: new Key("local"),
              margin: new EdgeInsets.fromLTRB(5.0, 5.0, 5.0, 5.0),
              decoration: new BoxDecoration(color: Colors.black),
              child: RTCVideoView(_localVideoRenderer)),
        ),
        Flexible(
          child: new Container(
              key: new Key("remote"),
              margin: new EdgeInsets.fromLTRB(5.0, 5.0, 5.0, 5.0),
              decoration: new BoxDecoration(color: Colors.black),
              child: RTCVideoView(_remoteVideoRenderer)),
        ),
        Flexible(
          child: new Container(
              key: new Key("remote2"),
              margin: new EdgeInsets.fromLTRB(5.0, 5.0, 5.0, 5.0),
              decoration: new BoxDecoration(color: Colors.black),
              child: RTCVideoView(_remoteVideoRenderer2)),
        )
      ]));

  Row offerAndAnswerButtons() =>
      Row(mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: <Widget>[
        new ElevatedButton(
          style: ButtonStyle(
            backgroundColor: MaterialStateProperty.all<Color>(Colors.amber),
          ),
          // onPressed: () {
          //   return showDialog(
          //       context: context,
          //       builder: (context) {
          //         return AlertDialog(
          //           content: Text(sdpController.text),
          //         );
          //       });
          // },
          onPressed: _createOffer,
          child: Text('Offer'),
        ),
        ElevatedButton(
          onPressed: _createAnswer,
          child: Text('Answer'),
          style: ButtonStyle(
            backgroundColor: MaterialStateProperty.all<Color>(Colors.amber),
          ),
        ),
      ]);

  Row sdpCandidateButtons() =>
      Row(mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: <Widget>[
        ElevatedButton(
          onPressed: _setRemoteDescription,
          child: Text('Set Remote Desc'),
          style: ButtonStyle(
            backgroundColor: MaterialStateProperty.all<Color>(Colors.amber),
          ),
        ),
        ElevatedButton(
          onPressed: _addCandidate,
          child: Text('Add Candidate'),
          style: ButtonStyle(
            backgroundColor: MaterialStateProperty.all<Color>(Colors.amber),
          ),
        )
      ]);

  Padding sdpCandidatesTF() => Padding(
        padding: const EdgeInsets.all(16.0),
        child: TextField(
          controller: txtController,
          keyboardType: TextInputType.multiline,
          maxLines: 4,
          maxLength: TextField.noMaxLength,
        ),
      );

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Container(
        child: Column(
          children: [
            videoRenderers(),
            offerAndAnswerButtons(),
            sdpCandidatesTF(),
            sdpCandidateButtons()
          ],
        ),
      ),
    );
  }
}
    

【问题讨论】:

    标签: flutter webrtc


    【解决方案1】:

    3 个用户的网格意味着每个用户建立两个连接,一个连接到其他两个用户。在每个客户端,这是两个完全不同的 RTCPeerConnections,您不能在它们之间重复使用候选者,因为每个候选者都包含专门为媒体分配的端口号以及要发送到的目标。

    如果您知道如何建立一个连接,那么您就会知道如何建立两个。只是把事情分开。

    【讨论】:

    • 是的。在代码中,我试图做到这一点。首先创建一个对等连接,然后再创建另一个。
    • 我需要为此工作创造空间吗?
    【解决方案2】:

    只需设置 1 个连接并尝试使用方向:多对多

    【讨论】:

    • 正如目前所写,您的答案尚不清楚。请edit 添加其他详细信息,以帮助其他人了解这如何解决所提出的问题。你可以找到更多关于如何写好答案的信息in the help center
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-08-01
    • 1970-01-01
    • 2020-06-12
    相关资源
    最近更新 更多