diff --git a/app/src/main/kotlin/com/example/childmonitor_multiple/MonitorService.kt b/app/src/main/kotlin/com/example/childmonitor_multiple/MonitorService.kt
index 60b6d49..e1e8488 100644
--- a/app/src/main/kotlin/com/example/childmonitor_multiple/MonitorService.kt
+++ b/app/src/main/kotlin/com/example/childmonitor_multiple/MonitorService.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.Notification
@@ -50,53 +34,80 @@ class MonitorService : Service() {
private var monitorThread: Thread? = null
var monitorActivity: MonitorActivity? = null
- private fun serviceConnection(socket: Socket) {
- val ma = this.monitorActivity
- ma?.runOnUiThread {
- val statusText = ma.findViewById(R.id.textStatus)
- statusText.setText(R.string.streaming)
- }
- val frequency: Int = AudioCodecDefines.FREQUENCY
- val channelConfiguration: Int = AudioCodecDefines.CHANNEL_CONFIGURATION_IN
- val audioEncoding: Int = AudioCodecDefines.ENCODING
- val bufferSize = AudioRecord.getMinBufferSize(frequency, channelConfiguration, audioEncoding)
- val audioRecord: AudioRecord = try {
- AudioRecord(
- MediaRecorder.AudioSource.MIC,
- frequency,
- channelConfiguration,
- audioEncoding,
- bufferSize
- )
- } catch (e: SecurityException) {
- // This should never happen, we asked for permission before
- throw RuntimeException(e)
- }
- val pcmBufferSize = bufferSize * 2
- val pcmBuffer = ShortArray(pcmBufferSize)
- val ulawBuffer = ByteArray(pcmBufferSize)
- try {
- audioRecord.startRecording()
- val out = socket.getOutputStream()
- socket.sendBufferSize = pcmBufferSize
- Log.d(TAG, "Socket send buffer size: " + socket.sendBufferSize)
- while (socket.isConnected && (this.currentSocket != null) && !Thread.currentThread().isInterrupted) {
- val read = audioRecord.read(pcmBuffer, 0, bufferSize)
- val encoded: Int = AudioCodecDefines.CODEC.encode(pcmBuffer, read, ulawBuffer, 0)
- out.write(ulawBuffer, 0, encoded)
- }
- } catch (e: Exception) {
- Log.e(TAG, "Connection failed", e)
- } finally {
- audioRecord.stop()
- }
+ override fun onCreate() {
+ super.onCreate()
+ this.notificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
+ this.nsdManager = this.getSystemService(NSD_SERVICE) as NsdManager
+ this.currentPort = 10000
+ this.currentSocket = null
+ Log.i(TAG, "ChildMonitor start")
}
- private fun handleMultiClientStreaming(serverSocket: ServerSocket, clients: MutableList) {
- val frequency: Int = AudioCodecDefines.FREQUENCY
- val channelConfiguration: Int = AudioCodecDefines.CHANNEL_CONFIGURATION_IN
- val audioEncoding: Int = AudioCodecDefines.ENCODING
- val bufferSize = AudioRecord.getMinBufferSize(frequency, channelConfiguration, audioEncoding)
+ override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
+ createNotificationChannel()
+ val n = buildNotification()
+ val foregroundServiceType =
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R)
+ ServiceInfo.FOREGROUND_SERVICE_TYPE_MICROPHONE
+ else 0
+ ServiceCompat.startForeground(this, ID, n, foregroundServiceType)
+ ensureMonitorThread()
+ return START_REDELIVER_INTENT
+ }
+
+ private fun ensureMonitorThread() {
+ if (monitorThread?.isAlive == true) return
+
+ val currentToken = Any()
+ this.connectionToken = currentToken
+
+ monitorThread = Thread {
+ while (connectionToken == currentToken) {
+ try {
+ ServerSocket(currentPort).use { serverSocket ->
+ currentSocket = serverSocket
+ registerService(serverSocket.localPort)
+
+ val clients = mutableListOf()
+
+ // Thread: neue Clients akzeptieren
+ Thread {
+ try {
+ while (!Thread.currentThread().isInterrupted) {
+ val client = serverSocket.accept()
+ client.tcpNoDelay = true
+ synchronized(clients) { clients.add(client) }
+ Log.i(TAG, "Neuer Client: ${client.inetAddress}")
+
+ // Statusanzeige auf Streaming
+ monitorActivity?.runOnUiThread {
+ monitorActivity?.findViewById(R.id.textStatus)
+ ?.setText(R.string.streaming)
+ }
+ }
+ } catch (e: IOException) {
+ Log.e(TAG, "Client-Akzeptierungsfehler: ${e.message}")
+ }
+ }.start()
+
+ // Aufnahme + Multi-Client-Streaming starten
+ handleMultiClientStreaming(serverSocket, clients)
+ }
+ } catch (e: Exception) {
+ if (connectionToken == currentToken) {
+ currentPort++
+ Log.e(TAG, "Failed to open server socket. Port increased to $currentPort", e)
+ }
+ }
+ }
+ }.also { it.start() }
+ }
+
+ private fun handleMultiClientStreaming(serverSocket: ServerSocket, clients: MutableList) {
+ val frequency = AudioCodecDefines.FREQUENCY
+ val channelConfiguration = AudioCodecDefines.CHANNEL_CONFIGURATION_IN
+ val audioEncoding = AudioCodecDefines.ENCODING
+ val bufferSize = AudioRecord.getMinBufferSize(frequency, channelConfiguration, audioEncoding)
val audioRecord = AudioRecord(
MediaRecorder.AudioSource.MIC,
frequency,
@@ -116,21 +127,27 @@ class MonitorService : Service() {
while (!Thread.currentThread().isInterrupted && !serverSocket.isClosed) {
val read = audioRecord.read(pcmBuffer, 0, bufferSize)
if (read > 0) {
- val encoded: Int = AudioCodecDefines.CODEC.encode(pcmBuffer, read, ulawBuffer, 0)
-
+ val encoded = AudioCodecDefines.CODEC.encode(pcmBuffer, read, ulawBuffer, 0)
synchronized(clients) {
val iterator = clients.iterator()
while (iterator.hasNext()) {
val client = iterator.next()
try {
- val out = client.getOutputStream()
- out.write(ulawBuffer, 0, encoded)
+ client.getOutputStream().write(ulawBuffer, 0, encoded)
} catch (e: IOException) {
- try { client.close() } catch (ignored: IOException) {}
+ try { client.close() } catch (_: IOException) {}
iterator.remove()
Log.w(TAG, "Client getrennt: ${client.inetAddress}")
}
}
+
+ // Wenn keine Clients mehr da, Status auf "Warte auf Eltern"
+ if (clients.isEmpty()) {
+ monitorActivity?.runOnUiThread {
+ monitorActivity?.findViewById(R.id.textStatus)
+ ?.setText(R.string.waitingForParent)
+ }
+ }
}
}
}
@@ -146,187 +163,85 @@ class MonitorService : Service() {
clients.clear()
}
Log.i(TAG, "Audioaufnahme beendet, alle Clients getrennt.")
- }
- }
-
-
- override fun onCreate() {
- Log.i(TAG, "ChildMonitor start")
- super.onCreate()
- this.notificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
- this.nsdManager = this.getSystemService(NSD_SERVICE) as NsdManager
- this.currentPort = 10000
- this.currentSocket = null
- }
-
- override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
- Log.i(TAG, "Received start id $startId: $intent")
- // Display a notification about us starting. We put an icon in the status bar.
- createNotificationChannel()
- val n = buildNotification()
- val foregroundServiceType = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) ServiceInfo.FOREGROUND_SERVICE_TYPE_MICROPHONE else 0 // Keep the linter happy
- ServiceCompat.startForeground(this, ID, n, foregroundServiceType)
- ensureMonitorThread()
- return START_REDELIVER_INTENT
- }
-
- private fun ensureMonitorThread() {
- var mt = this.monitorThread
- if (mt != null && mt.isAlive) {
- return
- }
- val currentToken = Any()
- this.connectionToken = currentToken
- mt = Thread {
- while (this.connectionToken == currentToken) {
- try {
- ServerSocket(this.currentPort).use { serverSocket ->
- this.currentSocket = serverSocket
- // Store the chosen port.
- val localPort = serverSocket.localPort
-
- // Register the service so that parent devices can
- // locate the child device
- registerService(localPort)
- val clients = mutableListOf()
-
-// Thread zum Akzeptieren neuer Clients
- Thread {
- try {
- while (!Thread.currentThread().isInterrupted) {
- val client = serverSocket.accept()
- client.tcpNoDelay = true
- synchronized(clients) { clients.add(client) }
- Log.i(TAG, "Neuer Client verbunden: ${client.inetAddress}")
- }
- } catch (e: IOException) {
- Log.e(TAG, "Client-Akzeptierungsfehler: ${e.message}")
- }
- }.start()
-
-// Aufnahme und Verteilung starten
- handleMultiClientStreaming(serverSocket, clients)
-
- }
- } catch (e: Exception) {
- if (this.connectionToken == currentToken) {
- // Just in case
- this.currentPort++
- Log.e(TAG, "Failed to open server socket. Port increased to $currentPort", e)
- }
- }
+ monitorActivity?.runOnUiThread {
+ monitorActivity?.findViewById(R.id.textStatus)
+ ?.setText(R.string.waitingForParent)
}
}
- this.monitorThread = mt
- mt.start()
}
private fun registerService(port: Int) {
- val serviceInfo = NsdServiceInfo()
- serviceInfo.serviceName = "ChildMonitor on " + Build.MODEL
- serviceInfo.serviceType = "_childmonitor._tcp."
- serviceInfo.port = port
- this.registrationListener = object : RegistrationListener {
+ val serviceInfo = NsdServiceInfo().apply {
+ serviceName = "ChildMonitor on ${Build.MODEL}"
+ serviceType = "_childmonitor._tcp."
+ this.port = port
+ }
+ registrationListener = object : RegistrationListener {
override fun onServiceRegistered(nsdServiceInfo: NsdServiceInfo) {
- // Save the service name. Android may have changed it in order to
- // resolve a conflict, so update the name you initially requested
- // with the name Android actually used.
- nsdServiceInfo.serviceName.let { serviceName ->
- Log.i(TAG, "Service name: $serviceName")
- monitorActivity?.let { ma ->
- ma.runOnUiThread {
- val statusText = ma.findViewById(R.id.textStatus)
- statusText.setText(R.string.waitingForParent)
- val serviceText = ma.findViewById(R.id.textService)
- serviceText.text = serviceName
- val portText = ma.findViewById(R.id.port)
- portText.text = port.toString()
- }
- }
+ monitorActivity?.runOnUiThread {
+ monitorActivity?.findViewById(R.id.textStatus)
+ ?.setText(R.string.waitingForParent)
+ monitorActivity?.findViewById(R.id.textService)
+ ?.text = nsdServiceInfo.serviceName
+ monitorActivity?.findViewById(R.id.port)
+ ?.text = port.toString()
}
}
override fun onRegistrationFailed(serviceInfo: NsdServiceInfo, errorCode: Int) {
- // Registration failed! Put debugging code here to determine why.
Log.e(TAG, "Registration failed: $errorCode")
}
- override fun onServiceUnregistered(arg0: NsdServiceInfo) {
- // Service has been unregistered. This only happens when you call
- // NsdManager.unregisterService() and pass in this listener.
- Log.i(TAG, "Unregistering service")
- }
-
- override fun onUnregistrationFailed(serviceInfo: NsdServiceInfo, errorCode: Int) {
- // Unregistration failed. Put debugging code here to determine why.
- Log.e(TAG, "Unregistration failed: $errorCode")
- }
+ override fun onServiceUnregistered(arg0: NsdServiceInfo) {}
+ override fun onUnregistrationFailed(serviceInfo: NsdServiceInfo, errorCode: Int) {}
}
- nsdManager.registerService(
- serviceInfo, NsdManager.PROTOCOL_DNS_SD, registrationListener)
+ nsdManager.registerService(serviceInfo, NsdManager.PROTOCOL_DNS_SD, registrationListener)
}
private fun unregisterService() {
- this.registrationListener?.let {
- this.registrationListener = null
- Log.i(TAG, "Unregistering monitoring service")
- this.nsdManager.unregisterService(it)
+ registrationListener?.let {
+ registrationListener = null
+ nsdManager.unregisterService(it)
}
}
private fun createNotificationChannel() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
- val serviceChannel = NotificationChannel(
+ val channel = NotificationChannel(
CHANNEL_ID,
"Foreground Service Channel",
NotificationManager.IMPORTANCE_DEFAULT
)
- this.notificationManager.createNotificationChannel(serviceChannel)
+ notificationManager.createNotificationChannel(channel)
}
}
private fun buildNotification(): Notification {
- val text: CharSequence = "Child Device"
- // 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
+ return NotificationCompat.Builder(this, CHANNEL_ID)
+ .setSmallIcon(R.drawable.listening_notification)
.setOngoing(true)
- .setTicker(text) // the status text
- .setContentTitle(text) // the label of the entry
- return b.build()
+ .setTicker("Child Device")
+ .setContentTitle("Child Device")
+ .build()
}
override fun onDestroy() {
- this.monitorThread?.let {
- this.monitorThread = null
- it.interrupt()
- }
+ monitorThread?.interrupt()
+ monitorThread = null
unregisterService()
- this.connectionToken = null
- this.currentSocket?.let {
- try {
- it.close()
- } catch (e: IOException) {
- Log.e(TAG, "Failed to close active socket on port $currentPort")
- }
- }
- this.currentSocket = null
-
- // Cancel the persistent notification.
- this.notificationManager.cancel(R.string.listening)
+ connectionToken = null
+ currentSocket?.close()
+ currentSocket = null
+ notificationManager.cancel(R.string.listening)
ServiceCompat.stopForeground(this, ServiceCompat.STOP_FOREGROUND_REMOVE)
- // Tell the user we stopped.
Toast.makeText(this, R.string.stopped, Toast.LENGTH_SHORT).show()
super.onDestroy()
}
- override fun onBind(intent: Intent): IBinder {
- return binder
- }
+ override fun onBind(intent: Intent): IBinder = binder
inner class MonitorBinder : Binder() {
- val service: MonitorService
- get() = this@MonitorService
+ val service: MonitorService get() = this@MonitorService
}
companion object {