git » sdk » commit 8e17104

Option to add media to an ongoing call

author Stephen Paul Weber
2023-10-05 04:22:36 UTC
committer Stephen Paul Weber
2023-10-05 04:22:36 UTC
parent cbd0f383d8e16615d50b8d98f4737944d9f4a0c0

Option to add media to an ongoing call

xmpp/Chat.hx +6 -0
xmpp/Client.hx +4 -0
xmpp/jingle/Session.hx +46 -2

diff --git a/xmpp/Chat.hx b/xmpp/Chat.hx
index 25b5943..123a8cd 100644
--- a/xmpp/Chat.hx
+++ b/xmpp/Chat.hx
@@ -7,6 +7,7 @@ import xmpp.Color;
 import xmpp.GenericStream;
 import xmpp.ID;
 import xmpp.MessageSync;
+import xmpp.jingle.PeerConnection;
 import xmpp.jingle.Session;
 import xmpp.queries.MAMQuery;
 using Lambda;
@@ -87,6 +88,11 @@ abstract class Chat {
 		session.propose(audio, video);
 	}
 
+	public function addMedia(streams: Array<MediaStream>) {
+		if (callStatus() != "ongoing") throw "cannot add media when no call ongoing";
+		jingleSessions.iterator().next().addMedia(streams);
+	}
+
 	public function acceptCall() {
 		for (session in jingleSessions) {
 			session.accept();
diff --git a/xmpp/Client.hx b/xmpp/Client.hx
index 39ea31c..69e4119 100644
--- a/xmpp/Client.hx
+++ b/xmpp/Client.hx
@@ -210,6 +210,10 @@ class Client extends xmpp.EventEmitter {
 					session.contentAdd(stanza);
 				}
 
+				if (session != null && jingle.attr.get("action") == "content-accept") {
+					session.contentAccept(stanza);
+				}
+
 				if (session != null && jingle.attr.get("action") == "transport-info") {
 					session.transportInfo(stanza);
 				}
diff --git a/xmpp/jingle/Session.hx b/xmpp/jingle/Session.hx
index a01f667..3eba09a 100644
--- a/xmpp/jingle/Session.hx
+++ b/xmpp/jingle/Session.hx
@@ -13,7 +13,9 @@ interface Session {
 	public function retract(): Void;
 	public function terminate(): Void;
 	public function contentAdd(stanza: Stanza): Void;
+	public function contentAccept(stanza: Stanza): Void;
 	public function transportInfo(stanza: Stanza): Promise<Void>;
+	public function addMedia(streams: Array<MediaStream>): Void;
 	public function callStatus():String;
 	public function videoTracks():Array<MediaStreamTrack>;
 }
@@ -55,6 +57,10 @@ class IncomingProposedSession implements Session {
 		trace("Got content-add before session-initiate: " + sid, this);
 	}
 
+	public function contentAccept(_) {
+		trace("Got content-accept before session-initiate: " + sid, this);
+	}
+
 	public function transportInfo(_) {
 		trace("Got transport-info before session-initiate: " + sid, this);
 		return Promise.resolve(null);
@@ -80,6 +86,10 @@ class IncomingProposedSession implements Session {
 		return session;
 	}
 
+	public function addMedia(_) {
+		throw "Cannot add media before call starts";
+	}
+
 	public function callStatus() {
 		return "incoming";
 	}
@@ -148,6 +158,10 @@ class OutgoingProposedSession implements Session {
 		trace("Got content-add before session-initiate: " + sid, this);
 	}
 
+	public function contentAccept(_) {
+		trace("Got content-accept before session-initiate: " + sid, this);
+	}
+
 	public function transportInfo(_) {
 		trace("Got transport-info before session-initiate: " + sid, this);
 		return Promise.resolve(null);
@@ -167,6 +181,10 @@ class OutgoingProposedSession implements Session {
 		return session;
 	}
 
+	public function addMedia(_) {
+		throw "Cannot add media before call starts";
+	}
+
 	public function callStatus() {
 		return "outgoing";
 	}
@@ -276,6 +294,7 @@ class InitiatedSession implements Session {
 			m.attributes.push(new Attribute("setup", peerDtlsSetup));
 		}
 		remoteDescription = remoteDescription.addContent(addThis);
+		// TODO: tie-break with any in-flight content-add we sent?
 		pc.setRemoteDescription({ type: SdpType.OFFER, sdp: remoteDescription.toSdp() }).then((_) -> {
 			afterMedia = () -> {
 				setupLocalDescription("content-accept", addThis.media.map((m) -> m.mid));
@@ -285,6 +304,18 @@ class InitiatedSession implements Session {
 		});
 	}
 
+	public function contentAccept(stanza: Stanza) {
+		if (remoteDescription == null) throw "Got content-accept before session-accept";
+		// TODO: check if matches a content-add we sent?
+
+		final addThis = SessionDescription.fromStanza(stanza, !initiator, remoteDescription);
+		for (m in addThis.media) {
+			m.attributes.push(new Attribute("setup", peerDtlsSetup));
+		}
+		remoteDescription = remoteDescription.addContent(addThis);
+		pc.setRemoteDescription({ type: SdpType.ANSWER, sdp: remoteDescription.toSdp() });
+	}
+
 	public function transportInfo(stanza: Stanza) {
 		if (pc == null) {
 			queuedInboundTransportInfo.push(stanza);
@@ -302,6 +333,19 @@ class InitiatedSession implements Session {
 		})).then((_) -> {});
 	}
 
+	public function addMedia(streams: Array<MediaStream>) {
+		if (pc == null) throw "tried to add media before PeerConnection exists";
+
+		final oldMids = localDescription.media.map((m) -> m.mid);
+		for (stream in streams) {
+			for (track in stream.getTracks()) {
+				pc.addTrack(track, stream);
+			}
+		}
+
+		setupLocalDescription("content-add", oldMids, true);
+	}
+
 	public function callStatus() {
 		return "ongoing";
 	}
@@ -380,7 +424,7 @@ class InitiatedSession implements Session {
 		});
 	}
 
-	private function setupLocalDescription(type: String, ?filterMedia: Array<String>) {
+	private function setupLocalDescription(type: String, ?filterMedia: Array<String>, ?filterOut: Bool = false) {
 		return pc.setLocalDescription(null).then((_) -> {
 			localDescription = SessionDescription.parse(pc.localDescription.sdp);
 			var descriptionToSend = localDescription;
@@ -388,7 +432,7 @@ class InitiatedSession implements Session {
 				descriptionToSend = new SessionDescription(
 					descriptionToSend.version,
 					descriptionToSend.name,
-					descriptionToSend.media.filter((m) -> filterMedia.contains(m.mid)),
+					descriptionToSend.media.filter((m) -> filterOut ? !filterMedia.contains(m.mid) : filterMedia.contains(m.mid)),
 					descriptionToSend.attributes,
 					descriptionToSend.identificationTags
 				);