| /* |
| * Copyright (c) 2020 The WebRTC project authors. All Rights Reserved. |
| * |
| * Use of this source code is governed by a BSD-style license |
| * that can be found in the LICENSE file in the root of the source |
| * tree. An additional intellectual property rights grant can be found |
| * in the file PATENTS. All contributing project authors may |
| * be found in the AUTHORS file in the root of the source tree. |
| */ |
| |
| package org.webrtc.examples.androidvoip; |
| |
| import android.Manifest.permission; |
| import android.app.Activity; |
| import android.app.AlertDialog; |
| import android.content.Context; |
| import android.content.pm.PackageManager; |
| import android.os.Bundle; |
| import android.view.Gravity; |
| import android.view.View; |
| import android.widget.AdapterView; |
| import android.widget.ArrayAdapter; |
| import android.widget.Button; |
| import android.widget.EditText; |
| import android.widget.RelativeLayout; |
| import android.widget.ScrollView; |
| import android.widget.Spinner; |
| import android.widget.Switch; |
| import android.widget.TextView; |
| import android.widget.Toast; |
| import android.widget.ToggleButton; |
| import androidx.core.app.ActivityCompat; |
| import androidx.core.content.ContextCompat; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Set; |
| import java.util.stream.Collectors; |
| import org.webrtc.ContextUtils; |
| |
| public class MainActivity extends Activity implements OnVoipClientTaskCompleted { |
| private static final int NUM_SUPPORTED_CODECS = 6; |
| |
| private VoipClient voipClient; |
| private List<String> supportedCodecs; |
| private boolean[] isDecoderSelected; |
| private Set<Integer> selectedDecoders; |
| |
| private Toast toast; |
| private ScrollView scrollView; |
| private TextView localIPAddressTextView; |
| private EditText localPortNumberEditText; |
| private EditText remoteIPAddressEditText; |
| private EditText remotePortNumberEditText; |
| private Spinner encoderSpinner; |
| private Button decoderSelectionButton; |
| private TextView decodersTextView; |
| private ToggleButton sessionButton; |
| private RelativeLayout switchLayout; |
| private Switch sendSwitch; |
| private Switch playoutSwitch; |
| |
| @Override |
| protected void onCreate(Bundle savedInstance) { |
| ContextUtils.initialize(getApplicationContext()); |
| |
| super.onCreate(savedInstance); |
| setContentView(R.layout.activity_main); |
| |
| System.loadLibrary("examples_androidvoip_jni"); |
| |
| voipClient = new VoipClient(getApplicationContext(), this); |
| voipClient.getAndSetUpLocalIPAddress(); |
| voipClient.getAndSetUpSupportedCodecs(); |
| |
| isDecoderSelected = new boolean[NUM_SUPPORTED_CODECS]; |
| selectedDecoders = new HashSet<>(); |
| |
| toast = Toast.makeText(this, "", Toast.LENGTH_SHORT); |
| |
| scrollView = (ScrollView) findViewById(R.id.scroll_view); |
| localIPAddressTextView = (TextView) findViewById(R.id.local_ip_address_text_view); |
| localPortNumberEditText = (EditText) findViewById(R.id.local_port_number_edit_text); |
| remoteIPAddressEditText = (EditText) findViewById(R.id.remote_ip_address_edit_text); |
| remotePortNumberEditText = (EditText) findViewById(R.id.remote_port_number_edit_text); |
| encoderSpinner = (Spinner) findViewById(R.id.encoder_spinner); |
| decoderSelectionButton = (Button) findViewById(R.id.decoder_selection_button); |
| decodersTextView = (TextView) findViewById(R.id.decoders_text_view); |
| sessionButton = (ToggleButton) findViewById(R.id.session_button); |
| switchLayout = (RelativeLayout) findViewById(R.id.switch_layout); |
| sendSwitch = (Switch) findViewById(R.id.start_send_switch); |
| playoutSwitch = (Switch) findViewById(R.id.start_playout_switch); |
| |
| setUpSessionButton(); |
| setUpSendAndPlayoutSwitch(); |
| } |
| |
| private void setUpEncoderSpinner(List<String> supportedCodecs) { |
| ArrayAdapter<String> encoderAdapter = |
| new ArrayAdapter<String>(this, android.R.layout.simple_spinner_item, supportedCodecs); |
| encoderAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); |
| encoderSpinner.setAdapter(encoderAdapter); |
| encoderSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { |
| @Override |
| public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { |
| voipClient.setEncoder((String) parent.getSelectedItem()); |
| } |
| @Override |
| public void onNothingSelected(AdapterView<?> parent) {} |
| }); |
| } |
| |
| private List<String> getSelectedDecoders() { |
| List<String> decoders = new ArrayList<>(); |
| for (int i = 0; i < supportedCodecs.size(); i++) { |
| if (selectedDecoders.contains(i)) { |
| decoders.add(supportedCodecs.get(i)); |
| } |
| } |
| return decoders; |
| } |
| |
| private void setUpDecoderSelectionButton(List<String> supportedCodecs) { |
| decoderSelectionButton.setOnClickListener((view) -> { |
| AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this); |
| dialogBuilder.setTitle(R.string.dialog_title); |
| |
| // Populate multi choice items with supported decoders. |
| String[] supportedCodecsArray = supportedCodecs.toArray(new String[0]); |
| dialogBuilder.setMultiChoiceItems( |
| supportedCodecsArray, isDecoderSelected, (dialog, position, isChecked) -> { |
| if (isChecked) { |
| selectedDecoders.add(position); |
| } else if (!isChecked) { |
| selectedDecoders.remove(position); |
| } |
| }); |
| |
| // "Ok" button. |
| dialogBuilder.setPositiveButton(R.string.ok_label, (dialog, position) -> { |
| List<String> decoders = getSelectedDecoders(); |
| String result = decoders.stream().collect(Collectors.joining(", ")); |
| if (result.isEmpty()) { |
| decodersTextView.setText(R.string.decoders_text_view_default); |
| } else { |
| decodersTextView.setText(result); |
| } |
| voipClient.setDecoders(decoders); |
| }); |
| |
| // "Dismiss" button. |
| dialogBuilder.setNegativeButton( |
| R.string.dismiss_label, (dialog, position) -> { dialog.dismiss(); }); |
| |
| // "Clear All" button. |
| dialogBuilder.setNeutralButton(R.string.clear_all_label, (dialog, position) -> { |
| Arrays.fill(isDecoderSelected, false); |
| selectedDecoders.clear(); |
| decodersTextView.setText(R.string.decoders_text_view_default); |
| }); |
| |
| AlertDialog dialog = dialogBuilder.create(); |
| dialog.show(); |
| }); |
| } |
| |
| private void setUpSessionButton() { |
| sessionButton.setOnCheckedChangeListener((button, isChecked) -> { |
| // Ask for permission on RECORD_AUDIO if not granted. |
| if (ContextCompat.checkSelfPermission(this, permission.RECORD_AUDIO) |
| != PackageManager.PERMISSION_GRANTED) { |
| String[] sList = {permission.RECORD_AUDIO}; |
| ActivityCompat.requestPermissions(this, sList, 1); |
| } |
| |
| if (isChecked) { |
| // Order matters here, addresses have to be set before starting session |
| // before setting codec. |
| voipClient.setLocalAddress(localIPAddressTextView.getText().toString(), |
| Integer.parseInt(localPortNumberEditText.getText().toString())); |
| voipClient.setRemoteAddress(remoteIPAddressEditText.getText().toString(), |
| Integer.parseInt(remotePortNumberEditText.getText().toString())); |
| voipClient.startSession(); |
| voipClient.setEncoder((String) encoderSpinner.getSelectedItem()); |
| voipClient.setDecoders(getSelectedDecoders()); |
| } else { |
| voipClient.stopSession(); |
| } |
| }); |
| } |
| |
| private void setUpSendAndPlayoutSwitch() { |
| sendSwitch.setOnCheckedChangeListener((button, isChecked) -> { |
| if (isChecked) { |
| voipClient.startSend(); |
| } else { |
| voipClient.stopSend(); |
| } |
| }); |
| |
| playoutSwitch.setOnCheckedChangeListener((button, isChecked) -> { |
| if (isChecked) { |
| voipClient.startPlayout(); |
| } else { |
| voipClient.stopPlayout(); |
| } |
| }); |
| } |
| |
| private void setUpIPAddressEditTexts(String localIPAddress) { |
| if (localIPAddress.isEmpty()) { |
| showToast("Please check your network configuration"); |
| } else { |
| localIPAddressTextView.setText(localIPAddress); |
| // By default remote IP address is the same as local IP address. |
| remoteIPAddressEditText.setText(localIPAddress); |
| } |
| } |
| |
| private void showToast(String message) { |
| if (toast != null) { |
| toast.cancel(); |
| toast = Toast.makeText(this, message, Toast.LENGTH_SHORT); |
| toast.setGravity(Gravity.TOP, 0, 200); |
| toast.show(); |
| } |
| } |
| |
| @Override |
| protected void onDestroy() { |
| voipClient.close(); |
| voipClient = null; |
| |
| super.onDestroy(); |
| } |
| |
| @Override |
| public void onGetLocalIPAddressCompleted(String localIPAddress) { |
| runOnUiThread(() -> { setUpIPAddressEditTexts(localIPAddress); }); |
| } |
| |
| @Override |
| public void onGetSupportedCodecsCompleted(List<String> supportedCodecs) { |
| runOnUiThread(() -> { |
| this.supportedCodecs = supportedCodecs; |
| setUpEncoderSpinner(supportedCodecs); |
| setUpDecoderSelectionButton(supportedCodecs); |
| }); |
| } |
| |
| @Override |
| public void onVoipClientInitializationCompleted(boolean isSuccessful) { |
| runOnUiThread(() -> { |
| if (!isSuccessful) { |
| showToast("Error initializing audio device"); |
| } |
| }); |
| } |
| |
| @Override |
| public void onStartSessionCompleted(boolean isSuccessful) { |
| runOnUiThread(() -> { |
| if (isSuccessful) { |
| showToast("Session started"); |
| switchLayout.setVisibility(View.VISIBLE); |
| scrollView.post(() -> { scrollView.fullScroll(ScrollView.FOCUS_DOWN); }); |
| } else { |
| showToast("Failed to start session"); |
| } |
| }); |
| } |
| |
| @Override |
| public void onStopSessionCompleted(boolean isSuccessful) { |
| runOnUiThread(() -> { |
| if (isSuccessful) { |
| showToast("Session stopped"); |
| // Set listeners to null so the checked state can be changed programmatically. |
| sendSwitch.setOnCheckedChangeListener(null); |
| playoutSwitch.setOnCheckedChangeListener(null); |
| sendSwitch.setChecked(false); |
| playoutSwitch.setChecked(false); |
| // Redo the switch listener setup. |
| setUpSendAndPlayoutSwitch(); |
| switchLayout.setVisibility(View.GONE); |
| } else { |
| showToast("Failed to stop session"); |
| } |
| }); |
| } |
| |
| @Override |
| public void onStartSendCompleted(boolean isSuccessful) { |
| runOnUiThread(() -> { |
| if (isSuccessful) { |
| showToast("Started sending"); |
| } else { |
| showToast("Error initializing microphone"); |
| } |
| }); |
| } |
| |
| @Override |
| public void onStopSendCompleted(boolean isSuccessful) { |
| runOnUiThread(() -> { |
| if (isSuccessful) { |
| showToast("Stopped sending"); |
| } else { |
| showToast("Microphone termination failed"); |
| } |
| }); |
| } |
| |
| @Override |
| public void onStartPlayoutCompleted(boolean isSuccessful) { |
| runOnUiThread(() -> { |
| if (isSuccessful) { |
| showToast("Started playout"); |
| } else { |
| showToast("Error initializing speaker"); |
| } |
| }); |
| } |
| |
| @Override |
| public void onStopPlayoutCompleted(boolean isSuccessful) { |
| runOnUiThread(() -> { |
| if (isSuccessful) { |
| showToast("Stopped playout"); |
| } else { |
| showToast("Speaker termination failed"); |
| } |
| }); |
| } |
| |
| @Override |
| public void onUninitializedVoipClient() { |
| runOnUiThread(() -> { showToast("Voip client is uninitialized"); }); |
| } |
| } |