package im.actor.core.modules.calls.peers; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import im.actor.core.api.ApiAdvertiseMaster; import im.actor.core.api.ApiAdvertiseSelf; import im.actor.core.api.ApiAnswer; import im.actor.core.api.ApiCandidate; import im.actor.core.api.ApiCloseSession; import im.actor.core.api.ApiEnableConnection; import im.actor.core.api.ApiMediaStreamsUpdated; import im.actor.core.api.ApiNeedDisconnect; import im.actor.core.api.ApiNeedOffer; import im.actor.core.api.ApiNegotinationSuccessful; import im.actor.core.api.ApiOffer; import im.actor.core.api.ApiOnRenegotiationNeeded; import im.actor.core.api.ApiWebRTCSignaling; import im.actor.core.modules.ModuleContext; import im.actor.core.modules.eventbus.EventBusActor; import im.actor.runtime.Log; import im.actor.runtime.actors.ActorRef; import im.actor.runtime.webrtc.WebRTCMediaStream; import im.actor.runtime.webrtc.WebRTCMediaTrack; /*-[ #pragma clang diagnostic ignored "-Wnullability-completeness" ]-*/ public class CallBusActor extends EventBusActor implements PeerCallCallback { private static final int STASH = 1; public static final long TIMEOUT = 18000; @NotNull private final PeerSettings selfSettings; @NotNull private final PeerCallCallback peerCallback; @NotNull private final CallBusCallback callBusCallback; private boolean isMasterReady; private long masterDeviceId; @Nullable private PeerCallInt peerCall; private boolean isConnected = false; private boolean isEnabled = false; public CallBusActor(@NotNull final CallBusCallback callBusCallback, @NotNull PeerSettings selfSettings, @NotNull ModuleContext context) { super(context); this.selfSettings = selfSettings; this.callBusCallback = callBusCallback; this.peerCallback = new CallbackWrapper(this); } @Override public void preStart() { super.preStart(); ActorRef ref = system().actorOf(getPath() + "/peer", () -> { return new PeerCallActor(peerCallback, CallBusActor.this.selfSettings, context()); }); this.peerCall = new PeerCallInt(ref); } @Override public void onBusStarted() { super.onBusStarted(); callBusCallback.onBusStarted(getBusId()); } // // PeerCall callback // @Override public void onOffer(long deviceId, long sessionId, @NotNull String sdp) { sendSignal(deviceId, new ApiOffer(sessionId, sdp, CallBusActor.this.selfSettings.toApi())); } @Override public void onAnswer(long deviceId, long sessionId, @NotNull String sdp) { sendSignal(deviceId, new ApiAnswer(sessionId, sdp)); } @Override public void onCandidate(long deviceId, long sessionId, int mdpIndex, @NotNull String id, @NotNull String sdp) { sendSignal(deviceId, new ApiCandidate(sessionId, mdpIndex, id, sdp)); } @Override public void onNegotiationSuccessful(final long deviceId, final long sessionId) { if (isMasterReady) { sendSignal(masterDeviceId, new ApiNegotinationSuccessful(deviceId, sessionId)); } else { stash(STASH); } } @Override public void onNegotiationNeeded(long deviceId, long sessionId) { if (isMasterReady) { sendSignal(masterDeviceId, new ApiOnRenegotiationNeeded(deviceId, sessionId)); } else { stash(STASH); } } @Override public void onMediaStreamsChanged(long deviceId, boolean isAudioEnabled, boolean isVideoEnabled) { sendSignal(deviceId, new ApiMediaStreamsUpdated(isAudioEnabled, isVideoEnabled)); } @Override public void onPeerStateChanged(long deviceId, @NotNull PeerState state) { if (state == PeerState.CONNECTED && !isConnected && !isEnabled) { isConnected = true; callBusCallback.onCallConnected(); } if (state == PeerState.ACTIVE && !isEnabled) { isEnabled = true; callBusCallback.onCallEnabled(); } } @Override public void onTrackAdded(long deviceId, WebRTCMediaTrack track) { callBusCallback.onTrackAdded(deviceId, track); } @Override public void onTrackRemoved(long deviceId, WebRTCMediaTrack track) { callBusCallback.onTrackRemoved(deviceId, track); } @Override public void onOwnTrackAdded(WebRTCMediaTrack track) { callBusCallback.onOwnTrackAdded(track); } @Override public void onOwnTrackRemoved(WebRTCMediaTrack track) { callBusCallback.onOwnTrackRemoved(track); } // // Actions // public void onChangeAudioEnabled(boolean isEnabled) { peerCall.onAudioEnabledChanged(isEnabled); } public void onChangeVideoEnabled(boolean isEnabled) { peerCall.onVideoEnabledChanged(isEnabled); } public void onOwnAnswered() { peerCall.onOwnStarted(); } // // Event Bus handler // @Override public void onDeviceConnected(int uid, long deviceId) { } @Override public void onDeviceDisconnected(int uid, long deviceId) { peerCall.disposePeer(deviceId); } @Override public final void onMessageReceived(@Nullable Integer senderId, @Nullable Long senderDeviceId, byte[] data) { if (senderId == null || senderDeviceId == null) { return; } ApiWebRTCSignaling signal; try { signal = ApiWebRTCSignaling.fromBytes(data); } catch (IOException e) { e.printStackTrace(); return; } Log.d("CallBusActor", "Message Received: " + signal); if (signal instanceof ApiAnswer) { ApiAnswer answer = (ApiAnswer) signal; peerCall.onAnswer(senderDeviceId, answer.getSessionId(), answer.getSdp()); } else if (signal instanceof ApiOffer) { ApiOffer offer = (ApiOffer) signal; peerCall.onAdvertised(senderDeviceId, new PeerSettings(offer.getOwnPeerSettings())); peerCall.onOffer(senderDeviceId, offer.getSessionId(), offer.getSdp()); } else if (signal instanceof ApiCandidate) { ApiCandidate candidate = (ApiCandidate) signal; peerCall.onCandidate(senderDeviceId, candidate.getSessionId(), candidate.getIndex(), candidate.getId(), candidate.getSdp()); } else if (signal instanceof ApiNeedOffer) { ApiNeedOffer needOffer = (ApiNeedOffer) signal; peerCall.onAdvertised(needOffer.getDevice(), new PeerSettings(needOffer.getPeerSettings())); peerCall.onOfferNeeded(needOffer.getDevice(), needOffer.getSessionId()); } else if (signal instanceof ApiNeedDisconnect) { ApiNeedDisconnect disconnect = (ApiNeedDisconnect) signal; peerCall.disposePeer(disconnect.getDevice()); } else if (signal instanceof ApiEnableConnection) { ApiEnableConnection connection = (ApiEnableConnection) signal; peerCall.onOwnStarted(); peerCall.onTheirStarted(connection.getDevice()); } else if (signal instanceof ApiCloseSession) { ApiCloseSession closeSession = (ApiCloseSession) signal; peerCall.closeSession(closeSession.getDevice(), closeSession.getSessionId()); } else if (signal instanceof ApiAdvertiseMaster) { ApiAdvertiseMaster advertiseMaster = (ApiAdvertiseMaster) signal; if (isMasterReady) { return; } isMasterReady = true; masterDeviceId = senderDeviceId; unstashAll(STASH); // // Advertise own settings to master device // sendSignal(masterDeviceId, new ApiAdvertiseSelf(selfSettings.toApi())); // // Sending Configuration to Peer Call // peerCall.onConfigurationReady(advertiseMaster.getServer()); } else if (signal instanceof ApiMediaStreamsUpdated) { ApiMediaStreamsUpdated streamsUpdated = (ApiMediaStreamsUpdated) signal; Boolean isAudioEnabled = streamsUpdated.isAudioEnabled(); if (isAudioEnabled == null) { isAudioEnabled = true; } Boolean isVideoEnabled = streamsUpdated.isVideoEnabled(); if (isVideoEnabled == null) { isVideoEnabled = true; } // Notify About Media State Changes peerCall.onMediaStateChanged(senderDeviceId, isAudioEnabled, isVideoEnabled); } } public final void sendSignal(long deviceId, @NotNull ApiWebRTCSignaling signal) { Log.d("CallBusActor", "Message Sent: " + signal); try { sendMessage(deviceId, signal.buildContainer()); } catch (IOException e) { e.printStackTrace(); } } @Override public void postStop() { super.postStop(); if (peerCall != null) { peerCall.kill(); peerCall = null; } callBusCallback.onBusStopped(); } @Override public void onReceive(Object message) { if (message instanceof JoinBus) { joinBus(((JoinBus) message).getBusId(), TIMEOUT); } else if (message instanceof JoinMasterBus) { JoinMasterBus joinMasterBus = (JoinMasterBus) message; connectBus(joinMasterBus.getBusId(), joinMasterBus.getDeviceId(), TIMEOUT, true); } else if (message instanceof AudioEnabled) { onChangeAudioEnabled(((AudioEnabled) message).isEnabled()); } else if (message instanceof VideoEnabled) { onChangeVideoEnabled(((VideoEnabled) message).isEnabled()); } else if (message instanceof OnAnswered) { onOwnAnswered(); } else { super.onReceive(message); } } public static class JoinBus { @NotNull private String busId; public JoinBus(@NotNull String busId) { this.busId = busId; } @NotNull public String getBusId() { return busId; } } public static class JoinMasterBus { @NotNull private String busId; private long deviceId; public JoinMasterBus(@NotNull String busId, long deviceId) { this.busId = busId; this.deviceId = deviceId; } @NotNull public String getBusId() { return busId; } public long getDeviceId() { return deviceId; } } public static class AudioEnabled { private boolean enabled; public AudioEnabled(boolean enabled) { this.enabled = enabled; } public boolean isEnabled() { return enabled; } } public static class VideoEnabled { private boolean enabled; public VideoEnabled(boolean enabled) { this.enabled = enabled; } public boolean isEnabled() { return enabled; } } public static class OnAnswered { } public class CallbackWrapper implements PeerCallCallback { @NotNull private final PeerCallCallback callCallback; public CallbackWrapper(@NotNull PeerCallCallback callCallback) { this.callCallback = callCallback; } @Override public void onOffer(final long deviceId, final long sessionId, @NotNull final String sdp) { self().post(() -> callCallback.onOffer(deviceId, sessionId, sdp)); } @Override public void onAnswer(final long deviceId, final long sessionId, @NotNull final String sdp) { self().post(() -> callCallback.onAnswer(deviceId, sessionId, sdp)); } @Override public void onCandidate(final long deviceId, final long sessionId, final int mdpIndex, @NotNull final String id, @NotNull final String sdp) { self().post(() -> callCallback.onCandidate(deviceId, sessionId, mdpIndex, id, sdp)); } @Override public void onNegotiationSuccessful(final long deviceId, final long sessionId) { self().post(() -> callCallback.onNegotiationSuccessful(deviceId, sessionId)); } @Override public void onNegotiationNeeded(long deviceId, long sessionId) { self().post(() -> callCallback.onNegotiationNeeded(deviceId, sessionId)); } @Override public void onMediaStreamsChanged(long deviceId, boolean isAudioEnabled, boolean isVideoEnabled) { self().post(() -> callCallback.onMediaStreamsChanged(deviceId, isAudioEnabled, isVideoEnabled)); } @Override public void onPeerStateChanged(final long deviceId, @NotNull final PeerState state) { self().post(() -> callCallback.onPeerStateChanged(deviceId, state)); } @Override public void onTrackAdded(long deviceId, WebRTCMediaTrack track) { self().post(() -> callCallback.onTrackAdded(deviceId, track)); } @Override public void onTrackRemoved(long deviceId, WebRTCMediaTrack track) { self().post(() -> callCallback.onTrackRemoved(deviceId, track)); } @Override public void onOwnTrackAdded(WebRTCMediaTrack track) { self().post(() -> callCallback.onOwnTrackAdded(track)); } @Override public void onOwnTrackRemoved(WebRTCMediaTrack track) { self().post(() -> callCallback.onOwnTrackRemoved(track)); } } }