I am developing an application that listens to a streamed audio. In the following code, when I first open the application, the stop and start service functions work fine, but when I exit the application using the back button and return, the stop and stop service functions do not work. Actually, the notification disappears and reappears correctly, but the audio stream continues. Additionally, when I checked with logs, there is no problem here, onDestroy in the service class is triggered when I press the stop button, but it continues to listen to the audio. Here is my codes:
MainActivity:
class MainActivity : ComponentActivity() {
private var isListening = false
private lateinit var audioTrack: AudioTrack
private var sampleRate = Constants.SAMPLE_RATE
private val channelConfig = AudioFormat.CHANNEL_CONFIGURATION_MONO
private val audioFormat = AudioFormat.ENCODING_PCM_16BIT
private var bufferSize = (sampleRate / 10.0).roundToInt()
private lateinit var listenButton: Button
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
if (ActivityCompat.checkSelfPermission(
this,
Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.RECORD_AUDIO, Manifest.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS), 0)
return
}
initializeListenButton()
TouristService.isServiceRunning.observe(this, Observer { isRunning ->
updateBroadcastButtonText(listenButton, isRunning)
isListening = isRunning
Log.d("Mesaj: ", "observe isListening: $isListening")
Log.d("Mesaj: ", "observe isRunnnig: $isRunning")
})
}
private fun initializeListenButton() {
listenButton = findViewById(R.id.listen_button)
listenButton.setOnClickListener {
isListening = !isListening
Log.d("Mesaj: ", "setOnClick isListening: $isListening")
initializeAudioTrack()
if (isListening) {
startService(Intent(this, TouristService::class.java))
GlobalScope.launch(Dispatchers.Default) {
try {
withContext(Dispatchers.IO) {
val group: InetAddress = InetAddress.getByName(Constants.HOST)
val socket = MulticastSocket(Constants.PORT)
socket.joinGroup(group)
audioTrack.play()
Log.d("Mesaj: ", "audioTrack.play")
val buffer = ByteArray(bufferSize)
var lastChecksum: Int? = null
while (audioTrack.playState == AudioTrack.PLAYSTATE_PLAYING) {
val packet = DatagramPacket(buffer, buffer.size)
socket.receive(packet)
val checksum = packet.data.sumOf { it.toInt() }
if (checksum == lastChecksum) {
continue
}
lastChecksum = checksum
audioTrack.write(packet.data, 0, packet.length)
}
socket.leaveGroup(group)
socket.close()
}
} catch (e: Exception) {
Toast.makeText(this@MainActivity, getString(R.string.error_while_listening) + e.message, Toast.LENGTH_SHORT).show()
}
audioTrack.stop()
Log.d("Mesaj: ", "audioTrack.stop")
audioTrack.release()
}
} else {
audioTrack.stop()
Log.d("Mesaj: ", "audioTrack.stop on else")
stopService(Intent(this, TouristService::class.java))
}
}
}
private fun initializeAudioTrack() {
audioTrack = AudioTrack.Builder()
.setAudioAttributes(
AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_MEDIA)
.setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
.build())
.setAudioFormat(AudioFormat.Builder()
.setEncoding(audioFormat)
.setSampleRate(sampleRate)
.setChannelMask(channelConfig)
.build())
.setBufferSizeInBytes(bufferSize)
.setTransferMode(AudioTrack.MODE_STREAM)
.setPerformanceMode(AudioTrack.PERFORMANCE_MODE_LOW_LATENCY)
.build()
}
private fun updateBroadcastButtonText(button: Button, isRunning: Boolean) {
button.text = if (isRunning) getString(R.string.stop_listening) else getString(R.string.start_listening)
}
Service:
class TouristService : Service() {
companion object {
val isServiceRunning = MutableLiveData<Boolean>()
}
override fun onBind(intent: Intent?): IBinder? {
return null
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
createNotificationChannel()
val notificationIntent = Intent(this, MainActivity::class.java)
val pendingIntentFlags =
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
val pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, pendingIntentFlags)
val notification = NotificationCompat.Builder(this, CHANNEL_ID)
.setContentTitle(getString(R.string.app_name))
.setContentText(getString(R.string.receiving_audio))
.setSmallIcon(R.drawable.ic_launcher_foreground)
.setContentIntent(pendingIntent)
.build()
startForeground(1, notification)
Log.d("Mesaj: ", "startForeground")
// Start recording audio
isServiceRunning.postValue(true)
return START_NOT_STICKY
}
override fun onDestroy() {
super.onDestroy()
Log.d("Mesaj: ", "Service ondestroy")
// Stop recording audio
isServiceRunning.postValue(false)
stopForeground(true)
}
override fun onTaskRemoved(rootIntent: Intent?) {
super.onTaskRemoved(rootIntent)
// Stop recording audio
Log.d("Mesaj: ", "Service onTaskRemoved")
isServiceRunning.postValue(false)
stopForeground(true)
}
private fun createNotificationChannel() {
val serviceChannel = NotificationChannel(
CHANNEL_ID,
"Tourist Service Channel",
NotificationManager.IMPORTANCE_DEFAULT
)
val manager = getSystemService(NotificationManager::class.java)
manager.createNotificationChannel(serviceChannel)
}
}