package com.bernard.murder.audio; import java.io.IOException; import java.net.SocketAddress; import java.net.SocketException; import java.net.UnknownHostException; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.UUID; import javax.sound.sampled.AudioFormat; import javax.swing.JOptionPane; import com.bernard.murder.BytesUtils; public class AudioServer { // Format des paquets [commande 1, UUID identifier, byte deviceType, String name] public static final byte DECLARE_NUMBER = 0x02; // Format des paquets [commande 1, UUID identifier, byte deviceType, int id] public static final byte OK_ID = 0x03; // Format des paquets [commande 1] public static final byte ASK_AUDIO_LIST= 0x04; // Format des paquets [commande 1, int Count, {int id, String name}] public static final byte GIVE_AUDIO_LIST = 0x05; // Format des paquets: [commande 1, int listenId, int myId] // -> Une enceinte demande à master le son d'un micro public static final byte ASK_STREAMING = 0x06; // Format des paquets: [commande 1, int id] // -> Master demande à un micro d'envoyer du son public static final byte START_STREAMING = 0x07; // Format des paquets: [commande 1, int listenId, int myId] // -> Une enceinte demande à master de ne plus recevoir le son d'un micro public static final byte ASK_STOP_STREAMING = 0x09; // Format des paquets: [commande 1, int id] // -> Master demande à un micro de ne plus émettre. public static final byte STOP_STREAMING = 0x08; // Format des paquets [commande 1, int id, ~ data] public static final byte AUDIO_STREAM = 0x01; // Format des paquets [commande 1, byte deviceType, int deviceId] // Un terminal indique qu'il se ferme à la connection public static final byte DISCONNECTING = 0x0A; public static final byte MASTER_DEVICE = 0x42; public static final byte SPEAKER_DEVICE = 0x01; public static final byte MIC_DEVICE = 0x02; public static AudioFormat formatAudio = new AudioFormat(8000f, 16, 1, true, true); public static int packetMaxSize = 97282; public static int communicationPort = 35295; public Serveur serveur; int micId = 0; int speakerId = 0; Map mics; Map speakers; Map micsAddr; Map speakersAddr; Map> listening; // micId, List private Set changeListeners; public AudioServer() throws SocketException, UnknownHostException { mics = new HashMap(); micsAddr = new HashMap(); speakers = new HashMap(); speakersAddr = new HashMap(); listening = new HashMap>(); changeListeners = new HashSet<>(); initServer(); } public void initServer() throws SocketException, UnknownHostException { serveur = new Serveur(this::receiveCommand, AudioServer.communicationPort); } public void receiveCommand(ByteBuffer data,SocketAddress senderAddress) { byte commande = data.get(); System.out.println("Commande reçue : "+commande+" de "+senderAddress+" de taille "+(data.limit()+1)); switch (commande) { case AudioServer.DECLARE_NUMBER: UUID uuid = new UUID(data.getLong(), data.getLong()); byte deviceType = data.get(); String deviceName = BytesUtils.readString(data); int newId; switch (deviceType) { case AudioServer.MIC_DEVICE: newId = micId++; mics.put(newId, deviceName); micsAddr.put(newId, senderAddress); listening.put(newId, new ArrayList<>()); publishAudioList(speakersAddr.values()); break; case AudioServer.SPEAKER_DEVICE: newId = speakerId++; speakers.put(newId, deviceName); speakersAddr.put(newId, senderAddress); break; default:return; } ByteBuffer out = ByteBuffer.allocate(AudioServer.packetMaxSize); out.put(AudioServer.OK_ID); out.putLong(uuid.getMostSignificantBits()); out.putLong(uuid.getLeastSignificantBits()); out.put(deviceType); out.putInt(newId); try { serveur.sendData(out, senderAddress); } catch (IOException e1) { e1.printStackTrace(); } changeListeners.forEach(Runnable::run); System.out.println("Accepting request from "+senderAddress); break; case AudioServer.ASK_STREAMING: int listened = data.getInt(); int listener = data.getInt(); if(listening.get(listened).contains(listener)) break; ByteBuffer out3 = ByteBuffer.allocate(AudioServer.packetMaxSize); out3.put(AudioServer.START_STREAMING); out3.putInt(listened); try { serveur.sendData(out3, micsAddr.get(listened)); listening.get(listened).add(listener); changeListeners.forEach(Runnable::run); } catch (IOException e2) { e2.printStackTrace(); } break; case AudioServer.ASK_STOP_STREAMING: int listened2 = data.getInt(); int listener2 = data.getInt(); if(listened2==-1)break; try{ listening.get(listened2).remove(listener2); }catch(IndexOutOfBoundsException e) { System.err.println("Les données enregistrées par ce serveur ne sont pas intègres !"); } changeListeners.forEach(Runnable::run); if(listening.get(listened2).isEmpty()) { try { ByteBuffer out4 = ByteBuffer.allocate(AudioServer.packetMaxSize); out4.put(AudioServer.STOP_STREAMING); out4.putInt(listened2); serveur.sendData(out4, micsAddr.get(listened2)); } catch (IOException e2) { e2.printStackTrace(); } } break; case AudioServer.AUDIO_STREAM: int micId = data.getInt(); data.position(data.limit()); for(int spck : listening.get(micId)) { SocketAddress dest = speakersAddr.get(spck); try { serveur.sendData(data, dest); } catch (IOException e1) { e1.printStackTrace(); JOptionPane.showMessageDialog(null, "Impossible de transmettre le son !","Son impossible !",JOptionPane.ERROR_MESSAGE); } } break; case AudioServer.ASK_AUDIO_LIST: System.out.println("Sending audio list to "+senderAddress+" : "); publishAudioList(Collections.singleton(senderAddress)); break; case AudioServer.DISCONNECTING: byte deviceType2 = data.get(); int deviceId = data.getInt(); if(deviceType2==AudioServer.MIC_DEVICE) { // On le déréférence if(!mics.containsKey(deviceId)) { System.out.println("Le micro d'id "+deviceId+" est déjà désinscrit"); break; } System.out.println("Déconnection du microphone "+mics.get(deviceId)); mics.remove(deviceId); micsAddr.remove(deviceId); changeListeners.forEach(Runnable::run); // On enlève tous les liens d'écoute for(int spkId : listening.get(deviceId)) { // Annoncer à ce speaker que le micro est déconnécté et qu'il ne peut plus l'écouter. ByteBuffer out5 = ByteBuffer.allocate(AudioServer.packetMaxSize); out5.put(AudioServer.DISCONNECTING); out5.put(AudioServer.MIC_DEVICE); out5.putInt(deviceId); try { serveur.sendData(out5, speakersAddr.get(spkId)); } catch (IOException e1) { e1.printStackTrace(); } } listening.remove(deviceId); publishAudioList(speakersAddr.values()); }else if(deviceType2==AudioServer.SPEAKER_DEVICE) { // On le déréférence System.out.println("Déconnection de l'enceinte "+speakers.get(deviceId)); speakers.remove(deviceId); speakersAddr.remove(deviceId); changeListeners.forEach(Runnable::run); int lstTo = listens(deviceId); if(lstTo!=-1) { // On enlève le lien d'écoute listening.get(lstTo).remove((Integer)deviceId); if(listening.get(lstTo).isEmpty()) { // Si il n'y a plus rien à écouter. try { ByteBuffer out4 = ByteBuffer.allocate(AudioServer.packetMaxSize); out4.put(AudioServer.STOP_STREAMING); out4.putInt(lstTo); serveur.sendData(out4, micsAddr.get(lstTo)); } catch (IOException e1) { e1.printStackTrace(); } } } }else { System.err.println("Je ne sais pas comment réagir à la déconnection d'un appareil de type "+deviceType2); } break; default: System.out.println("Je ne devait pas recevoir cette commade !"); } } public void addChangeListener(Runnable listener) { changeListeners.add(listener); } public void removeChangeListener(Runnable listener) { changeListeners.remove(listener); } public Map getMics() { return Collections.unmodifiableMap(mics); } public Map getSpeakers() { return Collections.unmodifiableMap(speakers); } public int listens(int spk) { for(int mic : listening.keySet()) { if(listening.get(mic).contains(spk)) return mic; } return -1; } public void publishAudioList(Collection co) { ByteBuffer out = ByteBuffer.allocate(AudioServer.packetMaxSize); out.put(AudioServer.GIVE_AUDIO_LIST); out.putInt(mics.size()); for(Entry mic : mics.entrySet()) { out.putInt(mic.getKey()); BytesUtils.writeString(out, mic.getValue()); } try { for(SocketAddress addr : co) { serveur.sendData(out, addr); } } catch (IOException e1) { e1.printStackTrace(); } } }