CallActivity.java 56 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368
  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.animation.Animator;
  27. import android.animation.AnimatorListenerAdapter;
  28. import android.content.BroadcastReceiver;
  29. import android.content.Context;
  30. import android.content.Intent;
  31. import android.content.IntentFilter;
  32. import android.content.res.Configuration;
  33. import android.os.Bundle;
  34. import android.support.annotation.NonNull;
  35. import android.support.annotation.Nullable;
  36. import android.support.v7.app.AppCompatActivity;
  37. import android.text.TextUtils;
  38. import android.util.Log;
  39. import android.view.View;
  40. import android.view.Window;
  41. import android.view.WindowManager;
  42. import android.widget.ImageButton;
  43. import android.widget.ImageView;
  44. import android.widget.LinearLayout;
  45. import android.widget.RelativeLayout;
  46. import android.widget.TextView;
  47. import com.bluelinelabs.logansquare.LoganSquare;
  48. import com.evernote.android.job.JobRequest;
  49. import com.evernote.android.job.util.Device;
  50. import com.nextcloud.talk.R;
  51. import com.nextcloud.talk.api.NcApi;
  52. import com.nextcloud.talk.api.helpers.api.ApiHelper;
  53. import com.nextcloud.talk.api.models.json.call.CallOverall;
  54. import com.nextcloud.talk.api.models.json.generic.GenericOverall;
  55. import com.nextcloud.talk.api.models.json.rooms.Room;
  56. import com.nextcloud.talk.api.models.json.rooms.RoomsOverall;
  57. import com.nextcloud.talk.api.models.json.signaling.DataChannelMessage;
  58. import com.nextcloud.talk.api.models.json.signaling.NCIceCandidate;
  59. import com.nextcloud.talk.api.models.json.signaling.NCMessagePayload;
  60. import com.nextcloud.talk.api.models.json.signaling.NCMessageWrapper;
  61. import com.nextcloud.talk.api.models.json.signaling.NCSignalingMessage;
  62. import com.nextcloud.talk.api.models.json.signaling.Signaling;
  63. import com.nextcloud.talk.api.models.json.signaling.SignalingOverall;
  64. import com.nextcloud.talk.api.models.json.signaling.settings.IceServer;
  65. import com.nextcloud.talk.api.models.json.signaling.settings.SignalingSettingsOverall;
  66. import com.nextcloud.talk.application.NextcloudTalkApplication;
  67. import com.nextcloud.talk.events.MediaStreamEvent;
  68. import com.nextcloud.talk.events.PeerConnectionEvent;
  69. import com.nextcloud.talk.events.SessionDescriptionSendEvent;
  70. import com.nextcloud.talk.persistence.entities.UserEntity;
  71. import com.nextcloud.talk.utils.database.user.UserUtils;
  72. import com.nextcloud.talk.webrtc.MagicAudioManager;
  73. import com.nextcloud.talk.webrtc.MagicPeerConnectionWrapper;
  74. import com.nextcloud.talk.webrtc.MagicWebRTCUtils;
  75. import org.apache.commons.lang3.StringEscapeUtils;
  76. import org.greenrobot.eventbus.EventBus;
  77. import org.greenrobot.eventbus.Subscribe;
  78. import org.greenrobot.eventbus.ThreadMode;
  79. import org.parceler.Parcels;
  80. import org.webrtc.AudioSource;
  81. import org.webrtc.AudioTrack;
  82. import org.webrtc.Camera1Enumerator;
  83. import org.webrtc.Camera2Enumerator;
  84. import org.webrtc.CameraEnumerator;
  85. import org.webrtc.CameraVideoCapturer;
  86. import org.webrtc.EglBase;
  87. import org.webrtc.IceCandidate;
  88. import org.webrtc.Logging;
  89. import org.webrtc.MediaConstraints;
  90. import org.webrtc.MediaStream;
  91. import org.webrtc.PeerConnection;
  92. import org.webrtc.PeerConnectionFactory;
  93. import org.webrtc.RendererCommon;
  94. import org.webrtc.SessionDescription;
  95. import org.webrtc.SurfaceViewRenderer;
  96. import org.webrtc.VideoCapturer;
  97. import org.webrtc.VideoRenderer;
  98. import org.webrtc.VideoSource;
  99. import org.webrtc.VideoTrack;
  100. import java.io.IOException;
  101. import java.net.CookieManager;
  102. import java.util.ArrayList;
  103. import java.util.HashMap;
  104. import java.util.HashSet;
  105. import java.util.List;
  106. import java.util.Set;
  107. import java.util.concurrent.TimeUnit;
  108. import javax.inject.Inject;
  109. import autodagger.AutoInjector;
  110. import butterknife.BindView;
  111. import butterknife.ButterKnife;
  112. import butterknife.OnClick;
  113. import io.reactivex.Observer;
  114. import io.reactivex.android.schedulers.AndroidSchedulers;
  115. import io.reactivex.disposables.Disposable;
  116. import io.reactivex.functions.BooleanSupplier;
  117. import io.reactivex.schedulers.Schedulers;
  118. import me.zhanghai.android.effortlesspermissions.AfterPermissionDenied;
  119. import me.zhanghai.android.effortlesspermissions.EffortlessPermissions;
  120. import me.zhanghai.android.effortlesspermissions.OpenAppDetailsDialogFragment;
  121. import pub.devrel.easypermissions.AfterPermissionGranted;
  122. @AutoInjector(NextcloudTalkApplication.class)
  123. public class CallActivity extends AppCompatActivity {
  124. private static final String TAG = "CallActivity";
  125. private static final String[] PERMISSIONS_CALL = {
  126. android.Manifest.permission.CAMERA,
  127. android.Manifest.permission.RECORD_AUDIO,
  128. };
  129. private static final String[] PERMISSIONS_CAMERA = {
  130. Manifest.permission.CAMERA
  131. };
  132. private static final String[] PERMISSIONS_MICROPHONE = {
  133. Manifest.permission.RECORD_AUDIO
  134. };
  135. @BindView(R.id.pip_video_view)
  136. SurfaceViewRenderer pipVideoView;
  137. @BindView(R.id.relative_layout)
  138. RelativeLayout relativeLayout;
  139. @BindView(R.id.remote_renderers_layout)
  140. LinearLayout remoteRenderersLayout;
  141. @BindView(R.id.call_controls)
  142. RelativeLayout callControls;
  143. @BindView(R.id.call_control_microphone)
  144. ImageButton microphoneControlButton;
  145. @BindView(R.id.call_control_camera)
  146. ImageButton cameraControlButton;
  147. @BindView(R.id.call_control_switch_camera)
  148. ImageButton cameraSwitchButton;
  149. @Inject
  150. NcApi ncApi;
  151. @Inject
  152. EventBus eventBus;
  153. @Inject
  154. UserUtils userUtils;
  155. @Inject
  156. CookieManager cookieManager;
  157. PeerConnectionFactory peerConnectionFactory;
  158. MediaConstraints audioConstraints;
  159. MediaConstraints videoConstraints;
  160. MediaConstraints sdpConstraints;
  161. MagicAudioManager audioManager;
  162. VideoSource videoSource;
  163. VideoTrack localVideoTrack;
  164. AudioSource audioSource;
  165. AudioTrack localAudioTrack;
  166. VideoCapturer videoCapturer;
  167. VideoRenderer localRenderer;
  168. EglBase rootEglBase;
  169. boolean leavingCall = false;
  170. boolean inCall = false;
  171. BooleanSupplier booleanSupplier = () -> leavingCall;
  172. Disposable signalingDisposable;
  173. Disposable pingDisposable;
  174. List<PeerConnection.IceServer> iceServers;
  175. private CameraEnumerator cameraEnumerator;
  176. private String roomToken;
  177. private UserEntity userEntity;
  178. private String callSession;
  179. private MediaStream localMediaStream;
  180. private String credentials;
  181. private List<MagicPeerConnectionWrapper> magicPeerConnectionWrapperList = new ArrayList<>();
  182. private boolean videoOn = false;
  183. private boolean audioOn = false;
  184. private BroadcastReceiver networkBroadcastReceier;
  185. private static int getSystemUiVisibility() {
  186. int flags = View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_FULLSCREEN;
  187. flags |= View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
  188. return flags;
  189. }
  190. @Override
  191. protected void onCreate(Bundle savedInstanceState) {
  192. super.onCreate(savedInstanceState);
  193. NextcloudTalkApplication.getSharedApplication().getComponentApplication().inject(this);
  194. requestWindowFeature(Window.FEATURE_NO_TITLE);
  195. getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN |
  196. WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
  197. | WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
  198. | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON);
  199. getWindow().getDecorView().setSystemUiVisibility(getSystemUiVisibility());
  200. setContentView(R.layout.activity_call);
  201. ButterKnife.bind(this);
  202. roomToken = getIntent().getExtras().getString("roomToken", "");
  203. userEntity = Parcels.unwrap(getIntent().getExtras().getParcelable("userEntity"));
  204. callSession = "0";
  205. credentials = ApiHelper.getCredentials(userEntity.getUsername(), userEntity.getToken());
  206. networkBroadcastReceier = new BroadcastReceiver() {
  207. @Override
  208. public void onReceive(Context context, Intent intent) {
  209. if ("android.net.conn.CONNECTIVITY_CHANGE".equals(intent.getAction())) {
  210. if (!Device.getNetworkType(context).equals(JobRequest.NetworkType.ANY)) {
  211. startPullingSignalingMessages(true);
  212. } else {
  213. //hangup(true);
  214. }
  215. }
  216. }
  217. };
  218. callControls.setZ(100.0f);
  219. basicInitialization();
  220. if (!userEntity.getCurrent()) {
  221. userUtils.createOrUpdateUser(null,
  222. null, null, null,
  223. null, true, null, userEntity.getId())
  224. .subscribe(new Observer<UserEntity>() {
  225. @Override
  226. public void onSubscribe(Disposable d) {
  227. }
  228. @Override
  229. public void onNext(UserEntity userEntity) {
  230. cookieManager.getCookieStore().removeAll();
  231. userUtils.disableAllUsersWithoutId(userEntity.getId());
  232. if (getIntent().getExtras().containsKey("fromNotification")) {
  233. handleFromNotification();
  234. } else {
  235. initViews();
  236. checkPermissions();
  237. }
  238. }
  239. @Override
  240. public void onError(Throwable e) {
  241. }
  242. @Override
  243. public void onComplete() {
  244. }
  245. });
  246. } else if (getIntent().getExtras().containsKey("fromNotification")) {
  247. handleFromNotification();
  248. } else {
  249. initViews();
  250. checkPermissions();
  251. }
  252. }
  253. private void performIceRestart() {
  254. for (int i = 0; i < magicPeerConnectionWrapperList.size(); i++) {
  255. sdpConstraints.optional.add(new MediaConstraints.KeyValuePair("IceRestart", "true"));
  256. PeerConnection.RTCConfiguration rtcConfiguration = new PeerConnection.RTCConfiguration(iceServers);
  257. magicPeerConnectionWrapperList.get(i).getPeerConnection().setConfiguration(rtcConfiguration);
  258. }
  259. }
  260. private void handleFromNotification() {
  261. ncApi.getRooms(credentials, ApiHelper.getUrlForGetRooms(userEntity.getBaseUrl()))
  262. .subscribeOn(Schedulers.newThread())
  263. .observeOn(AndroidSchedulers.mainThread())
  264. .subscribe(new Observer<RoomsOverall>() {
  265. @Override
  266. public void onSubscribe(Disposable d) {
  267. }
  268. @Override
  269. public void onNext(RoomsOverall roomsOverall) {
  270. for (Room room : roomsOverall.getOcs().getData()) {
  271. if (roomToken.equals(room.getRoomId())) {
  272. roomToken = room.getToken();
  273. break;
  274. }
  275. }
  276. initViews();
  277. checkPermissions();
  278. }
  279. @Override
  280. public void onError(Throwable e) {
  281. }
  282. @Override
  283. public void onComplete() {
  284. }
  285. });
  286. }
  287. private void toggleMedia(boolean enable, boolean video) {
  288. String message;
  289. if (video) {
  290. message = "videoOff";
  291. if (enable) {
  292. message = "videoOn";
  293. startVideoCapture();
  294. } else {
  295. try {
  296. videoCapturer.stopCapture();
  297. } catch (InterruptedException e) {
  298. Log.d(TAG, "Failed to stop capturing video while sensor is near the ear");
  299. }
  300. }
  301. if (localMediaStream != null && localMediaStream.videoTracks.size() > 0) {
  302. localMediaStream.videoTracks.get(0).setEnabled(enable);
  303. }
  304. if (enable) {
  305. pipVideoView.setVisibility(View.VISIBLE);
  306. } else {
  307. pipVideoView.setVisibility(View.INVISIBLE);
  308. }
  309. } else {
  310. message = "audioOff";
  311. if (enable) {
  312. message = "audioOn";
  313. }
  314. if (localMediaStream != null && localMediaStream.audioTracks.size() > 0) {
  315. localMediaStream.audioTracks.get(0).setEnabled(enable);
  316. }
  317. }
  318. if (inCall) {
  319. for (int i = 0; i < magicPeerConnectionWrapperList.size(); i++) {
  320. magicPeerConnectionWrapperList.get(i).sendChannelData(new DataChannelMessage(message));
  321. }
  322. }
  323. }
  324. @OnClick(R.id.call_control_microphone)
  325. public void onMicrophoneClick() {
  326. if (EffortlessPermissions.hasPermissions(this, PERMISSIONS_MICROPHONE)) {
  327. audioOn = !audioOn;
  328. if (audioOn) {
  329. microphoneControlButton.setImageResource(R.drawable.ic_mic_white_24px);
  330. } else {
  331. microphoneControlButton.setImageResource(R.drawable.ic_mic_off_white_24px);
  332. }
  333. toggleMedia(audioOn, false);
  334. } else if (EffortlessPermissions.somePermissionPermanentlyDenied(this, PERMISSIONS_MICROPHONE)) {
  335. // Microphone permission is permanently denied so we cannot request it normally.
  336. OpenAppDetailsDialogFragment.show(
  337. R.string.nc_microphone_permission_permanently_denied,
  338. R.string.nc_permissions_settings, this);
  339. } else {
  340. EffortlessPermissions.requestPermissions(this, R.string.nc_permissions_audio,
  341. 100, PERMISSIONS_MICROPHONE);
  342. }
  343. }
  344. @OnClick(R.id.call_control_hangup)
  345. public void onHangupClick() {
  346. hangup(false);
  347. }
  348. @OnClick(R.id.call_control_camera)
  349. public void onCameraClick() {
  350. if (EffortlessPermissions.hasPermissions(this, PERMISSIONS_CAMERA)) {
  351. videoOn = !videoOn;
  352. if (videoOn) {
  353. cameraControlButton.setImageResource(R.drawable.ic_videocam_white_24px);
  354. } else {
  355. cameraControlButton.setImageResource(R.drawable.ic_videocam_off_white_24px);
  356. }
  357. toggleMedia(videoOn, true);
  358. } else if (EffortlessPermissions.somePermissionPermanentlyDenied(this, PERMISSIONS_CAMERA)) {
  359. // Camera permission is permanently denied so we cannot request it normally.
  360. OpenAppDetailsDialogFragment.show(
  361. R.string.nc_camera_permission_permanently_denied,
  362. R.string.nc_permissions_settings, this);
  363. } else {
  364. EffortlessPermissions.requestPermissions(this, R.string.nc_permissions_video,
  365. 100, PERMISSIONS_CAMERA);
  366. }
  367. }
  368. @OnClick(R.id.call_control_switch_camera)
  369. public void switchCamera() {
  370. CameraVideoCapturer cameraVideoCapturer = (CameraVideoCapturer) videoCapturer;
  371. cameraVideoCapturer.switchCamera(null);
  372. }
  373. private void createCameraEnumerator() {
  374. if (Camera2Enumerator.isSupported(this)) {
  375. cameraEnumerator = new Camera2Enumerator(this);
  376. } else {
  377. cameraEnumerator = new Camera1Enumerator(true);
  378. }
  379. }
  380. private VideoCapturer createCameraCapturer(CameraEnumerator enumerator) {
  381. final String[] deviceNames = enumerator.getDeviceNames();
  382. // First, try to find front facing camera
  383. Logging.d(TAG, "Looking for front facing cameras.");
  384. for (String deviceName : deviceNames) {
  385. if (enumerator.isFrontFacing(deviceName)) {
  386. Logging.d(TAG, "Creating front facing camera capturer.");
  387. VideoCapturer videoCapturer = enumerator.createCapturer(deviceName, null);
  388. if (videoCapturer != null) {
  389. return videoCapturer;
  390. }
  391. }
  392. }
  393. // Front facing camera not found, try something else
  394. Logging.d(TAG, "Looking for other cameras.");
  395. for (String deviceName : deviceNames) {
  396. if (!enumerator.isFrontFacing(deviceName)) {
  397. Logging.d(TAG, "Creating other camera capturer.");
  398. VideoCapturer videoCapturer = enumerator.createCapturer(deviceName, null);
  399. if (videoCapturer != null) {
  400. return videoCapturer;
  401. }
  402. }
  403. }
  404. return null;
  405. }
  406. public void initViews() {
  407. if (cameraEnumerator.getDeviceNames().length < 2) {
  408. cameraSwitchButton.setVisibility(View.GONE);
  409. }
  410. // setting this to true because it's not shown by default
  411. pipVideoView.setMirror(true);
  412. pipVideoView.init(rootEglBase.getEglBaseContext(), null);
  413. pipVideoView.setZOrderMediaOverlay(true);
  414. // disabled because it causes some devices to crash
  415. pipVideoView.setEnableHardwareScaler(false);
  416. pipVideoView.setScalingType(RendererCommon.ScalingType.SCALE_ASPECT_FIT);
  417. }
  418. @AfterPermissionGranted(100)
  419. private void checkPermissions() {
  420. if (EffortlessPermissions.hasPermissions(this, PERMISSIONS_CALL)) {
  421. if (!videoOn) {
  422. onCameraClick();
  423. }
  424. if (!audioOn) {
  425. onMicrophoneClick();
  426. }
  427. if (cameraSwitchButton != null && cameraEnumerator.getDeviceNames().length > 1) {
  428. cameraSwitchButton.setVisibility(View.VISIBLE);
  429. }
  430. if (!inCall) {
  431. startCall();
  432. }
  433. } else if (EffortlessPermissions.somePermissionPermanentlyDenied(this,
  434. PERMISSIONS_CALL)) {
  435. if (EffortlessPermissions.hasPermissions(this, PERMISSIONS_CAMERA)) {
  436. if (!videoOn) {
  437. onCameraClick();
  438. }
  439. if (cameraSwitchButton != null && cameraEnumerator.getDeviceNames().length > 1) {
  440. cameraSwitchButton.setVisibility(View.VISIBLE);
  441. }
  442. } else if (!EffortlessPermissions.hasPermissions(this, PERMISSIONS_CAMERA)) {
  443. cameraControlButton.setImageResource(R.drawable.ic_videocam_off_white_24px);
  444. if (cameraSwitchButton != null) {
  445. cameraSwitchButton.setVisibility(View.INVISIBLE);
  446. }
  447. }
  448. if (EffortlessPermissions.hasPermissions(this, PERMISSIONS_MICROPHONE)) {
  449. if (!audioOn) {
  450. onMicrophoneClick();
  451. }
  452. } else if (!EffortlessPermissions.hasPermissions(this, PERMISSIONS_MICROPHONE)) {
  453. microphoneControlButton.setImageResource(R.drawable.ic_mic_off_white_24px);
  454. }
  455. if (!inCall) {
  456. startCall();
  457. }
  458. } else {
  459. EffortlessPermissions.requestPermissions(this, R.string.nc_permissions,
  460. 100, PERMISSIONS_CALL);
  461. }
  462. }
  463. @AfterPermissionDenied(100)
  464. private void onPermissionsDenied() {
  465. if (cameraSwitchButton != null) {
  466. cameraSwitchButton.setVisibility(View.INVISIBLE);
  467. }
  468. if (!inCall) {
  469. startCall();
  470. }
  471. }
  472. private void basicInitialization() {
  473. rootEglBase = EglBase.create();
  474. createCameraEnumerator();
  475. //Initialize PeerConnectionFactory globals.
  476. PeerConnectionFactory.InitializationOptions initializationOptions = PeerConnectionFactory.InitializationOptions
  477. .builder(this)
  478. .setEnableVideoHwAcceleration(true)
  479. .setFieldTrials(null)
  480. .createInitializationOptions();
  481. PeerConnectionFactory.initialize(initializationOptions);
  482. //Create a new PeerConnectionFactory instance.
  483. PeerConnectionFactory.Options options = new PeerConnectionFactory.Options();
  484. peerConnectionFactory = new PeerConnectionFactory(options);
  485. peerConnectionFactory.setVideoHwAccelerationOptions(rootEglBase.getEglBaseContext(),
  486. rootEglBase.getEglBaseContext());
  487. //Create MediaConstraints - Will be useful for specifying video and audio constraints.
  488. audioConstraints = new MediaConstraints();
  489. videoConstraints = new MediaConstraints();
  490. localMediaStream = peerConnectionFactory.createLocalMediaStream("NCMS");
  491. // Create and audio manager that will take care of audio routing,
  492. // audio modes, audio device enumeration etc.
  493. audioManager = MagicAudioManager.create(getApplicationContext());
  494. // Store existing audio settings and change audio mode to
  495. // MODE_IN_COMMUNICATION for best possible VoIP performance.
  496. Log.d(TAG, "Starting the audio manager...");
  497. audioManager.start(new MagicAudioManager.AudioManagerEvents() {
  498. @Override
  499. public void onAudioDeviceChanged(MagicAudioManager.AudioDevice selectedAudioDevice,
  500. Set<MagicAudioManager.AudioDevice> availableAudioDevices) {
  501. onAudioManagerDevicesChanged(selectedAudioDevice, availableAudioDevices);
  502. }
  503. });
  504. iceServers = new ArrayList<>();
  505. //create sdpConstraints
  506. sdpConstraints = new MediaConstraints();
  507. sdpConstraints.mandatory.add(new MediaConstraints.KeyValuePair("OfferToReceiveAudio", "true"));
  508. sdpConstraints.mandatory.add(new MediaConstraints.KeyValuePair("OfferToReceiveVideo", "true"));
  509. sdpConstraints.optional.add(new MediaConstraints.KeyValuePair("internalSctpDataChannels", "true"));
  510. sdpConstraints.optional.add(new MediaConstraints.KeyValuePair("DtlsSrtpKeyAgreement", "true"));
  511. cameraInitialization();
  512. microphoneInitialization();
  513. }
  514. private void cameraInitialization() {
  515. videoCapturer = createCameraCapturer(cameraEnumerator);
  516. //Create a VideoSource instance
  517. videoSource = peerConnectionFactory.createVideoSource(videoCapturer);
  518. localVideoTrack = peerConnectionFactory.createVideoTrack("NCv0", videoSource);
  519. localMediaStream.addTrack(localVideoTrack);
  520. localVideoTrack.setEnabled(false);
  521. //create a videoRenderer based on SurfaceViewRenderer instance
  522. localRenderer = new VideoRenderer(pipVideoView);
  523. // And finally, with our VideoRenderer ready, we
  524. // can add our renderer to the VideoTrack.
  525. localVideoTrack.addRenderer(localRenderer);
  526. }
  527. private void microphoneInitialization() {
  528. //create an AudioSource instance
  529. audioSource = peerConnectionFactory.createAudioSource(audioConstraints);
  530. localAudioTrack = peerConnectionFactory.createAudioTrack("NCa0", audioSource);
  531. localAudioTrack.setEnabled(false);
  532. localMediaStream.addTrack(localAudioTrack);
  533. }
  534. private void startCall() {
  535. inCall = true;
  536. animateCallControls(false, 5000);
  537. startPullingSignalingMessages(false);
  538. //registerNetworkReceiver();
  539. }
  540. @OnClick({R.id.pip_video_view, R.id.remote_renderers_layout})
  541. public void showCallControls() {
  542. if (callControls.getVisibility() != View.VISIBLE) {
  543. animateCallControls(true, 0);
  544. }
  545. }
  546. public void startPullingSignalingMessages(boolean restart) {
  547. if (restart) {
  548. dispose(null);
  549. //hangupNetworkCalls();
  550. }
  551. leavingCall = false;
  552. ncApi.getSignalingSettings(ApiHelper.getCredentials(userEntity.getUsername(), userEntity.getToken()),
  553. ApiHelper.getUrlForSignalingSettings(userEntity.getBaseUrl()))
  554. .subscribeOn(Schedulers.newThread())
  555. .observeOn(AndroidSchedulers.mainThread())
  556. .subscribe(new Observer<SignalingSettingsOverall>() {
  557. @Override
  558. public void onSubscribe(Disposable d) {
  559. }
  560. @Override
  561. public void onNext(SignalingSettingsOverall signalingSettingsOverall) {
  562. IceServer iceServer;
  563. for (int i = 0; i < signalingSettingsOverall.getOcs().getSettings().getStunServers().size();
  564. i++) {
  565. iceServer = signalingSettingsOverall.getOcs().getSettings().getStunServers().get(i);
  566. if (TextUtils.isEmpty(iceServer.getUsername()) || TextUtils.isEmpty(iceServer
  567. .getCredential())) {
  568. iceServers.add(new PeerConnection.IceServer(iceServer.getUrl()));
  569. } else {
  570. iceServers.add(new PeerConnection.IceServer(iceServer.getUrl(),
  571. iceServer.getUsername(), iceServer.getCredential()));
  572. }
  573. }
  574. for (int i = 0; i < signalingSettingsOverall.getOcs().getSettings().getTurnServers().size();
  575. i++) {
  576. iceServer = signalingSettingsOverall.getOcs().getSettings().getTurnServers().get(i);
  577. for (int j = 0; j < iceServer.getUrls().size(); j++) {
  578. if (TextUtils.isEmpty(iceServer.getUsername()) || TextUtils.isEmpty(iceServer
  579. .getCredential())) {
  580. iceServers.add(new PeerConnection.IceServer(iceServer.getUrls().get(j)));
  581. } else {
  582. iceServers.add(new PeerConnection.IceServer(iceServer.getUrls().get(j),
  583. iceServer.getUsername(), iceServer.getCredential()));
  584. }
  585. }
  586. }
  587. if (restart) {
  588. performIceRestart();
  589. } else {
  590. joinRoomAndCall();
  591. }
  592. }
  593. @Override
  594. public void onError(Throwable e) {
  595. }
  596. @Override
  597. public void onComplete() {
  598. }
  599. });
  600. }
  601. private void startVideoCapture() {
  602. if (videoCapturer != null) {
  603. videoCapturer.startCapture(1280, 720, 30);
  604. }
  605. }
  606. private void joinRoomAndCall() {
  607. ncApi.joinRoom(credentials, ApiHelper.getUrlForRoom(userEntity.getBaseUrl(), roomToken))
  608. .subscribeOn(Schedulers.newThread())
  609. .observeOn(AndroidSchedulers.mainThread())
  610. .subscribe(new Observer<CallOverall>() {
  611. @Override
  612. public void onSubscribe(Disposable d) {
  613. }
  614. @Override
  615. public void onNext(CallOverall callOverall) {
  616. ncApi.joinCall(credentials,
  617. ApiHelper.getUrlForCall(userEntity.getBaseUrl(), roomToken))
  618. .subscribeOn(Schedulers.newThread())
  619. .observeOn(AndroidSchedulers.mainThread())
  620. .subscribe(new Observer<GenericOverall>() {
  621. @Override
  622. public void onSubscribe(Disposable d) {
  623. }
  624. @Override
  625. public void onNext(GenericOverall genericOverall) {
  626. callSession = callOverall.getOcs().getData().getSessionId();
  627. // start pinging the call
  628. ncApi.pingCall(ApiHelper.getCredentials(userEntity.getUsername(), userEntity.getToken()),
  629. ApiHelper.getUrlForCallPing(userEntity.getBaseUrl(), roomToken))
  630. .subscribeOn(Schedulers.newThread())
  631. .observeOn(AndroidSchedulers.mainThread())
  632. .repeatWhen(completed -> completed.delay(5000, TimeUnit.MILLISECONDS))
  633. .repeatUntil(booleanSupplier)
  634. .retry(3)
  635. .subscribe(new Observer<GenericOverall>() {
  636. @Override
  637. public void onSubscribe(Disposable d) {
  638. pingDisposable = d;
  639. }
  640. @Override
  641. public void onNext(GenericOverall genericOverall) {
  642. }
  643. @Override
  644. public void onError(Throwable e) {
  645. dispose(pingDisposable);
  646. }
  647. @Override
  648. public void onComplete() {
  649. dispose(pingDisposable);
  650. }
  651. });
  652. // Start pulling signaling messages
  653. ncApi.pullSignalingMessages(ApiHelper.getCredentials(userEntity.getUsername(),
  654. userEntity.getToken()), ApiHelper.getUrlForSignaling(userEntity.getBaseUrl()))
  655. .subscribeOn(Schedulers.newThread())
  656. .observeOn(AndroidSchedulers.mainThread())
  657. .repeatWhen(observable -> observable.delay(1500,
  658. TimeUnit.MILLISECONDS))
  659. .repeatUntil(booleanSupplier)
  660. .retry(3)
  661. .subscribe(new Observer<SignalingOverall>() {
  662. @Override
  663. public void onSubscribe(Disposable d) {
  664. signalingDisposable = d;
  665. }
  666. @Override
  667. public void onNext(SignalingOverall signalingOverall) {
  668. if (signalingOverall.getOcs().getSignalings() != null) {
  669. for (int i = 0; i < signalingOverall.getOcs().getSignalings().size(); i++) {
  670. try {
  671. receivedSignalingMessage(signalingOverall.getOcs().getSignalings().get(i));
  672. } catch (IOException e) {
  673. e.printStackTrace();
  674. }
  675. }
  676. }
  677. }
  678. @Override
  679. public void onError(Throwable e) {
  680. Log.d("MARIO_DEBUG", e.getLocalizedMessage());
  681. dispose(signalingDisposable);
  682. }
  683. @Override
  684. public void onComplete() {
  685. dispose(signalingDisposable);
  686. }
  687. });
  688. }
  689. @Override
  690. public void onError(Throwable e) {
  691. Log.d("MARIO_DEBUG", e.getLocalizedMessage());
  692. }
  693. @Override
  694. public void onComplete() {
  695. }
  696. });
  697. }
  698. @Override
  699. public void onError(Throwable e) {
  700. }
  701. @Override
  702. public void onComplete() {
  703. }
  704. });
  705. }
  706. private void receivedSignalingMessage(Signaling signaling) throws IOException {
  707. String messageType = signaling.getType();
  708. if (leavingCall) {
  709. return;
  710. }
  711. if ("usersInRoom".equals(messageType)) {
  712. processUsersInRoom((List<HashMap<String, String>>) signaling.getMessageWrapper());
  713. } else if ("message".equals(messageType)) {
  714. NCSignalingMessage ncSignalingMessage = LoganSquare.parse(signaling.getMessageWrapper().toString(),
  715. NCSignalingMessage.class);
  716. if (ncSignalingMessage.getRoomType().equals("video")) {
  717. MagicPeerConnectionWrapper magicPeerConnectionWrapper = alwaysGetPeerConnectionWrapperForSessionId
  718. (ncSignalingMessage.getFrom());
  719. String type = null;
  720. if (ncSignalingMessage.getPayload() != null && ncSignalingMessage.getPayload().getType() !=
  721. null) {
  722. type = ncSignalingMessage.getPayload().getType();
  723. } else if (ncSignalingMessage.getType() != null) {
  724. type = ncSignalingMessage.getType();
  725. }
  726. if (type != null) {
  727. switch (type) {
  728. case "offer":
  729. case "answer":
  730. magicPeerConnectionWrapper.setNick(ncSignalingMessage.getPayload().getNick());
  731. String sessionDescriptionStringWithPreferredCodec = MagicWebRTCUtils.preferCodec
  732. (ncSignalingMessage.getPayload().getSdp(),
  733. "VP8", false);
  734. SessionDescription sessionDescriptionWithPreferredCodec = new SessionDescription(
  735. SessionDescription.Type.fromCanonicalForm(type),
  736. sessionDescriptionStringWithPreferredCodec);
  737. magicPeerConnectionWrapper.getPeerConnection().setRemoteDescription(magicPeerConnectionWrapper
  738. .getMagicSdpObserver(), sessionDescriptionWithPreferredCodec);
  739. break;
  740. case "candidate":
  741. NCIceCandidate ncIceCandidate = ncSignalingMessage.getPayload().getIceCandidate();
  742. IceCandidate iceCandidate = new IceCandidate(ncIceCandidate.getSdpMid(),
  743. ncIceCandidate.getSdpMLineIndex(), ncIceCandidate.getCandidate());
  744. magicPeerConnectionWrapper.addCandidate(iceCandidate);
  745. break;
  746. case "endOfCandidates":
  747. magicPeerConnectionWrapper.drainIceCandidates();
  748. break;
  749. default:
  750. break;
  751. }
  752. }
  753. } else {
  754. Log.d(TAG, "Something went very very wrong");
  755. }
  756. } else {
  757. Log.d(TAG, "Something went very very wrong");
  758. }
  759. }
  760. // This method is called when the audio manager reports audio device change,
  761. // e.g. from wired headset to speakerphone.
  762. private void onAudioManagerDevicesChanged(
  763. final MagicAudioManager.AudioDevice device, final Set<MagicAudioManager.AudioDevice> availableDevices) {
  764. Log.d(TAG, "onAudioManagerDevicesChanged: " + availableDevices + ", "
  765. + "selected: " + device);
  766. }
  767. private void processUsersInRoom(List<HashMap<String, String>> users) {
  768. List<String> newSessions = new ArrayList<>();
  769. Set<String> oldSesssions = new HashSet<>();
  770. for (HashMap<String, String> participant : users) {
  771. if (!participant.get("sessionId").equals(callSession)) {
  772. Object inCallObject = participant.get("inCall");
  773. if ((boolean) inCallObject) {
  774. newSessions.add(participant.get("sessionId"));
  775. } else {
  776. oldSesssions.add(participant.get("sessionId"));
  777. }
  778. }
  779. }
  780. for (MagicPeerConnectionWrapper magicPeerConnectionWrapper : magicPeerConnectionWrapperList) {
  781. oldSesssions.add(magicPeerConnectionWrapper.getSessionId());
  782. }
  783. // Calculate sessions that left the call
  784. oldSesssions.removeAll(newSessions);
  785. // Calculate sessions that join the call
  786. newSessions.removeAll(oldSesssions);
  787. if (leavingCall) {
  788. return;
  789. }
  790. for (String sessionId : newSessions) {
  791. alwaysGetPeerConnectionWrapperForSessionId(sessionId);
  792. }
  793. for (String sessionId : oldSesssions) {
  794. endPeerConnection(sessionId);
  795. }
  796. }
  797. private void deleteMagicPeerConnection(MagicPeerConnectionWrapper magicPeerConnectionWrapper) {
  798. magicPeerConnectionWrapper.removePeerConnection(magicPeerConnectionWrapperList.size() == 1);
  799. magicPeerConnectionWrapperList.remove(magicPeerConnectionWrapper);
  800. }
  801. private MagicPeerConnectionWrapper alwaysGetPeerConnectionWrapperForSessionId(String sessionId) {
  802. MagicPeerConnectionWrapper magicPeerConnectionWrapper;
  803. if ((magicPeerConnectionWrapper = getPeerConnectionWrapperForSessionId(sessionId)) != null) {
  804. return magicPeerConnectionWrapper;
  805. } else {
  806. magicPeerConnectionWrapper = new MagicPeerConnectionWrapper(peerConnectionFactory,
  807. iceServers, sdpConstraints, sessionId, callSession, localMediaStream);
  808. magicPeerConnectionWrapperList.add(magicPeerConnectionWrapper);
  809. return magicPeerConnectionWrapper;
  810. }
  811. }
  812. private MagicPeerConnectionWrapper getPeerConnectionWrapperForSessionId(String sessionId) {
  813. for (MagicPeerConnectionWrapper magicPeerConnectionWrapper : magicPeerConnectionWrapperList) {
  814. if (magicPeerConnectionWrapper.getSessionId().equals(sessionId)) {
  815. return magicPeerConnectionWrapper;
  816. }
  817. }
  818. return null;
  819. }
  820. private void hangup(boolean dueToNetworkChange) {
  821. leavingCall = true;
  822. inCall = false;
  823. dispose(null);
  824. for (int i = 0; i < magicPeerConnectionWrapperList.size(); i++) {
  825. endPeerConnection(magicPeerConnectionWrapperList.get(i).getSessionId());
  826. }
  827. if (!dueToNetworkChange) {
  828. pipVideoView.release();
  829. if (localMediaStream != null) {
  830. if (localMediaStream.videoTracks != null && localMediaStream.videoTracks.size() > 0) {
  831. localMediaStream.removeTrack(localMediaStream.videoTracks.get(0));
  832. }
  833. if (localMediaStream.audioTracks != null && localMediaStream.audioTracks.size() > 0) {
  834. localMediaStream.removeTrack(localMediaStream.audioTracks.get(0));
  835. }
  836. }
  837. localVideoTrack = null;
  838. localAudioTrack = null;
  839. localRenderer = null;
  840. localMediaStream = null;
  841. if (peerConnectionFactory != null) {
  842. peerConnectionFactory.dispose();
  843. peerConnectionFactory = null;
  844. }
  845. if (videoCapturer != null) {
  846. videoCapturer.dispose();
  847. videoCapturer = null;
  848. }
  849. rootEglBase.release();
  850. hangupNetworkCalls();
  851. }
  852. }
  853. private void hangupNetworkCalls() {
  854. String credentials = ApiHelper.getCredentials(userEntity.getUsername(), userEntity.getToken());
  855. ncApi.leaveCall(credentials, ApiHelper.getUrlForCall(userEntity.getBaseUrl(), roomToken))
  856. .subscribeOn(Schedulers.newThread())
  857. .observeOn(AndroidSchedulers.mainThread())
  858. .subscribe(new Observer<GenericOverall>() {
  859. @Override
  860. public void onSubscribe(Disposable d) {
  861. }
  862. @Override
  863. public void onNext(GenericOverall genericOverall) {
  864. ncApi.leaveRoom(credentials, ApiHelper.getUrlForRoom(userEntity.getBaseUrl(), roomToken))
  865. .subscribeOn(Schedulers.newThread())
  866. .observeOn(AndroidSchedulers.mainThread())
  867. .subscribe(new Observer<GenericOverall>() {
  868. @Override
  869. public void onSubscribe(Disposable d) {
  870. }
  871. @Override
  872. public void onNext(GenericOverall genericOverall) {
  873. finish();
  874. }
  875. @Override
  876. public void onError(Throwable e) {
  877. }
  878. @Override
  879. public void onComplete() {
  880. }
  881. });
  882. }
  883. @Override
  884. public void onError(Throwable e) {
  885. }
  886. @Override
  887. public void onComplete() {
  888. }
  889. });
  890. }
  891. private void gotNick(String sessionId, String nick) {
  892. RelativeLayout relativeLayout = remoteRenderersLayout.findViewWithTag(sessionId);
  893. if (relativeLayout != null) {
  894. TextView textView = relativeLayout.findViewById(R.id.peer_nick_text_view);
  895. textView.setText(nick);
  896. }
  897. }
  898. private void gotAudioOrVideoChange(boolean video, String sessionId, boolean change) {
  899. RelativeLayout relativeLayout = remoteRenderersLayout.findViewWithTag(sessionId);
  900. if (relativeLayout != null) {
  901. ImageView imageView;
  902. if (video) {
  903. imageView = relativeLayout.findViewById(R.id.remote_video_off);
  904. } else {
  905. imageView = relativeLayout.findViewById(R.id.remote_audio_off);
  906. }
  907. if (change && imageView.getVisibility() != View.INVISIBLE) {
  908. imageView.setVisibility(View.INVISIBLE);
  909. } else if (!change && imageView.getVisibility() != View.VISIBLE) {
  910. imageView.setVisibility(View.VISIBLE);
  911. }
  912. }
  913. }
  914. private void gotRemoteStream(MediaStream stream, String session) {
  915. removeMediaStream(session);
  916. if (stream.videoTracks.size() == 1) {
  917. VideoTrack videoTrack = stream.videoTracks.get(0);
  918. try {
  919. RelativeLayout relativeLayout = (RelativeLayout)
  920. getLayoutInflater().inflate(R.layout.surface_renderer, remoteRenderersLayout,
  921. false);
  922. relativeLayout.setTag(session);
  923. SurfaceViewRenderer surfaceViewRenderer = relativeLayout.findViewById(R.id
  924. .surface_view);
  925. surfaceViewRenderer.setMirror(false);
  926. surfaceViewRenderer.init(rootEglBase.getEglBaseContext(), null);
  927. surfaceViewRenderer.setZOrderMediaOverlay(false);
  928. // disabled because it causes some devices to crash
  929. surfaceViewRenderer.setEnableHardwareScaler(false);
  930. surfaceViewRenderer.setScalingType(RendererCommon.ScalingType.SCALE_ASPECT_FIT);
  931. VideoRenderer remoteRenderer = new VideoRenderer(surfaceViewRenderer);
  932. videoTrack.addRenderer(remoteRenderer);
  933. remoteRenderersLayout.addView(relativeLayout);
  934. gotNick(session, getPeerConnectionWrapperForSessionId(session).getNick());
  935. } catch (Exception e) {
  936. Log.d(TAG, "Failed to create a new video view");
  937. }
  938. }
  939. }
  940. @Override
  941. public void onDestroy() {
  942. if (inCall) {
  943. hangup(false);
  944. }
  945. //this.unregisterReceiver(networkBroadcastReceier);
  946. super.onDestroy();
  947. }
  948. private void dispose(@Nullable Disposable disposable) {
  949. if (disposable != null && !disposable.isDisposed()) {
  950. disposable.dispose();
  951. } else if (disposable == null) {
  952. if (pingDisposable != null && !pingDisposable.isDisposed()) {
  953. pingDisposable.dispose();
  954. pingDisposable = null;
  955. }
  956. if (signalingDisposable != null && !signalingDisposable.isDisposed()) {
  957. signalingDisposable.dispose();
  958. signalingDisposable = null;
  959. }
  960. }
  961. }
  962. @Override
  963. public void onStart() {
  964. super.onStart();
  965. eventBus.register(this);
  966. if (videoOn && EffortlessPermissions.hasPermissions(this, PERMISSIONS_CAMERA)) {
  967. startVideoCapture();
  968. }
  969. }
  970. @Override
  971. public void onStop() {
  972. super.onStop();
  973. eventBus.unregister(this);
  974. if (videoCapturer != null && EffortlessPermissions.hasPermissions(this, PERMISSIONS_CAMERA)) {
  975. try {
  976. videoCapturer.stopCapture();
  977. } catch (InterruptedException e) {
  978. Log.e(TAG, "Failed to stop the capturing process");
  979. }
  980. }
  981. }
  982. @Subscribe(threadMode = ThreadMode.BACKGROUND)
  983. public void onMessageEvent(PeerConnectionEvent peerConnectionEvent) {
  984. if (peerConnectionEvent.getPeerConnectionEventType().equals(PeerConnectionEvent.PeerConnectionEventType
  985. .CLOSE_PEER)) {
  986. endPeerConnection(peerConnectionEvent.getSessionId());
  987. } else if (peerConnectionEvent.getPeerConnectionEventType().equals(PeerConnectionEvent
  988. .PeerConnectionEventType.SENSOR_FAR) ||
  989. peerConnectionEvent.getPeerConnectionEventType().equals(PeerConnectionEvent
  990. .PeerConnectionEventType.SENSOR_NEAR)) {
  991. boolean enableVideo = peerConnectionEvent.getPeerConnectionEventType().equals(PeerConnectionEvent
  992. .PeerConnectionEventType.SENSOR_FAR) && videoOn;
  993. if (EffortlessPermissions.hasPermissions(this, PERMISSIONS_CAMERA) && inCall && videoOn) {
  994. runOnUiThread(() -> toggleMedia(enableVideo, true));
  995. }
  996. } else if (peerConnectionEvent.getPeerConnectionEventType().equals(PeerConnectionEvent
  997. .PeerConnectionEventType.NICK_CHANGE)) {
  998. runOnUiThread(() -> gotNick(peerConnectionEvent.getSessionId(), peerConnectionEvent.getNick()));
  999. } else if (peerConnectionEvent.getPeerConnectionEventType().equals(PeerConnectionEvent
  1000. .PeerConnectionEventType.VIDEO_CHANGE)) {
  1001. runOnUiThread(() -> gotAudioOrVideoChange(true, peerConnectionEvent.getSessionId(),
  1002. peerConnectionEvent.getChangeValue()));
  1003. } else if (peerConnectionEvent.getPeerConnectionEventType().equals(PeerConnectionEvent
  1004. .PeerConnectionEventType.AUDIO_CHANGE)) {
  1005. runOnUiThread(() -> gotAudioOrVideoChange(false, peerConnectionEvent.getSessionId(),
  1006. peerConnectionEvent.getChangeValue()));
  1007. }
  1008. }
  1009. private void endPeerConnection(String sessionId) {
  1010. MagicPeerConnectionWrapper magicPeerConnectionWrapper;
  1011. if ((magicPeerConnectionWrapper = getPeerConnectionWrapperForSessionId(sessionId)) != null) {
  1012. runOnUiThread(() -> removeMediaStream(sessionId));
  1013. deleteMagicPeerConnection(magicPeerConnectionWrapper);
  1014. }
  1015. }
  1016. private void removeMediaStream(String sessionId) {
  1017. if (remoteRenderersLayout.getChildCount() > 0) {
  1018. RelativeLayout relativeLayout = remoteRenderersLayout.findViewWithTag(sessionId);
  1019. if (relativeLayout != null) {
  1020. SurfaceViewRenderer surfaceViewRenderer = relativeLayout.findViewById(R.id.surface_view);
  1021. surfaceViewRenderer.release();
  1022. remoteRenderersLayout.removeView(relativeLayout);
  1023. remoteRenderersLayout.invalidate();
  1024. }
  1025. }
  1026. }
  1027. @Subscribe(threadMode = ThreadMode.MAIN)
  1028. public void onMessageEvent(MediaStreamEvent mediaStreamEvent) {
  1029. if (mediaStreamEvent.getMediaStream() != null) {
  1030. gotRemoteStream(mediaStreamEvent.getMediaStream(), mediaStreamEvent.getSession());
  1031. } else {
  1032. removeMediaStream(mediaStreamEvent.getSession());
  1033. }
  1034. }
  1035. @Subscribe(threadMode = ThreadMode.BACKGROUND)
  1036. public void onMessageEvent(SessionDescriptionSendEvent sessionDescriptionSend) throws IOException {
  1037. String credentials = ApiHelper.getCredentials(userEntity.getUsername(), userEntity.getToken());
  1038. NCMessageWrapper ncMessageWrapper = new NCMessageWrapper();
  1039. ncMessageWrapper.setEv("message");
  1040. ncMessageWrapper.setSessionId(callSession);
  1041. NCSignalingMessage ncSignalingMessage = new NCSignalingMessage();
  1042. ncSignalingMessage.setTo(sessionDescriptionSend.getPeerId());
  1043. ncSignalingMessage.setRoomType("video");
  1044. ncSignalingMessage.setType(sessionDescriptionSend.getType());
  1045. NCMessagePayload ncMessagePayload = new NCMessagePayload();
  1046. ncMessagePayload.setType(sessionDescriptionSend.getType());
  1047. if (!"candidate".equals(sessionDescriptionSend.getType())) {
  1048. ncMessagePayload.setSdp(sessionDescriptionSend.getSessionDescription().description);
  1049. ncMessagePayload.setNick(userEntity.getDisplayName());
  1050. } else {
  1051. ncMessagePayload.setIceCandidate(sessionDescriptionSend.getNcIceCandidate());
  1052. }
  1053. // Set all we need
  1054. ncSignalingMessage.setPayload(ncMessagePayload);
  1055. ncMessageWrapper.setSignalingMessage(ncSignalingMessage);
  1056. StringBuilder stringBuilder = new StringBuilder();
  1057. stringBuilder.append("{");
  1058. stringBuilder.append("\"fn\":\"");
  1059. stringBuilder.append(StringEscapeUtils.escapeJson(LoganSquare.serialize(ncMessageWrapper
  1060. .getSignalingMessage()))).append("\"");
  1061. stringBuilder.append(",");
  1062. stringBuilder.append("\"sessionId\":");
  1063. stringBuilder.append("\"").append(StringEscapeUtils.escapeJson(callSession)).append("\"");
  1064. stringBuilder.append(",");
  1065. stringBuilder.append("\"ev\":\"message\"");
  1066. stringBuilder.append("}");
  1067. List<String> strings = new ArrayList<>();
  1068. String stringToSend = stringBuilder.toString();
  1069. strings.add(stringToSend);
  1070. ncApi.sendSignalingMessages(credentials, ApiHelper.getUrlForSignaling(userEntity.getBaseUrl()),
  1071. strings.toString())
  1072. .retry(3)
  1073. .subscribeOn(Schedulers.newThread())
  1074. .subscribe(new Observer<SignalingOverall>() {
  1075. @Override
  1076. public void onSubscribe(Disposable d) {
  1077. }
  1078. @Override
  1079. public void onNext(SignalingOverall signalingOverall) {
  1080. if (signalingOverall.getOcs().getSignalings() != null) {
  1081. for (int i = 0; i < signalingOverall.getOcs().getSignalings().size(); i++) {
  1082. try {
  1083. receivedSignalingMessage(signalingOverall.getOcs().getSignalings().get(i));
  1084. } catch (IOException e) {
  1085. e.printStackTrace();
  1086. }
  1087. }
  1088. }
  1089. }
  1090. @Override
  1091. public void onError(Throwable e) {
  1092. }
  1093. @Override
  1094. public void onComplete() {
  1095. }
  1096. });
  1097. }
  1098. @Override
  1099. public void onConfigurationChanged(Configuration newConfig) {
  1100. // Checks the orientation of the screen
  1101. if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {
  1102. remoteRenderersLayout.setOrientation(LinearLayout.HORIZONTAL);
  1103. } else if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) {
  1104. remoteRenderersLayout.setOrientation(LinearLayout.VERTICAL);
  1105. }
  1106. super.onConfigurationChanged(newConfig);
  1107. }
  1108. @Override
  1109. public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
  1110. @NonNull int[] grantResults) {
  1111. super.onRequestPermissionsResult(requestCode, permissions, grantResults);
  1112. EffortlessPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults,
  1113. this);
  1114. }
  1115. private void registerNetworkReceiver() {
  1116. IntentFilter intentFilter = new IntentFilter();
  1117. intentFilter.addAction("android.net.conn.CONNECTIVITY_CHANGE");
  1118. intentFilter.addAction("android.net.wifi.STATE_CHANGE");
  1119. this.registerReceiver(networkBroadcastReceier, intentFilter);
  1120. }
  1121. private void animateCallControls(boolean show, long startDelay) {
  1122. float alpha;
  1123. long duration;
  1124. if (show) {
  1125. alpha = 1.0f;
  1126. duration = 1000;
  1127. } else {
  1128. alpha = 0.0f;
  1129. duration = 2500;
  1130. }
  1131. callControls.animate()
  1132. .translationY(0)
  1133. .alpha(alpha)
  1134. .setDuration(duration)
  1135. .setStartDelay(startDelay)
  1136. .setListener(new AnimatorListenerAdapter() {
  1137. @Override
  1138. public void onAnimationEnd(Animator animation) {
  1139. super.onAnimationEnd(animation);
  1140. if (callControls != null) {
  1141. if (!show) {
  1142. callControls.setVisibility(View.INVISIBLE);
  1143. } else {
  1144. callControls.setVisibility(View.VISIBLE);
  1145. animateCallControls(false, 10000);
  1146. }
  1147. }
  1148. }
  1149. });
  1150. }
  1151. @Override
  1152. public void onBackPressed() {
  1153. hangup(false);
  1154. }
  1155. }