Example of video conference client for Android
This example can be used to participate in video conference for three participants on Web Call Server and allows to publish WebRTC stream.
...
Volume control is located between the video elements.
Below are located controls for muting/unmuting audio and video for the published stream, input field for a text message and messages log.
Work with code of the example
To analyze the code, let's take class ConferenceActivity.java of of the conference example version with hash 4ed4c6d77, which can be downloaded with corresponding build build 1.0.1.338.
Unlike direct connection to server with method createSession(), , RoomManager object is used for managing connection to server and conference. Connection to server is established when RoomManager object is created, and method RoomManager.join() is called for joining a conference.
When joining, Room object is created for work with the "room". Participant objects are used for work with conference participants.
All events occurring in the "room" (a user joined/left, or sent a message), are sent to all users connected to the "room".
For example, in the following code, a user joins to a "room" and gets the list of already connected users.
Code Block | ||||
---|---|---|---|---|
| ||||
room = roomManager.join(roomOptions); room.on(new RoomEvent() { public void onState(final Room room) { for (final Participant participant : room.getParticipants()) { ... |
ParticipantView (SurfaceViewRenderer + TextView - line 613) is assigned for each of the other participants, to display the name and video stream of that participant (Bob and Cindy on the screenshot above).
1. Initialization of the API. line 98
...
...
Flashphoner.init(
...
)
...
For initialization, object Сontext is passed to the init() method.
Code Block | ||||
---|---|---|---|---|
| ||||
Flashphoner.init(this); |
2. Connection to the server.RoomManager object and session for connection to server are created when Connect button is clicked. line 145
Flashphoner.createRoomManager() code
RoomManagerOptions object with the following parameters is passed to createRoomManager() method()
- URL of WCS server
- username
Code Block | ||||
---|---|---|---|---|
| ||||
roomManagerRoomManagerOptions roomManagerOptions = Flashphoner.createRoomManager(roomManagerOptions); |
RoomManager object is created with method createRoomManager(), to which RoomManagerOptions object (line 139) with the following parameters is passed
- URL of WCS server
- username
Callback functions, which make appropriate changes in controls of the interface, are added for session events (line 150)
- onConnected() - will be called when connection is successfully established
- onDisconnection() - will be called when connection is closed
Code Block |
---|
new RoomManagerOptions(mWcsUrlView.getText().toString(), mLoginView.getText().toString());
/**
* RoomManager object is created with method createRoomManager().
* Connection session is created when RoomManager object is created.
*/
roomManager = Flashphoner.createRoomManager(roomManagerOptions); |
3. Receiving the event confirming successful connection.
RoomManager.onConnected() code
Code Block | ||||
---|---|---|---|---|
| ||||
roomManager.on(new RoomManagerEvent() { @Override public void onConnected(final Connection connection) { runOnUiThread(new Runnable() { ..... @Override } public void onDisconnectionrun(final Connection connection) { mConnectButton.setText(R....string.action_disconnect); } }); |
3. Joining a conference. line 145
After establishing connection to the server, method connection.join() is used to join the conference.
To join, name of the conference room is passed to the method. (The name can be specified as parameter in the URL of the client page; otherwise, random name will be generated.)
...
mConnectButton.setTag(R.string.action_disconnect);
mConnectButton.setEnabled(true);
mConnectStatus.setText(connection.getStatus());
mJoinButton.setEnabled(true);
}
});
} |
4. Joining a conference.
RoomManager.join() code
RoomOptions object with the name of the conference room is passed to the join() method.
Code Block | ||||
---|---|---|---|---|
| ||||
roomRoomOptions roomOptions = roomManager.join(roomOptions); |
Callback functions for conference room events are added (line 254)
- onState() - will be called when successfully joined the conference
- onJoined() - will be called when a new participants joins the conference: the interface is changed to display the new participant and playback of video stream published by that participant is started
- onLeft() - will be called when one of the participants leaves the conference: appropriate changes in the interface are done
- onPublished() - will be called when one of the participants starts publishing: playback of video stream published by that participant is started
- onFailed() - will be called in case of failure: the user leaves the conference
- onMessage() - will be called when one of the participants sends text message: the message is added to the messages log
In callback function onState(),
- the size of the collection of Participant objects returned by method Room.getParticipants() is determined to get the number of already connected participants
- if the maximum allowed number of participants had already been reached, the user leaves the "room" (line 264)
- otherwise, appropriate changes in the interface are done and playback of video stream published by the other participants is started
Code Block | ||||
---|---|---|---|---|
| ||||
room.on(new RoomEvent() {
public void onState(final Room room) {
.....
}
public void onJoined(final Participant participant) {
.....
}
public void onLeft(final Participant participant) {
.....
}
public void onPublished(final Participant participant) {
.....
}
public void onFailed(Room room, final String info) {
.....
}
public void onMessage(final Message message) {
.....
}
}); |
4. Video streaming. line 479
Video stream publication is started when Publish button is clicked.
Code Block | ||||
---|---|---|---|---|
| ||||
stream = room.publish(localRenderer); |
Method Room.publish() is called to publish a stream. SurfaceViewRenderer to be used to display video from the camera is passed to the method.
Callback function for processing stream statuses is added. (line 484)
Code Block | ||||
---|---|---|---|---|
| ||||
publishStream.on(new StreamStatusEvent() {
@Override
public void onStreamStatus(final Stream stream, final StreamStatus streamStatus) {
runOnUiThread(new Runnable() {
@Override
public void run() {
if (StreamStatus.PUBLISHING.equals(streamStatus)) {
.....
}
}
}
}); |
5. Playback of video stream. line 376
When one of the conference participants starts publishing, method Participant.play() is used to start playback of that stream.
SurfaceViewRenderer, in which the video will be displayed, is passed to the method.
Code Block | ||||
---|---|---|---|---|
| ||||
participant.play(participantView.surfaceViewRenderer); |
6. Stop of streaming. line 518
The following method is called to stop video streaming when Unpublish button is clicked.
Code Block | ||||
---|---|---|---|---|
| ||||
room.unpublish(); |
7. Leaving conference room. line 416
Method room.leave() is called for leaving conference room when Leave button is clicked.
Handler for processing response from server-side REST application is passed to the method.
Code Block | ||||
---|---|---|---|---|
| ||||
room.leave(new RestAppCommunicator.Handlernew RoomOptions(); roomOptions.setName(mJoinRoomView.getText().toString()); /** * The participant joins a conference room with method RoomManager.join(). * RoomOptions object is passed to the method. * Room object is created and returned by the method. */ room = roomManager.join(roomOptions); |
5. Receiving the event describing chat room state
Room.onState() code
On this event:
- the size of the collection of Participant objects returned by method Room.getParticipants() is determined to get the number of already connected participants
- if the maximum allowed number of participants had already been reached, the user leaves the "room"
- otherwise, appropriate changes in the interface are done and playback of video stream published by the other participants is started
Code Block | ||||
---|---|---|---|---|
| ||||
@Override
public void onState(final Room room) {
/**
* After joining, Room object with data of the room is received.
* Method Room.getParticipants() is used to check the number of already connected participants.
* The method returns collection of Participant objects.
* The collection size is determined, and, if the maximum allowed number (in this case, three) has already been reached, the user leaves the room with method Room.leave().
*/
if (room.getParticipants().size() >= 3) {
room.leave(null);
runOnUiThread(
new Runnable() {
@Override
public void run() {
mJoinStatus.setText("Room is full");
mJoinButton.setEnabled(true);
}
}
);
return;
}
final StringBuffer chatState = new StringBuffer("participants: ");
/**
* Iterating through the collection of the other participants returned by method Room.getParticipants().
* There is corresponding Participant object for each participant.
*/
for (final Participant participant : room.getParticipants()) {
/**
* A player view is assigned to each of the other participants in the room.
*/
final ParticipantView participantView = freeViews.poll();
if (participantView != null) {
chatState.append(participant.getName()).append(",");
busyViews.put(participant.getName(), participantView);
/**
* Playback of the stream being published by the other participant is started with method Participant.play().
* SurfaceViewRenderer to be used to display the video stream is passed when the method is called.
*/
participant.play(participantView.surfaceViewRenderer);
...
}
}
...
} |
4. Video streaming when permissions to use camera and microphone are granted.
Room.publish() code
SurfaceViewRenderer to be used to display video from the camera is passed to the publish() method
Code Block | ||||
---|---|---|---|---|
| ||||
case PUBLISH_REQUEST_CODE: {
if (grantResults.length == 0 ||
grantResults[0] != PackageManager.PERMISSION_GRANTED ||
grantResults[1] != PackageManager.PERMISSION_GRANTED) {
Log.i(TAG, "Permission has been denied by user");
} else {
mPublishButton.setEnabled(false);
/**
* Stream is created and published with method Room.publish().
* SurfaceViewRenderer to be used to display video from the camera is passed to the method.
*/
boolean record = mRecord.isChecked();
StreamOptions streamOptions = new StreamOptions();
streamOptions.setRecord(record);
stream = room.publish(localRenderer, streamOptions);
...
Log.i(TAG, "Permission has been granted by user");
}
} |
7. Receiving the event notifying that other participant joined to the room
Room.onJoined() code
Code Block | ||||
---|---|---|---|---|
| ||||
@Override
public void onJoined(final Participant participant) {
/**
* When a new participant joins the room, a player view is assigned to that participant.
*/
final ParticipantView participantView = freeViews.poll();
if (participantView != null) {
runOnUiThread(
new Runnable() {
@Override
public void run() {
participantView.login.setText(participant.getName());
addMessageHistory(participant.getName(), "joined");
}
}
);
busyViews.put(participant.getName(), participantView);
}
} |
8. Receiving the event notifying that other participant published video stream
Room.onPublished() код
When one of the conference participants starts publishing, method Participant.play() is used to start playback of that stream.
SurfaceViewRenderer, in which the video will be displayed, is passed to the method.
Code Block | ||||
---|---|---|---|---|
| ||||
@Override
public void onPublished(final Participant participant) {
/**
* When one of the other participants starts publishing, playback of the stream published by that participant is started.
*/
final ParticipantView participantView = busyViews.get(participant.getName());
if (participantView != null) {
participant.play(participantView.surfaceViewRenderer);
}
} |
9. Receiving the event notifying that other participant sent a message
Room.onMessage() code
Code Block | ||||
---|---|---|---|---|
| ||||
@Override
public void onMessage(final Message message) {
/**
* When one of the participants sends a text message, the received message is added to the messages log.
*/
runOnUiThread(
new Runnable() {
@Override
public void run() {
addMessageHistory(message.getFrom(), message.getText());
}
});
} |
10. Sending a message to other room participants
Participant.sendMessage() code
Code Block | ||||
---|---|---|---|---|
| ||||
mSendButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View view) {
String text = mMessage.getText().toString();
if (!"".equals(text)) {
for (Participant participant : room.getParticipants()) {
participant.sendMessage(text);
}
addMessageHistory(mLoginView.getText().toString(), text);
mMessage.setText("");
}
}
}); |
11. Streaming stop on "Unpublish" button pressing.
Room.unpublish() code
Code Block | ||||
---|---|---|---|---|
| ||||
@Override
public void onClick(View view) {
if (mPublishButton.getTag() == null || Integer.valueOf(R.string.action_publish).equals(mPublishButton.getTag())) {
ActivityCompat.requestPermissions(ConferenceActivity.this,
new String[]{Manifest.permission.RECORD_AUDIO, Manifest.permission.CAMERA},
PUBLISH_REQUEST_CODE);
} else {
mPublishButton.setEnabled(false);
/**
* Stream is unpublished with method Room.unpublish().
*/
room.unpublish();
}
View currentFocus = getCurrentFocus();
if (currentFocus != null) {
InputMethodManager inputManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
inputManager.hideSoftInputFromWindow(currentFocus.getWindowToken(), InputMethodManager.HIDE_NOT_ALWAYS);
}
}
}); |
12. Leaving chat room
Room.leave() code
Методу передается обработчик ответа REST-приложения WCS-сервера.
Code Block | ||||
---|---|---|---|---|
| ||||
room.leave(new RestAppCommunicator.Handler() {
@Override
public void onAccepted(Data data) {
runOnUiThread(action);
}
@Override
public void onRejected(Data data) {
runOnUiThread(action);
}
}); |
13. Disconnection.
RoomManager.disconnect() code
Code Block | ||||
---|---|---|---|---|
| ||||
mConnectButton.setEnabled(false);
/**
* Connection to WCS server is closed with method RoomManager.disconnect().
*/
roomManager.disconnect(); |
14. Mute/unmute audio and video for stream published
Stream.unmuteAudio(), Stream.muteAudio(), Stream.unmuteVideo(), Stream.muteVideo() code
Code Block | ||||
---|---|---|---|---|
| ||||
/** * MuteAudio switch is used to mute/unmute audio of the published stream. * Audio is muted with method Stream.muteAudio() and unmuted with method Stream.unmuteAudio(). */ mMuteAudio = (Switch) findViewById(R.id.mute_audio); mMuteAudio.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { public void onAccepted(Data dataonCheckedChanged(CompoundButton buttonView, boolean isChecked) { if (isChecked) { ......stream.muteAudio(); } else { public void onRejected(Data data) {stream.unmuteAudio(); ......} } }); |
8. Disconnection. line 213
Method RoomManager.disconnect() is called to close connection to the server.
Code Block | ||||
---|---|---|---|---|
| ||||
roomManager.disconnect(); |
9. Mute/unmute audio and video of the published stream. line 533, line 548
The following methods are used to mute/unmute audio and video
- Stream.unmuteAudio();
- Stream.muteAudio();
- Stream.unmuteVideo();
- Stream.muteVideo();
10. Sending text message. line 599
Method Participant.sendMessage() is used for sending text message to other participants. Message text is passed to the method.
When Send button is clicked,
- method Room.getParticipants() is used to get the collection of connected participants
- the message is sent to each participant
Code Block | ||||
---|---|---|---|---|
| ||||
for (Participant participant : room.getParticipants()) {
participant.sendMessage(text);
} |
11. Adjusting the volume line 100
The following methods are used to adjust the volume
...
/**
* MuteVideo switch is used to mute/unmute video of the published stream.
* Video is muted with method Stream.muteVideo() and unmuted with method Stream.unmuteVideo().
*/
mMuteVideo = (Switch) findViewById(R.id.mute_video);
mMuteVideo.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
if (isChecked) {
stream.muteVideo();
} else {
stream.unmuteVideo();
}
}
}); |