now correct "streaming" notice
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.Notification
|
import android.app.Notification
|
||||||
@@ -50,53 +34,80 @@ class MonitorService : Service() {
|
|||||||
private var monitorThread: Thread? = null
|
private var monitorThread: Thread? = null
|
||||||
var monitorActivity: MonitorActivity? = null
|
var monitorActivity: MonitorActivity? = null
|
||||||
|
|
||||||
private fun serviceConnection(socket: Socket) {
|
override fun onCreate() {
|
||||||
val ma = this.monitorActivity
|
super.onCreate()
|
||||||
ma?.runOnUiThread {
|
this.notificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
|
||||||
val statusText = ma.findViewById<TextView>(R.id.textStatus)
|
this.nsdManager = this.getSystemService(NSD_SERVICE) as NsdManager
|
||||||
statusText.setText(R.string.streaming)
|
this.currentPort = 10000
|
||||||
}
|
this.currentSocket = null
|
||||||
val frequency: Int = AudioCodecDefines.FREQUENCY
|
Log.i(TAG, "ChildMonitor start")
|
||||||
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()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
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(
|
val audioRecord = AudioRecord(
|
||||||
MediaRecorder.AudioSource.MIC,
|
MediaRecorder.AudioSource.MIC,
|
||||||
frequency,
|
frequency,
|
||||||
@@ -116,21 +127,27 @@ class MonitorService : Service() {
|
|||||||
while (!Thread.currentThread().isInterrupted && !serverSocket.isClosed) {
|
while (!Thread.currentThread().isInterrupted && !serverSocket.isClosed) {
|
||||||
val read = audioRecord.read(pcmBuffer, 0, bufferSize)
|
val read = audioRecord.read(pcmBuffer, 0, bufferSize)
|
||||||
if (read > 0) {
|
if (read > 0) {
|
||||||
val encoded: Int = AudioCodecDefines.CODEC.encode(pcmBuffer, read, ulawBuffer, 0)
|
val encoded = AudioCodecDefines.CODEC.encode(pcmBuffer, read, ulawBuffer, 0)
|
||||||
|
|
||||||
synchronized(clients) {
|
synchronized(clients) {
|
||||||
val iterator = clients.iterator()
|
val iterator = clients.iterator()
|
||||||
while (iterator.hasNext()) {
|
while (iterator.hasNext()) {
|
||||||
val client = iterator.next()
|
val client = iterator.next()
|
||||||
try {
|
try {
|
||||||
val out = client.getOutputStream()
|
client.getOutputStream().write(ulawBuffer, 0, encoded)
|
||||||
out.write(ulawBuffer, 0, encoded)
|
|
||||||
} catch (e: IOException) {
|
} catch (e: IOException) {
|
||||||
try { client.close() } catch (ignored: IOException) {}
|
try { client.close() } catch (_: IOException) {}
|
||||||
iterator.remove()
|
iterator.remove()
|
||||||
Log.w(TAG, "Client getrennt: ${client.inetAddress}")
|
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()
|
clients.clear()
|
||||||
}
|
}
|
||||||
Log.i(TAG, "Audioaufnahme beendet, alle Clients getrennt.")
|
Log.i(TAG, "Audioaufnahme beendet, alle Clients getrennt.")
|
||||||
}
|
monitorActivity?.runOnUiThread {
|
||||||
}
|
monitorActivity?.findViewById<TextView>(R.id.textStatus)
|
||||||
|
?.setText(R.string.waitingForParent)
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.monitorThread = mt
|
|
||||||
mt.start()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun registerService(port: Int) {
|
private fun registerService(port: Int) {
|
||||||
val serviceInfo = NsdServiceInfo()
|
val serviceInfo = NsdServiceInfo().apply {
|
||||||
serviceInfo.serviceName = "ChildMonitor on " + Build.MODEL
|
serviceName = "ChildMonitor on ${Build.MODEL}"
|
||||||
serviceInfo.serviceType = "_childmonitor._tcp."
|
serviceType = "_childmonitor._tcp."
|
||||||
serviceInfo.port = port
|
this.port = port
|
||||||
this.registrationListener = object : RegistrationListener {
|
}
|
||||||
|
registrationListener = object : RegistrationListener {
|
||||||
override fun onServiceRegistered(nsdServiceInfo: NsdServiceInfo) {
|
override fun onServiceRegistered(nsdServiceInfo: NsdServiceInfo) {
|
||||||
// Save the service name. Android may have changed it in order to
|
monitorActivity?.runOnUiThread {
|
||||||
// resolve a conflict, so update the name you initially requested
|
monitorActivity?.findViewById<TextView>(R.id.textStatus)
|
||||||
// with the name Android actually used.
|
?.setText(R.string.waitingForParent)
|
||||||
nsdServiceInfo.serviceName.let { serviceName ->
|
monitorActivity?.findViewById<TextView>(R.id.textService)
|
||||||
Log.i(TAG, "Service name: $serviceName")
|
?.text = nsdServiceInfo.serviceName
|
||||||
monitorActivity?.let { ma ->
|
monitorActivity?.findViewById<TextView>(R.id.port)
|
||||||
ma.runOnUiThread {
|
?.text = port.toString()
|
||||||
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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onRegistrationFailed(serviceInfo: NsdServiceInfo, errorCode: Int) {
|
override fun onRegistrationFailed(serviceInfo: NsdServiceInfo, errorCode: Int) {
|
||||||
// Registration failed! Put debugging code here to determine why.
|
|
||||||
Log.e(TAG, "Registration failed: $errorCode")
|
Log.e(TAG, "Registration failed: $errorCode")
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onServiceUnregistered(arg0: NsdServiceInfo) {
|
override fun onServiceUnregistered(arg0: NsdServiceInfo) {}
|
||||||
// Service has been unregistered. This only happens when you call
|
override fun onUnregistrationFailed(serviceInfo: NsdServiceInfo, errorCode: Int) {}
|
||||||
// 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")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
nsdManager.registerService(
|
nsdManager.registerService(serviceInfo, NsdManager.PROTOCOL_DNS_SD, registrationListener)
|
||||||
serviceInfo, NsdManager.PROTOCOL_DNS_SD, registrationListener)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun unregisterService() {
|
private fun unregisterService() {
|
||||||
this.registrationListener?.let {
|
registrationListener?.let {
|
||||||
this.registrationListener = null
|
registrationListener = null
|
||||||
Log.i(TAG, "Unregistering monitoring service")
|
nsdManager.unregisterService(it)
|
||||||
this.nsdManager.unregisterService(it)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createNotificationChannel() {
|
private fun createNotificationChannel() {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
val serviceChannel = NotificationChannel(
|
val channel = NotificationChannel(
|
||||||
CHANNEL_ID,
|
CHANNEL_ID,
|
||||||
"Foreground Service Channel",
|
"Foreground Service Channel",
|
||||||
NotificationManager.IMPORTANCE_DEFAULT
|
NotificationManager.IMPORTANCE_DEFAULT
|
||||||
)
|
)
|
||||||
this.notificationManager.createNotificationChannel(serviceChannel)
|
notificationManager.createNotificationChannel(channel)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun buildNotification(): Notification {
|
private fun buildNotification(): Notification {
|
||||||
val text: CharSequence = "Child Device"
|
return NotificationCompat.Builder(this, CHANNEL_ID)
|
||||||
// Set the info for the views that show in the notification panel.
|
.setSmallIcon(R.drawable.listening_notification)
|
||||||
val b = NotificationCompat.Builder(this, CHANNEL_ID)
|
|
||||||
b.setSmallIcon(R.drawable.listening_notification) // the status icon
|
|
||||||
.setOngoing(true)
|
.setOngoing(true)
|
||||||
.setTicker(text) // the status text
|
.setTicker("Child Device")
|
||||||
.setContentTitle(text) // the label of the entry
|
.setContentTitle("Child Device")
|
||||||
return b.build()
|
.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
this.monitorThread?.let {
|
monitorThread?.interrupt()
|
||||||
this.monitorThread = null
|
monitorThread = null
|
||||||
it.interrupt()
|
|
||||||
}
|
|
||||||
unregisterService()
|
unregisterService()
|
||||||
this.connectionToken = null
|
connectionToken = null
|
||||||
this.currentSocket?.let {
|
currentSocket?.close()
|
||||||
try {
|
currentSocket = null
|
||||||
it.close()
|
notificationManager.cancel(R.string.listening)
|
||||||
} 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)
|
|
||||||
ServiceCompat.stopForeground(this, ServiceCompat.STOP_FOREGROUND_REMOVE)
|
ServiceCompat.stopForeground(this, ServiceCompat.STOP_FOREGROUND_REMOVE)
|
||||||
// Tell the user we stopped.
|
|
||||||
Toast.makeText(this, R.string.stopped, Toast.LENGTH_SHORT).show()
|
Toast.makeText(this, R.string.stopped, Toast.LENGTH_SHORT).show()
|
||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onBind(intent: Intent): IBinder {
|
override fun onBind(intent: Intent): IBinder = binder
|
||||||
return binder
|
|
||||||
}
|
|
||||||
|
|
||||||
inner class MonitorBinder : Binder() {
|
inner class MonitorBinder : Binder() {
|
||||||
val service: MonitorService
|
val service: MonitorService get() = this@MonitorService
|
||||||
get() = this@MonitorService
|
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|||||||
Reference in New Issue
Block a user