В моем приложении я использую видеочат Twilio. Соединение установлено, но дело в том, что другой участник добавляет, что не может уведомить предыдущего пользователя, который уже подключен.
все сделано, но главная проблема - соединение между удаленным пользователем, с которым они не могут соединиться. если я использую два идентичных пользователя, отключите предыдущего пользователя и подключите нового к серверу. Пожалуйста, помогите мне. Это мой код
class VideoChatActivity : AppCompatActivity(), Room.Listener, KodeinAware, ApiListener {
private val audioManager by lazy {
this@VideoChatActivity.getSystemService(Context.AUDIO_SERVICE) as AudioManager
}
private val cameraCapturerCompat by lazy {
CameraCapturerCompat(this, getAvailableCameraSource())
}
private var previousAudioMode = 0
private var room: Room? = null
private var previousMicrophoneMute = false
private lateinit var localVideoView: VideoRenderer
val VIDEO_CODEC_NAMES = arrayOf(Vp8Codec.NAME, H264Codec.NAME, Vp9Codec.NAME)
val AUDIO_CODEC_NAMES = arrayOf(
IsacCodec.NAME, OpusCodec.NAME, PcmaCodec.NAME,
PcmuCodec.NAME, G722Codec.NAME
)
private val TAG = "MAin"
private var participantIdentity: String? = null
private lateinit var videoStatusTextView: TextView
private var localParticipant: LocalParticipant? = null
private val audioCodec: AudioCodec
get() {
val audioCodecName = OpusCodec.NAME
return when (audioCodecName) {
IsacCodec.NAME -> IsacCodec()
OpusCodec.NAME -> OpusCodec()
PcmaCodec.NAME -> PcmaCodec()
PcmuCodec.NAME -> PcmuCodec()
G722Codec.NAME -> G722Codec()
else -> OpusCodec()
}
}
private val videoCodec: VideoCodec
get() {
val videoCodecName = Vp8Codec.NAME
return when (videoCodecName) {
Vp8Codec.NAME -> {
val simulcast = false
Vp8Codec(simulcast)
}
H264Codec.NAME -> H264Codec()
Vp9Codec.NAME -> Vp9Codec()
else -> Vp8Codec()
}
}
private var localAudioTrack: LocalAudioTrack? = null
private var localVideoTrack: LocalVideoTrack? = null
private lateinit var videoView: VideoView
private lateinit var myVideoView: VideoView
//api
override val kodein by kodein()
private val factory: ChatViewModelFactory by instance()
private lateinit var viewModel: ChatViewModel
private var loadingDialog: LoadingDialog? = null
private var roomName = ""
private var roomToken = ""
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_video_chat)
viewModel = ViewModelProviders.of(this, factory).get(ChatViewModel::class.java)
viewModel.listener = this
loadingDialog = LoadingDialog(this)
videoView = findViewById(R.id.videoView)
videoStatusTextView = findViewById(R.id.videoStatusTextView)
myVideoView = findViewById(R.id.myVideoView)
myVideoView.mirror = true
viewModel.getVideoToken()
}
@SuppressLint("NewApi")
private fun CheckPermissions() {
if (ContextCompat.checkSelfPermission(
this,
Manifest.permission.CAMERA
) != PackageManager.PERMISSION_GRANTED ||
ContextCompat.checkSelfPermission(
this,
Manifest.permission.RECORD_AUDIO
) != PackageManager.PERMISSION_GRANTED
) {
requestPermissions(
arrayOf(
Manifest.permission.CAMERA,
Manifest.permission.RECORD_AUDIO
),
1001
)
return
} else {
OpenImageData()
}
}
private fun OpenImageData() {
audioManager.isSpeakerphoneOn = true
createAudioAndVideoTracks()
configureAudio(true)
val connectOptionsBuilder =
ConnectOptions.Builder(roomToken)
.roomName("DailyStandup")
.enableAutomaticSubscription(false)
localAudioTrack?.let { connectOptionsBuilder.audioTracks(listOf(it)) }
localVideoTrack?.let { connectOptionsBuilder.videoTracks(listOf(it)) }
localVideoTrack?.addRenderer(myVideoView)
connectOptionsBuilder.preferAudioCodecs(listOf(audioCodec))
connectOptionsBuilder.preferVideoCodecs(listOf(videoCodec))
connectOptionsBuilder.encodingParameters(EncodingParameters(0, 0))
room = Video.connect(this, connectOptionsBuilder.build(), this)
}
private fun configureAudio(enable: Boolean) {
with(audioManager) {
if (enable) {
previousAudioMode = audioManager.mode
// Request audio focus before making any device switch
requestAudioFocus()
/*
* Use MODE_IN_COMMUNICATION as the default audio mode. It is required
* to be in this mode when playout and/or recording starts for the best
* possible VoIP performance. Some devices have difficulties with
* speaker mode if this is not set.
*/
mode = AudioManager.MODE_IN_COMMUNICATION
/*
* Always disable microphone mute during a WebRTC call.
*/
previousMicrophoneMute = isMicrophoneMute
isMicrophoneMute = false
} else {
mode = previousAudioMode
abandonAudioFocus(null)
isMicrophoneMute = previousMicrophoneMute
}
}
}
private fun requestAudioFocus() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val playbackAttributes = AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION)
.setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
.build()
val focusRequest = AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT)
.setAudioAttributes(playbackAttributes)
.setAcceptsDelayedFocusGain(true)
.setOnAudioFocusChangeListener { }
.build()
audioManager.requestAudioFocus(focusRequest)
} else {
audioManager.requestAudioFocus(
null, AudioManager.STREAM_VOICE_CALL,
AudioManager.AUDIOFOCUS_GAIN_TRANSIENT
)
}
}
@SuppressLint("NewApi")
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<String>,
grantResults: IntArray
) {
when (requestCode) {
1001 -> {
var read = grantResults[0] == PackageManager.PERMISSION_GRANTED
var write = grantResults[1] == PackageManager.PERMISSION_GRANTED
if (grantResults.isNotEmpty() && read && write) {
OpenImageData()
} else if (Build.VERSION.SDK_INT >= 23 && !shouldShowRequestPermissionRationale(
Manifest.permission.CAMERA
) && !shouldShowRequestPermissionRationale(Manifest.permission.RECORD_AUDIO)
) {
rationale()
} else {
requestPermissions(
arrayOf(
Manifest.permission.CAMERA,
Manifest.permission.RECORD_AUDIO
),
1001
)
}
}
}
}
private fun rationale() {
val builder: AlertDialog.Builder =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
AlertDialog.Builder(
this,
android.R.style.Theme_Material_Light_Dialog_Alert
)
} else {
AlertDialog.Builder(this)
}
builder.setTitle("Mandatory Permissions")
.setMessage("Manually allow permissions in App settings")
.setPositiveButton("Proceed") { dialog, which ->
val intent = Intent()
intent.action = Settings.ACTION_APPLICATION_DETAILS_SETTINGS
val uri = Uri.fromParts("package", packageName, null)
intent.data = uri
startActivityForResult(intent, 1)
}
.setCancelable(false)
.setIcon(android.R.drawable.ic_dialog_alert)
.show()
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (resultCode == Activity.RESULT_OK) {
if (requestCode == 1) {
val read: Int = ContextCompat.checkSelfPermission(
this,
Manifest.permission.CAMERA
)
val write: Int = ContextCompat.checkSelfPermission(
this,
Manifest.permission.RECORD_AUDIO
)
if (read != PackageManager.PERMISSION_GRANTED || write != PackageManager.PERMISSION_GRANTED) {
rationale()
} else {
CheckPermissions()
}
}
}
}
private fun createAudioAndVideoTracks() {
// Share your microphone
localAudioTrack = LocalAudioTrack.create(this, true)
// Share your camera
localVideoTrack = LocalVideoTrack.create(
this,
true,
cameraCapturerCompat.videoCapturer
)
}
override fun onDestroy() {
super.onDestroy()
localVideoTrack?.release()
localAudioTrack?.release()
}
private fun getAvailableCameraSource(): CameraCapturer.CameraSource {
return if (CameraCapturer.isSourceAvailable(CameraCapturer.CameraSource.FRONT_CAMERA))
CameraCapturer.CameraSource.FRONT_CAMERA
else
CameraCapturer.CameraSource.BACK_CAMERA
}
override fun onRecordingStopped(room: Room) {
Log.e("VIDEO", "onRecordingStopped")
videoStatusTextView.text = "Recording Stop"
}
override fun onParticipantDisconnected(room: Room, remoteParticipant: RemoteParticipant) {
Log.e("VIDEO", "onParticipantDisconnected")
videoStatusTextView.text = "ParticipantDisconnected"
}
override fun onRecordingStarted(room: Room) {
Log.e("VIDEO", "onRecordingStarted")
videoStatusTextView.text = "onRecordingStarted"
}
override fun onConnectFailure(room: Room, twilioException: TwilioException) {
Log.e("VIDEO", "onConnectFailure" + twilioException.message)
videoStatusTextView.text = "onConnectFailure"
}
override fun onReconnected(room: Room) {
Log.e("VIDEO", "onReconnected")
videoStatusTextView.text = "onReconnected"
}
override fun onParticipantConnected(room: Room, remoteParticipant: RemoteParticipant) {
Log.e("VIDEO", "Particepate Connected")
videoStatusTextView.text = "onParticipantConnected"
addRemoteParticipant(remoteParticipant)
}
override fun onResume() {
super.onResume()
/*
* If the local video track was released when the app was put in the background, recreate.
*/
localVideoTrack = if (localVideoTrack == null) {
LocalVideoTrack.create(
this,
true,
cameraCapturerCompat.videoCapturer
)
} else {
localVideoTrack
}
localVideoTrack?.addRenderer(myVideoView)
audioManager.isSpeakerphoneOn = true
/*
* If connected to a Room then share the local video track.
*/
localVideoTrack?.let { localParticipant?.publishTrack(it) }
/*
* Update encoding parameters if they have changed.
*/
localParticipant?.setEncodingParameters(EncodingParameters(0, 0))
/*
* Route audio through cached value.
*/
audioManager.isSpeakerphoneOn = true
/*
* Update reconnecting UI
*/
room?.let {
// reconnectingProgressBar.visibility = if (it.state != Room.State.RECONNECTING)
// View.GONE else
// View.VISIBLE
videoStatusTextView.text = "Connected to ${it.name}"
}
}
override fun onConnected(room: Room) {
localParticipant = room.localParticipant
Log.e("VIDEO", "Connected")
Log.e("VIDEO", room.localParticipant.toString())
videoStatusTextView.text = "onConnected"
room.remoteParticipants.firstOrNull()?.let { addRemoteParticipant(it) }
}
override fun onDisconnected(room: Room, twilioException: TwilioException?) {
videoStatusTextView.text = "onDisconnected"
if (twilioException != null) {
Log.e("VIDEO", "Disonnected" + twilioException.message)
room.disconnect()
}
}
override fun onReconnecting(room: Room, twilioException: TwilioException) {
videoStatusTextView.text = "onReconnecting"
Log.e("VIDEO", "Reconnected" + twilioException.message)
}
private fun addRemoteParticipant(remoteParticipant: RemoteParticipant) {
Log.e("VIDEO", "ADD")
/*
* This app only displays video for one additional participant per Room
*/
// if (thumbnailVideoView.visibility == View.VISIBLE) {
// // Snackbar.make(connectActionFab,
// // "Multiple participants are not currently support in this UI",
// // Snackbar.LENGTH_LONG)
// // .setAction("Action", null).show()
// return
// }
participantIdentity = remoteParticipant.identity
//videoStatusTextView.text = "Participant $participantIdentity joined"
/*
* Add participant renderer
*/
remoteParticipant.remoteVideoTracks.firstOrNull()?.let { remoteVideoTrackPublication ->
if (remoteVideoTrackPublication.isTrackSubscribed) {
remoteVideoTrackPublication.remoteVideoTrack?.let { addRemoteParticipantVideo(it) }
}
}
/*
* Start listening for participant events
*/
remoteParticipant.setListener(participantListener)
}
private fun addRemoteParticipantVideo(videoTrack: VideoTrack) {
// moveLocalVideoToThumbnailView()
primaryVideoView.mirror = false
videoTrack.addRenderer(primaryVideoView)
}
private val participantListener = object : RemoteParticipant.Listener {
override fun onAudioTrackPublished(
remoteParticipant: RemoteParticipant,
remoteAudioTrackPublication: RemoteAudioTrackPublication
) {
Log.i(
TAG, "onAudioTrackPublished: " +
"[RemoteParticipant: identity=${remoteParticipant.identity}], " +
"[RemoteAudioTrackPublication: sid=${remoteAudioTrackPublication.trackSid}, " +
"enabled=${remoteAudioTrackPublication.isTrackEnabled}, " +
"subscribed=${remoteAudioTrackPublication.isTrackSubscribed}, " +
"name=${remoteAudioTrackPublication.trackName}]"
)
videoStatusTextView.text = "onAudioTrackAdded"
}
override fun onAudioTrackUnpublished(
remoteParticipant: RemoteParticipant,
remoteAudioTrackPublication: RemoteAudioTrackPublication
) {
Log.i(
TAG, "onAudioTrackUnpublished: " +
"[RemoteParticipant: identity=${remoteParticipant.identity}], " +
"[RemoteAudioTrackPublication: sid=${remoteAudioTrackPublication.trackSid}, " +
"enabled=${remoteAudioTrackPublication.isTrackEnabled}, " +
"subscribed=${remoteAudioTrackPublication.isTrackSubscribed}, " +
"name=${remoteAudioTrackPublication.trackName}]"
)
videoStatusTextView.text = "onAudioTrackRemoved"
}
override fun onDataTrackPublished(
remoteParticipant: RemoteParticipant,
remoteDataTrackPublication: RemoteDataTrackPublication
) {
Log.i(
TAG, "onDataTrackPublished: " +
"[RemoteParticipant: identity=${remoteParticipant.identity}], " +
"[RemoteDataTrackPublication: sid=${remoteDataTrackPublication.trackSid}, " +
"enabled=${remoteDataTrackPublication.isTrackEnabled}, " +
"subscribed=${remoteDataTrackPublication.isTrackSubscribed}, " +
"name=${remoteDataTrackPublication.trackName}]"
)
videoStatusTextView.text = "onDataTrackPublished"
}
override fun onDataTrackUnpublished(
remoteParticipant: RemoteParticipant,
remoteDataTrackPublication: RemoteDataTrackPublication
) {
Log.i(
TAG, "onDataTrackUnpublished: " +
"[RemoteParticipant: identity=${remoteParticipant.identity}], " +
"[RemoteDataTrackPublication: sid=${remoteDataTrackPublication.trackSid}, " +
"enabled=${remoteDataTrackPublication.isTrackEnabled}, " +
"subscribed=${remoteDataTrackPublication.isTrackSubscribed}, " +
"name=${remoteDataTrackPublication.trackName}]"
)
videoStatusTextView.text = "onDataTrackUnpublished"
}
override fun onVideoTrackPublished(
remoteParticipant: RemoteParticipant,
remoteVideoTrackPublication: RemoteVideoTrackPublication
) {
Log.i(
TAG, "onVideoTrackPublished: " +
"[RemoteParticipant: identity=${remoteParticipant.identity}], " +
"[RemoteVideoTrackPublication: sid=${remoteVideoTrackPublication.trackSid}, " +
"enabled=${remoteVideoTrackPublication.isTrackEnabled}, " +
"subscribed=${remoteVideoTrackPublication.isTrackSubscribed}, " +
"name=${remoteVideoTrackPublication.trackName}]"
)
videoStatusTextView.text = "onVideoTrackPublished"
}
override fun onVideoTrackUnpublished(
remoteParticipant: RemoteParticipant,
remoteVideoTrackPublication: RemoteVideoTrackPublication
) {
Log.i(
TAG, "onVideoTrackUnpublished: " +
"[RemoteParticipant: identity=${remoteParticipant.identity}], " +
"[RemoteVideoTrackPublication: sid=${remoteVideoTrackPublication.trackSid}, " +
"enabled=${remoteVideoTrackPublication.isTrackEnabled}, " +
"subscribed=${remoteVideoTrackPublication.isTrackSubscribed}, " +
"name=${remoteVideoTrackPublication.trackName}]"
)
videoStatusTextView.text = "onVideoTrackUnpublished"
}
override fun onAudioTrackSubscribed(
remoteParticipant: RemoteParticipant,
remoteAudioTrackPublication: RemoteAudioTrackPublication,
remoteAudioTrack: RemoteAudioTrack
) {
Log.i(
TAG, "onAudioTrackSubscribed: " +
"[RemoteParticipant: identity=${remoteParticipant.identity}], " +
"[RemoteAudioTrack: enabled=${remoteAudioTrack.isEnabled}, " +
"playbackEnabled=${remoteAudioTrack.isPlaybackEnabled}, " +
"name=${remoteAudioTrack.name}]"
)
videoStatusTextView.text = "onAudioTrackSubscribed"
}
override fun onAudioTrackUnsubscribed(
remoteParticipant: RemoteParticipant,
remoteAudioTrackPublication: RemoteAudioTrackPublication,
remoteAudioTrack: RemoteAudioTrack
) {
Log.i(
TAG, "onAudioTrackUnsubscribed: " +
"[RemoteParticipant: identity=${remoteParticipant.identity}], " +
"[RemoteAudioTrack: enabled=${remoteAudioTrack.isEnabled}, " +
"playbackEnabled=${remoteAudioTrack.isPlaybackEnabled}, " +
"name=${remoteAudioTrack.name}]"
)
videoStatusTextView.text = "onAudioTrackUnsubscribed"
}
override fun onAudioTrackSubscriptionFailed(
remoteParticipant: RemoteParticipant,
remoteAudioTrackPublication: RemoteAudioTrackPublication,
twilioException: TwilioException
) {
Log.i(
TAG, "onAudioTrackSubscriptionFailed: " +
"[RemoteParticipant: identity=${remoteParticipant.identity}], " +
"[RemoteAudioTrackPublication: sid=${remoteAudioTrackPublication.trackSid}, " +
"name=${remoteAudioTrackPublication.trackName}]" +
"[TwilioException: code=${twilioException.code}, " +
"message=${twilioException.message}]"
)
videoStatusTextView.text = "onAudioTrackSubscriptionFailed"
}
override fun onDataTrackSubscribed(
remoteParticipant: RemoteParticipant,
remoteDataTrackPublication: RemoteDataTrackPublication,
remoteDataTrack: RemoteDataTrack
) {
Log.i(
TAG, "onDataTrackSubscribed: " +
"[RemoteParticipant: identity=${remoteParticipant.identity}], " +
"[RemoteDataTrack: enabled=${remoteDataTrack.isEnabled}, " +
"name=${remoteDataTrack.name}]"
)
videoStatusTextView.text = "onDataTrackSubscribed"
}
override fun onDataTrackUnsubscribed(
remoteParticipant: RemoteParticipant,
remoteDataTrackPublication: RemoteDataTrackPublication,
remoteDataTrack: RemoteDataTrack
) {
Log.i(
TAG, "onDataTrackUnsubscribed: " +
"[RemoteParticipant: identity=${remoteParticipant.identity}], " +
"[RemoteDataTrack: enabled=${remoteDataTrack.isEnabled}, " +
"name=${remoteDataTrack.name}]"
)
videoStatusTextView.text = "onDataTrackUnsubscribed"
}
override fun onDataTrackSubscriptionFailed(
remoteParticipant: RemoteParticipant,
remoteDataTrackPublication: RemoteDataTrackPublication,
twilioException: TwilioException
) {
Log.i(
TAG, "onDataTrackSubscriptionFailed: " +
"[RemoteParticipant: identity=${remoteParticipant.identity}], " +
"[RemoteDataTrackPublication: sid=${remoteDataTrackPublication.trackSid}, " +
"name=${remoteDataTrackPublication.trackName}]" +
"[TwilioException: code=${twilioException.code}, " +
"message=${twilioException.message}]"
)
videoStatusTextView.text = "onDataTrackSubscriptionFailed"
}
override fun onVideoTrackSubscribed(
remoteParticipant: RemoteParticipant,
remoteVideoTrackPublication: RemoteVideoTrackPublication,
remoteVideoTrack: RemoteVideoTrack
) {
Log.i(
TAG, "onVideoTrackSubscribed: " +
"[RemoteParticipant: identity=${remoteParticipant.identity}], " +
"[RemoteVideoTrack: enabled=${remoteVideoTrack.isEnabled}, " +
"name=${remoteVideoTrack.name}]"
)
videoStatusTextView.text = "onVideoTrackSubscribed"
addRemoteParticipantVideo(remoteVideoTrack)
}
override fun onVideoTrackUnsubscribed(
remoteParticipant: RemoteParticipant,
remoteVideoTrackPublication: RemoteVideoTrackPublication,
remoteVideoTrack: RemoteVideoTrack
) {
Log.i(
TAG, "onVideoTrackUnsubscribed: " +
"[RemoteParticipant: identity=${remoteParticipant.identity}], " +
"[RemoteVideoTrack: enabled=${remoteVideoTrack.isEnabled}, " +
"name=${remoteVideoTrack.name}]"
)
videoStatusTextView.text = "onVideoTrackUnsubscribed"
removeParticipantVideo(remoteVideoTrack)
}
override fun onVideoTrackSubscriptionFailed(
remoteParticipant: RemoteParticipant,
remoteVideoTrackPublication: RemoteVideoTrackPublication,
twilioException: TwilioException
) {
Log.i(
TAG, "onVideoTrackSubscriptionFailed: " +
"[RemoteParticipant: identity=${remoteParticipant.identity}], " +
"[RemoteVideoTrackPublication: sid=${remoteVideoTrackPublication.trackSid}, " +
"name=${remoteVideoTrackPublication.trackName}]" +
"[TwilioException: code=${twilioException.code}, " +
"message=${twilioException.message}]"
)
videoStatusTextView.text = "onVideoTrackSubscriptionFailed"
}
override fun onAudioTrackEnabled(
remoteParticipant: RemoteParticipant,
remoteAudioTrackPublication: RemoteAudioTrackPublication
) {
}
override fun onVideoTrackEnabled(
remoteParticipant: RemoteParticipant,
remoteVideoTrackPublication: RemoteVideoTrackPublication
) {
}
override fun onVideoTrackDisabled(
remoteParticipant: RemoteParticipant,
remoteVideoTrackPublication: RemoteVideoTrackPublication
) {
}
override fun onAudioTrackDisabled(
remoteParticipant: RemoteParticipant,
remoteAudioTrackPublication: RemoteAudioTrackPublication
) {
}
}
private fun removeParticipantVideo(videoTrack: VideoTrack) {
videoTrack.removeRenderer(videoView)
}
override fun onStarted() {
loadingDialog?.setLoading(true)
}
override fun <T> onSuccess(response: T) {
loadingDialog?.setLoading(false)
var data = response as ApiResponse<VideoTokenResult>
roomName = data.result?.Room.toString()
roomToken = data.result?.Token.toString()
CheckPermissions()
}
override fun onFailer(message: String) {
loadingDialog?.setLoading(false)
}
override fun onUnauthorize(message: String, code: Int) {
loadingDialog?.setLoading(false)
}
}
Нельзя перейти в AddRemoteParticipant