1 /* <lambda>null2 * Copyright 2022 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package androidx.camera.integration.avsync 18 19 import android.content.Context 20 import android.content.Context.AUDIO_SERVICE 21 import android.media.AudioManager 22 import androidx.camera.core.Logger 23 import androidx.camera.integration.avsync.model.AudioGenerator 24 import androidx.camera.integration.avsync.model.CameraHelper 25 import androidx.camera.integration.avsync.model.CameraHelper.Companion.CameraImplementation 26 import androidx.compose.runtime.getValue 27 import androidx.compose.runtime.mutableStateOf 28 import androidx.compose.runtime.setValue 29 import androidx.core.util.Preconditions 30 import androidx.lifecycle.LifecycleOwner 31 import androidx.lifecycle.ViewModel 32 import androidx.lifecycle.viewModelScope 33 import kotlinx.coroutines.Dispatchers 34 import kotlinx.coroutines.Job 35 import kotlinx.coroutines.delay 36 import kotlinx.coroutines.flow.flow 37 import kotlinx.coroutines.flow.launchIn 38 import kotlinx.coroutines.flow.map 39 import kotlinx.coroutines.flow.onCompletion 40 import kotlinx.coroutines.withContext 41 42 private const val ACTIVE_LENGTH_SEC: Double = 0.5 43 private const val ACTIVE_INTERVAL_SEC: Double = 1.0 44 private const val ACTIVE_DELAY_SEC: Double = 0.0 45 private const val VOLUME_PERCENTAGE: Double = 1.0 46 private const val TAG = "SignalGeneratorViewModel" 47 48 enum class ActivationSignal { 49 Active, 50 Inactive 51 } 52 53 class SignalGeneratorViewModel : ViewModel() { 54 55 private var signalGenerationJob: Job? = null 56 private lateinit var audioGenerator: AudioGenerator 57 private lateinit var cameraHelper: CameraHelper 58 private lateinit var audioManager: AudioManager 59 private var originalVolume: Int = 0 60 61 var isGeneratorReady: Boolean by mutableStateOf(false) 62 private set 63 64 var isRecorderReady: Boolean by mutableStateOf(false) 65 private set 66 67 var isSignalGenerating: Boolean by mutableStateOf(false) 68 private set 69 70 var isActivePeriod: Boolean by mutableStateOf(false) 71 private set 72 73 var isRecording: Boolean by mutableStateOf(false) 74 private set 75 76 var isPaused: Boolean by mutableStateOf(false) 77 private set 78 initialRecordernull79 suspend fun initialRecorder( 80 context: Context, 81 lifecycleOwner: LifecycleOwner, 82 cameraImplementation: CameraImplementation 83 ) { 84 cameraHelper = CameraHelper(cameraImplementation) 85 withContext(Dispatchers.Main) { 86 isRecorderReady = cameraHelper.bindCamera(context, lifecycleOwner) 87 } 88 } 89 initialSignalGeneratornull90 suspend fun initialSignalGenerator(context: Context, beepFrequency: Int, beepEnabled: Boolean) { 91 audioManager = context.getSystemService(AUDIO_SERVICE) as AudioManager 92 saveOriginalVolume() 93 94 withContext(Dispatchers.Default) { 95 audioGenerator = AudioGenerator(beepEnabled) 96 audioGenerator.initial( 97 context = context, 98 frequency = beepFrequency, 99 beepLengthInSec = ACTIVE_LENGTH_SEC, 100 ) 101 isGeneratorReady = true 102 } 103 } 104 setVolumenull105 private fun setVolume(percentage: Double = VOLUME_PERCENTAGE) { 106 Preconditions.checkArgument(percentage in 0.0..1.0) 107 108 val maxVolume = audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC) 109 val targetVolume = (maxVolume * percentage).toInt() 110 audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, targetVolume, 0) 111 } 112 startSignalGenerationnull113 fun startSignalGeneration() { 114 Logger.d(TAG, "Start signal generation.") 115 Preconditions.checkState(isGeneratorReady) 116 117 setVolume() 118 119 signalGenerationJob?.cancel() 120 isSignalGenerating = true 121 signalGenerationJob = 122 activationSignalFlow() 123 .map { activationSignal -> 124 when (activationSignal) { 125 ActivationSignal.Active -> { 126 isActivePeriod = true 127 playBeepSound() 128 } 129 ActivationSignal.Inactive -> { 130 isActivePeriod = false 131 stopBeepSound() 132 } 133 } 134 } 135 .onCompletion { 136 stopBeepSound() 137 restoreOriginalVolume() 138 isActivePeriod = false 139 } 140 .launchIn(viewModelScope) 141 } 142 stopSignalGenerationnull143 fun stopSignalGeneration() { 144 Logger.d(TAG, "Stop signal generation.") 145 Preconditions.checkState(isGeneratorReady) 146 147 isSignalGenerating = false 148 signalGenerationJob?.cancel() 149 signalGenerationJob = null 150 } 151 startRecordingnull152 fun startRecording(context: Context) { 153 Logger.d(TAG, "Start recording.") 154 Preconditions.checkState(isRecorderReady) 155 156 cameraHelper.startRecording(context) 157 isRecording = true 158 } 159 stopRecordingnull160 fun stopRecording() { 161 Logger.d(TAG, "Stop recording.") 162 Preconditions.checkState(isRecorderReady) 163 164 cameraHelper.stopRecording() 165 isRecording = false 166 isPaused = false 167 } 168 pauseRecordingnull169 fun pauseRecording() { 170 Logger.d(TAG, "Pause recording.") 171 Preconditions.checkState(isRecorderReady) 172 173 cameraHelper.pauseRecording() 174 isPaused = true 175 } 176 resumeRecordingnull177 fun resumeRecording() { 178 Logger.d(TAG, "Resume recording.") 179 Preconditions.checkState(isRecorderReady) 180 181 cameraHelper.resumeRecording() 182 isPaused = false 183 } 184 saveOriginalVolumenull185 private fun saveOriginalVolume() { 186 originalVolume = audioManager.getStreamVolume(AudioManager.STREAM_MUSIC) 187 } 188 restoreOriginalVolumenull189 private fun restoreOriginalVolume() { 190 audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, originalVolume, 0) 191 } 192 <lambda>null193 private fun activationSignalFlow() = flow { 194 delay((ACTIVE_DELAY_SEC * 1000).toLong()) 195 while (true) { 196 emit(ActivationSignal.Active) 197 delay((ACTIVE_LENGTH_SEC * 1000).toLong()) 198 emit(ActivationSignal.Inactive) 199 delay((ACTIVE_INTERVAL_SEC * 1000).toLong()) 200 } 201 } 202 playBeepSoundnull203 private fun playBeepSound() { 204 audioGenerator.start() 205 } 206 stopBeepSoundnull207 private fun stopBeepSound() { 208 audioGenerator.stop() 209 } 210 } 211