Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unhandled Exception: Unable to RTCPeerConnection::addTrack: Error: peerConnection not found! #94

Open
huazhiyeluo opened this issue Aug 21, 2024 · 0 comments

Comments

@huazhiyeluo
Copy link

import 'dart:convert';

import 'package:flutter_webrtc/flutter_webrtc.dart';
import 'package:qim/controller/websocket.dart';
import 'package:qim/utils/functions.dart';

class Session {
  Session({required this.fromId, required this.toId});
  int fromId;
  int toId;
  RTCPeerConnection? pc;
  List<RTCIceCandidate> remoteCandidates = [];
}

enum SignalingState {
  connectionOpen,
  connectionClosed,
  connectionError,
}

enum CallState {
  callStateNew,
  callStateRinging,
  callStateInvite,
  callStateConnected,
  callStateBye,
}

class Signaling {
  Signaling();

  final Map<String, dynamic> configuration = {
    'iceServers': [
      {'urls': 'turn:xxxxx:3478?transport=tcp', 'credential': 'xxx', 'nickname': 'xxx'},
    ],
    'sdpSemantics': 'unified-plan'
  };

  final Map<String, dynamic> mediaConstraints = {
    'audio': true,
    'video': {
      'mandatory': {
        'maxWidth': '480', // 设置最大值
        'maxHeight': '640', // 设置最小值
        'maxFrameRate': '15', // 设置最大值
      },
      'facingMode': 'user',
      'optional': [],
    }
  };

  final Map<String, dynamic> pcConstraints = {
    'mandatory': {},
    'optional': [
      {'DtlsSrtpKeyAgreement': false},
    ]
  };

  final Map<String, dynamic> offerOptions = {
    'offerToReceiveAudio': true,
    'offerToReceiveVideo': true,
  };

  final Map<int, Session> _sessions = {};
  MediaStream? _localStream;
  late WebSocketController webSocketController;

  Function(SignalingState state)? onSignalingStateChange;
  Function(Session session, CallState state)? onCallStateChange;
  Function(MediaStream stream)? onLocalStream;
  Function(MediaStream stream)? onRemoteStream;

  Function(int fromId, int toId, int msgType, int msgMedia, String data)? onSendMsg;

  // 修复数据
  RTCSessionDescription _fixSdp(RTCSessionDescription s) {
    var sdp = s.sdp;
    s.sdp = sdp!.replaceAll('profile-level-id=640c1f', 'profile-level-id=42e032');
    return s;
  }

  //连接监控
  Future<void> connect(WebSocketController webSocketController) async {
    onReceive(webSocketController);
  }

  //创建session
  Future<Session> createSession(Session? session, int fromId, int toId) async {
    await logPrint("createSession");
    Session newSession = session ?? Session(fromId: fromId, toId: toId);
    _localStream = await createStream();

    RTCPeerConnection pc = await createPeerConnection(configuration, pcConstraints);
    pc.onTrack = (RTCTrackEvent event) {
      if (event.track.kind == 'video') {
        onRemoteStream?.call(event.streams[0]);
      }
    };
    _localStream!.getTracks().forEach((MediaStreamTrack track) async {
      await pc.addTrack(track, _localStream!);
    });

    pc.onIceCandidate = (RTCIceCandidate candidate) async {
      await Future.delayed(const Duration(seconds: 1), () {
        Map<String, dynamic> candidateMap = {
          'sdpMLineIndex': candidate.sdpMLineIndex,
          'sdpMid': candidate.sdpMid,
          'candidate': candidate.candidate,
        };
        onSendMsg?.call(fromId, toId, 4, 3, json.encode(candidateMap));
      });
    };

    pc.onIceConnectionState = (RTCIceConnectionState state) {};

    newSession.pc = pc;
    return newSession;
  }

  //创建流
  Future<MediaStream> createStream() async {
    await logPrint("createStream");
    MediaStream stream = await navigator.mediaDevices.getUserMedia(mediaConstraints);
    onLocalStream?.call(stream);
    return stream;
  }

  //创建offer
  Future<void> createOffer(Session session) async {
    await logPrint("createOffer");
    try {
      RTCSessionDescription s = await session.pc!.createOffer(offerOptions);
      await session.pc!.setLocalDescription(_fixSdp(s));
      Map<String, dynamic> offerMap = {
        'sdp': s.sdp,
        'type': s.type,
      };
      onSendMsg?.call(session.fromId, session.toId, 4, 4, json.encode(offerMap));
    } catch (e) {
      await logPrint("createOffer: error ,$e");
    }
  }

  //创建answer
  Future<void> createAnswer(Session session) async {
    await logPrint("createAnswer");
    try {
      RTCSessionDescription s = await session.pc!.createAnswer();
      await session.pc!.setLocalDescription(_fixSdp(s));
      Map<String, dynamic> answerMap = {
        'sdp': s.sdp,
        'type': s.type,
      };
      onSendMsg?.call(session.fromId, session.toId, 4, 5, json.encode(answerMap));
    } catch (e) {
      await logPrint("createAnswer: error ,$e");
    }
  }

  //关闭所有的留
  Future<void> cleanSessions() async {
    await logPrint("cleanSessions");
    if (_localStream != null) {
      _localStream!.getTracks().forEach((MediaStreamTrack track) async {
        await track.stop();
      });
      await _localStream!.dispose();
      _localStream = null;
    }
    _sessions.forEach((key, session) async {
      await session.pc?.close();
    });
    _sessions.clear();
  }

