Skip to content

Instantly share code, notes, and snippets.

@thenishchalraj
Created May 12, 2021 14:59
Show Gist options
  • Save thenishchalraj/55995075222f057ebe045cd080531034 to your computer and use it in GitHub Desktop.
Save thenishchalraj/55995075222f057ebe045cd080531034 to your computer and use it in GitHub Desktop.
Sample java class file to show the main code needed to create Instagram LIVE using Agora SDK and socket.io (Note: not everything for view is included but only the agora and socket snippet)
public class InstagramLiveAgoraSocket extends AppCompatActivity {
private static final int PERMISSION_REQ_ID = 22;
// Permission WRITE_EXTERNAL_STORAGE is not mandatory
// for Agora RTC SDK, just in case if you wanna save
// logs to external sdcard.
private static final String[] REQUESTED_PERMISSIONS = {
Manifest.permission.RECORD_AUDIO,
Manifest.permission.CAMERA,
Manifest.permission.WRITE_EXTERNAL_STORAGE
};
private RtcEngine mRtcEngine;
private RelativeLayout mVideoContainer;
private VideoCanvas mVideo;
// all other views variable
String token;
String liveTitle;
String channelName;
int userRole;
int channelProfile;
// Socket connection
private Socket socket;
private Emitter.Listener onConnect = new Emitter.Listener() {
@Override
public void call(Object... args) {
runOnUiThread(() -> {
Log.e("SOCKET: ", "connected");
// enable any layout element here
// just to show like Instagram that a user has joined
socket.emit("joinRoom", "room_" + roomId);
// add username or id as per your need
});
}
};
private Emitter.Listener onDisconnect = args -> runOnUiThread(() -> {
Log.e("SOCKET: ", "disconnected");
// disable any layout element
});
private Emitter.Listener onConnectError = args -> {
Log.e("SOCKET: ", "connection error");
};
private Emitter.Listener onNewMessage = args -> runOnUiThread(() -> {
JSONObject msgObj = (JSONObject) args[0];
Log.d("MESSAGE", msgObj.toString());
LiveComment msg = new LiveComment();
msg.setCommentText(msgObj.optString("message"));
addNewMessageToChat(msg);
});
private Emitter.Listener onLikeEvent = args -> runOnUiThread(() -> {
JSONObject msgObj = (JSONObject) args[0];
Log.d("LIKE", msgObj.toString());
LiveComment msg = new LiveComment();
msg.setCommentText(msgObj.optString("message"));
mTextViewLike.setText(String.valueOf(msgObj.optInt("likes")));
addNewMessageToChat(msg);
});
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_live_view);
Bundle liveData = getIntent().getExtras();
token = liveData.getString("agora_token");
channelName = liveData.getString("channel_name");
liveTitle = liveData.getString("title");
if (liveData.getString("isUser").equals("host")) {
userRole = Constants.CLIENT_ROLE_BROADCASTER;
} else {
userRole = Constants.CLIENT_ROLE_AUDIENCE;
}
channelProfile = Constants.CHANNEL_PROFILE_LIVE_BROADCASTING;
initSocket();
initUI();
if (checkSelfPermission(REQUESTED_PERMISSIONS[0], PERMISSION_REQ_ID) &&
checkSelfPermission(REQUESTED_PERMISSIONS[1], PERMISSION_REQ_ID) &&
checkSelfPermission(REQUESTED_PERMISSIONS[2], PERMISSION_REQ_ID)) {
initEngineAndJoinChannel();
}
}
private void initUI() {
// initiate view here for all the layout elements needed
if (userRole == Constants.CLIENT_ROLE_BROADCASTER) {
// make layout elements visible for broadcaster
} else if ( userRole == Constants.CLIENT_ROLE_AUDIENCE) {
// make layout elements visible for audience
}
setUpCommentsAdapter();
}
LiveCommentsAdapter mLiveCommentsAdapter;
private void setUpCommentsAdapter() {
LinearLayoutManager llm = new LinearLayoutManager(this);
llm.setStackFromEnd(true);
mRecyclerViewComments.setLayoutManager(llm);
mLiveCommentsAdapter = new LiveCommentsAdapter(this, liveComments);
mRecyclerViewComments.setHasFixedSize(true);
mRecyclerViewComments.setAdapter(mLiveCommentsAdapter);
}
private final IRtcEngineEventHandler mRtcEventHandler = new IRtcEngineEventHandler() {
@Override
public void onJoinChannelSuccess(String channel, final int uid, int elapsed) {
runOnUiThread(new Runnable() {
@Override
public void run() {
Log.e("Join channel success", String.valueOf((uid & 0xFFFFFFFFL)));
}
});
}
@Override
public void onUserJoined(final int uid, int elapsed) {
super.onUserJoined(uid, elapsed);
runOnUiThread(new Runnable() {
@Override
public void run() {
Log.e("inside --->>", " on user joined");
if (userRole == Constants.CLIENT_ROLE_BROADCASTER)
setupLocalVideo();
else setupRemoteVideo(uid);
}
});
}
@Override
public void onUserOffline(int uid, int reason) {
runOnUiThread(new Runnable() {
@Override
public void run() {
if (userRole == Constants.CLIENT_ROLE_BROADCASTER)
onRemoteUserLeft(uid);
}
});
}
};
private void setupRemoteVideo(int uid) {
SurfaceView mRemoteView;
Log.e("inside --->>", "setupremotevideo");
mRemoteView = RtcEngine.CreateRendererView(getBaseContext());
mVideoContainer.addView(mRemoteView);
// Set the remote video view.
mRtcEngine.setupRemoteVideo(new VideoCanvas(mRemoteView, VideoCanvas.RENDER_MODE_HIDDEN, uid));
}
private void setupLocalVideo() {
mRtcEngine.enableVideo();
SurfaceView localView = RtcEngine.CreateRendererView(getBaseContext());
localView.setZOrderMediaOverlay(true);
mVideoContainer.addView(localView);
mRtcEngine.setupLocalVideo(new VideoCanvas(localView, VideoCanvas.RENDER_MODE_HIDDEN, 0));
}
private void onRemoteUserLeft(int uid) {
if (mRemoteVideo != null && mRemoteVideo.uid == uid) {
removeFromParent(mRemoteVideo);
// Destroys remote view
mRemoteVideo = null;
}
}
private boolean checkSelfPermission(String permission, int requestCode) {
if (ContextCompat.checkSelfPermission(this, permission) !=
PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this, REQUESTED_PERMISSIONS, requestCode);
return false;
}
return true;
}
@Override
public void onRequestPermissionsResult(int requestCode,
@NonNull String[] permissions, @NonNull int[] grantResults) {
if (requestCode == PERMISSION_REQ_ID) {
if (grantResults[0] != PackageManager.PERMISSION_GRANTED ||
grantResults[1] != PackageManager.PERMISSION_GRANTED ||
grantResults[2] != PackageManager.PERMISSION_GRANTED) {
showLongToast("Need permissions " + Manifest.permission.RECORD_AUDIO +
"/" + Manifest.permission.CAMERA + "/" + Manifest.permission.WRITE_EXTERNAL_STORAGE);
finish();
return;
}
initEngineAndJoinChannel();
}
}
private void showLongToast(final String msg) {
this.runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(getApplicationContext(), msg, Toast.LENGTH_LONG).show();
}
});
}
private void initEngineAndJoinChannel() {
initializeEngine();
mRtcEngine.setChannelProfile(channelProfile);
mRtcEngine.setClientRole(userRole);
if (userRole == Constants.CLIENT_ROLE_BROADCASTER) setupLocalVideo();
setupVideoConfig();
joinChannel();
}
private void initializeEngine() {
try {
Log.e("Initializing: ", "AgoraEngine");
mRtcEngine = RtcEngine.create(getBaseContext(), getString(R.string.agora_app_id), mRtcEventHandler);
} catch (Exception e) {
Log.e("Exception: ", Log.getStackTraceString(e));
throw new RuntimeException("NEED TO check rtc sdk init fatal error\n" + Log.getStackTraceString(e));
}
}
private void setupVideoConfig() {
mRtcEngine.setVideoEncoderConfiguration(new VideoEncoderConfiguration(
VideoEncoderConfiguration.VD_640x480,
VideoEncoderConfiguration.FRAME_RATE.FRAME_RATE_FPS_15,
VideoEncoderConfiguration.STANDARD_BITRATE,
VideoEncoderConfiguration.ORIENTATION_MODE.ORIENTATION_MODE_FIXED_PORTRAIT));
}
private void joinChannel() {
if (TextUtils.isEmpty(token) || TextUtils.equals(token, "#YOUR ACCESS TOKEN#")) {
token = null; // default, no token
}
mRtcEngine.joinChannel(token.replace("\"", ""), channelName, "Extra Optional Data", 0);
}
@Override
protected void onDestroy() {
super.onDestroy();
if (userRole == Constants.CLIENT_ROLE_AUDIENCE) {
leaveChannel();
} else {
RtcEngine.destroy();
}
tearDownSocket();
}
private void leaveChannel() {
mRtcEngine.leaveChannel();
}
public void onLocalAudioMuteClicked(View view) {
// Stops/Resumes sending the local audio stream.
mRtcEngine.muteLocalAudioStream(mMuted);
// change the icon here according to condition
}
public void onSwitchCameraClicked(View view) {
mRtcEngine.switchCamera();
// change the icon here according to condition
}
public void onCallClicked(View view) {
endCall();
}
private void endCall() {
removeFromParent(mRemoteVideo);
mRemoteVideo = null;
leaveChannel();
// request to own server to tell the live has ended
}
private ViewGroup removeFromParent(VideoCanvas canvas) {
if (canvas != null) {
ViewParent parent = canvas.view.getParent();
if (parent != null) {
ViewGroup group = (ViewGroup) parent;
group.removeView(canvas.view);
return group;
}
}
return null;
}
public void onNewMessageSend(View view) {
String newMessage = mWriteCommentBox.getText().toString();
if (newMessage.trim().length() == 0) {
Toast.makeText(this, "Can't send empty message", Toast.LENGTH_SHORT).show();
}
else {
attemptToSendMessage(newMessage);
mWriteCommentBox.setText("");
}
}
private LiveComment attemptToSendMessage(String message) {
if (!socket.connected()) return null;
JSONObject commentObj = new JSONObject();
try {
commentObj.put("user", userName);
commentObj.put("message", message);
// include any other details as per your need
socket.emit("sendMessage", commentObj);
} catch (JSONException e) {
e.printStackTrace();
}
LiveComment liveComment = new LiveComment();
liveComment.setUserName(userName);
liveComment.setCommentText(message);
return liveComment;
}
private ArrayList<LiveComment> liveComments = new ArrayList<>();
private void addNewMessageToChat(LiveComment newMessage) {
liveComments.add(newMessage);
onChatScreenDataSetChanged();
fullScrollChatList();
}
private void onChatScreenDataSetChanged() {
if (liveComments.isEmpty()) {
mRecyclerViewComments.setVisibility(View.GONE);
} else {
mRecyclerViewComments.setVisibility(View.VISIBLE);
}
mLiveCommentsAdapter.notifyDataSetChanged();
}
private void fullScrollChatList() {
mRecyclerViewComments.postDelayed(() -> mRecyclerViewComments.scrollToPosition(liveComments.size() - 1), 100);
}
private void initSocket() {
try {
String socketUrl = "https://demoapp.com";
socket = IO.socket(socketUrl);
} catch (Exception e) {
e.printStackTrace();
}
socket.on(Socket.EVENT_CONNECT, onConnect);
socket.on(Socket.EVENT_DISCONNECT, onDisconnect);
socket.on(Socket.EVENT_CONNECT_ERROR, onConnectError);
socket.on(Socket.EVENT_CONNECT_TIMEOUT, onConnectError);
socket.on("newMessage", onNewMessage);
socket.on("likes", onLikeEvent);
socket.connect();
}
private void tearDownSocket() {
socket.disconnect();
socket.off(Socket.EVENT_CONNECT, onConnect);
socket.off(Socket.EVENT_DISCONNECT, onDisconnect);
socket.off(Socket.EVENT_CONNECT_ERROR, onConnectError);
socket.off(Socket.EVENT_CONNECT_TIMEOUT, onConnectError);
socket.off("newMessage", onNewMessage);
socket.off("likes", onLikeEvent);
}
}
@thenishchalraj
Copy link
Author

To read the related blog, click here.

That was a long blog to write, but completed it last night.

Build Instagram LIVE into Android apps using @AgoraIO and @SocketIO

Here's the blog: https://t.co/dFlqLyyxe6#Android #AndroidDevChallenge #InstagramLive #agora #socket #AndroidDev #Java pic.twitter.com/66ltkK0U0T

— Nishchal Raj (@thenishchalraj) May 13, 2021

If you liked it, do a re-tweet

and share with your connections here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment