mirror of
https://github.com/nextcloud/talk-android.git
synced 2024-11-23 05:25:31 +03:00
Merge pull request #2545 from nextcloud/add-listener-for-data-channel-messages
Add listener for data channel messages
This commit is contained in:
commit
5786baaeb7
8 changed files with 312 additions and 139 deletions
|
@ -76,7 +76,6 @@ import com.nextcloud.talk.models.json.generic.GenericOverall;
|
||||||
import com.nextcloud.talk.models.json.participants.Participant;
|
import com.nextcloud.talk.models.json.participants.Participant;
|
||||||
import com.nextcloud.talk.models.json.participants.ParticipantsOverall;
|
import com.nextcloud.talk.models.json.participants.ParticipantsOverall;
|
||||||
import com.nextcloud.talk.models.json.signaling.DataChannelMessage;
|
import com.nextcloud.talk.models.json.signaling.DataChannelMessage;
|
||||||
import com.nextcloud.talk.models.json.signaling.DataChannelMessageNick;
|
|
||||||
import com.nextcloud.talk.models.json.signaling.NCMessagePayload;
|
import com.nextcloud.talk.models.json.signaling.NCMessagePayload;
|
||||||
import com.nextcloud.talk.models.json.signaling.NCSignalingMessage;
|
import com.nextcloud.talk.models.json.signaling.NCSignalingMessage;
|
||||||
import com.nextcloud.talk.models.json.signaling.Signaling;
|
import com.nextcloud.talk.models.json.signaling.Signaling;
|
||||||
|
@ -264,9 +263,13 @@ public class CallActivity extends CallBaseActivity {
|
||||||
private InternalSignalingMessageSender internalSignalingMessageSender = new InternalSignalingMessageSender();
|
private InternalSignalingMessageSender internalSignalingMessageSender = new InternalSignalingMessageSender();
|
||||||
private SignalingMessageSender signalingMessageSender;
|
private SignalingMessageSender signalingMessageSender;
|
||||||
|
|
||||||
|
private Map<String, OfferAnswerNickProvider> offerAnswerNickProviders = new HashMap<>();
|
||||||
|
|
||||||
private Map<String, SignalingMessageReceiver.CallParticipantMessageListener> callParticipantMessageListeners =
|
private Map<String, SignalingMessageReceiver.CallParticipantMessageListener> callParticipantMessageListeners =
|
||||||
new HashMap<>();
|
new HashMap<>();
|
||||||
|
|
||||||
|
private Map<String, PeerConnectionWrapper.DataChannelMessageListener> dataChannelMessageListeners = new HashMap<>();
|
||||||
|
|
||||||
private SignalingMessageReceiver.ParticipantListMessageListener participantListMessageListener = new SignalingMessageReceiver.ParticipantListMessageListener() {
|
private SignalingMessageReceiver.ParticipantListMessageListener participantListMessageListener = new SignalingMessageReceiver.ParticipantListMessageListener() {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -2006,6 +2009,19 @@ public class CallActivity extends CallBaseActivity {
|
||||||
new CallActivityCallParticipantMessageListener(sessionId);
|
new CallActivityCallParticipantMessageListener(sessionId);
|
||||||
callParticipantMessageListeners.put(sessionId, callParticipantMessageListener);
|
callParticipantMessageListeners.put(sessionId, callParticipantMessageListener);
|
||||||
signalingMessageReceiver.addListener(callParticipantMessageListener, sessionId);
|
signalingMessageReceiver.addListener(callParticipantMessageListener, sessionId);
|
||||||
|
|
||||||
|
// DataChannel messages are sent only in video peers; (sender) screen peers do not even open them.
|
||||||
|
PeerConnectionWrapper.DataChannelMessageListener dataChannelMessageListener =
|
||||||
|
new CallActivityDataChannelMessageListener(sessionId);
|
||||||
|
dataChannelMessageListeners.put(sessionId, dataChannelMessageListener);
|
||||||
|
peerConnectionWrapper.addListener(dataChannelMessageListener);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!publisher && !hasExternalSignalingServer && offerAnswerNickProviders.get(sessionId) == null) {
|
||||||
|
OfferAnswerNickProvider offerAnswerNickProvider = new OfferAnswerNickProvider();
|
||||||
|
offerAnswerNickProviders.put(sessionId, offerAnswerNickProvider);
|
||||||
|
signalingMessageReceiver.addListener(offerAnswerNickProvider.getVideoWebRtcMessageListener(), sessionId, "video");
|
||||||
|
signalingMessageReceiver.addListener(offerAnswerNickProvider.getScreenWebRtcMessageListener(), sessionId, "screen");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (publisher) {
|
if (publisher) {
|
||||||
|
@ -2032,6 +2048,10 @@ public class CallActivity extends CallBaseActivity {
|
||||||
if (!(peerConnectionWrappers = getPeerConnectionWrapperListForSessionId(sessionId)).isEmpty()) {
|
if (!(peerConnectionWrappers = getPeerConnectionWrapperListForSessionId(sessionId)).isEmpty()) {
|
||||||
for (PeerConnectionWrapper peerConnectionWrapper : peerConnectionWrappers) {
|
for (PeerConnectionWrapper peerConnectionWrapper : peerConnectionWrappers) {
|
||||||
if (peerConnectionWrapper.getSessionId().equals(sessionId)) {
|
if (peerConnectionWrapper.getSessionId().equals(sessionId)) {
|
||||||
|
if (!justScreen && VIDEO_STREAM_TYPE_VIDEO.equals(peerConnectionWrapper.getVideoStreamType())) {
|
||||||
|
PeerConnectionWrapper.DataChannelMessageListener dataChannelMessageListener = dataChannelMessageListeners.remove(sessionId);
|
||||||
|
peerConnectionWrapper.removeListener(dataChannelMessageListener);
|
||||||
|
}
|
||||||
String videoStreamType = peerConnectionWrapper.getVideoStreamType();
|
String videoStreamType = peerConnectionWrapper.getVideoStreamType();
|
||||||
if (VIDEO_STREAM_TYPE_SCREEN.equals(videoStreamType) || !justScreen) {
|
if (VIDEO_STREAM_TYPE_SCREEN.equals(videoStreamType) || !justScreen) {
|
||||||
runOnUiThread(() -> removeMediaStream(sessionId, videoStreamType));
|
runOnUiThread(() -> removeMediaStream(sessionId, videoStreamType));
|
||||||
|
@ -2044,6 +2064,12 @@ public class CallActivity extends CallBaseActivity {
|
||||||
if (!justScreen) {
|
if (!justScreen) {
|
||||||
SignalingMessageReceiver.CallParticipantMessageListener listener = callParticipantMessageListeners.remove(sessionId);
|
SignalingMessageReceiver.CallParticipantMessageListener listener = callParticipantMessageListeners.remove(sessionId);
|
||||||
signalingMessageReceiver.removeListener(listener);
|
signalingMessageReceiver.removeListener(listener);
|
||||||
|
|
||||||
|
OfferAnswerNickProvider offerAnswerNickProvider = offerAnswerNickProviders.remove(sessionId);
|
||||||
|
if (offerAnswerNickProvider != null) {
|
||||||
|
signalingMessageReceiver.removeListener(offerAnswerNickProvider.getVideoWebRtcMessageListener());
|
||||||
|
signalingMessageReceiver.removeListener(offerAnswerNickProvider.getScreenWebRtcMessageListener());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2149,24 +2175,6 @@ public class CallActivity extends CallBaseActivity {
|
||||||
toggleMedia(enableVideo, true);
|
toggleMedia(enableVideo, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (peerConnectionEvent.getPeerConnectionEventType() ==
|
|
||||||
PeerConnectionEvent.PeerConnectionEventType.NICK_CHANGE) {
|
|
||||||
if (participantDisplayItems.get(participantDisplayItemId) != null) {
|
|
||||||
participantDisplayItems.get(participantDisplayItemId).setNick(peerConnectionEvent.getNick());
|
|
||||||
participantsAdapter.notifyDataSetChanged();
|
|
||||||
}
|
|
||||||
} else if (peerConnectionEvent.getPeerConnectionEventType() ==
|
|
||||||
PeerConnectionEvent.PeerConnectionEventType.VIDEO_CHANGE && !isVoiceOnlyCall) {
|
|
||||||
if (participantDisplayItems.get(participantDisplayItemId) != null) {
|
|
||||||
participantDisplayItems.get(participantDisplayItemId).setStreamEnabled(peerConnectionEvent.getChangeValue());
|
|
||||||
participantsAdapter.notifyDataSetChanged();
|
|
||||||
}
|
|
||||||
} else if (peerConnectionEvent.getPeerConnectionEventType() ==
|
|
||||||
PeerConnectionEvent.PeerConnectionEventType.AUDIO_CHANGE) {
|
|
||||||
if (participantDisplayItems.get(participantDisplayItemId) != null) {
|
|
||||||
participantDisplayItems.get(participantDisplayItemId).setAudioEnabled(peerConnectionEvent.getChangeValue());
|
|
||||||
participantsAdapter.notifyDataSetChanged();
|
|
||||||
}
|
|
||||||
} else if (peerConnectionEvent.getPeerConnectionEventType() ==
|
} else if (peerConnectionEvent.getPeerConnectionEventType() ==
|
||||||
PeerConnectionEvent.PeerConnectionEventType.PUBLISHER_FAILED) {
|
PeerConnectionEvent.PeerConnectionEventType.PUBLISHER_FAILED) {
|
||||||
setCallState(CallStatus.PUBLISHER_FAILED);
|
setCallState(CallStatus.PUBLISHER_FAILED);
|
||||||
|
@ -2176,12 +2184,12 @@ public class CallActivity extends CallBaseActivity {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void startSendingNick() {
|
private void startSendingNick() {
|
||||||
DataChannelMessageNick dataChannelMessage = new DataChannelMessageNick();
|
DataChannelMessage dataChannelMessage = new DataChannelMessage();
|
||||||
dataChannelMessage.setType("nickChanged");
|
dataChannelMessage.setType("nickChanged");
|
||||||
HashMap<String, String> nickChangedPayload = new HashMap<>();
|
Map<String, String> nickChangedPayload = new HashMap<>();
|
||||||
nickChangedPayload.put("userid", conversationUser.getUserId());
|
nickChangedPayload.put("userid", conversationUser.getUserId());
|
||||||
nickChangedPayload.put("name", conversationUser.getDisplayName());
|
nickChangedPayload.put("name", conversationUser.getDisplayName());
|
||||||
dataChannelMessage.setPayload(nickChangedPayload);
|
dataChannelMessage.setPayloadMap(nickChangedPayload);
|
||||||
for (PeerConnectionWrapper peerConnectionWrapper : peerConnectionWrapperList) {
|
for (PeerConnectionWrapper peerConnectionWrapper : peerConnectionWrapperList) {
|
||||||
if (peerConnectionWrapper.isMCUPublisher()) {
|
if (peerConnectionWrapper.isMCUPublisher()) {
|
||||||
Observable
|
Observable
|
||||||
|
@ -2196,7 +2204,7 @@ public class CallActivity extends CallBaseActivity {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onNext(@io.reactivex.annotations.NonNull Long aLong) {
|
public void onNext(@io.reactivex.annotations.NonNull Long aLong) {
|
||||||
peerConnectionWrapper.sendNickChannelData(dataChannelMessage);
|
peerConnectionWrapper.sendChannelData(dataChannelMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -2265,7 +2273,7 @@ public class CallActivity extends CallBaseActivity {
|
||||||
if (hasExternalSignalingServer) {
|
if (hasExternalSignalingServer) {
|
||||||
nick = webSocketClient.getDisplayNameForSession(session);
|
nick = webSocketClient.getDisplayNameForSession(session);
|
||||||
} else {
|
} else {
|
||||||
nick = peerConnectionWrapper != null ? peerConnectionWrapper.getNick() : "";
|
nick = offerAnswerNickProviders.get(session) != null ? offerAnswerNickProviders.get(session).getNick() : "";
|
||||||
}
|
}
|
||||||
|
|
||||||
String userId4Usage = userId;
|
String userId4Usage = userId;
|
||||||
|
@ -2278,11 +2286,14 @@ public class CallActivity extends CallBaseActivity {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String defaultGuestNick = getResources().getString(R.string.nc_nick_guest);
|
||||||
|
|
||||||
ParticipantDisplayItem participantDisplayItem = new ParticipantDisplayItem(baseUrl,
|
ParticipantDisplayItem participantDisplayItem = new ParticipantDisplayItem(baseUrl,
|
||||||
userId4Usage,
|
userId4Usage,
|
||||||
session,
|
session,
|
||||||
connected,
|
connected,
|
||||||
nick,
|
nick,
|
||||||
|
defaultGuestNick,
|
||||||
mediaStream,
|
mediaStream,
|
||||||
videoStreamType,
|
videoStreamType,
|
||||||
videoStreamEnabled,
|
videoStreamEnabled,
|
||||||
|
@ -2559,6 +2570,47 @@ public class CallActivity extends CallBaseActivity {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static class OfferAnswerNickProvider {
|
||||||
|
|
||||||
|
private class WebRtcMessageListener implements SignalingMessageReceiver.WebRtcMessageListener {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onOffer(String sdp, String nick) {
|
||||||
|
(OfferAnswerNickProvider.this).nick = nick;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAnswer(String sdp, String nick) {
|
||||||
|
(OfferAnswerNickProvider.this).nick = nick;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCandidate(String sdpMid, int sdpMLineIndex, String sdp) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onEndOfCandidates() {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final WebRtcMessageListener videoWebRtcMessageListener = new WebRtcMessageListener();
|
||||||
|
private final WebRtcMessageListener screenWebRtcMessageListener = new WebRtcMessageListener();
|
||||||
|
|
||||||
|
private String nick;
|
||||||
|
|
||||||
|
public WebRtcMessageListener getVideoWebRtcMessageListener() {
|
||||||
|
return videoWebRtcMessageListener;
|
||||||
|
}
|
||||||
|
|
||||||
|
public WebRtcMessageListener getScreenWebRtcMessageListener() {
|
||||||
|
return screenWebRtcMessageListener;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getNick() {
|
||||||
|
return nick;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private class CallActivityCallParticipantMessageListener implements SignalingMessageReceiver.CallParticipantMessageListener {
|
private class CallActivityCallParticipantMessageListener implements SignalingMessageReceiver.CallParticipantMessageListener {
|
||||||
|
|
||||||
private final String sessionId;
|
private final String sessionId;
|
||||||
|
@ -2573,6 +2625,66 @@ public class CallActivity extends CallBaseActivity {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class CallActivityDataChannelMessageListener implements PeerConnectionWrapper.DataChannelMessageListener {
|
||||||
|
|
||||||
|
private final String participantDisplayItemId;
|
||||||
|
|
||||||
|
private CallActivityDataChannelMessageListener(String sessionId) {
|
||||||
|
// DataChannel messages are sent only in video peers, so the listener only acts on the "video" items.
|
||||||
|
this.participantDisplayItemId = sessionId + "-video";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAudioOn() {
|
||||||
|
runOnUiThread(() -> {
|
||||||
|
if (participantDisplayItems.get(participantDisplayItemId) != null) {
|
||||||
|
participantDisplayItems.get(participantDisplayItemId).setAudioEnabled(true);
|
||||||
|
participantsAdapter.notifyDataSetChanged();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAudioOff() {
|
||||||
|
runOnUiThread(() -> {
|
||||||
|
if (participantDisplayItems.get(participantDisplayItemId) != null) {
|
||||||
|
participantDisplayItems.get(participantDisplayItemId).setAudioEnabled(false);
|
||||||
|
participantsAdapter.notifyDataSetChanged();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onVideoOn() {
|
||||||
|
runOnUiThread(() -> {
|
||||||
|
if (participantDisplayItems.get(participantDisplayItemId) != null) {
|
||||||
|
participantDisplayItems.get(participantDisplayItemId).setStreamEnabled(true);
|
||||||
|
participantsAdapter.notifyDataSetChanged();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onVideoOff() {
|
||||||
|
runOnUiThread(() -> {
|
||||||
|
if (participantDisplayItems.get(participantDisplayItemId) != null) {
|
||||||
|
participantDisplayItems.get(participantDisplayItemId).setStreamEnabled(false);
|
||||||
|
participantsAdapter.notifyDataSetChanged();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onNickChanged(String nick) {
|
||||||
|
runOnUiThread(() -> {
|
||||||
|
if (participantDisplayItems.get(participantDisplayItemId) != null) {
|
||||||
|
participantDisplayItems.get(participantDisplayItemId).setNick(nick);
|
||||||
|
participantsAdapter.notifyDataSetChanged();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private class InternalSignalingMessageSender implements SignalingMessageSender {
|
private class InternalSignalingMessageSender implements SignalingMessageSender {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -13,6 +13,7 @@ public class ParticipantDisplayItem {
|
||||||
private String session;
|
private String session;
|
||||||
private boolean connected;
|
private boolean connected;
|
||||||
private String nick;
|
private String nick;
|
||||||
|
private final String defaultGuestNick;
|
||||||
private String urlForAvatar;
|
private String urlForAvatar;
|
||||||
private MediaStream mediaStream;
|
private MediaStream mediaStream;
|
||||||
private String streamType;
|
private String streamType;
|
||||||
|
@ -20,12 +21,13 @@ public class ParticipantDisplayItem {
|
||||||
private EglBase rootEglBase;
|
private EglBase rootEglBase;
|
||||||
private boolean isAudioEnabled;
|
private boolean isAudioEnabled;
|
||||||
|
|
||||||
public ParticipantDisplayItem(String baseUrl, String userId, String session, boolean connected, String nick, MediaStream mediaStream, String streamType, boolean streamEnabled, EglBase rootEglBase) {
|
public ParticipantDisplayItem(String baseUrl, String userId, String session, boolean connected, String nick, String defaultGuestNick, MediaStream mediaStream, String streamType, boolean streamEnabled, EglBase rootEglBase) {
|
||||||
this.baseUrl = baseUrl;
|
this.baseUrl = baseUrl;
|
||||||
this.userId = userId;
|
this.userId = userId;
|
||||||
this.session = session;
|
this.session = session;
|
||||||
this.connected = connected;
|
this.connected = connected;
|
||||||
this.nick = nick;
|
this.nick = nick;
|
||||||
|
this.defaultGuestNick = defaultGuestNick;
|
||||||
this.mediaStream = mediaStream;
|
this.mediaStream = mediaStream;
|
||||||
this.streamType = streamType;
|
this.streamType = streamType;
|
||||||
this.streamEnabled = streamEnabled;
|
this.streamEnabled = streamEnabled;
|
||||||
|
@ -61,6 +63,10 @@ public class ParticipantDisplayItem {
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getNick() {
|
public String getNick() {
|
||||||
|
if (TextUtils.isEmpty(userId) && TextUtils.isEmpty(nick)) {
|
||||||
|
return defaultGuestNick;
|
||||||
|
}
|
||||||
|
|
||||||
return nick;
|
return nick;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -78,7 +84,7 @@ public class ParticipantDisplayItem {
|
||||||
if (!TextUtils.isEmpty(userId)) {
|
if (!TextUtils.isEmpty(userId)) {
|
||||||
urlForAvatar = ApiUtils.getUrlForAvatar(baseUrl, userId, true);
|
urlForAvatar = ApiUtils.getUrlForAvatar(baseUrl, userId, true);
|
||||||
} else {
|
} else {
|
||||||
urlForAvatar = ApiUtils.getUrlForGuestAvatar(baseUrl, nick, true);
|
urlForAvatar = ApiUtils.getUrlForGuestAvatar(baseUrl, getNick(), true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -120,6 +120,6 @@ public class PeerConnectionEvent {
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum PeerConnectionEventType {
|
public enum PeerConnectionEventType {
|
||||||
PEER_CONNECTED, PEER_DISCONNECTED, PEER_CLOSED, SENSOR_FAR, SENSOR_NEAR, NICK_CHANGE, AUDIO_CHANGE, VIDEO_CHANGE, PUBLISHER_FAILED
|
PEER_CONNECTED, PEER_DISCONNECTED, PEER_CLOSED, SENSOR_FAR, SENSOR_NEAR, PUBLISHER_FAILED
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,10 +34,15 @@ import kotlinx.android.parcel.TypeParceler
|
||||||
data class DataChannelMessage(
|
data class DataChannelMessage(
|
||||||
@JsonField(name = ["type"])
|
@JsonField(name = ["type"])
|
||||||
var type: String? = null,
|
var type: String? = null,
|
||||||
|
/** Can be String or Map<String, String>
|
||||||
|
* Use only for received messages */
|
||||||
@JsonField(name = ["payload"])
|
@JsonField(name = ["payload"])
|
||||||
var payload: Any? = null
|
var payload: Any? = null,
|
||||||
|
/** Use only to send messages */
|
||||||
|
@JsonField(name = ["payload"])
|
||||||
|
var payloadMap: Map<String, String>? = null
|
||||||
) : Parcelable {
|
) : Parcelable {
|
||||||
// This constructor is added to work with the 'com.bluelinelabs.logansquare.annotation.JsonObject'
|
// This constructor is added to work with the 'com.bluelinelabs.logansquare.annotation.JsonObject'
|
||||||
constructor() : this(null, null)
|
constructor() : this(null, null, null)
|
||||||
constructor(type: String) : this(type, null)
|
constructor(type: String) : this(type, null)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,40 +0,0 @@
|
||||||
/*
|
|
||||||
* Nextcloud Talk application
|
|
||||||
*
|
|
||||||
* @author Mario Danic
|
|
||||||
* @author Andy Scherzinger
|
|
||||||
* Copyright (C) 2022 Andy Scherzinger <info@andy-scherzinger.de>
|
|
||||||
* Copyright (C) 2017 Mario Danic <mario@lovelyhq.com>
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
package com.nextcloud.talk.models.json.signaling
|
|
||||||
|
|
||||||
import android.os.Parcelable
|
|
||||||
import com.bluelinelabs.logansquare.annotation.JsonField
|
|
||||||
import com.bluelinelabs.logansquare.annotation.JsonObject
|
|
||||||
import java.util.HashMap
|
|
||||||
import kotlinx.android.parcel.Parcelize
|
|
||||||
|
|
||||||
@Parcelize
|
|
||||||
@JsonObject
|
|
||||||
data class DataChannelMessageNick(
|
|
||||||
@JsonField(name = ["type"])
|
|
||||||
var type: String? = null,
|
|
||||||
@JsonField(name = ["payload"])
|
|
||||||
var payload: HashMap<String, String>? = null
|
|
||||||
) : Parcelable {
|
|
||||||
// This constructor is added to work with the 'com.bluelinelabs.logansquare.annotation.JsonObject'
|
|
||||||
constructor() : this(null, null)
|
|
||||||
}
|
|
|
@ -0,0 +1,77 @@
|
||||||
|
/*
|
||||||
|
* Nextcloud Talk application
|
||||||
|
*
|
||||||
|
* @author Daniel Calviño Sánchez
|
||||||
|
* Copyright (C) 2022 Daniel Calviño Sánchez <danxuliu@gmail.com>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
package com.nextcloud.talk.webrtc;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.LinkedHashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper class to register and notify DataChannelMessageListeners.
|
||||||
|
*
|
||||||
|
* This class is only meant for internal use by PeerConnectionWrapper; listeners must register themselves against
|
||||||
|
* a PeerConnectionWrapper rather than against a DataChannelMessageNotifier.
|
||||||
|
*/
|
||||||
|
public class DataChannelMessageNotifier {
|
||||||
|
|
||||||
|
private final Set<PeerConnectionWrapper.DataChannelMessageListener> dataChannelMessageListeners = new LinkedHashSet<>();
|
||||||
|
|
||||||
|
public synchronized void addListener(PeerConnectionWrapper.DataChannelMessageListener listener) {
|
||||||
|
if (listener == null) {
|
||||||
|
throw new IllegalArgumentException("DataChannelMessageListener can not be null");
|
||||||
|
}
|
||||||
|
|
||||||
|
dataChannelMessageListeners.add(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void removeListener(PeerConnectionWrapper.DataChannelMessageListener listener) {
|
||||||
|
dataChannelMessageListeners.remove(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void notifyAudioOn() {
|
||||||
|
for (PeerConnectionWrapper.DataChannelMessageListener listener : new ArrayList<>(dataChannelMessageListeners)) {
|
||||||
|
listener.onAudioOn();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void notifyAudioOff() {
|
||||||
|
for (PeerConnectionWrapper.DataChannelMessageListener listener : new ArrayList<>(dataChannelMessageListeners)) {
|
||||||
|
listener.onAudioOff();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void notifyVideoOn() {
|
||||||
|
for (PeerConnectionWrapper.DataChannelMessageListener listener : new ArrayList<>(dataChannelMessageListeners)) {
|
||||||
|
listener.onVideoOn();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void notifyVideoOff() {
|
||||||
|
for (PeerConnectionWrapper.DataChannelMessageListener listener : new ArrayList<>(dataChannelMessageListeners)) {
|
||||||
|
listener.onVideoOff();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void notifyNickChanged(String nick) {
|
||||||
|
for (PeerConnectionWrapper.DataChannelMessageListener listener : new ArrayList<>(dataChannelMessageListeners)) {
|
||||||
|
listener.onNickChanged(nick);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -25,7 +25,6 @@ import android.text.TextUtils;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import com.bluelinelabs.logansquare.LoganSquare;
|
import com.bluelinelabs.logansquare.LoganSquare;
|
||||||
import com.nextcloud.talk.R;
|
|
||||||
import com.nextcloud.talk.application.NextcloudTalkApplication;
|
import com.nextcloud.talk.application.NextcloudTalkApplication;
|
||||||
import com.nextcloud.talk.data.user.model.User;
|
import com.nextcloud.talk.data.user.model.User;
|
||||||
import com.nextcloud.talk.events.NetworkEvent;
|
import com.nextcloud.talk.events.NetworkEvent;
|
||||||
|
@ -384,7 +383,7 @@ public class MagicWebSocketInstance extends WebSocketListener {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return NextcloudTalkApplication.Companion.getSharedApplication().getString(R.string.nc_nick_guest);
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getUserIdForSession(String session) {
|
public String getUserIdForSession(String session) {
|
||||||
|
|
|
@ -24,16 +24,13 @@
|
||||||
package com.nextcloud.talk.webrtc;
|
package com.nextcloud.talk.webrtc;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.text.TextUtils;
|
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import com.bluelinelabs.logansquare.LoganSquare;
|
import com.bluelinelabs.logansquare.LoganSquare;
|
||||||
import com.nextcloud.talk.R;
|
|
||||||
import com.nextcloud.talk.application.NextcloudTalkApplication;
|
import com.nextcloud.talk.application.NextcloudTalkApplication;
|
||||||
import com.nextcloud.talk.events.MediaStreamEvent;
|
import com.nextcloud.talk.events.MediaStreamEvent;
|
||||||
import com.nextcloud.talk.events.PeerConnectionEvent;
|
import com.nextcloud.talk.events.PeerConnectionEvent;
|
||||||
import com.nextcloud.talk.models.json.signaling.DataChannelMessage;
|
import com.nextcloud.talk.models.json.signaling.DataChannelMessage;
|
||||||
import com.nextcloud.talk.models.json.signaling.DataChannelMessageNick;
|
|
||||||
import com.nextcloud.talk.models.json.signaling.NCIceCandidate;
|
import com.nextcloud.talk.models.json.signaling.NCIceCandidate;
|
||||||
import com.nextcloud.talk.models.json.signaling.NCMessagePayload;
|
import com.nextcloud.talk.models.json.signaling.NCMessagePayload;
|
||||||
import com.nextcloud.talk.models.json.signaling.NCSignalingMessage;
|
import com.nextcloud.talk.models.json.signaling.NCSignalingMessage;
|
||||||
|
@ -59,8 +56,8 @@ import java.io.IOException;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
@ -68,12 +65,26 @@ import javax.inject.Inject;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import autodagger.AutoInjector;
|
import autodagger.AutoInjector;
|
||||||
|
|
||||||
import static java.lang.Boolean.FALSE;
|
|
||||||
import static java.lang.Boolean.TRUE;
|
|
||||||
|
|
||||||
@AutoInjector(NextcloudTalkApplication.class)
|
@AutoInjector(NextcloudTalkApplication.class)
|
||||||
public class PeerConnectionWrapper {
|
public class PeerConnectionWrapper {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Listener for data channel messages.
|
||||||
|
*
|
||||||
|
* The messages are bound to a specific peer connection, so each listener is expected to handle messages only for
|
||||||
|
* a single peer connection.
|
||||||
|
*
|
||||||
|
* All methods are called on the so called "signaling" thread of WebRTC, which is an internal thread created by the
|
||||||
|
* WebRTC library and NOT the same thread where signaling messages are received.
|
||||||
|
*/
|
||||||
|
public interface DataChannelMessageListener {
|
||||||
|
void onAudioOn();
|
||||||
|
void onAudioOff();
|
||||||
|
void onVideoOn();
|
||||||
|
void onVideoOff();
|
||||||
|
void onNickChanged(String nick);
|
||||||
|
}
|
||||||
|
|
||||||
private static final String TAG = PeerConnectionWrapper.class.getCanonicalName();
|
private static final String TAG = PeerConnectionWrapper.class.getCanonicalName();
|
||||||
|
|
||||||
private final SignalingMessageReceiver signalingMessageReceiver;
|
private final SignalingMessageReceiver signalingMessageReceiver;
|
||||||
|
@ -81,10 +92,11 @@ public class PeerConnectionWrapper {
|
||||||
|
|
||||||
private final SignalingMessageSender signalingMessageSender;
|
private final SignalingMessageSender signalingMessageSender;
|
||||||
|
|
||||||
|
private final DataChannelMessageNotifier dataChannelMessageNotifier = new DataChannelMessageNotifier();
|
||||||
|
|
||||||
private List<IceCandidate> iceCandidates = new ArrayList<>();
|
private List<IceCandidate> iceCandidates = new ArrayList<>();
|
||||||
private PeerConnection peerConnection;
|
private PeerConnection peerConnection;
|
||||||
private String sessionId;
|
private String sessionId;
|
||||||
private String nick;
|
|
||||||
private final MediaConstraints mediaConstraints;
|
private final MediaConstraints mediaConstraints;
|
||||||
private DataChannel dataChannel;
|
private DataChannel dataChannel;
|
||||||
private final MagicSdpObserver magicSdpObserver;
|
private final MagicSdpObserver magicSdpObserver;
|
||||||
|
@ -160,6 +172,21 @@ public class PeerConnectionWrapper {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a listener for data channel messages.
|
||||||
|
*
|
||||||
|
* A listener is expected to be added only once. If the same listener is added again it will be notified just once.
|
||||||
|
*
|
||||||
|
* @param listener the DataChannelMessageListener
|
||||||
|
*/
|
||||||
|
public void addListener(DataChannelMessageListener listener) {
|
||||||
|
dataChannelMessageNotifier.addListener(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removeListener(DataChannelMessageListener listener) {
|
||||||
|
dataChannelMessageNotifier.removeListener(listener);
|
||||||
|
}
|
||||||
|
|
||||||
public String getVideoStreamType() {
|
public String getVideoStreamType() {
|
||||||
return videoStreamType;
|
return videoStreamType;
|
||||||
}
|
}
|
||||||
|
@ -203,18 +230,6 @@ public class PeerConnectionWrapper {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void sendNickChannelData(DataChannelMessageNick dataChannelMessage) {
|
|
||||||
ByteBuffer buffer;
|
|
||||||
if (dataChannel != null) {
|
|
||||||
try {
|
|
||||||
buffer = ByteBuffer.wrap(LoganSquare.serialize(dataChannelMessage).getBytes());
|
|
||||||
dataChannel.send(new DataChannel.Buffer(buffer, false));
|
|
||||||
} catch (IOException e) {
|
|
||||||
Log.d(TAG, "Failed to send channel data, attempting regular " + dataChannelMessage);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void sendChannelData(DataChannelMessage dataChannelMessage) {
|
public void sendChannelData(DataChannelMessage dataChannelMessage) {
|
||||||
ByteBuffer buffer;
|
ByteBuffer buffer;
|
||||||
if (dataChannel != null) {
|
if (dataChannel != null) {
|
||||||
|
@ -235,18 +250,6 @@ public class PeerConnectionWrapper {
|
||||||
return sessionId;
|
return sessionId;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getNick() {
|
|
||||||
if (!TextUtils.isEmpty(nick)) {
|
|
||||||
return nick;
|
|
||||||
} else {
|
|
||||||
return Objects.requireNonNull(NextcloudTalkApplication.Companion.getSharedApplication()).getString(R.string.nc_nick_guest);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setNick(String nick) {
|
|
||||||
this.nick = nick;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void sendInitialMediaStatus() {
|
private void sendInitialMediaStatus() {
|
||||||
if (localStream != null) {
|
if (localStream != null) {
|
||||||
if (localStream.videoTracks.size() == 1 && localStream.videoTracks.get(0).enabled()) {
|
if (localStream.videoTracks.size() == 1 && localStream.videoTracks.get(0).enabled()) {
|
||||||
|
@ -288,16 +291,14 @@ public class PeerConnectionWrapper {
|
||||||
private class WebRtcMessageListener implements SignalingMessageReceiver.WebRtcMessageListener {
|
private class WebRtcMessageListener implements SignalingMessageReceiver.WebRtcMessageListener {
|
||||||
|
|
||||||
public void onOffer(String sdp, String nick) {
|
public void onOffer(String sdp, String nick) {
|
||||||
onOfferOrAnswer("offer", sdp, nick);
|
onOfferOrAnswer("offer", sdp);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onAnswer(String sdp, String nick) {
|
public void onAnswer(String sdp, String nick) {
|
||||||
onOfferOrAnswer("answer", sdp, nick);
|
onOfferOrAnswer("answer", sdp);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onOfferOrAnswer(String type, String sdp, String nick) {
|
private void onOfferOrAnswer(String type, String sdp) {
|
||||||
setNick(nick);
|
|
||||||
|
|
||||||
SessionDescription sessionDescriptionWithPreferredCodec;
|
SessionDescription sessionDescriptionWithPreferredCodec;
|
||||||
|
|
||||||
boolean isAudio = false;
|
boolean isAudio = false;
|
||||||
|
@ -350,40 +351,53 @@ public class PeerConnectionWrapper {
|
||||||
String strData = new String(bytes);
|
String strData = new String(bytes);
|
||||||
Log.d(TAG, "Got msg: " + strData + " over " + TAG + " " + sessionId);
|
Log.d(TAG, "Got msg: " + strData + " over " + TAG + " " + sessionId);
|
||||||
|
|
||||||
|
DataChannelMessage dataChannelMessage;
|
||||||
try {
|
try {
|
||||||
DataChannelMessage dataChannelMessage = LoganSquare.parse(strData, DataChannelMessage.class);
|
dataChannelMessage = LoganSquare.parse(strData, DataChannelMessage.class);
|
||||||
|
|
||||||
String internalNick;
|
|
||||||
if ("nickChanged".equals(dataChannelMessage.getType())) {
|
|
||||||
if (dataChannelMessage.getPayload() instanceof String) {
|
|
||||||
internalNick = (String) dataChannelMessage.getPayload();
|
|
||||||
if (!internalNick.equals(nick)) {
|
|
||||||
setNick(internalNick);
|
|
||||||
EventBus.getDefault().post(new PeerConnectionEvent(PeerConnectionEvent.PeerConnectionEventType
|
|
||||||
.NICK_CHANGE, sessionId, getNick(), null, videoStreamType));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (dataChannelMessage.getPayload() != null) {
|
|
||||||
HashMap<String, String> payloadHashMap = (HashMap<String, String>) dataChannelMessage.getPayload();
|
|
||||||
EventBus.getDefault().post(new PeerConnectionEvent(PeerConnectionEvent.PeerConnectionEventType
|
|
||||||
.NICK_CHANGE, sessionId, payloadHashMap.get("name"), null, videoStreamType));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if ("audioOn".equals(dataChannelMessage.getType())) {
|
|
||||||
EventBus.getDefault().post(new PeerConnectionEvent(PeerConnectionEvent.PeerConnectionEventType
|
|
||||||
.AUDIO_CHANGE, sessionId, null, TRUE, videoStreamType));
|
|
||||||
} else if ("audioOff".equals(dataChannelMessage.getType())) {
|
|
||||||
EventBus.getDefault().post(new PeerConnectionEvent(PeerConnectionEvent.PeerConnectionEventType
|
|
||||||
.AUDIO_CHANGE, sessionId, null, FALSE, videoStreamType));
|
|
||||||
} else if ("videoOn".equals(dataChannelMessage.getType())) {
|
|
||||||
EventBus.getDefault().post(new PeerConnectionEvent(PeerConnectionEvent.PeerConnectionEventType
|
|
||||||
.VIDEO_CHANGE, sessionId, null, TRUE, videoStreamType));
|
|
||||||
} else if ("videoOff".equals(dataChannelMessage.getType())) {
|
|
||||||
EventBus.getDefault().post(new PeerConnectionEvent(PeerConnectionEvent.PeerConnectionEventType
|
|
||||||
.VIDEO_CHANGE, sessionId, null, FALSE, videoStreamType));
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
Log.d(TAG, "Failed to parse data channel message");
|
Log.d(TAG, "Failed to parse data channel message");
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ("nickChanged".equals(dataChannelMessage.getType())) {
|
||||||
|
String nick = null;
|
||||||
|
if (dataChannelMessage.getPayload() instanceof String) {
|
||||||
|
nick = (String) dataChannelMessage.getPayload();
|
||||||
|
} else if (dataChannelMessage.getPayload() instanceof Map) {
|
||||||
|
Map<String, String> payloadMap = (Map<String, String>) dataChannelMessage.getPayload();
|
||||||
|
nick = payloadMap.get("name");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nick != null) {
|
||||||
|
dataChannelMessageNotifier.notifyNickChanged(nick);
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ("audioOn".equals(dataChannelMessage.getType())) {
|
||||||
|
dataChannelMessageNotifier.notifyAudioOn();
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ("audioOff".equals(dataChannelMessage.getType())) {
|
||||||
|
dataChannelMessageNotifier.notifyAudioOff();
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ("videoOn".equals(dataChannelMessage.getType())) {
|
||||||
|
dataChannelMessageNotifier.notifyVideoOn();
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ("videoOff".equals(dataChannelMessage.getType())) {
|
||||||
|
dataChannelMessageNotifier.notifyVideoOff();
|
||||||
|
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue