3 retys added frst attempt

This commit is contained in:
pi
2025-10-27 11:39:42 +01:00
parent adafa6279a
commit cb7e9ac8ac
2 changed files with 100 additions and 94 deletions

View File

@@ -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 <http://www.gnu.org/licenses/>.
*/
package com.example.childmonitor_multiple package com.example.childmonitor_multiple
import android.app.Activity 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 // Don't attempt to unbind from the service unless the client has received some
// information about the service's state. // information about the service's state.
private var shouldUnbind = false private var shouldUnbind = false
private lateinit var statusText: TextView
private val connection: ServiceConnection = object : ServiceConnection { private val connection: ServiceConnection = object : ServiceConnection {
override fun onServiceConnected(className: ComponentName, service: IBinder) { override fun onServiceConnected(className: ComponentName, service: IBinder) {
// This is called when the connection with the service has been // 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 // service that we know is running in our own process, we can
// cast its IBinder to a concrete class and directly access it. // cast its IBinder to a concrete class and directly access it.
val bs = (service as ListenBinder).service val bs = (service as ListenBinder).service
Toast.makeText(this@ListenActivity, R.string.connect, Toast.makeText(this@ListenActivity, R.string.connect, Toast.LENGTH_SHORT).show()
Toast.LENGTH_SHORT).show()
val connectedText = findViewById<TextView>(R.id.connectedTo) val connectedText = findViewById<TextView>(R.id.connectedTo)
connectedText.text = bs.childDeviceName connectedText.text = bs.childDeviceName
val volumeView = findViewById<VolumeView>(R.id.volume) val volumeView = findViewById<VolumeView>(R.id.volume)
volumeView.volumeHistory = bs.volumeHistory volumeView.volumeHistory = bs.volumeHistory
bs.onUpdate = { volumeView.postInvalidate() } bs.onUpdate = { volumeView.postInvalidate() }
@@ -77,7 +64,7 @@ class ListenActivity : Activity() {
// (and thus won't be supporting component replacement by other // (and thus won't be supporting component replacement by other
// applications). // applications).
if (bindService(intent, connection, BIND_AUTO_CREATE)) { if (bindService(intent, connection, BIND_AUTO_CREATE)) {
this.shouldUnbind = true shouldUnbind = true
Log.i(TAG, "Bound listen service") Log.i(TAG, "Bound listen service")
} else { } else {
Log.e(TAG, "Error: The requested service doesn't " + Log.e(TAG, "Error: The requested service doesn't " +
@@ -86,32 +73,29 @@ class ListenActivity : Activity() {
} }
private fun doUnbindAndStopService() { private fun doUnbindAndStopService() {
if (this.shouldUnbind) { if (shouldUnbind) {
// Release information about the service's state.
unbindService(connection) unbindService(connection)
this.shouldUnbind = false shouldUnbind = false
} }
val context: Context = this stopService(Intent(this, ListenService::class.java))
val intent = Intent(context, ListenService::class.java)
context.stopService(intent)
} }
fun postErrorMessage() { fun postErrorMessage() {
val status = findViewById<TextView>(R.id.textStatus) statusText.post {
status.post { status.setText(R.string.disconnected) } statusText.text = "Verbindung getrennt (3 Fehlversuche)"
}
} }
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
val bundle = this.intent.extras
ensureServiceRunningAndBind(bundle)
this.volumeControlStream = AudioManager.STREAM_MUSIC
setContentView(R.layout.activity_listen) setContentView(R.layout.activity_listen)
val statusText = findViewById<TextView>(R.id.textStatus) statusText = findViewById(R.id.textStatus)
statusText.setText(R.string.listening) statusText.text = "Starte Verbindung…"
volumeControlStream = AudioManager.STREAM_MUSIC
ensureServiceRunningAndBind(intent.extras)
} }
public override fun onDestroy() { override fun onDestroy() {
doUnbindAndStopService() doUnbindAndStopService()
super.onDestroy() super.onDestroy()
} }

View File

@@ -49,6 +49,9 @@ class ListenService : Service() {
var childDeviceName: String? = null var childDeviceName: String? = null
private set private set
var onError: (() -> Unit)? = null
var onUpdate: (() -> Unit)? = null
override fun onCreate() { override fun onCreate() {
super.onCreate() super.onCreate()
this.notificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager this.notificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
@@ -62,11 +65,12 @@ class ListenService : Service() {
val name = it.getString("name") val name = it.getString("name")
childDeviceName = name childDeviceName = name
val n = buildNotification(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) ServiceCompat.startForeground(this, ID, n, foregroundServiceType)
val address = it.getString("address") val address = it.getString("address")
val port = it.getInt("port") val port = it.getInt("port")
doListen(address, port) doListenWithRetries(address, port)
} }
return START_REDELIVER_INTENT return START_REDELIVER_INTENT
} }
@@ -82,61 +86,76 @@ class ListenService : Service() {
Toast.makeText(this, R.string.stopped, Toast.LENGTH_SHORT).show() Toast.makeText(this, R.string.stopped, Toast.LENGTH_SHORT).show()
} }
override fun onBind(intent: Intent): IBinder { override fun onBind(intent: Intent): IBinder = binder
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)
}
}
inner class ListenBinder : Binder() { inner class ListenBinder : Binder() {
val service: ListenService val service: ListenService
get() = this@ListenService get() = this@ListenService
} }
var onError: (() -> Unit)? = null private fun buildNotification(name: String?): Notification {
var onUpdate: (() -> Unit)? = null // In this sample, we'll use the same text for the ticker and the expanded notification
private fun doListen(address: String?, port: Int) { 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 { val lt = Thread {
try { var attempts = 0
val socket = Socket(address, port) var connected = false
socket.soTimeout = 30_000
val success = streamAudio(socket) while (attempts < 3 && !connected && !Thread.currentThread().isInterrupted) {
if (!success) { try {
playAlert() Log.i(TAG, "Verbindungsversuch ${attempts + 1} zu $address:$port")
onError?.invoke() 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 this.listenThread = lt
lt.start() lt.start()
} }
@@ -148,10 +167,12 @@ class ListenService : Service() {
channelConfiguration, channelConfiguration,
audioEncoding, audioEncoding,
bufferSize, bufferSize,
AudioTrack.MODE_STREAM) AudioTrack.MODE_STREAM
)
try { try {
audioTrack.play() audioTrack.play()
} catch (e : java.lang.IllegalStateException) { } catch (e: IllegalStateException) {
Log.e(TAG, "Failed to play streamed audio audio for other reason", e) Log.e(TAG, "Failed to play streamed audio audio for other reason", e)
return false return false
} }
@@ -165,14 +186,12 @@ class ListenService : Service() {
val readBuffer = ByteArray(byteBufferSize) val readBuffer = ByteArray(byteBufferSize)
val decodedBuffer = ShortArray(byteBufferSize * 2) val decodedBuffer = ShortArray(byteBufferSize * 2)
try {
return try {
while (!Thread.currentThread().isInterrupted) { while (!Thread.currentThread().isInterrupted) {
val len = inputStream.read(readBuffer) val len = inputStream.read(readBuffer)
if (len < 0) { if (len < 0) return false
// If the current thread was not interrupted this means the remote stopped streaming val decoded = AudioCodecDefines.CODEC.decode(decodedBuffer, readBuffer, len, 0)
return Thread.currentThread().isInterrupted
}
val decoded: Int = AudioCodecDefines.CODEC.decode(decodedBuffer, readBuffer, len, 0)
if (decoded > 0) { if (decoded > 0) {
audioTrack.write(decodedBuffer, 0, decoded) audioTrack.write(decodedBuffer, 0, decoded)
val decodedBytes = ShortArray(decoded) val decodedBytes = ShortArray(decoded)
@@ -181,13 +200,16 @@ class ListenService : Service() {
onUpdate?.invoke() onUpdate?.invoke()
} }
} }
return true true
} catch (e: Exception) { } catch (e: Exception) {
Log.e(TAG, "Connection failed", e) Log.e(TAG, "Verbindungsfehler im Stream", e)
return false false
} finally { } finally {
audioTrack.stop() try {
socket.close() 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) val mp = MediaPlayer.create(this, R.raw.upward_beep_chromatic_fifths)
if (mp != null) { if (mp != null) {
Log.i(TAG, "Playing alert") Log.i(TAG, "Playing alert")
mp.setOnCompletionListener { obj: MediaPlayer -> obj.release() } mp.setOnCompletionListener { it.release() }
mp.start() mp.start()
} else { } else {
Log.e(TAG, "Failed to play alert") Log.e(TAG, "Failed to play alert")