org.webrtc.SurfaceViewRenderer - java examples

Here are the examples of the java api org.webrtc.SurfaceViewRenderer taken from open source projects. By voting up you can indicate which examples are most useful and appropriate.

57 Examples 7

19 View Complete Implementation : MainActivity.java
Copyright Apache License 2.0
Author : open-webrtc-toolkit
public clreplaced MainActivity extends AppCompatActivity implements VideoFragment.VideoFragmentListener, ActivityCompat.OnRequestPermissionsResultCallback, ConferenceClient.ConferenceClientObserver {

    static final int STATS_INTERVAL_MS = 5000;

    private static final String TAG = "OWT_CONF";

    private static final int OWT_REQUEST_CODE = 100;

    private static boolean contextHasInitialized = false;

    EglBase rootEglBase;

    private boolean fullScreen = false;

    private boolean settingsCurrent = false;

    private ExecutorService executor = Executors.newSingleThreadExecutor();

    private Timer statsTimer;

    private LoginFragment loginFragment;

    private VideoFragment videoFragment;

    private SettingsFragment settingsFragment;

    private View fragmentContainer;

    private View bottomView;

    private Button leftBtn, rightBtn, middleBtn, subscribeBtn, unSubscribeBtn;

    private ConferenceClient conferenceClient;

    private ConferenceInfo conferenceInfo;

    private Publication publication;

    private Subscription subscription;

    private LocalStream localStream;

    private RemoteStream stream2Sub;

    private OwtVideoCapturer capturer;

    private LocalStream screenStream;

    private OwtScreenCapturer screenCapturer;

    private Publication screenPublication;

    private SurfaceViewRenderer localRenderer, remoteRenderer;

    private RemoteStream remoteForwardStream = null;

    private int subscribeRemoteStreamChoice = 0;

    private int subscribeVideoCodecChoice = 0;

    private int subscribeSimulcastRidChoice = 0;

    private ArrayList<String> remoteStreamIdList = new ArrayList<>();

    private HashMap<String, RemoteStream> remoteStreamMap = new HashMap<>();

    private HashMap<String, List<String>> videoCodecMap = new HashMap<>();

    private HashMap<String, List<String>> simulcastStreamMap = new HashMap<>();

    private View.OnClickListener screenControl = new View.OnClickListener() {

        @Override
        public void onClick(View v) {
            if (fullScreen) {
                ActionBar actionBar = getSupportActionBar();
                if (actionBar != null) {
                    actionBar.hide();
                }
                bottomView.setVisibility(View.GONE);
                fullScreen = false;
            } else {
                ActionBar actionBar = getSupportActionBar();
                if (actionBar != null) {
                    actionBar.show();
                }
                bottomView.setVisibility(View.VISIBLE);
                fullScreen = true;
            }
        }
    };

    private View.OnClickListener settings = new View.OnClickListener() {

        @Override
        public void onClick(View v) {
            if (settingsCurrent) {
                switchFragment(loginFragment);
                rightBtn.setText(R.string.settings);
            } else {
                if (settingsFragment == null) {
                    settingsFragment = new SettingsFragment();
                }
                switchFragment(settingsFragment);
                rightBtn.setText(R.string.back);
            }
            settingsCurrent = !settingsCurrent;
        }
    };

    private View.OnClickListener subscribe = new View.OnClickListener() {

        @Override
        public void onClick(View v) {
            subscribeRemoteStreamChoice = 0;
            subscribeVideoCodecChoice = 0;
            subscribeSimulcastRidChoice = 0;
            final String[] items = (String[]) remoteStreamIdList.toArray(new String[0]);
            AlertDialog.Builder singleChoiceDialog = new AlertDialog.Builder(MainActivity.this);
            singleChoiceDialog.setreplacedle("Remote Stream List");
            singleChoiceDialog.setSingleChoiceItems(items, 0, (dialog, which) -> subscribeRemoteStreamChoice = which);
            singleChoiceDialog.setPositiveButton("ok", (dialog, which) -> chooseCodec(remoteStreamMap.get(items[subscribeRemoteStreamChoice])));
            singleChoiceDialog.show();
        }
    };

    private View.OnClickListener leaveRoom = new View.OnClickListener() {

        @Override
        public void onClick(View v) {
            executor.execute(() -> conferenceClient.leave());
        }
    };

    private View.OnClickListener unSubscribe = new View.OnClickListener() {

        @Override
        public void onClick(View v) {
            if (remoteForwardStream != null) {
                subscription.stop();
                remoteForwardStream.detach(remoteRenderer);
                runOnUiThread(() -> {
                    unSubscribeBtn.setVisibility(View.GONE);
                    subscribeBtn.setVisibility(View.VISIBLE);
                });
                subscribeRemoteStreamChoice = 0;
                subscribeVideoCodecChoice = 0;
            }
        }
    };

    private View.OnClickListener unpublish = new View.OnClickListener() {

        @Override
        public void onClick(View v) {
            localRenderer.setVisibility(View.GONE);
            rightBtn.setText(R.string.publish);
            rightBtn.setOnClickListener(publish);
            videoFragment.clearStats(true);
            executor.execute(() -> {
                publication.stop();
                localStream.detach(localRenderer);
                capturer.stopCapture();
                capturer.dispose();
                capturer = null;
                localStream.dispose();
                localStream = null;
            });
        }
    };

    private View.OnClickListener publish = new View.OnClickListener() {

        @Override
        public void onClick(View v) {
            rightBtn.setEnabled(false);
            rightBtn.setTextColor(Color.DKGRAY);
            executor.execute(() -> {
                boolean vga = settingsFragment == null || settingsFragment.resolutionVGA;
                boolean isCameraFront = settingsFragment == null || settingsFragment.cameraFront;
                capturer = OwtVideoCapturer.create(vga ? 640 : 1280, vga ? 480 : 720, 30, true, isCameraFront);
                localStream = new LocalStream(capturer, new MediaConstraints.AudioTrackConstraints());
                localStream.attach(localRenderer);
                ActionCallback<Publication> callback = new ActionCallback<Publication>() {

                    @Override
                    public void onSuccess(final Publication result) {
                        runOnUiThread(() -> {
                            localRenderer.setVisibility(View.VISIBLE);
                            rightBtn.setEnabled(true);
                            rightBtn.setTextColor(Color.WHITE);
                            rightBtn.setText(R.string.unpublish);
                            rightBtn.setOnClickListener(unpublish);
                        });
                        publication = result;
                        try {
                            JSONArray mixBody = new JSONArray();
                            JSONObject body = new JSONObject();
                            body.put("op", "add");
                            body.put("path", "/info/inViews");
                            body.put("value", "common");
                            mixBody.put(body);
                            String serverUrl = loginFragment.getServerUrl();
                            String uri = serverUrl + "/rooms/" + conferenceInfo.id() + "/streams/" + result.id();
                            HttpUtils.request(uri, "PATCH", mixBody.toString(), true);
                        } catch (JSONException e) {
                            e.printStackTrace();
                        }
                    }

                    @Override
                    public void onFailure(final OwtError error) {
                        runOnUiThread(() -> {
                            rightBtn.setEnabled(true);
                            rightBtn.setTextColor(Color.WHITE);
                            rightBtn.setText(R.string.publish);
                            Toast.makeText(MainActivity.this, "Failed to publish " + error.errorMessage, Toast.LENGTH_SHORT).show();
                        });
                    }
                };
                conferenceClient.publish(localStream, setPublishOptions(), callback);
            });
        }
    };

    private View.OnClickListener joinRoom = new View.OnClickListener() {

        @Override
        public void onClick(View v) {
            leftBtn.setEnabled(false);
            leftBtn.setTextColor(Color.DKGRAY);
            leftBtn.setText(R.string.connecting);
            rightBtn.setEnabled(false);
            rightBtn.setTextColor(Color.DKGRAY);
            executor.execute(() -> {
                String serverUrl = loginFragment.getServerUrl();
                String roomId = settingsFragment == null ? "" : settingsFragment.getRoomId();
                JSONObject joinBody = new JSONObject();
                try {
                    joinBody.put("role", "presenter");
                    joinBody.put("username", "user");
                    joinBody.put("room", roomId.equals("") ? "" : roomId);
                } catch (JSONException e) {
                    e.printStackTrace();
                }
                String uri = serverUrl + "/createToken/";
                String token = HttpUtils.request(uri, "POST", joinBody.toString(), true);
                conferenceClient.join(token, new ActionCallback<ConferenceInfo>() {

                    @Override
                    public void onSuccess(ConferenceInfo conferenceInfo) {
                        MainActivity.this.conferenceInfo = conferenceInfo;
                        for (RemoteStream remoteStream : conferenceInfo.getRemoteStreams()) {
                            remoteStreamIdList.add(remoteStream.id());
                            remoteStreamMap.put(remoteStream.id(), remoteStream);
                            getParameterByRemoteStream(remoteStream);
                            remoteStream.addObserver(new owt.base.RemoteStream.StreamObserver() {

                                @Override
                                public void onEnded() {
                                    remoteStreamIdList.remove(remoteStream.id());
                                    remoteStreamMap.remove(remoteStream.id());
                                }

                                @Override
                                public void onUpdated() {
                                }
                            });
                        }
                        requestPermission();
                    }

                    @Override
                    public void onFailure(OwtError e) {
                        runOnUiThread(() -> {
                            leftBtn.setEnabled(true);
                            leftBtn.setTextColor(Color.WHITE);
                            leftBtn.setText(R.string.connect);
                            rightBtn.setEnabled(true);
                            rightBtn.setTextColor(Color.WHITE);
                        });
                    }
                });
            });
        }
    };

    private View.OnClickListener shareScreen = new View.OnClickListener() {

        @TargetApi(Build.VERSION_CODES.LOLLIPOP)
        @Override
        public void onClick(View v) {
            middleBtn.setEnabled(false);
            middleBtn.setTextColor(Color.DKGRAY);
            if (middleBtn.getText().equals("ShareScreen")) {
                MediaProjectionManager manager = (MediaProjectionManager) getSystemService(MEDIA_PROJECTION_SERVICE);
                startActivityForResult(manager.createScreenCaptureIntent(), OWT_REQUEST_CODE);
            } else {
                executor.execute(() -> {
                    if (screenPublication != null) {
                        screenPublication.stop();
                        screenPublication = null;
                    }
                });
                middleBtn.setEnabled(true);
                middleBtn.setTextColor(Color.WHITE);
                middleBtn.setText(R.string.share_screen);
            }
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        requestWindowFeature(Window.FEATURE_NO_replacedLE);
        getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
        setContentView(R.layout.activity_main);
        fullScreen = true;
        bottomView = findViewById(R.id.bottom_bar);
        fragmentContainer = findViewById(R.id.fragment_container);
        leftBtn = findViewById(R.id.multi_func_btn_left);
        leftBtn.setOnClickListener(joinRoom);
        rightBtn = findViewById(R.id.multi_func_btn_right);
        rightBtn.setOnClickListener(settings);
        subscribeBtn = findViewById(R.id.multi_func_btn_subscribe);
        subscribeBtn.setOnClickListener(subscribe);
        subscribeBtn.setVisibility(View.GONE);
        unSubscribeBtn = findViewById(R.id.multi_func_btn_unsubscribe);
        unSubscribeBtn.setOnClickListener(unSubscribe);
        unSubscribeBtn.setVisibility(View.GONE);
        middleBtn = findViewById(R.id.multi_func_btn_middle);
        middleBtn.setOnClickListener(shareScreen);
        middleBtn.setVisibility(View.GONE);
        AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
        audioManager.setMode(AudioManager.MODE_IN_COMMUNICATION);
        loginFragment = new LoginFragment();
        switchFragment(loginFragment);
        initConferenceClient();
    }

    @Override
    protected void onPause() {
        if (localStream != null) {
            localStream.detach(localRenderer);
        }
        if (stream2Sub != null) {
            stream2Sub.detach(remoteRenderer);
        }
        super.onPause();
    }

    @Override
    protected void onResume() {
        super.onResume();
        if (localStream != null) {
            localStream.attach(localRenderer);
        }
        if (stream2Sub != null) {
            stream2Sub.attach(remoteRenderer);
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        System.exit(0);
    }

    private void initConferenceClient() {
        rootEglBase = EglBase.create();
        if (!contextHasInitialized) {
            ContextInitialization.create().setApplicationContext(this).addIgnoreNetworkType(ContextInitialization.NetworkType.LOOPBACK).setVideoHardwareAccelerationOptions(rootEglBase.getEglBaseContext(), rootEglBase.getEglBaseContext()).initialize();
            contextHasInitialized = true;
        }
        PeerConnection.IceServer iceServer = PeerConnection.IceServer.builder("turn:example.com?transport=tcp").setUsername("userName").setPreplacedword("preplacedward").createIceServer();
        List<PeerConnection.IceServer> iceServers = new ArrayList<>();
        iceServers.add(iceServer);
        PeerConnection.RTCConfiguration rtcConfiguration = new PeerConnection.RTCConfiguration(iceServers);
        HttpUtils.setUpINSECURESSLContext();
        rtcConfiguration.continualGatheringPolicy = GATHER_CONTINUALLY;
        ConferenceClientConfiguration configuration = ConferenceClientConfiguration.builder().setHostnameVerifier(HttpUtils.hostnameVerifier).setSSLContext(HttpUtils.sslContext).setRTCConfiguration(rtcConfiguration).build();
        conferenceClient = new ConferenceClient(configuration);
        conferenceClient.addObserver(this);
    }

    private void requestPermission() {
        String[] permissions = new String[] { Manifest.permission.CAMERA, Manifest.permission.RECORD_AUDIO, Manifest.permission.READ_EXTERNAL_STORAGE };
        for (String permission : permissions) {
            if (ContextCompat.checkSelfPermission(MainActivity.this, permission) != PERMISSION_GRANTED) {
                ActivityCompat.requestPermissions(MainActivity.this, permissions, OWT_REQUEST_CODE);
                return;
            }
        }
        onConnectSucceed();
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
        if (requestCode == OWT_REQUEST_CODE && grantResults.length == 3 && grantResults[0] == PERMISSION_GRANTED && grantResults[1] == PERMISSION_GRANTED && grantResults[2] == PERMISSION_GRANTED) {
            onConnectSucceed();
        }
    }

    private void onConnectSucceed() {
        runOnUiThread(() -> {
            if (videoFragment == null) {
                videoFragment = new VideoFragment();
            }
            videoFragment.setListener(MainActivity.this);
            switchFragment(videoFragment);
            leftBtn.setEnabled(true);
            leftBtn.setTextColor(Color.WHITE);
            leftBtn.setText(R.string.disconnect);
            leftBtn.setOnClickListener(leaveRoom);
            rightBtn.setEnabled(true);
            rightBtn.setTextColor(Color.WHITE);
            rightBtn.setText(R.string.publish);
            rightBtn.setOnClickListener(publish);
            fragmentContainer.setOnClickListener(screenControl);
            subscribeBtn.setVisibility(View.VISIBLE);
        });
        if (statsTimer != null) {
            statsTimer.cancel();
            statsTimer = null;
        }
        statsTimer = new Timer();
        statsTimer.schedule(new TimerTask() {

            @Override
            public void run() {
                getStats();
            }
        }, 0, STATS_INTERVAL_MS);
    }

    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        screenCapturer = new OwtScreenCapturer(data, 1280, 720);
        screenStream = new LocalStream(screenCapturer, new MediaConstraints.AudioTrackConstraints());
        executor.execute(() -> conferenceClient.publish(screenStream, setPublishOptions(), new ActionCallback<Publication>() {

            @Override
            public void onSuccess(Publication result) {
                runOnUiThread(() -> {
                    middleBtn.setEnabled(true);
                    middleBtn.setTextColor(Color.WHITE);
                    middleBtn.setText(R.string.stop_screen);
                });
                screenPublication = result;
            }

            @Override
            public void onFailure(OwtError error) {
                runOnUiThread(() -> {
                    middleBtn.setEnabled(true);
                    middleBtn.setTextColor(Color.WHITE);
                    middleBtn.setText(R.string.share_screen);
                });
                screenCapturer.stopCapture();
                screenCapturer.dispose();
                screenCapturer = null;
                screenStream.dispose();
                screenStream = null;
            }
        }));
    }

    public PublishOptions setPublishOptions() {
        PublishOptions options = null;
        VideoEncodingParameters h264 = new VideoEncodingParameters(H264);
        VideoEncodingParameters vp8 = new VideoEncodingParameters(VP8);
        VideoEncodingParameters vp9 = new VideoEncodingParameters(VP9);
        VideoEncodingParameters h265 = new VideoEncodingParameters(H265);
        if (settingsFragment != null && settingsFragment.VideoEncodingVP8) {
            options = PublishOptions.builder().addVideoParameter(vp8).build();
        } else if (settingsFragment != null && settingsFragment.VideoEncodingH264) {
            options = PublishOptions.builder().addVideoParameter(h264).build();
        } else if (settingsFragment != null && settingsFragment.VideoEncodingVP9) {
            options = PublishOptions.builder().addVideoParameter(vp9).build();
        } else if (settingsFragment != null && settingsFragment.VideoEncodingH265) {
            options = PublishOptions.builder().addVideoParameter(h265).build();
        } else {
            options = PublishOptions.builder().addVideoParameter(vp8).addVideoParameter(h264).addVideoParameter(vp9).build();
        }
        return options;
    }

    private void getStats() {
        if (publication != null) {
            publication.getStats(new ActionCallback<RTCStatsReport>() {

                @Override
                public void onSuccess(RTCStatsReport result) {
                    videoFragment.updateStats(result, true);
                }

                @Override
                public void onFailure(OwtError error) {
                }
            });
        }
        if (screenPublication != null) {
            screenPublication.getStats(new ActionCallback<RTCStatsReport>() {

                @Override
                public void onSuccess(RTCStatsReport result) {
                    videoFragment.updateStats(result, true);
                }

                @Override
                public void onFailure(OwtError error) {
                }
            });
        }
        if (subscription != null) {
            subscription.getStats(new ActionCallback<RTCStatsReport>() {

                @Override
                public void onSuccess(RTCStatsReport result) {
                    videoFragment.updateStats(result, false);
                }

                @Override
                public void onFailure(OwtError error) {
                }
            });
        }
    }

    private void switchFragment(Fragment fragment) {
        getSupportFragmentManager().beginTransaction().replace(R.id.fragment_container, fragment).commitAllowingStateLoss();
        if (fragment instanceof VideoFragment) {
            middleBtn.setVisibility(View.VISIBLE);
        } else {
            middleBtn.setVisibility(View.GONE);
        }
    }

    public void chooseCodec(RemoteStream remoteStream) {
        List<String> videoCodecList = videoCodecMap.get(remoteStream.id());
        removeDuplicate(videoCodecList);
        final String[] items = videoCodecList.toArray(new String[0]);
        AlertDialog.Builder singleChoiceDialog = new AlertDialog.Builder(MainActivity.this);
        singleChoiceDialog.setreplacedle("VideoCodec List");
        singleChoiceDialog.setSingleChoiceItems(items, 0, (dialog, which) -> subscribeVideoCodecChoice = which);
        singleChoiceDialog.setPositiveButton("ok", (dialog, which) -> {
            String chooseVideoCodec = items[subscribeVideoCodecChoice];
            if (simulcastStreamMap.containsKey(remoteStream.id())) {
                chooseRid(remoteStream, chooseVideoCodec);
            } else {
                subscribeForward(remoteStream, chooseVideoCodec, null);
            }
        });
        singleChoiceDialog.show();
    }

    public void chooseRid(RemoteStream remoteStream, String videoCodec) {
        List<String> ridList = simulcastStreamMap.get(remoteStream.id());
        removeDuplicate(ridList);
        final String[] items = (String[]) ridList.toArray(new String[0]);
        AlertDialog.Builder singleChoiceDialog = new AlertDialog.Builder(MainActivity.this);
        singleChoiceDialog.setreplacedle("Rid List");
        singleChoiceDialog.setSingleChoiceItems(items, 0, (dialog, which) -> subscribeSimulcastRidChoice = which);
        singleChoiceDialog.setPositiveButton("ok", (dialog, which) -> subscribeForward(remoteStream, videoCodec, items[subscribeSimulcastRidChoice]));
        singleChoiceDialog.show();
    }

    public void subscribeForward(RemoteStream remoteStream, String videoCodec, String rid) {
        VideoSubscriptionConstraints.Builder videoOptionBuilder = VideoSubscriptionConstraints.builder();
        VideoCodecParameters vcp = new VideoCodecParameters(H264);
        if (videoCodec.equals("VP8")) {
            vcp = new VideoCodecParameters(VP8);
        } else if (videoCodec.equals("H264")) {
            vcp = new VideoCodecParameters(H264);
        } else if (videoCodec.equals("VP9")) {
            vcp = new VideoCodecParameters(VP9);
        } else if (videoCodec.equals("H265")) {
            vcp = new VideoCodecParameters(H265);
        }
        if (rid != null) {
            videoOptionBuilder.setRid(rid);
        }
        VideoSubscriptionConstraints videoOption = videoOptionBuilder.addCodec(vcp).build();
        AudioSubscriptionConstraints audioOption = AudioSubscriptionConstraints.builder().addCodec(new AudioCodecParameters(OPUS)).addCodec(new AudioCodecParameters(PCMU)).build();
        SubscribeOptions options = SubscribeOptions.builder(true, true).setAudioOption(audioOption).setVideoOption(videoOption).build();
        conferenceClient.subscribe(remoteStream, options, new ActionCallback<Subscription>() {

            @Override
            public void onSuccess(Subscription result) {
                MainActivity.this.subscription = result;
                MainActivity.this.remoteForwardStream = remoteStream;
                remoteStream.attach(remoteRenderer);
                runOnUiThread(() -> {
                    subscribeBtn.setVisibility(View.GONE);
                    unSubscribeBtn.setVisibility(View.VISIBLE);
                });
            }

            @Override
            public void onFailure(OwtError error) {
                Log.e(TAG, "Failed to subscribe " + error.errorMessage);
            }
        });
    }

    @Override
    public void onRenderer(SurfaceViewRenderer localRenderer, SurfaceViewRenderer remoteRenderer) {
        this.localRenderer = localRenderer;
        this.remoteRenderer = remoteRenderer;
    }

    @Override
    public void onStreamAdded(RemoteStream remoteStream) {
        remoteStreamIdList.add(remoteStream.id());
        remoteStreamMap.put(remoteStream.id(), remoteStream);
        getParameterByRemoteStream(remoteStream);
        remoteStream.addObserver(new owt.base.RemoteStream.StreamObserver() {

            @Override
            public void onEnded() {
                remoteStreamIdList.remove(remoteStream.id());
                remoteStreamMap.remove(remoteStream.id());
            }

            @Override
            public void onUpdated() {
                getParameterByRemoteStream(remoteStream);
            }
        });
    }

    @Override
    public void onParticipantJoined(Participant participant) {
    }

    @Override
    public void onMessageReceived(String message, String from, String to) {
    }

    @Override
    public void onServerDisconnected() {
        runOnUiThread(() -> {
            switchFragment(loginFragment);
            leftBtn.setEnabled(true);
            leftBtn.setTextColor(Color.WHITE);
            leftBtn.setText(R.string.connect);
            leftBtn.setOnClickListener(joinRoom);
            rightBtn.setEnabled(true);
            rightBtn.setTextColor(Color.WHITE);
            rightBtn.setText(R.string.settings);
            rightBtn.setOnClickListener(settings);
            subscribeBtn.setVisibility(View.GONE);
            unSubscribeBtn.setVisibility(View.GONE);
            fragmentContainer.setOnClickListener(null);
        });
        if (statsTimer != null) {
            statsTimer.cancel();
            statsTimer = null;
        }
        if (capturer != null) {
            capturer.stopCapture();
            capturer.dispose();
            capturer = null;
        }
        if (localStream != null) {
            localStream.dispose();
            localStream = null;
        }
        publication = null;
        subscription = null;
        stream2Sub = null;
        remoteStreamIdList.clear();
        remoteStreamMap.clear();
    }

    public void getParameterByRemoteStream(RemoteStream remoteStream) {
        List<String> videoCodecList = new ArrayList<>();
        List<String> ridList = new ArrayList<>();
        SubscriptionCapabilities.VideoSubscriptionCapabilities videoSubscriptionCapabilities = remoteStream.extraSubscriptionCapability.videoSubscriptionCapabilities;
        for (VideoCodecParameters videoCodec : videoSubscriptionCapabilities.videoCodecs) {
            videoCodecList.add(videoCodec.name.name());
            videoCodecMap.put(remoteStream.id(), videoCodecList);
        }
        for (PublicationSettings.VideoPublicationSettings videoPublicationSetting : remoteStream.publicationSettings.videoPublicationSettings) {
            if (videoCodecMap.containsKey(remoteStream.id())) {
                videoCodecMap.get(remoteStream.id()).add(videoPublicationSetting.codec.name.name());
            } else {
                videoCodecList.add(videoPublicationSetting.codec.name.name());
                videoCodecMap.put(remoteStream.id(), videoCodecList);
            }
            if (videoPublicationSetting.rid != null) {
                ridList.add(videoPublicationSetting.rid);
            }
        }
        if (ridList.size() != 0) {
            simulcastStreamMap.put(remoteStream.id(), ridList);
        }
    }

    public void removeDuplicate(List<String> list) {
        LinkedHashSet<String> set = new LinkedHashSet<String>(list.size());
        set.addAll(list);
        list.clear();
        list.addAll(set);
    }
}

19 View Complete Implementation : BaseConversationFragment.java
Copyright BSD 3-Clause "New" or "Revised" License
Author : QuickBlox
protected void updateVideoView(SurfaceViewRenderer surfaceViewRenderer, boolean mirror) {
    updateVideoView(surfaceViewRenderer, mirror, RendererCommon.ScalingType.SCALE_ASPECT_FILL);
}

19 View Complete Implementation : CallSession.java
Copyright MIT License
Author : ddssingsong
public void setupRemoteVideo(SurfaceViewRenderer surfaceView) {
    ProxyVideoSink sink = new ProxyVideoSink();
    sink.setTarget(surfaceView);
    if (_remoteStream != null && _remoteStream.videoTracks.size() > 0) {
        _remoteStream.videoTracks.get(0).addSink(sink);
    }
}

19 View Complete Implementation : MXWebRtcView.java
Copyright Apache License 2.0
Author : matrix-org
/**
 * Use the older implementation of WebRtcView.
 * The latest version a stream URL instead of a stream.
 * It implies to have a React context.
 */
public clreplaced MXWebRtcView extends ViewGroup {

    /**
     * The scaling type to be utilized by default.
     * <p>
     * The default value is in accord with
     * https://www.w3.org/TR/html5/embedded-content-0.html#the-video-element:
     * <p>
     * In the absence of style rules to the contrary, video content should be
     * rendered inside the element's playback area such that the video content
     * is shown centered in the playback area at the largest possible size that
     * fits completely within it, with the video content's aspect ratio being
     * preserved. Thus, if the aspect ratio of the playback area does not match
     * the aspect ratio of the video, the video will be shown letterboxed or
     * pillarboxed. Areas of the element's playback area that do not contain the
     * video represent nothing.
     */
    private static final ScalingType DEFAULT_SCALING_TYPE = ScalingType.SCALE_ASPECT_FIT;

    /**
     * {@link View#isInLayout()} as a <tt>Method</tt> to be invoked via
     * reflection in order to accommodate its lack of availability before API
     * level 18. {@link ViewCompat#isInLayout(View)} is the best solution but I
     * could not make it available along with
     * {@link ViewCompat#isAttachedToWindow(View)} at the time of this writing.
     */
    private static final Method IS_IN_LAYOUT;

    private static final String LOG_TAG = MXWebRtcView.clreplaced.getSimpleName();

    static {
        // IS_IN_LAYOUT
        Method isInLayout = null;
        try {
            Method m = MXWebRtcView.clreplaced.getMethod("isInLayout");
            if (boolean.clreplaced.isreplacedignableFrom(m.getReturnType())) {
                isInLayout = m;
            }
        } catch (NoSuchMethodException e) {
        // Fall back to the behavior of ViewCompat#isInLayout(View).
        }
        IS_IN_LAYOUT = isInLayout;
    }

    /**
     * The height of the last video frame rendered by
     * {@link #surfaceViewRenderer}.
     */
    private int frameHeight;

    /**
     * The rotation (degree) of the last video frame rendered by
     * {@link #surfaceViewRenderer}.
     */
    private int frameRotation;

    /**
     * The width of the last video frame rendered by
     * {@link #surfaceViewRenderer}.
     */
    private int frameWidth;

    /**
     * The {@code Object} which synchronizes the access to the layout-related
     * state of this instance such as {@link #frameHeight},
     * {@link #frameRotation}, {@link #frameWidth}, and {@link #scalingType}.
     */
    private final Object layoutSyncRoot = new Object();

    /**
     * The indicator which determines whether this {@code WebRTCView} is to
     * mirror the video represented by {@link #videoTrack} during its rendering.
     */
    private boolean mirror;

    /**
     * The {@code RendererEvents} which listens to rendering events reported by
     * {@link #surfaceViewRenderer}.
     */
    private final RendererEvents rendererEvents = new RendererEvents() {

        @Override
        public void onFirstFrameRendered() {
        }

        @Override
        public void onFrameResolutionChanged(int videoWidth, int videoHeight, int rotation) {
            MXWebRtcView.this.onFrameResolutionChanged(videoWidth, videoHeight, rotation);
        }
    };

    /**
     * The {@code Runnable} representation of
     * {@link #requestSurfaceViewRendererLayout()}. Explicitly defined in order
     * to allow the use of the latter with {@link #post(Runnable)} without
     * initializing new instances on every (method) call.
     */
    private final Runnable requestSurfaceViewRendererLayoutRunnable = new Runnable() {

        @Override
        public void run() {
            requestSurfaceViewRendererLayout();
        }
    };

    /**
     * The scaling type this {@code WebRTCView} is to apply to the video
     * represented by {@link #videoTrack} during its rendering. An expression of
     * the CSS property {@code object-fit} in the terms of WebRTC.
     */
    private ScalingType scalingType;

    /**
     * The {@link View} and {@link VideoRenderer} implementation which
     * actually renders {@link #videoTrack} on behalf of this instance.
     */
    private final SurfaceViewRenderer surfaceViewRenderer;

    /**
     * The {@code VideoTrack}, if any, rendered by this {@code MXWebRTCView}.
     */
    private VideoTrack videoTrack;

    public MXWebRtcView(Context context) {
        super(context);
        surfaceViewRenderer = new SurfaceViewRenderer(context);
        addView(surfaceViewRenderer);
        setMirror(false);
        setScalingType(DEFAULT_SCALING_TYPE);
    }

    /**
     * Gets the {@code SurfaceViewRenderer} which renders {@link #videoTrack}.
     * Explicitly defined and used in order to facilitate switching the instance
     * at compile time. For example, reduces the number of modifications
     * necessary to switch the implementation from a {@code SurfaceViewRenderer}
     * that is a child of a {@code WebRTCView} to {@code WebRTCView} extending
     * {@code SurfaceViewRenderer}.
     *
     * @return The {@code SurfaceViewRenderer} which renders {@code videoTrack}.
     */
    private final SurfaceViewRenderer getSurfaceViewRenderer() {
        return surfaceViewRenderer;
    }

    /**
     * If this <tt>View</tt> has {@link View#isInLayout()}, invokes it and
     * returns its return value; otherwise, returns <tt>false</tt> like
     * {@link ViewCompat#isInLayout(View)}.
     *
     * @return If this <tt>View</tt> has <tt>View#isInLayout()</tt>, invokes it
     *         and returns its return value; otherwise, returns <tt>false</tt>.
     */
    private boolean invokeIsInLayout() {
        Method m = IS_IN_LAYOUT;
        boolean b = false;
        if (m != null) {
            try {
                b = (boolean) m.invoke(this);
            } catch (Throwable e) {
            // Fall back to the behavior of ViewCompat#isInLayout(View).
            }
        }
        return b;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected void onAttachedToWindow() {
        try {
            // Generally, OpenGL is only necessary while this View is attached
            // to a window so there is no point in having the whole rendering
            // infrastructure hooked up while this View is not attached to a
            // window. Additionally, a memory leak was solved in a similar way
            // on iOS.
            tryAddRendererToVideoTrack();
        } catch (Exception e) {
            Log.e(LOG_TAG, "onAttachedToWindow", e);
        } finally {
            super.onAttachedToWindow();
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected void onDetachedFromWindow() {
        try {
            // Generally, OpenGL is only necessary while this View is attached
            // to a window so there is no point in having the whole rendering
            // infrastructure hooked up while this View is not attached to a
            // window. Additionally, a memory leak was solved in a similar way
            // on iOS.
            removeRendererFromVideoTrack();
        } catch (Exception e) {
            Log.e(LOG_TAG, "onAttachedToWindow", e);
        } finally {
            super.onDetachedFromWindow();
        }
    }

    /**
     * Callback fired by {@link #surfaceViewRenderer} when the resolution or
     * rotation of the frame it renders has changed.
     *
     * @param videoWidth  The new width of the rendered video frame.
     * @param videoHeight The new height of the rendered video frame.
     * @param rotation    The new rotation of the rendered video frame.
     */
    private void onFrameResolutionChanged(int videoWidth, int videoHeight, int rotation) {
        boolean changed = false;
        synchronized (layoutSyncRoot) {
            if (frameHeight != videoHeight) {
                frameHeight = videoHeight;
                changed = true;
            }
            if (frameRotation != rotation) {
                frameRotation = rotation;
                changed = true;
            }
            if (frameWidth != videoWidth) {
                frameWidth = videoWidth;
                changed = true;
            }
        }
        if (changed) {
            // The onFrameResolutionChanged method call executes on the
            // surfaceViewRenderer's render Thread.
            post(requestSurfaceViewRendererLayoutRunnable);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        int height = b - t;
        int width = r - l;
        if (height == 0 || width == 0) {
            l = t = r = b = 0;
        } else {
            int frameHeight;
            int frameRotation;
            int frameWidth;
            ScalingType scalingType;
            synchronized (layoutSyncRoot) {
                frameHeight = this.frameHeight;
                frameRotation = this.frameRotation;
                frameWidth = this.frameWidth;
                scalingType = this.scalingType;
            }
            SurfaceViewRenderer surfaceViewRenderer = getSurfaceViewRenderer();
            switch(scalingType) {
                case SCALE_ASPECT_FILL:
                    // Fill this ViewGroup with surfaceViewRenderer and the latter
                    // will take care of filling itself with the video similarly to
                    // the cover value the CSS property object-fit.
                    r = width;
                    l = 0;
                    b = height;
                    t = 0;
                    break;
                case SCALE_ASPECT_FIT:
                default:
                    // Lay surfaceViewRenderer out inside this ViewGroup in accord
                    // with the contain value of the CSS property object-fit.
                    // SurfaceViewRenderer will fill itself with the video similarly
                    // to the cover or contain value of the CSS property object-fit
                    // (which will not matter, eventually).
                    if (frameHeight == 0 || frameWidth == 0) {
                        l = t = r = b = 0;
                    } else {
                        float frameAspectRatio = (frameRotation % 180 == 0) ? frameWidth / (float) frameHeight : frameHeight / (float) frameWidth;
                        Point frameDisplaySize = RendererCommon.getDisplaySize(scalingType, frameAspectRatio, width, height);
                        l = (width - frameDisplaySize.x) / 2;
                        t = (height - frameDisplaySize.y) / 2;
                        r = l + frameDisplaySize.x;
                        b = t + frameDisplaySize.y;
                    }
                    break;
            }
        }
        surfaceViewRenderer.layout(l, t, r, b);
    }

    /**
     * Stops rendering {@link #videoTrack} and releases the replacedociated acquired
     * resources (if rendering is in progress).
     */
    private void removeRendererFromVideoTrack() {
        if (surfaceViewRenderer != null) {
            if (videoTrack != null) {
                videoTrack.removeSink(surfaceViewRenderer);
            }
            getSurfaceViewRenderer().release();
            // Since this WebRTCView is no longer rendering anything, make sure
            // surfaceViewRenderer displays nothing as well.
            synchronized (layoutSyncRoot) {
                frameHeight = 0;
                frameRotation = 0;
                frameWidth = 0;
            }
            requestSurfaceViewRendererLayout();
        }
    }

    /**
     * Request that {@link #surfaceViewRenderer} be laid out (as soon as
     * possible) because layout-related state either of this instance or of
     * {@code surfaceViewRenderer} has changed.
     */
    @SuppressLint("WrongCall")
    private void requestSurfaceViewRendererLayout() {
        // Google/WebRTC just call requestLayout() on surfaceViewRenderer when
        // they change the value of its mirror or surfaceType property.
        getSurfaceViewRenderer().requestLayout();
        // The above is not enough though when the video frame's dimensions or
        // rotation change. The following will suffice.
        if (!invokeIsInLayout()) {
            onLayout(/* changed */
            false, getLeft(), getTop(), getRight(), getBottom());
        }
    }

    /**
     * Sets the indicator which determines whether this {@code WebRTCView} is to
     * mirror the video represented by {@link #videoTrack} during its rendering.
     *
     * @param mirror If this {@code WebRTCView} is to mirror the video
     *               represented by {@code videoTrack} during its rendering, {@code true};
     *               otherwise, {@code false}.
     */
    public void setMirror(boolean mirror) {
        if (this.mirror != mirror) {
            this.mirror = mirror;
            SurfaceViewRenderer surfaceViewRenderer = getSurfaceViewRenderer();
            surfaceViewRenderer.setMirror(mirror);
            // SurfaceViewRenderer takes the value of its mirror property into
            // account upon its layout.
            requestSurfaceViewRendererLayout();
        }
    }

    private void setScalingType(ScalingType scalingType) {
        SurfaceViewRenderer surfaceViewRenderer;
        synchronized (layoutSyncRoot) {
            if (this.scalingType == scalingType) {
                return;
            }
            this.scalingType = scalingType;
            surfaceViewRenderer = getSurfaceViewRenderer();
            surfaceViewRenderer.setScalingType(scalingType);
        }
        // Both this instance ant its SurfaceViewRenderer take the value of
        // their scalingType properties into account upon their layouts.
        requestSurfaceViewRendererLayout();
    }

    /**
     * Sets the {@code MediaStream} to be rendered by this {@code WebRTCView}.
     * The implementation renders the first {@link VideoTrack}, if any, of the
     * specified {@code mediaStream}.
     *
     * @param mediaStream The {@code MediaStream} to be rendered by this
     *                    {@code WebRTCView} or {@code null}.
     */
    public void setStream(MediaStream mediaStream) {
        VideoTrack videoTrack;
        if (mediaStream == null) {
            videoTrack = null;
        } else {
            List<VideoTrack> videoTracks = mediaStream.videoTracks;
            videoTrack = videoTracks.isEmpty() ? null : videoTracks.get(0);
        }
        setVideoTrack(videoTrack);
    }

    /**
     * Sets the {@code VideoTrack} to be rendered by this {@code WebRTCView}.
     *
     * @param videoTrack The {@code VideoTrack} to be rendered by this
     *                   {@code WebRTCView} or {@code null}.
     */
    private void setVideoTrack(VideoTrack videoTrack) {
        VideoTrack oldValue = this.videoTrack;
        if (oldValue != videoTrack) {
            if (oldValue != null) {
                removeRendererFromVideoTrack();
            }
            this.videoTrack = videoTrack;
            if (videoTrack != null) {
                tryAddRendererToVideoTrack();
            }
        }
    }

    /**
     * Sets the z-order of this {@link WebRTCView} in the stacking space of all
     * {@code WebRTCView}s. For more details, refer to the doreplacedentation of the
     * {@code zOrder} property of the JavaScript counterpart of
     * {@code WebRTCView} i.e. {@code RTCView}.
     *
     * @param zOrder The z-order to set on this {@code WebRTCView}.
     */
    public void setZOrder(int zOrder) {
        SurfaceViewRenderer surfaceViewRenderer = getSurfaceViewRenderer();
        switch(zOrder) {
            case 0:
                surfaceViewRenderer.setZOrderMediaOverlay(false);
                break;
            case 1:
                surfaceViewRenderer.setZOrderMediaOverlay(true);
                break;
            case 2:
                surfaceViewRenderer.setZOrderOnTop(true);
                break;
        }
    }

    /**
     * Starts rendering {@link #videoTrack} if rendering is not in progress and
     * all preconditions for the start of rendering are met.
     */
    private void tryAddRendererToVideoTrack() {
        if (videoTrack != null && ViewCompat.isAttachedToWindow(this)) {
            EglBase.Context sharedContext = EglUtils.getRootEglBaseContext();
            if (sharedContext == null) {
                // If SurfaceViewRenderer#init() is invoked, it will throw a
                // RuntimeException which will very likely kill the application.
                Log.e(LOG_TAG, "Failed to render a VideoTrack!");
                return;
            }
            SurfaceViewRenderer surfaceViewRenderer = getSurfaceViewRenderer();
            surfaceViewRenderer.init(sharedContext, rendererEvents);
            videoTrack.addSink(surfaceViewRenderer);
        }
    }
}

19 View Complete Implementation : WebRTCVideoView.java
Copyright Apache License 2.0
Author : shiguredo
public clreplaced WebRTCVideoView extends ViewGroup {

    @NonNull
    final SurfaceViewRenderer surfaceViewRenderer;

    private boolean isSurfaceViewRendererInitialized = false;

    @Nullable
    private VideoTrack videoTrack = null;

    private boolean isVideoTrackRendererAdded = false;

    public WebRTCVideoView(@Nullable final Context context) {
        super(context);
        if (!(context instanceof ThemedReactContext)) {
            throw new IllegalArgumentException("The context to initialize WebRTCVideoView is expected to be an instance of ThemedReactContext.");
        }
        surfaceViewRenderer = new SurfaceViewRenderer(context);
        final LayoutParams lp = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
        addView(surfaceViewRenderer, lp);
    }

    @Override
    protected void onAttachedToWindow() {
        super.onAttachedToWindow();
        if (!isSurfaceViewRendererInitialized) {
            final ThemedReactContext reactContext = getReactContext();
            final WebRTCModule module = reactContext.getNativeModule(WebRTCModule.clreplaced);
            surfaceViewRenderer.init(module.getEglContext(), null);
            isSurfaceViewRendererInitialized = true;
        }
        attachVideoTrackWithRenderer();
    }

    @Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();
        // XXX: このタイミングでViewをクリーンアップします
        // 本来このタイミングではdetachVideoTrackWithRenderer()だけ実施すればいいはずなのですが、
        // JSコンテキストリロード時に本来使用したいWebRTCVideoViewManager.onDropViewInstanceが呼び出されません。
        // なので仕方なくウィンドウからViewが外されたら二度と再利用されないという前提のもとにここでリセットを掛けます。
        // もし万が一Viewを別のWindowやViewGroupの間で移動したりしたときに正しく動作しなくなったらここが原因の可能性が高いです。
        // 基本的に一度捨てたViewを再利用するケースはほぼ無いので安全だとは思いますし、
        // そもそも問題が有るのは適切なタイミングで適切なコールバックを呼び出さないReactNativeですが、他に手がないです。
        release();
    }

    @Override
    protected void onLayout(final boolean changed, final int l, final int t, final int r, final int b) {
        // Do nothing special, just MATCH_PARENT layouting
        // currently the video content mode (fit/fill) is managed by `surfaceViewRenderer.setScalingType()`.
        // We don't have to manually calculate the size of the surfaceViewRenderer here. Just let it MATCH_PARENT/MATCH_PARENT.
        // XXX: そのうち修正しないとダメな可能性が高い、詳細はWebRTCVideoViewManager.objectFitのコメントを参照
        surfaceViewRenderer.layout(l, t, r, b);
    }

    @NonNull
    ThemedReactContext getReactContext() {
        return (ThemedReactContext) getContext();
    }

    void setVideoTrack(@Nullable final VideoTrack videoTrack) {
        // XXX: iOSの実装ではwindowがある場合のみ即座にrendererを紐付け、windowがまだ間に合ってない場合はwindow管理下になってから紐付けるようになっている。
        // Android側の実装でも同様にしないと問題が発生するかどうかをみてから判断する。
        if (this.videoTrack == videoTrack) {
            return;
        }
        detachVideoTrackWithRenderer();
        this.videoTrack = videoTrack;
        attachVideoTrackWithRenderer();
    }

    /**
     * 内部に保持しているSurfaceViewRendererをリリースして開放します。
     * 本メソッドを呼び出すと、それ以降本インスタンスは使用不可能になります。新しいインスタンスを作り直してください。
     * なお内部に抱えているvideoTrackはdispose()されません。そちらは必要に応じて別途開放してください。
     */
    void release() {
        setVideoTrack(null);
        surfaceViewRenderer.release();
    }

    private void attachVideoTrackWithRenderer() {
        if (videoTrack != null && !isVideoTrackRendererAdded) {
            videoTrack.addSink(surfaceViewRenderer);
            isVideoTrackRendererAdded = true;
        }
    }

    private void detachVideoTrackWithRenderer() {
        if (videoTrack != null && isVideoTrackRendererAdded) {
            videoTrack.removeSink(surfaceViewRenderer);
            isVideoTrackRendererAdded = false;
        }
    }
}

19 View Complete Implementation : ChatSingleActivity.java
Copyright MIT License
Author : ddssingsong
/**
 * 单聊界面
 * 1. 一对一视频通话
 * 2. 一对一语音通话
 */
public clreplaced ChatSingleActivity extends AppCompatActivity {

    private SurfaceViewRenderer local_view;

    private SurfaceViewRenderer remote_view;

    private ProxyVideoSink localRender;

    private ProxyVideoSink remoteRender;

    private WebRTCManager manager;

    private boolean videoEnable;

    private boolean isSwappedFeeds;

    private EglBase rootEglBase;

    public static void openActivity(Activity activity, boolean videoEnable) {
        Intent intent = new Intent(activity, ChatSingleActivity.clreplaced);
        intent.putExtra("videoEnable", videoEnable);
        activity.startActivity(intent);
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        requestWindowFeature(Window.FEATURE_NO_replacedLE);
        getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON | WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON);
        super.onCreate(savedInstanceState);
        setContentView(R.layout.wr_activity_chat_single);
        initVar();
        initListener();
    }

    private int previewX, previewY;

    private int moveX, moveY;

    private void initVar() {
        Intent intent = getIntent();
        videoEnable = intent.getBooleanExtra("videoEnable", false);
        ChatSingleFragment chatSingleFragment = new ChatSingleFragment();
        replaceFragment(chatSingleFragment, videoEnable);
        rootEglBase = EglBase.create();
        if (videoEnable) {
            local_view = findViewById(R.id.local_view_render);
            remote_view = findViewById(R.id.remote_view_render);
            // 本地图像初始化
            local_view.init(rootEglBase.getEglBaseContext(), null);
            local_view.setScalingType(RendererCommon.ScalingType.SCALE_ASPECT_FIT);
            local_view.setZOrderMediaOverlay(true);
            local_view.setMirror(true);
            localRender = new ProxyVideoSink();
            // 远端图像初始化
            remote_view.init(rootEglBase.getEglBaseContext(), null);
            remote_view.setScalingType(RendererCommon.ScalingType.SCALE_ASPECT_BALANCED);
            remote_view.setMirror(true);
            remoteRender = new ProxyVideoSink();
            setSwappedFeeds(true);
            local_view.setOnClickListener(v -> setSwappedFeeds(!isSwappedFeeds));
        }
        startCall();
    }

    @SuppressLint("ClickableViewAccessibility")
    private void initListener() {
        if (videoEnable) {
            // 设置小视频可以移动
            local_view.setOnTouchListener((view, motionEvent) -> {
                switch(motionEvent.getAction()) {
                    case MotionEvent.ACTION_DOWN:
                        previewX = (int) motionEvent.getX();
                        previewY = (int) motionEvent.getY();
                        break;
                    case MotionEvent.ACTION_MOVE:
                        int x = (int) motionEvent.getX();
                        int y = (int) motionEvent.getY();
                        moveX = (int) motionEvent.getX();
                        moveY = (int) motionEvent.getY();
                        RelativeLayout.LayoutParams lp = (RelativeLayout.LayoutParams) local_view.getLayoutParams();
                        // Clears the rule, as there is no removeRule until API 17.
                        lp.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM, 0);
                        lp.addRule(RelativeLayout.ALIGN_PARENT_END, 0);
                        lp.addRule(RelativeLayout.ALIGN_PARENT_TOP, 0);
                        lp.addRule(RelativeLayout.ALIGN_PARENT_START, 0);
                        int left = lp.leftMargin + (x - previewX);
                        int top = lp.topMargin + (y - previewY);
                        lp.leftMargin = left;
                        lp.topMargin = top;
                        view.setLayoutParams(lp);
                        break;
                    case MotionEvent.ACTION_UP:
                        if (moveX == 0 && moveY == 0) {
                            view.performClick();
                        }
                        moveX = 0;
                        moveY = 0;
                        break;
                }
                return true;
            });
        }
    }

    private void setSwappedFeeds(boolean isSwappedFeeds) {
        this.isSwappedFeeds = isSwappedFeeds;
        localRender.setTarget(isSwappedFeeds ? remote_view : local_view);
        remoteRender.setTarget(isSwappedFeeds ? local_view : remote_view);
    }

    private void startCall() {
        manager = WebRTCManager.getInstance();
        manager.setCallback(new IViewCallback() {

            @Override
            public void onSetLocalStream(MediaStream stream, String socketId) {
                if (stream.videoTracks.size() > 0) {
                    stream.videoTracks.get(0).addSink(localRender);
                }
                if (videoEnable) {
                    stream.videoTracks.get(0).setEnabled(true);
                }
            }

            @Override
            public void onAddRemoteStream(MediaStream stream, String socketId) {
                if (stream.videoTracks.size() > 0) {
                    stream.videoTracks.get(0).addSink(remoteRender);
                }
                if (videoEnable) {
                    stream.videoTracks.get(0).setEnabled(true);
                    runOnUiThread(() -> setSwappedFeeds(false));
                }
            }

            @Override
            public void onCloseWithId(String socketId) {
                runOnUiThread(() -> {
                    disConnect();
                    ChatSingleActivity.this.finish();
                });
            }
        });
        if (!PermissionUtil.isNeedRequestPermission(ChatSingleActivity.this)) {
            manager.joinRoom(getApplicationContext(), rootEglBase);
        }
    }

    private void replaceFragment(Fragment fragment, boolean videoEnable) {
        Bundle bundle = new Bundle();
        bundle.putBoolean("videoEnable", videoEnable);
        fragment.setArguments(bundle);
        FragmentManager manager = getSupportFragmentManager();
        manager.beginTransaction().replace(R.id.wr_container, fragment).commit();
    }

    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        return keyCode == KeyEvent.KEYCODE_BACK || super.onKeyDown(keyCode, event);
    }

    // 切换摄像头
    public void switchCamera() {
        manager.switchCamera();
    }

    // 挂断
    public void hangUp() {
        disConnect();
        this.finish();
    }

    // 静音
    public void toggleMic(boolean enable) {
        manager.toggleMute(enable);
    }

    // 扬声器
    public void toggleSpeaker(boolean enable) {
        manager.toggleSpeaker(enable);
    }

    @Override
    protected void onDestroy() {
        disConnect();
        super.onDestroy();
    }

    private void disConnect() {
        manager.exitRoom();
        if (localRender != null) {
            localRender.setTarget(null);
            localRender = null;
        }
        if (remoteRender != null) {
            remoteRender.setTarget(null);
            remoteRender = null;
        }
        if (local_view != null) {
            local_view.release();
            local_view = null;
        }
        if (remote_view != null) {
            remote_view.release();
            remote_view = null;
        }
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        for (int i = 0; i < permissions.length; i++) {
            Log.i(PeerConnectionHelper.TAG, "[Permission] " + permissions[i] + " is " + (grantResults[i] == PackageManager.PERMISSION_GRANTED ? "granted" : "denied"));
            if (grantResults[i] != PackageManager.PERMISSION_GRANTED) {
                finish();
                break;
            }
        }
        manager.joinRoom(getApplicationContext(), rootEglBase);
    }
}

19 View Complete Implementation : MainActivity.java
Copyright Apache License 2.0
Author : open-webrtc-toolkit
@Override
public void onReady(final SurfaceViewRenderer localRenderer, final SurfaceViewRenderer remoteRenderer) {
    this.localRenderer = localRenderer;
    this.remoteRenderer = remoteRenderer;
    localRenderer.init(rootEglBase.getEglBaseContext(), null);
    remoteRenderer.init(rootEglBase.getEglBaseContext(), null);
    executor.execute(() -> {
        if (capturer == null) {
            boolean vga = settingsFragment == null || settingsFragment.resolutionVGA;
            boolean isCameraFront = settingsFragment == null || settingsFragment.cameraFront;
            capturer = OwtVideoCapturer.create(vga ? 640 : 1280, vga ? 480 : 720, 30, true, isCameraFront);
            localStream = new LocalStream(capturer, new MediaConstraints.AudioTrackConstraints());
        }
        localStream.attach(localRenderer);
        if (remoteStream != null && !remoteStreamEnded) {
            remoteStream.attach(remoteRenderer);
        }
    });
}

19 View Complete Implementation : FragmentVideo.java
Copyright MIT License
Author : ddssingsong
@Override
public void didReceiveRemoteVideoTrack() {
    pipRenderer.setVisibility(View.VISIBLE);
    if (isOutgoing && localSurfaceView != null) {
        ((ViewGroup) localSurfaceView.getParent()).removeView(localSurfaceView);
        pipRenderer.addView(localSurfaceView);
        gEngineKit.getCurrentSession().setupLocalVideo(localSurfaceView);
    }
    SurfaceViewRenderer surfaceView = gEngineKit.getCurrentSession().createRendererView();
    if (surfaceView != null) {
        remoteSurfaceView = surfaceView;
        fullscreenRenderer.removeAllViews();
        fullscreenRenderer.addView(surfaceView);
        gEngineKit.getCurrentSession().setupRemoteVideo(surfaceView);
    }
}

19 View Complete Implementation : VideoConversationFragment.java
Copyright BSD 3-Clause "New" or "Revised" License
Author : QuickBlox
private void updateVideoView(SurfaceViewRenderer videoView) {
    RendererCommon.ScalingType scalingType = RendererCommon.ScalingType.SCALE_ASPECT_FILL;
    Log.i(TAG, "updateVideoView - scalingType = " + scalingType);
    if (videoView != null) {
        videoView.setScalingType(scalingType);
        videoView.setMirror(false);
        videoView.requestLayout();
    }
}

19 View Complete Implementation : MXWebRtcView.java
Copyright Apache License 2.0
Author : matrix-org
/**
 * Starts rendering {@link #videoTrack} if rendering is not in progress and
 * all preconditions for the start of rendering are met.
 */
private void tryAddRendererToVideoTrack() {
    if (videoTrack != null && ViewCompat.isAttachedToWindow(this)) {
        EglBase.Context sharedContext = EglUtils.getRootEglBaseContext();
        if (sharedContext == null) {
            // If SurfaceViewRenderer#init() is invoked, it will throw a
            // RuntimeException which will very likely kill the application.
            Log.e(LOG_TAG, "Failed to render a VideoTrack!");
            return;
        }
        SurfaceViewRenderer surfaceViewRenderer = getSurfaceViewRenderer();
        surfaceViewRenderer.init(sharedContext, rendererEvents);
        videoTrack.addSink(surfaceViewRenderer);
    }
}

19 View Complete Implementation : WebRtcCallActivity.java
Copyright GNU General Public License v3.0
Author : signalapp
private void handleTerminate(@NonNull Recipient recipient, @NonNull SurfaceViewRenderer localRenderer) /*, int terminationType */
{
    Log.i(TAG, "handleTerminate called");
    callScreen.setActiveCall(recipient, getString(R.string.RedPhone_ending_call), localRenderer);
    EventBus.getDefault().removeStickyEvent(WebRtcViewModel.clreplaced);
    delayedFinish();
}

19 View Complete Implementation : WebRtcCallScreen.java
Copyright GNU General Public License v3.0
Author : signalapp
public void setActiveCall(@NonNull Recipient personInfo, @NonNull String message, @Nullable String sas, SurfaceViewRenderer localRenderer, SurfaceViewRenderer remoteRenderer) {
    setCard(personInfo, message);
    setConnected(localRenderer, remoteRenderer);
    incomingCallButton.stopRingingAnimation();
    incomingCallButton.setVisibility(View.GONE);
    endCallButton.show();
}

19 View Complete Implementation : FragmentVideo.java
Copyright MIT License
Author : ddssingsong
@Override
public void didCreateLocalVideoTrack() {
    SurfaceViewRenderer surfaceView = gEngineKit.getCurrentSession().createRendererView();
    if (surfaceView != null) {
        surfaceView.setZOrderMediaOverlay(true);
        localSurfaceView = surfaceView;
        if (isOutgoing && remoteSurfaceView == null) {
            fullscreenRenderer.addView(surfaceView);
        } else {
            pipRenderer.addView(surfaceView);
        }
        gEngineKit.getCurrentSession().setupLocalVideo(surfaceView);
    }
}

19 View Complete Implementation : MainActivity.java
Copyright Apache License 2.0
Author : open-webrtc-toolkit
@Override
public void onRenderer(SurfaceViewRenderer localRenderer, SurfaceViewRenderer remoteRenderer) {
    this.localRenderer = localRenderer;
    this.remoteRenderer = remoteRenderer;
}

19 View Complete Implementation : RTCCall.java
Copyright GNU General Public License v3.0
Author : dakhnod
public void setRemoteRenderer(SurfaceViewRenderer remoteRenderer) {
    this.remoteRenderer = remoteRenderer;
}

19 View Complete Implementation : CallFragment.java
Copyright Apache License 2.0
Author : open-webrtc-toolkit
public clreplaced CallFragment extends Fragment implements View.OnClickListener {

    private static final String TAG = "OWT_P2P";

    private SurfaceViewRenderer fullRenderer, smallRenderer;

    private Button publishBtn, backBtn;

    private CallFragmentListener mListener;

    private float dX, dY;

    private boolean isPublishing = false;

    private View.OnTouchListener touchListener = new View.OnTouchListener() {

        @Override
        public boolean onTouch(View v, MotionEvent event) {
            if (v.getId() == R.id.small_renderer) {
                switch(event.getAction()) {
                    case MotionEvent.ACTION_DOWN:
                        dX = v.getX() - event.getRawX();
                        dY = v.getY() - event.getRawY();
                        break;
                    case MotionEvent.ACTION_MOVE:
                        v.animate().x(event.getRawX() + dX).y(event.getRawY() + dY).setDuration(0).start();
                        break;
                    case MotionEvent.ACTION_UP:
                        v.animate().x(event.getRawX() + dX >= event.getRawY() + dY ? event.getRawX() + dX : 0).y(event.getRawX() + dX >= event.getRawY() + dY ? 0 : event.getRawY() + dY).setDuration(10).start();
                        break;
                }
            }
            return true;
        }
    };

    public CallFragment() {
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View mView = inflater.inflate(R.layout.fragment_call, container, false);
        publishBtn = mView.findViewById(R.id.publish_btn);
        publishBtn.setText(isPublishing ? R.string.unpublish : R.string.publish);
        publishBtn.setOnClickListener(this);
        backBtn = mView.findViewById(R.id.back_btn);
        backBtn.setOnClickListener(this);
        fullRenderer = mView.findViewById(R.id.full_renderer);
        fullRenderer.setScalingType(RendererCommon.ScalingType.SCALE_ASPECT_FIT);
        fullRenderer.setEnableHardwareScaler(true);
        smallRenderer = mView.findViewById(R.id.small_renderer);
        smallRenderer.setScalingType(RendererCommon.ScalingType.SCALE_ASPECT_FIT);
        smallRenderer.setOnTouchListener(touchListener);
        smallRenderer.setEnableHardwareScaler(true);
        smallRenderer.setZOrderMediaOverlay(true);
        mListener.onReady(smallRenderer, fullRenderer);
        return mView;
    }

    @Override
    public void onAttach(Context context) {
        super.onAttach(context);
        mListener = (CallFragmentListener) context;
    }

    @Override
    public void onDetach() {
        fullRenderer.release();
        fullRenderer = null;
        smallRenderer.release();
        smallRenderer = null;
        super.onDetach();
    }

    @Override
    public void onClick(View v) {
        switch(v.getId()) {
            case R.id.publish_btn:
                publishBtn.setEnabled(false);
                if (publishBtn.getText().toString().equals("Publish")) {
                    mListener.onPublishRequest();
                } else {
                    mListener.onUnpublishRequest(false);
                    publishBtn.setText(R.string.publish);
                    isPublishing = false;
                    publishBtn.setEnabled(true);
                }
                break;
            case R.id.back_btn:
                mListener.onUnpublishRequest(true);
                isPublishing = false;
                break;
        }
    }

    void onPublished(final boolean succeed) {
        getActivity().runOnUiThread(() -> {
            isPublishing = succeed;
            publishBtn.setText(succeed ? R.string.unpublish : R.string.publish);
            publishBtn.setEnabled(true);
        });
    }

    public interface CallFragmentListener {

        void onReady(SurfaceViewRenderer localRenderer, SurfaceViewRenderer remoteRenderer);

        void onPublishRequest();

        void onUnpublishRequest(boolean stop);
    }
}

19 View Complete Implementation : CallSession.java
Copyright MIT License
Author : ddssingsong
public SurfaceViewRenderer createRendererView() {
    SurfaceViewRenderer renderer = new SurfaceViewRenderer(mContext);
    renderer.init(mRootEglBase.getEglBaseContext(), null);
    renderer.setScalingType(RendererCommon.ScalingType.SCALE_ASPECT_FIT);
    renderer.setMirror(true);
    return renderer;
}

19 View Complete Implementation : VideoActivity.java
Copyright MIT License
Author : Piasy
public clreplaced VideoActivity extends AppCompatActivity {

    private VideoSource mVideoSource;

    private VideoSink mVideoSink;

    private SurfaceViewRenderer mVideoView;

    private Mp4Recorder mMp4Recorder;

    private Mp4Recorder mHdMp4Recorder;

    private HwAvcEncoder mHwAvcEncoder;

    private HwAvcEncoder mHdHwAvcEncoder;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
        setContentView(R.layout.activity_video);
        VideoConfig config = VideoConfig.builder().previewWidth(1280).previewHeight(720).outputWidth(448).outputHeight(800).fps(30).outputBitrate(800).build();
        VideoConfig hdConfig = VideoConfig.builder().previewWidth(1280).previewHeight(720).outputWidth(720).outputHeight(1280).fps(30).outputBitrate(2000).build();
        VideoCapturer capturer = createVideoCapturer();
        mVideoView = (SurfaceViewRenderer) findViewById(R.id.mVideoView1);
        try {
            String filename = "video_source_record_" + System.currentTimeMillis();
            mMp4Recorder = new Mp4Recorder(new File(Environment.getExternalStorageDirectory(), filename + ".mp4"));
            mHdMp4Recorder = new Mp4Recorder(new File(Environment.getExternalStorageDirectory(), filename + "-hd.mp4"));
        } catch (IOException e) {
            e.printStackTrace();
            Toast.makeText(this, "start Mp4Recorder fail!", Toast.LENGTH_SHORT).show();
            finish();
            return;
        }
        mHwAvcEncoder = new HwAvcEncoder(config, mMp4Recorder);
        mHdHwAvcEncoder = new HwAvcEncoder(hdConfig, mHdMp4Recorder);
        mVideoSink = new VideoSink(mVideoView, mHwAvcEncoder, mHdHwAvcEncoder);
        mVideoSource = new VideoSource(getApplicationContext(), config, capturer, mVideoSink);
        mVideoView.init(mVideoSource.getRootEglBase().getEglBaseContext(), null);
        mHwAvcEncoder.start(mVideoSource.getRootEglBase());
        mHdHwAvcEncoder.start(mVideoSource.getRootEglBase());
        initView();
    }

    @Override
    protected void onStart() {
        super.onStart();
        mVideoSource.start();
    }

    @Override
    protected void onStop() {
        super.onStop();
        mVideoSource.stop();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        mVideoSource.destroy();
        mVideoView.release();
        mHwAvcEncoder.destroy();
        mHdHwAvcEncoder.destroy();
        mMp4Recorder.stop();
        mHdMp4Recorder.stop();
    }

    private void initView() {
        findViewById(R.id.mSwitch).setOnClickListener(new View.OnClickListener() {

            @Override
            public void onClick(final View v) {
                mVideoSource.switchCamera();
            }
        });
        final TextView tvRotateDegree = (TextView) findViewById(R.id.mTvRotateDegree);
        ((SeekBar) findViewById(R.id.mRotateSeek)).setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {

            @Override
            public void onProgressChanged(final SeekBar seekBar, final int progress, final boolean fromUser) {
                float rotateDegree = 360.0f * progress / 100;
                tvRotateDegree.setText(String.format("rotate: %.1f", rotateDegree));
                mVideoSink.rotate(rotateDegree);
            }

            @Override
            public void onStartTrackingTouch(final SeekBar seekBar) {
            }

            @Override
            public void onStopTrackingTouch(final SeekBar seekBar) {
            }
        });
        ((CheckBox) findViewById(R.id.mCbFlipH)).setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {

            @Override
            public void onCheckedChanged(final CompoundButton buttonView, final boolean isChecked) {
                mVideoSink.flipHorizontal(isChecked);
            }
        });
        ((CheckBox) findViewById(R.id.mCbFlipV)).setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {

            @Override
            public void onCheckedChanged(final CompoundButton buttonView, final boolean isChecked) {
                mVideoSink.flipVertical(isChecked);
            }
        });
    }

    private VideoCapturer createVideoCapturer() {
        switch(MainActivity.sVideoSource) {
            case VideoSource.SOURCE_CAMERA1:
                return VideoCapturers.createCamera1Capturer(true);
            case VideoSource.SOURCE_CAMERA2:
                return VideoCapturers.createCamera2Capturer(this);
            case VideoSource.SOURCE_SCREEN:
                return null;
            case VideoSource.SOURCE_FILE:
                return VideoCapturers.createFileVideoCapturer("");
            default:
                return null;
        }
    }
}

19 View Complete Implementation : ChatRoomActivity.java
Copyright MIT License
Author : ddssingsong
private void removeView(String userId) {
    ProxyVideoSink sink = _sinks.get(userId);
    SurfaceViewRenderer renderer = _videoViews.get(userId);
    if (sink != null) {
        sink.setTarget(null);
    }
    if (renderer != null) {
        renderer.release();
    }
    _sinks.remove(userId);
    _videoViews.remove(userId);
    _infos.remove(new MemberBean(userId));
    wr_video_view.removeView(renderer);
    int size = _infos.size();
    for (int i = 0; i < _infos.size(); i++) {
        MemberBean memberBean = _infos.get(i);
        SurfaceViewRenderer renderer1 = _videoViews.get(memberBean.getId());
        if (renderer1 != null) {
            FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
            layoutParams.height = getWidth(size);
            layoutParams.width = getWidth(size);
            layoutParams.leftMargin = getX(size, i);
            layoutParams.topMargin = getY(size, i);
            renderer1.setLayoutParams(layoutParams);
        }
    }
}

19 View Complete Implementation : BaseConversationFragment.java
Copyright BSD 3-Clause "New" or "Revised" License
Author : QuickBlox
protected void updateVideoView(SurfaceViewRenderer surfaceViewRenderer, boolean mirror, RendererCommon.ScalingType scalingType) {
    Log.i(TAG, "updateVideoView mirror:" + mirror + ", scalingType = " + scalingType);
    surfaceViewRenderer.setScalingType(scalingType);
    surfaceViewRenderer.setMirror(mirror);
    surfaceViewRenderer.requestLayout();
}

19 View Complete Implementation : WebRTCActivity.java
Copyright BSD 3-Clause "New" or "Revised" License
Author : GoBelieveIO
/**
 * Created by houxh on 15/9/8.
 */
