I am new to Agora.io and I am creating an android app for 1 to 1 video calling. Everything is working fine in my app - I can join/leave the channel and even see my own local image through my camera. However, in the call, only the audio is being transmitted and video is not being transmitted.
When I debug the app, I can see that the event 'onFirstRemoteVideoFrame' is not being triggered. I tried changing it to 'onFirstRemoteVideoDecoded'(which I saw in many tutorials but android studio says the method is depreciated) but it is still not working.
Also, please note that when I add the line '-keep class io.agora.**{;}' in my proguard-rules.pro file, it says that it can't find the class. So instead I am using '-keep class io.agora.{*;}'.
Below is the java code for my activity. I am using agora 3.1.3.
package com.guideu.helloagora;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import android.Manifest;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.Log;
import android.view.SurfaceView;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.RelativeLayout;
import android.widget.Toast;
import io.agora.rtc.IRtcEngineEventHandler;
import io.agora.rtc.RtcEngine;
import io.agora.rtc.video.VideoCanvas;
import io.agora.rtc.video.VideoEncoderConfiguration;
public class MainActivity extends AppCompatActivity {
private static final String TAG=MainActivity.class.getSimpleName();
private static final int PERMISSION_REQ_ID=22;
private static final String[] REQUESTED_PERMISSIONS={
Manifest.permission.RECORD_AUDIO,
Manifest.permission.CAMERA,
Manifest.permission.WRITE_EXTERNAL_STORAGE
};
private RtcEngine mRtcEngine; // Agora engine reference
private FrameLayout mLocalContainer;
private RelativeLayout mRemoteContainer;
private SurfaceView mLocalView;
private SurfaceView mRemoteView;
private ImageView mCallBtn;
private ImageView mMuteBtn;
private ImageView mSwitchCameraBtn;
private boolean mCallEnd;
private boolean mMuted;
//Agora engine event handler
private final IRtcEngineEventHandler mRTCHandler=new IRtcEngineEventHandler() {
@Override
public void onJoinChannelSuccess(String channel, final int uid, int elapsed) {
super.onJoinChannelSuccess(channel, uid, elapsed);
runOnUiThread(new Runnable() {
@Override
public void run() {
Log.i("agora","Join channel success, uid: " + (uid & 0xFFFFFFFFL));
}
});
}
@Override
public void onUserOffline(final int uid, int reason) {
super.onUserOffline(uid, reason);
runOnUiThread(new Runnable() {
@Override
public void run() {
Log.i("agora","User offline, uid: " + (uid & 0xFFFFFFFFL));
removeRemoteView();
}
});
}
@Override
public void onFirstRemoteVideoFrame(final int uid, int width, int height, int elapsed) {
super.onFirstRemoteVideoFrame(uid, width, height, elapsed);
runOnUiThread(new Runnable() {
@Override
public void run() {
Log.i("agora","First remote video decoded, uid: " + (uid & 0xFFFFFFFFL));
setupRemoteVideo(uid);
}
});
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initUI();
if(checkSelfPermission(REQUESTED_PERMISSIONS[0],PERMISSION_REQ_ID) &&
checkSelfPermission(REQUESTED_PERMISSIONS[1],PERMISSION_REQ_ID) &&
checkSelfPermission(REQUESTED_PERMISSIONS[2],PERMISSION_REQ_ID)){
//init engine
initEngineAndJoinChannel();
}
}
@Override
protected void onDestroy(){
super.onDestroy();
if(!mCallEnd){
leaveChannel();
}
RtcEngine.destroy();
}
private void initUI(){
mLocalContainer=findViewById(R.id.local_video_view_container);
mRemoteContainer=findViewById(R.id.remote_video_view_container);
mCallBtn=findViewById(R.id.btn_call);
mMuteBtn=findViewById(R.id.btn_mute);
mSwitchCameraBtn=findViewById(R.id.btn_switch_camera);
}
private void initEngineAndJoinChannel(){
//initialize engine
initializeEngine();
//setup video config
setupVideoConfig();
//setup local video
setupLocalVideo();
//join channel
joinChannel();
}
private void initializeEngine(){
try {
mRtcEngine = RtcEngine.create(getBaseContext(), getString(R.string.agora_app_id), mRTCHandler);
}
catch (Exception e){
Log.e(TAG,Log.getStackTraceString(e));
throw new RuntimeException("Need to check rtc sdk init fatal error\n" + Log.getStackTraceString(e));
}
}
private void setupVideoConfig(){
mRtcEngine.enableVideo();
mRtcEngine.setVideoEncoderConfiguration(new VideoEncoderConfiguration(
VideoEncoderConfiguration.VD_640x360,
VideoEncoderConfiguration.FRAME_RATE.FRAME_RATE_FPS_15,
VideoEncoderConfiguration.STANDARD_BITRATE,
VideoEncoderConfiguration.ORIENTATION_MODE.ORIENTATION_MODE_FIXED_PORTRAIT
));
}
private void setupLocalVideo(){
mRtcEngine.enableVideo();
mLocalView=RtcEngine.CreateRendererView(getBaseContext());
mLocalView.setZOrderMediaOverlay(true);
mLocalContainer.addView(mLocalView);
VideoCanvas localVideoCanvas=new VideoCanvas(mLocalView,VideoCanvas.RENDER_MODE_HIDDEN,0);
mRtcEngine.setupLocalVideo(localVideoCanvas);
}
private void setupRemoteVideo(int uid){
/*int count=mRemoteContainer.getChildCount();
View view=null;
for(int i=0;i<count;i++){
View v=mRemoteContainer.getChildAt(i);
if(v.getTag() instanceof Integer && ((int) v.getTag())==uid){
view=v;
}
}
if(view!=null){
return;
}*/
mRemoteView=RtcEngine.CreateRendererView(getBaseContext());
mRemoteContainer.addView(mRemoteView);
mRtcEngine.setupRemoteVideo(new VideoCanvas(mRemoteView,VideoCanvas.RENDER_MODE_HIDDEN,uid));
mRemoteView.setTag(uid);
}
private void removeRemoteView(){
if(mRemoteView!=null){
mRemoteContainer.removeView(mRemoteView);
}
mRemoteView=null;
}
private void joinChannel(){
String token=getString(R.string.agora_access_token);
if(TextUtils.isEmpty(token)){
token=null;
}
mRtcEngine.joinChannel(token,"HelloAgora","",0);
}
private void leaveChannel(){
mRtcEngine.leaveChannel();
}
public void onLocalAudioMuteClicked(View view){
mMuted=!mMuted;
mRtcEngine.muteLocalAudioStream(mMuted);
int res=mMuted?R.drawable.btn_mutecall:R.drawable.btn_unmute;
mMuteBtn.setImageResource(res);
}
public void onSwitchCameraClicked(View view){
mRtcEngine.switchCamera();
}
public void onCallClicked(View view){
if(mCallEnd){
startCall();
mCallEnd=false;
mCallBtn.setImageResource(R.drawable.btn_endcall);
}
else{
endCall();
mCallEnd=true;
mCallBtn.setImageResource(R.drawable.btn_startcall);
}
showButtons(!mCallEnd);
}
private void startCall(){
setupLocalVideo();
joinChannel();
}
private void endCall(){
removeLocalVideo();
removeRemoteView();
leaveChannel();
}
private void removeLocalVideo(){
if(mLocalView!=null){
mLocalContainer.removeView(mLocalView);
}
mLocalView=null;
}
private void showButtons(boolean show){
int visibility=show?View.VISIBLE:View.GONE;
mMuteBtn.setVisibility(visibility);
mSwitchCameraBtn.setVisibility(visibility);
}
private boolean checkSelfPermission(String permission, int requestCode){
if(ContextCompat.checkSelfPermission(this,permission)!= PackageManager.PERMISSION_GRANTED){
ActivityCompat.requestPermissions(this,REQUESTED_PERMISSIONS,requestCode);
Toast.makeText(this,"No Permission",Toast.LENGTH_LONG);
return false;
}
return true;
}
}
'''
You can try "onRemoteVideoStateChanged" callback instead of "onFirstRemoteVideoFrame".
Here is the code snippet: