使用 android Vidyo SDK 跟踪 `Vidyo` 会议参与者

Track `Vidyo` conference participants with android Vidyo SDK

我正在使用 Vidyo.io 服务将视频会议集成到我的 android 应用程序中。我已经能够使用 android-vidyo-sdk 附带的 VidyoConnector 示例应用程序成功实施视频会议。 但是,我想了解更多有关会议室和加入房间的参与者的信息。

我想象中的工作流程如下:

  1. 用户 1 想通过 android 设备进行 vidyo 电话会议。因此 User1 将使用 api 从我的后端服务器请求并获取一个新令牌。
  2. User1 将使用步骤 1 中获得的令牌和相应的 vidyo 会议室 resourceId 向其他参与者发送加入电话会议的邀请。
  3. 其他参与者将使用在步骤 2 中从 User1 获得的信息,并能够加入会议室。

到目前为止,我已参考 VidyoConnector 示例应用程序创建了一个名为 VideoChatActivity 的 activity,方法是对原始 VidyoConnector 示例应用程序的 MainActivity. VideoChatActivity 将通过 Intent 从我的应用程序中的另一个 activity 调用所有必要的信息来启动 vidyo 连接,如令牌、资源 ID、用户名等。跟踪每个的状态参与者 我已经实现了 VidyoConnector.IRegisterParticipantEventListener。请参考下面我的代码:

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();
    }
}

我想做的是为每次 vidyo 会议计时 session。为此,我需要区分创建房间的用户(在本例中为 User1)和后来加入房间的其他参与者。通过了解这一点,我可以根据 User1 的状态为 session 计时。当 User1 的 android 客户端收到对 VidyoConnector.IConnect 接口的 OnSuccess 方法的调用时(通过 VidyoConnector.Connect 启动到 vidyo 会议室的连接),我启动一个计时器并当 User1 的 android 客户端收到对 OnDisconnected 的调用时,我停止计时器以获取 session.

的总时间

现在的问题是我找不到区分创建房间的用户和后来加入房间的参与者的方法。现在每个人都被描绘成参与者。我注意到的另一件事是,只要会议室中有人,即使创建房间的用户离开,房间也会保持活动状态。 所以我的问题是:

  1. 有没有办法区分创建房间的 User 和后来加入房间的其他 Participants

  2. 有没有办法在创建房间的用户退出时破坏房间并将所有参与者踢出房间?

我知道可以在我的后端服务器上跟踪所有这些信息,但我想知道是否可以在 android 客户端上这样做。

谢谢。

应该为每个用户生成令牌。所以你的工作流程应该如下 -

  1. 用户 1 想通过 android 设备进行 vidyo 电话会议。因此 User1 将使用 api.
  2. 从我的后端服务器请求并获取一个新令牌
  3. 用户 1 然后将向其他参与者发送邀请以加入电话会议,并使用 Connect 通话中使用的 "Resource Id"
  4. 每个参与者都将获得自己的令牌,但会在 Connect 方法中使用 User1 共享的 "Resource Id"。

要区分用户和参与者,您必须在 android 应用中添加代码。当 user1 获得 "OnParticipantJoined" 时,它可以调用 "GetUserId" 来检查它是否匹配自己的 userId 或者它是否是不同的参与者。 关于踢出参与者,在当前版本的 Vidyo.io 中,无法将参与者踢出您的房间。在您的应用程序代码中实现这一点的一种方法是包装聊天方法。因此,如果 User1 离开自己的房间,它可以使用 "SendChatMessage" 向所有其他参与者发送 "Leave" 消息。在字符串中添加一些秘密前缀,例如 "xyz_Leave",以便参与者端的应用程序代码知道不显示此消息而是对其执行操作。