  Future<void> closeSession(Session session) async {
    await logPrint("closeSession");
    if (_localStream != null) {
      _localStream?.getTracks().forEach((MediaStreamTrack track) async {
        await track.stop();
      });
      await _localStream?.dispose();
      _localStream = null;
    }
    await session.pc?.close();
  }

  void switchCamera() async {
    if (_localStream!.getVideoTracks().isNotEmpty) {
      Helper.switchCamera(_localStream!.getVideoTracks()[0]);
    }
  }

  void turnCamera(bool numted) async {
    if (_localStream!.getVideoTracks().isNotEmpty) {
      _localStream!.getVideoTracks()[0].enabled = numted;
    }
  }

  //关闭
  void close() async {
    await cleanSessions();
  }

  //邀请通话
  void invite(int fromId, int toId) async {
    await logPrint("invite");
    Session session = await createSession(null, fromId, toId);
    _sessions[fromId] = session;

    await createOffer(session);
    onCallStateChange?.call(session, CallState.callStateNew);
    onCallStateChange?.call(session, CallState.callStateInvite);
  }

  //接通通话
  Future<void> accept(int fromId, int toId) async {
    await logPrint("accept");
    var session = _sessions[fromId];
    if (session == null) {
      return;
    }
    await createAnswer(session);
  }

  //拒接通话
  Future<void> reject(int fromId, int toId) async {
    await logPrint("reject");
    var session = _sessions[fromId];
    if (session == null) {
      return;
    }
    bye(fromId, toId);
  }

  //结束通话
  Future<void> bye(int fromId, int toId) async {
    await logPrint("bye");
    onSendMsg?.call(fromId, toId, 4, 1, "");
    var session = _sessions[fromId];
    if (session != null) {
      closeSession(session);
    }
  }

  void onReceive(WebSocketController webSocketController) {
    webSocketController.message.listen((msg) async {
      if ([4].contains(msg['msgType'])) {
        if (msg['msgMedia'] == 1) {
          Session? session = _sessions.remove(msg['toId']);
          if (session != null) {
            onCallStateChange?.call(session, CallState.callStateBye);
            closeSession(session);
          }
        }
        if (msg['msgMedia'] == 3) {
          _handleIceCandidate(msg);
        }
        //收到offer代表有人邀请你通话
        if (msg['msgMedia'] == 4) {
          _handleOffer(msg);
        }

        //收到answer代表对方接通 (接通后发送answer)
        if (msg['msgMedia'] == 5) {
          _handleAnswer(msg);
        }
      }
    });
  }

  //1-1、_handleIceCandidate
  void _handleIceCandidate(Map<dynamic, dynamic> msg) async {
    try {
      Session? session = _sessions[msg['toId']];

      Map candidateMap = json.decode(msg['content']['data']);
      RTCIceCandidate candidate =
          RTCIceCandidate(candidateMap['candidate'], candidateMap['sdpMid'], candidateMap['sdpMLineIndex']);

      if (session != null) {
        if (session.pc != null) {
          await session.pc?.addCandidate(candidate);
        } else {
          session.remoteCandidates.add(candidate);
        }
      } else {
        _sessions[msg['toId']] = Session(fromId: msg['toId'], toId: msg['fromId']);
        _sessions[msg['toId']]?.remoteCandidates.add(candidate);
      }
    } catch (e) {}
  }

  //1-2、_handleOffer
  void _handleOffer(Map<dynamic, dynamic> msg) async {
    try {
      Session? session = _sessions[msg['toId']];
      Session newSession = await createSession(session, msg['toId'], msg['fromId']);
      _sessions[msg['toId']] = newSession;

      Map offerMap = json.decode(msg['content']['data']);
      RTCSessionDescription offer = RTCSessionDescription(offerMap['sdp'], offerMap['type']);

      await newSession.pc?.setRemoteDescription(offer);

      if (newSession.remoteCandidates.isNotEmpty) {
        newSession.remoteCandidates.forEach((candidate) async {
          await newSession.pc?.addCandidate(candidate);
        });
        newSession.remoteCandidates.clear();
      }
      onCallStateChange?.call(newSession, CallState.callStateNew);
      onCallStateChange?.call(newSession, CallState.callStateRinging);
    } catch (e) {}
  }

  //1-3 _handleAnswer
  void _handleAnswer(Map<dynamic, dynamic> msg) async {
    try {
      Session? session = _sessions[msg['toId']];
      Map answerMap = json.decode(msg['content']['data']);
      RTCSessionDescription answer = RTCSessionDescription(answerMap['sdp'], answerMap['type']);

      await session?.pc?.setRemoteDescription(answer);

      onCallStateChange?.call(session!, CallState.callStateConnected);
    } catch (e) {}
  }
}

error:
Not found video track for RTCMediaStream: 16594E80-3A71-47AC-B1F0-0E58839BDA33
[ERROR:flutter/runtime/dart_vm_initializer.cc(41)] Unhandled Exception: Unable to RTCPeerConnection::addTrack: Error: peerConnection not found!
#0 RTCPeerConnectionNative.addTrack (package:flutter_webrtc/src/native/rtc_peerconnection_impl.dart:578:7)

#1 Signaling.createSession. (package:qim/utils/Signaling.dart:100:7)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant