Merge branch 'voip/fix-offre-issue-for-ios' into 'main'

fix: sdp negotiation issue on iOS, close #335.

Closes #335

See merge request famedly/company/frontend/famedlysdk!1150
This commit is contained in:
td 2022-11-03 02:48:58 +00:00
commit 70af77b3ac
1 changed files with 38 additions and 12 deletions

View File

@ -30,11 +30,19 @@ import 'package:matrix/src/utils/cached_stream_controller.dart';
/// version 1 /// version 1
const String voipProtoVersion = '1'; const String voipProtoVersion = '1';
class Timeouts {
/// The default life time for call events, in millisecond. /// The default life time for call events, in millisecond.
const lifetimeMs = 10 * 1000; static const lifetimeMs = 10 * 1000;
/// The length of time a call can be ringing for. /// The length of time a call can be ringing for.
const callTimeoutSec = 60; static const callTimeoutSec = 60;
/// The delay for ice gathering.
static const iceGatheringDelayMs = 200;
/// Delay before createOffer.
static const delayBeforeOfferMs = 100;
}
/// Wrapped MediaStream, used to adapt Widget to display /// Wrapped MediaStream, used to adapt Widget to display
class WrappedMediaStream { class WrappedMediaStream {
@ -551,7 +559,7 @@ class CallSession {
/// Send select_answer event. /// Send select_answer event.
await sendSelectCallAnswer( await sendSelectCallAnswer(
opts.room, callId, lifetimeMs, localPartyId, remotePartyId!); opts.room, callId, Timeouts.lifetimeMs, localPartyId, remotePartyId!);
} }
Future<void> onNegotiateReceived( Future<void> onNegotiateReceived(
@ -581,7 +589,7 @@ class CallSession {
if (description.type == 'offer') { if (description.type == 'offer') {
final answer = await pc!.createAnswer({}); final answer = await pc!.createAnswer({});
await sendCallNegotiate( await sendCallNegotiate(
room, callId, lifetimeMs, localPartyId, answer.sdp!, room, callId, Timeouts.lifetimeMs, localPartyId, answer.sdp!,
type: answer.type!); type: answer.type!);
await pc!.setLocalDescription(answer); await pc!.setLocalDescription(answer);
} }
@ -939,13 +947,18 @@ class CallSession {
video_muted: localUserMediaStream!.stream!.getVideoTracks().isEmpty) video_muted: localUserMediaStream!.stream!.getVideoTracks().isEmpty)
}); });
await pc!.setLocalDescription(answer);
setCallState(CallState.kConnecting);
// Allow a short time for initial candidates to be gathered
await Future.delayed(Duration(milliseconds: 200));
final res = await sendAnswerCall(room, callId, answer.sdp!, localPartyId, final res = await sendAnswerCall(room, callId, answer.sdp!, localPartyId,
type: answer.type!, type: answer.type!,
capabilities: callCapabilities, capabilities: callCapabilities,
metadata: metadata); metadata: metadata);
Logs().v('[VOIP] answer res => $res'); Logs().v('[VOIP] answer res => $res');
await pc!.setLocalDescription(answer);
setCallState(CallState.kConnecting);
inviteOrAnswerSent = true; inviteOrAnswerSent = true;
_answeredByUs = true; _answeredByUs = true;
} }
@ -964,7 +977,8 @@ class CallSession {
} }
Logs().d('[VOIP] Rejecting call: $callId'); Logs().d('[VOIP] Rejecting call: $callId');
await terminate(CallParty.kLocal, CallErrorCode.UserHangup, shouldEmit); await terminate(CallParty.kLocal, CallErrorCode.UserHangup, shouldEmit);
await sendCallReject(room, callId, lifetimeMs, localPartyId, reason); await sendCallReject(
room, callId, Timeouts.lifetimeMs, localPartyId, reason);
} }
Future<void> hangup([String? reason, bool suppressEvent = false]) async { Future<void> hangup([String? reason, bool suppressEvent = false]) async {
@ -1055,6 +1069,13 @@ class CallSession {
return; return;
} }
if (pc!.iceGatheringState ==
RTCIceGatheringState.RTCIceGatheringStateGathering) {
// Allow a short time for initial candidates to be gathered
await Future.delayed(
Duration(milliseconds: Timeouts.iceGatheringDelayMs));
}
if (callHasEnded) return; if (callHasEnded) return;
final callCapabilities = CallCapabilities() final callCapabilities = CallCapabilities()
@ -1063,12 +1084,12 @@ class CallSession {
final metadata = _getLocalSDPStreamMetadata(); final metadata = _getLocalSDPStreamMetadata();
if (state == CallState.kCreateOffer) { if (state == CallState.kCreateOffer) {
await sendInviteToCall( await sendInviteToCall(
room, callId, lifetimeMs, localPartyId, null, offer.sdp!, room, callId, Timeouts.lifetimeMs, localPartyId, null, offer.sdp!,
capabilities: callCapabilities, metadata: metadata); capabilities: callCapabilities, metadata: metadata);
inviteOrAnswerSent = true; inviteOrAnswerSent = true;
setCallState(CallState.kInviteSent); setCallState(CallState.kInviteSent);
inviteTimer = Timer(Duration(seconds: callTimeoutSec), () { inviteTimer = Timer(Duration(seconds: Timeouts.callTimeoutSec), () {
if (state == CallState.kInviteSent) { if (state == CallState.kInviteSent) {
hangup(CallErrorCode.InviteTimeout, false); hangup(CallErrorCode.InviteTimeout, false);
} }
@ -1077,7 +1098,7 @@ class CallSession {
}); });
} else { } else {
await sendCallNegotiate( await sendCallNegotiate(
room, callId, lifetimeMs, localPartyId, offer.sdp!, room, callId, Timeouts.lifetimeMs, localPartyId, offer.sdp!,
type: offer.type!, type: offer.type!,
capabilities: callCapabilities, capabilities: callCapabilities,
metadata: metadata); metadata: metadata);
@ -1088,6 +1109,11 @@ class CallSession {
Logs().i('Negotiation is needed!'); Logs().i('Negotiation is needed!');
makingOffer = true; makingOffer = true;
try { try {
// The first addTrack(audio track) on iOS will trigger
// onNegotiationNeeded, which causes creatOffer to only include
// audio m-line, add delay and wait for video track to be added,
// then createOffer can get audio/video m-line correctly.
await Future.delayed(Duration(milliseconds: Timeouts.delayBeforeOfferMs));
final offer = await pc!.createOffer({}); final offer = await pc!.createOffer({});
await _gotLocalOffer(offer); await _gotLocalOffer(offer);
} catch (e) { } catch (e) {