Merge branch 'client-enhance-add-call-methods' into 'master'

[Client] Add call methods

See merge request famedly/famedlysdk!153
This commit is contained in:
Christian Pauly 2020-01-04 18:39:57 +00:00
commit 5430bad08a
5 changed files with 171 additions and 0 deletions

View file

@ -32,6 +32,7 @@ export 'package:famedlysdk/src/utils/mx_content.dart';
export 'package:famedlysdk/src/utils/profile.dart'; export 'package:famedlysdk/src/utils/profile.dart';
export 'package:famedlysdk/src/utils/push_rules.dart'; export 'package:famedlysdk/src/utils/push_rules.dart';
export 'package:famedlysdk/src/utils/states_map.dart'; export 'package:famedlysdk/src/utils/states_map.dart';
export 'package:famedlysdk/src/utils/turn_server_credentials.dart';
export 'package:famedlysdk/src/account_data.dart'; export 'package:famedlysdk/src/account_data.dart';
export 'package:famedlysdk/src/client.dart'; export 'package:famedlysdk/src/client.dart';
export 'package:famedlysdk/src/event.dart'; export 'package:famedlysdk/src/event.dart';

View file

@ -30,6 +30,7 @@ import 'package:famedlysdk/src/presence.dart';
import 'package:famedlysdk/src/store_api.dart'; import 'package:famedlysdk/src/store_api.dart';
import 'package:famedlysdk/src/sync/user_update.dart'; import 'package:famedlysdk/src/sync/user_update.dart';
import 'package:famedlysdk/src/utils/matrix_file.dart'; import 'package:famedlysdk/src/utils/matrix_file.dart';
import 'package:famedlysdk/src/utils/turn_server_credentials.dart';
import 'package:pedantic/pedantic.dart'; import 'package:pedantic/pedantic.dart';
import 'room.dart'; import 'room.dart';
import 'event.dart'; import 'event.dart';
@ -400,6 +401,15 @@ class Client {
return; return;
} }
/// Get credentials for the client to use when initiating calls.
Future<TurnServerCredentials> getTurnServerCredentials() async {
final Map<String, dynamic> response = await this.jsonRequest(
type: HTTPType.GET,
action: "/client/r0/voip/turnServer",
);
return TurnServerCredentials.fromJson(response);
}
/// Fetches the pushrules for the logged in user. /// Fetches the pushrules for the logged in user.
/// These are needed for notifications on Android /// These are needed for notifications on Android
Future<PushRules> getPushrules() async { Future<PushRules> getPushrules() async {
@ -477,6 +487,18 @@ class Client {
final StreamController<AccountData> onAccountData = final StreamController<AccountData> onAccountData =
StreamController.broadcast(); StreamController.broadcast();
/// Will be called on call invites.
final StreamController<Event> onCallInvite = StreamController.broadcast();
/// Will be called on call hangups.
final StreamController<Event> onCallHangup = StreamController.broadcast();
/// Will be called on call candidates.
final StreamController<Event> onCallCandidates = StreamController.broadcast();
/// Will be called on call answers.
final StreamController<Event> onCallAnswer = StreamController.broadcast();
/// Matrix synchronisation is done with https long polling. This needs a /// Matrix synchronisation is done with https long polling. This needs a
/// timeout which is usually 30 seconds. /// timeout which is usually 30 seconds.
int syncTimeoutSec = 30; int syncTimeoutSec = 30;
@ -894,6 +916,16 @@ class Client {
_updateRoomsByEventUpdate(update); _updateRoomsByEventUpdate(update);
this.store?.storeEventUpdate(update); this.store?.storeEventUpdate(update);
onEvent.add(update); onEvent.add(update);
if (event["type"] == "m.call.invite") {
onCallInvite.add(Event.fromJson(event, getRoomById(roomID)));
} else if (event["type"] == "m.call.hangup") {
onCallHangup.add(Event.fromJson(event, getRoomById(roomID)));
} else if (event["type"] == "m.call.answer") {
onCallAnswer.add(Event.fromJson(event, getRoomById(roomID)));
} else if (event["type"] == "m.call.candidates") {
onCallCandidates.add(Event.fromJson(event, getRoomById(roomID)));
}
} }
} }

View file

