now correct "streaming" notice

This commit is contained in:
pi
2025-10-25 12:14:21 +02:00
parent 46cf690ba3
commit adafa6279a

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.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<TextView>(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<Socket>) {
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<Socket>()
// 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<TextView>(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<Socket>) {
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<TextView>(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<Socket>()
// 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<TextView>(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<TextView>(R.id.textStatus)
statusText.setText(R.string.waitingForParent)
val serviceText = ma.findViewById<TextView>(R.id.textService)
serviceText.text = serviceName
val portText = ma.findViewById<TextView>(R.id.port)
portText.text = port.toString()
}
}
monitorActivity?.runOnUiThread {
monitorActivity?.findViewById<TextView>(R.id.textStatus)
?.setText(R.string.waitingForParent)
monitorActivity?.findViewById<TextView>(R.id.textService)
?.text = nsdServiceInfo.serviceName
monitorActivity?.findViewById<TextView>(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 {