aboutsummaryrefslogtreecommitdiff
path: root/libdino
diff options
context:
space:
mode:
authorfiaxh <git@lightrise.org>2022-02-02 21:37:05 +0100
committerfiaxh <git@lightrise.org>2022-02-07 01:21:11 +0100
commit4ef50db3e581016365087759d5af8649e37ab8a7 (patch)
treeec7d47401f8424b5b9999941e05f61409594c539 /libdino
parent5ed8d28a27948ed949eb80b0da06d2c4ec9571ff (diff)
downloaddino-4ef50db3e581016365087759d5af8649e37ab8a7.tar.gz
dino-4ef50db3e581016365087759d5af8649e37ab8a7.zip
Various call UI/UX improvements
Diffstat (limited to 'libdino')
-rw-r--r--libdino/src/plugin/interfaces.vala2
-rw-r--r--libdino/src/service/call_peer_state.vala11
-rw-r--r--libdino/src/service/call_state.vala79
-rw-r--r--libdino/src/service/calls.vala45
-rw-r--r--libdino/src/service/notification_events.vala1
5 files changed, 87 insertions, 51 deletions
diff --git a/libdino/src/plugin/interfaces.vala b/libdino/src/plugin/interfaces.vala
index fb80fef6..23e64373 100644
--- a/libdino/src/plugin/interfaces.vala
+++ b/libdino/src/plugin/interfaces.vala
@@ -96,7 +96,7 @@ public abstract interface ConversationAdditionPopulator : ConversationItemPopula
public abstract interface VideoCallPlugin : Object {
- public abstract bool supports(string media);
+ public abstract bool supports(string? media);
// Video widget
public abstract VideoCallWidget? create_widget(WidgetType type);
diff --git a/libdino/src/service/call_peer_state.vala b/libdino/src/service/call_peer_state.vala
index 09440371..902a0792 100644
--- a/libdino/src/service/call_peer_state.vala
+++ b/libdino/src/service/call_peer_state.vala
@@ -3,6 +3,7 @@ using Gee;
using Xmpp;
public class Dino.PeerState : Object {
+ public signal void stream_created(string media);
public signal void counterpart_sends_video_updated(bool mute);
public signal void info_received(Xep.JingleRtp.CallSessionInfo session_info);
@@ -214,14 +215,14 @@ public class Dino.PeerState : Object {
// If video_content_parameter == null && !mute we're trying to mute a non-existant feed. It will be muted as soon as it is created.
}
- public Xep.JingleRtp.Stream? get_video_stream(Call call) {
+ public Xep.JingleRtp.Stream? get_video_stream() {
if (video_content_parameter != null) {
return video_content_parameter.stream;
}
return null;
}
- public Xep.JingleRtp.Stream? get_audio_stream(Call call) {
+ public Xep.JingleRtp.Stream? get_audio_stream() {
if (audio_content_parameter != null) {
return audio_content_parameter.stream;
}
@@ -235,8 +236,8 @@ public class Dino.PeerState : Object {
session.terminated.connect((stream, we_terminated, reason_name, reason_text) =>
session_terminated(we_terminated, reason_name, reason_text)
);
- session.additional_content_add_incoming.connect((session,stream, content) =>
- on_incoming_content_add(stream, session, content)
+ session.additional_content_add_incoming.connect((stream, content) =>
+ on_incoming_content_add(stream, content.session, content)
);
foreach (Xep.Jingle.Content content in session.contents) {
@@ -358,6 +359,8 @@ public class Dino.PeerState : Object {
} else if (media == "audio" && !we_should_send_audio) {
mute_own_audio(true);
}
+
+ stream_created(media);
}
private void on_counterpart_mute_update(bool mute, string? media) {
diff --git a/libdino/src/service/call_state.vala b/libdino/src/service/call_state.vala
index 7d205f7f..03ee9595 100644
--- a/libdino/src/service/call_state.vala
+++ b/libdino/src/service/call_state.vala
@@ -9,6 +9,7 @@ public class Dino.CallState : Object {
public signal void peer_left(Jid jid, PeerState peer_state, string? reason_name, string? reason_text);
public StreamInteractor stream_interactor;
+ public Plugins.VideoCallPlugin call_plugin = Dino.Application.get_default().plugin_registry.video_call_plugin;
public Call call;
public Xep.Muji.GroupCall? group_call { get; set; }
public Jid? parent_muc { get; set; }
@@ -109,22 +110,25 @@ public class Dino.CallState : Object {
terminated(call.account.bare_jid, null, null);
}
- public void end() {
+ public void end(string? reason_text = null) {
var peers_cpy = new ArrayList<PeerState>();
peers_cpy.add_all(peers.values);
if (group_call != null) {
- stream_interactor.get_module(MucManager.IDENTITY).part(call.account, group_call.muc_jid);
+ XmppStream stream = stream_interactor.get_stream(call.account);
+ if (stream != null) {
+ stream.get_module(Xep.Muc.Module.IDENTITY).exit(stream, group_call.muc_jid);
+ }
}
if (call.state == Call.State.IN_PROGRESS || call.state == Call.State.ESTABLISHING) {
foreach (PeerState peer in peers_cpy) {
- peer.end(Xep.Jingle.ReasonElement.SUCCESS);
+ peer.end(Xep.Jingle.ReasonElement.SUCCESS, reason_text);
}
call.state = Call.State.ENDED;
} else if (call.state == Call.State.RINGING) {
foreach (PeerState peer in peers_cpy) {
- peer.end(Xep.Jingle.ReasonElement.CANCEL);
+ peer.end(Xep.Jingle.ReasonElement.CANCEL, reason_text);
}
if (parent_muc != null && group_call != null) {
XmppStream stream = stream_interactor.get_stream(call.account);
@@ -138,7 +142,7 @@ public class Dino.CallState : Object {
call.end_time = new DateTime.now_utc();
- terminated(call.account.bare_jid, null, null);
+ terminated(call.account.bare_jid, null, reason_text);
}
public void mute_own_audio(bool mute) {
@@ -168,7 +172,7 @@ public class Dino.CallState : Object {
debug("[%s] Inviting to muji call %s", call.account.bare_jid.to_string(), invitee.to_string());
yield stream.get_module(Xep.Muc.Module.IDENTITY).change_affiliation(stream, group_call.muc_jid, invitee, null, "owner");
- stream.get_module(Xep.MujiMeta.Module.IDENTITY).send_invite(stream, invitee, group_call.muc_jid, we_should_send_video);
+ stream.get_module(Xep.MujiMeta.Module.IDENTITY).send_invite(stream, invitee, group_call.muc_jid, we_should_send_video, message_type);
// If the peer hasn't accepted within a minute, retract the invite
Timeout.add_seconds(60, () => {
@@ -183,13 +187,43 @@ public class Dino.CallState : Object {
if (!contains_peer) {
debug("[%s] Retracting invite to %s from %s", call.account.bare_jid.to_string(), group_call.muc_jid.to_string(), invitee.to_string());
- stream.get_module(Xep.MujiMeta.Module.IDENTITY).send_invite_retract_to_peer(stream, invitee, group_call.muc_jid);
+ stream.get_module(Xep.MujiMeta.Module.IDENTITY).send_invite_retract_to_peer(stream, invitee, group_call.muc_jid, message_type);
stream.get_module(Xep.Muc.Module.IDENTITY).change_affiliation.begin(stream, group_call.muc_jid, invitee, null, "none");
}
return false;
});
}
+ public Plugins.MediaDevice? get_microphone_device() {
+ if (peers.is_empty) return null;
+ var audio_stream = peers.values.to_array()[0].get_audio_stream();
+ return call_plugin.get_device(audio_stream, false);
+ }
+
+ public Plugins.MediaDevice? get_speaker_device() {
+ if (peers.is_empty) return null;
+ var audio_stream = peers.values.to_array()[0].get_audio_stream();
+ return call_plugin.get_device(audio_stream, true);
+ }
+
+ public Plugins.MediaDevice? get_video_device() {
+ if (peers.is_empty) return null;
+ var video_stream = peers.values.to_array()[0].get_video_stream();
+ return call_plugin.get_device(video_stream, false);
+ }
+
+ public void set_audio_device(Plugins.MediaDevice? device) {
+ foreach (PeerState peer_state in peers.values) {
+ call_plugin.set_device(peer_state.get_audio_stream(), device);
+ }
+ }
+
+ public void set_video_device(Plugins.MediaDevice? device) {
+ foreach (PeerState peer_state in peers.values) {
+ call_plugin.set_device(peer_state.get_video_stream(), device);
+ }
+ }
+
internal void rename_peer(Jid from_jid, Jid to_jid) {
debug("[%s] Renaming %s to %s exists %s", call.account.bare_jid.to_string(), from_jid.to_string(), to_jid.to_string(), peers.has_key(from_jid).to_string());
PeerState? peer_state = peers[from_jid];
@@ -226,23 +260,35 @@ public class Dino.CallState : Object {
this.bind_property("group-call", peer_state, "group-call", BindingFlags.SYNC_CREATE | BindingFlags.BIDIRECTIONAL);
peer_state.session_terminated.connect((we_terminated, reason_name, reason_text) => {
+ debug("[%s] Peer left %s: %s %s (%i peers remaining)", call.account.bare_jid.to_string(), reason_text ?? "", reason_name ?? "", peer_state.jid.to_string(), peers.size);
peers.unset(peer_state.jid);
- debug("[%s] Peer left %s left %i", call.account.bare_jid.to_string(), peer_state.jid.to_string(), peers.size);
if (peers.is_empty) {
- if (group_call != null) group_call.leave(stream_interactor.get_stream(call.account));
- on_call_terminated(peer_state.jid, we_terminated, reason_name, reason_text);
+ if (group_call != null) {
+ group_call.leave(stream_interactor.get_stream(call.account));
+ on_call_terminated(peer_state.jid, we_terminated, null, "All participants have left the group call");
+ } else {
+ on_call_terminated(peer_state.jid, we_terminated, reason_name, reason_text);
+ }
} else {
peer_left(peer_state.jid, peer_state, reason_name, reason_text);
}
});
}
+ public async bool can_convert_into_groupcall() {
+ if (peers.size == 0) return false;
+ Jid peer = peers.keys.to_array()[0];
+ bool peer_has_feature = yield stream_interactor.get_module(EntityInfo.IDENTITY).has_feature(call.account, peer, Xep.Muji.NS_URI);
+ bool can_initiate = stream_interactor.get_module(Calls.IDENTITY).can_initiate_groupcall(call.account);
+ return peer_has_feature && can_initiate;
+ }
+
public async void convert_into_group_call() {
XmppStream stream = stream_interactor.get_stream(call.account);
if (stream == null) return;
- Jid? muc_jid = null;
+ Jid? muc_jid = stream_interactor.get_module(MucManager.IDENTITY).default_muc_server[call.account];
if (muc_jid == null) {
warning("Failed to initiate group call: MUC server not known.");
return;
@@ -320,11 +366,18 @@ public class Dino.CallState : Object {
this.group_call.peer_left.connect((jid) => {
debug("[%s] Group call peer left: %s", call.account.bare_jid.to_string(), jid.to_string());
+ PeerState? peer_state = peers[jid];
if (!peers.has_key(jid)) return;
- // end() will in the end cause a `peer_left` signal and removal from `peers`
- peers[jid].end(Xep.Jingle.ReasonElement.CANCEL, "Peer left the MUJI MUC");
+ peer_left(jid, peer_state, Xep.Jingle.ReasonElement.CANCEL, "Peer left the MUJI MUC");
+ peer_state.end(Xep.Jingle.ReasonElement.CANCEL, "Peer left the MUJI MUC");
+ peers.unset(jid);
});
+ if (group_call.peers_to_connect_to.size > 3) {
+ end("Call too full - P2p calls don't work well with many participants");
+ return;
+ }
+
// Call all peers that are in the room already
foreach (Jid peer_jid in group_call.peers_to_connect_to) {
// Don't establish connection if we have one already (the person that invited us to the call)
diff --git a/libdino/src/service/calls.vala b/libdino/src/service/calls.vala
index 741aa673..44790014 100644
--- a/libdino/src/service/calls.vala
+++ b/libdino/src/service/calls.vala
@@ -67,38 +67,24 @@ namespace Dino {
return call_state;
}
- public async bool can_do_audio_calls_async(Conversation conversation) {
- if (!can_do_audio_calls()) return false;
-
- if (conversation.type_ == Conversation.Type.CHAT) {
- return (yield get_call_resources(conversation.account, conversation.counterpart)).size > 0 || has_jmi_resources(conversation.counterpart);
- } else {
- return stream_interactor.get_module(MucManager.IDENTITY).is_private_room(conversation.account, conversation.counterpart);
- }
- }
-
- private bool can_do_audio_calls() {
+ public bool can_we_do_calls(Account account) {
Plugins.VideoCallPlugin? plugin = Application.get_default().plugin_registry.video_call_plugin;
if (plugin == null) return false;
- return plugin.supports("audio");
+ return plugin.supports(null);
}
- public async bool can_do_video_calls_async(Conversation conversation) {
- if (!can_do_video_calls()) return false;
-
+ public async bool can_conversation_do_calls(Conversation conversation) {
if (conversation.type_ == Conversation.Type.CHAT) {
return (yield get_call_resources(conversation.account, conversation.counterpart)).size > 0 || has_jmi_resources(conversation.counterpart);
} else {
- return stream_interactor.get_module(MucManager.IDENTITY).is_private_room(conversation.account, conversation.counterpart);
+ bool is_private = stream_interactor.get_module(MucManager.IDENTITY).is_private_room(conversation.account, conversation.counterpart);
+ return is_private && can_initiate_groupcall(conversation.account);
}
}
- private bool can_do_video_calls() {
- Plugins.VideoCallPlugin? plugin = Application.get_default().plugin_registry.video_call_plugin;
- if (plugin == null) return false;
-
- return plugin.supports("video");
+ public bool can_initiate_groupcall(Account account) {
+ return stream_interactor.get_module(MucManager.IDENTITY).default_muc_server[account] != null;
}
public async Gee.List<Jid> get_call_resources(Account account, Jid counterpart) {
@@ -107,7 +93,10 @@ namespace Dino {
XmppStream? stream = stream_interactor.get_stream(account);
if (stream == null) return ret;
- Gee.List<Jid>? full_jids = stream.get_flag(Presence.Flag.IDENTITY).get_resources(counterpart);
+ Presence.Flag? presence_flag = stream.get_flag(Presence.Flag.IDENTITY);
+ if (presence_flag == null) return ret;
+
+ Gee.List<Jid>? full_jids = presence_flag.get_resources(counterpart);
if (full_jids == null) return ret;
foreach (Jid full_jid in full_jids) {
@@ -148,11 +137,6 @@ namespace Dino {
}
private void on_incoming_call(Account account, Xep.Jingle.Session session) {
- if (!can_do_audio_calls()) {
- warning("Incoming call but no call support detected. Ignoring.");
- return;
- }
-
Jid? muji_muc = null;
bool counterpart_wants_video = false;
foreach (Xep.Jingle.Content content in session.contents) {
@@ -268,7 +252,7 @@ namespace Dino {
if (!call.account.equals(account)) return;
// We already know the call; this is a reflection of our own invite
- if (call_states[call].parent_muc.equals_bare(inviter_jid)) return;
+ if (call_states[call].parent_muc != null && call_states[call].parent_muc.equals_bare(inviter_jid)) return;
if (call.counterparts.contains(inviter_jid) && call_states[call].accepted) {
// A call is converted into a group call.
@@ -337,11 +321,6 @@ namespace Dino {
Xep.JingleMessageInitiation.Module mi_module = stream_interactor.module_manager.get_module(account, Xep.JingleMessageInitiation.Module.IDENTITY);
mi_module.session_proposed.connect((from, to, sid, descriptions) => {
- if (!can_do_audio_calls()) {
- warning("Incoming call but no call support detected. Ignoring.");
- return;
- }
-
bool audio_requested = descriptions.any_match((description) => description.ns_uri == Xep.JingleRtp.NS_URI && description.get_attribute("media") == "audio");
bool video_requested = descriptions.any_match((description) => description.ns_uri == Xep.JingleRtp.NS_URI && description.get_attribute("media") == "video");
if (!audio_requested && !video_requested) return;
diff --git a/libdino/src/service/notification_events.vala b/libdino/src/service/notification_events.vala
index 2408aadc..5b8db842 100644
--- a/libdino/src/service/notification_events.vala
+++ b/libdino/src/service/notification_events.vala
@@ -118,6 +118,7 @@ public class NotificationEvents : StreamInteractionModule, Object {
}
private async void on_call_incoming(Call call, CallState call_state, Conversation conversation, bool video) {
+ if (!stream_interactor.get_module(Calls.IDENTITY).can_we_do_calls(call.account)) return;
string conversation_display_name = get_conversation_display_name(stream_interactor, conversation, null);
NotificationProvider notifier = yield notifier.wait_async();