mirror of
https://github.com/nextcloud/talk-android
synced 2025-07-17 01:35:03 +01:00
Merge pull request #3298 from nextcloud/feature/88/detectTalkingAndSendDataChannelMessage
detect when user is talking and send data channel message
This commit is contained in:
commit
bc7939ffd8
@ -39,7 +39,10 @@ import android.graphics.Color
|
|||||||
import android.graphics.Typeface
|
import android.graphics.Typeface
|
||||||
import android.graphics.drawable.Icon
|
import android.graphics.drawable.Icon
|
||||||
import android.media.AudioAttributes
|
import android.media.AudioAttributes
|
||||||
|
import android.media.AudioFormat
|
||||||
|
import android.media.AudioRecord
|
||||||
import android.media.MediaPlayer
|
import android.media.MediaPlayer
|
||||||
|
import android.media.MediaRecorder
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
@ -181,6 +184,7 @@ import java.util.Objects
|
|||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
import java.util.concurrent.atomic.AtomicInteger
|
import java.util.concurrent.atomic.AtomicInteger
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
import kotlin.math.abs
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
@AutoInjector(NextcloudTalkApplication::class)
|
@AutoInjector(NextcloudTalkApplication::class)
|
||||||
@ -363,6 +367,15 @@ class CallActivity : CallBaseActivity() {
|
|||||||
private var reactionAnimator: ReactionAnimator? = null
|
private var reactionAnimator: ReactionAnimator? = null
|
||||||
private var othersInCall = false
|
private var othersInCall = false
|
||||||
|
|
||||||
|
private lateinit var micInputAudioRecorder: AudioRecord
|
||||||
|
private var micInputAudioRecordThread: Thread? = null
|
||||||
|
private var isMicInputAudioThreadRunning: Boolean = false
|
||||||
|
private val bufferSize = AudioRecord.getMinBufferSize(
|
||||||
|
SAMPLE_RATE,
|
||||||
|
AudioFormat.CHANNEL_IN_MONO,
|
||||||
|
AudioFormat.ENCODING_PCM_16BIT
|
||||||
|
)
|
||||||
|
|
||||||
@SuppressLint("ClickableViewAccessibility")
|
@SuppressLint("ClickableViewAccessibility")
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
Log.d(TAG, "onCreate")
|
Log.d(TAG, "onCreate")
|
||||||
@ -523,6 +536,19 @@ class CallActivity : CallBaseActivity() {
|
|||||||
override fun onStop() {
|
override fun onStop() {
|
||||||
super.onStop()
|
super.onStop()
|
||||||
active = false
|
active = false
|
||||||
|
|
||||||
|
if (isMicInputAudioThreadRunning) {
|
||||||
|
stopMicInputDetection()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun stopMicInputDetection() {
|
||||||
|
if (micInputAudioRecordThread != null) {
|
||||||
|
micInputAudioRecorder.stop()
|
||||||
|
micInputAudioRecorder.release()
|
||||||
|
isMicInputAudioThreadRunning = false
|
||||||
|
micInputAudioRecordThread = null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun enableBluetoothManager() {
|
private fun enableBluetoothManager() {
|
||||||
@ -999,6 +1025,8 @@ class CallActivity : CallBaseActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun microphoneInitialization() {
|
private fun microphoneInitialization() {
|
||||||
|
startMicInputDetection()
|
||||||
|
|
||||||
// create an AudioSource instance
|
// create an AudioSource instance
|
||||||
audioSource = peerConnectionFactory!!.createAudioSource(audioConstraints)
|
audioSource = peerConnectionFactory!!.createAudioSource(audioConstraints)
|
||||||
localAudioTrack = peerConnectionFactory!!.createAudioTrack("NCa0", audioSource)
|
localAudioTrack = peerConnectionFactory!!.createAudioTrack("NCa0", audioSource)
|
||||||
@ -1006,6 +1034,62 @@ class CallActivity : CallBaseActivity() {
|
|||||||
localStream!!.addTrack(localAudioTrack)
|
localStream!!.addTrack(localAudioTrack)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressLint("MissingPermission")
|
||||||
|
private fun startMicInputDetection() {
|
||||||
|
if (permissionUtil!!.isMicrophonePermissionGranted() && micInputAudioRecordThread == null) {
|
||||||
|
var isSpeakingLongTerm = false
|
||||||
|
micInputAudioRecorder = AudioRecord(
|
||||||
|
MediaRecorder.AudioSource.MIC,
|
||||||
|
SAMPLE_RATE,
|
||||||
|
AudioFormat.CHANNEL_IN_MONO,
|
||||||
|
AudioFormat.ENCODING_PCM_16BIT,
|
||||||
|
bufferSize
|
||||||
|
)
|
||||||
|
isMicInputAudioThreadRunning = true
|
||||||
|
micInputAudioRecorder.startRecording()
|
||||||
|
micInputAudioRecordThread = Thread(
|
||||||
|
Runnable {
|
||||||
|
while (isMicInputAudioThreadRunning) {
|
||||||
|
val byteArr = ByteArray(bufferSize / 2)
|
||||||
|
micInputAudioRecorder.read(byteArr, 0, byteArr.size)
|
||||||
|
val isCurrentlySpeaking = abs(byteArr[0].toDouble()) > MICROPHONE_VALUE_THRESHOLD
|
||||||
|
|
||||||
|
if (isCurrentlySpeaking && !isSpeakingLongTerm) {
|
||||||
|
isSpeakingLongTerm = true
|
||||||
|
sendIsSpeakingMessage(true)
|
||||||
|
} else if (!isCurrentlySpeaking && isSpeakingLongTerm) {
|
||||||
|
isSpeakingLongTerm = false
|
||||||
|
sendIsSpeakingMessage(false)
|
||||||
|
}
|
||||||
|
Thread.sleep(MICROPHONE_VALUE_SLEEP)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
micInputAudioRecordThread!!.start()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("Detekt.NestedBlockDepth")
|
||||||
|
private fun sendIsSpeakingMessage(isSpeaking: Boolean) {
|
||||||
|
val isSpeakingMessage: String =
|
||||||
|
if (isSpeaking) SIGNALING_MESSAGE_SPEAKING_STARTED else SIGNALING_MESSAGE_SPEAKING_STOPPED
|
||||||
|
|
||||||
|
if (isConnectionEstablished && othersInCall) {
|
||||||
|
if (!hasMCU) {
|
||||||
|
for (peerConnectionWrapper in peerConnectionWrapperList) {
|
||||||
|
peerConnectionWrapper.sendChannelData(DataChannelMessage(isSpeakingMessage))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (peerConnectionWrapper in peerConnectionWrapperList) {
|
||||||
|
if (peerConnectionWrapper.sessionId == webSocketClient!!.sessionId) {
|
||||||
|
peerConnectionWrapper.sendChannelData(DataChannelMessage(isSpeakingMessage))
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun createCameraCapturer(enumerator: CameraEnumerator?): VideoCapturer? {
|
private fun createCameraCapturer(enumerator: CameraEnumerator?): VideoCapturer? {
|
||||||
val deviceNames = enumerator!!.deviceNames
|
val deviceNames = enumerator!!.deviceNames
|
||||||
|
|
||||||
@ -1151,10 +1235,10 @@ class CallActivity : CallBaseActivity() {
|
|||||||
private fun toggleMedia(enable: Boolean, video: Boolean) {
|
private fun toggleMedia(enable: Boolean, video: Boolean) {
|
||||||
var message: String
|
var message: String
|
||||||
if (video) {
|
if (video) {
|
||||||
message = "videoOff"
|
message = SIGNALING_MESSAGE_VIDEO_OFF
|
||||||
if (enable) {
|
if (enable) {
|
||||||
binding!!.cameraButton.alpha = OPACITY_ENABLED
|
binding!!.cameraButton.alpha = OPACITY_ENABLED
|
||||||
message = "videoOn"
|
message = SIGNALING_MESSAGE_VIDEO_ON
|
||||||
startVideoCapture()
|
startVideoCapture()
|
||||||
} else {
|
} else {
|
||||||
binding!!.cameraButton.alpha = OPACITY_DISABLED
|
binding!!.cameraButton.alpha = OPACITY_DISABLED
|
||||||
@ -1175,9 +1259,9 @@ class CallActivity : CallBaseActivity() {
|
|||||||
binding!!.selfVideoRenderer.visibility = View.INVISIBLE
|
binding!!.selfVideoRenderer.visibility = View.INVISIBLE
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
message = "audioOff"
|
message = SIGNALING_MESSAGE_AUDIO_OFF
|
||||||
if (enable) {
|
if (enable) {
|
||||||
message = "audioOn"
|
message = SIGNALING_MESSAGE_AUDIO_ON
|
||||||
binding!!.microphoneButton.alpha = OPACITY_ENABLED
|
binding!!.microphoneButton.alpha = OPACITY_ENABLED
|
||||||
} else {
|
} else {
|
||||||
binding!!.microphoneButton.alpha = OPACITY_DISABLED
|
binding!!.microphoneButton.alpha = OPACITY_DISABLED
|
||||||
@ -2967,7 +3051,7 @@ class CallActivity : CallBaseActivity() {
|
|||||||
val newX = event.rawX - binding!!.selfVideoViewWrapper.width / 2f
|
val newX = event.rawX - binding!!.selfVideoViewWrapper.width / 2f
|
||||||
binding!!.selfVideoViewWrapper.y = newY
|
binding!!.selfVideoViewWrapper.y = newY
|
||||||
binding!!.selfVideoViewWrapper.x = newX
|
binding!!.selfVideoViewWrapper.x = newX
|
||||||
} else if (event.actionMasked == MotionEvent.ACTION_UP && duration < 100) {
|
} else if (event.actionMasked == MotionEvent.ACTION_UP && duration < SWITCH_CAMERA_THRESHOLD_DURATION) {
|
||||||
switchCamera()
|
switchCamera()
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
@ -3000,5 +3084,18 @@ class CallActivity : CallBaseActivity() {
|
|||||||
const val CALL_TIME_ONE_HOUR = 3600
|
const val CALL_TIME_ONE_HOUR = 3600
|
||||||
const val CALL_DURATION_EMPTY = "--:--"
|
const val CALL_DURATION_EMPTY = "--:--"
|
||||||
const val API_RETRIES: Long = 3
|
const val API_RETRIES: Long = 3
|
||||||
|
|
||||||
|
const val SWITCH_CAMERA_THRESHOLD_DURATION = 100
|
||||||
|
|
||||||
|
private const val SAMPLE_RATE = 8000
|
||||||
|
private const val MICROPHONE_VALUE_THRESHOLD = 20
|
||||||
|
private const val MICROPHONE_VALUE_SLEEP: Long = 1000
|
||||||
|
|
||||||
|
private const val SIGNALING_MESSAGE_SPEAKING_STARTED = "speaking"
|
||||||
|
private const val SIGNALING_MESSAGE_SPEAKING_STOPPED = "stoppedSpeaking"
|
||||||
|
private const val SIGNALING_MESSAGE_VIDEO_ON = "videoOn"
|
||||||
|
private const val SIGNALING_MESSAGE_VIDEO_OFF = "videoOff"
|
||||||
|
private const val SIGNALING_MESSAGE_AUDIO_ON = "audioOn"
|
||||||
|
private const val SIGNALING_MESSAGE_AUDIO_OFF = "audioOff"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -93,7 +93,7 @@ abstract class SharedItemsViewHolder(
|
|||||||
item.id,
|
item.id,
|
||||||
item.mimeType,
|
item.mimeType,
|
||||||
true,
|
true,
|
||||||
FileViewerUtils.ProgressUi(progressBar, null, image),
|
FileViewerUtils.ProgressUi(progressBar, null, image)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -358,9 +358,11 @@ class FileViewerUtils(private val context: Context, private val user: User) {
|
|||||||
if (progressUi.previewImage.isShown && openWhenDownloaded) {
|
if (progressUi.previewImage.isShown && openWhenDownloaded) {
|
||||||
openFileByMimetype(fileName, mimetype)
|
openFileByMimetype(fileName, mimetype)
|
||||||
} else {
|
} else {
|
||||||
Log.d(TAG, "file " + fileName +
|
Log.d(
|
||||||
" was downloaded but it's not opened because view is not shown on screen or " +
|
TAG,
|
||||||
"openWhenDownloaded is false"
|
"file " + fileName +
|
||||||
|
" was downloaded but it's not opened because view is not shown on screen or " +
|
||||||
|
"openWhenDownloaded is false"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
progressUi.messageText?.text = fileName
|
progressUi.messageText?.text = fileName
|
||||||
|
@ -33,7 +33,8 @@
|
|||||||
android:layout_marginStart="@dimen/standard_margin"
|
android:layout_marginStart="@dimen/standard_margin"
|
||||||
android:layout_marginTop="@dimen/standard_eighth_margin"
|
android:layout_marginTop="@dimen/standard_eighth_margin"
|
||||||
android:layout_marginEnd="@dimen/standard_margin"
|
android:layout_marginEnd="@dimen/standard_margin"
|
||||||
android:layout_marginBottom="@dimen/standard_eighth_margin">
|
android:layout_marginBottom="@dimen/standard_eighth_margin"
|
||||||
|
tools:ignore="UselessParent">
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:id="@+id/expandCollapseIcon"
|
android:id="@+id/expandCollapseIcon"
|
||||||
|
@ -1,2 +1,2 @@
|
|||||||
DO NOT TOUCH; GENERATED BY DRONE
|
DO NOT TOUCH; GENERATED BY DRONE
|
||||||
<span class="mdl-layout-title">Lint Report: 94 warnings</span>
|
<span class="mdl-layout-title">Lint Report: 93 warnings</span>
|
||||||
|
Loading…
Reference in New Issue
Block a user