public clreplaced WebRTCActivity extends Activity implements PeerConnectionClient.PeerConnectionEvents {

    public static final String EXTRA_ROOMID = "org.appspot.apprtc.ROOMID";

    public static final String EXTRA_LOOPBACK = "org.appspot.apprtc.LOOPBACK";

    public static final String EXTRA_VIDEO_CALL = "org.appspot.apprtc.VIDEO_CALL";

    public static final String EXTRA_CAMERA2 = "org.appspot.apprtc.CAMERA2";

    public static final String EXTRA_VIDEO_WIDTH = "org.appspot.apprtc.VIDEO_WIDTH";

    public static final String EXTRA_VIDEO_HEIGHT = "org.appspot.apprtc.VIDEO_HEIGHT";

    public static final String EXTRA_VIDEO_FPS = "org.appspot.apprtc.VIDEO_FPS";

    public static final String EXTRA_VIDEO_CAPTUREQUALITYSLIDER_ENABLED = "org.appsopt.apprtc.VIDEO_CAPTUREQUALITYSLIDER";

    public static final String EXTRA_VIDEO_BITRATE = "org.appspot.apprtc.VIDEO_BITRATE";

    public static final String EXTRA_VIDEOCODEC = "org.appspot.apprtc.VIDEOCODEC";

    public static final String EXTRA_HWCODEC_ENABLED = "org.appspot.apprtc.HWCODEC";

    public static final String EXTRA_CAPTURETOTEXTURE_ENABLED = "org.appspot.apprtc.CAPTURETOTEXTURE";

    public static final String EXTRA_AUDIO_BITRATE = "org.appspot.apprtc.AUDIO_BITRATE";

    public static final String EXTRA_AUDIOCODEC = "org.appspot.apprtc.AUDIOCODEC";

    public static final String EXTRA_NOAUDIOPROCESSING_ENABLED = "org.appspot.apprtc.NOAUDIOPROCESSING";

    public static final String EXTRA_AECDUMP_ENABLED = "org.appspot.apprtc.AECDUMP";

    public static final String EXTRA_OPENSLES_ENABLED = "org.appspot.apprtc.OPENSLES";

    public static final String EXTRA_DISABLE_BUILT_IN_AEC = "org.appspot.apprtc.DISABLE_BUILT_IN_AEC";

    public static final String EXTRA_DISABLE_BUILT_IN_AGC = "org.appspot.apprtc.DISABLE_BUILT_IN_AGC";

    public static final String EXTRA_DISABLE_BUILT_IN_NS = "org.appspot.apprtc.DISABLE_BUILT_IN_NS";

    public static final String EXTRA_ENABLE_LEVEL_CONTROL = "org.appspot.apprtc.ENABLE_LEVEL_CONTROL";

    public static final String EXTRA_DISPLAY_HUD = "org.appspot.apprtc.DISPLAY_HUD";

    public static final String EXTRA_TRACING = "org.appspot.apprtc.TRACING";

    public static final String EXTRA_CMDLINE = "org.appspot.apprtc.CMDLINE";

    public static final String EXTRA_RUNTIME = "org.appspot.apprtc.RUNTIME";

    public static final String EXTRA_VIDEO_FILE_AS_CAMERA = "org.appspot.apprtc.VIDEO_FILE_AS_CAMERA";

    public static final String EXTRA_SAVE_REMOTE_VIDEO_TO_FILE = "org.appspot.apprtc.SAVE_REMOTE_VIDEO_TO_FILE";

    public static final String EXTRA_SAVE_REMOTE_VIDEO_TO_FILE_WIDTH = "org.appspot.apprtc.SAVE_REMOTE_VIDEO_TO_FILE_WIDTH";

    public static final String EXTRA_SAVE_REMOTE_VIDEO_TO_FILE_HEIGHT = "org.appspot.apprtc.SAVE_REMOTE_VIDEO_TO_FILE_HEIGHT";

    public static final String EXTRA_USE_VALUES_FROM_INTENT = "org.appspot.apprtc.USE_VALUES_FROM_INTENT";

    private static final String TAG = "CallRTCClient";

    // List of mandatory application permissions.
    private static final String[] MANDATORY_PERMISSIONS = { "android.permission.MODIFY_AUDIO_SETTINGS", "android.permission.RECORD_AUDIO", "android.permission.INTERNET" };

    // Peer connection statistics callback period in ms.
    private static final int STAT_CALLBACK_PERIOD = 1000;

    protected PeerConnectionClient peerConnectionClient = null;

    protected EglBase rootEglBase;

    protected SurfaceViewRenderer localRender;

    protected SurfaceViewRenderer remoteRender;

    protected Toast logToast;

    protected PeerConnectionClient.PeerConnectionParameters peerConnectionParameters;

    protected boolean iceConnected;

    protected boolean isError;

    protected long callStartedTimeMs = 0;

    protected boolean isCaller;

    protected String turnUserName;

    protected String turnPreplacedword;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Intent intent = getIntent();
        boolean loopback = intent.getBooleanExtra(EXTRA_LOOPBACK, false);
        boolean tracing = intent.getBooleanExtra(EXTRA_TRACING, false);
        boolean videoEnabled = intent.getBooleanExtra(EXTRA_VIDEO_CALL, true);
        Log.i(TAG, "video enabled:" + videoEnabled);
        peerConnectionParameters = new PeerConnectionClient.PeerConnectionParameters(videoEnabled, loopback, tracing, intent.getIntExtra(EXTRA_VIDEO_WIDTH, 0), intent.getIntExtra(EXTRA_VIDEO_HEIGHT, 0), intent.getIntExtra(EXTRA_VIDEO_FPS, 0), intent.getIntExtra(EXTRA_VIDEO_BITRATE, 0), intent.getStringExtra(EXTRA_VIDEOCODEC), intent.getBooleanExtra(EXTRA_HWCODEC_ENABLED, true), intent.getIntExtra(EXTRA_AUDIO_BITRATE, 0), intent.getStringExtra(EXTRA_AUDIOCODEC), intent.getBooleanExtra(EXTRA_NOAUDIOPROCESSING_ENABLED, false), intent.getBooleanExtra(EXTRA_AECDUMP_ENABLED, false), intent.getBooleanExtra(EXTRA_OPENSLES_ENABLED, false), intent.getBooleanExtra(EXTRA_DISABLE_BUILT_IN_AEC, false), intent.getBooleanExtra(EXTRA_DISABLE_BUILT_IN_AGC, false), intent.getBooleanExtra(EXTRA_DISABLE_BUILT_IN_NS, false), intent.getBooleanExtra(EXTRA_ENABLE_LEVEL_CONTROL, false));
        // Check for mandatory permissions.
        for (String permission : MANDATORY_PERMISSIONS) {
            if (checkCallingOrSelfPermission(permission) != PackageManager.PERMISSION_GRANTED) {
                Log.e(TAG, "Permission " + permission + " is not granted");
            }
        }
    }

    private boolean useCamera2() {
        return Camera2Enumerator.isSupported(this) && getIntent().getBooleanExtra(EXTRA_CAMERA2, true);
    }

    private boolean captureToTexture() {
        return getIntent().getBooleanExtra(EXTRA_CAPTURETOTEXTURE_ENABLED, true);
    }

    private VideoCapturer createCameraCapturer(CameraEnumerator enumerator) {
        final String[] deviceNames = enumerator.getDeviceNames();
        // First, try to find front facing camera
        Log.d(TAG, "Looking for front facing cameras.");
        for (String deviceName : deviceNames) {
            if (enumerator.isFrontFacing(deviceName)) {
                Log.d(TAG, "Creating front facing camera capturer.");
                VideoCapturer videoCapturer = enumerator.createCapturer(deviceName, null);
                if (videoCapturer != null) {
                    return videoCapturer;
                }
            }
        }
        // Front facing camera not found, try something else
        Log.d(TAG, "Looking for other cameras.");
        for (String deviceName : deviceNames) {
            if (!enumerator.isFrontFacing(deviceName)) {
                Log.d(TAG, "Creating other camera capturer.");
                VideoCapturer videoCapturer = enumerator.createCapturer(deviceName, null);
                if (videoCapturer != null) {
                    return videoCapturer;
                }
            }
        }
        return null;
    }

    protected void updateVideoView() {
    }

    protected void startStream() {
        logAndToast("Creating peer connection");
        peerConnectionClient = new PeerConnectionClient(getApplicationContext(), rootEglBase, peerConnectionParameters, this);
        PeerConnectionFactory.Options options = new PeerConnectionFactory.Options();
        peerConnectionClient.createPeerConnectionFactory(options);
        PeerConnection.IceServer server = new PeerConnection.IceServer("stun:stun.counterpath.net:3478");
        String username = turnUserName;
        String preplacedword = turnPreplacedword;
        PeerConnection.IceServer server2 = new PeerConnection.IceServer("turn:turn.gobelieve.io:3478?transport=udp", username, preplacedword);
        peerConnectionClient.clearIceServer();
        peerConnectionClient.addIceServer(server);
        peerConnectionClient.addIceServer(server2);
        VideoCapturer videoCapturer = null;
        if (peerConnectionParameters.videoCallEnabled) {
            videoCapturer = createVideoCapturer();
        }
        peerConnectionClient.createPeerConnection(localRender, remoteRender, videoCapturer);
        if (this.isCaller) {
            logAndToast("Creating OFFER...");
            // Create offer. Offer SDP will be sent to answering client in
            // PeerConnectionEvents.onLocalDescription event.
            peerConnectionClient.createOffer();
        }
    }

    protected void stopStream() {
        logAndToast("Remote end hung up; dropping PeerConnection");
        disconnect();
    }

    public void switchCamera(View v) {
        if (peerConnectionClient != null) {
            peerConnectionClient.switchCamera();
        }
    }

    public void toogleVideo(View v) {
        if (peerConnectionClient != null) {
            peerConnectionClient.toogleVideo();
        }
    }

    public void toogleAudio(View v) {
        if (peerConnectionClient != null) {
            peerConnectionClient.toogleAudio();
        }
    }

    // Disconnect from remote resources, dispose of local resources, and exit.
    private void disconnect() {
        if (peerConnectionClient != null) {
            peerConnectionClient.close();
            peerConnectionClient = null;
        }
        if (localRender != null) {
            localRender.release();
            localRender = null;
        }
        if (remoteRender != null) {
            remoteRender.release();
            remoteRender = null;
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        disconnect();
    }

    // Log |msg| and Toast about it.
    private void logAndToast(String msg) {
        Log.d(TAG, msg);
        if (logToast != null) {
            logToast.cancel();
        }
        logToast = Toast.makeText(this, msg, Toast.LENGTH_SHORT);
        logToast.show();
    }

    private void reportError(final String description) {
        runOnUiThread(new Runnable() {

            @Override
            public void run() {
                if (!isError) {
                    isError = true;
                    new AlertDialog.Builder(WebRTCActivity.this).setreplacedle(getText(R.string.channel_error_replacedle)).setMessage(description).setCancelable(false).setNeutralButton(R.string.ok, new DialogInterface.OnClickListener() {

                        @Override
                        public void onClick(DialogInterface dialog, int id) {
                            dialog.cancel();
                        }
                    }).create().show();
                }
            }
        });
    }

    private VideoCapturer createVideoCapturer() {
        VideoCapturer videoCapturer = null;
        String videoFileAsCamera = getIntent().getStringExtra(EXTRA_VIDEO_FILE_AS_CAMERA);
        if (videoFileAsCamera != null) {
            try {
                videoCapturer = new FileVideoCapturer(videoFileAsCamera);
            } catch (IOException e) {
                reportError("Failed to open video file for emulated camera");
                return null;
            }
        } else if (useCamera2()) {
            if (!captureToTexture()) {
                reportError(getString(R.string.camera2_texture_only_error));
                return null;
            }
            Log.d(TAG, "Creating capturer using camera2 API.");
            videoCapturer = createCameraCapturer(new Camera2Enumerator(this));
        } else {
            Log.d(TAG, "Creating capturer using camera1 API.");
            videoCapturer = createCameraCapturer(new Camera1Enumerator(captureToTexture()));
        }
        if (videoCapturer == null) {
            reportError("Failed to open camera");
            return null;
        }
        return videoCapturer;
    }

    // Put a |key|->|value| mapping in |json|.
    private static void jsonPut(JSONObject json, String key, Object value) {
        try {
            json.put(key, value);
        } catch (JSONException e) {
            throw new RuntimeException(e);
        }
    }

    // Converts a Java candidate to a JSONObject.
    private JSONObject toJsonCandidate(final IceCandidate candidate) {
        JSONObject json = new JSONObject();
        jsonPut(json, "label", candidate.sdpMLineIndex);
        jsonPut(json, "id", candidate.sdpMid);
        jsonPut(json, "candidate", candidate.sdp);
        return json;
    }

    // Converts a JSON candidate to a Java object.
    IceCandidate toJavaCandidate(JSONObject json) throws JSONException {
        return new IceCandidate(json.getString("id"), json.getInt("label"), json.getString("candidate"));
    }

    protected void processP2PMessage(JSONObject json) {
        try {
            String type = json.optString("type");
            if (type.equals("candidate")) {
                this.onRemoteIceCandidate(toJavaCandidate(json));
            } else if (type.equals("remove-candidates")) {
                JSONArray candidateArray = json.getJSONArray("candidates");
                IceCandidate[] candidates = new IceCandidate[candidateArray.length()];
                for (int i = 0; i < candidateArray.length(); ++i) {
                    candidates[i] = toJavaCandidate(candidateArray.getJSONObject(i));
                }
                this.onRemoteIceCandidatesRemoved(candidates);
            } else if (type.equals("answer")) {
                if (this.isCaller) {
                    SessionDescription sdp = new SessionDescription(SessionDescription.Type.fromCanonicalForm(type), json.getString("sdp"));
                    this.onRemoteDescription(sdp);
                } else {
                    reportError("Received answer for call initiator");
                }
            } else if (type.equals("offer")) {
                if (!this.isCaller) {
                    SessionDescription sdp = new SessionDescription(SessionDescription.Type.fromCanonicalForm(type), json.getString("sdp"));
                    this.onRemoteDescription(sdp);
                } else {
                    reportError("Received offer for call receiver");
                }
            } else {
                reportError("Unexpected WebSocket message");
            }
        } catch (JSONException e) {
            reportError("WebSocket message JSON parsing error: " + e.toString());
        }
    }

    public void onRemoteDescription(final SessionDescription sdp) {
        final long delta = System.currentTimeMillis() - callStartedTimeMs;
        runOnUiThread(new Runnable() {

            @Override
            public void run() {
                if (peerConnectionClient == null) {
                    Log.e(TAG, "Received remote SDP for non-initilized peer connection.");
                    return;
                }
                logAndToast("Received remote " + sdp.type + ", delay=" + delta + "ms");
                peerConnectionClient.setRemoteDescription(sdp);
                if (!WebRTCActivity.this.isCaller) {
                    logAndToast("Creating ANSWER...");
                    // Create answer. Answer SDP will be sent to offering client in
                    // PeerConnectionEvents.onLocalDescription event.
                    peerConnectionClient.createAnswer();
                }
            }
        });
    }

    public void onRemoteIceCandidate(final IceCandidate candidate) {
        runOnUiThread(new Runnable() {

            @Override
            public void run() {
                if (peerConnectionClient == null) {
                    Log.e(TAG, "Received ICE candidate for a non-initialized peer connection.");
                    return;
                }
                peerConnectionClient.addRemoteIceCandidate(candidate);
            }
        });
    }

    public void onRemoteIceCandidatesRemoved(final IceCandidate[] candidates) {
        runOnUiThread(new Runnable() {

            @Override
            public void run() {
                if (peerConnectionClient == null) {
                    Log.e(TAG, "Received ICE candidate removals for a non-initialized peer connection.");
                    return;
                }
                peerConnectionClient.removeRemoteIceCandidates(candidates);
            }
        });
    }

    // Should be called from UI thread
    private void callConnected() {
        final long delta = System.currentTimeMillis() - callStartedTimeMs;
        Log.i(TAG, "Call connected: delay=" + delta + "ms");
        if (peerConnectionClient == null || isError) {
            Log.w(TAG, "Call is connected in closed or error state");
            return;
        }
        // Update video view.
        updateVideoView();
        // Enable statistics callback.
        peerConnectionClient.enableStatsEvents(true, STAT_CALLBACK_PERIOD);
    }

    protected void sendP2PMessage(JSONObject json) {
    }

    public void sendOfferSdp(final SessionDescription sdp) {
        JSONObject json = new JSONObject();
        jsonPut(json, "sdp", sdp.description);
        jsonPut(json, "type", "offer");
        sendP2PMessage(json);
    }

    // Send local answer SDP to the other participant.
    public void sendAnswerSdp(final SessionDescription sdp) {
        JSONObject json = new JSONObject();
        jsonPut(json, "sdp", sdp.description);
        jsonPut(json, "type", "answer");
        sendP2PMessage(json);
    }

    // Send Ice candidate to the other participant.
    public void sendLocalIceCandidate(final IceCandidate candidate) {
        JSONObject json = new JSONObject();
        jsonPut(json, "type", "candidate");
        jsonPut(json, "label", candidate.sdpMLineIndex);
        jsonPut(json, "id", candidate.sdpMid);
        jsonPut(json, "candidate", candidate.sdp);
        sendP2PMessage(json);
    }

    // Send removed Ice candidates to the other participant.
    public void sendLocalIceCandidateRemovals(final IceCandidate[] candidates) {
        JSONObject json = new JSONObject();
        jsonPut(json, "type", "remove-candidates");
        JSONArray jsonArray = new JSONArray();
        for (final IceCandidate candidate : candidates) {
            jsonArray.put(toJsonCandidate(candidate));
        }
        jsonPut(json, "candidates", jsonArray);
        sendP2PMessage(json);
    }

    // -----Implementation of PeerConnectionClient.PeerConnectionEvents.---------
    // Send local peer connection SDP and ICE candidates to remote party.
    // All callbacks are invoked from peer connection client looper thread and
    // are routed to UI thread.
    @Override
    public void onLocalDescription(final SessionDescription sdp) {
        final long delta = System.currentTimeMillis() - callStartedTimeMs;
        runOnUiThread(new Runnable() {

            @Override
            public void run() {
                logAndToast("Sending " + sdp.type + ", delay=" + delta + "ms");
                if (WebRTCActivity.this.isCaller) {
                    WebRTCActivity.this.sendOfferSdp(sdp);
                } else {
                    WebRTCActivity.this.sendAnswerSdp(sdp);
                }
                if (peerConnectionParameters.videoMaxBitrate > 0) {
                    Log.d(TAG, "Set video maximum bitrate: " + peerConnectionParameters.videoMaxBitrate);
                    peerConnectionClient.setVideoMaxBitrate(peerConnectionParameters.videoMaxBitrate);
                }
            }
        });
    }

    @Override
    public void onIceCandidate(final IceCandidate candidate) {
        runOnUiThread(new Runnable() {

            @Override
            public void run() {
                WebRTCActivity.this.sendLocalIceCandidate(candidate);
            }
        });
    }

    @Override
    public void onIceCandidatesRemoved(final IceCandidate[] candidates) {
        runOnUiThread(new Runnable() {

            @Override
            public void run() {
                WebRTCActivity.this.sendLocalIceCandidateRemovals(candidates);
            }
        });
    }

    @Override
    public void onIceConnected() {
        final long delta = System.currentTimeMillis() - callStartedTimeMs;
        runOnUiThread(new Runnable() {

            @Override
            public void run() {
                logAndToast("ICE connected, delay=" + delta + "ms");
                iceConnected = true;
                callConnected();
            }
        });
    }

    @Override
    public void onIceDisconnected() {
        runOnUiThread(new Runnable() {

            @Override
            public void run() {
                logAndToast("ICE disconnected");
                iceConnected = false;
            }
        });
    }

    @Override
    public void onPeerConnectionClosed() {
    }

    private Map<String, String> getReportMap(StatsReport report) {
        Map<String, String> reportMap = new HashMap<String, String>();
        for (StatsReport.Value value : report.values) {
            reportMap.put(value.name, value.value);
        }
        return reportMap;
    }

    public void updateEncoderStatistics(final StatsReport[] reports) {
        StringBuilder encoderStat = new StringBuilder(128);
        StringBuilder bweStat = new StringBuilder();
        StringBuilder connectionStat = new StringBuilder();
        StringBuilder videoSendStat = new StringBuilder();
        StringBuilder videoRecvStat = new StringBuilder();
        String fps = null;
        String targetBitrate = null;
        String actualBitrate = null;
        for (StatsReport report : reports) {
            if (report.type.equals("ssrc") && report.id.contains("ssrc") && report.id.contains("send")) {
                // Send video statistics.
                Map<String, String> reportMap = getReportMap(report);
                String trackId = reportMap.get("googTrackId");
                if (trackId != null && trackId.contains(PeerConnectionClient.VIDEO_TRACK_ID)) {
                    fps = reportMap.get("googFrameRateSent");
                    videoSendStat.append(report.id).append("\n");
                    for (StatsReport.Value value : report.values) {
                        String name = value.name.replace("goog", "");
                        videoSendStat.append(name).append("=").append(value.value).append("\n");
                    }
                }
            } else if (report.type.equals("ssrc") && report.id.contains("ssrc") && report.id.contains("recv")) {
                // Receive video statistics.
                Map<String, String> reportMap = getReportMap(report);
                // Check if this stat is for video track.
                String frameWidth = reportMap.get("googFrameWidthReceived");
                if (frameWidth != null) {
                    videoRecvStat.append(report.id).append("\n");
                    for (StatsReport.Value value : report.values) {
                        String name = value.name.replace("goog", "");
                        videoRecvStat.append(name).append("=").append(value.value).append("\n");
                    }
                }
            } else if (report.id.equals("bweforvideo")) {
                // BWE statistics.
                Map<String, String> reportMap = getReportMap(report);
                targetBitrate = reportMap.get("googTargetEncBitrate");
                actualBitrate = reportMap.get("googActualEncBitrate");
                bweStat.append(report.id).append("\n");
                for (StatsReport.Value value : report.values) {
                    String name = value.name.replace("goog", "").replace("Available", "");
                    bweStat.append(name).append("=").append(value.value).append("\n");
                }
            } else if (report.type.equals("googCandidatePair")) {
                // Connection statistics.
                Map<String, String> reportMap = getReportMap(report);
                String activeConnection = reportMap.get("googActiveConnection");
                if (activeConnection != null && activeConnection.equals("true")) {
                    connectionStat.append(report.id).append("\n");
                    for (StatsReport.Value value : report.values) {
                        String name = value.name.replace("goog", "");
                        connectionStat.append(name).append("=").append(value.value).append("\n");
                    }
                }
            }
        }
        Log.i(TAG, bweStat.toString());
        Log.i(TAG, connectionStat.toString());
        Log.i(TAG, videoSendStat.toString());
        Log.i(TAG, videoRecvStat.toString());
        if (true) {
            if (fps != null) {
                encoderStat.append("Fps:  ").append(fps).append("\n");
            }
            if (targetBitrate != null) {
                encoderStat.append("Target BR: ").append(targetBitrate).append("\n");
            }
            if (actualBitrate != null) {
                encoderStat.append("Actual BR: ").append(actualBitrate).append("\n");
            }
        }
        Log.i(TAG, encoderStat.toString());
    }

    @Override
    public void onPeerConnectionStatsReady(final StatsReport[] reports) {
        runOnUiThread(new Runnable() {

            @Override
            public void run() {
                if (!isError && iceConnected) {
                    updateEncoderStatistics(reports);
                }
            }
        });
    }

    @Override
    public void onPeerConnectionError(final String description) {
        reportError(description);
    }
}

19 View Complete Implementation : RemoteParticipant.java
Copyright Apache License 2.0
Author : sergiopaniego
public void setVideoView(SurfaceViewRenderer videoView) {
    this.videoView = videoView;
}

19 View Complete Implementation : WebRtcCallScreen.java
Copyright GNU General Public License v3.0
Author : CableIM
private void setConnected(SurfaceViewRenderer localRenderer, SurfaceViewRenderer remoteRenderer) {
    if (localRenderLayout.getChildCount() == 0 && remoteRenderLayout.getChildCount() == 0) {
        if (localRenderer.getParent() != null) {
            ((ViewGroup) localRenderer.getParent()).removeView(localRenderer);
        }
        if (remoteRenderer.getParent() != null) {
            ((ViewGroup) remoteRenderer.getParent()).removeView(remoteRenderer);
        }
        localRenderLayout.setPosition(7, 70, 25, 25);
        localRenderLayout.setSquare(true);
        remoteRenderLayout.setPosition(0, 0, 100, 100);
        localRenderer.setLayoutParams(new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
        remoteRenderer.setLayoutParams(new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
        localRenderer.setMirror(true);
        localRenderer.setZOrderMediaOverlay(true);
        localRenderLayout.addView(localRenderer);
        remoteRenderLayout.addView(remoteRenderer);
    }
}

19 View Complete Implementation : WebRtcCallScreen.java
Copyright GNU General Public License v3.0
Author : signalapp
private void setConnected(SurfaceViewRenderer localRenderer, SurfaceViewRenderer remoteRenderer) {
    if (localRenderLayout.getChildCount() == 0) {
        if (localRenderer.getParent() != null) {
            ((ViewGroup) localRenderer.getParent()).removeView(localRenderer);
        }
        if (remoteRenderer.getParent() != null) {
            ((ViewGroup) remoteRenderer.getParent()).removeView(remoteRenderer);
        }
        localRenderLayout.setPosition(7, 70, 25, 25);
        remoteRenderLayout.setPosition(0, 0, 100, 100);
        localRenderer.setLayoutParams(new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
        remoteRenderer.setLayoutParams(new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
        localRenderer.setMirror(true);
        localRenderer.setZOrderMediaOverlay(true);
        localRenderLayout.addView(localRenderer);
        remoteRenderLayout.addView(remoteRenderer);
        this.localRenderer = localRenderer;
    }
}

19 View Complete Implementation : MXWebRtcView.java
Copyright Apache License 2.0
Author : matrix-org
private void setScalingType(ScalingType scalingType) {
    SurfaceViewRenderer surfaceViewRenderer;
    synchronized (layoutSyncRoot) {
        if (this.scalingType == scalingType) {
            return;
        }
        this.scalingType = scalingType;
        surfaceViewRenderer = getSurfaceViewRenderer();
        surfaceViewRenderer.setScalingType(scalingType);
    }
    // Both this instance ant its SurfaceViewRenderer take the value of
    // their scalingType properties into account upon their layouts.
    requestSurfaceViewRendererLayout();
}

19 View Complete Implementation : WebRtcCallScreen.java
Copyright GNU General Public License v3.0
Author : signalapp
public void setActiveCall(@NonNull Recipient personInfo, @NonNull String message, @NonNull SurfaceViewRenderer localRenderer) {
    setCard(personInfo, message);
    setRinging(localRenderer);
    incomingCallButton.stopRingingAnimation();
    incomingCallButton.setVisibility(View.GONE);
    endCallButton.show();
}

19 View Complete Implementation : WebRtcCallScreen.java
Copyright GNU General Public License v3.0
Author : signalapp
public void setLocalVideoState(@NonNull CameraState cameraState, @NonNull SurfaceViewRenderer localRenderer) {
    this.controls.setVideoAvailable(cameraState.getCameraCount() > 0);
    this.controls.setVideoEnabled(cameraState.isEnabled());
    this.controls.setCameraFlipAvailable(cameraState.getCameraCount() > 1);
    this.controls.setCameraFlipClickable(cameraState.getActiveDirection() != CameraState.Direction.PENDING);
    this.controls.setCameraFlipButtonEnabled(cameraState.getActiveDirection() == CameraState.Direction.BACK);
    localRenderer.setMirror(cameraState.getActiveDirection() == CameraState.Direction.FRONT);
    localRenderer.setScalingType(RendererCommon.ScalingType.SCALE_ASPECT_FIT);
    this.localRenderer = localRenderer;
    if (localRenderLayout.getChildCount() != 0) {
        displayLocalRendererInSmallLayout(!cameraState.isEnabled());
    } else {
        displayLocalRendererInLargeLayout(!cameraState.isEnabled());
    }
    localRenderer.setVisibility(cameraState.isEnabled() ? VISIBLE : INVISIBLE);
}

19 View Complete Implementation : MXWebRtcView.java
Copyright Apache License 2.0
Author : matrix-org
/**
 * {@inheritDoc}
 */
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
    int height = b - t;
    int width = r - l;
    if (height == 0 || width == 0) {
        l = t = r = b = 0;
    } else {
        int frameHeight;
        int frameRotation;
        int frameWidth;
        ScalingType scalingType;
        synchronized (layoutSyncRoot) {
            frameHeight = this.frameHeight;
            frameRotation = this.frameRotation;
            frameWidth = this.frameWidth;
            scalingType = this.scalingType;
        }
        SurfaceViewRenderer surfaceViewRenderer = getSurfaceViewRenderer();
        switch(scalingType) {
            case SCALE_ASPECT_FILL:
                // Fill this ViewGroup with surfaceViewRenderer and the latter
                // will take care of filling itself with the video similarly to
                // the cover value the CSS property object-fit.
                r = width;
                l = 0;
                b = height;
                t = 0;
                break;
            case SCALE_ASPECT_FIT:
            default:
                // Lay surfaceViewRenderer out inside this ViewGroup in accord
                // with the contain value of the CSS property object-fit.
                // SurfaceViewRenderer will fill itself with the video similarly
                // to the cover or contain value of the CSS property object-fit
                // (which will not matter, eventually).
                if (frameHeight == 0 || frameWidth == 0) {
                    l = t = r = b = 0;
                } else {
                    float frameAspectRatio = (frameRotation % 180 == 0) ? frameWidth / (float) frameHeight : frameHeight / (float) frameWidth;
                    Point frameDisplaySize = RendererCommon.getDisplaySize(scalingType, frameAspectRatio, width, height);
                    l = (width - frameDisplaySize.x) / 2;
                    t = (height - frameDisplaySize.y) / 2;
                    r = l + frameDisplaySize.x;
                    b = t + frameDisplaySize.y;
                }
                break;
        }
    }
    surfaceViewRenderer.layout(l, t, r, b);
}

19 View Complete Implementation : ChatRoomActivity.java
Copyright MIT License
Author : ddssingsong
private void addView(String id, MediaStream stream) {
    SurfaceViewRenderer renderer = new SurfaceViewRenderer(ChatRoomActivity.this);
    renderer.init(rootEglBase.getEglBaseContext(), null);
    renderer.setScalingType(RendererCommon.ScalingType.SCALE_ASPECT_FIT);
    renderer.setMirror(true);
    // set render
    ProxyVideoSink sink = new ProxyVideoSink();
    sink.setTarget(renderer);
    if (stream.videoTracks.size() > 0) {
        stream.videoTracks.get(0).addSink(sink);
    }
    _videoViews.put(id, renderer);
    _sinks.put(id, sink);
    _infos.add(new MemberBean(id));
    wr_video_view.addView(renderer);
    int size = _infos.size();
    for (int i = 0; i < size; i++) {
        MemberBean memberBean = _infos.get(i);
        SurfaceViewRenderer renderer1 = _videoViews.get(memberBean.getId());
        if (renderer1 != null) {
            FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
            layoutParams.height = getWidth(size);
            layoutParams.width = getWidth(size);
            layoutParams.leftMargin = getX(size, i);
            layoutParams.topMargin = getY(size, i);
            renderer1.setLayoutParams(layoutParams);
        }
    }
}

19 View Complete Implementation : FloatingVoipService.java
Copyright MIT License
Author : ddssingsong
private void showVideoInfo() {
    view.findViewById(R.id.audioLinearLayout).setVisibility(View.GONE);
    FrameLayout remoteVideoFrameLayout = view.findViewById(R.id.remoteVideoFrameLayout);
    remoteVideoFrameLayout.setVisibility(View.VISIBLE);
    SurfaceViewRenderer surfaceView = session.createRendererView();
    if (surfaceView != null) {
        remoteVideoFrameLayout.addView(surfaceView);
        session.setupRemoteVideo(surfaceView);
    }
}

19 View Complete Implementation : ChatRoomActivity.java
Copyright MIT License
Author : ddssingsong
private void exit() {
    manager.exitRoom();
    for (SurfaceViewRenderer renderer : _videoViews.values()) {
        renderer.release();
    }
    for (ProxyVideoSink sink : _sinks.values()) {
        sink.setTarget(null);
    }
    _videoViews.clear();
    _sinks.clear();
    _infos.clear();
}

19 View Complete Implementation : MXWebRtcView.java
Copyright Apache License 2.0
Author : matrix-org
/**
 * Sets the z-order of this {@link WebRTCView} in the stacking space of all
 * {@code WebRTCView}s. For more details, refer to the doreplacedentation of the
 * {@code zOrder} property of the JavaScript counterpart of
 * {@code WebRTCView} i.e. {@code RTCView}.
 *
 * @param zOrder The z-order to set on this {@code WebRTCView}.
 */
public void setZOrder(int zOrder) {
    SurfaceViewRenderer surfaceViewRenderer = getSurfaceViewRenderer();
    switch(zOrder) {
        case 0:
            surfaceViewRenderer.setZOrderMediaOverlay(false);
            break;
        case 1:
            surfaceViewRenderer.setZOrderMediaOverlay(true);
            break;
        case 2:
            surfaceViewRenderer.setZOrderOnTop(true);
            break;
    }
}

19 View Complete Implementation : WebRtcCallScreen.java
Copyright GNU General Public License v3.0
Author : signalapp
private void setRinging(SurfaceViewRenderer localRenderer) {
    if (localLargeRenderLayout.getChildCount() == 0) {
        if (localRenderer.getParent() != null) {
            ((ViewGroup) localRenderer.getParent()).removeView(localRenderer);
        }
        localLargeRenderLayout.setPosition(0, 0, 100, 100);
        localRenderer.setLayoutParams(new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
        localRenderer.setMirror(true);
        localRenderer.setZOrderMediaOverlay(true);
        localLargeRenderLayout.addView(localRenderer);
        this.localRenderer = localRenderer;
    }
}

18 View Complete Implementation : WebRtcViewModel.java
Copyright GNU General Public License v3.0
Author : bcmapp
public clreplaced WebRtcViewModel {

    public enum State {

        // Normal states
        CALL_INCOMING,
        CALL_OUTGOING,
        CALL_CONNECTED,
        CALL_RINGING,
        CALL_BUSY,
        CALL_DISCONNECTED,
        // Error states
        NETWORK_FAILURE,
        RECIPIENT_UNAVAILABLE,
        NO_SUCH_USER,
        UNTRUSTED_IDENreplacedY
    }

    @NonNull
    private final State state;

    @Nullable
    private final Recipient recipient;

    @Nullable
    private final IdenreplacedyKey idenreplacedyKey;

    private final boolean remoteVideoEnabled;

    private final boolean isBluetoothAvailable;

    private final boolean isMicrophoneEnabled;

    private final CameraState localCameraState;

    private final SurfaceViewRenderer localRenderer;

    private final SurfaceViewRenderer remoteRenderer;

    public WebRtcViewModel(@NonNull State state, @Nullable Recipient recipient, @NonNull CameraState localCameraState, @Nullable SurfaceViewRenderer localRenderer, @Nullable SurfaceViewRenderer remoteRenderer, boolean remoteVideoEnabled, boolean isBluetoothAvailable, boolean isMicrophoneEnabled) {
        this(state, recipient, null, localCameraState, localRenderer, remoteRenderer, remoteVideoEnabled, isBluetoothAvailable, isMicrophoneEnabled);
    }

    public WebRtcViewModel(@NonNull State state, @Nullable Recipient recipient, @Nullable IdenreplacedyKey idenreplacedyKey, @NonNull CameraState localCameraState, @Nullable SurfaceViewRenderer localRenderer, @Nullable SurfaceViewRenderer remoteRenderer, boolean remoteVideoEnabled, boolean isBluetoothAvailable, boolean isMicrophoneEnabled) {
        this.state = state;
        this.recipient = recipient;
        this.localCameraState = localCameraState;
        this.localRenderer = localRenderer;
        this.remoteRenderer = remoteRenderer;
        this.idenreplacedyKey = idenreplacedyKey;
        this.remoteVideoEnabled = remoteVideoEnabled;
        this.isBluetoothAvailable = isBluetoothAvailable;
        this.isMicrophoneEnabled = isMicrophoneEnabled;
    }

    @NonNull
    public State getState() {
        return state;
    }

    @Nullable
    public Recipient getRecipient() {
        return recipient;
    }

    @NonNull
    public CameraState getLocalCameraState() {
        return localCameraState;
    }

    @Nullable
    public IdenreplacedyKey getIdenreplacedyKey() {
        return idenreplacedyKey;
    }

    public boolean isRemoteVideoEnabled() {
        return remoteVideoEnabled;
    }

    public boolean isBluetoothAvailable() {
        return isBluetoothAvailable;
    }

    public boolean isMicrophoneEnabled() {
        return isMicrophoneEnabled;
    }

    @Nullable
    public SurfaceViewRenderer getLocalRenderer() {
        return localRenderer;
    }

    @Nullable
    public SurfaceViewRenderer getRemoteRenderer() {
        return remoteRenderer;
    }

    @NonNull
    public String toString() {
        String address = "";
        if (recipient != null) {
            address = recipient.getAddress().toString();
        }
        return "[State: " + state + ", recipient: " + address + ", idenreplacedy: " + idenreplacedyKey + ", remoteVideo: " + remoteVideoEnabled + ", localVideo: " + localCameraState.isEnabled() + "]";
    }
}

18 View Complete Implementation : CallSession.java
Copyright MIT License
Author : ddssingsong
public void setupLocalVideo(SurfaceViewRenderer SurfaceViewRenderer) {
    ProxyVideoSink sink = new ProxyVideoSink();
    sink.setTarget(SurfaceViewRenderer);
    if (_localStream.videoTracks.size() > 0) {
        _localStream.videoTracks.get(0).addSink(sink);
    }
}

18 View Complete Implementation : CallActivity.java
Copyright BSD 3-Clause "New" or "Revised" License
Author : duttaime
/**
 * Activity for peer connection call setup, call waiting
 * and call view.
 */
public clreplaced CallActivity extends Activity implements AppRTCClient.SignalingEvents, PeerConnectionClient.PeerConnectionEvents, CallFragment.OnCallEvents {

    private static final String TAG = "CallRTCClient";

    public static final String EXTRA_ROOMID = "org.appspot.apprtc.ROOMID";

    public static final String EXTRA_URLPARAMETERS = "org.appspot.apprtc.URLPARAMETERS";

    public static final String EXTRA_LOOPBACK = "org.appspot.apprtc.LOOPBACK";

    public static final String EXTRA_VIDEO_CALL = "org.appspot.apprtc.VIDEO_CALL";

    public static final String EXTRA_SCREENCAPTURE = "org.appspot.apprtc.SCREENCAPTURE";

    public static final String EXTRA_CAMERA2 = "org.appspot.apprtc.CAMERA2";

    public static final String EXTRA_VIDEO_WIDTH = "org.appspot.apprtc.VIDEO_WIDTH";

    public static final String EXTRA_VIDEO_HEIGHT = "org.appspot.apprtc.VIDEO_HEIGHT";

    public static final String EXTRA_VIDEO_FPS = "org.appspot.apprtc.VIDEO_FPS";

    public static final String EXTRA_VIDEO_CAPTUREQUALITYSLIDER_ENABLED = "org.appsopt.apprtc.VIDEO_CAPTUREQUALITYSLIDER";

    public static final String EXTRA_VIDEO_BITRATE = "org.appspot.apprtc.VIDEO_BITRATE";

    public static final String EXTRA_VIDEOCODEC = "org.appspot.apprtc.VIDEOCODEC";

    public static final String EXTRA_HWCODEC_ENABLED = "org.appspot.apprtc.HWCODEC";

    public static final String EXTRA_CAPTURETOTEXTURE_ENABLED = "org.appspot.apprtc.CAPTURETOTEXTURE";

    public static final String EXTRA_FLEXFEC_ENABLED = "org.appspot.apprtc.FLEXFEC";

    public static final String EXTRA_AUDIO_BITRATE = "org.appspot.apprtc.AUDIO_BITRATE";

    public static final String EXTRA_AUDIOCODEC = "org.appspot.apprtc.AUDIOCODEC";

    public static final String EXTRA_NOAUDIOPROCESSING_ENABLED = "org.appspot.apprtc.NOAUDIOPROCESSING";

    public static final String EXTRA_AECDUMP_ENABLED = "org.appspot.apprtc.AECDUMP";

    public static final String EXTRA_SAVE_INPUT_AUDIO_TO_FILE_ENABLED = "org.appspot.apprtc.SAVE_INPUT_AUDIO_TO_FILE";

    public static final String EXTRA_OPENSLES_ENABLED = "org.appspot.apprtc.OPENSLES";

    public static final String EXTRA_DISABLE_BUILT_IN_AEC = "org.appspot.apprtc.DISABLE_BUILT_IN_AEC";

    public static final String EXTRA_DISABLE_BUILT_IN_AGC = "org.appspot.apprtc.DISABLE_BUILT_IN_AGC";

    public static final String EXTRA_DISABLE_BUILT_IN_NS = "org.appspot.apprtc.DISABLE_BUILT_IN_NS";

    public static final String EXTRA_DISABLE_WEBRTC_AGC_AND_HPF = "org.appspot.apprtc.DISABLE_WEBRTC_GAIN_CONTROL";

    public static final String EXTRA_DISPLAY_HUD = "org.appspot.apprtc.DISPLAY_HUD";

    public static final String EXTRA_TRACING = "org.appspot.apprtc.TRACING";

    public static final String EXTRA_CMDLINE = "org.appspot.apprtc.CMDLINE";

    public static final String EXTRA_RUNTIME = "org.appspot.apprtc.RUNTIME";

    public static final String EXTRA_VIDEO_FILE_AS_CAMERA = "org.appspot.apprtc.VIDEO_FILE_AS_CAMERA";

    public static final String EXTRA_SAVE_REMOTE_VIDEO_TO_FILE = "org.appspot.apprtc.SAVE_REMOTE_VIDEO_TO_FILE";

    public static final String EXTRA_SAVE_REMOTE_VIDEO_TO_FILE_WIDTH = "org.appspot.apprtc.SAVE_REMOTE_VIDEO_TO_FILE_WIDTH";

    public static final String EXTRA_SAVE_REMOTE_VIDEO_TO_FILE_HEIGHT = "org.appspot.apprtc.SAVE_REMOTE_VIDEO_TO_FILE_HEIGHT";

    public static final String EXTRA_USE_VALUES_FROM_INTENT = "org.appspot.apprtc.USE_VALUES_FROM_INTENT";

    public static final String EXTRA_DATA_CHANNEL_ENABLED = "org.appspot.apprtc.DATA_CHANNEL_ENABLED";

    public static final String EXTRA_ORDERED = "org.appspot.apprtc.ORDERED";

    public static final String EXTRA_MAX_RETRANSMITS_MS = "org.appspot.apprtc.MAX_RETRANSMITS_MS";

    public static final String EXTRA_MAX_RETRANSMITS = "org.appspot.apprtc.MAX_RETRANSMITS";

    public static final String EXTRA_PROTOCOL = "org.appspot.apprtc.PROTOCOL";

    public static final String EXTRA_NEGOTIATED = "org.appspot.apprtc.NEGOTIATED";

    public static final String EXTRA_ID = "org.appspot.apprtc.ID";

    public static final String EXTRA_ENABLE_RTCEVENTLOG = "org.appspot.apprtc.ENABLE_RTCEVENTLOG";

    public static final String EXTRA_USE_LEGACY_AUDIO_DEVICE = "org.appspot.apprtc.USE_LEGACY_AUDIO_DEVICE";

    private static final int CAPTURE_PERMISSION_REQUEST_CODE = 1;

    // List of mandatory application permissions.
    private static final String[] MANDATORY_PERMISSIONS = { "android.permission.MODIFY_AUDIO_SETTINGS", "android.permission.RECORD_AUDIO", "android.permission.INTERNET" };

    // Peer connection statistics callback period in ms.
    private static final int STAT_CALLBACK_PERIOD = 1000;

    private static clreplaced ProxyVideoSink implements VideoSink {

        private VideoSink target;

        @Override
        synchronized public void onFrame(VideoFrame frame) {
            if (target == null) {
                Logging.d(TAG, "Dropping frame in proxy because target is null.");
                return;
            }
            target.onFrame(frame);
        }

        synchronized public void setTarget(VideoSink target) {
            this.target = target;
        }
    }

    private final ProxyVideoSink remoteProxyRenderer = new ProxyVideoSink();

    private final ProxyVideoSink localProxyVideoSink = new ProxyVideoSink();

    @Nullable
    private PeerConnectionClient peerConnectionClient = null;

    @Nullable
    private AppRTCClient appRtcClient;

    @Nullable
    private SignalingParameters signalingParameters;

    @Nullable
    private AppRTCAudioManager audioManager = null;

    @Nullable
    private SurfaceViewRenderer pipRenderer;

    @Nullable
    private SurfaceViewRenderer fullscreenRenderer;

    @Nullable
    private VideoFileRenderer videoFileRenderer;

    private final List<VideoSink> remoteSinks = new ArrayList<>();

    private Toast logToast;

    private boolean commandLineRun;

    private boolean activityRunning;

    private RoomConnectionParameters roomConnectionParameters;

    @Nullable
    private PeerConnectionParameters peerConnectionParameters;

    private boolean iceConnected;

    private boolean isError;

    private boolean callControlFragmentVisible = true;

    private long callStartedTimeMs = 0;

    private boolean micEnabled = true;

    private boolean screencaptureEnabled = false;

    private static Intent mediaProjectionPermissionResultData;

    private static int mediaProjectionPermissionResultCode;

    // True if local view is in the fullscreen renderer.
    private boolean isSwappedFeeds;

    // Controls
    private CallFragment callFragment;

    private HudFragment hudFragment;

    private CpuMonitor cpuMonitor;

    @Override
    // TODO(bugs.webrtc.org/8580): LayoutParams.FLAG_TURN_SCREEN_ON and
    // LayoutParams.FLAG_SHOW_WHEN_LOCKED are deprecated.
    @SuppressWarnings("deprecation")
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Thread.setDefaultUncaughtExceptionHandler(new UnhandledExceptionHandler(this));
        // Set window styles for fullscreen-window size. Needs to be done before
        // adding content.
        requestWindowFeature(Window.FEATURE_NO_replacedLE);
        getWindow().addFlags(LayoutParams.FLAG_FULLSCREEN | LayoutParams.FLAG_KEEP_SCREEN_ON | LayoutParams.FLAG_SHOW_WHEN_LOCKED | LayoutParams.FLAG_TURN_SCREEN_ON);
        getWindow().getDecorView().setSystemUiVisibility(getSystemUiVisibility());
        setContentView(R.layout.activity_call);
        iceConnected = false;
        signalingParameters = null;
        // Create UI controls.
        pipRenderer = findViewById(R.id.pip_video_view);
        fullscreenRenderer = findViewById(R.id.fullscreen_video_view);
        callFragment = new CallFragment();
        hudFragment = new HudFragment();
        // Show/hide call control fragment on view click.
        View.OnClickListener listener = new View.OnClickListener() {

            @Override
            public void onClick(View view) {
                toggleCallControlFragmentVisibility();
            }
        };
        // Swap feeds on pip view click.
        pipRenderer.setOnClickListener(new View.OnClickListener() {

            @Override
            public void onClick(View view) {
                setSwappedFeeds(!isSwappedFeeds);
            }
        });
        fullscreenRenderer.setOnClickListener(listener);
        remoteSinks.add(remoteProxyRenderer);
        final Intent intent = getIntent();
        final EglBase eglBase = EglBase.create();
        // Create video renderers.
        pipRenderer.init(eglBase.getEglBaseContext(), null);
        pipRenderer.setScalingType(ScalingType.SCALE_ASPECT_FIT);
        String saveRemoteVideoToFile = intent.getStringExtra(EXTRA_SAVE_REMOTE_VIDEO_TO_FILE);
        // When saveRemoteVideoToFile is set we save the video from the remote to a file.
        if (saveRemoteVideoToFile != null) {
            int videoOutWidth = intent.getIntExtra(EXTRA_SAVE_REMOTE_VIDEO_TO_FILE_WIDTH, 0);
            int videoOutHeight = intent.getIntExtra(EXTRA_SAVE_REMOTE_VIDEO_TO_FILE_HEIGHT, 0);
            try {
                videoFileRenderer = new VideoFileRenderer(saveRemoteVideoToFile, videoOutWidth, videoOutHeight, eglBase.getEglBaseContext());
                remoteSinks.add(videoFileRenderer);
            } catch (IOException e) {
                throw new RuntimeException("Failed to open video file for output: " + saveRemoteVideoToFile, e);
            }
        }
        fullscreenRenderer.init(eglBase.getEglBaseContext(), null);
        fullscreenRenderer.setScalingType(ScalingType.SCALE_ASPECT_FILL);
        pipRenderer.setZOrderMediaOverlay(true);
        pipRenderer.setEnableHardwareScaler(true);
        fullscreenRenderer.setEnableHardwareScaler(false);
        // Start with local feed in fullscreen and swap it to the pip when the call is connected.
        setSwappedFeeds(true);
        // Check for mandatory permissions.
        for (String permission : MANDATORY_PERMISSIONS) {
            if (checkCallingOrSelfPermission(permission) != PackageManager.PERMISSION_GRANTED) {
                logAndToast("Permission " + permission + " is not granted");
                setResult(RESULT_CANCELED);
                finish();
                return;
            }
        }
        Uri roomUri = intent.getData();
        if (roomUri == null) {
            logAndToast(getString(org.appspot.apprtc.R.string.missing_url));
            Log.e(TAG, "Didn't get any URL in intent!");
            setResult(RESULT_CANCELED);
            finish();
            return;
        }
        // Get Intent parameters.
        String roomId = intent.getStringExtra(EXTRA_ROOMID);
        Log.d(TAG, "Room ID: " + roomId);
        if (roomId == null || roomId.length() == 0) {
            logAndToast(getString(org.appspot.apprtc.R.string.missing_url));
            Log.e(TAG, "Incorrect room ID in intent!");
            setResult(RESULT_CANCELED);
            finish();
            return;
        }
        boolean loopback = intent.getBooleanExtra(EXTRA_LOOPBACK, false);
        boolean tracing = intent.getBooleanExtra(EXTRA_TRACING, false);
        int videoWidth = intent.getIntExtra(EXTRA_VIDEO_WIDTH, 0);
        int videoHeight = intent.getIntExtra(EXTRA_VIDEO_HEIGHT, 0);
        screencaptureEnabled = intent.getBooleanExtra(EXTRA_SCREENCAPTURE, false);
        // If capturing format is not specified for screencapture, use screen resolution.
        if (screencaptureEnabled && videoWidth == 0 && videoHeight == 0) {
            DisplayMetrics displayMetrics = getDisplayMetrics();
            videoWidth = displayMetrics.widthPixels;
            videoHeight = displayMetrics.heightPixels;
        }
        DataChannelParameters dataChannelParameters = null;
        if (intent.getBooleanExtra(EXTRA_DATA_CHANNEL_ENABLED, false)) {
            dataChannelParameters = new DataChannelParameters(intent.getBooleanExtra(EXTRA_ORDERED, true), intent.getIntExtra(EXTRA_MAX_RETRANSMITS_MS, -1), intent.getIntExtra(EXTRA_MAX_RETRANSMITS, -1), intent.getStringExtra(EXTRA_PROTOCOL), intent.getBooleanExtra(EXTRA_NEGOTIATED, false), intent.getIntExtra(EXTRA_ID, -1));
        }
        peerConnectionParameters = new PeerConnectionParameters(intent.getBooleanExtra(EXTRA_VIDEO_CALL, true), loopback, tracing, videoWidth, videoHeight, intent.getIntExtra(EXTRA_VIDEO_FPS, 0), intent.getIntExtra(EXTRA_VIDEO_BITRATE, 0), intent.getStringExtra(EXTRA_VIDEOCODEC), intent.getBooleanExtra(EXTRA_HWCODEC_ENABLED, true), intent.getBooleanExtra(EXTRA_FLEXFEC_ENABLED, false), intent.getIntExtra(EXTRA_AUDIO_BITRATE, 0), intent.getStringExtra(EXTRA_AUDIOCODEC), intent.getBooleanExtra(EXTRA_NOAUDIOPROCESSING_ENABLED, false), intent.getBooleanExtra(EXTRA_AECDUMP_ENABLED, false), intent.getBooleanExtra(EXTRA_SAVE_INPUT_AUDIO_TO_FILE_ENABLED, false), intent.getBooleanExtra(EXTRA_OPENSLES_ENABLED, false), intent.getBooleanExtra(EXTRA_DISABLE_BUILT_IN_AEC, false), intent.getBooleanExtra(EXTRA_DISABLE_BUILT_IN_AGC, false), intent.getBooleanExtra(EXTRA_DISABLE_BUILT_IN_NS, false), intent.getBooleanExtra(EXTRA_DISABLE_WEBRTC_AGC_AND_HPF, false), intent.getBooleanExtra(EXTRA_ENABLE_RTCEVENTLOG, false), intent.getBooleanExtra(EXTRA_USE_LEGACY_AUDIO_DEVICE, false), dataChannelParameters);
        commandLineRun = intent.getBooleanExtra(EXTRA_CMDLINE, false);
        int runTimeMs = intent.getIntExtra(EXTRA_RUNTIME, 0);
        Log.d(TAG, "VIDEO_FILE: '" + intent.getStringExtra(EXTRA_VIDEO_FILE_AS_CAMERA) + "'");
        // Create connection client. Use DirectRTCClient if room name is an IP otherwise use the
        // standard WebSocketRTCClient.
        if (loopback || !DirectRTCClient.IP_PATTERN.matcher(roomId).matches()) {
            appRtcClient = new WebSocketRTCClient(this);
        } else {
            Log.i(TAG, "Using DirectRTCClient because room name looks like an IP.");
            appRtcClient = new DirectRTCClient(this);
        }
        // Create connection parameters.
        String urlParameters = intent.getStringExtra(EXTRA_URLPARAMETERS);
        roomConnectionParameters = new RoomConnectionParameters(roomUri.toString(), roomId, loopback, urlParameters);
        // Create CPU monitor
        if (CpuMonitor.isSupported()) {
            cpuMonitor = new CpuMonitor(this);
            hudFragment.setCpuMonitor(cpuMonitor);
        }
        // Send intent arguments to fragments.
        callFragment.setArguments(intent.getExtras());
        hudFragment.setArguments(intent.getExtras());
        // Activate call and HUD fragments and start the call.
        FragmentTransaction ft = getFragmentManager().beginTransaction();
        ft.add(R.id.call_fragment_container, callFragment);
        ft.add(R.id.hud_fragment_container, hudFragment);
        ft.commit();
        // For command line execution run connection for <runTimeMs> and exit.
        if (commandLineRun && runTimeMs > 0) {
            (new Handler()).postDelayed(new Runnable() {

                @Override
                public void run() {
                    disconnect();
                }
            }, runTimeMs);
        }
        // Create peer connection client.
        peerConnectionClient = new PeerConnectionClient(getApplicationContext(), eglBase, peerConnectionParameters, CallActivity.this);
        PeerConnectionFactory.Options options = new PeerConnectionFactory.Options();
        if (loopback) {
            options.networkIgnoreMask = 0;
        }
        peerConnectionClient.createPeerConnectionFactory(options);
        if (screencaptureEnabled) {
            startScreenCapture();
        } else {
            startCall();
        }
    }

    @TargetApi(17)
    private DisplayMetrics getDisplayMetrics() {
        DisplayMetrics displayMetrics = new DisplayMetrics();
        WindowManager windowManager = (WindowManager) getApplication().getSystemService(Context.WINDOW_SERVICE);
        windowManager.getDefaultDisplay().getRealMetrics(displayMetrics);
        return displayMetrics;
    }

    @TargetApi(19)
    private static int getSystemUiVisibility() {
        int flags = View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_FULLSCREEN;
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
            flags |= View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
        }
        return flags;
    }

    @TargetApi(21)
    private void startScreenCapture() {
        MediaProjectionManager mediaProjectionManager = (MediaProjectionManager) getApplication().getSystemService(Context.MEDIA_PROJECTION_SERVICE);
        startActivityForResult(mediaProjectionManager.createScreenCaptureIntent(), CAPTURE_PERMISSION_REQUEST_CODE);
    }

    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        if (requestCode != CAPTURE_PERMISSION_REQUEST_CODE)
            return;
        mediaProjectionPermissionResultCode = resultCode;
        mediaProjectionPermissionResultData = data;
        startCall();
    }

    private boolean useCamera2() {
        return Camera2Enumerator.isSupported(this) && getIntent().getBooleanExtra(EXTRA_CAMERA2, true);
    }

    private boolean captureToTexture() {
        return getIntent().getBooleanExtra(EXTRA_CAPTURETOTEXTURE_ENABLED, false);
    }

    @Nullable
    private VideoCapturer createCameraCapturer(CameraEnumerator enumerator) {
        final String[] deviceNames = enumerator.getDeviceNames();
        // First, try to find front facing camera
        Logging.d(TAG, "Looking for front facing cameras.");
        for (String deviceName : deviceNames) {
            if (enumerator.isFrontFacing(deviceName)) {
                Logging.d(TAG, "Creating front facing camera capturer.");
                VideoCapturer videoCapturer = enumerator.createCapturer(deviceName, null);
                if (videoCapturer != null) {
                    return videoCapturer;
                }
            }
        }
        // Front facing camera not found, try something else
        Logging.d(TAG, "Looking for other cameras.");
        for (String deviceName : deviceNames) {
            if (!enumerator.isFrontFacing(deviceName)) {
                Logging.d(TAG, "Creating other camera capturer.");
                VideoCapturer videoCapturer = enumerator.createCapturer(deviceName, null);
                if (videoCapturer != null) {
                    return videoCapturer;
                }
            }
        }
        return null;
    }

    @TargetApi(21)
    @Nullable
    private VideoCapturer createScreenCapturer() {
        if (mediaProjectionPermissionResultCode != Activity.RESULT_OK) {
            reportError("User didn't give permission to capture the screen.");
            return null;
        }
        return new ScreenCapturerAndroid(mediaProjectionPermissionResultData, new MediaProjection.Callback() {

            @Override
            public void onStop() {
                reportError("User revoked permission to capture the screen.");
            }
        });
    }

    // Activity interfaces
    @Override
    public void onStop() {
        super.onStop();
        activityRunning = false;
        // Don't stop the video when using screencapture to allow user to show other apps to the remote
        // end.
        if (peerConnectionClient != null && !screencaptureEnabled) {
            peerConnectionClient.stopVideoSource();
        }
        if (cpuMonitor != null) {
            cpuMonitor.pause();
        }
    }

    @Override
    public void onStart() {
        super.onStart();
        activityRunning = true;
        // Video is not paused for screencapture. See onPause.
        if (peerConnectionClient != null && !screencaptureEnabled) {
            peerConnectionClient.startVideoSource();
        }
        if (cpuMonitor != null) {
            cpuMonitor.resume();
        }
    }

    @Override
    protected void onDestroy() {
        Thread.setDefaultUncaughtExceptionHandler(null);
        disconnect();
        if (logToast != null) {
            logToast.cancel();
        }
        activityRunning = false;
        super.onDestroy();
    }

    // CallFragment.OnCallEvents interface implementation.
    @Override
    public void onCallHangUp() {
        disconnect();
    }

    @Override
    public void onCameraSwitch() {
        if (peerConnectionClient != null) {
            peerConnectionClient.switchCamera();
        }
    }

    @Override
    public void onVideoScalingSwitch(ScalingType scalingType) {
        fullscreenRenderer.setScalingType(scalingType);
    }

    @Override
    public void onCaptureFormatChange(int width, int height, int framerate) {
        if (peerConnectionClient != null) {
            peerConnectionClient.changeCaptureFormat(width, height, framerate);
        }
    }

    @Override
    public boolean onToggleMic() {
        if (peerConnectionClient != null) {
            micEnabled = !micEnabled;
            peerConnectionClient.setAudioEnabled(micEnabled);
        }
        return micEnabled;
    }

    // Helper functions.
    private void toggleCallControlFragmentVisibility() {
        if (!iceConnected || !callFragment.isAdded()) {
            return;
        }
        // Show/hide call control fragment
        callControlFragmentVisible = !callControlFragmentVisible;
        FragmentTransaction ft = getFragmentManager().beginTransaction();
        if (callControlFragmentVisible) {
            ft.show(callFragment);
            ft.show(hudFragment);
        } else {
            ft.hide(callFragment);
            ft.hide(hudFragment);
        }
        ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
        ft.commit();
    }

    private void startCall() {
        if (appRtcClient == null) {
            Log.e(TAG, "AppRTC client is not allocated for a call.");
            return;
        }
        callStartedTimeMs = System.currentTimeMillis();
        // Start room connection.
        logAndToast(getString(org.appspot.apprtc.R.string.connecting_to, roomConnectionParameters.roomUrl));
        appRtcClient.connectToRoom(roomConnectionParameters);
        // Create and audio manager that will take care of audio routing,
        // audio modes, audio device enumeration etc.
        audioManager = AppRTCAudioManager.create(getApplicationContext());
        // Store existing audio settings and change audio mode to
        // MODE_IN_COMMUNICATION for best possible VoIP performance.
        Log.d(TAG, "Starting the audio manager...");
        audioManager.start(new AudioManagerEvents() {

            // This method will be called each time the number of available audio
            // devices has changed.
            @Override
            public void onAudioDeviceChanged(AudioDevice audioDevice, Set<AudioDevice> availableAudioDevices) {
                onAudioManagerDevicesChanged(audioDevice, availableAudioDevices);
            }
        });
    }

    // Should be called from UI thread
    private void callConnected() {
        final long delta = System.currentTimeMillis() - callStartedTimeMs;
        Log.i(TAG, "Call connected: delay=" + delta + "ms");
        if (peerConnectionClient == null || isError) {
            Log.w(TAG, "Call is connected in closed or error state");
            return;
        }
        // Enable statistics callback.
        peerConnectionClient.enableStatsEvents(true, STAT_CALLBACK_PERIOD);
        setSwappedFeeds(false);
    }

    // This method is called when the audio manager reports audio device change,
    // e.g. from wired headset to speakerphone.
    private void onAudioManagerDevicesChanged(final AudioDevice device, final Set<AudioDevice> availableDevices) {
        Log.d(TAG, "onAudioManagerDevicesChanged: " + availableDevices + ", " + "selected: " + device);
    // TODO(henrika): add callback handler.
    }

    // Disconnect from remote resources, dispose of local resources, and exit.
    private void disconnect() {
        activityRunning = false;
        remoteProxyRenderer.setTarget(null);
        localProxyVideoSink.setTarget(null);
        if (appRtcClient != null) {
            appRtcClient.disconnectFromRoom();
            appRtcClient = null;
        }
        if (pipRenderer != null) {
            pipRenderer.release();
            pipRenderer = null;
        }
        if (videoFileRenderer != null) {
            videoFileRenderer.release();
            videoFileRenderer = null;
        }
        if (fullscreenRenderer != null) {
            fullscreenRenderer.release();
            fullscreenRenderer = null;
        }
        if (peerConnectionClient != null) {
            peerConnectionClient.close();
            peerConnectionClient = null;
        }
        if (audioManager != null) {
            audioManager.stop();
            audioManager = null;
        }
        if (iceConnected && !isError) {
            setResult(RESULT_OK);
        } else {
            setResult(RESULT_CANCELED);
        }
        finish();
    }

    private void disconnectWithErrorMessage(final String errorMessage) {
        if (commandLineRun || !activityRunning) {
            Log.e(TAG, "Critical error: " + errorMessage);
            disconnect();
        } else {
            new AlertDialog.Builder(this).setreplacedle(getText(org.appspot.apprtc.R.string.channel_error_replacedle)).setMessage(errorMessage).setCancelable(false).setNeutralButton(org.appspot.apprtc.R.string.ok, new DialogInterface.OnClickListener() {

                @Override
                public void onClick(DialogInterface dialog, int id) {
                    dialog.cancel();
                    disconnect();
                }
            }).create().show();
        }
    }

    // Log |msg| and Toast about it.
    private void logAndToast(String msg) {
        Log.d(TAG, msg);
        if (logToast != null) {
            logToast.cancel();
        }
        logToast = Toast.makeText(this, msg, Toast.LENGTH_SHORT);
        logToast.show();
    }

    private void reportError(final String description) {
        runOnUiThread(new Runnable() {

            @Override
            public void run() {
                if (!isError) {
                    isError = true;
                    disconnectWithErrorMessage(description);
                }
            }
        });
    }

    @Nullable
    private VideoCapturer createVideoCapturer() {
        final VideoCapturer videoCapturer;
        String videoFileAsCamera = getIntent().getStringExtra(EXTRA_VIDEO_FILE_AS_CAMERA);
        if (videoFileAsCamera != null) {
            try {
                videoCapturer = new FileVideoCapturer(videoFileAsCamera);
            } catch (IOException e) {
                reportError("Failed to open video file for emulated camera");
                return null;
            }
        } else if (screencaptureEnabled) {
            return createScreenCapturer();
        } else if (useCamera2()) {
            if (!captureToTexture()) {
                reportError(getString(org.appspot.apprtc.R.string.camera2_texture_only_error));
                return null;
            }
            Logging.d(TAG, "Creating capturer using camera2 API.");
            videoCapturer = createCameraCapturer(new Camera2Enumerator(this));
        } else {
            Logging.d(TAG, "Creating capturer using camera1 API.");
            videoCapturer = createCameraCapturer(new Camera1Enumerator(captureToTexture()));
        }
        if (videoCapturer == null) {
            reportError("Failed to open camera");
            return null;
        }
        return videoCapturer;
    }

    private void setSwappedFeeds(boolean isSwappedFeeds) {
        Logging.d(TAG, "setSwappedFeeds: " + isSwappedFeeds);
        this.isSwappedFeeds = isSwappedFeeds;
        localProxyVideoSink.setTarget(isSwappedFeeds ? fullscreenRenderer : pipRenderer);
        remoteProxyRenderer.setTarget(isSwappedFeeds ? pipRenderer : fullscreenRenderer);
        fullscreenRenderer.setMirror(isSwappedFeeds);
        pipRenderer.setMirror(!isSwappedFeeds);
    }

    // -----Implementation of AppRTCClient.AppRTCSignalingEvents ---------------
    // All callbacks are invoked from websocket signaling looper thread and
    // are routed to UI thread.
    private void onConnectedToRoomInternal(final SignalingParameters params) {
        final long delta = System.currentTimeMillis() - callStartedTimeMs;
        signalingParameters = params;
        logAndToast("Creating peer connection, delay=" + delta + "ms");
        VideoCapturer videoCapturer = null;
        if (peerConnectionParameters.videoCallEnabled) {
            videoCapturer = createVideoCapturer();
        }
        peerConnectionClient.createPeerConnection(localProxyVideoSink, remoteSinks, videoCapturer, signalingParameters);
        if (signalingParameters.initiator) {
            logAndToast("Creating OFFER...");
            // Create offer. Offer SDP will be sent to answering client in
            // PeerConnectionEvents.onLocalDescription event.
            peerConnectionClient.createOffer();
        } else {
            if (params.offerSdp != null) {
                peerConnectionClient.setRemoteDescription(params.offerSdp);
                logAndToast("Creating ANSWER...");
                // Create answer. Answer SDP will be sent to offering client in
                // PeerConnectionEvents.onLocalDescription event.
                peerConnectionClient.createAnswer();
            }
            if (params.iceCandidates != null) {
                // Add remote ICE candidates from room.
                for (IceCandidate iceCandidate : params.iceCandidates) {
                    peerConnectionClient.addRemoteIceCandidate(iceCandidate);
                }
            }
        }
    }

    @Override
    public void onConnectedToRoom(final SignalingParameters params) {
        runOnUiThread(new Runnable() {

            @Override
            public void run() {
                onConnectedToRoomInternal(params);
            }
        });
    }

    @Override
    public void onRemoteDescription(final SessionDescription sdp) {
        final long delta = System.currentTimeMillis() - callStartedTimeMs;
        runOnUiThread(new Runnable() {

            @Override
            public void run() {
                if (peerConnectionClient == null) {
                    Log.e(TAG, "Received remote SDP for non-initilized peer connection.");
                    return;
                }
                logAndToast("Received remote " + sdp.type + ", delay=" + delta + "ms");
                peerConnectionClient.setRemoteDescription(sdp);
                if (!signalingParameters.initiator) {
                    logAndToast("Creating ANSWER...");
                    // Create answer. Answer SDP will be sent to offering client in
                    // PeerConnectionEvents.onLocalDescription event.
                    peerConnectionClient.createAnswer();
                }
            }
        });
    }

    @Override
    public void onRemoteIceCandidate(final IceCandidate candidate) {
        runOnUiThread(new Runnable() {

            @Override
            public void run() {
                if (peerConnectionClient == null) {
                    Log.e(TAG, "Received ICE candidate for a non-initialized peer connection.");
                    return;
                }
                peerConnectionClient.addRemoteIceCandidate(candidate);
            }
        });
    }

    @Override
    public void onRemoteIceCandidatesRemoved(final IceCandidate[] candidates) {
        runOnUiThread(new Runnable() {

            @Override
            public void run() {
                if (peerConnectionClient == null) {
                    Log.e(TAG, "Received ICE candidate removals for a non-initialized peer connection.");
                    return;
                }
                peerConnectionClient.removeRemoteIceCandidates(candidates);
            }
        });
    }

    @Override
    public void onChannelClose() {
        runOnUiThread(new Runnable() {

            @Override
            public void run() {
                logAndToast("Remote end hung up; dropping PeerConnection");
                disconnect();
            }
        });
    }

    @Override
    public void onChannelError(final String description) {
        reportError(description);
    }

    // -----Implementation of PeerConnectionClient.PeerConnectionEvents.---------
    // Send local peer connection SDP and ICE candidates to remote party.
    // All callbacks are invoked from peer connection client looper thread and
    // are routed to UI thread.
    @Override
    public void onLocalDescription(final SessionDescription sdp) {
        final long delta = System.currentTimeMillis() - callStartedTimeMs;
        runOnUiThread(new Runnable() {

            @Override
            public void run() {
                if (appRtcClient != null) {
                    logAndToast("Sending " + sdp.type + ", delay=" + delta + "ms");
                    if (signalingParameters.initiator) {
                        appRtcClient.sendOfferSdp(sdp);
                    } else {
                        appRtcClient.sendAnswerSdp(sdp);
                    }
                }
                if (peerConnectionParameters.videoMaxBitrate > 0) {
                    Log.d(TAG, "Set video maximum bitrate: " + peerConnectionParameters.videoMaxBitrate);
                    peerConnectionClient.setVideoMaxBitrate(peerConnectionParameters.videoMaxBitrate);
                }
            }
        });
    }

    @Override
    public void onIceCandidate(final IceCandidate candidate) {
        runOnUiThread(new Runnable() {

            @Override
            public void run() {
                if (appRtcClient != null) {
                    appRtcClient.sendLocalIceCandidate(candidate);
                }
            }
        });
    }

    @Override
    public void onIceCandidatesRemoved(final IceCandidate[] candidates) {
        runOnUiThread(new Runnable() {

            @Override
            public void run() {
                if (appRtcClient != null) {
                    appRtcClient.sendLocalIceCandidateRemovals(candidates);
                }
            }
        });
    }

    @Override
    public void onIceConnected() {
        final long delta = System.currentTimeMillis() - callStartedTimeMs;
        runOnUiThread(new Runnable() {

            @Override
            public void run() {
                logAndToast("ICE connected, delay=" + delta + "ms");
                iceConnected = true;
                callConnected();
            }
        });
    }

    @Override
    public void onIceDisconnected() {
        runOnUiThread(new Runnable() {

            @Override
            public void run() {
                logAndToast("ICE disconnected");
                iceConnected = false;
                disconnect();
            }
        });
    }

    @Override
    public void onPeerConnectionClosed() {
    }

    @Override
    public void onPeerConnectionStatsReady(final StatsReport[] reports) {
        runOnUiThread(new Runnable() {

            @Override
            public void run() {
                if (!isError && iceConnected) {
                    hudFragment.updateEncoderStatistics(reports);
                }
            }
        });
    }

    @Override
    public void onPeerConnectionError(final String description) {
        reportError(description);
    }
}

18 View Complete Implementation : MXWebRtcView.java
Copyright Apache License 2.0
Author : matrix-org
/**
 * Sets the indicator which determines whether this {@code WebRTCView} is to
 * mirror the video represented by {@link #videoTrack} during its rendering.
 *
 * @param mirror If this {@code WebRTCView} is to mirror the video
 *               represented by {@code videoTrack} during its rendering, {@code true};
 *               otherwise, {@code false}.
 */
public void setMirror(boolean mirror) {
    if (this.mirror != mirror) {
        this.mirror = mirror;
        SurfaceViewRenderer surfaceViewRenderer = getSurfaceViewRenderer();
        surfaceViewRenderer.setMirror(mirror);
        // SurfaceViewRenderer takes the value of its mirror property into
        // account upon its layout.
        requestSurfaceViewRendererLayout();
    }
}

18 View Complete Implementation : PeerVideoActivity.java
Copyright Apache License 2.0
Author : nubomedia-vtt
/**
 * Activity for receiving the video stream of a peer
 * (based on PeerVideoActivity of Pubnub's video chat tutorial example.
 */
public clreplaced PeerVideoActivity extends Activity implements NBMWebRTCPeer.Observer, RoomListener {

    private static final String TAG = "PeerVideoActivity";

    private NBMWebRTCPeer nbmWebRTCPeer;

    private SurfaceViewRenderer masterView;

    private SurfaceViewRenderer localView;

    private Map<Integer, String> videoRequestUserMapping;

    private int publishVideoRequestId;

    private TextView mCallStatus;

    private String username;

    private boolean backPressed = false;

    private Thread backPressedThread = null;

    private Handler mHandler = null;

    private CallState callState;

    private enum CallState {

        IDLE, PUBLISHING, PUBLISHED, WAITING_REMOTE_USER, RECEIVING_REMOTE_USER
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_video_chat);
        getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
        mHandler = new Handler();
        masterView = (SurfaceViewRenderer) findViewById(R.id.gl_surface);
        localView = (SurfaceViewRenderer) findViewById(R.id.gl_surface_local);
        this.mCallStatus = (TextView) findViewById(R.id.call_status);
        callState = CallState.IDLE;
        MainActivity.getKurentoRoomAPIInstance().addObserver(this);
    }

    @Override
    protected void onStart() {
        super.onStart();
        Bundle extras = getIntent().getExtras();
        this.username = extras.getString(Constants.USER_NAME, "");
        Log.i(TAG, "username: " + username);
        EglBase rootEglBase = EglBase.create();
        masterView.init(rootEglBase.getEglBaseContext(), null);
        masterView.setScalingType(RendererCommon.ScalingType.SCALE_ASPECT_FILL);
        localView.init(rootEglBase.getEglBaseContext(), null);
        localView.setScalingType(RendererCommon.ScalingType.SCALE_ASPECT_FILL);
        NBMMediaConfiguration peerConnectionParameters = new NBMMediaConfiguration(NBMMediaConfiguration.NBMRendererType.OPENGLES, NBMMediaConfiguration.NBMAudioCodec.OPUS, 0, NBMMediaConfiguration.NBMVideoCodec.VP8, 0, new NBMMediaConfiguration.NBMVideoFormat(352, 288, PixelFormat.RGB_888, 20), NBMMediaConfiguration.NBMCameraPosition.FRONT);
        videoRequestUserMapping = new HashMap<>();
        nbmWebRTCPeer = new NBMWebRTCPeer(peerConnectionParameters, this, localView, this);
        nbmWebRTCPeer.registerMasterRenderer(masterView);
        Log.i(TAG, "Initializing nbmWebRTCPeer...");
        nbmWebRTCPeer.initialize();
        callState = CallState.PUBLISHING;
        mCallStatus.setText("Publishing...");
    }

    @Override
    protected void onStop() {
        endCall();
        super.onStop();
    }

    @Override
    protected void onPause() {
        nbmWebRTCPeer.stopLocalMedia();
        super.onPause();
    }

    @Override
    protected void onResume() {
        super.onResume();
        nbmWebRTCPeer.startLocalMedia();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.menu_video_chat, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        int id = item.gereplacedemId();
        // noinspection SimplifiableIfStatement
        if (id == R.id.action_settings) {
            return true;
        }
        return super.onOptionsItemSelected(item);
    }

    @Override
    public void onBackPressed() {
        // Data channel test code
        /*DataChannel channel = nbmWebRTCPeer.getDataChannel("local", "test_channel_static");
        if (channel.state() == DataChannel.State.OPEN) {
            sendHelloMessage(channel);
            Log.i(TAG, "[datachannel] Datachannel open, sending hello");
        }
        else {
            Log.i(TAG, "[datachannel] Channel is not open! State: " + channel.state());
        }
        Log.i(TAG, "[DataChannel] Testing for existing channel");
        DataChannel channel =  nbmWebRTCPeer.getDataChannel("local", "default");
        if (channel == null) {
            DataChannel.Init init = new DataChannel.Init();
            init.negotiated = false;
            init.ordered = true;
            Log.i(TAG, "[DataChannel] Channel does not exist, creating...");
            channel = nbmWebRTCPeer.createDataChannel("local", "test_channel", init);
        }
        else {
            Log.i(TAG, "[DataChannel] Channel already exists. State: " + channel.state());
            sendHelloMessage(channel);
        }*/
        // If back button has not been pressed in a while then trigger thread and toast notification
        if (!this.backPressed) {
            this.backPressed = true;
            Toast.makeText(this, "Press back again to end.", Toast.LENGTH_SHORT).show();
            this.backPressedThread = new Thread(new Runnable() {

                @Override
                public void run() {
                    try {
                        Thread.sleep(1000);
                        backPressed = false;
                    } catch (InterruptedException e) {
                        Log.d("VCA-oBP", "Successfully interrupted");
                    }
                }
            });
            this.backPressedThread.start();
        } else // If button pressed the second time then call super back pressed
        // (eventually calls onDestroy)
        {
            if (this.backPressedThread != null)
                this.backPressedThread.interrupt();
            super.onBackPressed();
        }
    }

    public void hangup(View view) {
        finish();
    }

    private void GenerateOfferForRemote(String remote_name) {
        nbmWebRTCPeer.generateOffer(remote_name, false);
        callState = CallState.WAITING_REMOTE_USER;
        runOnUiThread(new Runnable() {

            @Override
            public void run() {
                mCallStatus.setText(R.string.waiting_remote_stream);
            }
        });
    }

    public void receiveFromRemote(View view) {
    // GenerateOfferForRemote();
    }

    /**
     * Terminates the current call and ends activity
     */
    private void endCall() {
        callState = CallState.IDLE;
        try {
            if (nbmWebRTCPeer != null) {
                nbmWebRTCPeer.close();
                nbmWebRTCPeer = null;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    public void onInitialize() {
        nbmWebRTCPeer.generateOffer("local", true);
    }

    @Override
    public void onLocalSdpOfferGenerated(final SessionDescription sessionDescription, final NBMPeerConnection nbmPeerConnection) {
        if (callState == CallState.PUBLISHING || callState == CallState.PUBLISHED) {
            Log.d(TAG, "Sending " + sessionDescription.type);
            publishVideoRequestId = ++Constants.id;
            MainActivity.getKurentoRoomAPIInstance().sendPublishVideo(sessionDescription.description, false, publishVideoRequestId);
        } else {
            // Asking for remote user video
            Log.d(TAG, "Sending " + sessionDescription.type);
            publishVideoRequestId = ++Constants.id;
            String username = nbmPeerConnection.getConnectionId();
            videoRequestUserMapping.put(publishVideoRequestId, username);
            MainActivity.getKurentoRoomAPIInstance().sendReceiveVideoFrom(username, "webcam", sessionDescription.description, publishVideoRequestId);
        }
    }

    @Override
    public void onLocalSdpAnswerGenerated(SessionDescription sessionDescription, NBMPeerConnection nbmPeerConnection) {
    }

    @Override
    public void onIceCandidate(IceCandidate iceCandidate, NBMPeerConnection nbmPeerConnection) {
        int sendIceCandidateRequestId = ++Constants.id;
        if (callState == CallState.PUBLISHING || callState == CallState.PUBLISHED) {
            MainActivity.getKurentoRoomAPIInstance().sendOnIceCandidate(this.username, iceCandidate.sdp, iceCandidate.sdpMid, Integer.toString(iceCandidate.sdpMLineIndex), sendIceCandidateRequestId);
        } else {
            MainActivity.getKurentoRoomAPIInstance().sendOnIceCandidate(nbmPeerConnection.getConnectionId(), iceCandidate.sdp, iceCandidate.sdpMid, Integer.toString(iceCandidate.sdpMLineIndex), sendIceCandidateRequestId);
        }
    }

    @Override
    public void onIceStatusChanged(PeerConnection.IceConnectionState iceConnectionState, NBMPeerConnection nbmPeerConnection) {
        Log.i(TAG, "onIceStatusChanged");
    }

    @Override
    public void onRemoteStreamAdded(MediaStream mediaStream, NBMPeerConnection nbmPeerConnection) {
        Log.i(TAG, "onRemoteStreamAdded");
        nbmWebRTCPeer.setActiveMasterStream(mediaStream);
        runOnUiThread(new Runnable() {

            @Override
            public void run() {
                mCallStatus.setText("");
            }
        });
    }

    @Override
    public void onRemoteStreamRemoved(MediaStream mediaStream, NBMPeerConnection nbmPeerConnection) {
        Log.i(TAG, "onRemoteStreamRemoved");
    }

    @Override
    public void onPeerConnectionError(String s) {
        Log.e(TAG, "onPeerConnectionError:" + s);
    }

    @Override
    public void onDataChannel(DataChannel dataChannel, NBMPeerConnection connection) {
        Log.i(TAG, "[datachannel] Peer opened data channel");
    }

    @Override
    public void onBufferedAmountChange(long l, NBMPeerConnection connection, DataChannel channel) {
    }

    public void sendHelloMessage(DataChannel channel) {
        byte[] rawMessage = "Hello Peer!".getBytes(Charset.forName("UTF-8"));
        ByteBuffer directData = ByteBuffer.allocateDirect(rawMessage.length);
        directData.put(rawMessage);
        directData.flip();
        DataChannel.Buffer data = new DataChannel.Buffer(directData, false);
        channel.send(data);
    }

    @Override
    public void onStateChange(NBMPeerConnection connection, DataChannel channel) {
        Log.i(TAG, "[datachannel] DataChannel onStateChange: " + channel.state());
        if (channel.state() == DataChannel.State.OPEN) {
            sendHelloMessage(channel);
            Log.i(TAG, "[datachannel] Datachannel open, sending first hello");
        }
    }

    @Override
    public void onMessage(DataChannel.Buffer buffer, NBMPeerConnection connection, DataChannel channel) {
        Log.i(TAG, "[datachannel] Message received: " + buffer.toString());
        sendHelloMessage(channel);
    }

    private Runnable offerWhenReady = new Runnable() {

        @Override
        public void run() {
            // Generate offers to receive video from all peers in the room
            for (Map.Entry<String, Boolean> entry : MainActivity.userPublishList.entrySet()) {
                if (entry.getValue()) {
                    GenerateOfferForRemote(entry.getKey());
                    Log.i(TAG, "I'm " + username + " DERP: Generating offer for peer " + entry.getKey());
                    // Set value to false so that if this function is called again we won't
                    // generate another offer for this user
                    entry.setValue(false);
                }
            }
        }
    };

    @Override
    public void onRoomResponse(RoomResponse response) {
        Log.d(TAG, "OnRoomResponse:" + response);
        int requestId = response.getId();
        if (requestId == publishVideoRequestId) {
            SessionDescription sd = new SessionDescription(SessionDescription.Type.ANSWER, response.getValue("sdpAnswer").get(0));
            // Check if we are waiting for publication of our own vide
            if (callState == CallState.PUBLISHING) {
                callState = CallState.PUBLISHED;
                nbmWebRTCPeer.processAnswer(sd, "local");
                mHandler.postDelayed(offerWhenReady, 2000);
            // Check if we are waiting for the video publication of the other peer
            } else if (callState == CallState.WAITING_REMOTE_USER) {
                // String user_name = Integer.toString(publishVideoRequestId);
                callState = CallState.RECEIVING_REMOTE_USER;
                String connectionId = videoRequestUserMapping.get(publishVideoRequestId);
                nbmWebRTCPeer.processAnswer(sd, connectionId);
            }
        }
    }

    @Override
    public void onRoomError(RoomError error) {
        Log.e(TAG, "OnRoomError:" + error);
    }

    @Override
    public void onRoomNotification(RoomNotification notification) {
        Log.i(TAG, "OnRoomNotification (state=" + callState.toString() + "):" + notification);
        Map<String, Object> map = notification.getParams();
        if (notification.getMethod().equals(RoomListener.METHOD_ICE_CANDIDATE)) {
            String sdpMid = map.get("sdpMid").toString();
            int sdpMLineIndex = Integer.valueOf(map.get("sdpMLineIndex").toString());
            String sdp = map.get("candidate").toString();
            IceCandidate ic = new IceCandidate(sdpMid, sdpMLineIndex, sdp);
            if (callState == CallState.PUBLISHING || callState == CallState.PUBLISHED) {
                nbmWebRTCPeer.addRemoteIceCandidate(ic, "local");
            } else {
                nbmWebRTCPeer.addRemoteIceCandidate(ic, notification.getParam("endpointName").toString());
            }
        } else // Somebody in the room published their video
        if (notification.getMethod().equals(RoomListener.METHOD_PARTICIPANT_PUBLISHED)) {
            mHandler.postDelayed(offerWhenReady, 2000);
        }
    }

    @Override
    public void onRoomConnected() {
    }

    @Override
    public void onRoomDisconnected() {
    }
}

18 View Complete Implementation : VideoFragment.java
Copyright Apache License 2.0
Author : open-webrtc-toolkit
public clreplaced VideoFragment extends Fragment {

    private VideoFragmentListener listener;

    private SurfaceViewRenderer fullRenderer, smallRenderer;

    private TextView statsInView, statsOutView;

    private float dX, dY;

    private BigInteger lastBytesSent = BigInteger.valueOf(0);

    private BigInteger lastBytesReceived = BigInteger.valueOf(0);

    private Long lastFrameDecoded = Long.valueOf(0);

    private Long lastFrameEncoded = Long.valueOf(0);

    private View.OnTouchListener touchListener = new View.OnTouchListener() {

        @Override
        public boolean onTouch(View v, MotionEvent event) {
            if (v.getId() == R.id.small_renderer) {
                switch(event.getAction()) {
                    case MotionEvent.ACTION_DOWN:
                        dX = v.getX() - event.getRawX();
                        dY = v.getY() - event.getRawY();
                        break;
                    case MotionEvent.ACTION_MOVE:
                        v.animate().x(event.getRawX() + dX).y(event.getRawY() + dY).setDuration(0).start();
                        break;
                    case MotionEvent.ACTION_UP:
                        v.animate().x(event.getRawX() + dX >= event.getRawY() + dY ? event.getRawX() + dX : 0).y(event.getRawX() + dX >= event.getRawY() + dY ? 0 : event.getRawY() + dY).setDuration(10).start();
                        break;
                }
            }
            return true;
        }
    };

    public VideoFragment() {
    }

    public void setListener(VideoFragmentListener listener) {
        this.listener = listener;
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View mView = inflater.inflate(R.layout.fragment_video, container, false);
        statsInView = mView.findViewById(R.id.stats_in);
        statsInView.setVisibility(View.GONE);
        statsOutView = mView.findViewById(R.id.stats_out);
        statsOutView.setVisibility(View.GONE);
        fullRenderer = mView.findViewById(R.id.full_renderer);
        smallRenderer = mView.findViewById(R.id.small_renderer);
        smallRenderer.init(((MainActivity) getActivity()).rootEglBase.getEglBaseContext(), null);
        smallRenderer.setMirror(true);
        smallRenderer.setOnTouchListener(touchListener);
        smallRenderer.setEnableHardwareScaler(true);
        smallRenderer.setZOrderMediaOverlay(true);
        fullRenderer.init(((MainActivity) getActivity()).rootEglBase.getEglBaseContext(), null);
        fullRenderer.setScalingType(RendererCommon.ScalingType.SCALE_ASPECT_FIT);
        fullRenderer.setEnableHardwareScaler(true);
        fullRenderer.setZOrderMediaOverlay(true);
        listener.onRenderer(smallRenderer, fullRenderer);
        clearStats(true);
        clearStats(false);
        return mView;
    }

    @Override
    public void onAttach(Context context) {
        super.onAttach(context);
    }

    @Override
    public void onDetach() {
        super.onDetach();
    }

    void clearStats(boolean outbound) {
        final TextView statsView = outbound ? statsOutView : statsInView;
        if (outbound) {
            lastBytesSent = BigInteger.valueOf(0);
            lastFrameEncoded = Long.valueOf(0);
        } else {
            lastBytesReceived = BigInteger.valueOf(0);
            lastFrameDecoded = Long.valueOf(0);
        }
        final String statsReport = (outbound ? "\n--- OUTBOUND ---" : "\n--- INBOUND ---") + "\nCodec: " + "\nResolution: " + "\nBitrate: " + "\nFrameRate: ";
        getActivity().runOnUiThread(new Runnable() {

            @Override
            public void run() {
                statsView.setVisibility(View.VISIBLE);
                statsView.setText(statsReport);
            }
        });
    }

    void updateStats(RTCStatsReport report, boolean outbound) {
        final TextView statsView = outbound ? statsOutView : statsInView;
        String codecId = null;
        String codec = "";
        long bytesSR = 0;
        long width = 0, height = 0;
        long frameRate = 0;
        for (RTCStats stats : report.getStatsMap().values()) {
            if (stats.getType().equals(outbound ? "outbound-rtp" : "inbound-rtp")) {
                Map<String, Object> members = stats.getMembers();
                if (members.get("mediaType").equals("video")) {
                    codecId = (String) members.get("codecId");
                    if (outbound) {
                        BigInteger bytes = (BigInteger) members.get("bytesSent");
                        bytesSR = bytes.longValue() - lastBytesSent.longValue();
                        lastBytesSent = bytes;
                    } else {
                        BigInteger bytes = (BigInteger) members.get("bytesReceived");
                        bytesSR = bytes.longValue() - lastBytesReceived.longValue();
                        lastBytesReceived = bytes;
                    }
                    long currentFrame = (long) members.get(outbound ? "framesEncoded" : "framesDecoded");
                    long lastFrame = outbound ? lastFrameEncoded : lastFrameDecoded;
                    frameRate = (currentFrame - lastFrame) * 1000 / MainActivity.STATS_INTERVAL_MS;
                    if (outbound) {
                        lastFrameEncoded = currentFrame;
                    } else {
                        lastFrameDecoded = currentFrame;
                    }
                }
            }
            if (stats.getType().equals("track")) {
                Map<String, Object> members = stats.getMembers();
                if (members.get("kind").equals("video")) {
                    width = members.get("frameWidth") == null ? 0 : (long) members.get("frameWidth");
                    height = members.get("frameHeight") == null ? 0 : (long) members.get("frameHeight");
                }
            }
        }
        if (codecId != null) {
            codec = (String) report.getStatsMap().get(codecId).getMembers().get("mimeType");
        }
        final String statsReport = (outbound ? "\n--- OUTBOUND ---" : "\n--- INBOUND ---") + "\nCodec: " + codec + "\nResolution: " + width + "x" + height + "\nBitrate: " + bytesSR * 8 / MainActivity.STATS_INTERVAL_MS + "kbps" + "\nFrameRate: " + frameRate;
        getActivity().runOnUiThread(() -> {
            statsView.setVisibility(View.VISIBLE);
            statsView.setText(statsReport);
        });
    }

    public interface VideoFragmentListener {

        void onRenderer(SurfaceViewRenderer localRenderer, SurfaceViewRenderer remoteRenderer);
    }
}

18 View Complete Implementation : MainActivity.java
Copyright Apache License 2.0
Author : open-webrtc-toolkit
public clreplaced MainActivity extends AppCompatActivity implements LoginFragment.LoginFragmentListener, CallFragment.CallFragmentListener, ChatFragment.ChatFragmentListener, P2PClient.P2PClientObserver {

    private static final String TAG = "OWT_P2P";

    private static final int OWT_REQUEST_CODE = 100;

    private static final int STATS_INTERVAL_MS = 10000;

    EglBase rootEglBase;

    private LoginFragment loginFragment;

    private CallFragment callFragment;

    private SettingsFragment settingsFragment;

    private ChatFragment chatFragment;

    private SurfaceViewRenderer localRenderer, remoteRenderer;

    private P2PClient p2PClient;

    private Publication publication;

    private String peerId;

    private boolean inCalling = false;

    private LocalStream localStream;

    private RemoteStream remoteStream;

    private boolean remoteStreamEnded = false;

    private OwtVideoCapturer capturer;

    private Timer statsTimer;

    private ExecutorService executor = Executors.newSingleThreadExecutor();

    private boolean enableLocalStream = false;

    private BottomNavigationView.OnNavigationItemSelectedListener mOnNavigationItemSelectedListener = new BottomNavigationView.OnNavigationItemSelectedListener() {

        @Override
        public boolean onNavigationItemSelected(@NonNull MenuItem item) {
            switch(item.gereplacedemId()) {
                case R.id.navigation_home:
                    switchFragment(inCalling ? callFragment : loginFragment);
                    return true;
                case R.id.navigation_settings:
                    if (settingsFragment == null) {
                        settingsFragment = new SettingsFragment();
                    }
                    switchFragment(settingsFragment);
                    return true;
                case R.id.navigation_chat:
                    if (chatFragment == null) {
                        chatFragment = new ChatFragment();
                    }
                    switchFragment(chatFragment);
                    return true;
            }
            return false;
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        requestWindowFeature(Window.FEATURE_NO_replacedLE);
        getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
        setContentView(R.layout.activity_main);
        BottomNavigationView navigation = findViewById(R.id.navigation);
        navigation.setOnNavigationItemSelectedListener(mOnNavigationItemSelectedListener);
        loginFragment = new LoginFragment();
        switchFragment(loginFragment);
        AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
        audioManager.setMode(AudioManager.MODE_IN_COMMUNICATION);
        initP2PClient();
    }

    private void initP2PClient() {
        rootEglBase = EglBase.create();
        ContextInitialization.create().setApplicationContext(this).setVideoHardwareAccelerationOptions(rootEglBase.getEglBaseContext(), rootEglBase.getEglBaseContext()).initialize();
        VideoEncodingParameters h264 = new VideoEncodingParameters(H264);
        VideoEncodingParameters h265 = new VideoEncodingParameters(H265);
        VideoEncodingParameters vp8 = new VideoEncodingParameters(VP8);
        P2PClientConfiguration configuration = P2PClientConfiguration.builder().addVideoParameters(h264).addVideoParameters(vp8).addVideoParameters(h265).build();
        p2PClient = new P2PClient(configuration, new SocketSignalingChannel());
        p2PClient.addObserver(this);
    }

    private void switchFragment(Fragment fragment) {
        getSupportFragmentManager().beginTransaction().replace(R.id.fragment_container, fragment).commitAllowingStateLoss();
    }

    private void requestPermission() {
        String[] permissions = new String[] { Manifest.permission.CAMERA, Manifest.permission.RECORD_AUDIO };
        for (String permission : permissions) {
            if (ContextCompat.checkSelfPermission(MainActivity.this, permission) != PERMISSION_GRANTED) {
                ActivityCompat.requestPermissions(MainActivity.this, permissions, OWT_REQUEST_CODE);
                return;
            }
        }
        onConnectSucceed();
    }

    public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
        if (requestCode == OWT_REQUEST_CODE && grantResults.length == 2 && grantResults[0] == PERMISSION_GRANTED && grantResults[1] == PERMISSION_GRANTED) {
            onConnectSucceed();
        }
    }

    private void onConnectSucceed() {
        runOnUiThread(new Runnable() {

            @Override
            public void run() {
                loginFragment.onConnected();
            }
        });
    }

    @Override
    public void onServerDisconnected() {
        runOnUiThread(new Runnable() {

            @Override
            public void run() {
                switchFragment(loginFragment);
                callFragment = null;
                settingsFragment = null;
            }
        });
    }

    @Override
    public void onStreamAdded(final RemoteStream remoteStream) {
        this.remoteStream = remoteStream;
        remoteStream.addObserver(new RemoteStream.StreamObserver() {

            @Override
            public void onEnded() {
                remoteStreamEnded = true;
            }

            @Override
            public void onUpdated() {
            // ignored in p2p.
            }
        });
        executor.execute(() -> {
            if (remoteRenderer != null) {
                remoteStream.attach(remoteRenderer);
            }
        });
    }

    @Override
    public void onDataReceived(String peerId, String message) {
        if (chatFragment == null) {
            chatFragment = new ChatFragment();
        }
        chatFragment.onMessage(peerId, message);
    }

    @Override
    public void onConnectRequest(final String server, final String myId) {
        executor.execute(() -> {
            JSONObject loginObj = new JSONObject();
            try {
                loginObj.put("host", server);
                loginObj.put("token", myId);
            } catch (JSONException e) {
                e.printStackTrace();
            }
            p2PClient.addAllowedRemotePeer(myId);
            p2PClient.connect(loginObj.toString(), new ActionCallback<String>() {

                @Override
                public void onSuccess(String result) {
                    requestPermission();
                }

                @Override
                public void onFailure(OwtError error) {
                    loginFragment.onConnectFailed(error.errorMessage);
                }
            });
        });
    }

    @Override
    public void onDisconnectRequest() {
        executor.execute(() -> p2PClient.disconnect());
    }

    @Override
    public void onCallRequest(final String peerId) {
        inCalling = true;
        this.peerId = peerId;
        executor.execute(() -> {
            p2PClient.addAllowedRemotePeer(peerId);
            if (callFragment == null) {
                callFragment = new CallFragment();
            }
            switchFragment(callFragment);
        });
    }

    @Override
    public void onReady(final SurfaceViewRenderer localRenderer, final SurfaceViewRenderer remoteRenderer) {
        this.localRenderer = localRenderer;
        this.remoteRenderer = remoteRenderer;
        localRenderer.init(rootEglBase.getEglBaseContext(), null);
        remoteRenderer.init(rootEglBase.getEglBaseContext(), null);
        executor.execute(() -> {
            if (capturer == null) {
                boolean vga = settingsFragment == null || settingsFragment.resolutionVGA;
                boolean isCameraFront = settingsFragment == null || settingsFragment.cameraFront;
                capturer = OwtVideoCapturer.create(vga ? 640 : 1280, vga ? 480 : 720, 30, true, isCameraFront);
                localStream = new LocalStream(capturer, new MediaConstraints.AudioTrackConstraints());
            }
            localStream.attach(localRenderer);
            if (remoteStream != null && !remoteStreamEnded) {
                remoteStream.attach(remoteRenderer);
            }
        });
    }

    @Override
    public void onPublishRequest() {
        if (!enableLocalStream) {
            localStream.enableAudio();
            localStream.enableVideo();
            enableLocalStream = true;
        }
        executor.execute(() -> p2PClient.publish(peerId, localStream, new ActionCallback<Publication>() {

            @Override
            public void onSuccess(Publication result) {
                inCalling = true;
                publication = result;
                callFragment.onPublished(true);
                if (statsTimer != null) {
                    statsTimer.cancel();
                    statsTimer = null;
                }
                statsTimer = new Timer();
                statsTimer.schedule(new TimerTask() {

                    @Override
                    public void run() {
                        getStats();
                    }
                }, 0, STATS_INTERVAL_MS);
            }

            @Override
            public void onFailure(OwtError error) {
                callFragment.onPublished(false);
                if (error.errorMessage.equals("Duplicated stream.")) {
                    // this mean you have published, so change the button to unpublish
                    callFragment.onPublished(true);
                }
            }
        }));
    }

    private void getStats() {
        if (publication != null) {
            publication.getStats(new ActionCallback<RTCStatsReport>() {

                @Override
                public void onSuccess(RTCStatsReport result) {
                }

                @Override
                public void onFailure(OwtError error) {
                }
            });
        }
    }

    @Override
    public void onUnpublishRequest(boolean back2main) {
        if (enableLocalStream) {
            localStream.disableAudio();
            localStream.disableVideo();
            enableLocalStream = false;
        }
        if (back2main) {
            inCalling = false;
            switchFragment(loginFragment);
            if (publication != null) {
                publication.stop();
                publication = null;
            }
            if (capturer != null) {
                capturer.stopCapture();
                capturer.dispose();
                capturer = null;
            }
            if (localStream != null) {
                localStream.dispose();
                localStream = null;
            }
            executor.execute(() -> p2PClient.stop(peerId));
        }
    }

    @Override
    public void onSendMessage(final String message) {
        executor.execute(() -> p2PClient.send(peerId, message, new ActionCallback<Void>() {

            @Override
            public void onSuccess(Void result) {
                chatFragment.onMessage("me", message);
            }

            @Override
            public void onFailure(OwtError error) {
            }
        }));
    }
}

18 View Complete Implementation : MainActivity.java
Copyright Apache License 2.0
Author : rome753
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    // create PeerConnectionFactory
    PeerConnectionFactory.InitializationOptions initializationOptions = PeerConnectionFactory.InitializationOptions.builder(this).createInitializationOptions();
    PeerConnectionFactory.initialize(initializationOptions);
    PeerConnectionFactory peerConnectionFactory = PeerConnectionFactory.builder().createPeerConnectionFactory();
    // create AudioSource
    AudioSource audioSource = peerConnectionFactory.createAudioSource(new MediaConstraints());
    AudioTrack audioTrack = peerConnectionFactory.createAudioTrack("101", audioSource);
    EglBase.Context eglBaseContext = EglBase.create().getEglBaseContext();
    SurfaceTextureHelper surfaceTextureHelper = SurfaceTextureHelper.create("CaptureThread", eglBaseContext);
    // create VideoCapturer
    VideoCapturer videoCapturer = createCameraCapturer();
    VideoSource videoSource = peerConnectionFactory.createVideoSource(videoCapturer.isScreencast());
    videoCapturer.initialize(surfaceTextureHelper, getApplicationContext(), videoSource.getCapturerObserver());
    videoCapturer.startCapture(480, 640, 30);
    SurfaceViewRenderer localView = findViewById(R.id.localView);
    localView.setMirror(true);
    localView.init(eglBaseContext, null);
    // create VideoTrack
    VideoTrack videoTrack = peerConnectionFactory.createVideoTrack("100", videoSource);
    // display in localView
    videoTrack.addSink(localView);
}

18 View Complete Implementation : MainActivity.java
Copyright Apache License 2.0
Author : rome753
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    peerConnectionMap = new HashMap<>();
    iceServers = new ArrayList<>();
    iceServers.add(PeerConnection.IceServer.builder("stun:stun.l.google.com:19302").createIceServer());
    eglBaseContext = EglBase.create().getEglBaseContext();
    // create PeerConnectionFactory
    PeerConnectionFactory.initialize(PeerConnectionFactory.InitializationOptions.builder(this).createInitializationOptions());
    PeerConnectionFactory.Options options = new PeerConnectionFactory.Options();
    DefaultVideoEncoderFactory defaultVideoEncoderFactory = new DefaultVideoEncoderFactory(eglBaseContext, true, true);
    DefaultVideoDecoderFactory defaultVideoDecoderFactory = new DefaultVideoDecoderFactory(eglBaseContext);
    peerConnectionFactory = PeerConnectionFactory.builder().setOptions(options).setVideoEncoderFactory(defaultVideoEncoderFactory).setVideoDecoderFactory(defaultVideoDecoderFactory).createPeerConnectionFactory();
    SurfaceTextureHelper surfaceTextureHelper = SurfaceTextureHelper.create("CaptureThread", eglBaseContext);
    // create VideoCapturer
    VideoCapturer videoCapturer = createCameraCapturer(true);
    VideoSource videoSource = peerConnectionFactory.createVideoSource(videoCapturer.isScreencast());
    videoCapturer.initialize(surfaceTextureHelper, getApplicationContext(), videoSource.getCapturerObserver());
    videoCapturer.startCapture(480, 640, 30);
    localView = findViewById(R.id.localView);
    localView.setMirror(true);
    localView.init(eglBaseContext, null);
    // create VideoTrack
    VideoTrack videoTrack = peerConnectionFactory.createVideoTrack("100", videoSource);
    // // display in localView
    videoTrack.addSink(localView);
    remoteViews = new SurfaceViewRenderer[] { findViewById(R.id.remoteView), findViewById(R.id.remoteView2), findViewById(R.id.remoteView3) };
    for (SurfaceViewRenderer remoteView : remoteViews) {
        remoteView.setMirror(false);
        remoteView.init(eglBaseContext, null);
    }
    mediaStream = peerConnectionFactory.createLocalMediaStream("mediaStream");
    mediaStream.addTrack(videoTrack);
    SignalingClient.get().init(this);
}

18 View Complete Implementation : WebRtcViewModel.java
Copyright GNU General Public License v3.0
Author : signalapp
public clreplaced WebRtcViewModel {

    public enum State {

        // Normal states
        CALL_INCOMING,
        CALL_OUTGOING,
        CALL_CONNECTED,
        CALL_RINGING,
        CALL_BUSY,
        CALL_DISCONNECTED,
        // Error states
        NETWORK_FAILURE,
        RECIPIENT_UNAVAILABLE,
        NO_SUCH_USER,
        UNTRUSTED_IDENreplacedY
    }

    @NonNull
    private final State state;

    @NonNull
    private final Recipient recipient;

    @Nullable
    private final IdenreplacedyKey idenreplacedyKey;

    private final boolean remoteVideoEnabled;

    private final boolean isBluetoothAvailable;

    private final boolean isMicrophoneEnabled;

    private final CameraState localCameraState;

    private final SurfaceViewRenderer localRenderer;

    private final SurfaceViewRenderer remoteRenderer;

    public WebRtcViewModel(@NonNull State state, @NonNull Recipient recipient, @NonNull CameraState localCameraState, @NonNull SurfaceViewRenderer localRenderer, @NonNull SurfaceViewRenderer remoteRenderer, boolean remoteVideoEnabled, boolean isBluetoothAvailable, boolean isMicrophoneEnabled) {
        this(state, recipient, null, localCameraState, localRenderer, remoteRenderer, remoteVideoEnabled, isBluetoothAvailable, isMicrophoneEnabled);
    }

    public WebRtcViewModel(@NonNull State state, @NonNull Recipient recipient, @Nullable IdenreplacedyKey idenreplacedyKey, @NonNull CameraState localCameraState, @NonNull SurfaceViewRenderer localRenderer, @NonNull SurfaceViewRenderer remoteRenderer, boolean remoteVideoEnabled, boolean isBluetoothAvailable, boolean isMicrophoneEnabled) {
        this.state = state;
        this.recipient = recipient;
        this.localCameraState = localCameraState;
        this.localRenderer = localRenderer;
        this.remoteRenderer = remoteRenderer;
        this.idenreplacedyKey = idenreplacedyKey;
        this.remoteVideoEnabled = remoteVideoEnabled;
        this.isBluetoothAvailable = isBluetoothAvailable;
        this.isMicrophoneEnabled = isMicrophoneEnabled;
    }

    @NonNull
    public State getState() {
        return state;
    }

    @NonNull
    public Recipient getRecipient() {
        return recipient;
    }

    @NonNull
    public CameraState getLocalCameraState() {
        return localCameraState;
    }

    @Nullable
    public IdenreplacedyKey getIdenreplacedyKey() {
        return idenreplacedyKey;
    }

    public boolean isRemoteVideoEnabled() {
        return remoteVideoEnabled;
    }

    public boolean isBluetoothAvailable() {
        return isBluetoothAvailable;
    }

    public boolean isMicrophoneEnabled() {
        return isMicrophoneEnabled;
    }

    public SurfaceViewRenderer getLocalRenderer() {
        return localRenderer;
    }

    public SurfaceViewRenderer getRemoteRenderer() {
        return remoteRenderer;
    }

    @NonNull
    public String toString() {
        return "[State: " + state + ", recipient: " + recipient.getId().serialize() + ", idenreplacedy: " + idenreplacedyKey + ", remoteVideo: " + remoteVideoEnabled + ", localVideo: " + localCameraState.isEnabled() + "]";
    }
}

17 View Complete Implementation : CallActivity.java
Copyright Apache License 2.0
Author : androidthings
/**
 * Activity for peer connection call setup, call waiting
 * and call view.
 */
public clreplaced CallActivity extends Activity implements AppRTCClient.SignalingEvents, PeerConnectionClient.PeerConnectionEvents {

    private static final String TAG = "CallActivity";

    private static final String APPRTC_URL = "https://appr.tc";

    private static final String UPPER_ALPHA_DIGITS = "ACEFGHJKLMNPQRUVWXY123456789";

    // Peer connection statistics callback period in ms.
    private static final int STAT_CALLBACK_PERIOD = 1000;

    private final ProxyRenderer remoteProxyRenderer = new ProxyRenderer();

    private final ProxyVideoSink localProxyVideoSink = new ProxyVideoSink();

    private final List<VideoRenderer.Callbacks> remoteRenderers = new ArrayList<>();

    private PeerConnectionClient peerConnectionClient = null;

    private AppRTCClient appRtcClient;

    private AppRTCClient.SignalingParameters signalingParameters;

    private SurfaceViewRenderer pipRenderer;

    private SurfaceViewRenderer fullscreenRenderer;

    private Toast logToast;

    private boolean activityRunning;

    private AppRTCClient.RoomConnectionParameters roomConnectionParameters;

    private PeerConnectionClient.PeerConnectionParameters peerConnectionParameters;

    private boolean iceConnected;

    private boolean isError;

    private long callStartedTimeMs = 0;

    private boolean micEnabled = true;

    private boolean isSwappedFeeds;

    // Control buttons for limited UI
    private ImageButton disconnectButton;

    private ImageButton cameraSwitchButton;

    private ImageButton toggleMuteButton;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_call);
        iceConnected = false;
        signalingParameters = null;
        // Create UI controls.
        pipRenderer = findViewById(R.id.pip_video_view);
        fullscreenRenderer = findViewById(R.id.fullscreen_video_view);
        disconnectButton = findViewById(R.id.button_call_disconnect);
        cameraSwitchButton = findViewById(R.id.button_call_switch_camera);
        toggleMuteButton = findViewById(R.id.button_call_toggle_mic);
        // Add buttons click events.
        disconnectButton.setOnClickListener(new OnClickListener() {

            public void onClick(View v) {
                onCallHangUp();
            }
        });
        cameraSwitchButton.setOnClickListener(new View.OnClickListener() {

            public void onClick(View view) {
                onCameraSwitch();
            }
        });
        toggleMuteButton.setOnClickListener(new View.OnClickListener() {

            public void onClick(View view) {
                boolean enabled = onToggleMic();
                toggleMuteButton.setAlpha(enabled ? 1.0f : 0.3f);
            }
        });
        // Swap feeds on pip view click.
        pipRenderer.setOnClickListener(new View.OnClickListener() {

            @Override
            public void onClick(View view) {
                setSwappedFeeds(!isSwappedFeeds);
            }
        });
        remoteRenderers.add(remoteProxyRenderer);
        // Create peer connection client.
        peerConnectionClient = new PeerConnectionClient();
        // Create video renderers.
        pipRenderer.init(peerConnectionClient.getRenderContext(), null);
        pipRenderer.setScalingType(ScalingType.SCALE_ASPECT_FIT);
        fullscreenRenderer.init(peerConnectionClient.getRenderContext(), null);
        fullscreenRenderer.setScalingType(ScalingType.SCALE_ASPECT_FILL);
        pipRenderer.setZOrderMediaOverlay(true);
        pipRenderer.setEnableHardwareScaler(true);
        fullscreenRenderer.setEnableHardwareScaler(true);
        // Start with local feed in fullscreen and swap it to the pip when the call is connected.
        setSwappedFeeds(true);
        // Generate a random room ID with 7 uppercase letters and digits
        String randomRoomID = randomString(7, UPPER_ALPHA_DIGITS);
        // Show the random room ID so that another client can join from https://appr.tc
        TextView roomIdTextView = findViewById(R.id.roomID);
        roomIdTextView.setText(getString(R.string.room_id_caption) + randomRoomID);
        Log.d(TAG, getString(R.string.room_id_caption) + randomRoomID);
        // Connect video call to the random room
        connectVideoCall(randomRoomID);
    }

    // Create a random string
    private String randomString(int length, String characterSet) {
        // consider using StringBuffer if needed
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < length; i++) {
            int randomInt = new SecureRandom().nextInt(characterSet.length());
            sb.append(characterSet.substring(randomInt, randomInt + 1));
        }
        return sb.toString();
    }

    // Join video call with randomly generated roomId
    private void connectVideoCall(String roomId) {
        Uri roomUri = Uri.parse(APPRTC_URL);
        int videoWidth = 0;
        int videoHeight = 0;
        peerConnectionParameters = new PeerConnectionClient.PeerConnectionParameters(true, false, false, videoWidth, videoHeight, 0, Integer.parseInt(getString(R.string.pref_maxvideobitratevalue_default)), getString(R.string.pref_videocodec_default), true, false, Integer.parseInt(getString(R.string.pref_startaudiobitratevalue_default)), getString(R.string.pref_audiocodec_default), false, false, false, false, false, false, false, false, null);
        // Create connection client. Use the standard WebSocketRTCClient.
        // DirectRTCClient could be used for point-to-point connection
        appRtcClient = new WebSocketRTCClient(this);
        // Create connection parameters.
        roomConnectionParameters = new AppRTCClient.RoomConnectionParameters(roomUri.toString(), roomId, false, null);
        peerConnectionClient.createPeerConnectionFactory(getApplicationContext(), peerConnectionParameters, CallActivity.this);
        startCall();
    }

    public void onCallHangUp() {
        disconnect();
    }

    public void onCameraSwitch() {
        if (peerConnectionClient != null) {
            peerConnectionClient.switchCamera();
        }
    }

    public boolean onToggleMic() {
        if (peerConnectionClient != null) {
            micEnabled = !micEnabled;
            peerConnectionClient.setAudioEnabled(micEnabled);
        }
        return micEnabled;
    }

    private void startCall() {
        if (appRtcClient == null) {
            Log.e(TAG, "AppRTC client is not allocated for a call.");
            return;
        }
        callStartedTimeMs = System.currentTimeMillis();
        // Start room connection.
        logAndToast(getString(R.string.connecting_to, roomConnectionParameters.roomUrl));
        appRtcClient.connectToRoom(roomConnectionParameters);
    }

    @UiThread
    private void callConnected() {
        final long delta = System.currentTimeMillis() - callStartedTimeMs;
        Log.i(TAG, "Call connected: delay=" + delta + "ms");
        if (peerConnectionClient == null || isError) {
            Log.w(TAG, "Call is connected in closed or error state");
            return;
        }
        // Enable statistics callback.
        peerConnectionClient.enableStatsEvents(true, STAT_CALLBACK_PERIOD);
        setSwappedFeeds(false);
    }

    // Disconnect from remote resources, dispose of local resources, and exit.
    private void disconnect() {
        activityRunning = false;
        remoteProxyRenderer.setTarget(null);
        localProxyVideoSink.setTarget(null);
        if (appRtcClient != null) {
            appRtcClient.disconnectFromRoom();
            appRtcClient = null;
        }
        if (pipRenderer != null) {
            pipRenderer.release();
            pipRenderer = null;
        }
        if (fullscreenRenderer != null) {
            fullscreenRenderer.release();
            fullscreenRenderer = null;
        }
        if (peerConnectionClient != null) {
            peerConnectionClient.close();
            peerConnectionClient = null;
        }
        if (iceConnected && !isError) {
            setResult(RESULT_OK);
        } else {
            setResult(RESULT_CANCELED);
        }
        finish();
    }

    private void disconnectWithErrorMessage(final String errorMessage) {
        if (!activityRunning) {
            Log.e(TAG, "Critical error: " + errorMessage);
            disconnect();
        } else {
            new AlertDialog.Builder(this).setreplacedle(getText(R.string.channel_error_replacedle)).setMessage(errorMessage).setCancelable(false).setNeutralButton(R.string.ok, new DialogInterface.OnClickListener() {

                @Override
                public void onClick(DialogInterface dialog, int id) {
                    dialog.cancel();
                    disconnect();
                }
            }).create().show();
        }
    }

    // Log |msg| and Toast about it.
    private void logAndToast(String msg) {
        Log.d(TAG, msg);
        if (logToast != null) {
            logToast.cancel();
        }
        logToast = Toast.makeText(this, msg, Toast.LENGTH_SHORT);
        logToast.show();
    }

    private void reportError(final String description) {
        runOnUiThread(new Runnable() {

            @Override
            public void run() {
                if (!isError) {
                    isError = true;
                    disconnectWithErrorMessage(description);
                }
            }
        });
    }

    // Create VideoCapturer
    private VideoCapturer createVideoCapturer() {
        final VideoCapturer videoCapturer;
        Logging.d(TAG, "Creating capturer using camera2 API.");
        videoCapturer = createCameraCapturer(new Camera2Enumerator(this));
        if (videoCapturer == null) {
            reportError("Failed to open camera");
            return null;
        }
        return videoCapturer;
    }

    // Create VideoCapturer from camera
    private VideoCapturer createCameraCapturer(CameraEnumerator enumerator) {
        final String[] deviceNames = enumerator.getDeviceNames();
        // First, try to find front facing camera
        Logging.d(TAG, "Looking for front facing cameras.");
        for (String deviceName : deviceNames) {
            if (enumerator.isFrontFacing(deviceName)) {
                Logging.d(TAG, "Creating front facing camera capturer.");
                VideoCapturer videoCapturer = enumerator.createCapturer(deviceName, null);
                if (videoCapturer != null) {
                    return videoCapturer;
                }
            }
        }
        // Front facing camera not found, try something else
        Logging.d(TAG, "Looking for other cameras.");
        for (String deviceName : deviceNames) {
            if (!enumerator.isFrontFacing(deviceName)) {
                Logging.d(TAG, "Creating other camera capturer.");
                VideoCapturer videoCapturer = enumerator.createCapturer(deviceName, null);
                if (videoCapturer != null) {
                    return videoCapturer;
                }
            }
        }
        return null;
    }

    private void setSwappedFeeds(boolean isSwappedFeeds) {
        Logging.d(TAG, "setSwappedFeeds: " + isSwappedFeeds);
        this.isSwappedFeeds = isSwappedFeeds;
        localProxyVideoSink.setTarget(isSwappedFeeds ? fullscreenRenderer : pipRenderer);
        remoteProxyRenderer.setTarget(isSwappedFeeds ? pipRenderer : fullscreenRenderer);
        fullscreenRenderer.setMirror(isSwappedFeeds);
        pipRenderer.setMirror(!isSwappedFeeds);
    }

    // -----Implementation of AppRTCClient.AppRTCSignalingEvents ---------------
    // All callbacks are invoked from websocket signaling looper thread and
    // are routed to UI thread.
    private void onConnectedToRoomInternal(final AppRTCClient.SignalingParameters params) {
        final long delta = System.currentTimeMillis() - callStartedTimeMs;
        signalingParameters = params;
        logAndToast("Creating peer connection, delay=" + delta + "ms");
        VideoCapturer videoCapturer = null;
        if (peerConnectionParameters.videoCallEnabled) {
            videoCapturer = createVideoCapturer();
        }
        peerConnectionClient.createPeerConnection(localProxyVideoSink, remoteRenderers, videoCapturer, signalingParameters);
        if (signalingParameters.initiator) {
            logAndToast("Creating OFFER...");
            // Create offer. Offer SDP will be sent to answering client in
            // PeerConnectionEvents.onLocalDescription event.
            peerConnectionClient.createOffer();
        } else {
            if (params.offerSdp != null) {
                peerConnectionClient.setRemoteDescription(params.offerSdp);
                logAndToast("Creating ANSWER...");
                // Create answer. Answer SDP will be sent to offering client in
                // PeerConnectionEvents.onLocalDescription event.
                peerConnectionClient.createAnswer();
            }
            if (params.iceCandidates != null) {
                // Add remote ICE candidates from room.
                for (IceCandidate iceCandidate : params.iceCandidates) {
                    peerConnectionClient.addRemoteIceCandidate(iceCandidate);
                }
            }
        }
    }

    @Override
    public void onConnectedToRoom(final AppRTCClient.SignalingParameters params) {
        runOnUiThread(new Runnable() {

            @Override
            public void run() {
                onConnectedToRoomInternal(params);
            }
        });
    }

    @Override
    public void onRemoteDescription(final SessionDescription sdp) {
        final long delta = System.currentTimeMillis() - callStartedTimeMs;
        runOnUiThread(new Runnable() {

            @Override
            public void run() {
                if (peerConnectionClient == null) {
                    Log.e(TAG, "Received remote SDP for non-initilized peer connection.");
                    return;
                }
                logAndToast("Received remote " + sdp.type + ", delay=" + delta + "ms");
                peerConnectionClient.setRemoteDescription(sdp);
                if (!signalingParameters.initiator) {
                    logAndToast("Creating ANSWER...");
                    // Create answer. Answer SDP will be sent to offering client in
                    // PeerConnectionEvents.onLocalDescription event.
                    peerConnectionClient.createAnswer();
                }
            }
        });
    }

    @Override
    public void onRemoteIceCandidate(final IceCandidate candidate) {
        runOnUiThread(new Runnable() {

            @Override
            public void run() {
                if (peerConnectionClient == null) {
                    Log.e(TAG, "Received ICE candidate for a non-initialized peer connection.");
                    return;
                }
                peerConnectionClient.addRemoteIceCandidate(candidate);
            }
        });
    }

    @Override
    public void onRemoteIceCandidatesRemoved(final IceCandidate[] candidates) {
        runOnUiThread(new Runnable() {

            @Override
            public void run() {
                if (peerConnectionClient == null) {
                    Log.e(TAG, "Received ICE candidate removals for a non-initialized peer connection.");
                    return;
                }
                peerConnectionClient.removeRemoteIceCandidates(candidates);
            }
        });
    }

    @Override
    public void onChannelClose() {
        runOnUiThread(new Runnable() {

            @Override
            public void run() {
                logAndToast("Remote end hung up; dropping PeerConnection");
                disconnect();
            }
        });
    }

    @Override
    public void onChannelError(final String description) {
        reportError(description);
    }

    // -----Implementation of PeerConnectionClient.PeerConnectionEvents.---------
    // Send local peer connection SDP and ICE candidates to remote party.
    // All callbacks are invoked from peer connection client looper thread and
    // are routed to UI thread.
    @Override
    public void onLocalDescription(final SessionDescription sdp) {
        final long delta = System.currentTimeMillis() - callStartedTimeMs;
        runOnUiThread(new Runnable() {

            @Override
            public void run() {
                if (appRtcClient != null) {
                    logAndToast("Sending " + sdp.type + ", delay=" + delta + "ms");
                    if (signalingParameters.initiator) {
                        appRtcClient.sendOfferSdp(sdp);
                    } else {
                        appRtcClient.sendAnswerSdp(sdp);
                    }
                }
                if (peerConnectionParameters.videoMaxBitrate > 0) {
                    Log.d(TAG, "Set video maximum bitrate: " + peerConnectionParameters.videoMaxBitrate);
                    peerConnectionClient.setVideoMaxBitrate(peerConnectionParameters.videoMaxBitrate);
                }
            }
        });
    }

    @Override
    public void onIceCandidate(final IceCandidate candidate) {
        runOnUiThread(new Runnable() {

            @Override
            public void run() {
                if (appRtcClient != null) {
                    appRtcClient.sendLocalIceCandidate(candidate);
                }
            }
        });
    }

    @Override
    public void onIceCandidatesRemoved(final IceCandidate[] candidates) {
        runOnUiThread(new Runnable() {

            @Override
            public void run() {
                if (appRtcClient != null) {
                    appRtcClient.sendLocalIceCandidateRemovals(candidates);
                }
            }
        });
    }

    @Override
    public void onIceConnected() {
        final long delta = System.currentTimeMillis() - callStartedTimeMs;
        runOnUiThread(new Runnable() {

            @Override
            public void run() {
                logAndToast("ICE connected, delay=" + delta + "ms");
                iceConnected = true;
                callConnected();
            }
        });
    }

    @Override
    public void onIceDisconnected() {
        runOnUiThread(new Runnable() {

            @Override
            public void run() {
                logAndToast("ICE disconnected");
                iceConnected = false;
                disconnect();
            }
        });
    }

    @Override
    public void onPeerConnectionClosed() {
    }

    @Override
    public void onPeerConnectionStatsReady(final StatsReport[] reports) {
    }

    @Override
    public void onPeerConnectionError(final String description) {
        reportError(description);
    }

    // Activity interfaces
    @Override
    public void onStop() {
        super.onStop();
        activityRunning = false;
        if (peerConnectionClient != null) {
            peerConnectionClient.stopVideoSource();
        }
    }

    @Override
    public void onStart() {
        super.onStart();
        activityRunning = true;
        // Video is not paused for screencapture. See onPause.
        if (peerConnectionClient != null) {
            peerConnectionClient.startVideoSource();
        }
    }

    @Override
    protected void onDestroy() {
        Thread.setDefaultUncaughtExceptionHandler(null);
        disconnect();
        if (logToast != null) {
            logToast.cancel();
        }
        activityRunning = false;
        super.onDestroy();
    }

    private static clreplaced ProxyRenderer implements VideoRenderer.Callbacks {

        private VideoRenderer.Callbacks target;

        @Override
        synchronized public void renderFrame(VideoRenderer.I420Frame frame) {
            if (target == null) {
                Logging.d(TAG, "Dropping frame in proxy because target is null.");
                VideoRenderer.renderFrameDone(frame);
                return;
            }
            target.renderFrame(frame);
        }

        synchronized public void setTarget(VideoRenderer.Callbacks target) {
            this.target = target;
        }
    }

    private static clreplaced ProxyVideoSink implements VideoSink {

        private VideoSink target;

        @Override
        synchronized public void onFrame(VideoFrame frame) {
            if (target == null) {
                Logging.d(TAG, "Dropping frame in proxy because target is null.");
                return;
            }
            target.onFrame(frame);
        }

        synchronized public void setTarget(VideoSink target) {
            this.target = target;
        }
    }
}

17 View Complete Implementation : FragmentVideo.java
Copyright MIT License
Author : ddssingsong
/**
 * 视频通话控制界面
 */
public clreplaced FragmentVideo extends Fragment implements CallSession.CallSessionCallback {

    private FrameLayout fullscreenRenderer;

    private FrameLayout pipRenderer;

    private LinearLayout inviteeInfoContainer;

    private ImageView portraitImageView;

    private TextView nameTextView;

    private TextView descTextView;

    private ImageView minimizeImageView;

    private ImageView outgoingAudioOnlyImageView;

    private ImageView outgoingHangupImageView;

    private LinearLayout audioLayout;

    private ImageView incomingAudioOnlyImageView;

    private LinearLayout hangupLinearLayout;

    private ImageView incomingHangupImageView;

    private LinearLayout acceptLinearLayout;

    private ImageView acceptImageView;

    private TextView durationTextView;

    private ImageView connectedAudioOnlyImageView;

    private ImageView connectedHangupImageView;

    private ImageView switchCameraImageView;

    private View incomingActionContainer;

    private View outgoingActionContainer;

    private View connectedActionContainer;

    private CallSingleActivity activity;

    private AVEngineKit gEngineKit;

    private boolean isOutgoing;

    private SurfaceViewRenderer localSurfaceView;

    private SurfaceViewRenderer remoteSurfaceView;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_video, container, false);
        initView(view);
        init();
        return view;
    }

    private void initView(View view) {
        fullscreenRenderer = view.findViewById(R.id.fullscreen_video_view);
        pipRenderer = view.findViewById(R.id.pip_video_view);
        inviteeInfoContainer = view.findViewById(R.id.inviteeInfoContainer);
        portraitImageView = view.findViewById(R.id.portraitImageView);
        nameTextView = view.findViewById(R.id.nameTextView);
        descTextView = view.findViewById(R.id.descTextView);
        minimizeImageView = view.findViewById(R.id.minimizeImageView);
        outgoingAudioOnlyImageView = view.findViewById(R.id.outgoingAudioOnlyImageView);
        outgoingHangupImageView = view.findViewById(R.id.outgoingHangupImageView);
        audioLayout = view.findViewById(R.id.audioLayout);
        incomingAudioOnlyImageView = view.findViewById(R.id.incomingAudioOnlyImageView);
        hangupLinearLayout = view.findViewById(R.id.hangupLinearLayout);
        incomingHangupImageView = view.findViewById(R.id.incomingHangupImageView);
        acceptLinearLayout = view.findViewById(R.id.acceptLinearLayout);
        acceptImageView = view.findViewById(R.id.acceptImageView);
        durationTextView = view.findViewById(R.id.durationTextView);
        connectedAudioOnlyImageView = view.findViewById(R.id.connectedAudioOnlyImageView);
        connectedHangupImageView = view.findViewById(R.id.connectedHangupImageView);
        switchCameraImageView = view.findViewById(R.id.switchCameraImageView);
        incomingActionContainer = view.findViewById(R.id.incomingActionContainer);
        outgoingActionContainer = view.findViewById(R.id.outgoingActionContainer);
        connectedActionContainer = view.findViewById(R.id.connectedActionContainer);
    }

    private void init() {
        gEngineKit = activity.getEngineKit();
        CallSession session = gEngineKit.getCurrentSession();
        if (session == null || EnumType.CallState.Idle == session.getState()) {
            activity.finish();
        } else if (EnumType.CallState.Connected == session.getState()) {
            incomingActionContainer.setVisibility(View.GONE);
            outgoingActionContainer.setVisibility(View.GONE);
            connectedActionContainer.setVisibility(View.VISIBLE);
            inviteeInfoContainer.setVisibility(View.GONE);
            minimizeImageView.setVisibility(View.VISIBLE);
        } else {
            if (isOutgoing) {
                incomingActionContainer.setVisibility(View.GONE);
                outgoingActionContainer.setVisibility(View.VISIBLE);
                connectedActionContainer.setVisibility(View.GONE);
                descTextView.setText(R.string.av_waiting);
            } else {
                incomingActionContainer.setVisibility(View.VISIBLE);
                outgoingActionContainer.setVisibility(View.GONE);
                connectedActionContainer.setVisibility(View.GONE);
                descTextView.setText(R.string.av_video_invite);
            }
        }
    }

    @Override
    public void onAttach(Context context) {
        super.onAttach(context);
        activity = (CallSingleActivity) getActivity();
        if (activity != null) {
            isOutgoing = activity.isOutgoing();
        }
    }

    @Override
    public void onDetach() {
        super.onDetach();
        activity = null;
    }

    @Override
    public void didCallEndWithReason(EnumType.CallEndReason var1) {
    }

    @Override
    public void didChangeState(EnumType.CallState state) {
        runOnUiThread(() -> {
            if (state == EnumType.CallState.Connected) {
                incomingActionContainer.setVisibility(View.GONE);
                outgoingActionContainer.setVisibility(View.GONE);
                connectedActionContainer.setVisibility(View.VISIBLE);
                inviteeInfoContainer.setVisibility(View.GONE);
                descTextView.setVisibility(View.GONE);
                minimizeImageView.setVisibility(View.VISIBLE);
                durationTextView.setVisibility(View.VISIBLE);
            } else {
            // do nothing now
            }
        });
    }

    @Override
    public void didChangeMode(boolean isAudio) {
    }

    @Override
    public void didCreateLocalVideoTrack() {
        SurfaceViewRenderer surfaceView = gEngineKit.getCurrentSession().createRendererView();
        if (surfaceView != null) {
            surfaceView.setZOrderMediaOverlay(true);
            localSurfaceView = surfaceView;
            if (isOutgoing && remoteSurfaceView == null) {
                fullscreenRenderer.addView(surfaceView);
            } else {
                pipRenderer.addView(surfaceView);
            }
            gEngineKit.getCurrentSession().setupLocalVideo(surfaceView);
        }
    }

    @Override
    public void didReceiveRemoteVideoTrack() {
        pipRenderer.setVisibility(View.VISIBLE);
        if (isOutgoing && localSurfaceView != null) {
            ((ViewGroup) localSurfaceView.getParent()).removeView(localSurfaceView);
            pipRenderer.addView(localSurfaceView);
            gEngineKit.getCurrentSession().setupLocalVideo(localSurfaceView);
        }
        SurfaceViewRenderer surfaceView = gEngineKit.getCurrentSession().createRendererView();
        if (surfaceView != null) {
            remoteSurfaceView = surfaceView;
            fullscreenRenderer.removeAllViews();
            fullscreenRenderer.addView(surfaceView);
            gEngineKit.getCurrentSession().setupRemoteVideo(surfaceView);
        }
    }

    @Override
    public void didError(String error) {
    }

    private void runOnUiThread(Runnable runnable) {
        if (getActivity() != null) {
            getActivity().runOnUiThread(runnable);
        }
    }
}

17 View Complete Implementation : WebRtcCallScreen.java
Copyright GNU General Public License v3.0
Author : signalapp
/**
 * A UI widget that encapsulates the entire in-call screen
 * for both initiators and responders.
 *
 * @author Moxie Marlinspike
 */
public clreplaced WebRtcCallScreen extends FrameLayout implements RecipientForeverObserver {

    @SuppressWarnings("unused")
    private static final String TAG = WebRtcCallScreen.clreplaced.getSimpleName();

    private ImageView photo;

    private SurfaceViewRenderer localRenderer;

    private PercentFrameLayout localRenderLayout;

    private PercentFrameLayout remoteRenderLayout;

    private PercentFrameLayout localLargeRenderLayout;

    private TextView name;

    private TextView phoneNumber;

    private TextView label;

    private TextView elapsedTime;

    private View untrustedIdenreplacedyContainer;

    private TextView untrustedIdenreplacedyExplanation;

    private Button acceptIdenreplacedyButton;

    private Button cancelIdenreplacedyButton;

    private TextView status;

    private FloatingActionButton endCallButton;

    private WebRtcCallControls controls;

    private RelativeLayout expandedInfo;

    private ViewGroup callHeader;

    private WebRtcAnswerDeclineButton incomingCallButton;

    private LiveRecipient recipient;

    private boolean minimized;

    public WebRtcCallScreen(Context context) {
        super(context);
        initialize();
    }

    public WebRtcCallScreen(Context context, AttributeSet attrs) {
        super(context, attrs);
        initialize();
    }

    public WebRtcCallScreen(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        initialize();
    }

    public void setActiveCall(@NonNull Recipient personInfo, @NonNull String message, @Nullable String sas, SurfaceViewRenderer localRenderer, SurfaceViewRenderer remoteRenderer) {
        setCard(personInfo, message);
        setConnected(localRenderer, remoteRenderer);
        incomingCallButton.stopRingingAnimation();
        incomingCallButton.setVisibility(View.GONE);
        endCallButton.show();
    }

    public void setActiveCall(@NonNull Recipient personInfo, @NonNull String message, @NonNull SurfaceViewRenderer localRenderer) {
        setCard(personInfo, message);
        setRinging(localRenderer);
        incomingCallButton.stopRingingAnimation();
        incomingCallButton.setVisibility(View.GONE);
        endCallButton.show();
    }

    public void setIncomingCall(Recipient personInfo) {
        setCard(personInfo, getContext().getString(R.string.CallScreen_Incoming_call));
        endCallButton.hide();
        incomingCallButton.setVisibility(View.VISIBLE);
        incomingCallButton.startRingingAnimation();
    }

    public void setUntrustedIdenreplacedy(Recipient personInfo, IdenreplacedyKey untrustedIdenreplacedy) {
        String name = recipient.get().toShortString(getContext());
        String introduction = String.format(getContext().getString(R.string.WebRtcCallScreen_new_safety_numbers), name, name);
        SpannableString spannableString = new SpannableString(introduction + " " + getContext().getString(R.string.WebRtcCallScreen_you_may_wish_to_verify_this_contact));
        spannableString.setSpan(new VerifySpan(getContext(), personInfo.getId(), untrustedIdenreplacedy), introduction.length() + 1, spannableString.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
        if (this.recipient != null)
            this.recipient.removeForeverObserver(this);
        this.recipient = personInfo.live();
        this.recipient.observeForever(this);
        setPersonInfo(personInfo);
        incomingCallButton.stopRingingAnimation();
        incomingCallButton.setVisibility(View.GONE);
        this.status.setText(R.string.WebRtcCallScreen_new_safety_number_replacedle);
        this.untrustedIdenreplacedyContainer.setVisibility(View.VISIBLE);
        this.untrustedIdenreplacedyExplanation.setText(spannableString);
        this.untrustedIdenreplacedyExplanation.setMovementMethod(LinkMovementMethod.getInstance());
        this.endCallButton.hide();
    }

    public void setIncomingCallActionListener(WebRtcAnswerDeclineButton.AnswerDeclineListener listener) {
        incomingCallButton.setAnswerDeclineListener(listener);
    }

    public void setAudioMuteButtonListener(WebRtcCallControls.MuteButtonListener listener) {
        this.controls.setAudioMuteButtonListener(listener);
    }

    public void setVideoMuteButtonListener(WebRtcCallControls.MuteButtonListener listener) {
        this.controls.setVideoMuteButtonListener(listener);
    }

    public void setCameraFlipButtonListener(WebRtcCallControls.CameraFlipButtonListener listener) {
        this.controls.setCameraFlipButtonListener(listener);
    }

    public void setSpeakerButtonListener(WebRtcCallControls.SpeakerButtonListener listener) {
        this.controls.setSpeakerButtonListener(listener);
    }

    public void setBluetoothButtonListener(WebRtcCallControls.BluetoothButtonListener listener) {
        this.controls.setBluetoothButtonListener(listener);
    }

    public void setHangupButtonListener(final HangupButtonListener listener) {
        endCallButton.setOnClickListener(v -> listener.onClick());
    }

    public void setAcceptIdenreplacedyListener(OnClickListener listener) {
        this.acceptIdenreplacedyButton.setOnClickListener(listener);
    }

    public void setCancelIdenreplacedyButton(OnClickListener listener) {
        this.cancelIdenreplacedyButton.setOnClickListener(listener);
    }

    public void updateAudioState(boolean isBluetoothAvailable, boolean isMicrophoneEnabled) {
        this.controls.updateAudioState(isBluetoothAvailable);
        this.controls.setMicrophoneEnabled(isMicrophoneEnabled);
    }

    public void setControlsEnabled(boolean enabled) {
        this.controls.setControlsEnabled(enabled);
    }

    public void setLocalVideoState(@NonNull CameraState cameraState, @NonNull SurfaceViewRenderer localRenderer) {
        this.controls.setVideoAvailable(cameraState.getCameraCount() > 0);
        this.controls.setVideoEnabled(cameraState.isEnabled());
        this.controls.setCameraFlipAvailable(cameraState.getCameraCount() > 1);
        this.controls.setCameraFlipClickable(cameraState.getActiveDirection() != CameraState.Direction.PENDING);
        this.controls.setCameraFlipButtonEnabled(cameraState.getActiveDirection() == CameraState.Direction.BACK);
        localRenderer.setMirror(cameraState.getActiveDirection() == CameraState.Direction.FRONT);
        localRenderer.setScalingType(RendererCommon.ScalingType.SCALE_ASPECT_FIT);
        this.localRenderer = localRenderer;
        if (localRenderLayout.getChildCount() != 0) {
            displayLocalRendererInSmallLayout(!cameraState.isEnabled());
        } else {
            displayLocalRendererInLargeLayout(!cameraState.isEnabled());
        }
        localRenderer.setVisibility(cameraState.isEnabled() ? VISIBLE : INVISIBLE);
    }

    public void setRemoteVideoEnabled(boolean enabled) {
        if (enabled && this.remoteRenderLayout.isHidden()) {
            this.photo.setVisibility(View.INVISIBLE);
            setMinimized(true);
            this.remoteRenderLayout.setHidden(false);
            this.remoteRenderLayout.requestLayout();
            if (localRenderLayout.isHidden())
                this.controls.displayVideoTooltip(callHeader);
        } else if (!enabled && !this.remoteRenderLayout.isHidden()) {
            setMinimized(false);
            this.photo.setVisibility(View.VISIBLE);
            this.remoteRenderLayout.setHidden(true);
            this.remoteRenderLayout.requestLayout();
        }
    }

    public boolean isVideoEnabled() {
        return controls.isVideoEnabled();
    }

    private void displayLocalRendererInLargeLayout(boolean hide) {
        if (localLargeRenderLayout.getChildCount() == 0) {
            localRenderLayout.removeAllViews();
            if (localRenderer != null) {
                localLargeRenderLayout.addView(localRenderer);
            }
        }
        localRenderLayout.setHidden(true);
        localRenderLayout.requestLayout();
        localLargeRenderLayout.setHidden(hide);
        localLargeRenderLayout.requestLayout();
        if (hide) {
            photo.setVisibility(View.VISIBLE);
        } else {
            photo.setVisibility(View.INVISIBLE);
        }
    }

    private void displayLocalRendererInSmallLayout(boolean hide) {
        if (localRenderLayout.getChildCount() == 0) {
            localLargeRenderLayout.removeAllViews();
            if (localRenderer != null) {
                localRenderLayout.addView(localRenderer);
            }
        }
        localLargeRenderLayout.setHidden(true);
        localLargeRenderLayout.requestLayout();
        localRenderLayout.setHidden(hide);
        localRenderLayout.requestLayout();
        if (remoteRenderLayout.isHidden()) {
            photo.setVisibility(View.VISIBLE);
        }
    }

    private void initialize() {
        LayoutInflater inflater = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        inflater.inflate(R.layout.webrtc_call_screen, this, true);
        this.elapsedTime = findViewById(R.id.elapsedTime);
        this.photo = findViewById(R.id.photo);
        this.localRenderLayout = findViewById(R.id.local_render_layout);
        this.remoteRenderLayout = findViewById(R.id.remote_render_layout);
        this.localLargeRenderLayout = findViewById(R.id.local_large_render_layout);
        this.phoneNumber = findViewById(R.id.phoneNumber);
        this.name = findViewById(R.id.name);
        this.label = findViewById(R.id.label);
        this.status = findViewById(R.id.callStateLabel);
        this.controls = findViewById(R.id.inCallControls);
        this.endCallButton = findViewById(R.id.hangup_fab);
        this.incomingCallButton = findViewById(R.id.answer_decline_button);
        this.untrustedIdenreplacedyContainer = findViewById(R.id.untrusted_layout);
        this.untrustedIdenreplacedyExplanation = findViewById(R.id.untrusted_explanation);
        this.acceptIdenreplacedyButton = findViewById(R.id.accept_safety_numbers);
        this.cancelIdenreplacedyButton = findViewById(R.id.cancel_safety_numbers);
        this.expandedInfo = findViewById(R.id.expanded_info);
        this.callHeader = findViewById(R.id.call_info_1);
        this.localRenderLayout.setHidden(true);
        this.remoteRenderLayout.setHidden(true);
        this.minimized = false;
        this.remoteRenderLayout.setOnClickListener(v -> {
            if (!this.remoteRenderLayout.isHidden()) {
                setMinimized(!minimized);
            }
        });
    }

    private void setRinging(SurfaceViewRenderer localRenderer) {
        if (localLargeRenderLayout.getChildCount() == 0) {
            if (localRenderer.getParent() != null) {
                ((ViewGroup) localRenderer.getParent()).removeView(localRenderer);
            }
            localLargeRenderLayout.setPosition(0, 0, 100, 100);
            localRenderer.setLayoutParams(new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
            localRenderer.setMirror(true);
            localRenderer.setZOrderMediaOverlay(true);
            localLargeRenderLayout.addView(localRenderer);
            this.localRenderer = localRenderer;
        }
    }

    private void setConnected(SurfaceViewRenderer localRenderer, SurfaceViewRenderer remoteRenderer) {
        if (localRenderLayout.getChildCount() == 0) {
            if (localRenderer.getParent() != null) {
                ((ViewGroup) localRenderer.getParent()).removeView(localRenderer);
            }
            if (remoteRenderer.getParent() != null) {
                ((ViewGroup) remoteRenderer.getParent()).removeView(remoteRenderer);
            }
            localRenderLayout.setPosition(7, 70, 25, 25);
            remoteRenderLayout.setPosition(0, 0, 100, 100);
            localRenderer.setLayoutParams(new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
            remoteRenderer.setLayoutParams(new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
            localRenderer.setMirror(true);
            localRenderer.setZOrderMediaOverlay(true);
            localRenderLayout.addView(localRenderer);
            remoteRenderLayout.addView(remoteRenderer);
            this.localRenderer = localRenderer;
        }
    }

    private void setPersonInfo(@NonNull final Recipient recipient) {
        GlideApp.with(getContext().getApplicationContext()).load(recipient.getContactPhoto()).fallback(recipient.getFallbackContactPhoto().asCallCard(getContext())).error(recipient.getFallbackContactPhoto().asCallCard(getContext())).diskCacheStrategy(DiskCacheStrategy.ALL).into(this.photo);
        if (FeatureFlags.PROFILE_DISPLAY) {
            this.name.setText(recipient.getDisplayName(getContext()));
            if (recipient.getE164().isPresent()) {
                this.phoneNumber.setText(recipient.requireE164());
                this.phoneNumber.setVisibility(View.VISIBLE);
            } else {
                this.phoneNumber.setVisibility(View.GONE);
            }
        } else {
            this.name.setText(recipient.getName(getContext()));
            if (recipient.getName(getContext()) == null && !TextUtils.isEmpty(recipient.getProfileName())) {
                this.phoneNumber.setText(recipient.requireE164() + " (~" + recipient.getProfileName() + ")");
            } else {
                this.phoneNumber.setText(recipient.requireE164());
            }
        }
    }

    private void setCard(Recipient recipient, String status) {
        if (this.recipient != null)
            this.recipient.removeForeverObserver(this);
        this.recipient = recipient.live();
        this.recipient.observeForever(this);
        setPersonInfo(recipient);
        this.status.setText(status);
        this.untrustedIdenreplacedyContainer.setVisibility(View.GONE);
    }

    private void setMinimized(boolean minimized) {
        if (minimized) {
            ViewCompat.animate(callHeader).translationY(-1 * expandedInfo.getHeight());
            ViewCompat.animate(status).alpha(0);
            ViewCompat.animate(endCallButton).translationY(endCallButton.getHeight() + ViewUtil.dpToPx(getContext(), 40));
            ViewCompat.animate(endCallButton).alpha(0);
            this.minimized = true;
        } else {
            ViewCompat.animate(callHeader).translationY(0);
            ViewCompat.animate(status).alpha(1);
            ViewCompat.animate(endCallButton).translationY(0);
            ViewCompat.animate(endCallButton).alpha(1).withEndAction(() -> {
                // Note: This is to work around an Android bug, see #6225
                endCallButton.requestLayout();
            });
            this.minimized = false;
        }
    }

    @Override
    public void onRecipientChanged(@NonNull Recipient recipient) {
        setPersonInfo(recipient);
    }

    public interface HangupButtonListener {

        void onClick();
    }
}

17 View Complete Implementation : CallActivity.java
Copyright MIT License
Author : wmhameed
/**
 * Activity for peer connection call setup, call waiting
 * and call view.
 */
public clreplaced CallActivity extends Activity implements AppRTCClient.SignalingEvents, NotificationCenter.NotificationCenterDelegate, PeerConnectionClient.PeerConnectionEvents, CallFragment.OnCallEvents, SensorEventListener {

    public static final String EXTRA_ROOMID = "org.appspot.apprtc.ROOMID";

    public static final String EXTRA_LOOPBACK = "org.appspot.apprtc.LOOPBACK";

    public static final String EXTRA_VIDEO_CALL = "org.appspot.apprtc.VIDEO_CALL";

    public static final String EXTRA_VIDEO_WIDTH = "org.appspot.apprtc.VIDEO_WIDTH";

    public static final String EXTRA_VIDEO_HEIGHT = "org.appspot.apprtc.VIDEO_HEIGHT";

    public static final String EXTRA_VIDEO_FPS = "org.appspot.apprtc.VIDEO_FPS";

    public static final String EXTRA_VIDEO_CAPTUREQUALITYSLIDER_ENABLED = "org.appsopt.apprtc.VIDEO_CAPTUREQUALITYSLIDER";

    public static final String EXTRA_VIDEO_BITRATE = "org.appspot.apprtc.VIDEO_BITRATE";

    public static final String EXTRA_VIDEOCODEC = "org.appspot.apprtc.VIDEOCODEC";

    public static final String EXTRA_HWCODEC_ENABLED = "org.appspot.apprtc.HWCODEC";

    public static final String EXTRA_CAPTURETOTEXTURE_ENABLED = "org.appspot.apprtc.CAPTURETOTEXTURE";

    public static final String EXTRA_AUDIO_BITRATE = "org.appspot.apprtc.AUDIO_BITRATE";

    public static final String EXTRA_AUDIOCODEC = "org.appspot.apprtc.AUDIOCODEC";

    public static final String EXTRA_NOAUDIOPROCESSING_ENABLED = "org.appspot.apprtc.NOAUDIOPROCESSING";

    public static final String EXTRA_OPENSLES_ENABLED = "org.appspot.apprtc.OPENSLES";

    public static final String EXTRA_DISPLAY_HUD = "org.appspot.apprtc.DISPLAY_HUD";

    public static final String EXTRA_TRACING = "org.appspot.apprtc.TRACING";

    public static final String EXTRA_CMDLINE = "org.appspot.apprtc.CMDLINE";

    public static final String EXTRA_RUNTIME = "org.appspot.apprtc.RUNTIME";

    public static final String EXTRA_RECEIVED = "org.appspot.apprtc.RECEIVED";

    public static final int CALL_HANGUP = 700;

    private static final String TAG = "CallRTCClient";

    public boolean isIncomingCall = false;

    // List of mandatory application permissions.
    private static final String[] MANDATORY_PERMISSIONS = { "android.permission.MODIFY_AUDIO_SETTINGS", "android.permission.RECORD_AUDIO", "android.permission.INTERNET" };

    // Peer connection statistics callback period in ms.
    private static final int STAT_CALLBACK_PERIOD = 1000;

    // Local preview screen position before call is connected.
    private static final int LOCAL_X_CONNECTING = 0;

    private static final int LOCAL_Y_CONNECTING = 0;

    private static final int LOCAL_WIDTH_CONNECTING = 100;

    private static final int LOCAL_HEIGHT_CONNECTING = 100;

    // Local preview screen position after call is connected.
    private static final int LOCAL_X_CONNECTED = 72;

    private static final int LOCAL_Y_CONNECTED = 72;

    private static final int LOCAL_WIDTH_CONNECTED = 25;

    private static final int LOCAL_HEIGHT_CONNECTED = 25;

    // Remote video screen position
    private static final int REMOTE_X = 0;

    private static final int REMOTE_Y = 0;

    private static final int REMOTE_WIDTH = 100;

    private static final int REMOTE_HEIGHT = 100;

    private PeerConnectionClient peerConnectionClient = null;

    private AppRTCClient appRtcClient;

    private SignalingParameters signalingParameters;

    private AppRTCAudioManager audioManager = null;

    private EglBase rootEglBase;

    private SurfaceViewRenderer localRender;

    private SurfaceViewRenderer remoteRender;

    private PercentFrameLayout localRenderLayout;

    private PercentFrameLayout remoteRenderLayout;

    private ScalingType scalingType;

    private Toast logToast;

    private boolean commandLineRun;

    private int runTimeMs;

    private boolean activityRunning;

    private RoomConnectionParameters roomConnectionParameters;

    private PeerConnectionParameters peerConnectionParameters;

    private boolean Received = false;

    private boolean iceConnected;

    private boolean isError;

    private boolean callControlFragmentVisible = true;

    private long callStartedTimeMs = 0;

    private SensorManager mSensorManager;

    private Sensor myProximitySensor;

    // Controls
    CallFragment callFragment;

    HudFragment hudFragment;

    PowerManager.WakeLock mProximityWakeLock;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mSensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);
        myProximitySensor = mSensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY);
        PowerManager powerManager = (PowerManager) ApplicationLoader.applicationContext.getSystemService(Context.POWER_SERVICE);
        mProximityWakeLock = powerManager.newWakeLock(PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK, TAG);
        Thread.setDefaultUncaughtExceptionHandler(new UnhandledExceptionHandler(this));
        NotificationCenter.getInstance().addObserver(this, CallActivity.CALL_HANGUP);
        // Set window styles for fullscreen-window size. Needs to be done before
        // adding content.
        requestWindowFeature(Window.FEATURE_NO_replacedLE);
        getWindow().addFlags(LayoutParams.FLAG_FULLSCREEN | LayoutParams.FLAG_KEEP_SCREEN_ON | LayoutParams.FLAG_DISMISS_KEYGUARD | LayoutParams.FLAG_SHOW_WHEN_LOCKED | LayoutParams.FLAG_TURN_SCREEN_ON);
        getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
        setContentView(R.layout.activity_call);
        iceConnected = false;
        signalingParameters = null;
        scalingType = ScalingType.SCALE_ASPECT_FILL;
        // Create UI controls.
        localRender = (SurfaceViewRenderer) findViewById(R.id.local_video_view);
        remoteRender = (SurfaceViewRenderer) findViewById(R.id.remote_video_view);
        localRenderLayout = (PercentFrameLayout) findViewById(R.id.local_video_layout);
        remoteRenderLayout = (PercentFrameLayout) findViewById(R.id.remote_video_layout);
        callFragment = new CallFragment();
        hudFragment = new HudFragment();
        // Show/hide call control fragment on view click.
        View.OnClickListener listener = new View.OnClickListener() {

            @Override
            public void onClick(View view) {
                toggleCallControlFragmentVisibility();
            }
        };
        localRender.setOnClickListener(listener);
        remoteRender.setOnClickListener(listener);
        // Create video renderers.
        rootEglBase = new EglBase();
        localRender.init(rootEglBase.getContext(), null);
        remoteRender.init(rootEglBase.getContext(), null);
        localRender.setZOrderMediaOverlay(true);
        updateVideoView();
        // Check for mandatory permissions.
        for (String permission : MANDATORY_PERMISSIONS) {
            if (checkCallingOrSelfPermission(permission) != PackageManager.PERMISSION_GRANTED) {
                logAndToast("Permission " + permission + " is not granted");
                setResult(RESULT_CANCELED);
                finish();
                return;
            }
        }
        // Get Intent parameters.
        final Intent intent = getIntent();
        Uri roomUri = intent.getData();
        if (roomUri == null) {
            logAndToast(getString(R.string.missing_url));
            Log.e(TAG, "Didn't get any URL in intent!");
            setResult(RESULT_CANCELED);
            finish();
            return;
        }
        String roomId = intent.getStringExtra(EXTRA_ROOMID);
        if (roomId == null || roomId.length() == 0) {
            logAndToast(getString(R.string.missing_url));
            Log.e(TAG, "Incorrect room ID in intent!");
            setResult(RESULT_CANCELED);
            finish();
            return;
        }
        // intent.getBooleanExtra(EXTRA_LOOPBACK, false);
        boolean loopback = false;
        // // TODO: 12/27/2015 add new methode for video and voice call
        boolean tracing = intent.getBooleanExtra(EXTRA_TRACING, false);
        peerConnectionParameters = new PeerConnectionParameters(intent.getBooleanExtra(EXTRA_VIDEO_CALL, true), loopback, tracing, intent.getIntExtra(EXTRA_VIDEO_WIDTH, 0), intent.getIntExtra(EXTRA_VIDEO_HEIGHT, 0), intent.getIntExtra(EXTRA_VIDEO_FPS, 0), intent.getIntExtra(EXTRA_VIDEO_BITRATE, 0), intent.getStringExtra(EXTRA_VIDEOCODEC), intent.getBooleanExtra(EXTRA_HWCODEC_ENABLED, true), intent.getBooleanExtra(EXTRA_CAPTURETOTEXTURE_ENABLED, false), intent.getIntExtra(EXTRA_AUDIO_BITRATE, 0), intent.getStringExtra(EXTRA_AUDIOCODEC), intent.getBooleanExtra(EXTRA_NOAUDIOPROCESSING_ENABLED, false), intent.getBooleanExtra(EXTRA_OPENSLES_ENABLED, false));
        commandLineRun = intent.getBooleanExtra(EXTRA_CMDLINE, false);
        runTimeMs = intent.getIntExtra(EXTRA_RUNTIME, 0);
        // Create connection client and connection parameters.
        appRtcClient = new WebSocketRTCClient(this, new LooperExecutor());
        roomConnectionParameters = new RoomConnectionParameters(roomUri.toString(), roomId, loopback);
        // Send intent arguments to fragments.
        callFragment.setArguments(intent.getExtras());
        hudFragment.setArguments(intent.getExtras());
        // Activate call and HUD fragments and start the call.
        FragmentTransaction ft = getFragmentManager().beginTransaction();
        ft.add(R.id.call_fragment_container, callFragment);
        ft.add(R.id.hud_fragment_container, hudFragment);
        ft.commitAllowingStateLoss();
        // if (true){return;}
        Received = intent.getBooleanExtra(EXTRA_RECEIVED, false);
        if (Received) {
            callFragment.updateUiCallingState(false);
        } else {
            callFragment.updateUiCallingState(true);
            startCall();
        }
        // For command line execution run connection for <runTimeMs> and exit.
        if (commandLineRun && runTimeMs > 0) {
            (new Handler()).postDelayed(new Runnable() {

                public void run() {
                    disconnect();
                }
            }, runTimeMs);
        }
        peerConnectionClient = PeerConnectionClient.getInstance();
        peerConnectionClient.createPeerConnectionFactory(CallActivity.this, peerConnectionParameters, CallActivity.this);
    }

    // Activity interfaces
    @Override
    public void onPause() {
        super.onPause();
        mSensorManager.unregisterListener(this);
        activityRunning = false;
        if (peerConnectionClient != null) {
            peerConnectionClient.stopVideoSource();
        }
    }

    @Override
    public void onResume() {
        super.onResume();
        mSensorManager.registerListener(this, myProximitySensor, SensorManager.SENSOR_DELAY_NORMAL);
        activityRunning = true;
        if (peerConnectionClient != null) {
            // // TODO: 12/27/2015  add pause video or stop feature
            peerConnectionClient.startVideoSource();
        }
    }

    @Override
    protected void onDestroy() {
        disconnect();
        if (logToast != null) {
            logToast.cancel();
        }
        activityRunning = false;
        rootEglBase.release();
        mSensorManager.unregisterListener(this);
        super.onDestroy();
    }

    // CallFragment.OnCallEvents interface implementation.
    @Override
    public void onCallHangUp() {
        WebRtcPhone.getInstance().AnswerCall();
        disconnect();
        ToneGenerator tg2 = new ToneGenerator(AudioManager.STREAM_VOICE_CALL, 100);
        tg2.startTone(ToneGenerator.TONE_PROP_BEEP2);
    }

    @Override
    public void onCameraSwitch() {
        if (peerConnectionClient != null) {
            peerConnectionClient.switchCamera();
        }
    }

    @Override
    public void onVideoScalingSwitch(ScalingType scalingType) {
        this.scalingType = scalingType;
        updateVideoView();
    }

    @Override
    public void onCaptureFormatChange(int width, int height, int framerate) {
        if (peerConnectionClient != null) {
            peerConnectionClient.changeCaptureFormat(width, height, framerate);
        }
    }

    @Override
    public void onCalling() {
    }

    @Override
    public void onCallAnswer() {
        startCall();
        callFragment.hideCallerId();
        // if(WebRtcPhone.getInstance().tg != null){
        // WebRtcPhone.getInstance().tg.stopTone();
        // WebRtcPhone.getInstance().tg.release();
        // }
        WebRtcPhone.getInstance().AnswerCall();
    }

    @Override
    public void onRinging() {
    }

    @Override
    public void onMuteChange() {
    }

    @Override
    public void onSpeakerChange() {
    }

    // Helper functions.
    private void toggleCallControlFragmentVisibility() {
        if (!iceConnected || !callFragment.isAdded()) {
            return;
        }
        // Show/hide call control fragment
        callControlFragmentVisible = !callControlFragmentVisible;
        FragmentTransaction ft = getFragmentManager().beginTransaction();
        if (callControlFragmentVisible) {
            ft.show(callFragment);
            ft.show(hudFragment);
        } else {
            ft.hide(callFragment);
            ft.hide(hudFragment);
        }
        ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
        ft.commit();
    }

    private void updateVideoView() {
        remoteRenderLayout.setPosition(REMOTE_X, REMOTE_Y, REMOTE_WIDTH, REMOTE_HEIGHT);
        remoteRender.setScalingType(scalingType);
        remoteRender.setMirror(false);
        if (iceConnected) {
            localRenderLayout.setPosition(LOCAL_X_CONNECTED, LOCAL_Y_CONNECTED, LOCAL_WIDTH_CONNECTED, LOCAL_HEIGHT_CONNECTED);
            localRender.setScalingType(ScalingType.SCALE_ASPECT_FIT);
        } else {
            localRenderLayout.setPosition(LOCAL_X_CONNECTING, LOCAL_Y_CONNECTING, LOCAL_WIDTH_CONNECTING, LOCAL_HEIGHT_CONNECTING);
            localRender.setScalingType(scalingType);
        }
        localRender.setMirror(true);
        localRender.requestLayout();
        remoteRender.requestLayout();
    }

    private void startCall() {
        if (appRtcClient == null) {
            Log.e(TAG, "AppRTC client is not allocated for a call.");
            return;
        }
        callStartedTimeMs = System.currentTimeMillis();
        // Start room connection.
        logAndToast(getString(R.string.connecting_to, roomConnectionParameters.roomUrl));
        appRtcClient.connectToRoom(roomConnectionParameters);
        // Create and audio manager that will take care of audio routing,
        // audio modes, audio device enumeration etc.
        audioManager = AppRTCAudioManager.create(this, new Runnable() {

            // This method will be called each time the audio state (number and
            // type of devices) has been changed.
            @Override
            public void run() {
                onAudioManagerChangedState();
            }
        });
        // Store existing audio settings and change audio mode to
        // MODE_IN_COMMUNICATION for best possible VoIP performance.
        Log.d(TAG, "Initializing the audio manager...");
        audioManager.init();
    // callFragment.hideCallerId();
    // inputMediaStream.audioTracks.getFirst().setEnabled(false);
    }

    // Should be called from UI thread
    private void callConnected() {
        final long delta = System.currentTimeMillis() - callStartedTimeMs;
        Log.i(TAG, "Call connected: delay=" + delta + "ms");
        if (peerConnectionClient == null || isError) {
            Log.w(TAG, "Call is connected in closed or error state");
            return;
        }
        // Update video view.
        updateVideoView();
        // Enable statistics callback.
        peerConnectionClient.enableStatsEvents(true, STAT_CALLBACK_PERIOD);
        callFragment.hideCallerId();
        WebRtcPhone.getInstance().tg.stopTone();
    }

    private void onAudioManagerChangedState() {
    // TODO(henrika): disable video if AppRTCAudioManager.AudioDevice.EARPIECE
    // is active.
    }

    // Disconnect from remote resources, dispose of local resources, and exit.
    private void disconnect() {
        activityRunning = false;
        if (appRtcClient != null) {
            appRtcClient.disconnectFromRoom();
            appRtcClient = null;
        }
        if (peerConnectionClient != null) {
            peerConnectionClient.close();
            peerConnectionClient = null;
        }
        if (localRender != null) {
            localRender.release();
            localRender = null;
        }
        if (remoteRender != null) {
            remoteRender.release();
            remoteRender = null;
        }
        if (audioManager != null) {
            audioManager.close();
            audioManager = null;
        }
        if (iceConnected && !isError) {
            setResult(RESULT_OK);
        } else {
            setResult(RESULT_CANCELED);
        }
        finish();
    }

    private void disconnectWithErrorMessage(final String errorMessage) {
        if (commandLineRun || !activityRunning) {
            FileLog.d(TAG, "Critical error: " + errorMessage);
            disconnect();
        } else {
            disconnect();
            FileLog.d(TAG, errorMessage);
        /*new AlertDialog.Builder(this)
          .setreplacedle(getText(R.string.channel_error_replacedle))
          .setMessage(errorMessage)
          .setCancelable(false)
          .setNeutralButton(R.string.ok, new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int id) {
              dialog.cancel();
              disconnect();
            }
          }).create().show();*/
        }
    }

    // Log |msg| and Toast about it.
    private void logAndToast(String msg) {
        FileLog.d(TAG, msg);
    /* if (logToast != null) {
      logToast.cancel();
    }
    logToast = Toast.makeText(this, msg, Toast.LENGTH_SHORT);
    logToast.show();*/
    }

    private void reportError(final String description) {
        runOnUiThread(new Runnable() {

            @Override
            public void run() {
                if (!isError) {
                    isError = true;
                    disconnectWithErrorMessage(description);
                }
            }
        });
    }

    // -----Implementation of AppRTCClient.AppRTCSignalingEvents ---------------
    // All callbacks are invoked from websocket signaling looper thread and
    // are routed to UI thread.
    private void onConnectedToRoomInternal(final SignalingParameters params) {
        final long delta = System.currentTimeMillis() - callStartedTimeMs;
        signalingParameters = params;
        logAndToast("Creating peer connection, delay=" + delta + "ms");
        peerConnectionClient.createPeerConnection(rootEglBase.getContext(), localRender, remoteRender, signalingParameters);
        if (signalingParameters.initiator) {
            logAndToast("Creating OFFER...");
            // Create offer. Offer SDP will be sent to answering client in
            // PeerConnectionEvents.onLocalDescription event.
            peerConnectionClient.createOffer();
        } else {
            if (params.offerSdp != null) {
                peerConnectionClient.setRemoteDescription(params.offerSdp);
                logAndToast("Creating ANSWER...");
                // Create answer. Answer SDP will be sent to offering client in
                // PeerConnectionEvents.onLocalDescription event.
                peerConnectionClient.createAnswer();
            }
            if (params.iceCandidates != null) {
                // Add remote ICE candidates from room.
                for (IceCandidate iceCandidate : params.iceCandidates) {
                    peerConnectionClient.addRemoteIceCandidate(iceCandidate);
                }
            }
        }
    }

    @Override
    public void onConnectedToRoom(final SignalingParameters params) {
        runOnUiThread(new Runnable() {

            @Override
            public void run() {
                onConnectedToRoomInternal(params);
            }
        });
    }

    @Override
    public void onRemoteDescription(final SessionDescription sdp) {
        final long delta = System.currentTimeMillis() - callStartedTimeMs;
        runOnUiThread(new Runnable() {

            @Override
            public void run() {
                if (peerConnectionClient == null) {
                    Log.e(TAG, "Received remote SDP for non-initilized peer connection.");
                    return;
                }
                logAndToast("Received remote " + sdp.type + ", delay=" + delta + "ms");
                peerConnectionClient.setRemoteDescription(sdp);
                if (!signalingParameters.initiator) {
                    logAndToast("Creating ANSWER...");
                    // Create answer. Answer SDP will be sent to offering client in
                    // PeerConnectionEvents.onLocalDescription event.
                    peerConnectionClient.createAnswer();
                }
            }
        });
    }

    @Override
    public void onRemoteIceCandidate(final IceCandidate candidate) {
        runOnUiThread(new Runnable() {

            @Override
            public void run() {
                if (peerConnectionClient == null) {
                    Log.e(TAG, "Received ICE candidate for non-initilized peer connection.");
                    return;
                }
                peerConnectionClient.addRemoteIceCandidate(candidate);
            }
        });
    }

    @Override
    public void onChannelClose() {
        runOnUiThread(new Runnable() {

            @Override
            public void run() {
                logAndToast("Remote end hung up; dropping PeerConnection");
                disconnect();
            }
        });
    }

    @Override
    public void onChannelError(final String description) {
        reportError(description);
    }

    // -----Implementation of PeerConnectionClient.PeerConnectionEvents.---------
    // Send local peer connection SDP and ICE candidates to remote party.
    // All callbacks are invoked from peer connection client looper thread and
    // are routed to UI thread.
    @Override
    public void onLocalDescription(final SessionDescription sdp) {
        final long delta = System.currentTimeMillis() - callStartedTimeMs;
        runOnUiThread(new Runnable() {

            @Override
            public void run() {
                if (appRtcClient != null) {
                    logAndToast("Sending " + sdp.type + ", delay=" + delta + "ms");
                    if (signalingParameters.initiator) {
                        appRtcClient.sendOfferSdp(sdp);
                    } else {
                        appRtcClient.sendAnswerSdp(sdp);
                    }
                }
            }
        });
    }

    @Override
    public void onIceCandidate(final IceCandidate candidate) {
        runOnUiThread(new Runnable() {

            @Override
            public void run() {
                if (appRtcClient != null) {
                    appRtcClient.sendLocalIceCandidate(candidate);
                }
            }
        });
    }

    @Override
    public void onIceConnected() {
        final long delta = System.currentTimeMillis() - callStartedTimeMs;
        runOnUiThread(new Runnable() {

            @Override
            public void run() {
                logAndToast("ICE connected, delay=" + delta + "ms");
                iceConnected = true;
                callConnected();
            }
        });
    }

    @Override
    public void onIceDisconnected() {
        runOnUiThread(new Runnable() {

            @Override
            public void run() {
                logAndToast("ICE disconnected");
                iceConnected = false;
                disconnect();
            }
        });
    }

    @Override
    public void onPeerConnectionClosed() {
    }

    @Override
    public void onPeerConnectionStatsReady(final StatsReport[] reports) {
        runOnUiThread(new Runnable() {

            @Override
            public void run() {
                if (!isError && iceConnected) {
                    hudFragment.updateEncoderStatistics(reports);
                }
            }
        });
    }

    @Override
    public void onPeerConnectionError(final String description) {
        reportError(description);
    }

    @Override
    public void didReceivedNotification(int id, Object... args) {
        if (id == CALL_HANGUP) {
            disconnect();
            WebRtcPhone.getInstance().AnswerCall();
        }
    }

    @Override
    public void onSensorChanged(SensorEvent event) {
        WindowManager.LayoutParams params = this.getWindow().getAttributes();
        if (event.sensor.getType() == Sensor.TYPE_PROXIMITY) {
            if (event.values[0] == 0) {
                /*  FileLog.d("onSensorChanged", "distance:" + event.values[0] + "");
              params.flags |= LayoutParams.FLAG_KEEP_SCREEN_ON;
              params.screenBrightness = 0;
              getWindow().setAttributes(params);*/
                if (!mProximityWakeLock.isHeld()) {
                    mProximityWakeLock.acquire();
                }
            } else {
                if (mProximityWakeLock.isHeld()) {
                    mProximityWakeLock.release();
                }
            /*params.flags |= LayoutParams.FLAG_KEEP_SCREEN_ON;
              params.screenBrightness = -1f;
              getWindow().setAttributes(params);*/
            }
        /*try {
            // Yeah, this is hidden field.
            //int field = PowerManager.clreplaced.getClreplaced().getField("PROXIMITY_SCREEN_OFF_WAKE_LOCK").getInt(null);
          } catch (Throwable ignored) {

          }*/
        }
    }

    @Override
    public void onAccuracyChanged(Sensor sensor, int accuracy) {
    }
}

16 View Complete Implementation : CallFragment.java
Copyright GNU Affero General Public License v3.0
Author : actorapp
public clreplaced CallFragment extends BaseFragment {

    protected static final int PERMISSIONS_REQUEST_FOR_CALL = 147;

    protected static final int NOTIFICATION_ID = 2;

    protected static final int TIMER_ID = 1;

    protected final ActorBinder ACTIVITY_BINDER = new ActorBinder();

    protected long callId = -1;

    protected Peer peer;

    protected Vibrator v;

    protected View answerContainer;

    protected Ringtone ringtone;

    protected CallVM call;

    protected AvatarView avatarView;

    protected TextView nameTV;

    protected ActorRef timer;

    protected TextView statusTV;

    protected View[] avatarLayers;

    protected View layer1;

    protected View layer2;

    protected View layer3;

    protected NotificationManager manager;

    protected CallState currentState;

    protected ImageButton endCall;

    protected View endCallContainer;

    protected boolean speakerOn = false;

    protected AudioManager audioManager;

    protected RecyclerListView membersList;

    protected float dX, dY;

    protected TintImageView muteCall;

    protected TextView muteCallTv;

    protected TintImageView speaker;

    protected TextView speakerTV;

    protected TintImageView videoIcon;

    protected TextView videoTv;

    // 
    // Video References
    // 
    protected EglBase eglContext;

    protected SurfaceViewRenderer localVideoView;

    protected VideoRenderer localRender;

    protected boolean isLocalViewConfigured;

    protected VideoTrack localTrack;

    protected SurfaceViewRenderer remoteVideoView;

    protected VideoRenderer remoteRender;

    protected boolean isRemoteViewConfigured;

    protected VideoTrack remoteTrack;

    // 
    // Vibrate/tone/wakelock
    // 
    boolean vibrate = true;

    protected PowerManager powerManager;

    protected PowerManager.WakeLock wakeLock;

    protected int field = 0x00000020;

    // 
    // Constructor
    // 
    static CallFragment create(long callId) {
        CallFragment res = new CallFragment();
        Bundle args = new Bundle();
        args.putLong("call_id", callId);
        res.setArguments(args);
        return res;
    }

    public CallFragment() {
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        this.callId = getArguments().getLong("call_id");
        this.call = messenger().getCall(callId);
        if (call == null) {
            this.peer = Peer.user(myUid());
        } else {
            this.peer = call.getPeer();
        }
        manager = (NotificationManager) getActivity().getSystemService(Context.NOTIFICATION_SERVICE);
        FrameLayout cont = (FrameLayout) inflater.inflate(R.layout.fragment_call, container, false);
        v = (Vibrator) getActivity().getSystemService(Context.VIBRATOR_SERVICE);
        CallBackgroundAvatarView backgroundAvatarView = (CallBackgroundAvatarView) cont.findViewById(R.id.background);
        // animator = new CallAvatarLayerAnimator(cont.findViewById(R.id.layer),
        // cont.findViewById(R.id.layer1),
        // cont.findViewById(R.id.layer2),
        // cont.findViewById(R.id.layer3),
        // cont.findViewById(R.id.layer4)
        // );
        layer1 = cont.findViewById(R.id.layer1);
        layer2 = cont.findViewById(R.id.layer2);
        layer3 = cont.findViewById(R.id.layer3);
        avatarLayers = new View[] { // cont.findViewById(R.id.layer),
        layer1, layer2, layer3 // cont.findViewById(R.id.layer4)
        };
        // TODO disabled while working on video, enable later!
        // showView(layer1);
        // showView(layer2);
        // showView(layer3);
        // wave(avatarLayers, 1.135f ,1900, -2f);
        for (int i = 0; i < avatarLayers.length; i++) {
            View layer = avatarLayers[i];
            ((GradientDrawable) layer.getBackground()).setColor(Color.WHITE);
            ((GradientDrawable) layer.getBackground()).setAlpha(50);
        }
        endCallContainer = cont.findViewById(R.id.end_call_container);
        answerContainer = cont.findViewById(R.id.answer_container);
        ImageButton answer = (ImageButton) cont.findViewById(R.id.answer);
        answer.setOnClickListener(v1 -> onAnswer());
        ImageButton notAnswer = (ImageButton) cont.findViewById(R.id.notAnswer);
        endCall = (ImageButton) cont.findViewById(R.id.end_call);
        notAnswer.setOnClickListener(v1 -> doEndCall());
        endCall.setOnClickListener(v1 -> doEndCall());
        // 
        // Avatar/Name bind
        // 
        avatarView = (AvatarView) cont.findViewById(R.id.avatar);
        avatarView.init(Screen.dp(130), 50);
        nameTV = (TextView) cont.findViewById(R.id.name);
        nameTV.setTextColor(ActorSDK.sharedActor().style.getProfilereplacedleColor());
        if (peer.getPeerType() == PeerType.PRIVATE) {
            UserVM user = users().get(peer.getPeerId());
            avatarView.bind(user);
            backgroundAvatarView.bind(user);
            bind(nameTV, user.getName());
        } else if (peer.getPeerType() == PeerType.GROUP) {
            GroupVM g = groups().get(peer.getPeerId());
            avatarView.bind(g);
            backgroundAvatarView.bind(g);
            bind(nameTV, g.getName());
        }
        nameTV.setSelected(true);
        // 
        // Members list
        // 
        membersList = (RecyclerListView) cont.findViewById(R.id.members_list);
        if (call != null) {
            CallMembersAdapter membersAdapter = new CallMembersAdapter(getActivity(), call.getMembers());
            membersList.setAdapter(membersAdapter);
        }
        // 
        // Members list/ avatar switch
        // 
        View.OnClickListener listener = v1 -> switchAvatarMembers();
        avatarView.setOnClickListener(listener);
        // cont.findViewById(R.id.background).setOnClickListener(listener);
        membersList.setOnItemClickListener((parent, view, position, id) -> switchAvatarMembers());
        statusTV = (TextView) cont.findViewById(R.id.status);
        // statusTV.setTextColor(ActorSDK.sharedActor().style.getTextSecondaryColor());
        // Check permission
        // 
        if (ContextCompat.checkSelfPermission(getActivity(), Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED || ContextCompat.checkSelfPermission(getActivity(), Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED || ContextCompat.checkSelfPermission(getActivity(), Manifest.permission.VIBRATE) != PackageManager.PERMISSION_GRANTED || ContextCompat.checkSelfPermission(getActivity(), Manifest.permission.MODIFY_AUDIO_SETTINGS) != PackageManager.PERMISSION_GRANTED || ContextCompat.checkSelfPermission(getActivity(), Manifest.permission.WAKE_LOCK) != PackageManager.PERMISSION_GRANTED) {
            Log.d("Permissions", "call - no permission :c");
            CallFragment.this.requestPermissions(new String[] { Manifest.permission.RECORD_AUDIO, Manifest.permission.CAMERA, Manifest.permission.VIBRATE, Manifest.permission.MODIFY_AUDIO_SETTINGS, Manifest.permission.WAKE_LOCK }, PERMISSIONS_REQUEST_FOR_CALL);
        }
        audioManager = (AudioManager) getActivity().getSystemService(Context.AUDIO_SERVICE);
        audioManager.getStreamVolume(AudioManager.STREAM_VOICE_CALL);
        speaker = (TintImageView) cont.findViewById(R.id.speaker);
        speaker.setResource(R.drawable.ic_volume_up_white_24dp);
        speakerTV = (TextView) cont.findViewById(R.id.speaker_tv);
        cont.findViewById(R.id.speaker_btn).setOnClickListener(v1 -> toggleSpeaker(speaker, speakerTV));
        checkSpeaker(speaker, speakerTV);
        muteCall = (TintImageView) cont.findViewById(R.id.mute);
        muteCallTv = (TextView) cont.findViewById(R.id.mute_tv);
        muteCall.setResource(R.drawable.ic_mic_off_white_24dp);
        cont.findViewById(R.id.mute_btn).setOnClickListener(v1 -> messenger().toggleCallMute(callId));
        videoIcon = (TintImageView) cont.findViewById(R.id.video);
        videoIcon.setResource(R.drawable.ic_videocam_white_24dp);
        videoTv = (TextView) cont.findViewById(R.id.video_tv);
        videoTv.setTextColor(getResources().getColor(R.color.picker_grey));
        videoIcon.setTint(getResources().getColor(R.color.picker_grey));
        cont.findViewById(R.id.video_btn).setOnClickListener(v1 -> messenger().toggleVideoEnabled(callId));
        final TintImageView back = (TintImageView) cont.findViewById(R.id.back);
        back.setResource(R.drawable.ic_message_white_24dp);
        cont.findViewById(R.id.back_btn).setOnClickListener(v1 -> getActivity().startActivity(Intents.openDialog(peer, false, getActivity())));
        final TintImageView add = (TintImageView) cont.findViewById(R.id.add);
        add.setResource(R.drawable.ic_person_add_white_24dp);
        TextView addTv = (TextView) cont.findViewById(R.id.add_user_tv);
        addTv.setTextColor(getResources().getColor(R.color.picker_grey));
        add.setTint(getResources().getColor(R.color.picker_grey));
        if (peer.getPeerType() == PeerType.PRIVATE) {
            eglContext = EglBase.create();
            remoteVideoView = (SurfaceViewRenderer) cont.findViewById(R.id.remote_renderer);
            localVideoView = new SurfaceViewRenderer(getActivity()) {

                private boolean aspectFixed = false;

                @Override
                public void renderFrame(VideoRenderer.I420Frame frame) {
                    if (!aspectFixed) {
                        aspectFixed = true;
                        int maxWH = Screen.getWidth() / 3 - Screen.dp(20);
                        float scale = Math.min(maxWH / (float) frame.width, maxWH / (float) frame.height);
                        int destW = (int) (scale * frame.width);
                        int destH = (int) (scale * frame.height);
                        boolean turned = frame.rotationDegree % 90 % 2 == 0;
                        localVideoView.post(new Runnable() {

                            @Override
                            public void run() {
                                localVideoView.getLayoutParams().height = turned ? destW : destH;
                                localVideoView.getLayoutParams().width = turned ? destH : destW;
                            }
                        });
                    }
                    super.renderFrame(frame);
                }
            };
            localVideoView.setVisibility(View.INVISIBLE);
            localVideoView.setZOrderMediaOverlay(true);
            localVideoView.setOnTouchListener((v1, event) -> {
                switch(event.getAction()) {
                    case MotionEvent.ACTION_DOWN:
                        dX = localVideoView.getX() - event.getRawX();
                        dY = localVideoView.getY() - event.getRawY();
                        break;
                    case MotionEvent.ACTION_MOVE:
                        localVideoView.animate().x(event.getRawX() + dX).y(event.getRawY() + dY).setDuration(0).start();
                    default:
                        return false;
                }
                return true;
            });
            int margin = Screen.dp(20);
            int localVideoWidth = Screen.getWidth() / 3 - margin;
            FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(localVideoWidth, Math.round(localVideoWidth / 1.5f), Gravity.TOP | Gravity.LEFT);
            int statusBarHeight = 0;
            int resourceId = getResources().getIdentifier("status_bar_height", "dimen", "android");
            if (resourceId > 0) {
                statusBarHeight = getResources().getDimensionPixelSize(resourceId);
            }
            params.setMargins(margin, margin + statusBarHeight, 0, 0);
            cont.addView(localVideoView, params);
        } else {
            if (call != null) {
                if (call.getIsVideoEnabled().get()) {
                    messenger().toggleVideoEnabled(callId);
                }
            }
        }
        return cont;
    }

    public void toggleSpeaker(TintImageView speaker, TextView speakerTV) {
        toggleSpeaker(speaker, speakerTV, !speakerOn);
    }

    public void toggleSpeaker(TintImageView speaker, TextView speakerTV, boolean speakerOn) {
        this.speakerOn = speakerOn;
        audioManager.setSpeakerphoneOn(speakerOn);
        checkSpeaker(speaker, speakerTV);
    }

    public void checkSpeaker(TintImageView speaker, TextView speakerTV) {
        if (speakerOn) {
            speaker.setTint(Color.WHITE);
            speakerTV.setTextColor(Color.WHITE);
        } else {
            speaker.setTint(getResources().getColor(R.color.picker_grey));
            speakerTV.setTextColor(getResources().getColor(R.color.picker_grey));
        }
    }

    public void switchAvatarMembers() {
        if (peer.getPeerType() == PeerType.GROUP) {
            if (avatarView.getVisibility() == View.VISIBLE) {
                hideView(avatarView);
                showView(membersList);
            } else {
                hideView(membersList);
                showView(avatarView);
            }
        }
    }

    protected void startTimer() {
        final DateFormat formatter = new SimpleDateFormat("HH:mm:ss");
        formatter.setTimeZone(TimeZone.getTimeZone("UTC"));
        if (timer == null) {
            timer = ActorSystem.system().actorOf(Props.create(() -> new TimerActor(300)), "calls/timer");
            timer.send(new TimerActor.Register((currentTime, timeFromRegister) -> {
                if (getActivity() != null) {
                    getActivity().runOnUiThread(() -> {
                        if (currentState == CallState.IN_PROGRESS) {
                            statusTV.setText(formatter.format(new Date(timeFromRegister)));
                        }
                    });
                }
            }, TIMER_ID));
        }
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
        if (requestCode == PERMISSIONS_REQUEST_FOR_CALL) {
            if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
            // Permission  granted
            } else {
                messenger().endCall(callId);
            }
        }
    }

    protected void initIncoming() {
        getActivity().getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON | WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON);
        answerContainer.setVisibility(View.VISIBLE);
        endCallContainer.setVisibility(View.GONE);
        new Thread(() -> {
            try {
                Thread.sleep(1100);
                Uri notification = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_RINGTONE);
                ringtone = RingtoneManager.getRingtone(getActivity(), notification);
                if (getActivity() != null & answerContainer.getVisibility() == View.VISIBLE && currentState == CallState.RINGING) {
                    if (ringtone != null) {
                        ringtone.play();
                    }
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();
    }

    protected void onAnswer() {
        endCallContainer.setVisibility(View.VISIBLE);
        answerContainer.setVisibility(View.GONE);
        if (ringtone != null) {
            ringtone.stop();
        }
        messenger().answerCall(callId);
    }

    protected void doEndCall() {
        messenger().endCall(callId);
        onCallEnd();
    }

    public void enableWakeLock() {
        powerManager = (PowerManager) getActivity().getSystemService(Context.POWER_SERVICE);
        wakeLock = powerManager.newWakeLock(field, getActivity().getLocalClreplacedName());
        if (!wakeLock.isHeld()) {
            wakeLock.acquire();
        }
    }

    public void onConnected() {
        vibrate = false;
        v.cancel();
        v.vibrate(200);
    }

    public void onCallEnd() {
        audioManager.setSpeakerphoneOn(false);
        vibrate = false;
        if (ringtone != null) {
            ringtone.stop();
        }
        if (v != null) {
            v.cancel();
        }
        if (timer != null) {
            timer.send(PoisonPill.INSTANCE);
        }
        manager.cancel(NOTIFICATION_ID);
        if (getActivity() != null) {
            getActivity().finish();
        }
    }

    public void disableWakeLock() {
        if (wakeLock != null) {
            if (wakeLock.isHeld()) {
                wakeLock.release();
            }
        }
    }

    @Override
    public void onResume() {
        super.onResume();
        enableWakeLock();
        // 
        // Bind State
        // 
        bind(call.getState(), (val, valueModel) -> {
            if (currentState != val) {
                currentState = val;
                switch(val) {
                    case RINGING:
                        if (call.isOutgoing()) {
                            statusTV.setText(R.string.call_outgoing);
                        } else {
                            statusTV.setText(R.string.call_incoming);
                            toggleSpeaker(speaker, speakerTV, true);
                            initIncoming();
                        }
                        break;
                    case CONNECTING:
                        statusTV.setText(R.string.call_connecting);
                        break;
                    case IN_PROGRESS:
                        toggleSpeaker(speaker, speakerTV, false);
                        onConnected();
                        startTimer();
                        break;
                    case ENDED:
                        statusTV.setText(R.string.call_ended);
                        onCallEnd();
                        break;
                }
            }
        });
        // 
        // Is Muted
        // 
        bind(call.getIsAudioEnabled(), (val, valueModel) -> {
            if (getActivity() != null) {
                if (!val) {
                    muteCallTv.setTextColor(getResources().getColor(R.color.picker_grey));
                    muteCall.setTint(getResources().getColor(R.color.picker_grey));
                } else {
                    muteCallTv.setTextColor(Color.WHITE);
                    muteCall.setTint(Color.WHITE);
                }
            }
        });
        // 
        // Bind Video Streams
        // 
        if (peer.getPeerType() == PeerType.PRIVATE) {
            // 
            // Video Button
            // 
            bind(call.getIsVideoEnabled(), (val, valueModel) -> {
                if (val) {
                    videoTv.setTextColor(Color.WHITE);
                    videoIcon.setTint(Color.WHITE);
                } else {
                    videoTv.setTextColor(getResources().getColor(R.color.picker_grey));
                    videoIcon.setTint(getResources().getColor(R.color.picker_grey));
                }
            });
            // 
            // Bind Own Stream
            // 
            ACTIVITY_BINDER.bind(call.getOwnVideoTracks(), (videoTracks, valueModel) -> {
                boolean isNeedUnbind = true;
                if (videoTracks.size() > 0) {
                    if (!isLocalViewConfigured) {
                        localVideoView.init(eglContext.getEglBaseContext(), null);
                        isLocalViewConfigured = true;
                    }
                    VideoTrack videoTrack = ((AndroidVideoTrack) videoTracks.get(0)).getVideoTrack();
                    if (videoTrack != localTrack) {
                        if (localTrack != null) {
                            localTrack.removeRenderer(localRender);
                            localRender.dispose();
                        }
                        localTrack = videoTrack;
                        localRender = new VideoRenderer(localVideoView);
                        localTrack.addRenderer(localRender);
                        localVideoView.setVisibility(View.VISIBLE);
                    }
                    isNeedUnbind = false;
                }
                if (isNeedUnbind) {
                    if (localTrack != null) {
                        localTrack.removeRenderer(localRender);
                        localTrack = null;
                        localRender.dispose();
                        localRender = null;
                    }
                    if (isLocalViewConfigured) {
                        localVideoView.release();
                        isLocalViewConfigured = false;
                    }
                    localVideoView.setVisibility(View.INVISIBLE);
                }
            });
            // 
            // Bind Their Stream
            // 
            ACTIVITY_BINDER.bind(call.getTheirVideoTracks(), (videoTracks, valueModel) -> {
                boolean isNeedUnbind = true;
                if (videoTracks.size() > 0) {
                    if (!isRemoteViewConfigured) {
                        remoteVideoView.init(eglContext.getEglBaseContext(), null);
                        isRemoteViewConfigured = true;
                    }
                    VideoTrack videoTrack = ((AndroidVideoTrack) videoTracks.get(0)).getVideoTrack();
                    if (videoTrack != remoteTrack) {
                        if (remoteTrack != null) {
                            remoteTrack.removeRenderer(remoteRender);
                            remoteRender.dispose();
                        }
                        remoteTrack = videoTrack;
                        remoteRender = new VideoRenderer(remoteVideoView);
                        remoteTrack.addRenderer(remoteRender);
                        remoteVideoView.setVisibility(View.VISIBLE);
                        avatarView.setVisibility(View.INVISIBLE);
                        nameTV.setVisibility(View.INVISIBLE);
                    }
                    isNeedUnbind = false;
                }
                if (isNeedUnbind) {
                    if (remoteTrack != null) {
                        remoteTrack.removeRenderer(remoteRender);
                        remoteTrack = null;
                        remoteRender.dispose();
                        remoteRender = null;
                    }
                    if (isRemoteViewConfigured) {
                        remoteVideoView.release();
                        isRemoteViewConfigured = false;
                    }
                    remoteVideoView.setVisibility(View.INVISIBLE);
                    avatarView.setVisibility(View.VISIBLE);
                    nameTV.setVisibility(View.VISIBLE);
                }
            });
        } else {
            videoTv.setTextColor(getResources().getColor(R.color.picker_grey));
            videoIcon.setTint(getResources().getColor(R.color.picker_grey));
        }
        // 
        // Hide "in progress" notification
        // 
        manager.cancel(NOTIFICATION_ID);
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        if (item.gereplacedemId() == R.id.members) {
            switchAvatarMembers();
        }
        return super.onOptionsItemSelected(item);
    }

    @Override
    public void onPause() {
        super.onPause();
        disableWakeLock();
        if (peer.getPeerType() == PeerType.PRIVATE) {
            // Release Local Viewport
            if (localTrack != null) {
                localTrack.removeRenderer(localRender);
                localRender.dispose();
                localRender = null;
                localTrack = null;
            }
            if (isLocalViewConfigured) {
                localVideoView.release();
                isLocalViewConfigured = false;
            }
            // Release Remote Viewport
            if (remoteTrack != null) {
                remoteTrack.removeRenderer(remoteRender);
                remoteRender.dispose();
                remoteRender = null;
                remoteTrack = null;
            }
            if (isRemoteViewConfigured) {
                remoteVideoView.release();
                isRemoteViewConfigured = false;
            }
        }
        // 
        // Unbind call streams
        // 
        ACTIVITY_BINDER.unbindAll();
        // 
        // Show In Progress
        // 
        if (call != null && call.getState().get() != CallState.ENDED) {
            final NotificationCompat.Builder builder = new NotificationCompat.Builder(getActivity());
            builder.setAutoCancel(true);
            builder.setSmallIcon(R.drawable.ic_app_notify);
            builder.setPriority(NotificationCompat.PRIORITY_MAX);
            builder.setContentreplacedle(getActivity().getString(R.string.call_notification));
            Intent intent = new Intent(getActivity(), CallActivity.clreplaced);
            intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
            intent.putExtra("callId", callId);
            builder.setContentIntent(PendingIntent.getActivity(getActivity(), 0, intent, PendingIntent.FLAG_UPDATE_CURRENT));
            Notification n = builder.build();
            n.flags += Notification.FLAG_ONGOING_EVENT;
            manager.notify(NOTIFICATION_ID, n);
        }
    }

    clreplaced CallMembersAdapter extends HolderAdapter<CallMember> {

        protected ArrayList<CallMember> members;

        protected CallMembersAdapter(Context context, final ValueModel<ArrayList<CallMember>> members) {
            super(context);
            this.members = members.get();
            members.subscribe((val, valueModel) -> {
                CallMembersAdapter.this.members = val;
                notifyDataSetChanged();
                Log.d("STATUS CHANGED", val.toString());
            });
        }

        @Override
        public int getCount() {
            return members.size();
        }

        @Override
        public CallMember gereplacedem(int position) {
            return members.get(position);
        }

        @Override
        public long gereplacedemId(int position) {
            return members.get(position).getUid();
        }

        @Override
        protected ViewHolder<CallMember> createHolder(CallMember obj) {
            return new MemberHolder();
        }

        protected clreplaced MemberHolder extends ViewHolder<CallMember> {

            CallMember data;

            protected TextView userName;

            protected TextView status;

            protected AvatarView avatarView;

            @Override
            public View init(final CallMember data, ViewGroup viewGroup, Context context) {
                View res = ((Activity) context).getLayoutInflater().inflate(R.layout.fragment_call_member_item, viewGroup, false);
                userName = (TextView) res.findViewById(R.id.name);
                userName.setTextColor(ActorSDK.sharedActor().style.getTextPrimaryColor());
                status = (TextView) res.findViewById(R.id.status);
                status.setTextColor(ActorSDK.sharedActor().style.getTextSecondaryColor());
                avatarView = (AvatarView) res.findViewById(R.id.avatar);
                avatarView.init(Screen.dp(35), 18);
                this.data = data;
                return res;
            }

            @Override
            public void bind(CallMember data, int position, Context context) {
                UserVM user = users().get(data.getUid());
                this.data = data;
                avatarView.bind(user);
                userName.setText(user.getName().get());
                status.setText(data.getState().name());
            }

            @Override
            public void unbind(boolean full) {
                if (full) {
                    avatarView.unbind();
                }
            }
        }
    }
}

16 View Complete Implementation : CallActivity.java
Copyright GNU General Public License v3.0
Author : Jhuster
public clreplaced CallActivity extends AppCompatActivity {

    private static final int VIDEO_RESOLUTION_WIDTH = 1280;

    private static final int VIDEO_RESOLUTION_HEIGHT = 720;

    private static final int VIDEO_FPS = 30;

    private TextView mLogcatView;

    private Button mStartCallBtn;

    private Button mEndCallBtn;

    private static final String TAG = "CallActivity";

    public static final String VIDEO_TRACK_ID = "ARDAMSv0";

    public static final String AUDIO_TRACK_ID = "ARDAMSa0";

    private EglBase mRootEglBase;

    private PeerConnection mPeerConnection;

    private PeerConnectionFactory mPeerConnectionFactory;

    private SurfaceTextureHelper mSurfaceTextureHelper;

    private SurfaceViewRenderer mLocalSurfaceView;

    private SurfaceViewRenderer mRemoteSurfaceView;

    private VideoTrack mVideoTrack;

    private AudioTrack mAudioTrack;

    private VideoCapturer mVideoCapturer;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_call);
        mLogcatView = findViewById(R.id.LogcatView);
        mStartCallBtn = findViewById(R.id.StartCallButton);
        mEndCallBtn = findViewById(R.id.EndCallButton);
        RTCSignalClient.getInstance().setSignalEventListener(mOnSignalEventListener);
        String serverAddr = getIntent().getStringExtra("ServerAddr");
        String roomName = getIntent().getStringExtra("RoomName");
        RTCSignalClient.getInstance().joinRoom(serverAddr, UUID.randomUUID().toString(), roomName);
        mRootEglBase = EglBase.create();
        mLocalSurfaceView = findViewById(R.id.LocalSurfaceView);
        mRemoteSurfaceView = findViewById(R.id.RemoteSurfaceView);
        mLocalSurfaceView.init(mRootEglBase.getEglBaseContext(), null);
        mLocalSurfaceView.setScalingType(RendererCommon.ScalingType.SCALE_ASPECT_FILL);
        mLocalSurfaceView.setMirror(true);
        mLocalSurfaceView.setEnableHardwareScaler(false);
        mRemoteSurfaceView.init(mRootEglBase.getEglBaseContext(), null);
        mRemoteSurfaceView.setScalingType(RendererCommon.ScalingType.SCALE_ASPECT_FILL);
        mRemoteSurfaceView.setMirror(true);
        mRemoteSurfaceView.setEnableHardwareScaler(true);
        mRemoteSurfaceView.setZOrderMediaOverlay(true);
        ProxyVideoSink videoSink = new ProxyVideoSink();
        videoSink.setTarget(mLocalSurfaceView);
        mPeerConnectionFactory = createPeerConnectionFactory(this);
        // NOTE: this _must_ happen while PeerConnectionFactory is alive!
        Logging.enableLogToDebugOutput(Logging.Severity.LS_VERBOSE);
        mVideoCapturer = createVideoCapturer();
        mSurfaceTextureHelper = SurfaceTextureHelper.create("CaptureThread", mRootEglBase.getEglBaseContext());
        VideoSource videoSource = mPeerConnectionFactory.createVideoSource(false);
        mVideoCapturer.initialize(mSurfaceTextureHelper, getApplicationContext(), videoSource.getCapturerObserver());
        mVideoTrack = mPeerConnectionFactory.createVideoTrack(VIDEO_TRACK_ID, videoSource);
        mVideoTrack.setEnabled(true);
        mVideoTrack.addSink(videoSink);
        AudioSource audioSource = mPeerConnectionFactory.createAudioSource(new MediaConstraints());
        mAudioTrack = mPeerConnectionFactory.createAudioTrack(AUDIO_TRACK_ID, audioSource);
        mAudioTrack.setEnabled(true);
    }

    @Override
    protected void onResume() {
        super.onResume();
        mVideoCapturer.startCapture(VIDEO_RESOLUTION_WIDTH, VIDEO_RESOLUTION_HEIGHT, VIDEO_FPS);
    }

    @Override
    protected void onPause() {
        super.onPause();
        try {
            mVideoCapturer.stopCapture();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        doEndCall();
        mLocalSurfaceView.release();
        mRemoteSurfaceView.release();
        mVideoCapturer.dispose();
        mSurfaceTextureHelper.dispose();
        PeerConnectionFactory.stopInternalTracingCapture();
        PeerConnectionFactory.shutdownInternalTracer();
        RTCSignalClient.getInstance().leaveRoom();
    }

    public static clreplaced ProxyVideoSink implements VideoSink {

        private VideoSink mTarget;

        @Override
        synchronized public void onFrame(VideoFrame frame) {
            if (mTarget == null) {
                Log.d(TAG, "Dropping frame in proxy because target is null.");
                return;
            }
            mTarget.onFrame(frame);
        }

        synchronized void setTarget(VideoSink target) {
            this.mTarget = target;
        }
    }

    public static clreplaced SimpleSdpObserver implements SdpObserver {

        @Override
        public void onCreateSuccess(SessionDescription sessionDescription) {
            Log.i(TAG, "SdpObserver: onCreateSuccess !");
        }

        @Override
        public void onSetSuccess() {
            Log.i(TAG, "SdpObserver: onSetSuccess");
        }

        @Override
        public void onCreateFailure(String msg) {
            Log.e(TAG, "SdpObserver onCreateFailure: " + msg);
        }

        @Override
        public void onSetFailure(String msg) {
            Log.e(TAG, "SdpObserver onSetFailure: " + msg);
        }
    }

    public void onClickStartCallButton(View v) {
        doStartCall();
    }

    public void onClickEndCallButton(View v) {
        doEndCall();
    }

    private void updateCallState(boolean idle) {
        runOnUiThread(new Runnable() {

            @Override
            public void run() {
                if (idle) {
                    mStartCallBtn.setVisibility(View.VISIBLE);
                    mEndCallBtn.setVisibility(View.GONE);
                    mRemoteSurfaceView.setVisibility(View.GONE);
                } else {
                    mStartCallBtn.setVisibility(View.GONE);
                    mEndCallBtn.setVisibility(View.VISIBLE);
                    mRemoteSurfaceView.setVisibility(View.VISIBLE);
                }
            }
        });
    }

    public void doStartCall() {
        logcatOnUI("Start Call, Wait ...");
        if (mPeerConnection == null) {
            mPeerConnection = createPeerConnection();
        }
        MediaConstraints mediaConstraints = new MediaConstraints();
        mediaConstraints.mandatory.add(new MediaConstraints.KeyValuePair("OfferToReceiveAudio", "true"));
        mediaConstraints.mandatory.add(new MediaConstraints.KeyValuePair("OfferToReceiveVideo", "true"));
        mediaConstraints.optional.add(new MediaConstraints.KeyValuePair("DtlsSrtpKeyAgreement", "true"));
        mPeerConnection.createOffer(new SimpleSdpObserver() {

            @Override
            public void onCreateSuccess(SessionDescription sessionDescription) {
                Log.i(TAG, "Create local offer success: \n" + sessionDescription.description);
                mPeerConnection.setLocalDescription(new SimpleSdpObserver(), sessionDescription);
                JSONObject message = new JSONObject();
                try {
                    message.put("userId", RTCSignalClient.getInstance().getUserId());
                    message.put("msgType", RTCSignalClient.MESSAGE_TYPE_OFFER);
                    message.put("sdp", sessionDescription.description);
                    RTCSignalClient.getInstance().sendMessage(message);
                } catch (JSONException e) {
                    e.printStackTrace();
                }
            }
        }, mediaConstraints);
    }

    public void doEndCall() {
        logcatOnUI("End Call, Wait ...");
        hanup();
        JSONObject message = new JSONObject();
        try {
            message.put("userId", RTCSignalClient.getInstance().getUserId());
            message.put("msgType", RTCSignalClient.MESSAGE_TYPE_HANGUP);
            RTCSignalClient.getInstance().sendMessage(message);
        } catch (JSONException e) {
            e.printStackTrace();
        }
    }

    public void doAnswerCall() {
        logcatOnUI("Answer Call, Wait ...");
        if (mPeerConnection == null) {
            mPeerConnection = createPeerConnection();
        }
        MediaConstraints sdpMediaConstraints = new MediaConstraints();
        Log.i(TAG, "Create answer ...");
        mPeerConnection.createAnswer(new SimpleSdpObserver() {

            @Override
            public void onCreateSuccess(SessionDescription sessionDescription) {
                Log.i(TAG, "Create answer success !");
                mPeerConnection.setLocalDescription(new SimpleSdpObserver(), sessionDescription);
                JSONObject message = new JSONObject();
                try {
                    message.put("userId", RTCSignalClient.getInstance().getUserId());
                    message.put("msgType", RTCSignalClient.MESSAGE_TYPE_ANSWER);
                    message.put("sdp", sessionDescription.description);
                    RTCSignalClient.getInstance().sendMessage(message);
                } catch (JSONException e) {
                    e.printStackTrace();
                }
            }
        }, sdpMediaConstraints);
        updateCallState(false);
    }

    private void hanup() {
        logcatOnUI("Hanup Call, Wait ...");
        if (mPeerConnection == null) {
            return;
        }
        mPeerConnection.close();
        mPeerConnection = null;
        logcatOnUI("Hanup Done.");
        updateCallState(true);
    }

    public PeerConnection createPeerConnection() {
        Log.i(TAG, "Create PeerConnection ...");
        PeerConnection.RTCConfiguration configuration = new PeerConnection.RTCConfiguration(new ArrayList<>());
        PeerConnection connection = mPeerConnectionFactory.createPeerConnection(configuration, mPeerConnectionObserver);
        if (connection == null) {
            Log.e(TAG, "Failed to createPeerConnection !");
            return null;
        }
        connection.addTrack(mVideoTrack);
        connection.addTrack(mAudioTrack);
        return connection;
    }

    public PeerConnectionFactory createPeerConnectionFactory(Context context) {
        final VideoEncoderFactory encoderFactory;
        final VideoDecoderFactory decoderFactory;
        encoderFactory = new DefaultVideoEncoderFactory(mRootEglBase.getEglBaseContext(), false, /* enableIntelVp8Encoder */
        true);
        decoderFactory = new DefaultVideoDecoderFactory(mRootEglBase.getEglBaseContext());
        PeerConnectionFactory.initialize(PeerConnectionFactory.InitializationOptions.builder(context).setEnableInternalTracer(true).createInitializationOptions());
        PeerConnectionFactory.Builder builder = PeerConnectionFactory.builder().setVideoEncoderFactory(encoderFactory).setVideoDecoderFactory(decoderFactory);
        builder.setOptions(null);
        return builder.createPeerConnectionFactory();
    }

    /*
     * Read more about Camera2 here
     * https://developer.android.com/reference/android/hardware/camera2/package-summary.html
     **/
    private VideoCapturer createVideoCapturer() {
        if (Camera2Enumerator.isSupported(this)) {
            return createCameraCapturer(new Camera2Enumerator(this));
        } else {
            return createCameraCapturer(new Camera1Enumerator(true));
        }
    }

    private VideoCapturer createCameraCapturer(CameraEnumerator enumerator) {
        final String[] deviceNames = enumerator.getDeviceNames();
        // First, try to find front facing camera
        Log.d(TAG, "Looking for front facing cameras.");
        for (String deviceName : deviceNames) {
            if (enumerator.isFrontFacing(deviceName)) {
                Logging.d(TAG, "Creating front facing camera capturer.");
                VideoCapturer videoCapturer = enumerator.createCapturer(deviceName, null);
                if (videoCapturer != null) {
                    return videoCapturer;
                }
            }
        }
        // Front facing camera not found, try something else
        Log.d(TAG, "Looking for other cameras.");
        for (String deviceName : deviceNames) {
            if (!enumerator.isFrontFacing(deviceName)) {
                Logging.d(TAG, "Creating other camera capturer.");
                VideoCapturer videoCapturer = enumerator.createCapturer(deviceName, null);
                if (videoCapturer != null) {
                    return videoCapturer;
                }
            }
        }
        return null;
    }

    private PeerConnection.Observer mPeerConnectionObserver = new PeerConnection.Observer() {

        @Override
        public void onSignalingChange(PeerConnection.SignalingState signalingState) {
            Log.i(TAG, "onSignalingChange: " + signalingState);
        }

        @Override
        public void onIceConnectionChange(PeerConnection.IceConnectionState iceConnectionState) {
            Log.i(TAG, "onIceConnectionChange: " + iceConnectionState);
        }

        @Override
        public void onIceConnectionReceivingChange(boolean b) {
            Log.i(TAG, "onIceConnectionChange: " + b);
        }

        @Override
        public void onIceGatheringChange(PeerConnection.IceGatheringState iceGatheringState) {
            Log.i(TAG, "onIceGatheringChange: " + iceGatheringState);
        }

        @Override
        public void onIceCandidate(IceCandidate iceCandidate) {
            Log.i(TAG, "onIceCandidate: " + iceCandidate);
            try {
                JSONObject message = new JSONObject();
                message.put("userId", RTCSignalClient.getInstance().getUserId());
                message.put("msgType", RTCSignalClient.MESSAGE_TYPE_CANDIDATE);
                message.put("label", iceCandidate.sdpMLineIndex);
                message.put("id", iceCandidate.sdpMid);
                message.put("candidate", iceCandidate.sdp);
                RTCSignalClient.getInstance().sendMessage(message);
            } catch (JSONException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void onIceCandidatesRemoved(IceCandidate[] iceCandidates) {
            for (int i = 0; i < iceCandidates.length; i++) {
                Log.i(TAG, "onIceCandidatesRemoved: " + iceCandidates[i]);
            }
            mPeerConnection.removeIceCandidates(iceCandidates);
        }

        @Override
        public void onAddStream(MediaStream mediaStream) {
            Log.i(TAG, "onAddStream: " + mediaStream.videoTracks.size());
        }

        @Override
        public void onRemoveStream(MediaStream mediaStream) {
            Log.i(TAG, "onRemoveStream");
        }

        @Override
        public void onDataChannel(DataChannel dataChannel) {
            Log.i(TAG, "onDataChannel");
        }

        @Override
        public void onRenegotiationNeeded() {
            Log.i(TAG, "onRenegotiationNeeded");
        }

        @Override
        public void onAddTrack(RtpReceiver rtpReceiver, MediaStream[] mediaStreams) {
            MediaStreamTrack track = rtpReceiver.track();
            if (track instanceof VideoTrack) {
                Log.i(TAG, "onAddVideoTrack");
                VideoTrack remoteVideoTrack = (VideoTrack) track;
                remoteVideoTrack.setEnabled(true);
                ProxyVideoSink videoSink = new ProxyVideoSink();
                videoSink.setTarget(mRemoteSurfaceView);
                remoteVideoTrack.addSink(videoSink);
            }
        }
    };

    private RTCSignalClient.OnSignalEventListener mOnSignalEventListener = new RTCSignalClient.OnSignalEventListener() {

        @Override
        public void onConnected() {
            logcatOnUI("Signal Server Connected !");
        }

        @Override
        public void onConnecting() {
            logcatOnUI("Signal Server Connecting !");
        }

        @Override
        public void onDisconnected() {
            logcatOnUI("Signal Server Connecting !");
        }

        @Override
        public void onRemoteUserJoined(String userId) {
            logcatOnUI("Remote User Joined: " + userId);
        }

        @Override
        public void onRemoteUserLeft(String userId) {
            logcatOnUI("Remote User Leaved: " + userId);
        }

        @Override
        public void onBroadcastReceived(JSONObject message) {
            Log.i(TAG, "onBroadcastReceived: " + message);
            try {
                String userId = message.getString("userId");
                int type = message.getInt("msgType");
                switch(type) {
                    case RTCSignalClient.MESSAGE_TYPE_OFFER:
                        onRemoteOfferReceived(userId, message);
                        break;
                    case RTCSignalClient.MESSAGE_TYPE_ANSWER:
                        onRemoteAnswerReceived(userId, message);
                        break;
                    case RTCSignalClient.MESSAGE_TYPE_CANDIDATE:
                        onRemoteCandidateReceived(userId, message);
                        break;
                    case RTCSignalClient.MESSAGE_TYPE_HANGUP:
                        onRemoteHangup(userId);
                        break;
                }
            } catch (JSONException e) {
                e.printStackTrace();
            }
        }

        private void onRemoteOfferReceived(String userId, JSONObject message) {
            logcatOnUI("Receive Remote Call ...");
            if (mPeerConnection == null) {
                mPeerConnection = createPeerConnection();
            }
            try {
                String description = message.getString("sdp");
                mPeerConnection.setRemoteDescription(new SimpleSdpObserver(), new SessionDescription(SessionDescription.Type.OFFER, description));
                doAnswerCall();
            } catch (JSONException e) {
                e.printStackTrace();
            }
        }

        private void onRemoteAnswerReceived(String userId, JSONObject message) {
            logcatOnUI("Receive Remote Answer ...");
            try {
                String description = message.getString("sdp");
                mPeerConnection.setRemoteDescription(new SimpleSdpObserver(), new SessionDescription(SessionDescription.Type.ANSWER, description));
            } catch (JSONException e) {
                e.printStackTrace();
            }
            updateCallState(false);
        }

        private void onRemoteCandidateReceived(String userId, JSONObject message) {
            logcatOnUI("Receive Remote Candidate ...");
            try {
                IceCandidate remoteIceCandidate = new IceCandidate(message.getString("id"), message.getInt("label"), message.getString("candidate"));
                mPeerConnection.addIceCandidate(remoteIceCandidate);
            } catch (JSONException e) {
                e.printStackTrace();
            }
        }

        private void onRemoteHangup(String userId) {
            logcatOnUI("Receive Remote Hanup Event ...");
            hanup();
        }
    };

    private void logcatOnUI(String msg) {
        Log.i(TAG, msg);
        runOnUiThread(new Runnable() {

            @Override
            public void run() {
                String output = mLogcatView.getText() + "\n" + msg;
                mLogcatView.setText(output);
            }
        });
    }
}

16 View Complete Implementation : MainActivity.java
Copyright Apache License 2.0
Author : rome753
public clreplaced MainActivity extends AppCompatActivity {

    PeerConnectionFactory peerConnectionFactory;

    PeerConnection peerConnectionLocal;

    PeerConnection peerConnectionRemote;

    SurfaceViewRenderer localView;

    SurfaceViewRenderer remoteView;

    MediaStream mediaStreamLocal;

    MediaStream mediaStreamRemote;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        EglBase.Context eglBaseContext = EglBase.create().getEglBaseContext();
        // create PeerConnectionFactory
        PeerConnectionFactory.initialize(PeerConnectionFactory.InitializationOptions.builder(this).createInitializationOptions());
        PeerConnectionFactory.Options options = new PeerConnectionFactory.Options();
        DefaultVideoEncoderFactory defaultVideoEncoderFactory = new DefaultVideoEncoderFactory(eglBaseContext, true, true);
        DefaultVideoDecoderFactory defaultVideoDecoderFactory = new DefaultVideoDecoderFactory(eglBaseContext);
        peerConnectionFactory = PeerConnectionFactory.builder().setOptions(options).setVideoEncoderFactory(defaultVideoEncoderFactory).setVideoDecoderFactory(defaultVideoDecoderFactory).createPeerConnectionFactory();
        SurfaceTextureHelper surfaceTextureHelper = SurfaceTextureHelper.create("CaptureThread", eglBaseContext);
        // create VideoCapturer
        VideoCapturer videoCapturer = createCameraCapturer(true);
        VideoSource videoSource = peerConnectionFactory.createVideoSource(videoCapturer.isScreencast());
        videoCapturer.initialize(surfaceTextureHelper, getApplicationContext(), videoSource.getCapturerObserver());
        videoCapturer.startCapture(480, 640, 30);
        localView = findViewById(R.id.localView);
        localView.setMirror(true);
        localView.init(eglBaseContext, null);
        // create VideoTrack
        VideoTrack videoTrack = peerConnectionFactory.createVideoTrack("100", videoSource);
        // // display in localView
        // videoTrack.addSink(localView);
        SurfaceTextureHelper remoteSurfaceTextureHelper = SurfaceTextureHelper.create("RemoteCaptureThread", eglBaseContext);
        // create VideoCapturer
        VideoCapturer remoteVideoCapturer = createCameraCapturer(false);
        VideoSource remoteVideoSource = peerConnectionFactory.createVideoSource(remoteVideoCapturer.isScreencast());
        remoteVideoCapturer.initialize(remoteSurfaceTextureHelper, getApplicationContext(), remoteVideoSource.getCapturerObserver());
        remoteVideoCapturer.startCapture(480, 640, 30);
        remoteView = findViewById(R.id.remoteView);
        remoteView.setMirror(false);
        remoteView.init(eglBaseContext, null);
        // create VideoTrack
        VideoTrack remoteVideoTrack = peerConnectionFactory.createVideoTrack("102", remoteVideoSource);
        // // display in remoteView
        // remoteVideoTrack.addSink(remoteView);
        mediaStreamLocal = peerConnectionFactory.createLocalMediaStream("mediaStreamLocal");
        mediaStreamLocal.addTrack(videoTrack);
        mediaStreamRemote = peerConnectionFactory.createLocalMediaStream("mediaStreamRemote");
        mediaStreamRemote.addTrack(remoteVideoTrack);
        call(mediaStreamLocal, mediaStreamRemote);
    }

    private void call(MediaStream localMediaStream, MediaStream remoteMediaStream) {
        List<PeerConnection.IceServer> iceServers = new ArrayList<>();
        peerConnectionLocal = peerConnectionFactory.createPeerConnection(iceServers, new PeerConnectionAdapter("localconnection") {

            @Override
            public void onIceCandidate(IceCandidate iceCandidate) {
                super.onIceCandidate(iceCandidate);
                peerConnectionRemote.addIceCandidate(iceCandidate);
            }

            @Override
            public void onAddStream(MediaStream mediaStream) {
                super.onAddStream(mediaStream);
                VideoTrack remoteVideoTrack = mediaStream.videoTracks.get(0);
                runOnUiThread(() -> {
                    remoteVideoTrack.addSink(localView);
                });
            }
        });
        peerConnectionRemote = peerConnectionFactory.createPeerConnection(iceServers, new PeerConnectionAdapter("remoteconnection") {

            @Override
            public void onIceCandidate(IceCandidate iceCandidate) {
                super.onIceCandidate(iceCandidate);
                peerConnectionLocal.addIceCandidate(iceCandidate);
            }

            @Override
            public void onAddStream(MediaStream mediaStream) {
                super.onAddStream(mediaStream);
                VideoTrack localVideoTrack = mediaStream.videoTracks.get(0);
                runOnUiThread(() -> {
                    localVideoTrack.addSink(remoteView);
                });
            }
        });
        peerConnectionLocal.addStream(localMediaStream);
        peerConnectionLocal.createOffer(new SdpAdapter("local offer sdp") {

            @Override
            public void onCreateSuccess(SessionDescription sessionDescription) {
                super.onCreateSuccess(sessionDescription);
                // todo crashed here
                peerConnectionLocal.setLocalDescription(new SdpAdapter("local set local"), sessionDescription);
                peerConnectionRemote.addStream(remoteMediaStream);
                peerConnectionRemote.setRemoteDescription(new SdpAdapter("remote set remote"), sessionDescription);
                peerConnectionRemote.createAnswer(new SdpAdapter("remote answer sdp") {

                    @Override
                    public void onCreateSuccess(SessionDescription sdp) {
                        super.onCreateSuccess(sdp);
                        peerConnectionRemote.setLocalDescription(new SdpAdapter("remote set local"), sdp);
                        peerConnectionLocal.setRemoteDescription(new SdpAdapter("local set remote"), sdp);
                    }
                }, new MediaConstraints());
            }
        }, new MediaConstraints());
    }

    private VideoCapturer createCameraCapturer(boolean isFront) {
        Camera1Enumerator enumerator = new Camera1Enumerator(false);
        final String[] deviceNames = enumerator.getDeviceNames();
        // First, try to find front facing camera
        for (String deviceName : deviceNames) {
            if (isFront ? enumerator.isFrontFacing(deviceName) : enumerator.isBackFacing(deviceName)) {
                VideoCapturer videoCapturer = enumerator.createCapturer(deviceName, null);
                if (videoCapturer != null) {
                    return videoCapturer;
                }
            }
        }
        return null;
    }
}