Use outgoing JMI if contact has supporting device
This commit is contained in:
parent
b63a20f918
commit
3454201e5a
|
@ -77,6 +77,8 @@ namespace Dino.Entities {
|
||||||
.value(db.call.state, State.ENDED); // No point in persisting states that can't survive a restart
|
.value(db.call.state, State.ENDED); // No point in persisting states that can't survive a restart
|
||||||
if (end_time != null) {
|
if (end_time != null) {
|
||||||
builder.value(db.call.end_time, (long) end_time.to_unix());
|
builder.value(db.call.end_time, (long) end_time.to_unix());
|
||||||
|
} else {
|
||||||
|
builder.value(db.call.end_time, (long) local_time.to_unix());
|
||||||
}
|
}
|
||||||
id = (int) builder.perform();
|
id = (int) builder.perform();
|
||||||
|
|
||||||
|
|
|
@ -21,15 +21,16 @@ namespace Dino {
|
||||||
public string id { get { return IDENTITY.id; } }
|
public string id { get { return IDENTITY.id; } }
|
||||||
|
|
||||||
private StreamInteractor stream_interactor;
|
private StreamInteractor stream_interactor;
|
||||||
|
private Database db;
|
||||||
private Xep.JingleRtp.SessionInfoType session_info_type;
|
private Xep.JingleRtp.SessionInfoType session_info_type;
|
||||||
|
|
||||||
private HashMap<Account, HashMap<Call, string>> sid_by_call = new HashMap<Account, HashMap<Call, string>>(Account.hash_func, Account.equals_func);
|
private HashMap<Account, HashMap<Call, string>> sid_by_call = new HashMap<Account, HashMap<Call, string>>(Account.hash_func, Account.equals_func);
|
||||||
private HashMap<Account, HashMap<string, Call>> call_by_sid = new HashMap<Account, HashMap<string, Call>>(Account.hash_func, Account.equals_func);
|
private HashMap<Account, HashMap<string, Call>> call_by_sid = new HashMap<Account, HashMap<string, Call>>(Account.hash_func, Account.equals_func);
|
||||||
public HashMap<Call, Xep.Jingle.Session> sessions = new HashMap<Call, Xep.Jingle.Session>(Call.hash_func, Call.equals_func);
|
public HashMap<Call, Xep.Jingle.Session> sessions = new HashMap<Call, Xep.Jingle.Session>(Call.hash_func, Call.equals_func);
|
||||||
|
|
||||||
public Call? mi_accepted_call = null;
|
public HashMap<Account, Call> jmi_call = new HashMap<Account, Call>(Account.hash_func, Account.equals_func);
|
||||||
public string? mi_accepted_sid = null;
|
public HashMap<Account, string> jmi_sid = new HashMap<Account, string>(Account.hash_func, Account.equals_func);
|
||||||
public bool mi_accepted_video = false;
|
public HashMap<Account, bool> jmi_video = new HashMap<Account, bool>(Account.hash_func, Account.equals_func);
|
||||||
|
|
||||||
private HashMap<Call, bool> counterpart_sends_video = new HashMap<Call, bool>(Call.hash_func, Call.equals_func);
|
private HashMap<Call, bool> counterpart_sends_video = new HashMap<Call, bool>(Call.hash_func, Call.equals_func);
|
||||||
private HashMap<Call, bool> we_should_send_video = new HashMap<Call, bool>(Call.hash_func, Call.equals_func);
|
private HashMap<Call, bool> we_should_send_video = new HashMap<Call, bool>(Call.hash_func, Call.equals_func);
|
||||||
|
@ -46,6 +47,7 @@ namespace Dino {
|
||||||
|
|
||||||
private Calls(StreamInteractor stream_interactor, Database db) {
|
private Calls(StreamInteractor stream_interactor, Database db) {
|
||||||
this.stream_interactor = stream_interactor;
|
this.stream_interactor = stream_interactor;
|
||||||
|
this.db = db;
|
||||||
|
|
||||||
stream_interactor.account_added.connect(on_account_added);
|
stream_interactor.account_added.connect(on_account_added);
|
||||||
}
|
}
|
||||||
|
@ -75,29 +77,50 @@ namespace Dino {
|
||||||
|
|
||||||
stream_interactor.get_module(CallStore.IDENTITY).add_call(call, conversation);
|
stream_interactor.get_module(CallStore.IDENTITY).add_call(call, conversation);
|
||||||
|
|
||||||
XmppStream? stream = stream_interactor.get_stream(conversation.account);
|
|
||||||
if (stream == null) return null;
|
|
||||||
|
|
||||||
Gee.List<Jid> call_resources = yield get_call_resources(conversation);
|
|
||||||
if (call_resources.size > 0) {
|
|
||||||
Jid full_jid = call_resources[0];
|
|
||||||
Xep.Jingle.Session session = yield stream.get_module(Xep.JingleRtp.Module.IDENTITY).start_call(stream, full_jid, video);
|
|
||||||
sessions[call] = session;
|
|
||||||
call_by_sid[call.account][session.sid] = call;
|
|
||||||
sid_by_call[call.account][call] = session.sid;
|
|
||||||
|
|
||||||
connect_session_signals(call, session);
|
|
||||||
}
|
|
||||||
|
|
||||||
we_should_send_video[call] = video;
|
we_should_send_video[call] = video;
|
||||||
we_should_send_audio[call] = true;
|
we_should_send_audio[call] = true;
|
||||||
|
|
||||||
|
if (yield has_jmi_resources(conversation)) {
|
||||||
|
XmppStream? stream = stream_interactor.get_stream(conversation.account);
|
||||||
|
jmi_call[conversation.account] = call;
|
||||||
|
jmi_video[conversation.account] = video;
|
||||||
|
jmi_sid[conversation.account] = Xmpp.random_uuid();
|
||||||
|
|
||||||
|
call_by_sid[call.account][jmi_sid[conversation.account]] = call;
|
||||||
|
|
||||||
|
var descriptions = new ArrayList<StanzaNode>();
|
||||||
|
descriptions.add(new StanzaNode.build("description", "urn:xmpp:jingle:apps:rtp:1").add_self_xmlns().put_attribute("media", "audio"));
|
||||||
|
if (video) {
|
||||||
|
descriptions.add(new StanzaNode.build("description", "urn:xmpp:jingle:apps:rtp:1").add_self_xmlns().put_attribute("media", "video"));
|
||||||
|
}
|
||||||
|
|
||||||
|
stream.get_module(Xmpp.Xep.JingleMessageInitiation.Module.IDENTITY).send_session_propose_to_peer(stream, conversation.counterpart, jmi_sid[call.account], descriptions);
|
||||||
|
} else {
|
||||||
|
Gee.List<Jid> call_resources = yield get_call_resources(conversation);
|
||||||
|
if (call_resources.size == 0) {
|
||||||
|
warning("No call resources");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
yield call_resource(conversation.account, call_resources[0], call, video);
|
||||||
|
}
|
||||||
|
|
||||||
conversation.last_active = call.time;
|
conversation.last_active = call.time;
|
||||||
call_outgoing(call, conversation);
|
call_outgoing(call, conversation);
|
||||||
|
|
||||||
return call;
|
return call;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async void call_resource(Account account, Jid full_jid, Call call, bool video, string? sid = null) {
|
||||||
|
XmppStream? stream = stream_interactor.get_stream(account);
|
||||||
|
if (stream == null) return;
|
||||||
|
|
||||||
|
Xep.Jingle.Session session = yield stream.get_module(Xep.JingleRtp.Module.IDENTITY).start_call(stream, full_jid, video, sid);
|
||||||
|
sessions[call] = session;
|
||||||
|
sid_by_call[call.account][call] = session.sid;
|
||||||
|
|
||||||
|
connect_session_signals(call, session);
|
||||||
|
}
|
||||||
|
|
||||||
public void end_call(Conversation conversation, Call call) {
|
public void end_call(Conversation conversation, Call call) {
|
||||||
XmppStream? stream = stream_interactor.get_stream(call.account);
|
XmppStream? stream = stream_interactor.get_stream(call.account);
|
||||||
if (stream == null) return;
|
if (stream == null) return;
|
||||||
|
@ -130,15 +153,17 @@ namespace Dino {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Only a JMI so far
|
// Only a JMI so far
|
||||||
XmppStream stream = stream_interactor.get_stream(call.account);
|
Account account = call.account;
|
||||||
|
string sid = sid_by_call[call.account][call];
|
||||||
|
XmppStream stream = stream_interactor.get_stream(account);
|
||||||
if (stream == null) return;
|
if (stream == null) return;
|
||||||
|
|
||||||
mi_accepted_call = call;
|
jmi_call[account] = call;
|
||||||
mi_accepted_sid = sid_by_call[call.account][call];
|
jmi_sid[account] = sid;
|
||||||
mi_accepted_video = we_should_send_video[call];
|
jmi_video[account] = we_should_send_video[call];
|
||||||
|
|
||||||
stream.get_module(Xep.JingleMessageInitiation.Module.IDENTITY).send_session_accept_to_self(stream, mi_accepted_sid);
|
stream.get_module(Xep.JingleMessageInitiation.Module.IDENTITY).send_session_accept_to_self(stream, sid);
|
||||||
stream.get_module(Xep.JingleMessageInitiation.Module.IDENTITY).send_session_proceed_to_peer(stream, call.counterpart, mi_accepted_sid);
|
stream.get_module(Xep.JingleMessageInitiation.Module.IDENTITY).send_session_proceed_to_peer(stream, call.counterpart, sid);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -211,7 +236,11 @@ namespace Dino {
|
||||||
// If video_feed == null && !mute we're trying to mute a non-existant feed. It will be muted as soon as it is created.
|
// If video_feed == null && !mute we're trying to mute a non-existant feed. It will be muted as soon as it is created.
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Gee.List<Jid> get_call_resources(Conversation conversation) {
|
public async bool can_do_calls(Conversation conversation) {
|
||||||
|
return (yield get_call_resources(conversation)).size > 0 || yield has_jmi_resources(conversation);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Gee.List<Jid> get_call_resources(Conversation conversation) {
|
||||||
ArrayList<Jid> ret = new ArrayList<Jid>();
|
ArrayList<Jid> ret = new ArrayList<Jid>();
|
||||||
|
|
||||||
XmppStream? stream = stream_interactor.get_stream(conversation.account);
|
XmppStream? stream = stream_interactor.get_stream(conversation.account);
|
||||||
|
@ -228,6 +257,15 @@ namespace Dino {
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async bool has_jmi_resources(Conversation conversation) {
|
||||||
|
int64 jmi_resources = db.entity.select()
|
||||||
|
.with(db.entity.jid_id, "=", db.get_jid_id(conversation.counterpart))
|
||||||
|
.join_with(db.entity_feature, db.entity.caps_hash, db.entity_feature.entity)
|
||||||
|
.with(db.entity_feature.feature, "=", Xep.JingleMessageInitiation.NS_URI)
|
||||||
|
.count();
|
||||||
|
return jmi_resources > 0;
|
||||||
|
}
|
||||||
|
|
||||||
public bool should_we_send_video(Call call) {
|
public bool should_we_send_video(Call call) {
|
||||||
return we_should_send_video[call];
|
return we_should_send_video[call];
|
||||||
}
|
}
|
||||||
|
@ -252,13 +290,14 @@ namespace Dino {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Session might have already been accepted via Jingle Message Initiation
|
// Session might have already been accepted via Jingle Message Initiation
|
||||||
bool already_accepted = mi_accepted_sid == session.sid && mi_accepted_call.account.equals(account) &&
|
bool already_accepted = jmi_sid.contains(account) &&
|
||||||
mi_accepted_call.counterpart.equals_bare(session.peer_full_jid) &&
|
jmi_sid[account] == session.sid && jmi_call[account].account.equals(account) &&
|
||||||
mi_accepted_video == counterpart_wants_video;
|
jmi_call[account].counterpart.equals_bare(session.peer_full_jid) &&
|
||||||
|
jmi_video[account] == counterpart_wants_video;
|
||||||
|
|
||||||
Call? call = null;
|
Call? call = null;
|
||||||
if (already_accepted) {
|
if (already_accepted) {
|
||||||
call = mi_accepted_call;
|
call = jmi_call[account];
|
||||||
} else {
|
} else {
|
||||||
call = create_received_call(account, session.peer_full_jid, account.full_jid, counterpart_wants_video);
|
call = create_received_call(account, session.peer_full_jid, account.full_jid, counterpart_wants_video);
|
||||||
}
|
}
|
||||||
|
@ -334,16 +373,15 @@ namespace Dino {
|
||||||
}
|
}
|
||||||
if (call.state == Call.State.IN_PROGRESS) {
|
if (call.state == Call.State.IN_PROGRESS) {
|
||||||
call.state = Call.State.ENDED;
|
call.state = Call.State.ENDED;
|
||||||
call_terminated(call, reason_name, reason_text);
|
|
||||||
} else if (call.state == Call.State.RINGING || call.state == Call.State.ESTABLISHING) {
|
} else if (call.state == Call.State.RINGING || call.state == Call.State.ESTABLISHING) {
|
||||||
if (reason_name == Xep.Jingle.ReasonElement.DECLINE) {
|
if (reason_name == Xep.Jingle.ReasonElement.DECLINE) {
|
||||||
call.state = Call.State.DECLINED;
|
call.state = Call.State.DECLINED;
|
||||||
} else {
|
} else {
|
||||||
call.state = Call.State.FAILED;
|
call.state = Call.State.FAILED;
|
||||||
}
|
}
|
||||||
call_terminated(call, reason_name, reason_text);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
call_terminated(call, reason_name, reason_text);
|
||||||
remove_call_from_datastructures(call);
|
remove_call_from_datastructures(call);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -478,11 +516,21 @@ namespace Dino {
|
||||||
mi_module.session_accepted.connect((from, sid) => {
|
mi_module.session_accepted.connect((from, sid) => {
|
||||||
if (!call_by_sid[account].has_key(sid)) return;
|
if (!call_by_sid[account].has_key(sid)) return;
|
||||||
|
|
||||||
// Ignore session-accepted from ourselves
|
if (from.equals_bare(account.bare_jid)) { // Carboned message from our account
|
||||||
if (!from.equals(account.full_jid)) {
|
// Ignore carbon from ourselves
|
||||||
|
if (from.equals(account.full_jid)) return;
|
||||||
|
|
||||||
Call call = call_by_sid[account][sid];
|
Call call = call_by_sid[account][sid];
|
||||||
call.state = Call.State.OTHER_DEVICE_ACCEPTED;
|
call.state = Call.State.OTHER_DEVICE_ACCEPTED;
|
||||||
remove_call_from_datastructures(call);
|
remove_call_from_datastructures(call);
|
||||||
|
} else if (from.equals_bare(call_by_sid[account][sid].counterpart)) { // Message from our peer
|
||||||
|
// We proposed the call
|
||||||
|
if (jmi_sid.has_key(account) && jmi_sid[account] == sid) {
|
||||||
|
call_resource(account, from, jmi_call[account], jmi_video[account], jmi_sid[account]);
|
||||||
|
jmi_call.unset(account);
|
||||||
|
jmi_sid.unset(account);
|
||||||
|
jmi_video.unset(account);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
mi_module.session_rejected.connect((from, to, sid) => {
|
mi_module.session_rejected.connect((from, to, sid) => {
|
||||||
|
|
|
@ -116,9 +116,9 @@ namespace Dino.Ui {
|
||||||
private async void update_visibility() {
|
private async void update_visibility() {
|
||||||
if (conversation.type_ == Conversation.Type.CHAT) {
|
if (conversation.type_ == Conversation.Type.CHAT) {
|
||||||
Conversation conv_bak = conversation;
|
Conversation conv_bak = conversation;
|
||||||
Gee.List<Jid>? resources = yield stream_interactor.get_module(Calls.IDENTITY).get_call_resources(conversation);
|
bool can_do_calls = yield stream_interactor.get_module(Calls.IDENTITY).can_do_calls(conversation);
|
||||||
if (conv_bak != conversation) return;
|
if (conv_bak != conversation) return;
|
||||||
visible = resources != null && resources.size > 0;
|
visible = can_do_calls;
|
||||||
} else {
|
} else {
|
||||||
visible = false;
|
visible = false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -249,12 +249,4 @@ public class DtlsSrtp {
|
||||||
}
|
}
|
||||||
return sb.str;
|
return sb.str;
|
||||||
}
|
}
|
||||||
|
|
||||||
private uint8[] uint8_pt_to_a(uint8* data, uint size) {
|
|
||||||
uint8[size] ret = new uint8[size];
|
|
||||||
for (int i = 0; i < size; i++) {
|
|
||||||
ret[i] = data[i];
|
|
||||||
}
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -102,7 +102,7 @@ namespace Xmpp.Xep.Jingle {
|
||||||
return (yield is_jingle_available(stream, full_jid)) && (yield select_transport(stream, type, components, full_jid, Set.empty())) != null;
|
return (yield is_jingle_available(stream, full_jid)) && (yield select_transport(stream, type, components, full_jid, Set.empty())) != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Session create_session(XmppStream stream, Gee.List<Content> contents, Jid receiver_full_jid, string sid = random_uuid()) throws Error {
|
public async Session create_session(XmppStream stream, Gee.List<Content> contents, Jid receiver_full_jid, string? sid = null) throws Error {
|
||||||
if (!yield is_jingle_available(stream, receiver_full_jid)) {
|
if (!yield is_jingle_available(stream, receiver_full_jid)) {
|
||||||
throw new Error.NO_SHARED_PROTOCOLS("No Jingle support");
|
throw new Error.NO_SHARED_PROTOCOLS("No Jingle support");
|
||||||
}
|
}
|
||||||
|
@ -111,7 +111,7 @@ namespace Xmpp.Xep.Jingle {
|
||||||
throw new Error.GENERAL("Couldn't determine own JID");
|
throw new Error.GENERAL("Couldn't determine own JID");
|
||||||
}
|
}
|
||||||
|
|
||||||
Session session = new Session.initiate_sent(stream, sid, my_jid, receiver_full_jid);
|
Session session = new Session.initiate_sent(stream, sid ?? random_uuid(), my_jid, receiver_full_jid);
|
||||||
session.terminated.connect((session, stream, _1, _2, _3) => { stream.get_flag(Flag.IDENTITY).remove_session(session.sid); });
|
session.terminated.connect((session, stream, _1, _2, _3) => { stream.get_flag(Flag.IDENTITY).remove_session(session.sid); });
|
||||||
|
|
||||||
foreach (Content content in contents) {
|
foreach (Content content in contents) {
|
||||||
|
|
|
@ -26,7 +26,7 @@ public abstract class Module : XmppStreamModule {
|
||||||
public abstract Stream create_stream(Jingle.Content content);
|
public abstract Stream create_stream(Jingle.Content content);
|
||||||
public abstract void close_stream(Stream stream);
|
public abstract void close_stream(Stream stream);
|
||||||
|
|
||||||
public async Jingle.Session start_call(XmppStream stream, Jid receiver_full_jid, bool video) throws Jingle.Error {
|
public async Jingle.Session start_call(XmppStream stream, Jid receiver_full_jid, bool video, string? sid = null) throws Jingle.Error {
|
||||||
|
|
||||||
Jingle.Module jingle_module = stream.get_module(Jingle.Module.IDENTITY);
|
Jingle.Module jingle_module = stream.get_module(Jingle.Module.IDENTITY);
|
||||||
|
|
||||||
|
@ -72,7 +72,7 @@ public abstract class Module : XmppStreamModule {
|
||||||
|
|
||||||
// Create session
|
// Create session
|
||||||
try {
|
try {
|
||||||
Jingle.Session session = yield jingle_module.create_session(stream, contents, receiver_full_jid);
|
Jingle.Session session = yield jingle_module.create_session(stream, contents, receiver_full_jid, sid);
|
||||||
return session;
|
return session;
|
||||||
} catch (Jingle.Error e) {
|
} catch (Jingle.Error e) {
|
||||||
throw new Jingle.Error.GENERAL(@"Couldn't create Jingle session: $(e.message)");
|
throw new Jingle.Error.GENERAL(@"Couldn't create Jingle session: $(e.message)");
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
using Gee;
|
using Gee;
|
||||||
|
|
||||||
namespace Xmpp.Xep.JingleMessageInitiation {
|
namespace Xmpp.Xep.JingleMessageInitiation {
|
||||||
private const string NS_URI = "urn:xmpp:jingle-message:0";
|
public const string NS_URI = "urn:xmpp:jingle-message:0";
|
||||||
|
|
||||||
public class Module : XmppStreamModule {
|
public class Module : XmppStreamModule {
|
||||||
public static ModuleIdentity<Module> IDENTITY = new ModuleIdentity<Module>(NS_URI, "0353_jingle_message_initiation");
|
public static ModuleIdentity<Module> IDENTITY = new ModuleIdentity<Module>(NS_URI, "0353_jingle_message_initiation");
|
||||||
|
@ -11,6 +11,17 @@ namespace Xmpp.Xep.JingleMessageInitiation {
|
||||||
public signal void session_accepted(Jid from, string sid);
|
public signal void session_accepted(Jid from, string sid);
|
||||||
public signal void session_rejected(Jid from, Jid to, string sid);
|
public signal void session_rejected(Jid from, Jid to, string sid);
|
||||||
|
|
||||||
|
public void send_session_propose_to_peer(XmppStream stream, Jid to, string sid, Gee.List<StanzaNode> descriptions) {
|
||||||
|
StanzaNode propose_node = new StanzaNode.build("propose", NS_URI).add_self_xmlns().put_attribute("id", sid, NS_URI);
|
||||||
|
foreach (StanzaNode desc_node in descriptions) {
|
||||||
|
propose_node.put_node(desc_node);
|
||||||
|
}
|
||||||
|
|
||||||
|
MessageStanza accepted_message = new MessageStanza() { to=to };
|
||||||
|
accepted_message.stanza.put_node(propose_node);
|
||||||
|
stream.get_module(MessageModule.IDENTITY).send_message.begin(stream, accepted_message);
|
||||||
|
}
|
||||||
|
|
||||||
public void send_session_accept_to_self(XmppStream stream, string sid) {
|
public void send_session_accept_to_self(XmppStream stream, string sid) {
|
||||||
MessageStanza accepted_message = new MessageStanza() { to=Bind.Flag.get_my_jid(stream).bare_jid };
|
MessageStanza accepted_message = new MessageStanza() { to=Bind.Flag.get_my_jid(stream).bare_jid };
|
||||||
accepted_message.stanza.put_node(
|
accepted_message.stanza.put_node(
|
||||||
|
@ -58,7 +69,6 @@ namespace Xmpp.Xep.JingleMessageInitiation {
|
||||||
switch (mi_node.name) {
|
switch (mi_node.name) {
|
||||||
case "accept":
|
case "accept":
|
||||||
case "proceed":
|
case "proceed":
|
||||||
if (!message.from.equals_bare(Bind.Flag.get_my_jid(stream))) return;
|
|
||||||
session_accepted(message.from, mi_node.get_attribute("id"));
|
session_accepted(message.from, mi_node.get_attribute("id"));
|
||||||
break;
|
break;
|
||||||
case "propose":
|
case "propose":
|
||||||
|
|
Loading…
Reference in a new issue