Search code examples
javaandroidsdkvideo-conferencing

Track `Vidyo` conference participants with android Vidyo SDK


I am using Vidyo.io service to integrate video conferencing within my android app. I have been able to successfully implement the video conferencing using the VidyoConnector sample app that comes with the android-vidyo-sdk. However, I would like to get more information about the conference room and the participants who join the room.

The work flow that I have imagined is as follows:

  1. User1 wants to make a vidyo conference call from an android device. So User1 will ask for, and get a new token from my backend server using an api.
  2. User1 will then send an invitation to other participants to join the conference call with the token that was obtained in step 1 and corresponding vidyo conference room resourceId.
  3. Other participants will use the information obtained from User1 in step 2 and will be able to join the conference room.

So far, I have referenced VidyoConnector sample app to create an activity called VideoChatActivity by making some modifications to the original VidyoConnector sample app’s MainActivity. The VideoChatActivity will be invoked via Intent from another activity within my app with all the necessary information to initiate the vidyo connection like- token, resourceId, username etc. To track the status of each participant I have implemented the VidyoConnector.IRegisterParticipantEventListener. Please refer my code below:

public class VideoChatActivity extends Activity
        implements VidyoConnector.IConnect,
        VidyoConnector.IRegisterParticipantEventListener {

    private enum VIDYO_CONNECTOR_STATE {
        VC_CONNECTED,
        VC_DISCONNECTED,
        VC_DISCONNECTED_UNEXPECTED,
        VC_CONNECTION_FAILURE
    }

    private static final String TAG = "VideoChatActivity";

    private VIDYO_CONNECTOR_STATE mVidyoConnectorState = VIDYO_CONNECTOR_STATE.VC_DISCONNECTED;
    private boolean mVidyoConnectorConstructed = false;
    private boolean mVidyoClientInitialized = false;
    private VidyoConnector mVidyoConnector = null;
    private ToggleButton mToggleConnectButton;
    private ProgressBar mConnectionSpinner;
    private LinearLayout mToolbarLayout;
    private String mHost;
    private String mDisplayName;
    private String mToken;
    private String mResourceId;
    private TextView mToolbarStatus;
    private FrameLayout mVideoFrame;
    private boolean mAutoJoin = false;
    private boolean mAllowReconnect = true;
    private String mReturnURL = null;


    /*
     *  Operating System Events
     */

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        Log.d(TAG, "onCreate");
        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_video_chat);

        // Initialize the member variables
        mToggleConnectButton = (ToggleButton) findViewById(R.id.video_chat_toggleConnectButton);
        mToolbarLayout = (LinearLayout) findViewById(R.id.video_chat_toolbarLayout);
        mVideoFrame = (FrameLayout) findViewById(R.id.video_chat_videoFrame);
        mToolbarStatus = (TextView) findViewById(R.id.video_chat_toolbarStatusText);
        mConnectionSpinner = (ProgressBar) findViewById(R.id.video_chat_connectionSpinner);

        // Initialize the VidyoClient
        Connector.SetApplicationUIContext(this);
        mVidyoClientInitialized = Connector.Initialize();
    }

    @Override
    protected void onNewIntent(Intent intent) {
        Log.d(TAG, "onNewIntent");
        super.onNewIntent(intent);

        // New intent was received so set it to use in onStart()
        setIntent(intent);
    }

    @Override
    protected void onStart() {
        Log.d(TAG, "onStart");
        super.onStart();

        // If the app was launched by a different app, then get any parameters; otherwise use default settings
        Intent intent = getIntent();
        mHost = intent.hasExtra("host") ? intent.getStringExtra("host") : "prod.vidyo.io";
        mToken = intent.hasExtra("token") ? intent.getStringExtra("token") : "";
        mDisplayName = intent.hasExtra("displayName") ? intent.getStringExtra("displayName") : "";
        mResourceId = intent.hasExtra("resourceId") ? intent.getStringExtra("resourceId") : "";
        mReturnURL = intent.hasExtra("returnURL") ? intent.getStringExtra("returnURL") : null;
        mAutoJoin = intent.getBooleanExtra("autoJoin", false);
        mAllowReconnect = intent.getBooleanExtra("allowReconnect", true);

        Log.d(TAG, "onStart: autoJoin = " + mAutoJoin + ", allowReconnect = " + mAllowReconnect);

        if (mDisplayName.equals("")) {
            Profile myProfile = getMyProfile();
            if (myProfile != null && myProfile.getDisplayName() != null) {
                mDisplayName = myProfile.getDisplayName();
            }
        }

        // Enable toggle connect button
        mToggleConnectButton.setEnabled(true);
    }

    @Override
    protected void onResume() {
        Log.d(TAG, "onResume");
        super.onResume();
        ViewTreeObserver viewTreeObserver = mVideoFrame.getViewTreeObserver();
        if (viewTreeObserver.isAlive()) {
            viewTreeObserver.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
                @Override
                public void onGlobalLayout() {
                    mVideoFrame.getViewTreeObserver().removeOnGlobalLayoutListener(this);

                    // If the vidyo connector was not previously successfully constructed then construct it

                    if (!mVidyoConnectorConstructed) {

                        if (mVidyoClientInitialized) {


                            mVidyoConnector = new VidyoConnector(mVideoFrame,
                                                                 VidyoConnector.VidyoConnectorViewStyle.VIDYO_CONNECTORVIEWSTYLE_Default,
                                                                 16,
                                                                 "info@VidyoClient info@VidyoConnector warning",
                                                                 "",
                                                                 0);

                            if (mVidyoConnector != null) {
                                mVidyoConnectorConstructed = true;
                                // Set initial position
                                RefreshUI();
                            } else {
                                Log.d(TAG, "VidyoConnector Construction failed - cannot " +
                                        "connect...");
                            }
                        } else {
                            Log.d(TAG, "ERROR: VidyoClientInitialize failed - not constructing " +
                                    "VidyoConnector ...");
                        }

                        Log.d(TAG, "onResume: mVidyoConnectorConstructed => " +
                                (mVidyoConnectorConstructed ? "success" : "failed"));
                    }

                    // If configured to auto-join, then simulate a click of the toggle connect button
                    if (mVidyoConnectorConstructed && mAutoJoin) {
                        mToggleConnectButton.performClick();
                    }
                }
            });
        }
    }

    @Override
    protected void onPause() {
        Log.d(TAG, "onPause");
        super.onPause();
    }

    @Override
    protected void onRestart() {
        Log.d(TAG, "onRestart");
        super.onRestart();
        if (mVidyoConnector != null) {
            mVidyoConnector.SetMode(VidyoConnector.VidyoConnectorMode.VIDYO_CONNECTORMODE_Foreground);
        }
    }

    @Override
    protected void onStop() {
        Log.d(TAG, "onStop");
        if (mVidyoConnector != null) {
            mVidyoConnector.SetMode(VidyoConnector.VidyoConnectorMode.VIDYO_CONNECTORMODE_Background);
        }
        super.onStop();
    }

    @Override
    protected void onDestroy() {
        Log.d(TAG, "onDestroy");
        Connector.Uninitialize();
        super.onDestroy();
    }

    /*
     *  Connector Events
     */

    // Handle successful connection.
    @Override
    public void OnSuccess() {
        Log.d(TAG, "OnSuccess: connection successful");
        ConnectorStateUpdated(VIDYO_CONNECTOR_STATE.VC_CONNECTED, "Connected");
    }

    // Handle attempted connection failure.
    @Override
    public void OnFailure(VidyoConnector.VidyoConnectorFailReason reason) {
        Log.d(TAG, "onFailure: connection failure reason : " + reason.toString());

        // Update UI to reflect connection failed
        ConnectorStateUpdated(VIDYO_CONNECTOR_STATE.VC_CONNECTION_FAILURE, "Connection failed");
    }

    // Handle an existing session being disconnected.
    @Override
    public void OnDisconnected(VidyoConnector.VidyoConnectorDisconnectReason reason) {
        Log.d(TAG, "OnDisconnected: disconnect reason : " + reason.toString());
        if (reason == VidyoConnector.VidyoConnectorDisconnectReason.VIDYO_CONNECTORDISCONNECTREASON_Disconnected) {
            ConnectorStateUpdated(VIDYO_CONNECTOR_STATE.VC_DISCONNECTED, "Disconnected");
        } else {
            ConnectorStateUpdated(VIDYO_CONNECTOR_STATE.VC_DISCONNECTED_UNEXPECTED, "Unexpected disconnection");
        }
    }

    @Override
    public void OnParticipantJoined(final VidyoParticipant vidyoParticipant) {
        Log.d(TAG, "joined participant id : " + vidyoParticipant.GetId());
        Log.d(TAG, "joined participant name : " + vidyoParticipant.GetName());
        Log.d(TAG, "joined participant userId : " + vidyoParticipant.GetUserId());
        Log.d(TAG, "joined participant object ptr : " + vidyoParticipant.GetObjectPtr());
        Log.d(TAG, "joined participant isHidden : " + vidyoParticipant.IsHidden());
        Log.d(TAG, "joined participant isLocal : " + vidyoParticipant.IsLocal());
        Log.d(TAG, "joined participant isRecording : " + vidyoParticipant.IsRecording());
        Log.d(TAG, "joined participant isSelectable : " + vidyoParticipant.IsSelectable());
    }

    @Override
    public void OnParticipantLeft(VidyoParticipant vidyoParticipant) {
        Log.d(TAG, "left participant id : " + vidyoParticipant.GetId());
        Log.d(TAG, "left participant name : " + vidyoParticipant.GetName());
        Log.d(TAG, "left participant userId : " + vidyoParticipant.GetUserId());
        Log.d(TAG, "left participant object ptr : " + vidyoParticipant.GetObjectPtr());
        Log.d(TAG, "left participant isHidden : " + vidyoParticipant.IsHidden());
        Log.d(TAG, "left participant isLocal : " + vidyoParticipant.IsLocal());
        Log.d(TAG, "left participant isRecording : " + vidyoParticipant.IsRecording());
        Log.d(TAG, "left participant isSelectable : " + vidyoParticipant.IsSelectable());
    }

    @Override
    public void OnDynamicParticipantChanged(ArrayList<VidyoParticipant> arrayList, ArrayList<VidyoRemoteCamera> arrayList1) {
        for (VidyoParticipant participant : arrayList) {
            Log.d(TAG, "Participant : " + participant.GetName());
        }

        for (VidyoRemoteCamera remoteCamera : arrayList1) {
            Log.d(TAG, "remote camera : " + remoteCamera.GetName());
        }
    }

    @Override
    public void OnLoudestParticipantChanged(VidyoParticipant vidyoParticipant, boolean b) {
        Log.d(TAG, "loudest participant id : " + vidyoParticipant.GetId());
        Log.d(TAG, "loudest participant name : " + vidyoParticipant.GetName());
        Log.d(TAG, "loudest participant userId : " + vidyoParticipant.GetUserId());
        Log.d(TAG, "loudest participant object ptr : " + vidyoParticipant.GetObjectPtr());
        Log.d(TAG, "loudest participant isHidden : " + vidyoParticipant.IsHidden());
        Log.d(TAG, "loudest participant isLocal : " + vidyoParticipant.IsLocal());
        Log.d(TAG, "loudest participant isRecording : " + vidyoParticipant.IsRecording());
        Log.d(TAG, "loudest participant isSelectable : " + vidyoParticipant.IsSelectable());

        Log.d(TAG, "boolean : " + b);
    }

    /*
     * Private Utility Functions
     */
    // Refresh the UI
    private void RefreshUI() {
        // Refresh the rendering of the video
        mVidyoConnector.ShowViewAt(mVideoFrame, 0, 0, mVideoFrame.getWidth(), mVideoFrame.getHeight());
        Log.d(TAG, "VidyoConnectorShowViewAt: x = 0, y = 0, w = " + mVideoFrame.getWidth() + ", h = " + mVideoFrame.getHeight());
    }

    // The state of the VidyoConnector connection changed, reconfigure the UI.
    // If connected, dismiss the controls layout
    private void ConnectorStateUpdated(VIDYO_CONNECTOR_STATE state, final String statusText) {
        Log.d(TAG, "ConnectorStateUpdated, state = " + state.toString());

        mVidyoConnectorState = state;

        // Execute this code on the main thread since it is updating the UI layout

        runOnUiThread(new Runnable() {
            @Override
            public void run() {

                // Update the toggle connect button to either start call or end call image
                mToggleConnectButton.setChecked(mVidyoConnectorState == VIDYO_CONNECTOR_STATE.VC_CONNECTED);

                // Set the status text in the toolbar
                mToolbarStatus.setText(statusText);

                if (mVidyoConnectorState == VIDYO_CONNECTOR_STATE.VC_CONNECTED) {
                    // Enable the toggle toolbar control

                } else {
                    // VidyoConnector is disconnected

                    // Disable the toggle toolbar control

                    // If a return URL was provided as an input parameter, then return to that application
                    if (mReturnURL != null) {
                        // Provide a callstate of either 0 or 1, depending on whether the call was successful
                        Intent returnApp = getPackageManager().getLaunchIntentForPackage(mReturnURL);
                        returnApp.putExtra("callstate", (mVidyoConnectorState == VIDYO_CONNECTOR_STATE.VC_DISCONNECTED) ? 1 : 0);
                        startActivity(returnApp);
                    }

                    // If the allow-reconnect flag is set to false and a normal (non-failure) disconnect occurred,
                    // then disable the toggle connect button, in order to prevent reconnection.
                    if (!mAllowReconnect && (mVidyoConnectorState == VIDYO_CONNECTOR_STATE.VC_DISCONNECTED)) {
                        mToggleConnectButton.setEnabled(false);
                        mToolbarStatus.setText("Call ended");
                    }
                }

                // Hide the spinner animation
                mConnectionSpinner.setVisibility(View.INVISIBLE);
            }
        });
    }

    /*
     * Button Event Callbacks
     */

    // The Connect button was pressed.
    // If not in a call, attempt to connect to the backend service.
    // If in a call, disconnect.
    public void ToggleConnectButtonPressed(View v) {
        if (mToggleConnectButton.isChecked()) {
            mToolbarStatus.setText("Connecting...");

            // Display the spinner animation
            mConnectionSpinner.setVisibility(View.VISIBLE);

            final boolean status = mVidyoConnector.Connect(
                    mHost,
                    mToken,
                    mDisplayName,
                    mResourceId,
                    this);
            if (!status) {
                // Hide the spinner animation
                mConnectionSpinner.setVisibility(View.INVISIBLE);

                ConnectorStateUpdated(VIDYO_CONNECTOR_STATE.VC_CONNECTION_FAILURE, "Connection failed");
            }
            Log.d(TAG, "VidyoConnectorConnect status = " + status);
            mVidyoConnector.RegisterParticipantEventListener(this);
        } else {
            // The button just switched to the callStart image: The user is either connected to a resource
            // or is in the process of connecting to a resource; call VidyoConnectorDisconnect to either disconnect
            // or abort the connection attempt.
            // Change the button back to the callEnd image because do not want to assume that the Disconnect
            // call will actually end the call. Need to wait for the callback to be received
            // before swapping to the callStart image.
            mToggleConnectButton.setChecked(true);

            mToolbarStatus.setText("Disconnecting...");

            mVidyoConnector.UnregisterParticipantEventListener();

            mVidyoConnector.Disconnect();
        }
    }

    // Toggle the microphone privacy
    public void MicrophonePrivacyButtonPressed(View v) {
        mVidyoConnector.SetMicrophonePrivacy(((ToggleButton) v).isChecked());
    }

    // Toggle the camera privacy
    public void CameraPrivacyButtonPressed(View v) {
        mVidyoConnector.SetCameraPrivacy(((ToggleButton) v).isChecked());
    }

    // Handle the camera swap button being pressed. Cycle the camera.
    public void CameraSwapButtonPressed(View v) {
        mVidyoConnector.CycleCamera();
    }
}

What I want to do is time each of the vidyo conference session. For this, I need to differentiate between the user who created the room (User1 in this case) and other Participants who joined the room later. By knowing this, I can time the session based on User1’s status. When the android client of User1 receives a call to OnSuccess method of VidyoConnector.IConnect interface (upon initiating a connection to the vidyo conference room via VidyoConnector.Connect), I start a timer and when the android client of User1 receives a call to OnDisconnected I stop the timer to get the total time of the session.

The issue right now is that I cannot find a way to differentiate between the User who created the room and the Participants who joined the room later. Everyone is depicted as a Participant right now. One more thing that I have noticed is that as long as there is someone present in the conference room, the room stays alive even if the user who created the room leaves it. So my questions would be:

  1. Is there a way to differentiate between the User who created the room and other Participants that join the room later?

  2. Is there a way to destroy the room and kick out all the Participants from the room when the User that created the room exits?

I understand that it might be possible to track all these info on my backend server but I want to know if it would be possible to do so on the android client.

Thanks.


Solution

  • The token should be generated for each user. So your work flow should be as follows -

    1. User1 wants to make a vidyo conference call from an android device. So User1 will ask for, and get a new token from my backend server using an api.
    2. User1 will then send an invitation to other participants to join the conference call with the "Resource Id" used in the Connect call.
    3. Each participant will get their own token, but will use the "Resource Id" shared by User1, in the Connect method.

    To differentiate the user and the participant, you will have to add code in your android app. When user1 gets the "OnParticipantJoined" it can call "GetUserId" to check if it matches its own userId or if it is a different participant. Regarding booting out participants, in the current version of Vidyo.io there is no way to kick out participants from your room. One way of implementing this in your app code is by wrapping the chat methods. So if User1 is leaving its own room it can send a "Leave" message using the "SendChatMessage" to all other participants. Add some secret prefix in the string like "xyz_Leave", so that your app code on the participant side knows not to display this message but to act on it.