@ -220,6 +220,18 @@ class Event {
return EventTypes.Sticker; return EventTypes.Sticker;
case "m.room.message": case "m.room.message":
return EventTypes.Message; return EventTypes.Message;
case "m.call.encrypted":
return EventTypes.Encrypted;
case "m.call.encryption":
return EventTypes.Encryption;
case "m.call.invite":
return EventTypes.CallInvite;
case "m.call.answer":
return EventTypes.CallAnswer;
case "m.call.candidates":
return EventTypes.CallCandidates;
case "m.call.hangup":
return EventTypes.CallHangup;
} }
return EventTypes.Unknown; return EventTypes.Unknown;
} }
@ -394,5 +406,11 @@ enum EventTypes {
RoomAvatar, RoomAvatar,
GuestAccess, GuestAccess,
HistoryVisibility, HistoryVisibility,
Encryption,
Encrypted,
CallInvite,
CallAnswer,
CallCandidates,
CallHangup,
Unknown, Unknown,
} }

View file

@ -1048,6 +1048,103 @@ class Room {
data: data, data: data,
); );
} }
/// This is sent by the caller when they wish to establish 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 0.
/// [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.
/// [type] The type of session description. Must be 'offer'.
/// [sdp] The SDP text of the session description.
Future<String> inviteToCall(String callId, int lifetime, String sdp,
{String type = "offer", int version = 0, String txid}) async {
if (txid == null) txid = "txid${DateTime.now().millisecondsSinceEpoch}";
final Map<String, dynamic> response = await client.jsonRequest(
type: HTTPType.PUT,
action: "/client/r0/rooms/$id/send/m.call.invite/$txid",
data: {
"call_id": callId,
"lifetime": lifetime,
"offer": {"sdp": sdp, "type": type},
"version": version,
},
);
return response["event_id"];
}
/// 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.
///
/// [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.
///
/// [candidates] Array of objects describing the candidates. Example:
///
/// ```
/// [
/// {
/// "candidate": "candidate:863018703 1 udp 2122260223 10.9.64.156 43670 typ host generation 0",
/// "sdpMLineIndex": 0,
/// "sdpMid": "audio"
/// }
/// ],
/// ```
Future<String> sendCallCandidates(
String callId,
List<Map<String, dynamic>> candidates, {
int version = 0,
String txid,
}) async {
if (txid == null) txid = "txid${DateTime.now().millisecondsSinceEpoch}";
final Map<String, dynamic> response = await client.jsonRequest(
type: HTTPType.PUT,
action: "/client/r0/rooms/$id/send/m.call.candidates/$txid",
data: {
"call_id": callId,
"candidates": candidates,
"version": version,
},
);
return response["event_id"];
}
/// This event is sent by the callee when they wish to answer 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.
/// [type] The type of session description. Must be 'answer'.
/// [sdp] The SDP text of the session description.
Future<String> answerCall(String callId, String sdp,
{String type = "answer", int version = 0, String txid}) async {
if (txid == null) txid = "txid${DateTime.now().millisecondsSinceEpoch}";
final Map<String, dynamic> response = await client.jsonRequest(
type: HTTPType.PUT,
action: "/client/r0/rooms/$id/send/m.call.answer/$txid",
data: {
"call_id": callId,
"answer": {"sdp": sdp, "type": type},
"version": version,
},
);
return response["event_id"];
}
/// This event is sent by the callee when they wish to answer the call.
/// [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.
Future<String> hangupCall(String callId,
{int version = 0, String txid}) async {
if (txid == null) txid = "txid${DateTime.now().millisecondsSinceEpoch}";
final Map<String, dynamic> response = await client.jsonRequest(
type: HTTPType.PUT,
action: "/client/r0/rooms/$id/send/m.call.hangup/$txid",
data: {
"call_id": callId,
"version": version,
},
);
return response["event_id"];
}
} }
enum PushRuleState { notify, mentions_only, dont_notify } enum PushRuleState { notify, mentions_only, dont_notify }

View file

@ -0,0 +1,23 @@
/// Credentials for the client to use when initiating calls.
class TurnServerCredentials {
/// The username to use.
final String username;
/// The password to use.
final String password;
/// A list of TURN URIs
final List<String> uris;
/// The time-to-live in seconds
final int ttl;
const TurnServerCredentials(
this.username, this.password, this.uris, this.ttl);
TurnServerCredentials.fromJson(Map<String, dynamic> json)
: username = json['username'],
password = json['password'],
uris = json['uris'].cast<String>(),
ttl = json['ttl'];
}