package im.actor.core.modules.calls.peers; import java.util.HashMap; import java.util.List; import im.actor.core.api.ApiICEServer; import im.actor.core.modules.ModuleContext; import im.actor.core.modules.calls.peers.messages.RTCAdvertised; import im.actor.core.modules.calls.peers.messages.RTCAnswer; import im.actor.core.modules.calls.peers.messages.RTCCandidate; import im.actor.core.modules.calls.peers.messages.RTCCloseSession; import im.actor.core.modules.calls.peers.messages.RTCDispose; import im.actor.core.modules.calls.peers.messages.RTCMasterAdvertised; import im.actor.core.modules.calls.peers.messages.RTCMediaStateUpdated; import im.actor.core.modules.calls.peers.messages.RTCNeedOffer; import im.actor.core.modules.calls.peers.messages.RTCOffer; import im.actor.core.modules.calls.peers.messages.RTCStart; import im.actor.core.modules.ModuleActor; import im.actor.runtime.Log; import im.actor.runtime.WebRTC; import im.actor.runtime.actors.messages.PoisonPill; import im.actor.runtime.function.CountedReference; import im.actor.runtime.webrtc.WebRTCMediaStream; import im.actor.runtime.webrtc.WebRTCMediaTrack; /** * Manages Connections to all Nodes */ public class PeerCallActor extends ModuleActor { private static final String TAG = "PeerCallActor"; // Parent Actor for handling events private final PeerCallCallback callback; // Peer Settings private final PeerSettings selfSettings; // WebRTC objects private List<ApiICEServer> iceServers; private HashMap<Long, PeerNodeInt> refs = new HashMap<>(); private CountedReference<WebRTCMediaStream> currentMediaStream; private boolean isCurrentStreamAudioRequired; private boolean isCurrentStreamVideoRequired; private boolean isAudioRequired; private boolean isVideoRequired; // State objects private boolean isOwnStarted = false; private boolean isBuildingStream = false; private boolean isAudioEnabled = true; private boolean isVideoEnabled = false; public PeerCallActor(PeerCallCallback callback, PeerSettings selfSettings, ModuleContext context) { super(context); this.callback = callback; this.selfSettings = selfSettings; this.isAudioRequired = true; this.isVideoRequired = false; } // // Media Settings // public void onAudioEnabled(boolean isAudioEnabled) { if (this.isAudioEnabled != isAudioEnabled) { this.isAudioEnabled = isAudioEnabled; if (!isAudioRequired) { isAudioRequired = true; } if (currentMediaStream != null) { for (WebRTCMediaTrack audio : currentMediaStream.get().getAudioTracks()) { audio.setEnabled(isAudioEnabled); if (isAudioEnabled) { callback.onOwnTrackAdded(audio); } else { callback.onOwnTrackRemoved(audio); } } } requestStreamIfNeeded(); for (Long deviceId : refs.keySet()) { callback.onMediaStreamsChanged(deviceId, isAudioEnabled, isVideoEnabled); } } } public void onVideoEnabled(boolean isVideoEnabled) { if (this.isVideoEnabled != isVideoEnabled) { this.isVideoEnabled = isVideoEnabled; if (!isVideoRequired) { isVideoRequired = true; } if (currentMediaStream != null) { for (WebRTCMediaTrack video : currentMediaStream.get().getVideoTracks()) { video.setEnabled(isVideoEnabled); if (isVideoEnabled) { callback.onOwnTrackAdded(video); } else { callback.onOwnTrackRemoved(video); } } } requestStreamIfNeeded(); for (Long deviceId : refs.keySet()) { callback.onMediaStreamsChanged(deviceId, isAudioEnabled, isVideoEnabled); } } } public void onOwnStarted() { if (isOwnStarted) { return; } isOwnStarted = true; requestStreamIfNeeded(); } private void requestStreamIfNeeded() { if (!isBuildingStream && isOwnStarted && ((currentMediaStream == null) || (isCurrentStreamAudioRequired != isAudioRequired) || (isCurrentStreamVideoRequired != isVideoRequired))) { isBuildingStream = true; isCurrentStreamAudioRequired = isAudioRequired; isCurrentStreamVideoRequired = isVideoRequired; WebRTC.getUserMedia(isAudioRequired, isVideoRequired).then(mediaStream -> { isBuildingStream = false; // Checking if required types are not changed if (isCurrentStreamAudioRequired != isAudioRequired || isCurrentStreamVideoRequired != isVideoRequired) { // Closing unwanted media stream mediaStream.close(); requestStreamIfNeeded(); return; } // Update Stream Locally CountedReference<WebRTCMediaStream> newStream = new VerboseReference(mediaStream); CountedReference<WebRTCMediaStream> oldStream = currentMediaStream; currentMediaStream = newStream; // Releasing old stream if (oldStream != null) { if (isAudioEnabled) { for (WebRTCMediaTrack track : oldStream.get().getAudioTracks()) { callback.onOwnTrackRemoved(track); } } if (isVideoEnabled) { for (WebRTCMediaTrack track : oldStream.get().getVideoTracks()) { callback.onOwnTrackRemoved(track); } } oldStream.release(); } // Connecting to a new stream for (WebRTCMediaTrack audio : mediaStream.getAudioTracks()) { audio.setEnabled(isAudioEnabled); if (isAudioEnabled) { callback.onOwnTrackAdded(audio); } } for (WebRTCMediaTrack video : mediaStream.getVideoTracks()) { video.setEnabled(isVideoEnabled); if (isVideoEnabled) { callback.onOwnTrackAdded(video); } } // Update references in all active nodes for (PeerNodeInt node : refs.values()) { node.replaceOwnStream(newStream); } }).failure(e -> { Log.d(TAG, "Unable to load stream"); self().send(PoisonPill.INSTANCE); }); } } public void onMasterAdvertised(List<ApiICEServer> iceServers) { if (this.iceServers == null) { this.iceServers = iceServers; for (PeerNodeInt node : refs.values()) { node.onAdvertisedMaster(iceServers); } } } // // Peer Collection // public PeerNodeInt getPeer(long deviceId) { if (!refs.containsKey(deviceId)) { return createNewPeer(deviceId); } return refs.get(deviceId); } public PeerNodeInt createNewPeer(final long deviceId) { // Creating Peer Node PeerNodeInt peerNodeInt = new PeerNodeInt(deviceId, new NodeCallback(), selfSettings, self(), context()); // Setting Own Stream if available if (currentMediaStream != null) { peerNodeInt.replaceOwnStream(currentMediaStream); } // Set advertise info if available if (this.iceServers != null) { peerNodeInt.onAdvertisedMaster(iceServers); } // Notify new node about current streams configuration callback.onMediaStreamsChanged(deviceId, isAudioEnabled, isVideoEnabled); refs.put(deviceId, peerNodeInt); return peerNodeInt; } public void disposePeer(long deviceId) { PeerNodeInt peerNodeInt = refs.remove(deviceId); if (peerNodeInt != null) { peerNodeInt.kill(); } } @Override public void postStop() { super.postStop(); for (PeerNodeInt d : refs.values()) { d.kill(); } refs.clear(); if (currentMediaStream != null) { if (isAudioEnabled) { for (WebRTCMediaTrack track : currentMediaStream.get().getAudioTracks()) { callback.onOwnTrackRemoved(track); } } if (isVideoEnabled) { for (WebRTCMediaTrack track : currentMediaStream.get().getVideoTracks()) { callback.onOwnTrackRemoved(track); } } currentMediaStream.release(); currentMediaStream = null; } } @Override public void onReceive(Object message) { if (message instanceof RTCStart) { RTCStart start = (RTCStart) message; getPeer(start.getDeviceId()).startConnection(); } else if (message instanceof RTCDispose) { RTCDispose dispose = (RTCDispose) message; disposePeer(dispose.getDeviceId()); } else if (message instanceof RTCOffer) { RTCOffer offer = (RTCOffer) message; getPeer(offer.getDeviceId()).onOffer(offer.getSessionId(), offer.getSdp()); } else if (message instanceof RTCAnswer) { RTCAnswer answer = (RTCAnswer) message; getPeer(answer.getDeviceId()).onAnswer(answer.getSessionId(), answer.getSdp()); } else if (message instanceof RTCCandidate) { RTCCandidate candidate = (RTCCandidate) message; getPeer(candidate.getDeviceId()).onCandidate(candidate.getSessionId(), candidate.getMdpIndex(), candidate.getId(), candidate.getSdp()); } else if (message instanceof RTCNeedOffer) { RTCNeedOffer needOffer = (RTCNeedOffer) message; getPeer(needOffer.getDeviceId()).onOfferNeeded(needOffer.getSessionId()); } else if (message instanceof RTCAdvertised) { RTCAdvertised advertised = (RTCAdvertised) message; getPeer(advertised.getDeviceId()).onAdvertised(advertised.getSettings()); } else if (message instanceof RTCCloseSession) { RTCCloseSession closeSession = (RTCCloseSession) message; getPeer(closeSession.getDeviceId()).closeSession(closeSession.getSessionId()); } else if (message instanceof RTCMediaStateUpdated) { RTCMediaStateUpdated mediaStateUpdated = (RTCMediaStateUpdated) message; getPeer(mediaStateUpdated.getDeviceId()).onMediaStateChanged(mediaStateUpdated.isAudioEnabled(), mediaStateUpdated.isVideoEnabled()); } else if (message instanceof RTCMasterAdvertised) { RTCMasterAdvertised masterAdvertised = (RTCMasterAdvertised) message; onMasterAdvertised(masterAdvertised.getIceServers()); } else if (message instanceof AudioEnabled) { AudioEnabled muteChanged = (AudioEnabled) message; onAudioEnabled(muteChanged.isEnabled()); } else if (message instanceof VideoEnabled) { VideoEnabled videoEnabled = (VideoEnabled) message; onVideoEnabled(videoEnabled.isEnabled()); } else if (message instanceof OwnStarted) { onOwnStarted(); } else { super.onReceive(message); } } public static class AudioEnabled { private boolean isEnabled; public AudioEnabled(boolean isEnabled) { this.isEnabled = isEnabled; } public boolean isEnabled() { return isEnabled; } } public static class VideoEnabled { private boolean enabled; public VideoEnabled(boolean enabled) { this.enabled = enabled; } public boolean isEnabled() { return enabled; } } public static class OwnStarted { } private class NodeCallback implements PeerNodeCallback { @Override public void onOffer(long deviceId, long sessionId, String sdp) { callback.onOffer(deviceId, sessionId, sdp); } @Override public void onAnswer(long deviceId, long sessionId, String sdp) { callback.onAnswer(deviceId, sessionId, sdp); } @Override public void onNegotiationSuccessful(long deviceId, long sessionId) { callback.onNegotiationSuccessful(deviceId, sessionId); } @Override public void onNegotiationNeeded(long deviceId, long sessionId) { callback.onNegotiationNeeded(deviceId, sessionId); } @Override public void onCandidate(long deviceId, long sessionId, int mdpIndex, String id, String sdp) { callback.onCandidate(deviceId, sessionId, mdpIndex, id, sdp); } @Override public void onPeerStateChanged(long deviceId, PeerState state) { callback.onPeerStateChanged(deviceId, state); } @Override public void onTrackAdded(long deviceId, WebRTCMediaTrack track) { callback.onTrackAdded(deviceId, track); } @Override public void onTrackRemoved(long deviceId, WebRTCMediaTrack track) { callback.onTrackRemoved(deviceId, track); } } private static int referenceId = 0; private static class VerboseReference extends CountedReference<WebRTCMediaStream> { private int id = referenceId++; public VerboseReference(WebRTCMediaStream value) { super(value); } @Override public synchronized void acquire(int counter) { Log.d("CountedReference(" + id + ")", "acquire(" + counter + ")"); } @Override public synchronized void release(int counter) { Log.d("CountedReference(" + id + ")", "release(" + counter + ")"); } } }