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:
commit
70af77b3ac
|
|
@ -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) {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue