CallActivity.java 35 KB

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