feat: MSC2746: Improved Signalling for 1:1 VoIP.
This commit is contained in:
parent
e13b00d127
commit
ea34f0b82c
|
|
@ -802,6 +802,27 @@ class Client extends MatrixApi {
|
||||||
/// Will be called on call answers.
|
/// Will be called on call answers.
|
||||||
final StreamController<Event> onCallAnswer = StreamController.broadcast();
|
final StreamController<Event> onCallAnswer = StreamController.broadcast();
|
||||||
|
|
||||||
|
/// Will be called on call replaces.
|
||||||
|
final StreamController<Event> onCallReplaces = StreamController.broadcast();
|
||||||
|
|
||||||
|
/// Will be called on select answers.
|
||||||
|
final StreamController<Event> onCallSelectAnswer =
|
||||||
|
StreamController.broadcast();
|
||||||
|
|
||||||
|
/// Will be called on rejects.
|
||||||
|
final StreamController<Event> onCallReject = StreamController.broadcast();
|
||||||
|
|
||||||
|
/// Will be called on negotiates.
|
||||||
|
final StreamController<Event> onCallNegotiate = StreamController.broadcast();
|
||||||
|
|
||||||
|
/// Will be called on Asserted Identity received.
|
||||||
|
final StreamController<Event> onAssertedIdentityReceived =
|
||||||
|
StreamController.broadcast();
|
||||||
|
|
||||||
|
/// Will be called on SDPStream Metadata changed.
|
||||||
|
final StreamController<Event> onSDPStreamMetadataChangedReceived =
|
||||||
|
StreamController.broadcast();
|
||||||
|
|
||||||
/// Will be called when another device is requesting session keys for a room.
|
/// Will be called when another device is requesting session keys for a room.
|
||||||
final StreamController<RoomKeyRequest> onRoomKeyRequest =
|
final StreamController<RoomKeyRequest> onRoomKeyRequest =
|
||||||
StreamController.broadcast();
|
StreamController.broadcast();
|
||||||
|
|
@ -1419,6 +1440,26 @@ class Client extends MatrixApi {
|
||||||
onCallAnswer.add(Event.fromJson(rawUnencryptedEvent, room));
|
onCallAnswer.add(Event.fromJson(rawUnencryptedEvent, room));
|
||||||
} else if (rawUnencryptedEvent['type'] == EventTypes.CallCandidates) {
|
} else if (rawUnencryptedEvent['type'] == EventTypes.CallCandidates) {
|
||||||
onCallCandidates.add(Event.fromJson(rawUnencryptedEvent, room));
|
onCallCandidates.add(Event.fromJson(rawUnencryptedEvent, room));
|
||||||
|
} else if (rawUnencryptedEvent['type'] == EventTypes.CallSelectAnswer) {
|
||||||
|
onCallSelectAnswer.add(Event.fromJson(rawUnencryptedEvent, room));
|
||||||
|
} else if (rawUnencryptedEvent['type'] == EventTypes.CallReject) {
|
||||||
|
onCallReject.add(Event.fromJson(rawUnencryptedEvent, room));
|
||||||
|
} else if (rawUnencryptedEvent['type'] == EventTypes.CallNegotiate) {
|
||||||
|
onCallNegotiate.add(Event.fromJson(rawUnencryptedEvent, room));
|
||||||
|
} else if (rawUnencryptedEvent['type'] == EventTypes.CallReplaces) {
|
||||||
|
onCallReplaces.add(Event.fromJson(rawUnencryptedEvent, room));
|
||||||
|
} else if (rawUnencryptedEvent['type'] ==
|
||||||
|
EventTypes.CallAssertedIdentity ||
|
||||||
|
rawUnencryptedEvent['type'] ==
|
||||||
|
EventTypes.CallAssertedIdentityPrefix) {
|
||||||
|
onAssertedIdentityReceived
|
||||||
|
.add(Event.fromJson(rawUnencryptedEvent, room));
|
||||||
|
} else if (rawUnencryptedEvent['type'] ==
|
||||||
|
EventTypes.CallSDPStreamMetadataChanged ||
|
||||||
|
rawUnencryptedEvent['type'] ==
|
||||||
|
EventTypes.CallSDPStreamMetadataChangedPrefix) {
|
||||||
|
onSDPStreamMetadataChangedReceived
|
||||||
|
.add(Event.fromJson(rawUnencryptedEvent, room));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,10 @@ import 'utils/marked_unread.dart';
|
||||||
import 'utils/matrix_file.dart';
|
import 'utils/matrix_file.dart';
|
||||||
import 'utils/matrix_localizations.dart';
|
import 'utils/matrix_localizations.dart';
|
||||||
|
|
||||||
|
/// https://github.com/matrix-org/matrix-doc/pull/2746
|
||||||
|
/// version 1
|
||||||
|
const String voipProtoVersion = '1';
|
||||||
|
|
||||||
enum PushRuleState { notify, mentionsOnly, dontNotify }
|
enum PushRuleState { notify, mentionsOnly, dontNotify }
|
||||||
enum JoinRules { public, knock, invite, private }
|
enum JoinRules { public, knock, invite, private }
|
||||||
enum GuestAccess { canJoin, forbidden }
|
enum GuestAccess { canJoin, forbidden }
|
||||||
|
|
@ -52,6 +56,153 @@ const Map<HistoryVisibility, String> _historyVisibilityMap = {
|
||||||
HistoryVisibility.worldReadable: 'world_readable',
|
HistoryVisibility.worldReadable: 'world_readable',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class CallReplacesTarget {
|
||||||
|
String id;
|
||||||
|
String display_name;
|
||||||
|
String avatar_url;
|
||||||
|
|
||||||
|
CallReplacesTarget();
|
||||||
|
factory CallReplacesTarget.fromJson(Map<String, dynamic> json) {
|
||||||
|
return CallReplacesTarget()
|
||||||
|
..id = json['id'].toString()
|
||||||
|
..display_name = json['display_name'].toString()
|
||||||
|
..avatar_url = json['avatar_url'].toString();
|
||||||
|
}
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return {
|
||||||
|
if (id != null) 'id': id,
|
||||||
|
if (display_name != null) 'display_name': display_name,
|
||||||
|
if (avatar_url != null) 'avatar_url': avatar_url,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// MSC2747: VoIP call transfers
|
||||||
|
/// https://github.com/matrix-org/matrix-doc/pull/2747
|
||||||
|
class CallReplaces {
|
||||||
|
String replacement_id;
|
||||||
|
CallReplacesTarget target_user;
|
||||||
|
String create_call;
|
||||||
|
String await_call;
|
||||||
|
String target_room;
|
||||||
|
|
||||||
|
CallReplaces();
|
||||||
|
factory CallReplaces.fromJson(Map<String, dynamic> json) {
|
||||||
|
return CallReplaces()
|
||||||
|
..replacement_id = json['replacement_id'].toString()
|
||||||
|
..create_call = json['create_call'].toString()
|
||||||
|
..await_call = json['await_call'].toString()
|
||||||
|
..target_room = json['target_room'].toString()
|
||||||
|
..target_user = CallReplacesTarget.fromJson(json['target_user']);
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return {
|
||||||
|
if (replacement_id != null) 'replacement_id': replacement_id,
|
||||||
|
if (target_user != null) 'target_user': target_user.toJson(),
|
||||||
|
if (create_call != null) 'create_call': create_call,
|
||||||
|
if (await_call != null) 'await_call': await_call,
|
||||||
|
if (target_room != null) 'target_room': target_room,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Change to "sdp_stream_metadata" when MSC3077 is merged
|
||||||
|
const String sdpStreamMetadataKey = 'org.matrix.msc3077.sdp_stream_metadata';
|
||||||
|
|
||||||
|
/// https://github.com/matrix-org/matrix-doc/blob/dbkr/msc2747/proposals/2747-voip-call-transfer.md#capability-advertisment
|
||||||
|
/// https://github.com/matrix-org/matrix-doc/blob/dbkr/msc2746/proposals/2746-reliable-voip.md#add-dtmf
|
||||||
|
class CallCapabilities {
|
||||||
|
bool transferee;
|
||||||
|
bool dtmf;
|
||||||
|
CallCapabilities();
|
||||||
|
factory CallCapabilities.fromJson(Map<String, dynamic> json) {
|
||||||
|
return CallCapabilities()
|
||||||
|
..dtmf = json['m.call.dtmf'] as bool ?? false
|
||||||
|
..transferee = json['m.call.transferee'] as bool ?? false;
|
||||||
|
}
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return {
|
||||||
|
if (transferee != null) 'm.call.transferee': transferee,
|
||||||
|
if (dtmf != null) 'm.call.dtmf': dtmf,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// MSC3077: Support for multi-stream VoIP
|
||||||
|
/// https://github.com/matrix-org/matrix-doc/pull/3077
|
||||||
|
///
|
||||||
|
/// MSC3291: Muting in VoIP calls
|
||||||
|
/// https://github.com/SimonBrandner/matrix-doc/blob/msc/muting/proposals/3291-muting.md
|
||||||
|
///
|
||||||
|
/// This MSC proposes adding an sdp_stream_metadata field
|
||||||
|
/// to the events containing a session description i.e.:
|
||||||
|
/// m.call.invite, m.call.answer, m.call.negotiate
|
||||||
|
///
|
||||||
|
class SDPStreamPurpose {
|
||||||
|
// SDPStreamMetadataPurpose
|
||||||
|
String purpose;
|
||||||
|
bool audio_muted;
|
||||||
|
bool video_muted;
|
||||||
|
|
||||||
|
SDPStreamPurpose();
|
||||||
|
factory SDPStreamPurpose.fromJson(Map<String, dynamic> json) {
|
||||||
|
return SDPStreamPurpose()
|
||||||
|
..audio_muted = json['audio_muted'] as bool ?? false
|
||||||
|
..video_muted = json['video_muted'] as bool ?? false
|
||||||
|
..purpose = json['purpose'] as String;
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return {
|
||||||
|
'purpose': purpose,
|
||||||
|
if (audio_muted != null) 'audio_muted': audio_muted,
|
||||||
|
if (video_muted != null) 'video_muted': video_muted,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class SDPStreamMetadataPurpose {
|
||||||
|
static String Usermedia = 'm.usermedia';
|
||||||
|
static String Screenshare = 'm.screenshare';
|
||||||
|
}
|
||||||
|
|
||||||
|
class SDPStreamMetadata {
|
||||||
|
Map<String, SDPStreamPurpose> sdpStreamMetadatas;
|
||||||
|
SDPStreamMetadata(this.sdpStreamMetadatas);
|
||||||
|
|
||||||
|
factory SDPStreamMetadata.fromJson(Map<String, dynamic> json) {
|
||||||
|
return SDPStreamMetadata(json
|
||||||
|
.map((key, value) => MapEntry(key, SDPStreamPurpose.fromJson(value))));
|
||||||
|
}
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return sdpStreamMetadatas
|
||||||
|
.map((key, value) => MapEntry(key, value.toJson()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// MSC3086: Asserted identity on VoIP calls
|
||||||
|
/// https://github.com/matrix-org/matrix-doc/pull/3086
|
||||||
|
class AssertedIdentity {
|
||||||
|
String id;
|
||||||
|
String displayName;
|
||||||
|
String avatarUrl;
|
||||||
|
AssertedIdentity();
|
||||||
|
factory AssertedIdentity.fromJson(Map<String, dynamic> json) {
|
||||||
|
return AssertedIdentity()
|
||||||
|
..displayName = json['display_name'] as String
|
||||||
|
..id = json['id'] as String
|
||||||
|
..avatarUrl = json['avatar_url'] as String;
|
||||||
|
}
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return {
|
||||||
|
if (displayName != null) 'display_name': displayName,
|
||||||
|
if (id != null) 'id': id,
|
||||||
|
if (avatarUrl != null) 'avatar_url': avatarUrl,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const String messageSendingStatusKey =
|
const String messageSendingStatusKey =
|
||||||
'com.famedly.famedlysdk.message_sending_status';
|
'com.famedly.famedlysdk.message_sending_status';
|
||||||
|
|
||||||
|
|
@ -1494,20 +1645,32 @@ class Room {
|
||||||
|
|
||||||
/// This is sent by the caller when they wish to establish a call.
|
/// This is sent by the caller when they wish to establish a call.
|
||||||
/// [callId] is a unique identifier for the call.
|
/// [callId] is a unique identifier for the call.
|
||||||
/// [version] is the version of the VoIP specification this message adheres to. This specification is version 0.
|
/// [version] is the version of the VoIP specification this message adheres to. This specification is version 1.
|
||||||
/// [lifetime] is the time in milliseconds that the invite is valid for. Once the invite age exceeds this value,
|
/// [lifetime] is the time in milliseconds that the invite is valid for. Once the invite age exceeds this value,
|
||||||
/// clients should discard it. They should also no longer show the call as awaiting an answer in the UI.
|
/// clients should discard it. They should also no longer show the call as awaiting an answer in the UI.
|
||||||
/// [type] The type of session description. Must be 'offer'.
|
/// [type] The type of session description. Must be 'offer'.
|
||||||
/// [sdp] The SDP text of the session description.
|
/// [sdp] The SDP text of the session description.
|
||||||
Future<String> inviteToCall(String callId, int lifetime, String sdp,
|
/// [invitee] The user ID of the person who is being invited. Invites without an invitee field are defined to be
|
||||||
{String type = 'offer', int version = 0, String txid}) async {
|
/// intended for any member of the room other than the sender of the event.
|
||||||
|
/// [party_id] The party ID for call, Can be set to client.deviceId.
|
||||||
|
Future<String> inviteToCall(
|
||||||
|
String callId, int lifetime, String party_id, String invitee, String sdp,
|
||||||
|
{String type = 'offer',
|
||||||
|
String version = voipProtoVersion,
|
||||||
|
String txid,
|
||||||
|
CallCapabilities capabilities,
|
||||||
|
SDPStreamMetadata metadata}) async {
|
||||||
txid ??= 'txid${DateTime.now().millisecondsSinceEpoch}';
|
txid ??= 'txid${DateTime.now().millisecondsSinceEpoch}';
|
||||||
|
|
||||||
final content = {
|
final content = {
|
||||||
'call_id': callId,
|
'call_id': callId,
|
||||||
|
'party_id': party_id,
|
||||||
|
'version': version,
|
||||||
'lifetime': lifetime,
|
'lifetime': lifetime,
|
||||||
'offer': {'sdp': sdp, 'type': type},
|
'offer': {'sdp': sdp, 'type': type},
|
||||||
'version': version,
|
if (invitee != null) 'invitee': invitee,
|
||||||
|
if (capabilities != null) 'capabilities': capabilities.toJson(),
|
||||||
|
if (metadata != null) sdpStreamMetadataKey: metadata.toJson(),
|
||||||
};
|
};
|
||||||
return await _sendContent(
|
return await _sendContent(
|
||||||
EventTypes.CallInvite,
|
EventTypes.CallInvite,
|
||||||
|
|
@ -1516,12 +1679,94 @@ class Room {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The calling party sends the party_id of the first selected answer.
|
||||||
|
///
|
||||||
|
/// Usually after receiving the first answer sdp in the client.onCallAnswer event,
|
||||||
|
/// save the `party_id`, and then send `CallSelectAnswer` to others peers that the call has been picked up.
|
||||||
|
///
|
||||||
|
/// [callId] is a unique identifier for the call.
|
||||||
|
/// [version] is the version of the VoIP specification this message adheres to. This specification is version 1.
|
||||||
|
/// [party_id] The party ID for call, Can be set to client.deviceId.
|
||||||
|
/// [selected_party_id] The party ID for the selected answer.
|
||||||
|
Future<String> selectCallAnswer(
|
||||||
|
String callId, int lifetime, String party_id, String selected_party_id,
|
||||||
|
{String version = voipProtoVersion, String txid}) async {
|
||||||
|
txid ??= 'txid${DateTime.now().millisecondsSinceEpoch}';
|
||||||
|
|
||||||
|
final content = {
|
||||||
|
'call_id': callId,
|
||||||
|
'party_id': party_id,
|
||||||
|
'version': version,
|
||||||
|
'lifetime': lifetime,
|
||||||
|
'selected_party_id': selected_party_id,
|
||||||
|
};
|
||||||
|
|
||||||
|
return await _sendContent(
|
||||||
|
EventTypes.CallSelectAnswer,
|
||||||
|
content,
|
||||||
|
txid: txid,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reject a call
|
||||||
|
/// [callId] is a unique identifier for the call.
|
||||||
|
/// [version] is the version of the VoIP specification this message adheres to. This specification is version 1.
|
||||||
|
/// [party_id] The party ID for call, Can be set to client.deviceId.
|
||||||
|
Future<String> sendCallReject(String callId, int lifetime, String party_id,
|
||||||
|
{String version = voipProtoVersion, String txid}) async {
|
||||||
|
txid ??= 'txid${DateTime.now().millisecondsSinceEpoch}';
|
||||||
|
|
||||||
|
final content = {
|
||||||
|
'call_id': callId,
|
||||||
|
'party_id': party_id,
|
||||||
|
'version': version,
|
||||||
|
'lifetime': lifetime,
|
||||||
|
};
|
||||||
|
|
||||||
|
return await _sendContent(
|
||||||
|
EventTypes.CallReject,
|
||||||
|
content,
|
||||||
|
txid: txid,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// When local audio/video tracks are added/deleted or hold/unhold,
|
||||||
|
/// need to createOffer and renegotiation.
|
||||||
|
/// [callId] is a unique identifier for the call.
|
||||||
|
/// [version] is the version of the VoIP specification this message adheres to. This specification is version 1.
|
||||||
|
/// [party_id] The party ID for call, Can be set to client.deviceId.
|
||||||
|
Future<String> sendCallNegotiate(
|
||||||
|
String callId, int lifetime, String party_id, String sdp,
|
||||||
|
{String type = 'offer',
|
||||||
|
String version = voipProtoVersion,
|
||||||
|
String txid,
|
||||||
|
CallCapabilities capabilities,
|
||||||
|
SDPStreamMetadata metadata}) async {
|
||||||
|
txid ??= 'txid${DateTime.now().millisecondsSinceEpoch}';
|
||||||
|
final content = {
|
||||||
|
'call_id': callId,
|
||||||
|
'party_id': party_id,
|
||||||
|
'version': version,
|
||||||
|
'lifetime': lifetime,
|
||||||
|
'description': {'sdp': sdp, 'type': type},
|
||||||
|
if (capabilities != null) 'capabilities': capabilities.toJson(),
|
||||||
|
if (metadata != null) sdpStreamMetadataKey: metadata.toJson(),
|
||||||
|
};
|
||||||
|
return await _sendContent(
|
||||||
|
EventTypes.CallNegotiate,
|
||||||
|
content,
|
||||||
|
txid: txid,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/// This is sent by callers after sending an invite and by the callee after answering.
|
/// This is sent by callers after sending an invite and by the callee after answering.
|
||||||
/// Its purpose is to give the other party additional ICE candidates to try using to communicate.
|
/// Its purpose is to give the other party additional ICE candidates to try using to communicate.
|
||||||
///
|
///
|
||||||
/// [callId] The ID of the call this event relates to.
|
/// [callId] The ID of the call this event relates to.
|
||||||
///
|
///
|
||||||
/// [version] The version of the VoIP specification this messages adheres to. This specification is version 0.
|
/// [version] The version of the VoIP specification this messages adheres to. This specification is version 1.
|
||||||
|
///
|
||||||
|
/// [party_id] The party ID for call, Can be set to client.deviceId.
|
||||||
///
|
///
|
||||||
/// [candidates] Array of objects describing the candidates. Example:
|
/// [candidates] Array of objects describing the candidates. Example:
|
||||||
///
|
///
|
||||||
|
|
@ -1536,15 +1781,17 @@ class Room {
|
||||||
/// ```
|
/// ```
|
||||||
Future<String> sendCallCandidates(
|
Future<String> sendCallCandidates(
|
||||||
String callId,
|
String callId,
|
||||||
|
String party_id,
|
||||||
List<Map<String, dynamic>> candidates, {
|
List<Map<String, dynamic>> candidates, {
|
||||||
int version = 0,
|
String version = voipProtoVersion,
|
||||||
String txid,
|
String txid,
|
||||||
}) async {
|
}) async {
|
||||||
txid ??= 'txid${DateTime.now().millisecondsSinceEpoch}';
|
txid ??= 'txid${DateTime.now().millisecondsSinceEpoch}';
|
||||||
final content = {
|
final content = {
|
||||||
'call_id': callId,
|
'call_id': callId,
|
||||||
'candidates': candidates,
|
'party_id': party_id,
|
||||||
'version': version,
|
'version': version,
|
||||||
|
'candidates': candidates,
|
||||||
};
|
};
|
||||||
return await _sendContent(
|
return await _sendContent(
|
||||||
EventTypes.CallCandidates,
|
EventTypes.CallCandidates,
|
||||||
|
|
@ -1555,16 +1802,24 @@ class Room {
|
||||||
|
|
||||||
/// This event is sent by the callee when they wish to answer the call.
|
/// This event is sent by the callee when they wish to answer the call.
|
||||||
/// [callId] is a unique identifier for the call.
|
/// [callId] is a unique identifier for the call.
|
||||||
/// [version] is the version of the VoIP specification this message adheres to. This specification is version 0.
|
/// [version] is the version of the VoIP specification this message adheres to. This specification is version 1.
|
||||||
/// [type] The type of session description. Must be 'answer'.
|
/// [type] The type of session description. Must be 'answer'.
|
||||||
/// [sdp] The SDP text of the session description.
|
/// [sdp] The SDP text of the session description.
|
||||||
Future<String> answerCall(String callId, String sdp,
|
/// [party_id] The party ID for call, Can be set to client.deviceId.
|
||||||
{String type = 'answer', int version = 0, String txid}) async {
|
Future<String> answerCall(String callId, String sdp, String party_id,
|
||||||
|
{String type = 'answer',
|
||||||
|
String version = voipProtoVersion,
|
||||||
|
String txid,
|
||||||
|
CallCapabilities capabilities,
|
||||||
|
SDPStreamMetadata metadata}) async {
|
||||||
txid ??= 'txid${DateTime.now().millisecondsSinceEpoch}';
|
txid ??= 'txid${DateTime.now().millisecondsSinceEpoch}';
|
||||||
final content = {
|
final content = {
|
||||||
'call_id': callId,
|
'call_id': callId,
|
||||||
'answer': {'sdp': sdp, 'type': type},
|
'party_id': party_id,
|
||||||
'version': version,
|
'version': version,
|
||||||
|
'answer': {'sdp': sdp, 'type': type},
|
||||||
|
if (capabilities != null) 'capabilities': capabilities.toJson(),
|
||||||
|
if (metadata != null) sdpStreamMetadataKey: metadata.toJson(),
|
||||||
};
|
};
|
||||||
return await _sendContent(
|
return await _sendContent(
|
||||||
EventTypes.CallAnswer,
|
EventTypes.CallAnswer,
|
||||||
|
|
@ -1575,14 +1830,17 @@ class Room {
|
||||||
|
|
||||||
/// This event is sent by the callee when they wish to answer the call.
|
/// This event is sent by the callee when they wish to answer the call.
|
||||||
/// [callId] The ID of the call this event relates to.
|
/// [callId] The ID of the call this event relates to.
|
||||||
/// [version] is the version of the VoIP specification this message adheres to. This specification is version 0.
|
/// [version] is the version of the VoIP specification this message adheres to. This specification is version 1.
|
||||||
Future<String> hangupCall(String callId,
|
/// [party_id] The party ID for call, Can be set to client.deviceId.
|
||||||
{int version = 0, String txid}) async {
|
Future<String> hangupCall(String callId, String party_id, String hangupCause,
|
||||||
|
{String version = voipProtoVersion, String txid}) async {
|
||||||
txid ??= 'txid${DateTime.now().millisecondsSinceEpoch}';
|
txid ??= 'txid${DateTime.now().millisecondsSinceEpoch}';
|
||||||
|
|
||||||
final content = {
|
final content = {
|
||||||
'call_id': callId,
|
'call_id': callId,
|
||||||
|
'party_id': party_id,
|
||||||
'version': version,
|
'version': version,
|
||||||
|
if (hangupCause != null) 'reason': hangupCause,
|
||||||
};
|
};
|
||||||
return await _sendContent(
|
return await _sendContent(
|
||||||
EventTypes.CallHangup,
|
EventTypes.CallHangup,
|
||||||
|
|
@ -1591,6 +1849,83 @@ class Room {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Send SdpStreamMetadata Changed event.
|
||||||
|
///
|
||||||
|
/// This MSC also adds a new call event m.call.sdp_stream_metadata_changed,
|
||||||
|
/// which has the common VoIP fields as specified in
|
||||||
|
/// MSC2746 (version, call_id, party_id) and a sdp_stream_metadata object which
|
||||||
|
/// is the same thing as sdp_stream_metadata in m.call.negotiate, m.call.invite
|
||||||
|
/// and m.call.answer. The client sends this event the when sdp_stream_metadata
|
||||||
|
/// has changed but no negotiation is required
|
||||||
|
/// (e.g. the user mutes their camera/microphone).
|
||||||
|
///
|
||||||
|
/// [callId] The ID of the call this event relates to.
|
||||||
|
/// [version] is the version of the VoIP specification this message adheres to. This specification is version 1.
|
||||||
|
/// [party_id] The party ID for call, Can be set to client.deviceId.
|
||||||
|
/// [metadata] The sdp_stream_metadata object.
|
||||||
|
Future<String> sendSDPStreamMetadataChanged(
|
||||||
|
String callId, String party_id, SDPStreamMetadata metadata,
|
||||||
|
{String version = voipProtoVersion, String txid}) async {
|
||||||
|
txid ??= 'txid${DateTime.now().millisecondsSinceEpoch}';
|
||||||
|
final content = {
|
||||||
|
'call_id': callId,
|
||||||
|
'party_id': party_id,
|
||||||
|
'version': version,
|
||||||
|
sdpStreamMetadataKey: metadata.toJson(),
|
||||||
|
};
|
||||||
|
return await _sendContent(
|
||||||
|
EventTypes.CallSDPStreamMetadataChangedPrefix,
|
||||||
|
content,
|
||||||
|
txid: txid,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// CallReplacesEvent for Transfered calls
|
||||||
|
///
|
||||||
|
/// [callId] The ID of the call this event relates to.
|
||||||
|
/// [version] is the version of the VoIP specification this message adheres to. This specification is version 1.
|
||||||
|
/// [party_id] The party ID for call, Can be set to client.deviceId.
|
||||||
|
/// [callReplaces] transfer info
|
||||||
|
Future<String> sendCallReplaces(
|
||||||
|
String callId, String party_id, CallReplaces callReplaces,
|
||||||
|
{String version = voipProtoVersion, String txid}) async {
|
||||||
|
txid ??= 'txid${DateTime.now().millisecondsSinceEpoch}';
|
||||||
|
final content = {
|
||||||
|
'call_id': callId,
|
||||||
|
'party_id': party_id,
|
||||||
|
'version': version,
|
||||||
|
...callReplaces.toJson(),
|
||||||
|
};
|
||||||
|
return await _sendContent(
|
||||||
|
EventTypes.CallReplaces,
|
||||||
|
content,
|
||||||
|
txid: txid,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// send AssertedIdentity event
|
||||||
|
///
|
||||||
|
/// [callId] The ID of the call this event relates to.
|
||||||
|
/// [version] is the version of the VoIP specification this message adheres to. This specification is version 1.
|
||||||
|
/// [party_id] The party ID for call, Can be set to client.deviceId.
|
||||||
|
/// [assertedIdentity] the asserted identity
|
||||||
|
Future<String> sendAssertedIdentity(
|
||||||
|
String callId, String party_id, AssertedIdentity assertedIdentity,
|
||||||
|
{String version = voipProtoVersion, String txid}) async {
|
||||||
|
txid ??= 'txid${DateTime.now().millisecondsSinceEpoch}';
|
||||||
|
final content = {
|
||||||
|
'call_id': callId,
|
||||||
|
'party_id': party_id,
|
||||||
|
'version': version,
|
||||||
|
'asserted_identity': assertedIdentity.toJson(),
|
||||||
|
};
|
||||||
|
return await _sendContent(
|
||||||
|
EventTypes.CallAssertedIdentity,
|
||||||
|
content,
|
||||||
|
txid: txid,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/// A room may be public meaning anyone can join the room without any prior action. Alternatively,
|
/// A room may be public meaning anyone can join the room without any prior action. Alternatively,
|
||||||
/// it can be invite meaning that a user who wishes to join the room must first receive an invite
|
/// it can be invite meaning that a user who wishes to join the room must first receive an invite
|
||||||
/// to the room from someone already inside of the room. Currently, knock and private are reserved
|
/// to the room from someone already inside of the room. Currently, knock and private are reserved
|
||||||
|
|
|
||||||
|
|
@ -2133,10 +2133,26 @@ class FakeMatrixApi extends MockClient {
|
||||||
(var req) => {},
|
(var req) => {},
|
||||||
'/client/r0/rooms/!localpart%3Aserver.abc/send/m.call.answer/1234':
|
'/client/r0/rooms/!localpart%3Aserver.abc/send/m.call.answer/1234':
|
||||||
(var req) => {},
|
(var req) => {},
|
||||||
|
'/client/r0/rooms/!localpart%3Aserver.abc/send/m.call.select_answer/1234':
|
||||||
|
(var req) => {},
|
||||||
|
'/client/r0/rooms/!localpart%3Aserver.abc/send/m.call.reject/1234':
|
||||||
|
(var req) => {},
|
||||||
|
'/client/r0/rooms/!localpart%3Aserver.abc/send/m.call.negotiate/1234':
|
||||||
|
(var req) => {},
|
||||||
'/client/r0/rooms/!localpart%3Aserver.abc/send/m.call.candidates/1234':
|
'/client/r0/rooms/!localpart%3Aserver.abc/send/m.call.candidates/1234':
|
||||||
(var req) => {},
|
(var req) => {},
|
||||||
'/client/r0/rooms/!localpart%3Aserver.abc/send/m.call.hangup/1234':
|
'/client/r0/rooms/!localpart%3Aserver.abc/send/m.call.hangup/1234':
|
||||||
(var req) => {},
|
(var req) => {},
|
||||||
|
'/client/r0/rooms/!localpart%3Aserver.abc/send/m.call.replaces/1234':
|
||||||
|
(var req) => {},
|
||||||
|
'/client/r0/rooms/!localpart%3Aserver.abc/send/m.call.asserted_identity/1234':
|
||||||
|
(var req) => {},
|
||||||
|
'/client/r0/rooms/!localpart%3Aserver.abc/send/m.call.sdp_stream_metadata_changed/1234':
|
||||||
|
(var req) => {},
|
||||||
|
'/client/r0/rooms/!localpart%3Aserver.abc/send/org.matrix.call.sdp_stream_metadata_changed/1234':
|
||||||
|
(var req) => {},
|
||||||
|
'/client/r0/rooms/!localpart%3Aserver.abc/send/org.matrix.call.asserted_identity/1234':
|
||||||
|
(var req) => {},
|
||||||
'/client/r0/rooms/!1234%3Aexample.com/redact/1143273582443PhrSn%3Aexample.org/1234':
|
'/client/r0/rooms/!1234%3Aexample.com/redact/1143273582443PhrSn%3Aexample.org/1234':
|
||||||
(var req) => {'event_id': '1234'},
|
(var req) => {'event_id': '1234'},
|
||||||
'/client/r0/pushrules/global/room/!localpart%3Aserver.abc': (var req) =>
|
'/client/r0/pushrules/global/room/!localpart%3Aserver.abc': (var req) =>
|
||||||
|
|
|
||||||
|
|
@ -683,10 +683,25 @@ void main() {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Test call methods', () async {
|
test('Test call methods', () async {
|
||||||
await room.inviteToCall('1234', 1234, 'sdp', txid: '1234');
|
await room.inviteToCall('1234', 1234, '4567', '7890', 'sdp',
|
||||||
await room.answerCall('1234', 'sdp', txid: '1234');
|
txid: '1234');
|
||||||
await room.hangupCall('1234', txid: '1234');
|
await room.answerCall('1234', 'sdp', '4567', txid: '1234');
|
||||||
await room.sendCallCandidates('1234', [], txid: '1234');
|
await room.sendCallCandidates('1234', '4567', [], txid: '1234');
|
||||||
|
await room.selectCallAnswer('1234', 1234, '4567', '6789', txid: '1234');
|
||||||
|
await room.sendCallReject('1234', 1234, '4567', txid: '1234');
|
||||||
|
await room.sendCallNegotiate('1234', 1234, '4567', 'sdp', txid: '1234');
|
||||||
|
await room.hangupCall('1234', '4567', 'user_hangup', txid: '1234');
|
||||||
|
await room.sendAssertedIdentity(
|
||||||
|
'1234',
|
||||||
|
'4567',
|
||||||
|
AssertedIdentity()
|
||||||
|
..displayName = 'name'
|
||||||
|
..id = 'some_id',
|
||||||
|
txid: '1234');
|
||||||
|
await room.sendCallReplaces('1234', '4567', CallReplaces(), txid: '1234');
|
||||||
|
await room.sendSDPStreamMetadataChanged(
|
||||||
|
'1234', '4567', SDPStreamMetadata({}),
|
||||||
|
txid: '1234');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('enableEncryption', () async {
|
test('enableEncryption', () async {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue