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
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<TextView>(R.id.connectedTo)
connectedText.text = bs.childDeviceName
val volumeView = findViewById<VolumeView>(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<TextView>(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<TextView>(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()
}

View File

@@ -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")