3 retys added frst attempt
This commit is contained in:
@@ -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()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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")
|
||||||
|
|||||||
Reference in New Issue
Block a user