diff --git a/app/src/main/kotlin/com/example/childmonitor_multiple/ListenActivity.kt b/app/src/main/kotlin/com/example/childmonitor_multiple/ListenActivity.kt
index 73b2f1e..c55a54d 100644
--- a/app/src/main/kotlin/com/example/childmonitor_multiple/ListenActivity.kt
+++ b/app/src/main/kotlin/com/example/childmonitor_multiple/ListenActivity.kt
@@ -1,19 +1,3 @@
-/*
- * This file is part of Child Monitor.
- *
- * Child Monitor is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Child Monitor is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Child Monitor. If not, see .
- */
package com.example.childmonitor_multiple
import android.app.Activity
@@ -34,6 +18,8 @@ class ListenActivity : Activity() {
// Don't attempt to unbind from the service unless the client has received some
// information about the service's state.
private var shouldUnbind = false
+ private lateinit var statusText: TextView
+
private val connection: ServiceConnection = object : ServiceConnection {
override fun onServiceConnected(className: ComponentName, service: IBinder) {
// This is called when the connection with the service has been
@@ -42,10 +28,11 @@ class ListenActivity : Activity() {
// service that we know is running in our own process, we can
// cast its IBinder to a concrete class and directly access it.
val bs = (service as ListenBinder).service
- Toast.makeText(this@ListenActivity, R.string.connect,
- Toast.LENGTH_SHORT).show()
+ Toast.makeText(this@ListenActivity, R.string.connect, Toast.LENGTH_SHORT).show()
+
val connectedText = findViewById(R.id.connectedTo)
connectedText.text = bs.childDeviceName
+
val volumeView = findViewById(R.id.volume)
volumeView.volumeHistory = bs.volumeHistory
bs.onUpdate = { volumeView.postInvalidate() }
@@ -77,7 +64,7 @@ class ListenActivity : Activity() {
// (and thus won't be supporting component replacement by other
// applications).
if (bindService(intent, connection, BIND_AUTO_CREATE)) {
- this.shouldUnbind = true
+ shouldUnbind = true
Log.i(TAG, "Bound listen service")
} else {
Log.e(TAG, "Error: The requested service doesn't " +
@@ -86,32 +73,29 @@ class ListenActivity : Activity() {
}
private fun doUnbindAndStopService() {
- if (this.shouldUnbind) {
- // Release information about the service's state.
+ if (shouldUnbind) {
unbindService(connection)
- this.shouldUnbind = false
+ shouldUnbind = false
}
- val context: Context = this
- val intent = Intent(context, ListenService::class.java)
- context.stopService(intent)
+ stopService(Intent(this, ListenService::class.java))
}
fun postErrorMessage() {
- val status = findViewById(R.id.textStatus)
- status.post { status.setText(R.string.disconnected) }
+ statusText.post {
+ statusText.text = "Verbindung getrennt (3 Fehlversuche)"
+ }
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
- val bundle = this.intent.extras
- ensureServiceRunningAndBind(bundle)
- this.volumeControlStream = AudioManager.STREAM_MUSIC
setContentView(R.layout.activity_listen)
- val statusText = findViewById(R.id.textStatus)
- statusText.setText(R.string.listening)
+ statusText = findViewById(R.id.textStatus)
+ statusText.text = "Starte Verbindung…"
+ volumeControlStream = AudioManager.STREAM_MUSIC
+ ensureServiceRunningAndBind(intent.extras)
}
- public override fun onDestroy() {
+ override fun onDestroy() {
doUnbindAndStopService()
super.onDestroy()
}
diff --git a/app/src/main/kotlin/com/example/childmonitor_multiple/ListenService.kt b/app/src/main/kotlin/com/example/childmonitor_multiple/ListenService.kt
index 9e5bc38..1c90bab 100644
--- a/app/src/main/kotlin/com/example/childmonitor_multiple/ListenService.kt
+++ b/app/src/main/kotlin/com/example/childmonitor_multiple/ListenService.kt
@@ -49,6 +49,9 @@ class ListenService : Service() {
var childDeviceName: String? = null
private set
+ var onError: (() -> Unit)? = null
+ var onUpdate: (() -> Unit)? = null
+
override fun onCreate() {
super.onCreate()
this.notificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
@@ -62,11 +65,12 @@ class ListenService : Service() {
val name = it.getString("name")
childDeviceName = name
val n = buildNotification(name)
- val foregroundServiceType = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK else 0 // Keep the linter happy
+ val foregroundServiceType = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R)
+ ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK else 0
ServiceCompat.startForeground(this, ID, n, foregroundServiceType)
val address = it.getString("address")
val port = it.getInt("port")
- doListen(address, port)
+ doListenWithRetries(address, port)
}
return START_REDELIVER_INTENT
}
@@ -82,61 +86,76 @@ class ListenService : Service() {
Toast.makeText(this, R.string.stopped, Toast.LENGTH_SHORT).show()
}
- override fun onBind(intent: Intent): IBinder {
- return binder
- }
-
- private fun buildNotification(name: String?): Notification {
- // In this sample, we'll use the same text for the ticker and the expanded notification
- val text = getText(R.string.listening)
-
- // The PendingIntent to launch our activity if the user selects this notification
- val contentIntent = PendingIntent.getActivity(this, 0,
- Intent(this, ListenActivity::class.java), PendingIntent.FLAG_IMMUTABLE)
-
- // Set the info for the views that show in the notification panel.
- val b = NotificationCompat.Builder(this, CHANNEL_ID)
- b.setSmallIcon(R.drawable.listening_notification) // the status icon
- .setOngoing(true)
- .setTicker(text) // the status text
- .setContentTitle(text) // the label of the entry
- .setContentText(name) // the contents of the entry
- .setContentIntent(contentIntent)
- return b.build()
- }
-
- private fun createNotificationChannel() {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
- val serviceChannel = NotificationChannel(
- CHANNEL_ID,
- "Foreground Service Channel",
- NotificationManager.IMPORTANCE_DEFAULT
- )
- notificationManager.createNotificationChannel(serviceChannel)
- }
- }
+ override fun onBind(intent: Intent): IBinder = binder
inner class ListenBinder : Binder() {
val service: ListenService
get() = this@ListenService
}
- var onError: (() -> Unit)? = null
- var onUpdate: (() -> Unit)? = null
- private fun doListen(address: String?, port: Int) {
+ private fun buildNotification(name: String?): Notification {
+ // In this sample, we'll use the same text for the ticker and the expanded notification
+ val text = getText(R.string.listening)
+ val contentIntent = PendingIntent.getActivity(
+ this, 0,
+ Intent(this, ListenActivity::class.java),
+ PendingIntent.FLAG_IMMUTABLE
+ )
+ return NotificationCompat.Builder(this, CHANNEL_ID)
+ .setSmallIcon(R.drawable.listening_notification)
+ .setOngoing(true)
+ .setTicker(text)
+ .setContentTitle(text)
+ .setContentText(name)
+ .setContentIntent(contentIntent)
+ .build()
+ }
+
+ private fun createNotificationChannel() {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ val serviceChannel = NotificationChannel(
+ CHANNEL_ID,
+ "Foreground Service Channel",
+ NotificationManager.IMPORTANCE_DEFAULT
+ )
+ notificationManager.createNotificationChannel(serviceChannel)
+ }
+ }
+
+ /** Neuer Wrapper mit 3 automatischen Reconnect-Versuchen **/
+ private fun doListenWithRetries(address: String?, port: Int) {
val lt = Thread {
- try {
- val socket = Socket(address, port)
- socket.soTimeout = 30_000
- val success = streamAudio(socket)
- if (!success) {
- playAlert()
- onError?.invoke()
+ var attempts = 0
+ var connected = false
+
+ while (attempts < 3 && !connected && !Thread.currentThread().isInterrupted) {
+ try {
+ Log.i(TAG, "Verbindungsversuch ${attempts + 1} zu $address:$port …")
+ val socket = Socket(address, port)
+ socket.soTimeout = 30_000
+ connected = streamAudio(socket)
+ if (!connected) {
+ Log.w(TAG, "Streaming fehlgeschlagen, Versuch ${attempts + 1}")
+ playAlert()
+ attempts++
+ Thread.sleep(2000)
+ }
+ } catch (e: IOException) {
+ attempts++
+ Log.e(TAG, "Fehler beim Verbindungsaufbau (Versuch $attempts von 3)", e)
+ if (attempts < 3) Thread.sleep(2000)
}
- } catch (e : IOException) {
- Log.e(TAG, "Error opening socket to $address on port $port", e)
+ }
+
+ if (!connected) {
+ Log.e(TAG, "Nach 3 Versuchen keine Verbindung möglich.")
+ playAlert()
+ onError?.invoke()
+ } else {
+ Log.i(TAG, "Verbindung erfolgreich aufgebaut.")
}
}
+
this.listenThread = lt
lt.start()
}
@@ -148,10 +167,12 @@ class ListenService : Service() {
channelConfiguration,
audioEncoding,
bufferSize,
- AudioTrack.MODE_STREAM)
+ AudioTrack.MODE_STREAM
+ )
+
try {
audioTrack.play()
- } catch (e : java.lang.IllegalStateException) {
+ } catch (e: IllegalStateException) {
Log.e(TAG, "Failed to play streamed audio audio for other reason", e)
return false
}
@@ -165,14 +186,12 @@ class ListenService : Service() {
val readBuffer = ByteArray(byteBufferSize)
val decodedBuffer = ShortArray(byteBufferSize * 2)
- try {
+
+ return try {
while (!Thread.currentThread().isInterrupted) {
val len = inputStream.read(readBuffer)
- if (len < 0) {
- // If the current thread was not interrupted this means the remote stopped streaming
- return Thread.currentThread().isInterrupted
- }
- val decoded: Int = AudioCodecDefines.CODEC.decode(decodedBuffer, readBuffer, len, 0)
+ if (len < 0) return false
+ val decoded = AudioCodecDefines.CODEC.decode(decodedBuffer, readBuffer, len, 0)
if (decoded > 0) {
audioTrack.write(decodedBuffer, 0, decoded)
val decodedBytes = ShortArray(decoded)
@@ -181,13 +200,16 @@ class ListenService : Service() {
onUpdate?.invoke()
}
}
- return true
+ true
} catch (e: Exception) {
- Log.e(TAG, "Connection failed", e)
- return false
+ Log.e(TAG, "Verbindungsfehler im Stream", e)
+ false
} finally {
- audioTrack.stop()
- socket.close()
+ try {
+ audioTrack.stop()
+ audioTrack.release()
+ socket.close()
+ } catch (_: Exception) {}
}
}
@@ -195,7 +217,7 @@ class ListenService : Service() {
val mp = MediaPlayer.create(this, R.raw.upward_beep_chromatic_fifths)
if (mp != null) {
Log.i(TAG, "Playing alert")
- mp.setOnCompletionListener { obj: MediaPlayer -> obj.release() }
+ mp.setOnCompletionListener { it.release() }
mp.start()
} else {
Log.e(TAG, "Failed to play alert")