diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index b8d3c2fd..54d168ee 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -192,7 +192,7 @@ jobs: touch emulator.log chmod 777 emulator.log adb logcat io.antmedia:I >> emulator.log & - ./gradlew jacocoTestReport; EXIT_CODE=$?; exit $EXIT_CODE + ./gradlew jacocoTestReport; - name: Archive Test Report if: always() @@ -234,6 +234,7 @@ jobs: runs-on: ubuntu-latest if: always() steps: + - name: Checkout repository uses: actions/checkout@v2 - name: Delete runner diff --git a/webrtc-android-framework/build.gradle b/webrtc-android-framework/build.gradle index 550c0c13..8e095f84 100644 --- a/webrtc-android-framework/build.gradle +++ b/webrtc-android-framework/build.gradle @@ -11,7 +11,7 @@ android { compileSdkVersion 34 defaultConfig { - minSdkVersion 21 + minSdkVersion 24 targetSdkVersion 34 testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" @@ -74,6 +74,7 @@ task jacocoTestReport(type: JacocoReport, dependsOn: ['testDebugUnitTest','creat dependencies { api fileTree(include: ['*.jar'], dir: 'libs') + implementation("com.squareup.okhttp3:okhttp:4.12.0") implementation 'com.google.code.findbugs:jsr305:3.0.2' implementation 'androidx.annotation:annotation:1.5.0' testImplementation 'junit:junit:4.13.2' diff --git a/webrtc-android-framework/src/main/java/io/antmedia/webrtcandroidframework/api/DefaultConferenceWebRTCListener.java b/webrtc-android-framework/src/main/java/io/antmedia/webrtcandroidframework/api/DefaultConferenceWebRTCListener.java index e0dc4324..ded7c6fa 100644 --- a/webrtc-android-framework/src/main/java/io/antmedia/webrtcandroidframework/api/DefaultConferenceWebRTCListener.java +++ b/webrtc-android-framework/src/main/java/io/antmedia/webrtcandroidframework/api/DefaultConferenceWebRTCListener.java @@ -2,6 +2,7 @@ import org.webrtc.VideoTrack; +import io.antmedia.webrtcandroidframework.core.WebRTCClient; import io.antmedia.webrtcandroidframework.websocket.Broadcast; /** @@ -84,8 +85,8 @@ public void onPlayFinished(String streamId) { } @Override - public void onReconnectionAttempt(String streamId) { - super.onReconnectionAttempt(streamId); + public void onReconnectionAttempt(String streamId, WebRTCClient.Mode mode) { + super.onReconnectionAttempt(streamId, mode); if(streamId.equals(this.streamId)) { publishReconnecting = true; } diff --git a/webrtc-android-framework/src/main/java/io/antmedia/webrtcandroidframework/api/DefaultWebRTCListener.java b/webrtc-android-framework/src/main/java/io/antmedia/webrtcandroidframework/api/DefaultWebRTCListener.java index 4feca6a7..5bfe44c0 100644 --- a/webrtc-android-framework/src/main/java/io/antmedia/webrtcandroidframework/api/DefaultWebRTCListener.java +++ b/webrtc-android-framework/src/main/java/io/antmedia/webrtcandroidframework/api/DefaultWebRTCListener.java @@ -7,8 +7,8 @@ import java.util.ArrayList; -import de.tavendo.autobahn.WebSocket; import io.antmedia.webrtcandroidframework.core.StreamInfo; +import io.antmedia.webrtcandroidframework.core.WebRTCClient; import io.antmedia.webrtcandroidframework.websocket.Broadcast; import io.antmedia.webrtcandroidframework.websocket.Subscriber; @@ -78,11 +78,6 @@ public void onError(String description, String streamId) { callbackCalled(messageText); } - @Override - public void onSignalChannelClosed(WebSocket.WebSocketConnectionObserver.WebSocketCloseNotification code, String streamId) { - String messageText = "Signal channel closed for " + streamId + " : " + code; - callbackCalled(messageText); - } @Override public void streamIdInUse(String streamId) { @@ -158,7 +153,7 @@ public void onVideoTrackEnded(VideoTrack track) { } @Override - public void onReconnectionAttempt(String streamId) { + public void onReconnectionAttempt(String streamId, WebRTCClient.Mode mode) { String messageText = "Reconnection attempt for " + streamId; callbackCalled(messageText); } @@ -280,7 +275,7 @@ public void onShutdown(){ protected void callbackCalled(String messageText) { Log.d(DefaultWebRTCListener.class.getName(), messageText); } - + @Override public void onSubscriberCount(String streamId, int count) { String messageText = "On Subscriber Count "+streamId; @@ -293,4 +288,7 @@ public void onSubscriberList(String streamId, Subscriber[] subscribers) { callbackCalled(messageText); } -} \ No newline at end of file + +} + + diff --git a/webrtc-android-framework/src/main/java/io/antmedia/webrtcandroidframework/api/IWebRTCClient.java b/webrtc-android-framework/src/main/java/io/antmedia/webrtcandroidframework/api/IWebRTCClient.java index 9ac35367..03f0d1d1 100644 --- a/webrtc-android-framework/src/main/java/io/antmedia/webrtcandroidframework/api/IWebRTCClient.java +++ b/webrtc-android-framework/src/main/java/io/antmedia/webrtcandroidframework/api/IWebRTCClient.java @@ -270,6 +270,12 @@ void publish(String streamId, String token, boolean videoCallEnabled, boolean au */ boolean isReconnectionInProgress(); + boolean isPlayConnected(); + + boolean isPlayReconnecting(); + + boolean isPublishReconnecting(); + /** * Get the error * diff --git a/webrtc-android-framework/src/main/java/io/antmedia/webrtcandroidframework/api/IWebRTCListener.java b/webrtc-android-framework/src/main/java/io/antmedia/webrtcandroidframework/api/IWebRTCListener.java index 6c6791b4..6e9ec00d 100644 --- a/webrtc-android-framework/src/main/java/io/antmedia/webrtcandroidframework/api/IWebRTCListener.java +++ b/webrtc-android-framework/src/main/java/io/antmedia/webrtcandroidframework/api/IWebRTCListener.java @@ -4,8 +4,8 @@ import java.util.ArrayList; -import de.tavendo.autobahn.WebSocket; import io.antmedia.webrtcandroidframework.core.StreamInfo; +import io.antmedia.webrtcandroidframework.core.WebRTCClient; import io.antmedia.webrtcandroidframework.websocket.Broadcast; import io.antmedia.webrtcandroidframework.websocket.Subscriber; @@ -61,8 +61,6 @@ public interface IWebRTCListener { void onError(String description, String streamId); - void onSignalChannelClosed(WebSocket.WebSocketConnectionObserver.WebSocketCloseNotification code, String streamId); - /** * It's called if client tried to stream with a stream id that is currently used in another stream. * @@ -138,7 +136,7 @@ public interface IWebRTCListener { * @param streamId * It's called when reconnection attempt is started */ - void onReconnectionAttempt(String streamId); + void onReconnectionAttempt(String streamId, WebRTCClient.Mode ClientType); /** * It's called when joiened the room @@ -264,3 +262,4 @@ public interface IWebRTCListener { */ void onSubscriberList(String streamId, Subscriber[] subscribers); } + diff --git a/webrtc-android-framework/src/main/java/io/antmedia/webrtcandroidframework/core/DataChannelConstants.java b/webrtc-android-framework/src/main/java/io/antmedia/webrtcandroidframework/core/DataChannelConstants.java index 05d9f25c..00f16daa 100644 --- a/webrtc-android-framework/src/main/java/io/antmedia/webrtcandroidframework/core/DataChannelConstants.java +++ b/webrtc-android-framework/src/main/java/io/antmedia/webrtcandroidframework/core/DataChannelConstants.java @@ -3,6 +3,7 @@ public class DataChannelConstants { public static final String VIDEO_TRACK_ASSIGNMENT_LIST = "VIDEO_TRACK_ASSIGNMENT_LIST"; + public static final String TRACK_LIST_UPDATED = "TRACK_LIST_UPDATED"; public static final String PAYLOAD = "payload"; public static final String TRACK_ID = "trackId"; public static final String VIDEO_LABEL = "videoLabel"; diff --git a/webrtc-android-framework/src/main/java/io/antmedia/webrtcandroidframework/core/WebRTCClient.java b/webrtc-android-framework/src/main/java/io/antmedia/webrtcandroidframework/core/WebRTCClient.java index 8e6c929f..0c3ea814 100644 --- a/webrtc-android-framework/src/main/java/io/antmedia/webrtcandroidframework/core/WebRTCClient.java +++ b/webrtc-android-framework/src/main/java/io/antmedia/webrtcandroidframework/core/WebRTCClient.java @@ -16,7 +16,6 @@ import android.util.DisplayMetrics; import android.util.Log; import android.view.WindowManager; -import android.widget.GridLayout; import android.widget.Toast; import androidx.annotation.NonNull; @@ -114,7 +113,8 @@ public enum Mode { private String errorString = null; private boolean streamStoppedByUser = false; - private boolean reconnectionInProgress = false; + private boolean publishReconnectionInProgress = false; + private boolean playReconnectionInProgress = false; private boolean autoPlayTracks = false; private boolean waitingForPlay = false; @@ -264,6 +264,9 @@ public void createReconnectorRunnables() { if(released || streamStoppedByUser){ return; } + if(wsHandler.pingPongExecutor == null){ + wsHandler.startPingPongTimer(); + } publishReconnectionHandler.postDelayed(publishReconnectorRunnable, PEER_RECONNECTION_RETRY_DELAY_MS); for (PeerInfo peerInfo : peers.values()) { @@ -283,7 +286,7 @@ public void createReconnectorRunnables() { */ } - config.webRTCListener.onReconnectionAttempt(peerInfo.id); + config.webRTCListener.onReconnectionAttempt(peerInfo.id,peerInfo.mode); if (peerInfo.mode.equals(Mode.PUBLISH)) { Log.d(TAG, "Reconnect attempt for publish"); @@ -300,7 +303,9 @@ public void createReconnectorRunnables() { if(released || streamStoppedByUser){ return; } - releaseRemoteRenderers(); + if(wsHandler.pingPongExecutor == null){ + wsHandler.startPingPongTimer(); + } playReconnectionHandler.postDelayed(playReconnectorRunnable, PEER_RECONNECTION_RETRY_DELAY_MS); @@ -312,6 +317,7 @@ public void createReconnectorRunnables() { && pc.iceConnectionState() != PeerConnection.IceConnectionState.COMPLETED)) { + releaseRemoteRenderers(); if (pc != null) { pc.close(); /* @@ -321,7 +327,7 @@ public void createReconnectorRunnables() { */ } - config.webRTCListener.onReconnectionAttempt(peerInfo.id); + config.webRTCListener.onReconnectionAttempt(peerInfo.id,peerInfo.mode); if (peerInfo.mode.equals(Mode.PLAY)) { Log.d(TAG, "Reconnect attempt for play"); @@ -341,6 +347,10 @@ public void createReconnectorRunnables() { if(released || streamStoppedByUser){ return; } + if(wsHandler.pingPongExecutor == null){ + wsHandler.startPingPongTimer(); + } + peerReconnectionHandler.postDelayed(peerReconnectorRunnable, PEER_RECONNECTION_RETRY_DELAY_MS); for (PeerInfo peerInfo : peers.values()) { @@ -360,7 +370,7 @@ public void createReconnectorRunnables() { */ } - config.webRTCListener.onReconnectionAttempt(peerInfo.id); + config.webRTCListener.onReconnectionAttempt(peerInfo.id,peerInfo.mode); if (peerInfo.mode.equals(Mode.PUBLISH)) { Log.d(TAG, "Reconnect attempt for publish"); @@ -521,7 +531,7 @@ public void onIceConnectionChange(final PeerConnection.IceConnectionState newSta } else if (newState == PeerConnection.IceConnectionState.DISCONNECTED || newState == PeerConnection.IceConnectionState.CLOSED) { onIceDisconnected(streamId); } else if (newState == PeerConnection.IceConnectionState.FAILED) { - onIceFailed(streamId); + onIceDisconnected(streamId); } }); } @@ -536,6 +546,7 @@ public void onConnectionChange(final PeerConnection.PeerConnectionState newState onDisconnected(); } else if (newState == PeerConnection.PeerConnectionState.FAILED) { reportError(streamId, "DTLS connection failed."); + onDisconnected(); } }); } @@ -975,20 +986,22 @@ public void onWebSocketConnected() { } private void publishPlayIfRequested() { - if(wsHandler == null){ - return; - } - for (Map.Entry entry : peers.entrySet()) { - PeerInfo peerInfo = entry.getValue(); - Mode peerMode = peerInfo.mode; - if (peerMode == Mode.PUBLISH && peerInfo.peerConnection == null) { - Log.i(TAG, "Processing publish request for peer streamId: " + peerInfo.id); - wsHandler.startPublish(peerInfo.id, peerInfo.token, peerInfo.videoCallEnabled, peerInfo.audioCallEnabled, peerInfo.subscriberId, peerInfo.subscriberCode, peerInfo.streamName, peerInfo.mainTrackId); + synchronized (this) { + if (wsHandler == null) { + return; } - - if (peerMode == Mode.PLAY && peerInfo.peerConnection == null) { + for (Map.Entry entry : peers.entrySet()) { + PeerInfo peerInfo = entry.getValue(); + Mode peerMode = peerInfo.mode; + if (!publishReconnectionInProgress && peerMode == Mode.PUBLISH && peerInfo.peerConnection == null) { + Log.i(TAG, "Processing publish request for peer streamId: " + peerInfo.id); + wsHandler.startPublish(peerInfo.id, peerInfo.token, peerInfo.videoCallEnabled, peerInfo.audioCallEnabled, peerInfo.subscriberId, peerInfo.subscriberCode, peerInfo.streamName, peerInfo.mainTrackId); + } + if (!playReconnectionInProgress && peerMode == Mode.PLAY && peerInfo.peerConnection == null) { Log.i(TAG, "Processing play request for peer streamId: " + peerInfo.id); wsHandler.startPlay(peerInfo.id, peerInfo.token, null, peerInfo.subscriberId, peerInfo.subscriberName, peerInfo.subscriberCode, peerInfo.metaData, peerInfo.disableTracksByDefault); + } + } } } @@ -1062,7 +1075,7 @@ public void play(PlayParams params) { }); createPeerInfo(params.getStreamId(), params.getToken(), false, false, params.getSubscriberId(), params.getSubscriberName(), params.getSubscriberCode(), "", "", params.getViewerInfo(), params.isDisableTracksByDefault(), Mode.PLAY); - if (!isReconnectionInProgress()) { + if (!isPlayReconnecting()) { init(); } @@ -1186,22 +1199,13 @@ public void release(boolean closeWebsocket) { releaseRenderer(config.localVideoRenderer, localVideoTrack, localVideoSink); } - for (SurfaceViewRenderer remoteVideoRenderer : config.remoteVideoRenderers) { - if (remoteVideoRenderer.getTag() != null) { - releaseRenderer(remoteVideoRenderer); - } - } + releaseRemoteRenderers(); localVideoTrack = null; localAudioTrack = null; remoteVideoSinks.clear(); - mainHandler.post(() -> { - //if closeInternal works before releasing renderer, app stucks - executor.execute(this::closeInternal); - }); - if (audioManager != null) { audioManager.stop(); @@ -1210,6 +1214,11 @@ public void release(boolean closeWebsocket) { config.webRTCListener.onShutdown(); + mainHandler.post(() -> { + //if closeInternal works before releasing renderer, app stucks + executor.execute(this::closeInternal); + }); + } public void releaseRenderer(SurfaceViewRenderer renderer, VideoTrack track, VideoSink sink) { @@ -1360,17 +1369,34 @@ public void onIceConnected(String streamId) { } public void rePublishPlay() { - if (streamStoppedByUser || reconnectionInProgress) { - return; - } - reconnectionInProgress = true; + synchronized (this) { + if (streamStoppedByUser) { + return; + } - if(isConference()){ - Log.i(TAG, "Conference! Will try to republish in " + PEER_RECONNECTION_DELAY_MS + " ms."); - publishReconnectionHandler.postDelayed(publishReconnectorRunnable, PEER_RECONNECTION_DELAY_MS); - }else{ - Log.i(TAG, "Peer was connected before. Will try to republish/replay in " + PEER_RECONNECTION_DELAY_MS + " ms."); - peerReconnectionHandler.postDelayed(peerReconnectorRunnable, PEER_RECONNECTION_DELAY_MS); + if (isConference()) { + if(isPublishConnected() && isPlayConnected()){ + onConnected(getPublishStreamId()); + Log.i(TAG, "------ Connected Automatically ----"); + return; + } + Log.i(TAG, "Conference! Will try to republish in " + PEER_RECONNECTION_DELAY_MS + " ms."); + Log.i(TAG,"publish connected :"+ isPublishConnected() +"play connected" +isPlayConnected()); + if (!isPublishConnected() && !publishReconnectionInProgress) { + publishReconnectionInProgress = true; + publishReconnectionHandler.postDelayed(publishReconnectorRunnable, PEER_RECONNECTION_DELAY_MS); + Log.d(TAG, "------------------------------------- Publish Reconnection --------------------------------------"); + } + if (!isPlayConnected() && !playReconnectionInProgress) { + playReconnectionInProgress = true; + playReconnectionHandler.postDelayed(playReconnectorRunnable, PEER_RECONNECTION_DELAY_MS); + Log.d(TAG, "------------------------------------- Play Reconnection --------------------------------------"); + } + } else { + Log.i(TAG, "Peer was connected before. Will try to republish/replay in " + PEER_RECONNECTION_DELAY_MS + " ms."); + publishReconnectionInProgress = true; + peerReconnectionHandler.postDelayed(peerReconnectorRunnable, PEER_RECONNECTION_DELAY_MS); + } } } @@ -1379,23 +1405,18 @@ public void onIceDisconnected(String streamId) { this.handler.post(() -> { Log.d(TAG, "ICE disconnected"); - if (config.webRTCListener != null) { + if (config.webRTCListener != null) { config.webRTCListener.onIceDisconnected(streamId); } - if (streamStoppedByUser) { - release(true); - return; - } - - if (config.reconnectionEnabled) { - rePublishPlay(); - } - - if (isConference()) { - releaseRemoteRenderers(); - } + if (streamStoppedByUser) { + release(true); + return; + } + if (config.reconnectionEnabled) { + rePublishPlay(); + } }); } @@ -1403,6 +1424,10 @@ private boolean isConference(){ return roomId != null; } + public void setRoomId(String roomId) { + this.roomId = roomId; + } + private boolean isAllPeersConnected() { for (Map.Entry entry : peers.entrySet()) { PeerConnection peerConnection = entry.getValue().peerConnection; @@ -1431,21 +1456,21 @@ public void onIceFailed(String streamId) { }); } - private boolean isPublishConnected(){ + public boolean isPublishConnected(){ for (Map.Entry entry : peers.entrySet()) { PeerConnection peerConnection = entry.getValue().peerConnection; if(peerConnection == null){ return false; } - PeerConnection.PeerConnectionState peerConnectionState = peerConnection.connectionState(); - if(entry.getValue().mode == Mode.PUBLISH && peerConnectionState != PeerConnection.PeerConnectionState.CONNECTED){ + PeerConnection.IceConnectionState peerConnectionState = peerConnection.iceConnectionState(); + if(entry.getValue().mode == Mode.PUBLISH && peerConnectionState != PeerConnection.IceConnectionState.CONNECTED){ return false; } } return true; } - private boolean isPlayConnected(){ + public boolean isPlayConnected(){ for (Map.Entry entry : peers.entrySet()) { PeerConnection peerConnection = entry.getValue().peerConnection; if(peerConnection == null){ @@ -1459,22 +1484,48 @@ private boolean isPlayConnected(){ return true; } + @Override + public boolean isPlayReconnecting() { + return playReconnectionInProgress; + } + @Override + public boolean isPublishReconnecting() { + return publishReconnectionInProgress; + } + + public void onConnected(String streamId) { Log.i(TAG, "Connected for streamId:" + streamId); - if(config.reconnectionEnabled && reconnectionInProgress && isConference() && isPublishConnected() && !isPlayConnected()){ - Log.i(TAG,"Conference reconnection. Publish connected. Play not connected. Try to reconnect play."); - publishReconnectionHandler.removeCallbacksAndMessages(null); - playReconnectionHandler.postDelayed(playReconnectorRunnable, PEER_RECONNECTION_DELAY_MS); + if(isConference() && config.reconnectionEnabled){ + if(isPublishConnected()){ + Log.i(TAG,"Publish reconnected"); + publishReconnectionHandler.removeCallbacksAndMessages(null); + publishReconnectionInProgress = false; + } + if(isPlayConnected()){ + Log.i(TAG,"Play reconnected"); + playReconnectionHandler.removeCallbacksAndMessages(null); + playReconnectionInProgress = false; + } + if(isPlayConnected() && isPublishConnected()){ + this.handler.post(() -> { + if (config.webRTCListener != null) { + config.webRTCListener.onReconnectionSuccess(); + } + }); + } + streamStoppedByUser = false; return; } - if (config.reconnectionEnabled && reconnectionInProgress && isAllPeersConnected()) { + if (config.reconnectionEnabled && isAllPeersConnected()) { Log.i(TAG, "All peers reconnected. Reconnection completed successfully."); - reconnectionInProgress = false; peerReconnectionHandler.removeCallbacksAndMessages(null); publishReconnectionHandler.removeCallbacksAndMessages(null); playReconnectionHandler.removeCallbacksAndMessages(null); + playReconnectionInProgress = false; + publishReconnectionInProgress = false; this.handler.post(() -> { if (config.webRTCListener != null) { config.webRTCListener.onReconnectionSuccess(); @@ -1526,6 +1577,9 @@ public void onTakeConfiguration(String streamId, SessionDescription sdp) { @Override public void onPublishFinished(String streamId) { + if(!isStreamStoppedByUser()){ + rePublishPlay(); + } this.handler.post(() -> { if (config.webRTCListener != null) { config.webRTCListener.onPublishFinished(streamId); @@ -1537,6 +1591,9 @@ public void onPublishFinished(String streamId) { @Override public void onPlayFinished(String streamId) { waitingForPlay = false; + if(!isStreamStoppedByUser()){ + rePublishPlay(); + } this.handler.post(() -> { if (config.webRTCListener != null) { config.webRTCListener.onPlayFinished(streamId); @@ -1548,6 +1605,7 @@ public void onPlayFinished(String streamId) { public void onPublishStarted(String streamId) { Log.d(TAG,"Publish started."); streamStoppedByUser = false; + publishReconnectionInProgress = false; this.handler.post(() -> { if (config.webRTCListener != null) { @@ -1562,7 +1620,7 @@ public void onPlayStarted(String streamId) { Log.d(TAG, "Play started."); streamStoppedByUser = false; - reconnectionInProgress = false; + playReconnectionInProgress = false; waitingForPlay = false; this.handler.post(() -> { @@ -1608,6 +1666,7 @@ public void onLeftTheRoom(String roomId) { config.webRTCListener.onLeftTheRoom(roomId); } + @Override public void onSessionRestored(String streamId) { streamStoppedByUser = false; @@ -2218,7 +2277,8 @@ public void closeInternal() { onPeerConnectionClosed(); clearStatsCollector(); - reconnectionInProgress = false; + playReconnectionInProgress = false; + publishReconnectionInProgress = false; peerReconnectionHandler.removeCallbacksAndMessages(null); publishReconnectionHandler.removeCallbacksAndMessages(null); playReconnectionHandler.removeCallbacksAndMessages(null); @@ -2807,7 +2867,7 @@ public void setAutoPlayTracks(boolean autoPlayTracks) { } public boolean isReconnectionInProgress() { - return reconnectionInProgress; + return publishReconnectionInProgress || playReconnectionInProgress; } public CustomWebRtcAudioRecord getAudioInput() { diff --git a/webrtc-android-framework/src/main/java/io/antmedia/webrtcandroidframework/websocket/WebSocketHandler.java b/webrtc-android-framework/src/main/java/io/antmedia/webrtcandroidframework/websocket/WebSocketHandler.java index 86405df6..bb252b7c 100644 --- a/webrtc-android-framework/src/main/java/io/antmedia/webrtcandroidframework/websocket/WebSocketHandler.java +++ b/webrtc-android-framework/src/main/java/io/antmedia/webrtcandroidframework/websocket/WebSocketHandler.java @@ -10,30 +10,35 @@ import org.webrtc.IceCandidate; import org.webrtc.SessionDescription; -import java.net.URI; -import java.net.URISyntaxException; import java.util.ArrayList; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; -import de.tavendo.autobahn.WebSocket; -import de.tavendo.autobahn.WebSocketConnection; -import de.tavendo.autobahn.WebSocketException; import io.antmedia.webrtcandroidframework.core.StreamInfo; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; +import okhttp3.WebSocket; +import okhttp3.WebSocketListener; import static io.antmedia.webrtcandroidframework.websocket.WebSocketConstants.DEFINITION; import static io.antmedia.webrtcandroidframework.websocket.WebSocketConstants.NOTIFICATION_COMMAND; import static io.antmedia.webrtcandroidframework.websocket.WebSocketConstants.WEBSOCKET_CONNECTION_TIMEOUT; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + import com.google.gson.Gson; import com.google.gson.GsonBuilder; -public class WebSocketHandler implements WebSocket.WebSocketConnectionObserver { +public class WebSocketHandler extends WebSocketListener{ private static final String TAG = "WebSocketHandler"; private static final int CLOSE_TIMEOUT = 1000; - private WebSocketConnection ws; + public boolean isWsOpen = false; + + private WebSocket ws; private final Handler handler; private String wsServerUrl; private final Object closeEventLock = new Object(); @@ -50,6 +55,7 @@ public class WebSocketHandler implements WebSocket.WebSocketConnectionObserver { private Handler wsReconnectionHandler = new Handler(); + OkHttpClient client = new OkHttpClient(); Gson gson; @@ -62,43 +68,25 @@ public WebSocketHandler(AntMediaSignallingEvents signallingListener, Handler han gson = builder.create(); } - public WebSocketConnection creteWebSocket(){ - return new WebSocketConnection(); + public WebSocket connectWebSocket(String wsServerUrl){ + if (isWsOpen) { + disconnect(true); + } + Request request = new Request.Builder().url(wsServerUrl).build(); + return client.newWebSocket(request, this); } public void connect(final String wsUrl) { - checkIfCalledOnValidThread(); - if(wsUrl==null || wsUrl.isBlank()) - return; - wsServerUrl = wsUrl; - - ws = creteWebSocket(); - //Thread connectorThread = new Thread(() -> { - try { - ws.connect(new URI(wsServerUrl), this); - } catch (WebSocketException e) { - e.printStackTrace(); - disconnect(false); - } catch (URISyntaxException e) { - e.printStackTrace(); - disconnect(false); - } - //}); - /* - connectorThread.start(); - handler.postDelayed(new Runnable() { - public void run() { - if (connectorThread.isAlive()) { - connectorThread.interrupt(); - Log.e(TAG, "exception occurred while waiting for websocket"); - } - } - },WEBSOCKET_CONNECTION_TIMEOUT); - */ + synchronized (this) { + if (wsUrl == null || wsUrl.isBlank()) + return; + wsServerUrl = wsUrl; + ws = connectWebSocket(wsServerUrl); + } } public void sendTextMessage(String message) { - if (ws.isConnected()) { - ws.sendTextMessage(message); + if (isConnected()) { + ws.send(message); Log.e(TAG, "sent websocket message:" + message); } else { Log.d(TAG, "Web Socket is not connected"); @@ -106,9 +94,9 @@ public void sendTextMessage(String message) { } public void disconnect(boolean waitForComplete) { - checkIfCalledOnValidThread(); Log.d(TAG, "Disconnect WebSocket."); - ws.disconnect(); + stopPingPongTimer(); + ws.close(1000, "Disconnecting WebSocket"); // Wait for websocket close event to prevent websocket library from // sending any pending messages to deleted looper thread. if (waitForComplete) { @@ -134,24 +122,35 @@ public void checkIfCalledOnValidThread() { } @Override - public void onOpen() { - Log.d(TAG, "WebSocket connection opened."); - signallingListener.onWebSocketConnected(); + public void onOpen(@NonNull WebSocket webSocket, @NonNull Response response) { + synchronized (this) { + Log.d(TAG, "WebSocket connection opened."); + isWsOpen = true; + startPingPongTimer(); + handler.post(() -> { + signallingListener.onWebSocketConnected(); + }); + } } @Override - public void onClose(WebSocketCloseNotification webSocketCloseNotification, String s) { - Log.d(TAG, "WebSocket connection closed."); - signallingListener.onWebSocketDisconnected(); - synchronized (closeEventLock) { - closeEvent = true; - closeEventLock.notify(); + public void onClosed(@NonNull WebSocket webSocket, int code, @NonNull String reason) { + synchronized (this) { + isWsOpen = false; stopPingPongTimer(); + handler.post(() -> { + Log.d(TAG, "WebSocket connection closed."); + signallingListener.onWebSocketDisconnected(); + synchronized (closeEventLock) { + closeEvent = true; + closeEventLock.notify(); + } + }); } } @Override - public void onTextMessage(String msg) { + public void onMessage(@NonNull WebSocket webSocket, @NonNull String msg) { Log.e(TAG, "onTextMessage: "+msg); if (!isConnected()) { Log.e(TAG, "Got WebSocket message in non registered state."); @@ -218,11 +217,9 @@ else if (commandText.equals(NOTIFICATION_COMMAND)) { Log.d(TAG, "notification: " + definition); if (definition.equals(WebSocketConstants.PUBLISH_STARTED)) { signallingListener.onPublishStarted(streamId); - startPingPongTimer(); } else if (definition.equals(WebSocketConstants.PUBLISH_FINISHED)) { signallingListener.onPublishFinished(streamId); - stopPingPongTimer(); } else if (definition.equals(WebSocketConstants.PLAY_STARTED)) { signallingListener.onPlayStarted(streamId); @@ -312,7 +309,6 @@ else if (commandText.equals(WebSocketConstants.ERROR_COMMAND)) String definition= json.getString(DEFINITION); Log.d(TAG, "error command received: "+ definition); - //stopPingPongTimer(); signallingListener.onError(streamId, definition); @@ -344,12 +340,19 @@ else if (commandText.equals(WebSocketConstants.PONG_COMMAND)) } @Override - public void onRawTextMessage(byte[] bytes) { + public void onFailure(@NonNull WebSocket webSocket, @NonNull Throwable t, @Nullable Response response) { - } - - @Override - public void onBinaryMessage(byte[] bytes) { + synchronized (this) { + isWsOpen = false; + stopPingPongTimer(); + handler.post(() -> { + signallingListener.onWebSocketDisconnected(); + System.out.println("⚠️ WebSocket failure: " + t.getClass().getName() + " - " + t.getMessage()); + if (response != null) { + System.out.println("HTTP code: " + response.code() + " / message: " + response.message()); + } + }); + } } @@ -380,7 +383,6 @@ public void getSubscriberList(String streamId, long offset, long size) { } public void startPublish(String streamId, String token, boolean videoEnabled, boolean audioEnabled, String subscriberId, String subscriberCode, String streamName, String mainTrackId){ - checkIfCalledOnValidThread(); JSONObject json = new JSONObject(); try { json.put(WebSocketConstants.COMMAND, WebSocketConstants.PUBLISH_COMMAND); @@ -545,16 +547,15 @@ public void enableTrack(String streamId, String trackId, boolean enabled) { public void startPingPongTimer(){ - Log.d(TAG, "Ping Pong timer is started"); - Runnable timerTask = new Runnable() { @Override public void run() { Log.d(TAG, "Ping Pong timer is executed"); sendPingPongMessage(); - if (pingPongTimoutCount == 2){ + if (pingPongTimoutCount >= 3) { Log.d(TAG, "Ping Pong websocket response not received for 4 seconds"); stopPingPongTimer(); + isWsOpen = false; disconnect(true); } pingPongTimoutCount++; @@ -562,19 +563,27 @@ public void run() { } }; - pingPongExecutor = Executors.newSingleThreadScheduledExecutor(); - pingPongExecutor.scheduleAtFixedRate(timerTask, TIMER_DELAY, TIMER_PERIOD, TimeUnit.MILLISECONDS); + synchronized (this) { + if(this.pingPongExecutor != null) { + return; + } + + pingPongExecutor = Executors.newSingleThreadScheduledExecutor(); + Log.d(TAG, "Ping Pong timer is started"); + pingPongExecutor.scheduleAtFixedRate(timerTask, TIMER_DELAY, TIMER_PERIOD, TimeUnit.MILLISECONDS); + } } public void stopPingPongTimer(){ + synchronized (this) { + Log.d(TAG, "Ping Pong timer stop called"); - Log.d(TAG, "Ping Pong timer stop called"); - - if (pingPongExecutor != null) { - pingPongExecutor.shutdown(); - pingPongExecutor = null; - pingPongTimoutCount = 0; + if (pingPongExecutor != null) { + pingPongExecutor.shutdown(); + pingPongExecutor = null; + pingPongTimoutCount = 0; + } } } @@ -657,7 +666,7 @@ public AntMediaSignallingEvents getSignallingListener() { } public boolean isConnected() { - return ws !=null && ws.isConnected(); + return ws != null && isWsOpen; } public void forceStreamQuality(String mainTrackStreamId, String subTrackStreamId, int height) { diff --git a/webrtc-android-framework/src/main/java/org/webrtc/audio/WebRtcAudioTrack.java b/webrtc-android-framework/src/main/java/org/webrtc/audio/WebRtcAudioTrack.java index 6b32e682..3e97b312 100644 --- a/webrtc-android-framework/src/main/java/org/webrtc/audio/WebRtcAudioTrack.java +++ b/webrtc-android-framework/src/main/java/org/webrtc/audio/WebRtcAudioTrack.java @@ -538,10 +538,12 @@ public static void setSpeakerMute(boolean mute) { // Releases the native AudioTrack resources. private void releaseAudioResources() { - Logging.d(TAG, "releaseAudioResources"); - if (audioTrack != null) { - audioTrack.release(); - audioTrack = null; + synchronized (this) { + Logging.d(TAG, "releaseAudioResources"); + if (audioTrack != null) { + audioTrack.release(); + audioTrack = null; + } } } diff --git a/webrtc-android-framework/src/test/java/io/antmedia/webrtcandroidframework/WebRTCClientTest.java b/webrtc-android-framework/src/test/java/io/antmedia/webrtcandroidframework/WebRTCClientTest.java index b59bc10b..cc7673d4 100644 --- a/webrtc-android-framework/src/test/java/io/antmedia/webrtcandroidframework/WebRTCClientTest.java +++ b/webrtc-android-framework/src/test/java/io/antmedia/webrtcandroidframework/WebRTCClientTest.java @@ -1,6 +1,7 @@ package io.antmedia.webrtcandroidframework; import static org.awaitility.Awaitility.await; +import static org.awaitility.Awaitility.reset; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; @@ -710,6 +711,9 @@ public void testReconnection() { verify(wsHandler, timeout(WebRTCClient.PEER_RECONNECTION_DELAY_MS + 1000).atLeast(1)).startPublish(anyString(),anyString(),anyBoolean(),anyBoolean(),anyString(),anyString(),anyString(),anyString()); + + + } @Test diff --git a/webrtc-android-framework/src/test/java/io/antmedia/webrtcandroidframework/WebSocketHandlerTest.java b/webrtc-android-framework/src/test/java/io/antmedia/webrtcandroidframework/WebSocketHandlerTest.java index 3ccccdea..15c192b9 100644 --- a/webrtc-android-framework/src/test/java/io/antmedia/webrtcandroidframework/WebSocketHandlerTest.java +++ b/webrtc-android-framework/src/test/java/io/antmedia/webrtcandroidframework/WebSocketHandlerTest.java @@ -20,11 +20,11 @@ import java.util.concurrent.Executors; import de.tavendo.autobahn.WebSocketConnection; -import de.tavendo.autobahn.WebSocketException; import io.antmedia.webrtcandroidframework.websocket.AntMediaSignallingEvents; import io.antmedia.webrtcandroidframework.websocket.Broadcast; import io.antmedia.webrtcandroidframework.websocket.WebSocketConstants; import io.antmedia.webrtcandroidframework.websocket.WebSocketHandler; +import okhttp3.WebSocket; import static org.junit.Assert.assertEquals; import static org.mockito.Mockito.*; @@ -41,7 +41,7 @@ public class WebSocketHandlerTest { private Handler handler; @Mock - private WebSocketConnection ws; + private WebSocket ws; @Mock private IceCandidate iceCandidate; @@ -63,7 +63,7 @@ public void testOnTextMessageStartCommand() { String message = "{\"command\": \"start\", \"streamId\": \"stream123\"}"; doReturn(true).when(webSocketHandler).isConnected(); - webSocketHandler.onTextMessage(message); + webSocketHandler.onMessage(ws,message); verify(signallingListener).onStartStreaming("stream123"); @@ -87,7 +87,7 @@ public void testOnTextMessageTakeConfigurationCommand() { } String message = json.toString(); - webSocketHandler.onTextMessage(message); + webSocketHandler.onMessage(ws,message); verify(signallingListener).onTakeConfiguration(eq(streamId), any(SessionDescription.class)); } @@ -111,7 +111,7 @@ public void testOnTextMessageTakeCandidateCommand() { String message = json.toString(); - webSocketHandler.onTextMessage(message); + webSocketHandler.onMessage(ws,message); verify(signallingListener).onRemoteIceCandidate(eq(streamId), any(IceCandidate.class)); @@ -131,7 +131,7 @@ public void testOnTextMessageLeavedTheRoomNotification() { doReturn(true).when(webSocketHandler).isConnected(); String message = json.toString(); - webSocketHandler.onTextMessage(message); + webSocketHandler.onMessage(ws,message); verify(signallingListener).onLeftTheRoom(null); } @@ -447,7 +447,7 @@ public void testOnBroadcastObjectNotification() { String message = json.toString(); - webSocketHandler.onTextMessage(message); + webSocketHandler.onMessage(ws,message); ArgumentCaptor captor = ArgumentCaptor.forClass(Broadcast.class); verify(signallingListener).onBroadcastObject(captor.capture()); @@ -460,12 +460,10 @@ public void testOnBroadcastObjectNotification() { } @Test - public void testWsConnect() throws InterruptedException, URISyntaxException, WebSocketException { + public void testWsConnect() throws InterruptedException, URISyntaxException { String url = "wss://test.antmedia.io:5443/LiveApp/websocket"; - doReturn(ws).when(webSocketHandler).creteWebSocket(); webSocketHandler.connect(url); - Thread.sleep(3000); - verify(ws,times(1)).connect(new URI(url),webSocketHandler); + verify(webSocketHandler,times(1)).connectWebSocket(url); } @Test diff --git a/webrtc-android-framework/src/test/java/io/antmedia/webrtcandroidframework/api/DefaultConferenceWebRTCListenerTest.java b/webrtc-android-framework/src/test/java/io/antmedia/webrtcandroidframework/api/DefaultConferenceWebRTCListenerTest.java index d6642379..d2b74a85 100644 --- a/webrtc-android-framework/src/test/java/io/antmedia/webrtcandroidframework/api/DefaultConferenceWebRTCListenerTest.java +++ b/webrtc-android-framework/src/test/java/io/antmedia/webrtcandroidframework/api/DefaultConferenceWebRTCListenerTest.java @@ -19,7 +19,8 @@ import java.util.ArrayList; -import de.tavendo.autobahn.WebSocket; +import io.antmedia.webrtcandroidframework.core.WebRTCClient; + public class DefaultConferenceWebRTCListenerTest{ private String roomId; @@ -82,11 +83,6 @@ public void testOnError() { verify(defaultWebRTCListener, times(1)).callbackCalled(anyString()); } - @Test - public void testOnSignalChannelClosed() { - defaultWebRTCListener.onSignalChannelClosed(WebSocket.WebSocketConnectionObserver.WebSocketCloseNotification.NORMAL, "streamId"); - verify(defaultWebRTCListener, times(1)).callbackCalled(anyString()); - } @Test public void testStreamIdInUse() { @@ -157,7 +153,7 @@ public void testOnVideoTrackEnded() { @Test public void testOnReconnectionAttempt() { - defaultWebRTCListener.onReconnectionAttempt("streamId"); + defaultWebRTCListener.onReconnectionAttempt("streamId", WebRTCClient.Mode.PUBLISH); verify(defaultWebRTCListener, times(1)).callbackCalled(anyString()); } @@ -199,16 +195,16 @@ public void testOnSatatusUpdateFor() { @Test public void testReconnecting() { - defaultWebRTCListener.onReconnectionAttempt(roomId); + defaultWebRTCListener.onReconnectionAttempt(roomId,WebRTCClient.Mode.PUBLISH); assertFalse(defaultWebRTCListener.isPublishReconnectingForTest()); - defaultWebRTCListener.onReconnectionAttempt(streamId); + defaultWebRTCListener.onReconnectionAttempt(streamId,WebRTCClient.Mode.PUBLISH); assertTrue(defaultWebRTCListener.isPublishReconnectingForTest()); defaultWebRTCListener.onPublishStarted(streamId); assertFalse(defaultWebRTCListener.isPublishReconnectingForTest()); - defaultWebRTCListener.onReconnectionAttempt(streamId); + defaultWebRTCListener.onReconnectionAttempt(streamId,WebRTCClient.Mode.PUBLISH); assertTrue(defaultWebRTCListener.isPublishReconnectingForTest()); defaultWebRTCListener.onSessionRestored(streamId); @@ -224,7 +220,7 @@ public void testStartAfterPublishStarted() { verify(mockWebRTCClient, times(1)).play(roomId); //playStarted false, but play should not be called because publish is reconnecting state - defaultWebRTCListener.onReconnectionAttempt(streamId); + defaultWebRTCListener.onReconnectionAttempt(streamId,WebRTCClient.Mode.PUBLISH); assertTrue(defaultWebRTCListener.isPublishReconnectingForTest()); defaultWebRTCListener.onPublishStarted(streamId); verify(mockWebRTCClient, times(2)).play(roomId); diff --git a/webrtc-android-framework/src/test/java/io/antmedia/webrtcandroidframework/api/DefaultWebRTCListenerTest.java b/webrtc-android-framework/src/test/java/io/antmedia/webrtcandroidframework/api/DefaultWebRTCListenerTest.java index c5360854..f1387981 100644 --- a/webrtc-android-framework/src/test/java/io/antmedia/webrtcandroidframework/api/DefaultWebRTCListenerTest.java +++ b/webrtc-android-framework/src/test/java/io/antmedia/webrtcandroidframework/api/DefaultWebRTCListenerTest.java @@ -7,7 +7,8 @@ import org.mockito.MockitoAnnotations; import org.webrtc.SurfaceViewRenderer; import org.webrtc.VideoTrack; -import de.tavendo.autobahn.WebSocket; + +import io.antmedia.webrtcandroidframework.core.WebRTCClient; import io.antmedia.webrtcandroidframework.websocket.Broadcast; import java.util.ArrayList; @@ -72,11 +73,6 @@ public void testOnError() { verify(defaultWebRTCListener, times(1)).callbackCalled(anyString()); } - @Test - public void testOnSignalChannelClosed() { - defaultWebRTCListener.onSignalChannelClosed(WebSocket.WebSocketConnectionObserver.WebSocketCloseNotification.NORMAL, "streamId"); - verify(defaultWebRTCListener, times(1)).callbackCalled(anyString()); - } @Test public void testStreamIdInUse() { @@ -147,7 +143,7 @@ public void testOnVideoTrackEnded() { @Test public void testOnReconnectionAttempt() { - defaultWebRTCListener.onReconnectionAttempt("streamId"); + defaultWebRTCListener.onReconnectionAttempt("streamId", WebRTCClient.Mode.PUBLISH); verify(defaultWebRTCListener, times(1)).callbackCalled(anyString()); } diff --git a/webrtc-android-sample-app/src/androidTest/java/io/antmedia/webrtc_android_sample_app/ConferenceActivityTest.java b/webrtc-android-sample-app/src/androidTest/java/io/antmedia/webrtc_android_sample_app/ConferenceActivityTest.java index 6b058d8d..f43a8494 100644 --- a/webrtc-android-sample-app/src/androidTest/java/io/antmedia/webrtc_android_sample_app/ConferenceActivityTest.java +++ b/webrtc-android-sample-app/src/androidTest/java/io/antmedia/webrtc_android_sample_app/ConferenceActivityTest.java @@ -184,7 +184,7 @@ public void testJoinWithExternalParticipant() throws InterruptedException { onView(withId(R.id.multitrack_stats_popup_play_stats_video_track_recyclerview)).inRoot(isDialog()).check(matches(isDisplayed())); - // Thread.sleep(5000); + Thread.sleep(5000); onView(withId(R.id.multitrack_stats_popup_play_stats_video_track_recyclerview)) .check((view, noViewFoundException) -> { @@ -200,7 +200,7 @@ public void testJoinWithExternalParticipant() throws InterruptedException { onView(withId(R.id. stats_popup_container)).perform(swipeUp()); - // Thread.sleep(3000); + Thread.sleep(3000); onView(withId(R.id.multitrack_stats_popup_close_button)).perform(click()); diff --git a/webrtc-android-sample-app/src/main/java/io/antmedia/webrtc_android_sample_app/basic/DynamicConferenceActivity.java b/webrtc-android-sample-app/src/main/java/io/antmedia/webrtc_android_sample_app/basic/DynamicConferenceActivity.java index 1c9f2646..d20a156d 100644 --- a/webrtc-android-sample-app/src/main/java/io/antmedia/webrtc_android_sample_app/basic/DynamicConferenceActivity.java +++ b/webrtc-android-sample-app/src/main/java/io/antmedia/webrtc_android_sample_app/basic/DynamicConferenceActivity.java @@ -26,6 +26,7 @@ import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; @@ -39,9 +40,11 @@ import io.antmedia.webrtcandroidframework.api.IDataChannelObserver; import io.antmedia.webrtcandroidframework.api.IWebRTCClient; import io.antmedia.webrtcandroidframework.core.DataChannelConstants; +import io.antmedia.webrtcandroidframework.core.WebRTCClient; import io.antmedia.webrtcandroidframework.core.model.PlayStats; import io.antmedia.webrtcandroidframework.core.model.TrackStats; import io.antmedia.webrtcandroidframework.core.PermissionHandler; +import io.antmedia.webrtcandroidframework.websocket.Broadcast; public class DynamicConferenceActivity extends TestableActivity { @@ -102,6 +105,8 @@ public class DynamicConferenceActivity extends TestableActivity { */ private HashMap streamIdVideoTrackMap = new HashMap<>(); + private List allParticipants = new ArrayList<>(); + @Override protected void onCreate(Bundle savedInstanceState) { @@ -222,10 +227,17 @@ public void textMessageReceived(String messageText) { super.textMessageReceived(messageText); try{ JSONObject msgJsonObj = new JSONObject(messageText); - if(msgJsonObj.has(DataChannelConstants.EVENT_TYPE) && msgJsonObj.getString(DataChannelConstants.EVENT_TYPE).equals(DataChannelConstants.VIDEO_TRACK_ASSIGNMENT_LIST)){ - trackAssignments = msgJsonObj.getJSONArray(DataChannelConstants.PAYLOAD); - matchStreamIdAndVideoTrack(); + if(msgJsonObj.has(DataChannelConstants.EVENT_TYPE)){ + String eventType = msgJsonObj.getString(DataChannelConstants.EVENT_TYPE); + if(eventType.equals(DataChannelConstants.VIDEO_TRACK_ASSIGNMENT_LIST)){ + trackAssignments = msgJsonObj.getJSONArray(DataChannelConstants.PAYLOAD); + matchStreamIdAndVideoTrack(); + } + else if(eventType.equals(DataChannelConstants.TRACK_LIST_UPDATED)){ + webRTCClient.getBroadcastObject(streamId); + } } + }catch (Exception e){ Log.e(getClass().getSimpleName(),"Cant parse data channel message to JSON object. "+e.getMessage()); } @@ -262,6 +274,16 @@ public void onPublishStarted(String streamId) { publishStarted = true; joinButton.setEnabled(true); } + @Override + public void onReconnectionAttempt(String streamId, WebRTCClient.Mode mode) { + if(mode == WebRTCClient.Mode.PLAY) + removeAllRenderers(); + } + + @Override + public void onBroadcastObject(Broadcast broadcast) { + + } @Override public void onShutdown() { @@ -287,7 +309,6 @@ public void onIceDisconnected(String streamId) { }else{ statusIndicatorTextView.setTextColor(getResources().getColor(R.color.red)); statusIndicatorTextView.setText(getResources().getString(R.string.disconnected)); - removeAllRenderers(); joinButton.setEnabled(true); joinButton.setImageDrawable(ContextCompat.getDrawable(getApplicationContext(), R.drawable.ic_join_call)); } @@ -335,10 +356,13 @@ public void onNewVideoTrack(VideoTrack track, String trackId) { runOnUiThread(() -> { SurfaceViewRenderer r = addSurfaceViewRenderer(); + r.setTag(R.id.accelerate,track.id()); + if (r.getTag() == null) { r.setTag(track); webRTCClient.setRendererForVideoTrack(r, track); } + r.setTag(R.id.accelerate,track.id()); videoTrackList.add(track); if(trackAssignments != null){ matchStreamIdAndVideoTrack(); @@ -365,7 +389,6 @@ public void toggleSendVideo() { if(webRTCClient.isSendVideoEnabled()){ webRTCClient.toggleSendVideo(false); toggleSendVideoButton.setImageDrawable(ContextCompat.getDrawable(getApplicationContext(), R.drawable.ic_camera_off)); - }else{ webRTCClient.toggleSendVideo(true); toggleSendVideoButton.setImageDrawable(ContextCompat.getDrawable(getApplicationContext(), R.drawable.ic_camera_on)); @@ -584,14 +607,13 @@ public void removeSurfaceViewRenderer(SurfaceViewRenderer renderer){ remoteParticipantsGridLayout.removeView(renderer); webRTCClient.getConfig().remoteVideoRenderers.remove(renderer); } + public void removeAllRenderers(){ ArrayList toRemove = new ArrayList<>(webRTCClient.getConfig().remoteVideoRenderers); for (SurfaceViewRenderer r : toRemove) { webRTCClient.releaseRenderer(r); - runOnUiThread(() -> { - remoteParticipantsGridLayout.removeView(r); - webRTCClient.getConfig().remoteVideoRenderers.remove(r); - }); + remoteParticipantsGridLayout.removeView(r); + webRTCClient.getConfig().remoteVideoRenderers.remove(r); } } }