flutter-webrtc / flutter-webrtc

WebRTC plugin for Flutter Mobile/Desktop/Web
MIT License
4.14k stars 1.13k forks source link

There is no transfer of audio even if it has a TURN server(with source code) #583

Closed Appleprabh closed 3 years ago

Appleprabh commented 3 years ago

SetRemoteDescription() creates a error as

E/flutter (32531): [ERROR:flutter/lib/ui/ui_dart_state.cc(186)] Unhandled Exception: Unable to RTCPeerConnection::setRemoteDescription: peerConnectionSetRemoteDescription(): WEBRTC_SET_REMOTE_DESCRIPTION_ERROR: SessionDescription is NULL.

Code:

import 'dart:convert';

import 'package:flutter/material.dart';
import 'package:flutter_webrtc/flutter_webrtc.dart';
import 'package:sdp_transform/sdp_transform.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,
      ),
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  MediaStream _localStream;
  RTCPeerConnection _peerConnection;
  RTCVideoRenderer _remoteRenderer = RTCVideoRenderer();

  // var docId = TextEditingController();
  var docId = "yZj2WDHpVGho8YU0rgbD";

  var firebaseInstance = FirebaseFirestore.instance.collection("dmeet");

  _createOffer() async {
    RTCSessionDescription description =
        await _peerConnection.createOffer({'offerToReceiveAudio': 1});
    print(description.sdp);
    var session = parse(description.sdp);
    Map<String, dynamic> sdp = {"sdp": session.toString()};

    await firebaseInstance.doc().collection("sdp").doc("offersdp").set(sdp);

    _peerConnection.setLocalDescription(description);
  }

  _createAnswer() async {
    firebaseInstance
        .doc(docId)
        .collection("sdp")
        .doc("offersdp")
        .get()
        .then((value) async {
      RTCSessionDescription description =
          RTCSessionDescription(value.data()["sdp"], "offer");

      if (description != null) {
        await _peerConnection
            .setRemoteDescription(description)
            .then((value) async {
          RTCSessionDescription description =
              await _peerConnection.createAnswer({"offerToReceiveAudio": 1});
          var session = parse(description.sdp);
          Map<String, dynamic> answersdp = {
            "sdp": session.toString(),
          };
          await firebaseInstance
              .doc(docId)
              .collection("sdp")
              .doc("answersdp")
              .set(answersdp);
        });
      }
    });
  }

  _getUserMedia() async {
    final Map<String, dynamic> mediaConstraints = {
      'audio': true,
      'video': false,
    };
    MediaStream stream =
        await navigator.mediaDevices.getUserMedia(mediaConstraints);
    return stream;
  }

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

    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) {
        print(json.encode({
          'candidate': e.candidate.toString(),
          'sdpMid': e.sdpMid.toString(),
          'sdpMlineIndex': e.sdpMlineIndex,
        }));
      }
    };

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

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

    return pc;
  }

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

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Container(
        child: Center(
          child: Row(
            children: [
              ElevatedButton(
                  onPressed: _createOffer, child: Text("Create offer")),
              SizedBox(
                width: 30,
              ),
              ElevatedButton(
                onPressed: _createAnswer,
                child: Text("Answer offer"),
              )
            ],
          ),
        ),
      ),
    );
  }
}

The offer SDP produced is here

{version: 0, origin: {username: -, sessionId: 235019911232120872, sessionVersion: 2, netType: IN, ipVer: 4, address: 127.0.0.1}, name: -, invalid: [{value: -}], timing: {start: 0, stop: 0}, groups: [{type: BUNDLE, mids: audio}], msidSemantic: {semantic: WMS, token: 94307e1a-ffac-46f5-b72f-842f3a4b6c97}, media: [{rtp: [{payload: 111, codec: opus, rate: 48000, encoding: 2}, {payload: 103, codec: ISAC, rate: 16000, encoding: null}, {payload: 104, codec: ISAC, rate: 32000, encoding: null}, {payload: 9, codec: G722, rate: 8000, encoding: null}, {payload: 102, codec: ILBC, rate: 8000, encoding: null}, {payload: 0, codec: PCMU, rate: 8000, encoding: null}, {payload: 8, codec: PCMA, rate: 8000, encoding: null}, {payload: 106, codec: CN, rate: 32000, encoding: null}, {payload: 105, codec: CN, rate: 16000, encoding: null}, {payload: 13, codec: CN, rate: 8000, encoding: null}, {payload: 110, codec: telephone-event, rate: 48000, encoding: null}, {payload: 112, codec: telephone-event, rate: 32000, encoding: null}, {payload: 113, codec: telephone-event, rate: 16000, encoding: null}, {payload: 126, codec: telephone-event, rate: 8000, encoding: null}], fmtp: [{payload: 111, config: minptime=10;useinbandfec=1}], type: audio, port: 9, protocol: UDP/TLS/RTP/SAVPF, payloads: 111 103 104 9 102 0 8 106 105 13 110 112 113 126, connection: {version: 4, ip: 0.0.0.0}, rtcp: {port: 9, netType: IN, ipVer: 4, address: 0.0.0.0}, iceUfrag: n5en, icePwd: N0GqwgZyVnrg3qzQDuodM4A3, iceOptions: trickle, fingerprint: {type: sha-256, hash: 41:84:D4:92:97:C4:1C:1F:BF:81:DE:76:79:AA:AB:86:B4:BE:E8:0D:37:C1:75:43:61:69:C6:8F:C4:0C:81:1A}, setup: actpass, mid: audio, ext: [{value: 1, direction: null, uri: urn:ietf:params:rtp-hdrext:ssrc-audio-level, config: null}, {value: 2, direction: null, uri: http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time, config: null}, {value: 3, direction: null, uri: http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01, config: null}], direction: sendrecv, rtcpMux: rtcp-mux, rtcpFb: [{payload: 111, type: transport-cc, subtype: null}], ssrcs: [{id: 1854707329, attribute: cname, value: a2rQk0DnUlHW7zPh}, {id: 1854707329, attribute: msid, value: 94307e1a-ffac-46f5-b72f-842f3a4b6c97 d93853f4-6889-4b68-b48e-af2e10b9297e}, {id: 1854707329, attribute: mslabel, value: 94307e1a-ffac-46f5-b72f-842f3a4b6c97}, {id: 1854707329, attribute: label, value: d93853f4-6889-4b68-b48e-af2e10b9297e}]}]}

While setting this sdp as a remote sdp it produces the error.

cloudwebrtc commented 3 years ago

Please remove all var session = parse(description.sdp);; You use sdp_transform to convert sdp to map, but when setting sdp for pc, it must be String,

Appleprabh commented 3 years ago

Yeah It resolved it!

Appleprabh commented 3 years ago

@cloudwebrtc I have got some issue that I connected a mobile in mobile data and another in wifi and there is no audio transfer so I sorted it and setted up my TURN server. But still It doesn't work the code is here.

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 {
    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);
        });
      });
    });

    if (remotesaved) {}
  }

  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"),
              )
            ],
          ),
        ),
      ),
    );
  }
}

What may be the possible solution?