chore: Use webrtc interface to build the voip module.

This commit is contained in:
cloudwebrtc 2021-11-19 17:51:38 +08:00
parent 56d9ba7d4a
commit 93b623f2d5
3 changed files with 76 additions and 177 deletions

View File

@ -1,11 +1,57 @@
import 'dart:async'; import 'dart:async';
import 'dart:core'; import 'dart:core';
import 'voip_abstract.dart'; import 'package:webrtc_interface/webrtc_interface.dart';
import 'package:sdp_transform/sdp_transform.dart' as sdp_transform; import 'package:sdp_transform/sdp_transform.dart' as sdp_transform;
import '../matrix.dart'; import '../matrix.dart';
MediaDevices mediaDevices = Null as MediaDevices;
RTCFactory factory = Null as RTCFactory;
class RTCVideoRenderer extends VideoRenderer {
RTCVideoRenderer() : super() {
muted = true;
}
@override
late bool muted;
@override
MediaStream? srcObject;
@override
Future<bool> audioOutput(String deviceId) {
// TODO: implement audioOutput
throw UnimplementedError();
}
@override
Future<void> initialize() {
// TODO: implement initialize
throw UnimplementedError();
}
@override
// TODO: implement renderVideo
bool get renderVideo => throw UnimplementedError();
@override
// TODO: implement textureId
int? get textureId => throw UnimplementedError();
@override
// TODO: implement videoHeight
int get videoHeight => throw UnimplementedError();
@override
// TODO: implement videoWidth
int get videoWidth => throw UnimplementedError();
@override
Future<void> dispose() => throw UnimplementedError();
}
/// The default life time for call events, in millisecond. /// The default life time for call events, in millisecond.
const lifetimeMs = 10 * 1000; const lifetimeMs = 10 * 1000;
@ -393,7 +439,7 @@ class CallSession {
try { try {
await pc!.setRemoteDescription(description); await pc!.setRemoteDescription(description);
if (description.type == 'offer') { if (description.type == 'offer') {
final answer = await pc!.createAnswer(); final answer = await pc!.createAnswer({});
await room.sendCallNegotiate( await room.sendCallNegotiate(
callId, lifetimeMs, localPartyId, answer.sdp!, callId, lifetimeMs, localPartyId, answer.sdp!,
type: answer.type!); type: answer.type!);
@ -555,7 +601,8 @@ class CallSession {
if (purpose == SDPStreamMetadataPurpose.Usermedia) { if (purpose == SDPStreamMetadataPurpose.Usermedia) {
speakerOn = type == CallType.kVideo; speakerOn = type == CallType.kVideo;
if (!kIsWeb && !voip.background) { //TODO: Confirm that the platform is not Web.
if (/*!kIsWeb && */ !voip.background) {
final audioTrack = stream.getAudioTracks()[0]; final audioTrack = stream.getAudioTracks()[0];
audioTrack.enableSpeakerphone(speakerOn); audioTrack.enableSpeakerphone(speakerOn);
} }
@ -658,13 +705,16 @@ class CallSession {
speakerOn = !speakerOn; speakerOn = !speakerOn;
} }
//TODO: move to the app.
Future<void> switchCamera() async { Future<void> switchCamera() async {
if (localUserMediaStream != null) { if (localUserMediaStream != null) {
/*
await Helper.switchCamera( await Helper.switchCamera(
localUserMediaStream!.stream!.getVideoTracks()[0]); localUserMediaStream!.stream!.getVideoTracks()[0]);
if (kIsMobile) { if (kIsMobile) {
facingMode == 'user' ? facingMode = 'environment' : facingMode = 'user'; facingMode == 'user' ? facingMode = 'environment' : facingMode = 'user';
} }
*/
} }
} }
@ -960,7 +1010,7 @@ class CallSession {
: false, : false,
}; };
try { try {
return await navigator.mediaDevices.getUserMedia(mediaConstraints); return await mediaDevices.getUserMedia(mediaConstraints);
} catch (e) { } catch (e) {
_getUserMediaFailed(e); _getUserMediaFailed(e);
} }
@ -973,7 +1023,7 @@ class CallSession {
'video': true, 'video': true,
}; };
try { try {
return await navigator.mediaDevices.getDisplayMedia(mediaConstraints); return await mediaDevices.getDisplayMedia(mediaConstraints);
} catch (e) { } catch (e) {
_getUserMediaFailed(e); _getUserMediaFailed(e);
} }
@ -985,7 +1035,7 @@ class CallSession {
'iceServers': opts.iceServers, 'iceServers': opts.iceServers,
'sdpSemantics': 'unified-plan' 'sdpSemantics': 'unified-plan'
}; };
final pc = await createPeerConnection(configuration); final pc = await factory.createPeerConnection(configuration);
pc.onTrack = (RTCTrackEvent event) { pc.onTrack = (RTCTrackEvent event) {
if (event.streams.isNotEmpty) { if (event.streams.isNotEmpty) {
final stream = event.streams[0]; final stream = event.streams[0];
@ -1115,47 +1165,16 @@ class CallSession {
} }
} }
class VoIP with WidgetsBindingObserver { class VoIP {
TurnServerCredentials? _turnServerCredentials; TurnServerCredentials? _turnServerCredentials;
Map<String, CallSession> calls = <String, CallSession>{}; Map<String, CallSession> calls = <String, CallSession>{};
String? currentCID; String? currentCID;
//ConnectivityResult _currentConnectivity; Function(CallSession session)? onNewCall;
Function(CallSession session)? onIncomingCall; Function(CallSession session)? onCallEnded;
OverlayEntry? overlayEntry;
String? get localPartyId => client.deviceID; String? get localPartyId => client.deviceID;
bool background = false; bool background = false;
Client client; Client client;
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
Logs().v('AppLifecycleState = $state');
background = !(state != AppLifecycleState.detached &&
state != AppLifecycleState.paused);
}
void addCallingOverlay(
BuildContext context, String callId, CallSession call) {
if (overlayEntry != null) {
Logs().w('[VOIP] addCallingOverlay: The call session already exists?');
overlayEntry?.remove();
}
/* TODO:
final overlay = Overlay.of(context);
overlayEntry = OverlayEntry(
builder: (_) => Calling(
context: famedly.context,
client: client,
callId: callId,
call: call,
onClear: () {
overlayEntry?.remove();
overlayEntry = null;
}),
);
overlay.insert(overlayEntry);
*/
}
VoIP(this.client) : super() { VoIP(this.client) : super() {
client.onCallInvite.stream.listen(onCallInvite); client.onCallInvite.stream.listen(onCallInvite);
client.onCallAnswer.stream.listen(onCallAnswer); client.onCallAnswer.stream.listen(onCallAnswer);
@ -1168,19 +1187,19 @@ class VoIP with WidgetsBindingObserver {
client.onSDPStreamMetadataChangedReceived.stream client.onSDPStreamMetadataChangedReceived.stream
.listen(onSDPStreamMetadataChangedReceived); .listen(onSDPStreamMetadataChangedReceived);
client.onAssertedIdentityReceived.stream.listen(onAssertedIdentityReceived); client.onAssertedIdentityReceived.stream.listen(onAssertedIdentityReceived);
/*
Connectivity().onConnectivityChanged.listen(_handleNetworkChanged);
Connectivity()
.checkConnectivity()
.then((result) => _currentConnectivity = result)
.catchError((e) => _currentConnectivity = ConnectivityResult.none);
*/
if (!kIsWeb) { /* TODO: implement this in the fanedly-app.
final wb = WidgetsBinding.instance; Connectivity().onConnectivityChanged.listen(_handleNetworkChanged);
wb!.addObserver(this); Connectivity()
didChangeAppLifecycleState(wb.lifecycleState!); .checkConnectivity()
} .then((result) => _currentConnectivity = result)
.catchError((e) => _currentConnectivity = ConnectivityResult.none);
if (!kIsWeb) {
final wb = WidgetsBinding.instance;
wb!.addObserver(this);
didChangeAppLifecycleState(wb.lifecycleState!);
}
*/
} }
Future<void> onCallInvite(Event event) async { Future<void> onCallInvite(Event event) async {
@ -1254,8 +1273,7 @@ class VoIP with WidgetsBindingObserver {
.then((_) { .then((_) {
// Popup CallingPage for incoming call. // Popup CallingPage for incoming call.
if (!background) { if (!background) {
//TODO: onNewCall?.call(newCall);
//addCallingOverlay(famedly.context, callId, newCall);
} }
}); });
currentCID = callId; currentCID = callId;
@ -1264,8 +1282,7 @@ class VoIP with WidgetsBindingObserver {
/// Forced to enable signaling synchronization until the end of the call. /// Forced to enable signaling synchronization until the end of the call.
client.backgroundSync = true; client.backgroundSync = true;
/// Handle incoming call for callkeep plugin. ///TODO: notify the callkeep that the call is incoming.
onIncomingCall?.call(newCall);
} }
// Play ringtone // Play ringtone
playRingtone(); playRingtone();
@ -1352,9 +1369,7 @@ class VoIP with WidgetsBindingObserver {
// hangup in any case, either if the other party hung up or we did on another device // hangup in any case, either if the other party hung up or we did on another device
call.terminate(CallParty.kRemote, call.terminate(CallParty.kRemote,
event.content['reason'] ?? CallErrorCode.UserHangup, true); event.content['reason'] ?? CallErrorCode.UserHangup, true);
onCallEnded?.call(call);
overlayEntry?.remove();
overlayEntry = null;
} else { } else {
Logs().v('[VOIP] onCallHangup: Session [$callId] not found!'); Logs().v('[VOIP] onCallHangup: Session [$callId] not found!');
} }
@ -1534,8 +1549,7 @@ class VoIP with WidgetsBindingObserver {
currentCID = callId; currentCID = callId;
await newCall.initOutboundCall(type).then((_) { await newCall.initOutboundCall(type).then((_) {
if (!background) { if (!background) {
//TODO: onNewCall?.call(newCall);
//addCallingOverlay(famdly.context, callId, newCall);
} }
}); });
currentCID = callId; currentCID = callId;

View File

@ -1,116 +0,0 @@
import 'dart:io';
class MediaStreamTrack {
bool get enabled => false;
set enabled(bool value) {}
String get kind => throw UnimplementedError();
Future<void> stop() async {}
Future<void> enableSpeakerphone(bool enable) async {}
}
class MediaStream {
String get id => throw UnimplementedError();
List<MediaStreamTrack> getAudioTracks() => throw UnimplementedError();
List<MediaStreamTrack> getVideoTracks() => throw UnimplementedError();
List<MediaStreamTrack> getTracks() => throw UnimplementedError();
Future<void> dispose() async {}
}
class RTCPeerConnection {
Function(RTCTrackEvent event)? onTrack;
Function()? onRenegotiationNeeded;
Function(RTCIceCandidate)? onIceCandidate;
Function(dynamic state)? onIceGatheringState;
Function(dynamic state)? onIceConnectionState;
Future<RTCSessionDescription> createOffer(Map<String, dynamic> constraints) {
throw UnimplementedError();
}
Future<RTCSessionDescription> createAnswer(Map<String, dynamic> constraints) {
throw UnimplementedError();
}
Future<void> setRemoteDescription(RTCSessionDescription description) async {}
Future<void> setLocalDescription(RTCSessionDescription description) async {}
Future<RTCRtpSender> addTrack(
MediaStreamTrack track, MediaStream stream) async {
return RTCRtpSender();
}
Future<void> removeTrack(RTCRtpSender sender) async {}
Future<void> close() async {}
Future<void> dispose() async {}
Future<void> addIceCandidate(RTCIceCandidate candidate) async {}
Future<void> addStream(MediaStream stream) async {}
Future<void> removeStream(MediaStream stream) async {}
Future<List<dynamic>> getTransceivers() async {
throw UnimplementedError();
}
Future<List<RTCRtpSender>> getSenders() async {
throw UnimplementedError();
}
Future<void> addCandidate(RTCIceCandidate candidate) async {}
dynamic get signalingState => throw UnimplementedError();
}
class RTCIceCandidate {
String get candidate => throw UnimplementedError();
String get sdpMid => throw UnimplementedError();
int get sdpMLineIndex => throw UnimplementedError();
Map<String, dynamic> toMap() => throw UnimplementedError();
RTCIceCandidate(String candidate, String sdpMid, int sdpMLineIndex);
}
class RTCRtpSender {
MediaStreamTrack? get track => throw UnimplementedError();
DtmfSender get dtmfSender => throw UnimplementedError();
}
class RTCSessionDescription {
late String type;
late String sdp;
RTCSessionDescription(this.sdp, this.type);
}
class RTCTrackEvent {
late List<MediaStream> streams;
}
enum TransceiverDirection {
SendRecv,
SendOnly,
RecvOnly,
Inactive,
}
enum RTCSignalingState { RTCSignalingStateStable }
class RTCVideoRenderer {}
const kIsWeb = false;
bool get kIsMobile => !kIsWeb && (Platform.isAndroid || Platform.isIOS);
class Helper {
static Future<void> switchCamera(MediaStreamTrack track) async {}
}
class DtmfSender {
Future<void> insertDTMF(String tones) async {}
}
Future<MediaStream> createPeerConnection(
Map<String, dynamic> constraints) async {
throw UnimplementedError();
}

View File

@ -23,6 +23,7 @@ dependencies:
slugify: ^2.0.0 slugify: ^2.0.0
html: ^0.15.0 html: ^0.15.0
collection: ^1.15.0 collection: ^1.15.0
webrtc_interface: ^1.0.0
sdp_transform: ^0.3.2 sdp_transform: ^0.3.2
dev_dependencies: dev_dependencies: