Skip to content

Commit fe44d5c

Browse files
Add an AudioOnly example to demonstrate how to make OneToOne voice calls.
1 parent 995a765 commit fe44d5c

5 files changed

Lines changed: 329 additions & 0 deletions

File tree

Lines changed: 262 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,262 @@
1+
package io.agora.api.example.examples.basic_video_audio;
2+
3+
import android.content.Context;
4+
import android.os.Bundle;
5+
import android.os.Handler;
6+
import android.util.Log;
7+
import android.view.LayoutInflater;
8+
import android.view.View;
9+
import android.view.ViewGroup;
10+
import android.widget.Button;
11+
import android.widget.EditText;
12+
import android.widget.Toast;
13+
14+
import androidx.annotation.NonNull;
15+
import androidx.annotation.Nullable;
16+
17+
import com.yanzhenjie.permission.AndPermission;
18+
import com.yanzhenjie.permission.runtime.Permission;
19+
20+
import io.agora.api.example.R;
21+
import io.agora.api.example.annotation.Example;
22+
import io.agora.api.example.common.BaseFragment;
23+
import io.agora.rtc.Constants;
24+
import io.agora.rtc.IRtcEngineEventHandler;
25+
import io.agora.rtc.RtcEngine;
26+
27+
@Example(
28+
group = "BASIC VIDEO/AUDIO",
29+
name = "AudioOnly",
30+
actionId = R.id.action_mainFragment_to_audioOnly
31+
)
32+
public class AudioOnly extends BaseFragment implements View.OnClickListener
33+
{
34+
private Button join, mute, speaker;
35+
private EditText et_channel;
36+
37+
private Handler handler;
38+
protected RtcEngine engine;
39+
protected int myUid;
40+
41+
@Override
42+
public void onCreate(@Nullable Bundle savedInstanceState)
43+
{
44+
super.onCreate(savedInstanceState);
45+
handler = new Handler();
46+
}
47+
48+
@Nullable
49+
@Override
50+
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState)
51+
{
52+
View view = inflater.inflate(R.layout.fragment_audio_only, container, false);
53+
initView(view);
54+
return view;
55+
}
56+
57+
protected void initView(View view)
58+
{
59+
join = view.findViewById(R.id.btn_join);
60+
mute = view.findViewById(R.id.btn_mute);
61+
speaker = view.findViewById(R.id.btn_speaker);
62+
et_channel = view.findViewById(R.id.et_channel);
63+
view.findViewById(R.id.btn_join).setOnClickListener(this);
64+
}
65+
66+
@Override
67+
public void onDestroy()
68+
{
69+
super.onDestroy();
70+
// release the RtcEngine
71+
engine.leaveChannel();
72+
handler.post(RtcEngine::destroy);
73+
engine = null;
74+
}
75+
76+
protected void joinChannel(String channelId)
77+
{
78+
Context context = getContext();
79+
if (context == null) return;
80+
81+
try
82+
{
83+
engine = RtcEngine.create(getContext(), getString(R.string.agora_app_id), eventHandler);
84+
/** Sets the channel profile of the Agora RtcEngine.
85+
CHANNEL_PROFILE_COMMUNICATION(0): (Default) The Communication profile.
86+
Use this profile in one-on-one calls or group calls, where all users can talk freely.
87+
CHANNEL_PROFILE_LIVE_BROADCASTING(1): The Live-Broadcast profile. Users in a live-broadcast
88+
channel have a role as either broadcaster or audience. A broadcaster can both send and receive streams;
89+
an audience can only receive streams.*/
90+
engine.setChannelProfile(Constants.CHANNEL_PROFILE_COMMUNICATION);
91+
}
92+
catch (Exception e)
93+
{
94+
e.printStackTrace();
95+
}
96+
97+
/** Allows a user to join a channel.
98+
if you do not specify the uid, we will generate the uid for you*/
99+
int res = engine.joinChannel(getString(R.string.agora_access_token), channelId, "Extra Optional Data", 0);
100+
if (res != 0)
101+
{
102+
// Usually happens with invalid parameters
103+
// Error code description can be found at:
104+
// en: https://docs.agora.io/en/Voice/API%20Reference/java/classio_1_1agora_1_1rtc_1_1_i_rtc_engine_event_handler_1_1_error_code.html
105+
// cn: https://docs.agora.io/cn/Voice/API%20Reference/java/classio_1_1agora_1_1rtc_1_1_i_rtc_engine_event_handler_1_1_error_code.html
106+
showAlert(RtcEngine.getErrorDescription(Math.abs(res)));
107+
return;
108+
}
109+
// Prevent repeated entry
110+
join.setClickable(false);
111+
}
112+
113+
@Override
114+
public void onClick(View v)
115+
{
116+
if (v.getId() == R.id.btn_join)
117+
{
118+
// call when join button hit
119+
String channelId = et_channel.getText().toString();
120+
// Check permission
121+
if (AndPermission.hasPermissions(this, Permission.Group.STORAGE, Permission.Group.MICROPHONE, Permission.Group.CAMERA))
122+
{
123+
joinChannel(channelId);
124+
return;
125+
}
126+
// Request permission
127+
AndPermission.with(this).runtime().permission(
128+
Permission.Group.STORAGE,
129+
Permission.Group.MICROPHONE,
130+
Permission.Group.CAMERA
131+
).onGranted(permissions ->
132+
{
133+
// Permissions Granted
134+
joinChannel(channelId);
135+
}).start();
136+
}
137+
else if(v.getId() == R.id.btn_mute)
138+
{
139+
mute.setActivated(!mute.isActivated());
140+
mute.setText(getString(mute.isActivated() ? R.string.openmicrophone : R.string.closemicrophone));
141+
// Turn off / on the microphone, stop / start local audio collection and push streaming.
142+
engine.muteLocalAudioStream(mute.isActivated());
143+
}
144+
else if(v.getId() == R.id.btn_speaker)
145+
{
146+
speaker.setActivated(!speaker.isActivated());
147+
speaker.setText(getString(speaker.isActivated() ? R.string.earpiece : R.string.speaker));
148+
// Turn off / on the speaker and change the audio playback route.
149+
engine.setEnableSpeakerphone(speaker.isActivated());
150+
}
151+
}
152+
153+
private IRtcEngineEventHandler eventHandler = new IRtcEngineEventHandler()
154+
{
155+
private final String TAG = IRtcEngineEventHandler.class.getSimpleName();
156+
157+
@Override
158+
public void onWarning(int warn)
159+
{
160+
Log.w(TAG, String.format("onWarning code %d message %s", warn, RtcEngine.getErrorDescription(warn)));
161+
}
162+
163+
@Override
164+
public void onError(int err)
165+
{
166+
Log.e(TAG, String.format("onError code %d message %s", err, RtcEngine.getErrorDescription(err)));
167+
}
168+
169+
@Override
170+
public void onJoinChannelSuccess(String channel, int uid, int elapsed)
171+
{
172+
Log.i(TAG, String.format("onJoinChannelSuccess channel %s uid %d", channel, uid));
173+
showLongToast(String.format("onJoinChannelSuccess channel %s uid %d", channel, uid));
174+
myUid = uid;
175+
/** After successfully joining the channel, the user is allowed to switch
176+
* the audio route and switch the microphone.*/
177+
mute.setOnClickListener(AudioOnly.this);
178+
speaker.setOnClickListener(AudioOnly.this);
179+
}
180+
181+
/**Since v2.9.0.
182+
* This callback indicates the state change of the remote audio stream.
183+
* PS: This callback does not work properly when the number of users (in the Communication profile) or
184+
* broadcasters (in the Live-broadcast profile) in the channel exceeds 17.
185+
* @param uid ID of the user whose audio state changes.
186+
* @param state State of the remote audio
187+
* REMOTE_AUDIO_STATE_STOPPED(0): The remote audio is in the default state, probably due
188+
* to REMOTE_AUDIO_REASON_LOCAL_MUTED(3), REMOTE_AUDIO_REASON_REMOTE_MUTED(5),
189+
* or REMOTE_AUDIO_REASON_REMOTE_OFFLINE(7).
190+
* REMOTE_AUDIO_STATE_STARTING(1): The first remote audio packet is received.
191+
* REMOTE_AUDIO_STATE_DECODING(2): The remote audio stream is decoded and plays normally,
192+
* probably due to REMOTE_AUDIO_REASON_NETWORK_RECOVERY(2),
193+
* REMOTE_AUDIO_REASON_LOCAL_UNMUTED(4) or REMOTE_AUDIO_REASON_REMOTE_UNMUTED(6).
194+
* REMOTE_AUDIO_STATE_FROZEN(3): The remote audio is frozen, probably due to
195+
* REMOTE_AUDIO_REASON_NETWORK_CONGESTION(1).
196+
* REMOTE_AUDIO_STATE_FAILED(4): The remote audio fails to start, probably due to
197+
* REMOTE_AUDIO_REASON_INTERNAL(0).
198+
* @param reason The reason of the remote audio state change.
199+
* REMOTE_AUDIO_REASON_INTERNAL(0): Internal reasons.
200+
* REMOTE_AUDIO_REASON_NETWORK_CONGESTION(1): Network congestion.
201+
* REMOTE_AUDIO_REASON_NETWORK_RECOVERY(2): Network recovery.
202+
* REMOTE_AUDIO_REASON_LOCAL_MUTED(3): The local user stops receiving the remote audio
203+
* stream or disables the audio module.
204+
* REMOTE_AUDIO_REASON_LOCAL_UNMUTED(4): The local user resumes receiving the remote audio
205+
* stream or enables the audio module.
206+
* REMOTE_AUDIO_REASON_REMOTE_MUTED(5): The remote user stops sending the audio stream or
207+
* disables the audio module.
208+
* REMOTE_AUDIO_REASON_REMOTE_UNMUTED(6): The remote user resumes sending the audio stream
209+
* or enables the audio module.
210+
* REMOTE_AUDIO_REASON_REMOTE_OFFLINE(7): The remote user leaves the channel.
211+
* @param elapsed Time elapsed (ms) from the local user calling the joinChannel method
212+
* until the SDK triggers this callback.*/
213+
@Override
214+
public void onRemoteAudioStateChanged(int uid, int state, int reason, int elapsed)
215+
{
216+
super.onRemoteAudioStateChanged(uid, state, reason, elapsed);
217+
Log.e(TAG, "onRemoteAudioStateChanged->" + uid + ", state->" + state + ", reason->" + reason);
218+
}
219+
220+
/**Occurs when a remote user (Communication)/host (Live Broadcast) joins the channel.
221+
* @param uid ID of the user whose audio state changes.
222+
* @param elapsed Time delay (ms) from the local user calling joinChannel/setClientRole
223+
* until this callback is triggered.*/
224+
@Override
225+
public void onUserJoined(int uid, int elapsed)
226+
{
227+
super.onUserJoined(uid, elapsed);
228+
Log.e(TAG, "onUserJoined->" + uid);
229+
showLongToast(String.format("user %d joined!", uid));
230+
}
231+
232+
/**Occurs when a remote user (Communication)/host (Live Broadcast) leaves the channel.
233+
* @param uid ID of the user whose audio state changes.
234+
* @param reason Reason why the user goes offline:
235+
* USER_OFFLINE_QUIT(0): The user left the current channel.
236+
* USER_OFFLINE_DROPPED(1): The SDK timed out and the user dropped offline because no data
237+
* packet was received within a certain period of time. If a user quits the
238+
* call and the message is not passed to the SDK (due to an unreliable channel),
239+
* the SDK assumes the user dropped offline.
240+
* USER_OFFLINE_BECOME_AUDIENCE(2): (Live broadcast only.) The client role switched from
241+
* the host to the audience.*/
242+
@Override
243+
public void onUserOffline(int uid, int reason)
244+
{
245+
Log.i(TAG, String.format("user %d offline! reason:%d", uid, reason));
246+
showLongToast(String.format("user %d offline! reason:%d", uid, reason));
247+
}
248+
};
249+
private final void showLongToast(final String msg)
250+
{
251+
if(getActivity() == null)
252+
{return;}
253+
getActivity().runOnUiThread(new Runnable()
254+
{
255+
@Override
256+
public void run()
257+
{
258+
Toast.makeText(getContext().getApplicationContext(), msg, Toast.LENGTH_LONG).show();
259+
}
260+
});
261+
}
262+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
3+
xmlns:tools="http://schemas.android.com/tools"
4+
android:layout_width="match_parent"
5+
android:layout_height="match_parent"
6+
tools:context=".examples.basic_video_audio.AudioOnly">
7+
8+
<androidx.appcompat.widget.AppCompatButton
9+
android:id="@+id/btn_mute"
10+
android:layout_width="wrap_content"
11+
android:layout_height="wrap_content"
12+
android:text="@string/closemicrophone"
13+
android:layout_alignParentEnd="true"
14+
android:layout_above="@id/ll_join"
15+
android:layout_marginEnd="16dp"
16+
android:layout_marginBottom="16dp"/>
17+
18+
<androidx.appcompat.widget.AppCompatButton
19+
android:id="@+id/btn_speaker"
20+
android:layout_width="wrap_content"
21+
android:layout_height="wrap_content"
22+
android:text="@string/speaker"
23+
android:layout_alignParentEnd="true"
24+
android:layout_above="@id/btn_mute"
25+
android:layout_marginEnd="16dp"
26+
android:layout_marginBottom="16dp"/>
27+
28+
<LinearLayout
29+
android:id="@+id/ll_join"
30+
android:layout_width="match_parent"
31+
android:layout_height="wrap_content"
32+
android:layout_alignParentBottom="true"
33+
android:gravity="center_vertical"
34+
android:orientation="horizontal">
35+
36+
<androidx.appcompat.widget.AppCompatEditText
37+
android:id="@+id/et_channel"
38+
android:layout_width="0dp"
39+
android:layout_height="wrap_content"
40+
android:layout_weight="1"
41+
android:hint="@string/channel_id" />
42+
43+
<androidx.appcompat.widget.AppCompatButton
44+
android:id="@+id/btn_join"
45+
android:layout_width="wrap_content"
46+
android:layout_height="wrap_content"
47+
android:text="@string/join" />
48+
49+
</LinearLayout>
50+
51+
</RelativeLayout>

Android/APIExample/app/src/main/res/navigation/nav_graph.xml

100644100755
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,20 @@
1818
<action
1919
android:id="@+id/action_mainFragment_to_RTMPPublishing"
2020
app:destination="@id/RTMPPublishing" />
21+
<action
22+
android:id="@+id/action_mainFragment_to_audioOnly"
23+
app:destination="@id/audioOnly" />
2124
</fragment>
2225
<fragment
2326
android:id="@+id/joinChannel"
2427
android:name="io.agora.api.example.examples.basic_video_audio.JoinChannel"
2528
android:label="JoinChannel"
2629
tools:layout="@layout/fragment_join_channel" />
30+
<fragment
31+
android:id="@+id/audioOnly"
32+
android:name="io.agora.api.example.examples.basic_video_audio.AudioOnly"
33+
android:label="AudioOnly"
34+
tools:layout="@layout/fragment_join_channel" />
2735
<fragment
2836
android:id="@+id/RTMPInjection"
2937
android:name="io.agora.api.example.examples.live_broadcasting.RTMPInjection"

Android/APIExample/app/src/main/res/values-zh/strings.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,8 @@
99
<string name="leave">离开</string>
1010
<string name="stop">停止</string>
1111
<string name="permission">请授予权限</string>
12+
<string name="earpiece">听筒</string>
13+
<string name="speaker">扬声器</string>
14+
<string name="openmicrophone">打开麦克风</string>
15+
<string name="closemicrophone">关闭麦克风</string>
1216
</resources>

Android/APIExample/app/src/main/res/values/strings.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,8 @@
88
<string name="leave">Leave</string>
99
<string name="stop">Stop</string>
1010
<string name="permission">Please granted the request permissions</string>
11+
<string name="earpiece">Earpiece</string>
12+
<string name="speaker">Speaker</string>
13+
<string name="openmicrophone">OpenMicrophone</string>
14+
<string name="closemicrophone">CloseMicrophone</string>
1115
</resources>

0 commit comments

Comments
 (0)