1

我正在使用 Agora 进行视频通话功能。我已经为 1-1 视频通话运行了 agora 的示例代码,引用了这个 - https://docs.agora.io/en/Video/start_call_android?platform=Android

在示例中,我们有一个本地视图和一个远程视图,这运行良好。现在我想显示两个远程视图和一个本地视图。为此,我创建了具有两个远程视图和一个本地视图的布局。

同样在 onFirstRemoteVideoDecoded 方法中,我检查了是否添加了第一个远程视图,然后设置了第一个远程视图,否则设置了第二个远程视图。当用户离开时与 onUserOffline 相同我检查了第一个远程视图是否记录了运行方法 onRemoteUserLeft() 或 onRemoteUserLeft1() 删除相关视图。

要加入频道,我提供相同的频道名称和不同的令牌

`

var token1: String? = ("<token-string>")
    if (token1!!.isEmpty()) {
            token1 = null
    }
    mRtcEngine!!.joinChannel(
        token1,
        "67124678",
        "null",
        0
        )

我有一个提供频道名称和相关令牌的链接。https://mobile.quirkysemicolon.com/getTokens.php

我对如何在我的应用程序中使用此链接在同一频道上加入多个用户感到困惑。

谁能建议我如何实现此功能?

这是我的布局:

    <?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <FrameLayout
        android:id="@+id/remote_video_view_container"
        android:layout_width="0dp"
        android:layout_height="284dp"
        android:layout_alignParentTop="true"
        android:layout_alignParentEnd="true"
        android:layout_alignParentRight="true"
        android:background="@android:color/darker_gray"
        app:layout_constraintEnd_toStartOf="@+id/remote1_video_view_container"
        app:layout_constraintHorizontal_chainStyle="spread_inside"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />


    <FrameLayout
        android:id="@+id/remote1_video_view_container"
        android:layout_width="0dp"
        android:layout_height="284dp"
        android:layout_alignParentTop="true"
        android:layout_alignParentEnd="true"
        android:layout_alignParentRight="true"
        android:background="@android:color/darker_gray"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toEndOf="@+id/remote_video_view_container"
        app:layout_constraintTop_toTopOf="parent" />

    <FrameLayout
        android:id="@+id/local_video_view_container"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:background="@android:color/white"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toEndOf="@+id/container"
        app:layout_constraintTop_toBottomOf="@+id/remote1_video_view_container" />

    <RelativeLayout
        android:id="@+id/container"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:background="@android:color/white"
        android:padding="20dp"

        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintEnd_toStartOf="@+id/local_video_view_container"
        app:layout_constraintHorizontal_chainStyle="spread_inside"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/remote1_video_view_container">


        <TextView
            android:id="@+id/textView2"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_alignParentEnd="true"
            android:layout_alignParentRight="true"
            android:layout_gravity="center_horizontal"
            android:layout_marginTop="20dp"
            android:textSize="18sp"
            android:textColor="@android:color/black"
            android:text="Channel :" />

        <Button
            android:id="@+id/button"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="Start"
            android:textColor="@android:color/white"
            android:background="@android:color/holo_green_dark"
            android:layout_marginTop="20dp"
            android:layout_gravity="center_horizontal"
            android:layout_below="@id/textView2"/>

        <Button
            android:id="@+id/button2"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="End"
            android:onClick="onEndCallClicked"
            android:textColor="@android:color/white"
            android:background="@android:color/holo_red_dark"
            android:layout_marginTop="20dp"
            android:layout_gravity="center_horizontal"
            android:layout_below="@id/button"/>

        <TextView
            android:id="@+id/textView5"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_below="@id/button2"
            android:layout_marginTop="20dp"
            android:textSize="18sp"
            android:textColor="@android:color/black"
            android:layout_gravity="center_horizontal"
            android:text="Token(Push Notify) : " />

    </RelativeLayout>


</androidx.constraintlayout.widget.ConstraintLayout>

Ans 这是我的活动

class MultipleRemoteViewActivity: AppCompatActivity() {

    private var mRtcEngine: RtcEngine? = null
    private var mReceiver: BroadcastReceiver? = null
    var mIntentFilter: IntentFilter?  = null
    var mIsRemote1Joined: Boolean = false
    var mIsRemote1Left: Boolean = false

    private val mRtcEventHandler = object : IRtcEngineEventHandler() {
        /**
         * Occurs when the first remote video frame is received and decoded.
         * This callback is triggered in either of the following scenarios:
         *
         *     The remote user joins the channel and sends the video stream.
         *     The remote user stops sending the video stream and re-sends it after 15 seconds. Possible reasons include:
         *         The remote user leaves channel.
         *         The remote user drops offline.
         *         The remote user calls the muteLocalVideoStream method.
         *         The remote user calls the disableVideo method.
         *
         * @param uid User ID of the remote user sending the video streams.
         * @param width Width (pixels) of the video stream.
         * @param height Height (pixels) of the video stream.
         * @param elapsed Time elapsed (ms) from the local user calling the joinChannel method until this callback is triggered.
         */
        override fun onFirstRemoteVideoDecoded(uid: Int, width: Int, height: Int, elapsed: Int) {
            runOnUiThread {
                if(!mIsRemote1Joined)
                    setupRemoteVideo(uid)
                else
                    setupRemoteVideo1(uid)
            }
        }

        override fun onUserOffline(uid: Int, reason: Int) {
            runOnUiThread {
                if(!mIsRemote1Left)
                    onRemoteUserLeft()
                else
                    onRemoteUserLeft1()
            }
        }

        override fun onUserMuteVideo(uid: Int, muted: Boolean) {
            runOnUiThread { onRemoteUserVideoMuted(uid, muted) }
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.multiple_remoteview_layout)

        if (checkSelfPermission(Manifest.permission.RECORD_AUDIO,
                MultipleRemoteViewActivity.PERMISSION_REQ_ID_RECORD_AUDIO
            ) && checkSelfPermission(
                Manifest.permission.CAMERA, MultipleRemoteViewActivity.PERMISSION_REQ_ID_CAMERA
            )) {
            initAgoraEngineAndJoinChannel()
        }

    }

    private fun initAgoraEngineAndJoinChannel() {
        initializeAgoraEngine()

    }

    private fun checkSelfPermission(permission: String, requestCode: Int): Boolean {
        Log.i(MultipleRemoteViewActivity.LOG_TAG, "checkSelfPermission $permission $requestCode")
        if (ContextCompat.checkSelfPermission(
                this,
                permission
            ) != PackageManager.PERMISSION_GRANTED) {

            ActivityCompat.requestPermissions(
                this,
                arrayOf(permission),
                requestCode
            )
            return false
        }
        return true
    }

    override fun onRequestPermissionsResult(
        requestCode: Int,
        permissions: Array<String>, grantResults: IntArray
    ) {
        Log.i(MultipleRemoteViewActivity.LOG_TAG, "onRequestPermissionsResult " + grantResults[0] + " " + requestCode)

        when (requestCode) {
            MultipleRemoteViewActivity.PERMISSION_REQ_ID_RECORD_AUDIO -> {
                if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                    checkSelfPermission(Manifest.permission.CAMERA,
                        MultipleRemoteViewActivity.PERMISSION_REQ_ID_CAMERA
                    )
                } else {
                    showLongToast("No permission for " + Manifest.permission.RECORD_AUDIO)
                    finish()
                }
            }
            MultipleRemoteViewActivity.PERMISSION_REQ_ID_CAMERA -> {
                if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                    initAgoraEngineAndJoinChannel()
                } else {
                    showLongToast("No permission for " + Manifest.permission.CAMERA)
                    finish()
                }
            }
        }
    }

    private fun showLongToast(msg: String) {
        this.runOnUiThread { Toast.makeText(applicationContext, msg, Toast.LENGTH_LONG).show() }
    }

    override fun onDestroy() {
        super.onDestroy()

        leaveChannel()
        /*
          Destroys the RtcEngine instance and releases all resources used by the Agora SDK.

          This method is useful for apps that occasionally make voice or video calls,
          to free up resources for other operations when not making calls.
         */
        RtcEngine.destroy()
        mRtcEngine = null
    }

    fun onLocalVideoMuteClicked(view: View) {
      /*  val iv = view as ImageView
        if (iv.isSelected) {
            iv.isSelected = false
            iv.clearColorFilter()
        } else {
            iv.isSelected = true
            iv.setColorFilter(resources.getColor(R.color.colorPrimary), PorterDuff.Mode.MULTIPLY)

        }

        // Stops/Resumes sending the local video stream.
        mRtcEngine!!.muteLocalVideoStream(iv.isSelected)*/

        val container = findViewById(R.id.local_video_view_container) as FrameLayout
        val surfaceView = container.getChildAt(0) as SurfaceView


      //  surfaceView.setZOrderMediaOverlay(!iv.isSelected) // surfaceView.visibility = if (iv.isSelected) View.GONE else View.VISIBLE
    }

/*    fun onLocalAudioMuteClicked(view: View) {
        val iv = view as ImageView
        if (iv.isSelected) {
            iv.isSelected = false
            iv.clearColorFilter()
        } else {
            iv.isSelected = true
            iv.setColorFilter(resources.getColor(R.color.colorPrimary), PorterDuff.Mode.MULTIPLY)
        }

        // Stops/Resumes sending the local audio stream.
        mRtcEngine!!.muteLocalAudioStream(iv.isSelected)
    }*/

/*
    fun onSwitchCameraClicked(view: View) {
        // Switches between front and rear cameras.
        mRtcEngine!!.switchCamera()
    }
*/

    fun onEndCallClicked(view: View) {
        finish()
    }

    private fun initializeAgoraEngine() {
        try {
            mRtcEngine = RtcEngine.create(
                baseContext,
                getString(R.string.agora_app_id),
                mRtcEventHandler
            )

            setupVideoProfile()
            setupLocalVideo()
            joinChannel()

        } catch (e: Exception) {
            Log.e(MultipleRemoteViewActivity.LOG_TAG, Log.getStackTraceString(e))

            throw RuntimeException(
                "NEED TO check rtc sdk init fatal error\n" + Log.getStackTraceString(e)
            )
        }
    }

    private fun setupVideoProfile() {
        // In simple use cases, we only need to enable video capturing
        // and rendering once at the initialization step.
        // Note: audio recording and playing is enabled by default.
        mRtcEngine!!.enableVideo()
//      mRtcEngine!!.setVideoProfile(Constants.VIDEO_PROFILE_360P, false) // Earlier than 2.3.0

        // Please go to this page for detailed explanation
        // https://docs.agora.io/en/Video/API%20Reference/java/classio_1_1agora_1_1rtc_1_1_rtc_engine.html#af5f4de754e2c1f493096641c5c5c1d8f
        mRtcEngine!!.setVideoEncoderConfiguration(
            VideoEncoderConfiguration(
                VideoEncoderConfiguration.VD_640x360,
                VideoEncoderConfiguration.FRAME_RATE.FRAME_RATE_FPS_15,
                VideoEncoderConfiguration.STANDARD_BITRATE,
                VideoEncoderConfiguration.ORIENTATION_MODE.ORIENTATION_MODE_FIXED_PORTRAIT
            )
        )
    }

    private fun setupLocalVideo() {

        val container = findViewById(R.id.local_video_view_container) as FrameLayout
        val surfaceView = RtcEngine.CreateRendererView(baseContext)
        surfaceView.setZOrderMediaOverlay(true)
        container.addView(surfaceView)
        // Initializes the local video view.
        // RENDER_MODE_FIT: Uniformly scale the video until one of its dimension fits the boundary. Areas that are not filled due to the disparity in the aspect ratio are filled with black.
        mRtcEngine!!.setupLocalVideo(VideoCanvas(surfaceView, VideoCanvas.RENDER_MODE_FIT, 0))
    }

    private fun joinChannel() {
        // 1. Users can only see each other after they join the
        // same channel successfully using the same app id.
        // 2. One token is only valid for the channel name that
        // you use to generate this token.
        var token: String? = getString(R.string.agora_access_token)
        if (token!!.isEmpty()) {
            token = null
        }
        mRtcEngine!!.joinChannel(
            token,
            "67124678",
            "null",
            0
        ) // if you do not specify the uid, we will generate the uid for you

        var token1: String? = ("<token-string>")
        if (token1!!.isEmpty()) {
                token1 = null
        }
        mRtcEngine!!.joinChannel(
            token1,
            "67124678",
            "null",
            0
            ) // if you do not specify the uid, we will generate the uid for you
    }

    private fun setupRemoteVideo(uid: Int) {
        // Only one remote video view is available for this
        // tutorial. Here we check if there exists a surface
        // view tagged as this uid.
        val container = findViewById(R.id.remote_video_view_container) as FrameLayout

        if (container.childCount >= 1) {
            return
        }
        /*
          Creates the video renderer view.
          CreateRendererView returns the SurfaceView type. The operation and layout of the view
          are managed by the app, and the Agora SDK renders the view provided by the app.
          The video display view must be created using this method instead of directly
          calling SurfaceView.
         */
        val surfaceView = RtcEngine.CreateRendererView(baseContext)
        container.addView(surfaceView)
        // Initializes the video view of a remote user.
        mRtcEngine!!.setupRemoteVideo(VideoCanvas(surfaceView, VideoCanvas.RENDER_MODE_FIT, uid))

        surfaceView.tag = uid // for mark purpose

        mIsRemote1Joined = true

      //  val tipMsg = findViewById<TextView>(R.id.quick_tips_when_use_agora_sdk) // optional UI
       // tipMsg.visibility = View.GONE
    }

    private fun setupRemoteVideo1(uid: Int) {
        // Only one remote video view is available for this
        // tutorial. Here we check if there exists a surface
        // view tagged as this uid.
        val container1 = findViewById(R.id.remote1_video_view_container) as FrameLayout

        if (container1.childCount >= 1) {
            return
        }

        /*
          Creates the video renderer view.
          CreateRendererView returns the SurfaceView type. The operation and layout of the view
          are managed by the app, and the Agora SDK renders the view provided by the app.
          The video display view must be created using this method instead of directly
          calling SurfaceView.
         */

        val surfaceView1 = RtcEngine.CreateRendererView(baseContext)
        container1.addView(surfaceView1)
        // Initializes the video view of a remote user.
        mRtcEngine!!.setupRemoteVideo(VideoCanvas(surfaceView1, VideoCanvas.RENDER_MODE_FIT, uid))

        surfaceView1.tag = uid // for mark purpose

        //  val tipMsg = findViewById<TextView>(R.id.quick_tips_when_use_agora_sdk) // optional UI
        // tipMsg.visibility = View.GONE
    }

    private fun leaveChannel() {
        mRtcEngine!!.leaveChannel()
    }

    private fun onRemoteUserLeft() {
        val container = findViewById(R.id.remote_video_view_container) as FrameLayout
        container.removeAllViews()
        mIsRemote1Left = true
    }

    private fun onRemoteUserLeft1() {
        val container1 = findViewById(R.id.remote1_video_view_container) as FrameLayout
        container1.removeAllViews()
    }

    private fun onRemoteUserVideoMuted(uid: Int, muted: Boolean) {
        val container = findViewById(R.id.remote_video_view_container) as FrameLayout

        val surfaceView = container.getChildAt(0) as? SurfaceView

        val tag = surfaceView?.tag
        if (tag != null && tag as Int == uid) {
            surfaceView.visibility = if (muted) View.GONE else View.VISIBLE
        }

        val container1 = findViewById(R.id.remote1_video_view_container) as FrameLayout

        val surfaceView1 = container1.getChildAt(0) as? SurfaceView

        val tag1 = surfaceView1?.tag
        if (tag1 != null && tag1 as Int == uid) {
            surfaceView1.visibility = if (muted) View.GONE else View.VISIBLE
        }
    }

    companion object {

        private val LOG_TAG = MultipleRemoteViewActivity::class.java.simpleName

        private const val PERMISSION_REQ_ID_RECORD_AUDIO = 22
        private const val PERMISSION_REQ_ID_CAMERA = PERMISSION_REQ_ID_RECORD_AUDIO + 1
    }
}

现在我的代码中的问题是:我在三个设备上加入具有相同应用程序 ID 的同一频道,一个是 web-chrome 应用程序,另外两个在 android 设备上。当我从两台设备加入时,我可以看到一个远程视图和一个本地视图,但是当这三个设备加入同一个频道并且如果其中一个设备离开频道时,两个远程视图都停止工作并且无法加入相同的频道再次..

请提出解决方案...

4

1 回答 1

0

您可以查看我们的群组视频示例应用供您参考:https ://github.com/AgoraIO/Basic-Video-Call/tree/master/Group-Video/OpenVideoCall-Android

一般来说,如果你想让多人加入同一个频道,你需要确保他们有相同的 app id 和频道名称。

关于令牌,每个加入频道的用户都需要有单独的 UID,这也意味着他们的令牌会有所不同。

因此工作流程将如下所示:

用户 A 想要加入频道 -> 用户 A 向后端发送请求 -> 服务器生成随机 UID 并使用该 UID 生成新令牌 -> 服务器返回生成的 UID 和令牌 -> 用户 A 使用这些凭据加入频道

加入频道的每个后续用户都将使用相同的工作流程。请记住,对于这个工作流程,UID 是由您自己生成的,而不是由 Agora 随机生成的(即您不应该像您所做的那样将 0 作为 UID 传递)。

于 2020-11-06T03:39:46.337 回答