在 flutter 中回答 webRTC 的 SDP 时,代码甚至没有进入 onIceCandiate()

Code doesn't even enter the onIceCandiate() while answering the SDP for webRTC in flutter

代码流甚至没有进入 onIceCandidate 函数,同时响应 webRTC 连接的 SDP。 webRTC 用于 android 中的 VOIP 语音呼叫,我还通过 viagene 网站设置了 TURN 服务器。

import 'dart:convert';

import 'package:flutter/material.dart';
import 'package:flutter_webrtc/flutter_webrtc.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:cloud_firestore/cloud_firestore.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp();
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: MyHomePage(title: 'WebRTC lets learn together'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

  final String title;

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

class _MyHomePageState extends State<MyHomePage> {
  CollectionReference firebaseInstance =
      FirebaseFirestore.instance.collection("dmeet");
  RTCPeerConnection _peerConnection;

  MediaStream _localStream;
  RTCVideoRenderer _remoteRenderer = RTCVideoRenderer();
  var docId = TextEditingController();
  var l;

  var document;

  _createOfferSdp() async {
    RTCSessionDescription description =
        await _peerConnection.createOffer({'offerToReceiveAudio': 1});
    Map<String, dynamic> session = {"sdp": description.sdp};
    document = firebaseInstance.doc();
    document.collection("sdp").doc("offersdp").set(session);
    await _peerConnection.setLocalDescription(description);
    document.collection("icecandidate").snapshots().listen((result) async {
      dynamic candidate = new RTCIceCandidate(
          result['candidate'], result['sdpMid'], result['sdpMlineIndex']);
      await _peerConnection.addCandidate(candidate);
    });
    print(session);
    _peerConnection.onIceCandidate = (event) {
      if (event.candidate != null) {
        Map<String, dynamic> icecandidate = {
          "candidate": event.candidate,
          "sdpMid": event.sdpMid,
          "sdpMlineIndex": event.sdpMlineIndex
        };
        document.collection("candidate").doc().set(icecandidate);
      }
    };
  }

  bool remotesaved = false;

  _createAnswerSdp() async {
    _peerConnection.onIceCandidate = (event) {
      print("Candiate ${event.candidate}");
      if (event.candidate != null) {
        // Map<String, dynamic> icecandidate = {
        //   "candidate": event.candidate,
        //   "sdpMid": event.sdpMid,
        //   "sdpMlineIndex": event.sdpMlineIndex
        // };
        // document.collection("candidate").doc().set(icecandidate);
        print("Candidate: ${event.candidate}");
      }
    };
    firebaseInstance
        .doc(docId.text)
        .collection("sdp")
        .doc("offersdp")
        .get()
        .then((value) async {
      var remoteSession = value.data()["sdp"];
      RTCSessionDescription description1 =
          RTCSessionDescription(remoteSession, "offer");
      await _peerConnection
          .setRemoteDescription(description1)
          .then((value) async {
        RTCSessionDescription description2 =
            await _peerConnection.createAnswer({'offerToReceiveAudio': 1});
        Map<String, dynamic> session = {"sdp": description2.sdp};
        firebaseInstance
            .doc(docId.text)
            .collection("sdp")
            .doc("answersdp")
            .set(session);

        final iceCandidate = await firebaseInstance
             .doc(docId.text)
             .collection("candidate")
             .get();
        iceCandidate.docs.forEach((element) async {
          print("Candidate ${element.data()["candidate"]}");
          dynamic candidate = RTCIceCandidate(element.data()['candidate'],
               element.data()['sdpMid'], element.data()['sdpMlineIndex']);
           await _peerConnection.addCandidate(candidate);
         });
      });
    });
  }

  showAlertDialog(BuildContext context) {
    // set up the buttons
    Widget cancelButton = FlatButton(
      child: Text("Cancel"),
      onPressed: () {},
    );
    Widget continueButton = FlatButton(
      child: Text("Continue"),
      onPressed: _createAnswerSdp,
    );

    // set up the AlertDialog
    AlertDialog alert = AlertDialog(
      title: Text("AlertDialog"),
      content: TextField(
        controller: docId,
      ),
      actions: [
        cancelButton,
        continueButton,
      ],
    );

    // show the dialog
    showDialog(
      context: context,
      builder: (BuildContext context) {
        return alert;
      },
    );
  }

  initRenderer() async {
    await _remoteRenderer.initialize();
  }

  @override
  void initState() {
    _createPeerConnection().then((pc) {
      _peerConnection = pc;
    });
    initRenderer();
    // _localStream.initialize();
    super.initState();
  }

  @override
  void dispose() {
    _remoteRenderer.dispose();
    super.dispose();
  }

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

    MediaStream stream = await navigator.getUserMedia(mediaConstraints);

    // _localStream = stream;

    // _peerConnection.addStream(stream);

    return stream;
  }

  _createPeerConnection() async {
    Map<String, dynamic> configuration = {
      "iceServers": [
        {"url": "stun:stun.l.google.com:19302"},
        {
          "url": "turn:numb.viagenie.ca",
          "username": "******@gmail.com",
          "credential": "*****",
        }
      ]
    };

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

    _localStream = await _getUserMedia();

    RTCPeerConnection pc =
        await createPeerConnection(configuration, offerSdpConstraints);
    pc.addStream(_localStream);

    pc.onIceCandidate = (e) {
      if (e.candidate != null) {
        l = json.encode({
          'candidate': e.candidate.toString(),
          'sdpMid': e.sdpMid.toString(),
          'sdpMlineIndex': e.sdpMlineIndex,
        });
        print("Her $l");
      }
    };

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

    return pc;
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Container(
        child: Center(
          child: Row(
            children: [
              Flexible(child: RTCVideoView(_remoteRenderer)),
              ElevatedButton(
                child: Text("Create"),
                onPressed: _createOfferSdp,
              ),
              ElevatedButton(
                onPressed: () {
                  showAlertDialog(context);
                },
                child: Text("Join"),
              )
            ],
          ),
        ),
      ),
    );
  }
}

甚至没有输入的行是函数 _createAnwerSdp() 和它的下一行! createAnswerSdp函数用于获取冰候补时接听电话

问题的可能原因是什么?

所以,我可以清楚地看到,您没有为将要接听此电话的远程用户设置任何本地描述。

_peerConnection.setLocalDescription(description2);

希望这可能有所帮助!