Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
147 changes: 113 additions & 34 deletions app/src/main/java/com/nextcloud/talk/activities/CallActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,7 @@ import org.webrtc.VideoSource
import org.webrtc.VideoTrack
import java.io.IOException
import java.util.Objects
import java.util.concurrent.CopyOnWriteArrayList
import java.util.concurrent.TimeUnit
import java.util.concurrent.atomic.AtomicInteger
import javax.inject.Inject
Expand Down Expand Up @@ -248,7 +249,7 @@ class CallActivity : CallBaseActivity() {
private var callSession: String? = null
private var localStream: MediaStream? = null
private var credentials: String? = null
private val peerConnectionWrapperList: MutableList<PeerConnectionWrapper> = ArrayList()
private val peerConnectionWrapperList: MutableList<PeerConnectionWrapper> = CopyOnWriteArrayList()
private var videoOn = false
private var microphoneOn = false
var isVoiceOnlyCall = false
Expand Down Expand Up @@ -318,9 +319,23 @@ class CallActivity : CallBaseActivity() {
private var conversationPassword: String? = null
private var powerManagerUtils: PowerManagerUtils? = null
private var handler: Handler? = null

private val callingTimeoutRunnable = Runnable { setCallState(CallStatus.CALLING_TIMEOUT) }

@Volatile
private var currentCallStatus: CallStatus? = null

private var mediaPlayer: MediaPlayer? = null

@Volatile
private var callingSoundRequested = false

@Volatile
private var audioRouteReady = false

@Volatile
private var remoteAudioPlayoutEnabled = false

private var binding: CallActivityBinding? = null
private var audioOutputDialog: AudioOutputDialog? = null
private var moreCallActionsDialog: MoreCallActionsDialog? = null
Expand Down Expand Up @@ -921,15 +936,17 @@ class CallActivity : CallBaseActivity() {
fun setDefaultAudioOutputChannel(selectedAudioDevice: AudioDevice?) {
if (audioManager != null) {
audioManager!!.setDefaultAudioDevice(selectedAudioDevice)
updateAudioOutputButton(audioManager!!.currentAudioDevice)
updateAudioOutputButton(audioManager!!.audioDeviceForUi)
}
}

fun setAudioOutputChannel(selectedAudioDevice: AudioDevice?) {
if (audioManager != null) {
audioManager!!.selectAudioDevice(selectedAudioDevice)
updateAudioOutputButton(audioManager!!.currentAudioDevice)
fun setAudioOutputChannel(selectedAudioDevice: AudioDevice?): Boolean {
val activeAudioManager = audioManager ?: return false
val accepted = activeAudioManager.selectAudioDevice(selectedAudioDevice)
if (accepted) {
updateAudioOutputButton(activeAudioManager.audioDeviceForUi)
}
return accepted
}

private fun updateAudioOutputButton(activeAudioDevice: AudioDevice) {
Expand Down Expand Up @@ -1054,7 +1071,7 @@ class CallActivity : CallBaseActivity() {
}

private fun prepareCall() {
stopCallingSound()
releaseCallingSound()
basicInitialization()
initViews()
// updateSelfVideoViewPosition(true)
Expand Down Expand Up @@ -1132,6 +1149,13 @@ class CallActivity : CallBaseActivity() {

private fun onAudioManagerDevicesChanged(currentDevice: AudioDevice, availableDevices: Set<AudioDevice>) {
Log.d(TAG, "onAudioManagerDevicesChanged: $availableDevices, currentDevice: $currentDevice")
audioRouteReady = audioManager?.isAudioRouteReady == true
updateRemoteAudioPlayout()
if (audioRouteReady) {
maybeStartCallingSound()
} else {
releaseCallingSound()
}
val shouldDisableProximityLock =
currentDevice == AudioDevice.WIRED_HEADSET ||
currentDevice == AudioDevice.SPEAKER_PHONE ||
Expand All @@ -1144,7 +1168,7 @@ class CallActivity : CallBaseActivity() {
if (audioOutputDialog != null) {
audioOutputDialog!!.updateOutputDeviceList()
}
updateAudioOutputButton(currentDevice)
updateAudioOutputButton(audioManager?.audioDeviceForUi ?: currentDevice)
}

private fun cameraInitialization() {
Expand Down Expand Up @@ -2050,6 +2074,9 @@ class CallActivity : CallBaseActivity() {
audioSource = null
}
runOnUiThread {
audioRouteReady = false
remoteAudioPlayoutEnabled = false
peerConnectionWrapperList.forEach { it.setRemoteAudioPlayoutEnabled(false) }
if (audioManager != null) {
audioManager!!.stop()
audioManager = null
Expand Down Expand Up @@ -2431,6 +2458,7 @@ class CallActivity : CallBaseActivity() {
}
peerConnectionWrapper = createPeerConnectionWrapperForSessionIdAndType(publisher, sessionId, type)
peerConnectionWrapperList.add(peerConnectionWrapper)
peerConnectionWrapper.setRemoteAudioPlayoutEnabled(remoteAudioPlayoutEnabled)
if (!publisher) {
if (!callViewModel.doesParticipantExist(sessionId)) {
addCallParticipant(sessionId)
Expand Down Expand Up @@ -2674,13 +2702,14 @@ class CallActivity : CallBaseActivity() {
} else {
handler!!.removeCallbacksAndMessages(null)
}
handler!!.post { updateRemoteAudioPlayout() }
when (callState) {
CallStatus.CONNECTING -> handler!!.post { handleCallStateConnected() }
CallStatus.CALLING_TIMEOUT -> handler!!.post { handleCallStateCallingTimeout() }
CallStatus.PUBLISHER_FAILED -> handler!!.post { handleCallStatePublisherFailed() }
CallStatus.RECONNECTING -> handler!!.post { handleCallStateReconnecting() }
CallStatus.JOINED -> {
handler!!.postDelayed({ setCallState(CallStatus.CALLING_TIMEOUT) }, CALLING_TIMEOUT)
handler!!.postDelayed(callingTimeoutRunnable, CALLING_TIMEOUT)
handler!!.post { handleCallStateJoined() }
}

Expand Down Expand Up @@ -2757,7 +2786,7 @@ class CallActivity : CallBaseActivity() {
}

private fun handleCallStateReconnecting() {
playCallingSound()
stopCallingSound()
binding!!.callStates.callStateTextView.setText(R.string.nc_call_reconnecting)
if (binding!!.callStates.callStateRelativeLayout.visibility != View.VISIBLE) {
binding!!.callStates.callStateRelativeLayout.visibility = View.VISIBLE
Expand All @@ -2775,6 +2804,7 @@ class CallActivity : CallBaseActivity() {

private fun handleCallStatePublisherFailed() {
// No calling sound when the publisher failed
stopCallingSound()
binding!!.callStates.callStateTextView.setText(R.string.nc_call_reconnecting)
if (binding!!.callStates.callStateRelativeLayout.visibility != View.VISIBLE) {
binding!!.callStates.callStateRelativeLayout.visibility = View.VISIBLE
Expand Down Expand Up @@ -2809,7 +2839,7 @@ class CallActivity : CallBaseActivity() {
}

private fun handleCallStateConnected() {
playCallingSound()
requestCallingSound()
if (isIncomingCallFromNotification) {
binding!!.callStates.callStateTextView.setText(R.string.nc_call_incoming)
} else {
Expand All @@ -2830,46 +2860,95 @@ class CallActivity : CallBaseActivity() {
}
}

private fun playCallingSound() {
stopCallingSound()
private fun requestCallingSound() {
if (Looper.myLooper() != Looper.getMainLooper()) {
runOnUiThread { requestCallingSound() }
return
}
callingSoundRequested = true
maybeStartCallingSound()
}

private fun isRemoteAudioPlayoutAllowed(): Boolean =
(currentCallStatus === CallStatus.JOINED || currentCallStatus === CallStatus.IN_CONVERSATION) &&
audioRouteReady

private fun updateRemoteAudioPlayout() {
val enabled = isRemoteAudioPlayoutAllowed()
remoteAudioPlayoutEnabled = enabled
peerConnectionWrapperList.forEach { it.setRemoteAudioPlayoutEnabled(enabled) }
}

private fun maybeStartCallingSound() {
if (!callingSoundRequested || mediaPlayer != null || audioManager?.isAudioRouteReady != true) {
return
}
val ringtoneUri: Uri? = if (isIncomingCallFromNotification) {
getCallRingtoneUri(applicationContext, appPreferences)
} else {
("android.resource://" + applicationContext.packageName + "/raw/tr110_1_kap8_3_freiton1").toUri()
}
if (ringtoneUri != null) {
mediaPlayer = MediaPlayer()
val player = MediaPlayer()
mediaPlayer = player
try {
mediaPlayer!!.setDataSource(this, ringtoneUri)
mediaPlayer!!.isLooping = true
player.setDataSource(this, ringtoneUri)
player.isLooping = true
val audioAttributes = AudioAttributes.Builder().setContentType(
AudioAttributes.CONTENT_TYPE_SONIFICATION
)
.setUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION)
.build()
mediaPlayer!!.setAudioAttributes(audioAttributes)
mediaPlayer!!.setOnPreparedListener { mp: MediaPlayer? -> mediaPlayer!!.start() }
mediaPlayer!!.prepareAsync()
player.setAudioAttributes(audioAttributes)
player.setOnPreparedListener { preparedPlayer ->
if (mediaPlayer === preparedPlayer &&
callingSoundRequested &&
audioManager?.isAudioRouteReady == true
) {
preparedPlayer.start()
} else {
if (mediaPlayer === preparedPlayer) {
mediaPlayer = null
}
preparedPlayer.release()
}
}
player.prepareAsync()
} catch (e: IOException) {
Log.e(TAG, "Failed to play sound")
Log.e(TAG, "Failed to play sound", e)
if (mediaPlayer === player) {
mediaPlayer = null
}
player.release()
}
}
}

private fun stopCallingSound() {
if (mediaPlayer != null) {
try {
if (mediaPlayer!!.isPlaying) {
mediaPlayer!!.stop()
}
} catch (e: IllegalStateException) {
Log.e(TAG, "mediaPlayer was not initialized", e)
} finally {
if (mediaPlayer != null) {
mediaPlayer!!.release()
}
mediaPlayer = null
if (Looper.myLooper() != Looper.getMainLooper()) {
runOnUiThread { stopCallingSound() }
return
}
callingSoundRequested = false
releaseCallingSound()
}

private fun releaseCallingSound() {
if (Looper.myLooper() != Looper.getMainLooper()) {
runOnUiThread { releaseCallingSound() }
return
}
val player = mediaPlayer ?: return
mediaPlayer = null
player.setOnPreparedListener(null)
try {
if (player.isPlaying) {
player.stop()
}
} catch (e: IllegalStateException) {
Log.e(TAG, "mediaPlayer was not initialized", e)
} finally {
player.release()
}
}

Expand Down Expand Up @@ -3081,13 +3160,13 @@ class CallActivity : CallBaseActivity() {
fun onMessageEvent(networkEvent: NetworkEvent) {
if (networkEvent.networkConnectionEvent == NetworkEvent.NetworkConnectionEvent.NETWORK_CONNECTED) {
if (handler != null) {
handler!!.removeCallbacksAndMessages(null)
handler!!.removeCallbacks(callingTimeoutRunnable)
}
} else if (networkEvent.networkConnectionEvent ==
NetworkEvent.NetworkConnectionEvent.NETWORK_DISCONNECTED
) {
if (handler != null) {
handler!!.removeCallbacksAndMessages(null)
handler!!.removeCallbacks(callingTimeoutRunnable)
}
}
}
Expand Down
27 changes: 16 additions & 11 deletions app/src/main/java/com/nextcloud/talk/ui/dialog/AudioOutputDialog.kt
Original file line number Diff line number Diff line change
Expand Up @@ -44,25 +44,26 @@ class AudioOutputDialog(val callActivity: CallActivity) : BottomSheetDialog(call
}

fun updateOutputDeviceList() {
if (callActivity.audioManager?.audioDevices?.contains(WebRtcAudioManager.AudioDevice.BLUETOOTH) == false) {
val activeAudioManager = callActivity.audioManager
if (activeAudioManager?.audioDevices?.contains(WebRtcAudioManager.AudioDevice.BLUETOOTH) != true) {
dialogAudioOutputBinding.audioOutputBluetooth.visibility = View.GONE
} else {
dialogAudioOutputBinding.audioOutputBluetooth.visibility = View.VISIBLE
}

if (callActivity.audioManager?.audioDevices?.contains(WebRtcAudioManager.AudioDevice.EARPIECE) == false) {
if (activeAudioManager?.audioDevices?.contains(WebRtcAudioManager.AudioDevice.EARPIECE) != true) {
dialogAudioOutputBinding.audioOutputEarspeaker.visibility = View.GONE
} else {
dialogAudioOutputBinding.audioOutputEarspeaker.visibility = View.VISIBLE
}

if (callActivity.audioManager?.audioDevices?.contains(WebRtcAudioManager.AudioDevice.SPEAKER_PHONE) == false) {
if (activeAudioManager?.audioDevices?.contains(WebRtcAudioManager.AudioDevice.SPEAKER_PHONE) != true) {
dialogAudioOutputBinding.audioOutputSpeaker.visibility = View.GONE
} else {
dialogAudioOutputBinding.audioOutputSpeaker.visibility = View.VISIBLE
}

if (callActivity.audioManager?.currentAudioDevice?.equals(
if (activeAudioManager?.currentAudioDevice?.equals(
WebRtcAudioManager.AudioDevice.WIRED_HEADSET
) == true
) {
Expand All @@ -77,7 +78,8 @@ class AudioOutputDialog(val callActivity: CallActivity) : BottomSheetDialog(call
}

private fun highlightActiveOutputChannel() {
when (callActivity.audioManager?.currentAudioDevice) {
viewThemeUtils.platform.themeDialogDark(dialogAudioOutputBinding.root)
when (callActivity.audioManager?.audioDeviceForUi) {
WebRtcAudioManager.AudioDevice.BLUETOOTH -> {
viewThemeUtils.platform.colorImageView(
dialogAudioOutputBinding.audioOutputBluetoothIcon,
Expand Down Expand Up @@ -118,18 +120,21 @@ class AudioOutputDialog(val callActivity: CallActivity) : BottomSheetDialog(call

private fun initClickListeners() {
dialogAudioOutputBinding.audioOutputBluetooth.setOnClickListener {
callActivity.setAudioOutputChannel(WebRtcAudioManager.AudioDevice.BLUETOOTH)
dismiss()
if (callActivity.setAudioOutputChannel(WebRtcAudioManager.AudioDevice.BLUETOOTH)) {
dismiss()
}
}

dialogAudioOutputBinding.audioOutputSpeaker.setOnClickListener {
callActivity.setAudioOutputChannel(WebRtcAudioManager.AudioDevice.SPEAKER_PHONE)
dismiss()
if (callActivity.setAudioOutputChannel(WebRtcAudioManager.AudioDevice.SPEAKER_PHONE)) {
dismiss()
}
}

dialogAudioOutputBinding.audioOutputEarspeaker.setOnClickListener {
callActivity.setAudioOutputChannel(WebRtcAudioManager.AudioDevice.EARPIECE)
dismiss()
if (callActivity.setAudioOutputChannel(WebRtcAudioManager.AudioDevice.EARPIECE)) {
dismiss()
}
}
}

Expand Down
Loading