Add support for Jingle RTP sessions (XEP-0167) to xmpp-vala
Co-authored-by: fiaxh <git@lightrise.org>
This commit is contained in:
parent
d703b7c09d
commit
dfd7940104
|
@ -114,6 +114,7 @@ SOURCES
|
||||||
"src/module/xep/0198_stream_management.vala"
|
"src/module/xep/0198_stream_management.vala"
|
||||||
"src/module/xep/0199_ping.vala"
|
"src/module/xep/0199_ping.vala"
|
||||||
"src/module/xep/0203_delayed_delivery.vala"
|
"src/module/xep/0203_delayed_delivery.vala"
|
||||||
|
"src/module/xep/0215_external_service_discovery.vala"
|
||||||
"src/module/xep/0234_jingle_file_transfer.vala"
|
"src/module/xep/0234_jingle_file_transfer.vala"
|
||||||
"src/module/xep/0249_direct_muc_invitations.vala"
|
"src/module/xep/0249_direct_muc_invitations.vala"
|
||||||
"src/module/xep/0260_jingle_socks5_bytestreams.vala"
|
"src/module/xep/0260_jingle_socks5_bytestreams.vala"
|
||||||
|
@ -123,6 +124,7 @@ SOURCES
|
||||||
"src/module/xep/0313_message_archive_management.vala"
|
"src/module/xep/0313_message_archive_management.vala"
|
||||||
"src/module/xep/0333_chat_markers.vala"
|
"src/module/xep/0333_chat_markers.vala"
|
||||||
"src/module/xep/0334_message_processing_hints.vala"
|
"src/module/xep/0334_message_processing_hints.vala"
|
||||||
|
"src/module/xep/0353_jingle_message_initiation.vala"
|
||||||
"src/module/xep/0359_unique_stable_stanza_ids.vala"
|
"src/module/xep/0359_unique_stable_stanza_ids.vala"
|
||||||
"src/module/xep/0363_http_file_upload.vala"
|
"src/module/xep/0363_http_file_upload.vala"
|
||||||
"src/module/xep/0380_explicit_encryption.vala"
|
"src/module/xep/0380_explicit_encryption.vala"
|
||||||
|
|
142
xmpp-vala/src/module/xep/0167_jingle_rtp/content_parameters.vala
Normal file
142
xmpp-vala/src/module/xep/0167_jingle_rtp/content_parameters.vala
Normal file
|
@ -0,0 +1,142 @@
|
||||||
|
using Gee;
|
||||||
|
using Xmpp;
|
||||||
|
using Xmpp.Xep;
|
||||||
|
|
||||||
|
public class Xmpp.Xep.JingleRtp.Parameters : Jingle.ContentParameters, Object {
|
||||||
|
|
||||||
|
public signal void stream_created(Stream stream);
|
||||||
|
public signal void connection_ready();
|
||||||
|
|
||||||
|
public string media { get; private set; }
|
||||||
|
public string? ssrc { get; private set; }
|
||||||
|
public bool rtcp_mux { get; private set; }
|
||||||
|
|
||||||
|
public string? bandwidth { get; private set; }
|
||||||
|
public string? bandwidth_type { get; private set; }
|
||||||
|
|
||||||
|
public bool encryption_required { get; private set; default = false; }
|
||||||
|
public PayloadType? agreed_payload_type { get; private set; }
|
||||||
|
public Gee.List<PayloadType> payload_types = new ArrayList<PayloadType>(PayloadType.equals_func);
|
||||||
|
public Gee.List<Crypto> cryptos = new ArrayList<Crypto>();
|
||||||
|
|
||||||
|
public weak Stream? stream { get; private set; }
|
||||||
|
|
||||||
|
private Module parent;
|
||||||
|
|
||||||
|
public Parameters(Module parent,
|
||||||
|
string media, Gee.List<PayloadType> payload_types,
|
||||||
|
string? ssrc = null, bool rtcp_mux = false,
|
||||||
|
string? bandwidth = null, string? bandwidth_type = null,
|
||||||
|
bool encryption_required = false, Gee.List<Crypto> cryptos = new ArrayList<Crypto>()
|
||||||
|
) {
|
||||||
|
this.parent = parent;
|
||||||
|
this.media = media;
|
||||||
|
this.ssrc = ssrc;
|
||||||
|
this.rtcp_mux = rtcp_mux;
|
||||||
|
this.bandwidth = bandwidth;
|
||||||
|
this.bandwidth_type = bandwidth_type;
|
||||||
|
this.encryption_required = encryption_required;
|
||||||
|
this.payload_types = payload_types;
|
||||||
|
this.cryptos = cryptos;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Parameters.from_node(Module parent, StanzaNode node) throws Jingle.IqError {
|
||||||
|
this.parent = parent;
|
||||||
|
this.media = node.get_attribute("media");
|
||||||
|
this.ssrc = node.get_attribute("ssrc");
|
||||||
|
this.rtcp_mux = node.get_subnode("rtcp-mux") != null;
|
||||||
|
StanzaNode? encryption = node.get_subnode("encryption");
|
||||||
|
if (encryption != null) {
|
||||||
|
this.encryption_required = encryption.get_attribute_bool("required", this.encryption_required);
|
||||||
|
foreach (StanzaNode crypto in encryption.get_subnodes("crypto")) {
|
||||||
|
this.cryptos.add(Crypto.parse(crypto));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
foreach (StanzaNode payloadType in node.get_subnodes("payload-type")) {
|
||||||
|
this.payload_types.add(PayloadType.parse(payloadType));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async void handle_proposed_content(XmppStream stream, Jingle.Session session, Jingle.Content content) {
|
||||||
|
agreed_payload_type = yield parent.pick_payload_type(media, payload_types);
|
||||||
|
if (agreed_payload_type == null) {
|
||||||
|
debug("no usable payload type");
|
||||||
|
content.reject();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void accept(XmppStream stream, Jingle.Session session, Jingle.Content content) {
|
||||||
|
debug("[%p] Jingle RTP on_accept", stream);
|
||||||
|
|
||||||
|
Jingle.DatagramConnection rtp_datagram = (Jingle.DatagramConnection) content.get_transport_connection(1);
|
||||||
|
Jingle.DatagramConnection rtcp_datagram = (Jingle.DatagramConnection) content.get_transport_connection(2);
|
||||||
|
|
||||||
|
ulong rtcp_ready_handler_id = 0;
|
||||||
|
rtcp_ready_handler_id = rtcp_datagram.notify["ready"].connect(() => {
|
||||||
|
this.stream.on_rtcp_ready();
|
||||||
|
|
||||||
|
rtcp_datagram.disconnect(rtcp_ready_handler_id);
|
||||||
|
rtcp_ready_handler_id = 0;
|
||||||
|
});
|
||||||
|
|
||||||
|
ulong rtp_ready_handler_id = 0;
|
||||||
|
rtp_ready_handler_id = rtp_datagram.notify["ready"].connect(() => {
|
||||||
|
this.stream.on_rtp_ready();
|
||||||
|
connection_ready();
|
||||||
|
|
||||||
|
rtp_datagram.disconnect(rtp_ready_handler_id);
|
||||||
|
rtp_ready_handler_id = 0;
|
||||||
|
});
|
||||||
|
|
||||||
|
session.notify["state"].connect((obj, _) => {
|
||||||
|
Jingle.Session session2 = (Jingle.Session) obj;
|
||||||
|
if (session2.state == Jingle.Session.State.ENDED) {
|
||||||
|
if (rtcp_ready_handler_id != 0) rtcp_datagram.disconnect(rtcp_ready_handler_id);
|
||||||
|
if (rtp_ready_handler_id != 0) rtp_datagram.disconnect(rtp_ready_handler_id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.stream = parent.create_stream(content);
|
||||||
|
rtp_datagram.datagram_received.connect(this.stream.on_recv_rtp_data);
|
||||||
|
rtcp_datagram.datagram_received.connect(this.stream.on_recv_rtcp_data);
|
||||||
|
this.stream.on_send_rtp_data.connect(rtp_datagram.send_datagram);
|
||||||
|
this.stream.on_send_rtcp_data.connect(rtcp_datagram.send_datagram);
|
||||||
|
this.stream_created(this.stream);
|
||||||
|
this.stream.create();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void handle_accept(XmppStream stream, Jingle.Session session, Jingle.Content content, StanzaNode description_node) {
|
||||||
|
Gee.List<StanzaNode> payload_type_nodes = description_node.get_subnodes("payload-type");
|
||||||
|
if (payload_type_nodes.size == 0) {
|
||||||
|
warning("Counterpart didn't include any payload types");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
PayloadType preferred_payload_type = PayloadType.parse(payload_type_nodes[0]);
|
||||||
|
if (!payload_types.contains(preferred_payload_type)) {
|
||||||
|
warning("Counterpart's preferred content type doesn't match any of our sent ones");
|
||||||
|
}
|
||||||
|
agreed_payload_type = preferred_payload_type;
|
||||||
|
|
||||||
|
accept(stream, session, content);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void terminate(bool we_terminated, string? reason_name, string? reason_text) {
|
||||||
|
if (stream != null) parent.close_stream(stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
public StanzaNode get_description_node() {
|
||||||
|
StanzaNode ret = new StanzaNode.build("description", NS_URI)
|
||||||
|
.add_self_xmlns()
|
||||||
|
.put_attribute("media", media);
|
||||||
|
|
||||||
|
if (agreed_payload_type != null) {
|
||||||
|
ret.put_node(agreed_payload_type.to_xml());
|
||||||
|
} else {
|
||||||
|
foreach (PayloadType payload_type in payload_types) {
|
||||||
|
ret.put_node(payload_type.to_xml());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
23
xmpp-vala/src/module/xep/0167_jingle_rtp/content_type.vala
Normal file
23
xmpp-vala/src/module/xep/0167_jingle_rtp/content_type.vala
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
using Gee;
|
||||||
|
using Xmpp;
|
||||||
|
using Xmpp.Xep;
|
||||||
|
|
||||||
|
public class Xmpp.Xep.JingleRtp.ContentType : Jingle.ContentType, Object {
|
||||||
|
public string ns_uri { get { return NS_URI; } }
|
||||||
|
public Jingle.TransportType required_transport_type { get { return Jingle.TransportType.DATAGRAM; } }
|
||||||
|
public uint8 required_components { get { return 2; /* RTP + RTCP */ } }
|
||||||
|
|
||||||
|
private Module module;
|
||||||
|
|
||||||
|
public ContentType(Module module) {
|
||||||
|
this.module = module;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Jingle.ContentParameters parse_content_parameters(StanzaNode description) throws Jingle.IqError {
|
||||||
|
return new Parameters.from_node(module, description);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Jingle.ContentParameters create_content_parameters(Object object) throws Jingle.IqError {
|
||||||
|
assert_not_reached();
|
||||||
|
}
|
||||||
|
}
|
175
xmpp-vala/src/module/xep/0167_jingle_rtp/jingle_rtp_module.vala
Normal file
175
xmpp-vala/src/module/xep/0167_jingle_rtp/jingle_rtp_module.vala
Normal file
|
@ -0,0 +1,175 @@
|
||||||
|
using Gee;
|
||||||
|
using Xmpp;
|
||||||
|
using Xmpp.Xep;
|
||||||
|
|
||||||
|
namespace Xmpp.Xep.JingleRtp {
|
||||||
|
|
||||||
|
public const string NS_URI = "urn:xmpp:jingle:apps:rtp:1";
|
||||||
|
public const string NS_URI_AUDIO = "urn:xmpp:jingle:apps:rtp:audio";
|
||||||
|
public const string NS_URI_VIDEO = "urn:xmpp:jingle:apps:rtp:video";
|
||||||
|
|
||||||
|
public abstract class Module : XmppStreamModule {
|
||||||
|
public static Xmpp.ModuleIdentity<Module> IDENTITY = new Xmpp.ModuleIdentity<Module>(NS_URI, "0167_jingle_rtp");
|
||||||
|
|
||||||
|
private ContentType content_type;
|
||||||
|
public SessionInfoType session_info_type = new SessionInfoType();
|
||||||
|
|
||||||
|
protected Module() {
|
||||||
|
content_type = new ContentType(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract async Gee.List<PayloadType> get_supported_payloads(string media);
|
||||||
|
public abstract async PayloadType? pick_payload_type(string media, Gee.List<PayloadType> payloads);
|
||||||
|
public abstract Stream create_stream(Jingle.Content content);
|
||||||
|
public abstract void close_stream(Stream stream);
|
||||||
|
|
||||||
|
public async Jingle.Session start_call(XmppStream stream, Jid receiver_full_jid, bool video) throws Jingle.Error {
|
||||||
|
|
||||||
|
Jingle.Module jingle_module = stream.get_module(Jingle.Module.IDENTITY);
|
||||||
|
|
||||||
|
Jid? my_jid = stream.get_flag(Bind.Flag.IDENTITY).my_jid;
|
||||||
|
if (my_jid == null) {
|
||||||
|
throw new Jingle.Error.GENERAL("Couldn't determine own JID");
|
||||||
|
}
|
||||||
|
|
||||||
|
ArrayList<Jingle.Content> contents = new ArrayList<Jingle.Content>();
|
||||||
|
|
||||||
|
// Create audio content
|
||||||
|
Parameters audio_content_parameters = new Parameters(this, "audio", yield get_supported_payloads("audio"));
|
||||||
|
Jingle.Transport? audio_transport = yield jingle_module.select_transport(stream, content_type.required_transport_type, content_type.required_components, receiver_full_jid, Set.empty());
|
||||||
|
if (audio_transport == null) {
|
||||||
|
throw new Jingle.Error.NO_SHARED_PROTOCOLS("No suitable audio transports");
|
||||||
|
}
|
||||||
|
Jingle.TransportParameters audio_transport_params = audio_transport.create_transport_parameters(stream, content_type.required_components, my_jid, receiver_full_jid);
|
||||||
|
Jingle.Content audio_content = new Jingle.Content.initiate_sent("voice", Jingle.Senders.BOTH,
|
||||||
|
content_type, audio_content_parameters,
|
||||||
|
audio_transport, audio_transport_params,
|
||||||
|
null, null,
|
||||||
|
my_jid, receiver_full_jid);
|
||||||
|
contents.add(audio_content);
|
||||||
|
|
||||||
|
Jingle.Content? video_content = null;
|
||||||
|
if (video) {
|
||||||
|
// Create video content
|
||||||
|
Parameters video_content_parameters = new Parameters(this, "video", yield get_supported_payloads("video"));
|
||||||
|
Jingle.Transport? video_transport = yield stream.get_module(Jingle.Module.IDENTITY).select_transport(stream, content_type.required_transport_type, content_type.required_components, receiver_full_jid, Set.empty());
|
||||||
|
if (video_transport == null) {
|
||||||
|
throw new Jingle.Error.NO_SHARED_PROTOCOLS("No suitable video transports");
|
||||||
|
}
|
||||||
|
Jingle.TransportParameters video_transport_params = video_transport.create_transport_parameters(stream, content_type.required_components, my_jid, receiver_full_jid);
|
||||||
|
video_content = new Jingle.Content.initiate_sent("webcam", Jingle.Senders.BOTH,
|
||||||
|
content_type, video_content_parameters,
|
||||||
|
video_transport, video_transport_params,
|
||||||
|
null, null,
|
||||||
|
my_jid, receiver_full_jid);
|
||||||
|
contents.add(video_content);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create session
|
||||||
|
try {
|
||||||
|
Jingle.Session session = yield jingle_module.create_session(stream, contents, receiver_full_jid);
|
||||||
|
return session;
|
||||||
|
} catch (Jingle.Error e) {
|
||||||
|
throw new Jingle.Error.GENERAL(@"Couldn't create Jingle session: $(e.message)");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Jingle.Content add_outgoing_video_content(XmppStream stream, Jingle.Session session) {
|
||||||
|
Jid my_jid = session.local_full_jid;
|
||||||
|
Jid receiver_full_jid = session.peer_full_jid;
|
||||||
|
|
||||||
|
Jingle.Content? content = null;
|
||||||
|
foreach (Jingle.Content c in session.contents.values) {
|
||||||
|
Parameters? parameters = c.content_params as Parameters;
|
||||||
|
if (parameters == null) continue;
|
||||||
|
|
||||||
|
if (parameters.media == "video") {
|
||||||
|
content = c;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (content == null) {
|
||||||
|
// Content for video does not yet exist -> create it
|
||||||
|
Parameters video_content_parameters = new Parameters(this, "video", yield get_supported_payloads("video"));
|
||||||
|
Jingle.Transport? video_transport = yield stream.get_module(Jingle.Module.IDENTITY).select_transport(stream, content_type.required_transport_type, content_type.required_components, receiver_full_jid, Set.empty());
|
||||||
|
if (video_transport == null) {
|
||||||
|
throw new Jingle.Error.NO_SHARED_PROTOCOLS("No suitable video transports");
|
||||||
|
}
|
||||||
|
Jingle.TransportParameters video_transport_params = video_transport.create_transport_parameters(stream, content_type.required_components, my_jid, receiver_full_jid);
|
||||||
|
content = new Jingle.Content.initiate_sent("webcam",
|
||||||
|
session.we_initiated ? Jingle.Senders.INITIATOR : Jingle.Senders.RESPONDER,
|
||||||
|
content_type, video_content_parameters,
|
||||||
|
video_transport, video_transport_params,
|
||||||
|
null, null,
|
||||||
|
my_jid, receiver_full_jid);
|
||||||
|
|
||||||
|
session.add_content.begin(content);
|
||||||
|
} else {
|
||||||
|
// Content for video already exists -> modify senders
|
||||||
|
bool we_initiated = session.we_initiated;
|
||||||
|
Jingle.Senders want_sender = we_initiated ? Jingle.Senders.INITIATOR : Jingle.Senders.RESPONDER;
|
||||||
|
if (content.senders == Jingle.Senders.BOTH || content.senders == want_sender) {
|
||||||
|
warning("want to add video but senders is already both/target");
|
||||||
|
} else if (content.senders == Jingle.Senders.NONE) {
|
||||||
|
content.modify(want_sender);
|
||||||
|
} else {
|
||||||
|
content.modify(Jingle.Senders.BOTH);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void attach(XmppStream stream) {
|
||||||
|
stream.get_module(ServiceDiscovery.Module.IDENTITY).add_feature(stream, NS_URI);
|
||||||
|
stream.get_module(ServiceDiscovery.Module.IDENTITY).add_feature(stream, NS_URI_AUDIO);
|
||||||
|
stream.get_module(ServiceDiscovery.Module.IDENTITY).add_feature(stream, NS_URI_VIDEO);
|
||||||
|
stream.get_module(Jingle.Module.IDENTITY).register_content_type(content_type);
|
||||||
|
stream.get_module(Jingle.Module.IDENTITY).register_session_info_type(session_info_type);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void detach(XmppStream stream) {
|
||||||
|
stream.get_module(ServiceDiscovery.Module.IDENTITY).remove_feature(stream, NS_URI);
|
||||||
|
stream.get_module(ServiceDiscovery.Module.IDENTITY).remove_feature(stream, NS_URI_AUDIO);
|
||||||
|
stream.get_module(ServiceDiscovery.Module.IDENTITY).remove_feature(stream, NS_URI_VIDEO);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async bool is_available(XmppStream stream, Jid full_jid) {
|
||||||
|
bool? has_feature = yield stream.get_module(ServiceDiscovery.Module.IDENTITY).has_entity_feature(stream, full_jid, NS_URI);
|
||||||
|
if (has_feature == null || !(!)has_feature) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return yield stream.get_module(Jingle.Module.IDENTITY).is_available(stream, content_type.required_transport_type, content_type.required_components, full_jid);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string get_ns() { return NS_URI; }
|
||||||
|
public override string get_id() { return IDENTITY.id; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Crypto {
|
||||||
|
public string cryptoSuite { get; private set; }
|
||||||
|
public string keyParams { get; private set; }
|
||||||
|
public string? sessionParams { get; private set; }
|
||||||
|
public string? tag { get; private set; }
|
||||||
|
|
||||||
|
public static Crypto parse(StanzaNode node) {
|
||||||
|
Crypto crypto = new Crypto();
|
||||||
|
crypto.cryptoSuite = node.get_attribute("crypto-suite");
|
||||||
|
crypto.keyParams = node.get_attribute("key-params");
|
||||||
|
crypto.sessionParams = node.get_attribute("session-params");
|
||||||
|
crypto.tag = node.get_attribute("tag");
|
||||||
|
return crypto;
|
||||||
|
}
|
||||||
|
|
||||||
|
public StanzaNode to_xml() {
|
||||||
|
StanzaNode node = new StanzaNode.build("crypto", NS_URI)
|
||||||
|
.put_attribute("crypto-suite", cryptoSuite)
|
||||||
|
.put_attribute("key-params", keyParams);
|
||||||
|
if (sessionParams != null) node.put_attribute("session-params", sessionParams);
|
||||||
|
if (tag != null) node.put_attribute("tag", tag);
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
52
xmpp-vala/src/module/xep/0167_jingle_rtp/payload_type.vala
Normal file
52
xmpp-vala/src/module/xep/0167_jingle_rtp/payload_type.vala
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
using Gee;
|
||||||
|
using Xmpp;
|
||||||
|
using Xmpp.Xep;
|
||||||
|
|
||||||
|
public class Xmpp.Xep.JingleRtp.PayloadType {
|
||||||
|
public uint8 id { get; set; }
|
||||||
|
public string? name { get; set; }
|
||||||
|
public uint8 channels { get; set; default = 1; }
|
||||||
|
public uint32 clockrate { get; set; }
|
||||||
|
public uint32 maxptime { get; set; }
|
||||||
|
public uint32 ptime { get; set; }
|
||||||
|
public Map<string, string> parameters = new HashMap<string, string>();
|
||||||
|
|
||||||
|
public static PayloadType parse(StanzaNode node) {
|
||||||
|
PayloadType payloadType = new PayloadType();
|
||||||
|
payloadType.channels = (uint8) node.get_attribute_uint("channels", payloadType.channels);
|
||||||
|
payloadType.clockrate = node.get_attribute_uint("clockrate");
|
||||||
|
payloadType.id = (uint8) node.get_attribute_uint("id");
|
||||||
|
payloadType.maxptime = node.get_attribute_uint("maxptime");
|
||||||
|
payloadType.name = node.get_attribute("name");
|
||||||
|
payloadType.ptime = node.get_attribute_uint("ptime");
|
||||||
|
foreach (StanzaNode parameter in node.get_subnodes("parameter")) {
|
||||||
|
payloadType.parameters[parameter.get_attribute("name")] = parameter.get_attribute("value");
|
||||||
|
}
|
||||||
|
return payloadType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public StanzaNode to_xml() {
|
||||||
|
StanzaNode node = new StanzaNode.build("payload-type", NS_URI)
|
||||||
|
.put_attribute("id", id.to_string());
|
||||||
|
if (channels != 1) node.put_attribute("channels", channels.to_string());
|
||||||
|
if (clockrate != 0) node.put_attribute("clockrate", clockrate.to_string());
|
||||||
|
if (maxptime != 0) node.put_attribute("maxptime", maxptime.to_string());
|
||||||
|
if (name != null) node.put_attribute("name", name);
|
||||||
|
if (ptime != 0) node.put_attribute("ptime", ptime.to_string());
|
||||||
|
foreach (string parameter in parameters.keys) {
|
||||||
|
node.put_node(new StanzaNode.build("parameter", NS_URI)
|
||||||
|
.put_attribute("name", parameter)
|
||||||
|
.put_attribute("value", parameters[parameter]));
|
||||||
|
}
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool equals_func(PayloadType a, PayloadType b) {
|
||||||
|
return a.id == b.id &&
|
||||||
|
a.name == b.name &&
|
||||||
|
a.channels == b.channels &&
|
||||||
|
a.clockrate == b.clockrate &&
|
||||||
|
a.maxptime == b.maxptime &&
|
||||||
|
a.ptime == b.ptime;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,67 @@
|
||||||
|
using Gee;
|
||||||
|
using Xmpp;
|
||||||
|
using Xmpp.Xep;
|
||||||
|
|
||||||
|
namespace Xmpp.Xep.JingleRtp {
|
||||||
|
|
||||||
|
public enum CallSessionInfo {
|
||||||
|
ACTIVE,
|
||||||
|
HOLD,
|
||||||
|
UNHOLD,
|
||||||
|
MUTE,
|
||||||
|
UNMUTE,
|
||||||
|
RINGING
|
||||||
|
}
|
||||||
|
|
||||||
|
public class SessionInfoType : Jingle.SessionInfoNs, Object {
|
||||||
|
public const string NS_URI = "urn:xmpp:jingle:apps:rtp:info:1";
|
||||||
|
public string ns_uri { get { return NS_URI; } }
|
||||||
|
|
||||||
|
public signal void info_received(Jingle.Session session, CallSessionInfo info);
|
||||||
|
public signal void mute_update_received(Jingle.Session session, bool mute, string name);
|
||||||
|
|
||||||
|
public void handle_content_session_info(XmppStream stream, Jingle.Session session, StanzaNode info, Iq.Stanza iq) throws Jingle.IqError {
|
||||||
|
switch (info.name) {
|
||||||
|
case "active":
|
||||||
|
info_received(session, CallSessionInfo.ACTIVE);
|
||||||
|
break;
|
||||||
|
case "hold":
|
||||||
|
info_received(session, CallSessionInfo.HOLD);
|
||||||
|
break;
|
||||||
|
case "unhold":
|
||||||
|
info_received(session, CallSessionInfo.UNHOLD);
|
||||||
|
break;
|
||||||
|
case "mute":
|
||||||
|
string? name = info.get_attribute("name");
|
||||||
|
mute_update_received(session, true, name);
|
||||||
|
info_received(session, CallSessionInfo.MUTE);
|
||||||
|
break;
|
||||||
|
case "unmute":
|
||||||
|
string? name = info.get_attribute("name");
|
||||||
|
mute_update_received(session, false, name);
|
||||||
|
info_received(session, CallSessionInfo.UNMUTE);
|
||||||
|
break;
|
||||||
|
case "ringing":
|
||||||
|
info_received(session, CallSessionInfo.RINGING);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void send_mute(Jingle.Session session, bool mute, string media) {
|
||||||
|
string node_name = mute ? "mute" : "unmute";
|
||||||
|
|
||||||
|
foreach (Jingle.Content content in session.contents.values) {
|
||||||
|
Parameters? parameters = content.content_params as Parameters;
|
||||||
|
if (parameters != null && parameters.media == media) {
|
||||||
|
StanzaNode session_info_content = new StanzaNode.build(node_name, NS_URI).add_self_xmlns().put_attribute("name", content.content_name);
|
||||||
|
session.send_session_info(session_info_content);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void send_ringing(Jingle.Session session) {
|
||||||
|
StanzaNode session_info_content = new StanzaNode.build("ringing", NS_URI).add_self_xmlns();
|
||||||
|
session.send_session_info(session_info_content);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
46
xmpp-vala/src/module/xep/0167_jingle_rtp/stream.vala
Normal file
46
xmpp-vala/src/module/xep/0167_jingle_rtp/stream.vala
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
public abstract class Xmpp.Xep.JingleRtp.Stream : Object {
|
||||||
|
public Jingle.Content content { get; protected set; }
|
||||||
|
public string name { get {
|
||||||
|
return content.content_name;
|
||||||
|
}}
|
||||||
|
public string? media { get {
|
||||||
|
var content_params = content.content_params;
|
||||||
|
if (content_params is Parameters) {
|
||||||
|
return ((Parameters)content_params).media;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}}
|
||||||
|
public JingleRtp.PayloadType? payload_type { get {
|
||||||
|
var content_params = content.content_params;
|
||||||
|
if (content_params is Parameters) {
|
||||||
|
return ((Parameters)content_params).agreed_payload_type;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}}
|
||||||
|
public bool sending { get {
|
||||||
|
return content.session.senders_include_us(content.senders);
|
||||||
|
}}
|
||||||
|
public bool receiving { get {
|
||||||
|
return content.session.senders_include_counterpart(content.senders);
|
||||||
|
}}
|
||||||
|
|
||||||
|
protected Stream(Jingle.Content content) {
|
||||||
|
this.content = content;
|
||||||
|
}
|
||||||
|
|
||||||
|
public signal void on_send_rtp_data(Bytes bytes);
|
||||||
|
public signal void on_send_rtcp_data(Bytes bytes);
|
||||||
|
|
||||||
|
public abstract void on_recv_rtp_data(Bytes bytes);
|
||||||
|
public abstract void on_recv_rtcp_data(Bytes bytes);
|
||||||
|
|
||||||
|
public abstract void on_rtp_ready();
|
||||||
|
public abstract void on_rtcp_ready();
|
||||||
|
|
||||||
|
public abstract void create();
|
||||||
|
public abstract void destroy();
|
||||||
|
|
||||||
|
public string to_string() {
|
||||||
|
return @"$name/$media stream in $(content.session.sid)";
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue