package audio import audio.Bass.BASS_STREAMPROC_END import audio.BassEnc.BASS_ENCODE_PCM import kotlinx.coroutines.CoroutineName import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.delay import org.tinylog.Logger import java.io.PipedInputStream import java.io.PipedOutputStream import java.net.DatagramPacket import java.net.DatagramSocket import java.net.InetSocketAddress import java.util.function.BiConsumer import java.util.function.Consumer @Deprecated("Sepertinya gak jadi pake") @Suppress("unused") /** * UDPReceiverToFile is a class that listens for UDP packets on a specified address and port * and writes the received data to a specified file. * It is designed to run in a separate thread and can be stopped when no longer needed. * @param listeningAddress The address to listen for incoming UDP packets. * @param listeningPort The port to listen for incoming UDP packets. * @param samplingrate The sampling rate for the audio data, default is 44,100 Hz. * @param channel The number of audio channels, default is 1 (mono). * @param outputFilePath The path to the file where the received data will be written. * @param senderIP The IP address of the sender from which to accept packets. */ class UDPReceiverToFile(listeningAddress: String, listeningPort: Int, val samplingrate: Int=44100, val channel: Int=1, val outputFilePath: String, val senderIP: String) { private var socket: DatagramSocket? = null private val bass : Bass = Bass.Instance private val bassenc : BassEnc = BassEnc.Instance private var isReceiving: Boolean = false private val pipeIn= PipedInputStream(16*1024) // 16K var isReady: Boolean = false; private set var bytesReceived: Long = 0; private set var bytesWritten: Long = 0; private set init { try{ val socketaddress = InetSocketAddress(listeningAddress, listeningPort) socket = DatagramSocket(socketaddress) isReady = true } catch (e : Exception) { Logger.error {"Failed to create UDP socket: ${e.message}" } } } private val streamProc = Bass.STREAMPROC { handle, buffer, length, user -> try{ val dd = ByteArray(length) val bytesread = pipeIn.read(dd) if (bytesread>0){ buffer?.write(0, dd, 0, bytesread) // Write the data to the buffer bytesWritten += bytesread // Return the number of bytes read bytesread } else { // if bytesread is 0, it means the pipe is empty, return BASS_STREAMPROC_END BASS_STREAMPROC_END } } catch (e : Exception){ // If an error occurs, log it and return BASS_STREAMPROC_END Logger.error { "STREAMPROC exception on UDPReceiverToFile $senderIP $outputFilePath" } BASS_STREAMPROC_END } } /** * Starts receiving data from the UDP socket and writing it to the specified file. * This method runs in a separate thread. * @param callback A BiConsumer that accepts a Boolean indicating success or failure and a String message. * @param udpIsReceiving A Consumer that accepts a Boolean indicating whether UDP is currently receiving */ fun startReceiving(callback : BiConsumer, udpIsReceiving: Consumer) { var isReceiving = false if (isReady){ val scope = CoroutineScope(Dispatchers.Default) scope.launch(CoroutineName("UDPReceiverToFile UDP $senderIP $outputFilePath")) { Logger.info { "UDPReceiverToFile started, listening on ${socket?.localSocketAddress} , saving to $outputFilePath" } PipedOutputStream(pipeIn).use { pipeOut -> while (isReceiving) { try{ val xx = DatagramPacket(ByteArray(1500),1500) socket?.receive(xx) if (xx.address.hostAddress!= senderIP) continue if (xx.length < 1) continue pipeOut.write(xx.data, 0, xx.length) bytesReceived += xx.length if (!isReceiving){ isReceiving = true udpIsReceiving.accept(true) } } catch (e : Exception){ Logger.error { "Error receiving UDP packet: ${e.message}" } continue } } } Logger.info { "UDPReceiverToFile ended" } } scope.launch(CoroutineName("UDPReceiverToFile BASS $senderIP $outputFilePath")) { bass.BASS_SetDevice(0) // Set to No Sound device, we are not playing audio val streamhandle = bass.BASS_StreamCreate(samplingrate, channel, 0, streamProc, null) if (streamhandle!=0){ bass.BASS_ChannelPlay(streamhandle,false) val encodehandle = bassenc.BASS_Encode_Start(streamhandle, outputFilePath, BASS_ENCODE_PCM, null, null) if (encodehandle!=0){ Logger.info { "UDPReceiverToFile started writing to $outputFilePath" } callback.accept(true, "UDPReceiverToFile started successfully, writing to $outputFilePath") while (isReceiving) { try { delay(1000) } catch (e: InterruptedException) { Logger.error { "UDPReceiverToFile thread interrupted: ${e.message}" } break } } bassenc.BASS_Encode_Stop(encodehandle) bass.BASS_StreamFree(streamhandle) Logger.info { "UDPReceiverToFile stopped writing to $outputFilePath" } callback.accept(false, "UDPReceiverToFile stopped successfully, written bytes: $bytesWritten") } else { callback.accept(false, "Failed to start encoding: ${bass.BASS_ErrorGetCode()}") } } else { callback.accept(false, "Failed to create stream: ${bass.BASS_ErrorGetCode()}") } } } else callback.accept(false, "UDPReceiverToFile is not ready. Check if the socket was created successfully.") } /** * Stop UDPReceiverToFile from receiving data. */ fun stopReceiving() { isReceiving = false } }