Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for federated calls #4120

Merged
merged 11 commits into from
Sep 13, 2024
Merged
14 changes: 12 additions & 2 deletions app/src/main/java/com/nextcloud/talk/activities/CallActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -1453,7 +1453,7 @@ class CallActivity : CallBaseActivity() {
private fun fetchSignalingSettings() {
Log.d(TAG, "fetchSignalingSettings")
val apiVersion = ApiUtils.getSignalingApiVersion(conversationUser, intArrayOf(ApiUtils.API_V3, 2, 1))
ncApi!!.getSignalingSettings(credentials, ApiUtils.getUrlForSignalingSettings(apiVersion, baseUrl))
ncApi!!.getSignalingSettings(credentials, ApiUtils.getUrlForSignalingSettings(apiVersion, baseUrl, roomToken!!))
.subscribeOn(Schedulers.io())
.retry(API_RETRIES)
.observeOn(AndroidSchedulers.mainThread())
Expand All @@ -1475,6 +1475,8 @@ class CallActivity : CallBaseActivity() {
signalingSettingsOverall.ocs!!.settings!!.externalSignalingServer
externalSignalingServer!!.externalSignalingTicket =
signalingSettingsOverall.ocs!!.settings!!.externalSignalingTicket
externalSignalingServer!!.federation =
signalingSettingsOverall.ocs!!.settings!!.federation
hasExternalSignalingServer = true
} else {
hasExternalSignalingServer = false
Expand Down Expand Up @@ -1630,7 +1632,9 @@ class CallActivity : CallBaseActivity() {

private fun callOrJoinRoomViaWebSocket() {
if (hasExternalSignalingServer) {
webSocketClient!!.joinRoomWithRoomTokenAndSession(roomToken!!, callSession)
webSocketClient!!.joinRoomWithRoomTokenAndSession(
roomToken!!, callSession, externalSignalingServer!!.federation
)
} else {
performCall()
}
Expand Down Expand Up @@ -2157,6 +2161,10 @@ class CallActivity : CallBaseActivity() {
Log.d(TAG, " newSession joined: $sessionId")
addCallParticipant(sessionId)

if (participant.actorType != null && participant.actorId != null) {
callParticipants[sessionId]!!.setActor(participant.actorType, participant.actorId)
}

val userId = participant.userId
if (userId != null) {
callParticipants[sessionId]!!.setUserId(userId)
Expand Down Expand Up @@ -2510,10 +2518,12 @@ class CallActivity : CallBaseActivity() {
}
val defaultGuestNick = resources.getString(R.string.nc_nick_guest)
val participantDisplayItem = ParticipantDisplayItem(
context,
baseUrl,
defaultGuestNick,
rootEglBase,
videoStreamType,
roomToken,
callParticipantModel
)
val sessionId = callParticipantModel.sessionId
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,16 @@
*/
package com.nextcloud.talk.adapters;

import android.content.Context;
import android.os.Handler;
import android.os.Looper;
import android.text.TextUtils;

import com.nextcloud.talk.call.CallParticipantModel;
import com.nextcloud.talk.call.RaisedHand;
import com.nextcloud.talk.models.json.participants.Participant;
import com.nextcloud.talk.utils.ApiUtils;
import com.nextcloud.talk.utils.DisplayUtils;

import org.webrtc.EglBase;
import org.webrtc.MediaStream;
Expand All @@ -29,15 +32,21 @@ public class ParticipantDisplayItem {

private final ParticipantDisplayItemNotifier participantDisplayItemNotifier = new ParticipantDisplayItemNotifier();

private final Context context;

private final String baseUrl;
private final String defaultGuestNick;
private final EglBase rootEglBase;

private final String session;
private final String streamType;

private final String roomToken;

private final CallParticipantModel callParticipantModel;

private Participant.ActorType actorType;
private String actorId;
private String userId;
private PeerConnection.IceConnectionState iceConnectionState;
private String nick;
Expand All @@ -62,15 +71,19 @@ public void onReaction(String reaction) {
}
};

public ParticipantDisplayItem(String baseUrl, String defaultGuestNick, EglBase rootEglBase, String streamType,
CallParticipantModel callParticipantModel) {
public ParticipantDisplayItem(Context context, String baseUrl, String defaultGuestNick, EglBase rootEglBase,
String streamType, String roomToken, CallParticipantModel callParticipantModel) {
this.context = context;

this.baseUrl = baseUrl;
this.defaultGuestNick = defaultGuestNick;
this.rootEglBase = rootEglBase;

this.session = callParticipantModel.getSessionId();
this.streamType = streamType;

this.roomToken = roomToken;

this.callParticipantModel = callParticipantModel;
this.callParticipantModel.addObserver(callParticipantModelObserver, handler);

Expand All @@ -82,6 +95,8 @@ public void destroy() {
}

private void updateFromModel() {
actorType = callParticipantModel.getActorType();
actorId = callParticipantModel.getActorId();
userId = callParticipantModel.getUserId();
nick = callParticipantModel.getNick();

Expand All @@ -107,7 +122,10 @@ private void updateFromModel() {
}

private void updateUrlForAvatar() {
if (!TextUtils.isEmpty(userId)) {
if (actorType == Participant.ActorType.FEDERATED) {
int darkTheme = DisplayUtils.INSTANCE.isDarkModeOn(context) ? 1 : 0;
urlForAvatar = ApiUtils.getUrlForFederatedAvatar(baseUrl, roomToken, actorId, darkTheme, true);
} else if (!TextUtils.isEmpty(userId)) {
urlForAvatar = ApiUtils.getUrlForAvatar(baseUrl, userId, true);
} else {
urlForAvatar = ApiUtils.getUrlForGuestAvatar(baseUrl, getNick(), true);
Expand Down Expand Up @@ -166,6 +184,8 @@ public void removeObserver(Observer observer) {
public String toString() {
return "ParticipantSession{" +
"userId='" + userId + '\'' +
", actorType='" + actorType + '\'' +
", actorId='" + actorId + '\'' +
", session='" + session + '\'' +
", nick='" + nick + '\'' +
", urlForAvatar='" + urlForAvatar + '\'' +
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
*/
package com.nextcloud.talk.call;

import com.nextcloud.talk.models.json.participants.Participant;
import com.nextcloud.talk.signaling.SignalingMessageReceiver;
import com.nextcloud.talk.webrtc.PeerConnectionWrapper;

Expand Down Expand Up @@ -132,6 +133,10 @@ public CallParticipantModel getCallParticipantModel() {
return callParticipantModel;
}

public void setActor(Participant.ActorType actorType, String actorId) {
callParticipantModel.setActor(actorType, actorId);
}

public void setUserId(String userId) {
callParticipantModel.setUserId(userId);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,8 @@ public void onAllParticipantsUpdate(long inCall) {

private Participant copyParticipant(Participant participant) {
Participant copiedParticipant = new Participant();
copiedParticipant.setActorId(participant.getActorId());
copiedParticipant.setActorType(participant.getActorType());
copiedParticipant.setInCall(participant.getInCall());
copiedParticipant.setInternal(participant.getInternal());
copiedParticipant.setLastPing(participant.getLastPing());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@

import android.os.Handler;

import com.nextcloud.talk.models.json.participants.Participant;

import org.webrtc.MediaStream;
import org.webrtc.PeerConnection;

Expand All @@ -25,6 +27,8 @@
*
* Audio and video in screen shares, on the other hand, are always seen as available.
*
* Actor type and actor id will be set only in Talk >= 20.
*
* Clients of the model can observe it with CallParticipantModel.Observer to be notified when any value changes.
* Getters called after receiving a notification are guaranteed to provide at least the value that triggered the
* notification, but it may return even a more up to date one (so getting the value again on the following
Expand All @@ -39,6 +43,8 @@ public class CallParticipantModel {

protected final String sessionId;

protected Data<Participant.ActorType> actorType;
protected Data<String> actorId;
protected Data<String> userId;
protected Data<String> nick;

Expand Down Expand Up @@ -81,6 +87,8 @@ public void setValue(T value) {
public CallParticipantModel(String sessionId) {
this.sessionId = sessionId;

this.actorType = new Data<>();
this.actorId = new Data<>();
this.userId = new Data<>();
this.nick = new Data<>();

Expand All @@ -101,6 +109,14 @@ public String getSessionId() {
return sessionId;
}

public Participant.ActorType getActorType() {
return actorType.getValue();
}

public String getActorId() {
return actorId.getValue();
}

public String getUserId() {
return userId.getValue();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
*/
package com.nextcloud.talk.call;

import com.nextcloud.talk.models.json.participants.Participant;

import org.webrtc.MediaStream;
import org.webrtc.PeerConnection;

Expand All @@ -20,6 +22,11 @@ public MutableCallParticipantModel(String sessionId) {
super(sessionId);
}

public void setActor(Participant.ActorType actorType, String actorId) {
this.actorType.setValue(actorType);
this.actorId.setValue(actorId);
}

public void setUserId(String userId) {
this.userId.setValue(userId);
}
Expand Down
59 changes: 54 additions & 5 deletions app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -120,10 +120,12 @@ import com.nextcloud.talk.jobs.ShareOperationWorker
import com.nextcloud.talk.jobs.UploadAndShareFilesWorker
import com.nextcloud.talk.location.LocationPickerActivity
import com.nextcloud.talk.messagesearch.MessageSearchActivity
import com.nextcloud.talk.models.ExternalSignalingServer
import com.nextcloud.talk.models.domain.ConversationModel
import com.nextcloud.talk.models.json.capabilities.SpreedCapability
import com.nextcloud.talk.models.json.chat.ReadStatus
import com.nextcloud.talk.models.json.conversations.ConversationEnums
import com.nextcloud.talk.models.json.signaling.settings.SignalingSettingsOverall
import com.nextcloud.talk.polls.ui.PollCreateDialogFragment
import com.nextcloud.talk.remotefilebrowser.activities.RemoteFileBrowserActivity
import com.nextcloud.talk.shareditems.activities.SharedItemsActivity
Expand Down Expand Up @@ -177,6 +179,8 @@ import com.stfalcon.chatkit.messages.MessageHolders
import com.stfalcon.chatkit.messages.MessageHolders.ContentChecker
import com.stfalcon.chatkit.messages.MessagesListAdapter
import com.stfalcon.chatkit.utils.DateFormatter
import io.reactivex.Observer
import io.reactivex.disposables.Disposable
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.collect
Expand Down Expand Up @@ -302,6 +306,7 @@ class ChatActivity :

var webSocketInstance: WebSocketInstance? = null
var signalingMessageSender: SignalingMessageSender? = null
var externalSignalingServer: ExternalSignalingServer? = null

var getRoomInfoTimerHandler: Handler? = null

Expand Down Expand Up @@ -631,10 +636,12 @@ class ChatActivity :

logConversationInfos("joinRoomWithPassword#onNext")

setupWebsocket()
if (webSocketInstance != null) {
webSocketInstance?.joinRoomWithRoomTokenAndSession(
roomToken,
sessionIdAfterRoomJoined
sessionIdAfterRoomJoined,
externalSignalingServer?.federation
)
}
if (startCallFromNotification != null && startCallFromNotification) {
Expand Down Expand Up @@ -952,7 +959,6 @@ class ChatActivity :

pullChatMessagesPending = false

setupWebsocket()
webSocketInstance?.getSignalingMessageReceiver()?.addListener(localParticipantMessageListener)
webSocketInstance?.getSignalingMessageReceiver()?.addListener(conversationMessageListener)

Expand Down Expand Up @@ -2391,10 +2397,12 @@ class ChatActivity :
} else {
Log.d(TAG, "sessionID was valid -> skip joinRoom")

setupWebsocket()
if (webSocketInstance != null) {
webSocketInstance?.joinRoomWithRoomTokenAndSession(
roomToken,
sessionIdAfterRoomJoined
sessionIdAfterRoomJoined,
externalSignalingServer?.federation
)
}
}
Expand Down Expand Up @@ -2423,16 +2431,57 @@ class ChatActivity :
}

private fun setupWebsocket() {
if (conversationUser == null) {
if (currentConversation == null || conversationUser == null) {
return
}
webSocketInstance = WebSocketConnectionHelper.getWebSocketInstanceForUser(conversationUser!!)

if (currentConversation!!.remoteServer != null) {
val apiVersion = ApiUtils.getSignalingApiVersion(conversationUser!!, intArrayOf(ApiUtils.API_V3, 2, 1))
ncApi!!.getSignalingSettings(credentials, ApiUtils.getUrlForSignalingSettings(apiVersion, conversationUser!!.baseUrl,
roomToken!!)).blockingSubscribe(object : Observer<SignalingSettingsOverall> {
override fun onSubscribe(d: Disposable) {
// unused atm
}

override fun onNext(signalingSettingsOverall: SignalingSettingsOverall) {
if (signalingSettingsOverall.ocs!!.settings!!.externalSignalingServer == null) {
return
}

externalSignalingServer = ExternalSignalingServer()
externalSignalingServer!!.externalSignalingServer = signalingSettingsOverall.ocs!!.settings!!
.externalSignalingServer
externalSignalingServer!!.externalSignalingTicket = signalingSettingsOverall.ocs!!.settings!!
.externalSignalingTicket
externalSignalingServer!!.federation = signalingSettingsOverall.ocs!!.settings!!.federation

webSocketInstance = WebSocketConnectionHelper.getExternalSignalingInstanceForServer(
externalSignalingServer!!.externalSignalingServer,
conversationUser,
externalSignalingServer!!.externalSignalingTicket,
TextUtils.isEmpty(credentials)
)
}

override fun onError(e: Throwable) {
Log.e(CallActivity.TAG, e.message, e)
}

override fun onComplete() {
// unused atm
}
})
} else {
webSocketInstance = WebSocketConnectionHelper.getWebSocketInstanceForUser(conversationUser!!)
}

if (webSocketInstance == null) {
Log.d(TAG, "webSocketInstance not set up. This should only happen when not using the HPB")
}

signalingMessageSender = webSocketInstance?.signalingMessageSender
webSocketInstance?.getSignalingMessageReceiver()?.addListener(localParticipantMessageListener)
webSocketInstance?.getSignalingMessageReceiver()?.addListener(conversationMessageListener)
}

private fun processCallStartedMessages(chatMessageList: List<ChatMessage>) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ package com.nextcloud.talk.models
import android.os.Parcelable
import com.bluelinelabs.logansquare.annotation.JsonField
import com.bluelinelabs.logansquare.annotation.JsonObject
import com.nextcloud.talk.models.json.signaling.settings.FederationSettings
import kotlinx.parcelize.Parcelize

@Parcelize
Expand All @@ -18,8 +19,10 @@ data class ExternalSignalingServer(
@JsonField(name = ["externalSignalingServer"])
var externalSignalingServer: String? = null,
@JsonField(name = ["externalSignalingTicket"])
var externalSignalingTicket: String? = null
var externalSignalingTicket: String? = null,
@JsonField(name = ["federation"])
var federation: FederationSettings? = null
) : Parcelable {
// This constructor is added to work with the 'com.bluelinelabs.logansquare.annotation.JsonObject'
constructor() : this(null, null)
constructor() : this(null, null, null)
}
Loading
Loading