Open
Description
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)
Metadata
Metadata
Assignees
Labels
No labels