git » sdk » commit c165645

Call API refinements

author Stephen Paul Weber
2025-10-29 02:21:46 UTC
committer Stephen Paul Weber
2025-10-29 02:23:59 UTC
parent a50c707984da049d1a65deac6825e1eae4e26f9e

Call API refinements

Use the Session object instead of just IDs in more places. Call the
retract event any time a call ends for any reason. Provide an accessor
for the list of unmuted audio tracks.

borogove/Chat.hx +1 -0
borogove/Client.hx +4 -4
borogove/calls/Session.hx +55 -20

diff --git a/borogove/Chat.hx b/borogove/Chat.hx
index a0e6ddd..3781554 100644
--- a/borogove/Chat.hx
+++ b/borogove/Chat.hx
@@ -618,6 +618,7 @@ abstract class Chat {
 		final session = new OutgoingProposedSession(client, JID.parse(chatId));
 		jingleSessions.set(session.sid, session);
 		session.propose(audio, video);
+		return session;
 	}
 
 	@HaxeCBridge.noemit
diff --git a/borogove/Client.hx b/borogove/Client.hx
index 33355ec..777abcf 100644
--- a/borogove/Client.hx
+++ b/borogove/Client.hx
@@ -1278,9 +1278,9 @@ class Client extends EventEmitter {
 		@param handler takes two arguments, the associated Chat ID and Session ID
 		@returns token for use with removeEventListener
 	**/
-	public function addCallRingingListener(handler:(String,String)->Void) {
+	public function addCallRingingListener(handler:(Session)->Void) {
 		return this.on("call/ringing", (data) -> {
-			handler(data.chatId, data.sid);
+			handler(data);
 			return EventHandled;
 		});
 	}
@@ -1320,9 +1320,9 @@ class Client extends EventEmitter {
 		       the new MediaStreamTrack, and an array of any associated MediaStreams
 		@returns token for use with removeEventListener
 	**/
-	public function addCallTrackListener(handler:(String,MediaStreamTrack,Array<MediaStream>)->Void) {
+	public function addCallTrackListener(handler:(InitiatedSession,MediaStreamTrack,Array<MediaStream>)->Void) {
 		return this.on("call/track", (data) -> {
-			handler(data.chatId, data.track, data.streams);
+			handler(data.session, data.track, data.streams);
 			return EventHandled;
 		});
 	}
diff --git a/borogove/calls/Session.hx b/borogove/calls/Session.hx
index 7358814..5a4e1d7 100644
--- a/borogove/calls/Session.hx
+++ b/borogove/calls/Session.hx
@@ -44,6 +44,7 @@ interface Session {
 	private function transportInfo(stanza: Stanza): Promise<Any>;
 	public function addMedia(streams: Array<MediaStream>): Void;
 	public function callStatus():CallStatus;
+	public function audioTracks():Array<MediaStreamTrack>;
 	public function videoTracks():Array<MediaStreamTrack>;
 	public function dtmf():Null<DTMFSender>;
 }
@@ -103,6 +104,7 @@ class IncomingProposedSession implements Session {
 			client.notifyMessageHandlers(stored[0], CorrectionEvent);
 		});
 		client.getDirectChat(from.asBare().asString(), false).jingleSessions.remove(sid);
+		client.trigger("call/retract", { chatId: chatId, sid: sid });
 	}
 
 	public function retract() {
@@ -143,7 +145,8 @@ class IncomingProposedSession implements Session {
 		});
 	}
 
-	public function initiate(stanza: Stanza) {
+	@:allow(borogove)
+	private function initiate(stanza: Stanza) {
 		// TODO: check if new session has corrent media
 		final session = InitiatedSession.fromSessionInitiate(client, stanza);
 		if (session.sid != sid) throw "id mismatch";
@@ -153,18 +156,27 @@ class IncomingProposedSession implements Session {
 		return session;
 	}
 
+	@HaxeCBridge.noemit
 	public function addMedia(_) {
 		throw "Cannot add media before call starts";
 	}
 
+	@HaxeCBridge.noemit
 	public function callStatus() {
 		return Incoming;
 	}
 
+	@HaxeCBridge.noemit
+	public function audioTracks() {
+		return [];
+	}
+
+	@HaxeCBridge.noemit
 	public function videoTracks() {
 		return [];
 	}
 
+	@HaxeCBridge.noemit
 	public function dtmf() {
 		return null;
 	}
@@ -187,13 +199,15 @@ class OutgoingProposedSession implements Session {
 	private var audio = false;
 	private var video = false;
 
-	public function new(client: Client, to: JID) {
+	@:allow(borogove)
+	private function new(client: Client, to: JID) {
 		this.client = client;
 		this.to = to;
 		this._sid = ID.long();
 	}
 
-	public function propose(audio: Bool, video: Bool) {
+	@:allow(borogove)
+	private function propose(audio: Bool, video: Bool) {
 		this.audio = audio;
 		this.video = video;
 		final event = new Stanza("propose", { xmlns: "urn:xmpp:jingle-message:0", id: sid });
@@ -210,14 +224,10 @@ class OutgoingProposedSession implements Session {
 				.tag("store", { xmlns: "urn:xmpp:hints" });
 			client.sendStanza(stanza);
 			client.notifyMessageHandlers(stored[0], DeliveryEvent);
-			client.trigger("call/ringing", { chatId: chatId, sid: sid });
+			client.trigger("call/ringing", this);
 		});
 	}
 
-	public function ring() {
-		trace("Tried to accept before initiate: " + sid, this);
-	}
-
 	public function hangup() {
 		final event = new Stanza("retract", { xmlns: "urn:xmpp:jingle-message:0", id: sid });
 		final msg = mkCallMessage(to, client.jid, event);
@@ -230,35 +240,43 @@ class OutgoingProposedSession implements Session {
 			client.notifyMessageHandlers(stored[0], CorrectionEvent);
 		});
 		client.getDirectChat(to.asBare().asString(), false).jingleSessions.remove(sid);
+		client.trigger("call/retract", { chatId: chatId, sid: sid });
 	}
 
-	public function retract() {
+	@:allow(borogove)
+	private function retract() {
 		// Other side rejected the call
 		client.trigger("call/retract", { chatId: chatId, sid: sid });
 	}
 
-	public function terminate() {
+	@:allow(borogove)
+	private function terminate() {
 		trace("Tried to terminate before session-initiate: " + sid, this);
 	}
 
-	public function contentAdd(_) {
+	@:allow(borogove)
+	private function contentAdd(_) {
 		trace("Got content-add before session-initiate: " + sid, this);
 	}
 
-	public function contentAccept(_) {
+	@:allow(borogove)
+	private function contentAccept(_) {
 		trace("Got content-accept before session-initiate: " + sid, this);
 	}
 
-	public function transportInfo(_) {
+	@:allow(borogove)
+	private function transportInfo(_) {
 		trace("Got transport-info before session-initiate: " + sid, this);
 		return Promise.resolve(null);
 	}
 
+	@HaxeCBridge.noemit
 	public function accept() {
 		trace("Tried to accept before initiate: " + sid, this);
 	}
 
-	public function initiate(stanza: Stanza) {
+	@:allow(borogove)
+	private function initiate(stanza: Stanza) {
 		final jmi = stanza.getChild("proceed", "urn:xmpp:jingle-message:0");
 		if (jmi == null) throw "no jmi: " + stanza;
 		if (jmi.attr.get("id") != sid) throw "sid doesn't match: " + jmi.attr.get("id") + " vs " + sid;
@@ -269,6 +287,7 @@ class OutgoingProposedSession implements Session {
 		return session;
 	}
 
+	@HaxeCBridge.noemit
 	public function addMedia(_) {
 		throw "Cannot add media before call starts";
 	}
@@ -277,10 +296,17 @@ class OutgoingProposedSession implements Session {
 		return Outgoing;
 	}
 
+	@HaxeCBridge.noemit
+	public function audioTracks() {
+		return [];
+	}
+
+	@HaxeCBridge.noemit
 	public function videoTracks() {
 		return [];
 	}
 
+	@HaxeCBridge.noemit
 	public function dtmf() {
 		return null;
 	}
@@ -381,15 +407,17 @@ class InitiatedSession implements Session {
 
 	@:allow(borogove)
 	private function terminate() {
+		client.trigger("call/retract", { chatId: chatId, sid: sid });
+
 		if (pc == null) return;
-		pc.close();
-		for (tranceiver in pc.getTransceivers()) {
+		final oldPc = pc;
+		pc = null;
+		oldPc.close();
+		for (tranceiver in oldPc.getTransceivers()) {
 			if (tranceiver.sender != null && tranceiver.sender.track != null) {
 				tranceiver.sender.track.stop();
 			}
 		}
-		pc = null;
-		client.trigger("call/retract", { chatId: chatId, sid: sid });
 
 		final event = new Stanza("finish", { xmlns: "urn:xmpp:jingle-message:0", id: sid });
 		final msg = mkCallMessage(counterpart, client.jid, event);
@@ -487,7 +515,7 @@ class InitiatedSession implements Session {
 	}
 
 	public function callStatus() {
-		return if (pc == null || pc.connectionState == "connecting") {
+		return if (pc == null || pc.connectionState == "connecting" || pc.connectionState == "new") {
 			Connecting;
 		} else if (pc.connectionState == "failed" || pc.connectionState == "closed") {
 			Failed;
@@ -496,6 +524,13 @@ class InitiatedSession implements Session {
 		}
 	}
 
+	public function audioTracks(): Array<MediaStreamTrack> {
+		if (pc == null) return [];
+		return pc.getTransceivers()
+			.filter((t) -> t.receiver != null && t.receiver.track != null && t.receiver.track.kind == "audio" && !t.receiver.track.muted)
+			.map((t) -> t.receiver.track);
+	}
+
 	public function videoTracks(): Array<MediaStreamTrack> {
 		if (pc == null) return [];
 		return pc.getTransceivers()
@@ -571,7 +606,7 @@ class InitiatedSession implements Session {
 		client.getIceServers((servers) -> {
 			pc = new PeerConnection({ iceServers: cast servers }, null);
 			pc.addEventListener("track", (event) -> {
-				client.trigger("call/track", { chatId: chatId, track: event.track, streams: event.streams });
+				client.trigger("call/track", { session: this, track: event.track, streams: event.streams });
 			});
 			pc.addEventListener("negotiationneeded", (event) -> { trace("renegotiate", event); return; });
 			pc.addEventListener("icecandidate", (event) -> {