CallActivity.java 37 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893
  1. /*
  2. * Nextcloud Talk application
  3. *
  4. * @author Mario Danic
  5. * Copyright (C) 2017 Mario Danic <mario@lovelyhq.com>
  6. *
  7. * This program is free software: you can redistribute it and/or modify
  8. * it under the terms of the GNU General Public License as published by
  9. * the Free Software Foundation, either version 3 of the License, or
  10. * at your option) any later version.
  11. *
  12. * This program is distributed in the hope that it will be useful,
  13. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. * GNU General Public License for more details.
  16. *
  17. * You should have received a copy of the GNU General Public License
  18. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  19. *
  20. * Inspired by:
  21. * - Google samples
  22. * - https://github.com/vivek1794/webrtc-android-codelab (MIT licence)
  23. */
  24. package com.nextcloud.talk.activities;
  25. import android.Manifest;
  26. import android.content.res.Configuration;
  27. import android.content.res.Resources;
  28. import android.os.Bundle;
  29. import android.support.annotation.Nullable;
  30. import android.support.v7.app.AppCompatActivity;
  31. import android.text.TextUtils;
  32. import android.util.Log;
  33. import android.util.TypedValue;
  34. import android.view.View;
  35. import android.view.Window;
  36. import android.view.WindowManager;
  37. import android.widget.LinearLayout;
  38. import android.widget.RelativeLayout;
  39. import android.widget.TextView;
  40. import com.bluelinelabs.logansquare.LoganSquare;
  41. import com.nextcloud.talk.R;
  42. import com.nextcloud.talk.api.NcApi;
  43. import com.nextcloud.talk.api.helpers.api.ApiHelper;
  44. import com.nextcloud.talk.api.models.json.call.CallOverall;
  45. import com.nextcloud.talk.api.models.json.generic.GenericOverall;
  46. import com.nextcloud.talk.api.models.json.signaling.NCIceCandidate;
  47. import com.nextcloud.talk.api.models.json.signaling.NCMessagePayload;
  48. import com.nextcloud.talk.api.models.json.signaling.NCMessageWrapper;
  49. import com.nextcloud.talk.api.models.json.signaling.NCSignalingMessage;
  50. import com.nextcloud.talk.api.models.json.signaling.Signaling;
  51. import com.nextcloud.talk.api.models.json.signaling.SignalingOverall;
  52. import com.nextcloud.talk.api.models.json.signaling.settings.IceServer;
  53. import com.nextcloud.talk.api.models.json.signaling.settings.SignalingSettingsOverall;
  54. import com.nextcloud.talk.application.NextcloudTalkApplication;
  55. import com.nextcloud.talk.events.MediaStreamEvent;
  56. import com.nextcloud.talk.events.PeerConnectionEvent;
  57. import com.nextcloud.talk.events.SessionDescriptionSendEvent;
  58. import com.nextcloud.talk.persistence.entities.UserEntity;
  59. import com.nextcloud.talk.webrtc.MagicAudioManager;
  60. import com.nextcloud.talk.webrtc.MagicPeerConnectionWrapper;
  61. import org.apache.commons.lang3.StringEscapeUtils;
  62. import org.greenrobot.eventbus.EventBus;
  63. import org.greenrobot.eventbus.Subscribe;
  64. import org.greenrobot.eventbus.ThreadMode;
  65. import org.parceler.Parcels;
  66. import org.webrtc.AudioSource;
  67. import org.webrtc.AudioTrack;
  68. import org.webrtc.Camera1Enumerator;
  69. import org.webrtc.CameraEnumerator;
  70. import org.webrtc.EglBase;
  71. import org.webrtc.IceCandidate;
  72. import org.webrtc.Logging;
  73. import org.webrtc.MediaConstraints;
  74. import org.webrtc.MediaStream;
  75. import org.webrtc.PeerConnection;
  76. import org.webrtc.PeerConnectionFactory;
  77. import org.webrtc.RendererCommon;
  78. import org.webrtc.SessionDescription;
  79. import org.webrtc.SurfaceViewRenderer;
  80. import org.webrtc.VideoCapturer;
  81. import org.webrtc.VideoRenderer;
  82. import org.webrtc.VideoSource;
  83. import org.webrtc.VideoTrack;
  84. import java.io.IOException;
  85. import java.util.ArrayList;
  86. import java.util.HashMap;
  87. import java.util.HashSet;
  88. import java.util.List;
  89. import java.util.Set;
  90. import java.util.concurrent.TimeUnit;
  91. import javax.inject.Inject;
  92. import autodagger.AutoInjector;
  93. import butterknife.BindView;
  94. import butterknife.ButterKnife;
  95. import io.reactivex.Observer;
  96. import io.reactivex.android.schedulers.AndroidSchedulers;
  97. import io.reactivex.disposables.Disposable;
  98. import io.reactivex.functions.BooleanSupplier;
  99. import io.reactivex.schedulers.Schedulers;
  100. import ru.alexbykov.nopermission.PermissionHelper;
  101. @AutoInjector(NextcloudTalkApplication.class)
  102. public class CallActivity extends AppCompatActivity {
  103. private static final String TAG = "CallActivity";
  104. @BindView(R.id.pip_video_view)
  105. SurfaceViewRenderer pipVideoView;
  106. @BindView(R.id.full_screen_surface_view)
  107. SurfaceViewRenderer fullScreenVideoView;
  108. @BindView(R.id.relative_layout)
  109. RelativeLayout relativeLayout;
  110. @BindView(R.id.remote_renderers_layout)
  111. LinearLayout remoteRenderersLayout;
  112. @Inject
  113. NcApi ncApi;
  114. @Inject
  115. EventBus eventBus;
  116. PeerConnectionFactory peerConnectionFactory;
  117. MediaConstraints audioConstraints;
  118. MediaConstraints videoConstraints;
  119. MediaConstraints sdpConstraints;
  120. MagicAudioManager audioManager;
  121. VideoSource videoSource;
  122. VideoTrack localVideoTrack;
  123. AudioSource audioSource;
  124. AudioTrack localAudioTrack;
  125. VideoCapturer videoCapturer;
  126. VideoRenderer localRenderer;
  127. EglBase rootEglBase;
  128. boolean leavingCall = false;
  129. BooleanSupplier booleanSupplier = () -> leavingCall;
  130. Disposable signalingDisposable;
  131. Disposable pingDisposable;
  132. List<PeerConnection.IceServer> iceServers;
  133. private String roomToken;
  134. private UserEntity userEntity;
  135. private String callSession;
  136. private MediaStream localMediaStream;
  137. private String credentials;
  138. private List<MagicPeerConnectionWrapper> magicPeerConnectionWrapperList = new ArrayList<>();
  139. private static int getSystemUiVisibility() {
  140. int flags = View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_FULLSCREEN;
  141. flags |= View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
  142. return flags;
  143. }
  144. @Override
  145. protected void onCreate(Bundle savedInstanceState) {
  146. super.onCreate(savedInstanceState);
  147. NextcloudTalkApplication.getSharedApplication().getComponentApplication().inject(this);
  148. requestWindowFeature(Window.FEATURE_NO_TITLE);
  149. getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN |
  150. WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
  151. | WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
  152. | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON);
  153. getWindow().getDecorView().setSystemUiVisibility(getSystemUiVisibility());
  154. setContentView(R.layout.activity_call);
  155. ButterKnife.bind(this);
  156. roomToken = getIntent().getExtras().getString("roomToken", "");
  157. userEntity = Parcels.unwrap(getIntent().getExtras().getParcelable("userEntity"));
  158. callSession = "0";
  159. credentials = ApiHelper.getCredentials(userEntity.getUsername(), userEntity.getToken());
  160. initViews();
  161. PermissionHelper permissionHelper = new PermissionHelper(this);
  162. permissionHelper.check(Manifest.permission.CAMERA, Manifest.permission.RECORD_AUDIO)
  163. .onSuccess(this::start)
  164. .onDenied(new Runnable() {
  165. @Override
  166. public void run() {
  167. finish();
  168. }
  169. })
  170. .run();
  171. }
  172. private VideoCapturer createVideoCapturer() {
  173. videoCapturer = createCameraCapturer(new Camera1Enumerator(false));
  174. return videoCapturer;
  175. }
  176. private VideoCapturer createCameraCapturer(CameraEnumerator enumerator) {
  177. final String[] deviceNames = enumerator.getDeviceNames();
  178. // First, try to find front facing camera
  179. Logging.d(TAG, "Looking for front facing cameras.");
  180. for (String deviceName : deviceNames) {
  181. if (enumerator.isFrontFacing(deviceName)) {
  182. Logging.d(TAG, "Creating front facing camera capturer.");
  183. VideoCapturer videoCapturer = enumerator.createCapturer(deviceName, null);
  184. if (videoCapturer != null) {
  185. return videoCapturer;
  186. }
  187. }
  188. }
  189. // Front facing camera not found, try something else
  190. Logging.d(TAG, "Looking for other cameras.");
  191. for (String deviceName : deviceNames) {
  192. if (!enumerator.isFrontFacing(deviceName)) {
  193. Logging.d(TAG, "Creating other camera capturer.");
  194. VideoCapturer videoCapturer = enumerator.createCapturer(deviceName, null);
  195. if (videoCapturer != null) {
  196. return videoCapturer;
  197. }
  198. }
  199. }
  200. return null;
  201. }
  202. public void initViews() {
  203. pipVideoView.setMirror(true);
  204. rootEglBase = EglBase.create();
  205. pipVideoView.init(rootEglBase.getEglBaseContext(), null);
  206. pipVideoView.setZOrderMediaOverlay(true);
  207. pipVideoView.setEnableHardwareScaler(true);
  208. pipVideoView.setScalingType(RendererCommon.ScalingType.SCALE_ASPECT_FIT);
  209. fullScreenVideoView.setMirror(true);
  210. fullScreenVideoView.init(rootEglBase.getEglBaseContext(), null);
  211. fullScreenVideoView.setZOrderMediaOverlay(true);
  212. fullScreenVideoView.setEnableHardwareScaler(true);
  213. fullScreenVideoView.setScalingType(RendererCommon.ScalingType.SCALE_ASPECT_FILL);
  214. }
  215. public void start() {
  216. //Initialize PeerConnectionFactory globals.
  217. //Params are context, initAudio,initVideo and videoCodecHwAcceleration
  218. PeerConnectionFactory.initializeAndroidGlobals(this, true, true,
  219. false);
  220. //Create a new PeerConnectionFactory instance.
  221. PeerConnectionFactory.Options options = new PeerConnectionFactory.Options();
  222. peerConnectionFactory = new PeerConnectionFactory(options);
  223. //Now create a VideoCapturer instance. Callback methods are there if you want to do something! Duh!
  224. VideoCapturer videoCapturerAndroid = createVideoCapturer();
  225. //Create MediaConstraints - Will be useful for specifying video and audio constraints.
  226. audioConstraints = new MediaConstraints();
  227. videoConstraints = new MediaConstraints();
  228. //Create a VideoSource instance
  229. videoSource = peerConnectionFactory.createVideoSource(videoCapturerAndroid);
  230. localVideoTrack = peerConnectionFactory.createVideoTrack("NCv0", videoSource);
  231. //create an AudioSource instance
  232. audioSource = peerConnectionFactory.createAudioSource(audioConstraints);
  233. localAudioTrack = peerConnectionFactory.createAudioTrack("NCa0", audioSource);
  234. localMediaStream = peerConnectionFactory.createLocalMediaStream("NCMS");
  235. localMediaStream.addTrack(localAudioTrack);
  236. localMediaStream.addTrack(localVideoTrack);
  237. // Create and audio manager that will take care of audio routing,
  238. // audio modes, audio device enumeration etc.
  239. audioManager = MagicAudioManager.create(getApplicationContext());
  240. // Store existing audio settings and change audio mode to
  241. // MODE_IN_COMMUNICATION for best possible VoIP performance.
  242. Log.d(TAG, "Starting the audio manager...");
  243. audioManager.start(new MagicAudioManager.AudioManagerEvents() {
  244. @Override
  245. public void onAudioDeviceChanged(MagicAudioManager.AudioDevice selectedAudioDevice,
  246. Set<MagicAudioManager.AudioDevice> availableAudioDevices) {
  247. onAudioManagerDevicesChanged(selectedAudioDevice, availableAudioDevices);
  248. }
  249. });
  250. Resources r = getResources();
  251. int px = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 120, r.getDisplayMetrics());
  252. videoCapturerAndroid.startCapture(px, px, 30);
  253. //create a videoRenderer based on SurfaceViewRenderer instance
  254. localRenderer = new VideoRenderer(fullScreenVideoView);
  255. // And finally, with our VideoRenderer ready, we
  256. // can add our renderer to the VideoTrack.
  257. localVideoTrack.addRenderer(localRenderer);
  258. iceServers = new ArrayList<>();
  259. //create sdpConstraints
  260. sdpConstraints = new MediaConstraints();
  261. sdpConstraints.mandatory.add(new MediaConstraints.KeyValuePair("OfferToReceiveAudio", "true"));
  262. sdpConstraints.mandatory.add(new MediaConstraints.KeyValuePair("OfferToReceiveVideo", "true"));
  263. sdpConstraints.optional.add(new MediaConstraints.KeyValuePair("internalSctpDataChannels", "true"));
  264. sdpConstraints.optional.add(new MediaConstraints.KeyValuePair("DtlsSrtpKeyAgreement", "true"));
  265. ncApi.getSignalingSettings(ApiHelper.getCredentials(userEntity.getUsername(), userEntity.getToken()),
  266. ApiHelper.getUrlForSignalingSettings(userEntity.getBaseUrl()))
  267. .subscribeOn(Schedulers.newThread())
  268. .observeOn(AndroidSchedulers.mainThread())
  269. .subscribe(new Observer<SignalingSettingsOverall>() {
  270. @Override
  271. public void onSubscribe(Disposable d) {
  272. }
  273. @Override
  274. public void onNext(SignalingSettingsOverall signalingSettingsOverall) {
  275. IceServer iceServer;
  276. for (int i = 0; i < signalingSettingsOverall.getOcs().getSettings().getStunServers().size();
  277. i++) {
  278. iceServer = signalingSettingsOverall.getOcs().getSettings().getStunServers().get(i);
  279. if (TextUtils.isEmpty(iceServer.getUsername()) || TextUtils.isEmpty(iceServer
  280. .getCredential())) {
  281. iceServers.add(new PeerConnection.IceServer(iceServer.getUrl()));
  282. } else {
  283. iceServers.add(new PeerConnection.IceServer(iceServer.getUrl(),
  284. iceServer.getUsername(), iceServer.getCredential()));
  285. }
  286. }
  287. joinRoomAndCall();
  288. }
  289. @Override
  290. public void onError(Throwable e) {
  291. }
  292. @Override
  293. public void onComplete() {
  294. }
  295. });
  296. }
  297. private void joinRoomAndCall() {
  298. ncApi.joinRoom(credentials, ApiHelper.getUrlForRoom(userEntity.getBaseUrl(), roomToken))
  299. .subscribeOn(Schedulers.newThread())
  300. .observeOn(AndroidSchedulers.mainThread())
  301. .subscribe(new Observer<CallOverall>() {
  302. @Override
  303. public void onSubscribe(Disposable d) {
  304. }
  305. @Override
  306. public void onNext(CallOverall callOverall) {
  307. ncApi.joinCall(credentials,
  308. ApiHelper.getUrlForCall(userEntity.getBaseUrl(), roomToken))
  309. .subscribeOn(Schedulers.newThread())
  310. .observeOn(AndroidSchedulers.mainThread())
  311. .subscribe(new Observer<GenericOverall>() {
  312. @Override
  313. public void onSubscribe(Disposable d) {
  314. }
  315. @Override
  316. public void onNext(GenericOverall genericOverall) {
  317. callSession = callOverall.getOcs().getData().getSessionId();
  318. // start pinging the call
  319. ncApi.pingCall(ApiHelper.getCredentials(userEntity.getUsername(), userEntity.getToken()),
  320. ApiHelper.getUrlForCallPing(userEntity.getBaseUrl(), roomToken))
  321. .subscribeOn(Schedulers.newThread())
  322. .observeOn(AndroidSchedulers.mainThread())
  323. .repeatWhen(observable -> observable.delay(5000, TimeUnit.MILLISECONDS))
  324. .repeatUntil(booleanSupplier)
  325. .retry(3)
  326. .subscribe(new Observer<GenericOverall>() {
  327. @Override
  328. public void onSubscribe(Disposable d) {
  329. pingDisposable = d;
  330. }
  331. @Override
  332. public void onNext(GenericOverall genericOverall) {
  333. }
  334. @Override
  335. public void onError(Throwable e) {
  336. dispose(pingDisposable);
  337. }
  338. @Override
  339. public void onComplete() {
  340. dispose(pingDisposable);
  341. }
  342. });
  343. // Start pulling signaling messages
  344. ncApi.pullSignalingMessages(ApiHelper.getCredentials(userEntity.getUsername(),
  345. userEntity.getToken()), ApiHelper.getUrlForSignaling(userEntity.getBaseUrl()))
  346. .subscribeOn(Schedulers.newThread())
  347. .observeOn(AndroidSchedulers.mainThread())
  348. .repeatWhen(observable -> observable.delay(1500, TimeUnit
  349. .MILLISECONDS))
  350. .repeatUntil(booleanSupplier)
  351. .retry(3)
  352. .subscribe(new Observer<SignalingOverall>() {
  353. @Override
  354. public void onSubscribe(Disposable d) {
  355. signalingDisposable = d;
  356. }
  357. @Override
  358. public void onNext(SignalingOverall signalingOverall) {
  359. if (signalingOverall.getOcs().getSignalings() != null) {
  360. for (int i = 0; i < signalingOverall.getOcs().getSignalings().size(); i++) {
  361. try {
  362. receivedSignalingMessage(signalingOverall.getOcs().getSignalings().get(i));
  363. } catch (IOException e) {
  364. e.printStackTrace();
  365. }
  366. }
  367. }
  368. }
  369. @Override
  370. public void onError(Throwable e) {
  371. dispose(signalingDisposable);
  372. }
  373. @Override
  374. public void onComplete() {
  375. dispose(signalingDisposable);
  376. }
  377. });
  378. }
  379. @Override
  380. public void onError(Throwable e) {
  381. }
  382. @Override
  383. public void onComplete() {
  384. }
  385. });
  386. }
  387. @Override
  388. public void onError(Throwable e) {
  389. }
  390. @Override
  391. public void onComplete() {
  392. }
  393. });
  394. }
  395. private void receivedSignalingMessage(Signaling signaling) throws IOException {
  396. String messageType = signaling.getType();
  397. if (leavingCall) {
  398. return;
  399. }
  400. if ("usersInRoom".equals(messageType)) {
  401. processUsersInRoom((List<HashMap<String, String>>) signaling.getMessageWrapper());
  402. } else if ("message".equals(messageType)) {
  403. NCSignalingMessage ncSignalingMessage = LoganSquare.parse(signaling.getMessageWrapper().toString(),
  404. NCSignalingMessage.class);
  405. if (ncSignalingMessage.getRoomType().equals("video")) {
  406. MagicPeerConnectionWrapper magicPeerConnectionWrapper = alwaysGetPeerConnectionWrapperForSessionId
  407. (ncSignalingMessage.getFrom());
  408. String type = null;
  409. if (ncSignalingMessage.getPayload() != null && ncSignalingMessage.getPayload().getType() !=
  410. null) {
  411. type = ncSignalingMessage.getPayload().getType();
  412. } else if (ncSignalingMessage.getType() != null) {
  413. type = ncSignalingMessage.getType();
  414. }
  415. if (type != null) {
  416. switch (type) {
  417. case "offer":
  418. case "answer":
  419. magicPeerConnectionWrapper.setNick(ncSignalingMessage.getPayload().getNick());
  420. magicPeerConnectionWrapper.getPeerConnection().setRemoteDescription(magicPeerConnectionWrapper
  421. .getMagicSdpObserver(), new SessionDescription(SessionDescription.Type.fromCanonicalForm(type),
  422. ncSignalingMessage.getPayload().getSdp()));
  423. break;
  424. case "candidate":
  425. NCIceCandidate ncIceCandidate = ncSignalingMessage.getPayload().getIceCandidate();
  426. IceCandidate iceCandidate = new IceCandidate(ncIceCandidate.getSdpMid(),
  427. ncIceCandidate.getSdpMLineIndex(), ncIceCandidate.getCandidate());
  428. magicPeerConnectionWrapper.addCandidate(iceCandidate);
  429. break;
  430. case "endOfCandidates":
  431. magicPeerConnectionWrapper.drainIceCandidates();
  432. break;
  433. default:
  434. break;
  435. }
  436. }
  437. } else {
  438. Log.d(TAG, "Something went very very wrong");
  439. }
  440. } else {
  441. Log.d(TAG, "Something went very very wrong");
  442. }
  443. }
  444. // This method is called when the audio manager reports audio device change,
  445. // e.g. from wired headset to speakerphone.
  446. private void onAudioManagerDevicesChanged(
  447. final MagicAudioManager.AudioDevice device, final Set<MagicAudioManager.AudioDevice> availableDevices) {
  448. Log.d(TAG, "onAudioManagerDevicesChanged: " + availableDevices + ", "
  449. + "selected: " + device);
  450. }
  451. private void processUsersInRoom(List<HashMap<String, String>> users) {
  452. List<String> newSessions = new ArrayList<>();
  453. Set<String> oldSesssions = new HashSet<>();
  454. for (HashMap<String, String> participant : users) {
  455. Object inCallObject = participant.get("inCall");
  456. if (!participant.get("sessionId").equals(callSession) && (boolean) inCallObject) {
  457. newSessions.add(participant.get("sessionId"));
  458. } else if (!participant.get("sessionId").equals(callSession) && !(boolean) inCallObject) {
  459. oldSesssions.add(participant.get("sessionId"));
  460. }
  461. }
  462. for (MagicPeerConnectionWrapper magicPeerConnectionWrapper : magicPeerConnectionWrapperList) {
  463. oldSesssions.add(magicPeerConnectionWrapper.getSessionId());
  464. }
  465. // Calculate sessions that left the call
  466. oldSesssions.removeAll(newSessions);
  467. // Calculate sessions that join the call
  468. newSessions.removeAll(oldSesssions);
  469. if (leavingCall) {
  470. return;
  471. }
  472. for (String sessionId : newSessions) {
  473. alwaysGetPeerConnectionWrapperForSessionId(sessionId);
  474. }
  475. for (String sessionId : oldSesssions) {
  476. endPeerConnection(sessionId);
  477. }
  478. }
  479. private void deleteMagicPeerConnection(MagicPeerConnectionWrapper magicPeerConnectionWrapper) {
  480. if (magicPeerConnectionWrapper.getPeerConnection() != null) {
  481. magicPeerConnectionWrapper.getPeerConnection().close();
  482. }
  483. magicPeerConnectionWrapperList.remove(magicPeerConnectionWrapper);
  484. }
  485. private MagicPeerConnectionWrapper alwaysGetPeerConnectionWrapperForSessionId(String sessionId) {
  486. MagicPeerConnectionWrapper magicPeerConnectionWrapper;
  487. if ((magicPeerConnectionWrapper = getPeerConnectionWrapperForSessionId(sessionId)) != null) {
  488. return magicPeerConnectionWrapper;
  489. } else {
  490. magicPeerConnectionWrapper = new MagicPeerConnectionWrapper(peerConnectionFactory,
  491. iceServers, sdpConstraints, sessionId, callSession, localMediaStream);
  492. magicPeerConnectionWrapperList.add(magicPeerConnectionWrapper);
  493. return magicPeerConnectionWrapper;
  494. }
  495. }
  496. private MagicPeerConnectionWrapper getPeerConnectionWrapperForSessionId(String sessionId) {
  497. for (MagicPeerConnectionWrapper magicPeerConnectionWrapper : magicPeerConnectionWrapperList) {
  498. if (magicPeerConnectionWrapper.getSessionId().equals(sessionId)) {
  499. return magicPeerConnectionWrapper;
  500. }
  501. }
  502. return null;
  503. }
  504. private void hangup() {
  505. leavingCall = true;
  506. dispose(null);
  507. for (int i = 0; i < magicPeerConnectionWrapperList.size(); i++) {
  508. endPeerConnection(magicPeerConnectionWrapperList.get(i).getSessionId());
  509. }
  510. for (MagicPeerConnectionWrapper magicPeerConnectionWrapper : magicPeerConnectionWrapperList) {
  511. endPeerConnection(magicPeerConnectionWrapper.getSessionId());
  512. }
  513. if (videoCapturer != null) {
  514. videoCapturer.dispose();
  515. }
  516. if (localMediaStream.videoTracks.size() > 0) {
  517. localMediaStream.removeTrack(localMediaStream.videoTracks.get(0));
  518. }
  519. if (localMediaStream.audioTracks.size() > 0) {
  520. localMediaStream.removeTrack(localMediaStream.audioTracks.get(0));
  521. }
  522. localVideoTrack = null;
  523. localAudioTrack = null;
  524. localRenderer = null;
  525. localMediaStream = null;
  526. videoCapturer.dispose();
  527. videoCapturer = null;
  528. pipVideoView.release();
  529. String credentials = ApiHelper.getCredentials(userEntity.getUsername(), userEntity.getToken());
  530. ncApi.leaveCall(credentials, ApiHelper.getUrlForCall(userEntity.getBaseUrl(), roomToken))
  531. .subscribeOn(Schedulers.newThread())
  532. .observeOn(AndroidSchedulers.mainThread())
  533. .subscribe(new Observer<GenericOverall>() {
  534. @Override
  535. public void onSubscribe(Disposable d) {
  536. }
  537. @Override
  538. public void onNext(GenericOverall genericOverall) {
  539. ncApi.leaveRoom(credentials, ApiHelper.getUrlForRoom(userEntity.getBaseUrl(), roomToken))
  540. .subscribeOn(Schedulers.newThread())
  541. .observeOn(AndroidSchedulers.mainThread())
  542. .subscribe(new Observer<GenericOverall>() {
  543. @Override
  544. public void onSubscribe(Disposable d) {
  545. }
  546. @Override
  547. public void onNext(GenericOverall genericOverall) {
  548. }
  549. @Override
  550. public void onError(Throwable e) {
  551. }
  552. @Override
  553. public void onComplete() {
  554. }
  555. });
  556. }
  557. @Override
  558. public void onError(Throwable e) {
  559. }
  560. @Override
  561. public void onComplete() {
  562. }
  563. });
  564. }
  565. private void gotNick(String sessionId, String nick) {
  566. RelativeLayout relativeLayout = remoteRenderersLayout.findViewWithTag(sessionId);
  567. if (relativeLayout != null) {
  568. TextView textView = relativeLayout.findViewById(R.id.peer_nick_text_view);
  569. textView.setText(nick);
  570. }
  571. }
  572. private void gotRemoteStream(MediaStream stream, String session) {
  573. if (fullScreenVideoView != null) {
  574. remoteRenderersLayout.setVisibility(View.VISIBLE);
  575. pipVideoView.setVisibility(View.VISIBLE);
  576. localVideoTrack.removeRenderer(localRenderer);
  577. localRenderer = new VideoRenderer(pipVideoView);
  578. localVideoTrack.addRenderer(localRenderer);
  579. relativeLayout.removeView(fullScreenVideoView);
  580. }
  581. removeMediaStream(session);
  582. if (stream.videoTracks.size() == 1) {
  583. VideoTrack videoTrack = stream.videoTracks.get(0);
  584. try {
  585. RelativeLayout relativeLayout = (RelativeLayout)
  586. getLayoutInflater().inflate(R.layout.surface_renderer, remoteRenderersLayout,
  587. false);
  588. relativeLayout.setTag(session);
  589. SurfaceViewRenderer surfaceViewRenderer = relativeLayout.findViewById(R.id
  590. .surface_view);
  591. surfaceViewRenderer.setMirror(false);
  592. surfaceViewRenderer.init(rootEglBase.getEglBaseContext(), null);
  593. surfaceViewRenderer.setZOrderMediaOverlay(true);
  594. surfaceViewRenderer.setEnableHardwareScaler(true);
  595. surfaceViewRenderer.setScalingType(RendererCommon.ScalingType.SCALE_ASPECT_FIT);
  596. VideoRenderer remoteRenderer = new VideoRenderer(surfaceViewRenderer);
  597. videoTrack.addRenderer(remoteRenderer);
  598. remoteRenderersLayout.addView(relativeLayout);
  599. relativeLayout.invalidate();
  600. gotNick(session, getPeerConnectionWrapperForSessionId(session).getNick());
  601. } catch (Exception e) {
  602. Log.d(TAG, "Failed to create a new video view");
  603. }
  604. }
  605. }
  606. @Override
  607. public void onDestroy() {
  608. hangup();
  609. super.onDestroy();
  610. }
  611. private void dispose(@Nullable Disposable disposable) {
  612. if (disposable != null && !disposable.isDisposed()) {
  613. disposable.dispose();
  614. } else if (disposable == null) {
  615. if (pingDisposable != null && !pingDisposable.isDisposed()) {
  616. pingDisposable.dispose();
  617. pingDisposable = null;
  618. }
  619. if (signalingDisposable != null && !signalingDisposable.isDisposed()) {
  620. signalingDisposable.dispose();
  621. signalingDisposable = null;
  622. }
  623. }
  624. }
  625. @Override
  626. public void onStart() {
  627. super.onStart();
  628. eventBus.register(this);
  629. }
  630. @Override
  631. public void onStop() {
  632. super.onStop();
  633. eventBus.unregister(this);
  634. }
  635. @Subscribe(threadMode = ThreadMode.BACKGROUND)
  636. public void onMessageEvent(PeerConnectionEvent peerConnectionEvent) {
  637. endPeerConnection(peerConnectionEvent.getSessionId());
  638. }
  639. private void endPeerConnection(String sessionId) {
  640. MagicPeerConnectionWrapper magicPeerConnectionWrapper;
  641. if ((magicPeerConnectionWrapper = getPeerConnectionWrapperForSessionId(sessionId)) != null) {
  642. runOnUiThread(() -> removeMediaStream(sessionId));
  643. deleteMagicPeerConnection(magicPeerConnectionWrapper);
  644. }
  645. }
  646. private void removeMediaStream(String sessionId) {
  647. if (remoteRenderersLayout.getChildCount() > 0) {
  648. RelativeLayout relativeLayout = remoteRenderersLayout.findViewWithTag(sessionId);
  649. if (relativeLayout != null) {
  650. SurfaceViewRenderer surfaceViewRenderer = relativeLayout.findViewById(R.id.surface_view);
  651. surfaceViewRenderer.release();
  652. remoteRenderersLayout.removeView(relativeLayout);
  653. remoteRenderersLayout.invalidate();
  654. }
  655. }
  656. }
  657. @Subscribe(threadMode = ThreadMode.MAIN)
  658. public void onMessageEvent(MediaStreamEvent mediaStreamEvent) {
  659. if (mediaStreamEvent.getMediaStream() != null) {
  660. gotRemoteStream(mediaStreamEvent.getMediaStream(), mediaStreamEvent.getSession());
  661. } else {
  662. removeMediaStream(mediaStreamEvent.getSession());
  663. }
  664. }
  665. @Subscribe(threadMode = ThreadMode.BACKGROUND)
  666. public void onMessageEvent(SessionDescriptionSendEvent sessionDescriptionSend) throws IOException {
  667. String credentials = ApiHelper.getCredentials(userEntity.getUsername(), userEntity.getToken());
  668. NCMessageWrapper ncMessageWrapper = new NCMessageWrapper();
  669. ncMessageWrapper.setEv("message");
  670. ncMessageWrapper.setSessionId(callSession);
  671. NCSignalingMessage ncSignalingMessage = new NCSignalingMessage();
  672. ncSignalingMessage.setTo(sessionDescriptionSend.getPeerId());
  673. ncSignalingMessage.setRoomType("video");
  674. ncSignalingMessage.setType(sessionDescriptionSend.getType());
  675. NCMessagePayload ncMessagePayload = new NCMessagePayload();
  676. ncMessagePayload.setType(sessionDescriptionSend.getType());
  677. if (!"candidate".equals(sessionDescriptionSend.getType())) {
  678. ncMessagePayload.setSdp(sessionDescriptionSend.getSessionDescription().description);
  679. ncMessagePayload.setNick(userEntity.getDisplayName());
  680. } else {
  681. ncMessagePayload.setIceCandidate(sessionDescriptionSend.getNcIceCandidate());
  682. }
  683. // Set all we need
  684. ncSignalingMessage.setPayload(ncMessagePayload);
  685. ncMessageWrapper.setSignalingMessage(ncSignalingMessage);
  686. StringBuilder stringBuilder = new StringBuilder();
  687. stringBuilder.append("{");
  688. stringBuilder.append("\"fn\":\"");
  689. stringBuilder.append(StringEscapeUtils.escapeJson(LoganSquare.serialize(ncMessageWrapper
  690. .getSignalingMessage()))).append("\"");
  691. stringBuilder.append(",");
  692. stringBuilder.append("\"sessionId\":");
  693. stringBuilder.append("\"").append(StringEscapeUtils.escapeJson(callSession)).append("\"");
  694. stringBuilder.append(",");
  695. stringBuilder.append("\"ev\":\"message\"");
  696. stringBuilder.append("}");
  697. List<String> strings = new ArrayList<>();
  698. String stringToSend = stringBuilder.toString();
  699. strings.add(stringToSend);
  700. ncApi.sendSignalingMessages(credentials, ApiHelper.getUrlForSignaling(userEntity.getBaseUrl()),
  701. strings.toString())
  702. .retry(3)
  703. .subscribeOn(Schedulers.newThread())
  704. .subscribe(new Observer<SignalingOverall>() {
  705. @Override
  706. public void onSubscribe(Disposable d) {
  707. }
  708. @Override
  709. public void onNext(SignalingOverall signalingOverall) {
  710. if (signalingOverall.getOcs().getSignalings() != null) {
  711. for (int i = 0; i < signalingOverall.getOcs().getSignalings().size(); i++) {
  712. try {
  713. receivedSignalingMessage(signalingOverall.getOcs().getSignalings().get(i));
  714. } catch (IOException e) {
  715. e.printStackTrace();
  716. }
  717. }
  718. }
  719. }
  720. @Override
  721. public void onError(Throwable e) {
  722. }
  723. @Override
  724. public void onComplete() {
  725. }
  726. });
  727. }
  728. @Override
  729. public void onConfigurationChanged(Configuration newConfig) {
  730. // Checks the orientation of the screen
  731. if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {
  732. remoteRenderersLayout.setOrientation(LinearLayout.HORIZONTAL);
  733. } else if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) {
  734. remoteRenderersLayout.setOrientation(LinearLayout.VERTICAL);
  735. }
  736. super.onConfigurationChanged(newConfig);
  737. }
  738. }