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));
}
}
}