diff --git a/src/main/java/com/bernard/murder/BytesUtils.java b/src/main/java/com/bernard/murder/BytesUtils.java index f8eb436..a89a5d7 100755 --- a/src/main/java/com/bernard/murder/BytesUtils.java +++ b/src/main/java/com/bernard/murder/BytesUtils.java @@ -30,7 +30,7 @@ public class BytesUtils { } public static void dump(ByteBuffer buffer) { - System.out.println(toStream(buffer).limit(buffer.position()).map(b -> b.toString()).collect(Collectors.joining(","))); + System.out.println(toStream(buffer).limit(buffer.position()).map(b -> Integer.toString(Byte.toUnsignedInt(b))).collect(Collectors.joining(","))); } } diff --git a/src/main/java/com/bernard/murder/audio/AudioServer.java b/src/main/java/com/bernard/murder/audio/AudioServer.java index af5c080..0f10f02 100755 --- a/src/main/java/com/bernard/murder/audio/AudioServer.java +++ b/src/main/java/com/bernard/murder/audio/AudioServer.java @@ -6,13 +6,18 @@ 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; @@ -27,17 +32,25 @@ public class AudioServer { // 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; @@ -57,19 +70,18 @@ public class AudioServer { Map speakersAddr; Map> listening; // micId, List - public AudioServer() { + 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<>(); - try { - initServer(); - } catch (SocketException | UnknownHostException e) { - e.printStackTrace(); - } + initServer(); } public void initServer() throws SocketException, UnknownHostException { @@ -78,7 +90,7 @@ public class AudioServer { public void receiveCommand(ByteBuffer data,SocketAddress senderAddress) { byte commande = data.get(); - System.out.println("Commande reçue : "+commande); + System.out.println("Commande reçue : "+commande+" de "+senderAddress+" de taille "+(data.limit()+1)); switch (commande) { case AudioServer.DECLARE_NUMBER: @@ -93,8 +105,9 @@ public class AudioServer { mics.put(newId, deviceName); micsAddr.put(newId, senderAddress); listening.put(newId, new ArrayList<>()); + publishAudioList(speakersAddr.values()); break; - case AudioServer.SPEAKER_DEVICE: + case AudioServer.SPEAKER_DEVICE: newId = speakerId++; speakers.put(newId, deviceName); speakersAddr.put(newId, senderAddress); @@ -112,74 +125,179 @@ public class AudioServer { } 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(); - listening.get(listened).add(listener); + + 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.STOP_STREAMING: + case AudioServer.ASK_STOP_STREAMING: int listened2 = data.getInt(); int listener2 = data.getInt(); - listening.get(listener2).remove(listened2); - ByteBuffer out4 = ByteBuffer.allocate(AudioServer.packetMaxSize); - out4.put(AudioServer.STOP_STREAMING); - out4.putInt(listened2); - try { - serveur.sendData(out4, micsAddr.get(listened2)); - } catch (IOException e2) { - e2.printStackTrace(); + 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(); - byte[] audioData = new byte[data.remaining()]; - data.get(audioData); - + data.position(data.limit()); for(int spck : listening.get(micId)) { - data.clear(); 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: - ByteBuffer out2 = ByteBuffer.allocate(AudioServer.packetMaxSize); - out2.put(AudioServer.GIVE_AUDIO_LIST); - out2.putInt(mics.size()); - for(Entry mic : mics.entrySet()) { - out2.putInt(mic.getKey()); - BytesUtils.writeString(out2, mic.getValue()); - } + System.out.println("Sending audio list to "+senderAddress+" : "); - BytesUtils.dump(out2); - try { - serveur.sendData(out2, senderAddress); - } catch (IOException e1) { - e1.printStackTrace(); + 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: - break; + 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(); + } + } } diff --git a/src/main/java/com/bernard/murder/audio/MicServer.java b/src/main/java/com/bernard/murder/audio/MicServer.java index a22cbc0..34d8700 100755 --- a/src/main/java/com/bernard/murder/audio/MicServer.java +++ b/src/main/java/com/bernard/murder/audio/MicServer.java @@ -5,6 +5,8 @@ import java.net.SocketAddress; import java.net.SocketException; import java.net.UnknownHostException; import java.nio.ByteBuffer; +import java.util.HashSet; +import java.util.Set; import java.util.UUID; import javax.sound.sampled.LineUnavailableException; @@ -34,11 +36,14 @@ public class MicServer { Runnable serverAnswered; + Set disconnectListener; + public MicServer(SocketAddress adresse,String micName,TargetDataLine tdl) { this.micName = micName; this.masterAddress = adresse; this.micLine = tdl; + this.disconnectListener = new HashSet<>(); try { initServer(); initializeAudioId(); @@ -51,6 +56,7 @@ public class MicServer { public void initializeMicDevice() throws LineUnavailableException { micLine.open(AudioServer.formatAudio); packetLength = micLine.getBufferSize()/5; + System.out.println("Longueur du paquet: "+packetLength); } public void initServer() throws SocketException, UnknownHostException { @@ -79,18 +85,21 @@ public class MicServer { public void receiveCommand(ByteBuffer data) { byte commande = data.get(); + System.out.println("Commande reçue : "+commande); switch (commande) { case AudioServer.START_STREAMING: - int micId = data.getInt(); - if(micId != this.micId)return; + int askedMicId = data.getInt(); + if(askedMicId != this.micId)return; shouldStream = true; + micLine.start(); launchDataStream(); break; case AudioServer.STOP_STREAMING: - int micId2 = data.getInt(); - if(micId2 != this.micId)return; + int askedMicId2 = data.getInt(); + if(askedMicId2 != this.micId)return; shouldStream = false; + micLine.stop(); break; case AudioServer.OK_ID: @@ -101,19 +110,30 @@ public class MicServer { if(!askedUUID.equals(uuid) || deviceType!=AudioServer.MIC_DEVICE) return; - micId = deviceId; + this.micId = deviceId; new Thread(serverAnswered).start(); - if(micLine==null) + if(!micLine.isOpen()) try { initializeMicDevice(); } catch (LineUnavailableException e) { e.printStackTrace(); } break; - + + case AudioServer.DISCONNECTING: + byte deviceType2 = data.get(); + if(deviceType2==AudioServer.MASTER_DEVICE) { + System.out.println("Le master s'est déconnécté, on fait de même !"); + masterAddress=null; + this.dispose(); + disconnectListener.forEach(Runnable::run); + }else { + System.out.println("Un appareil de type "+deviceType2+" s'est déconécté, mais je m'en fout ^^"); + } default: + System.out.println("Je ne devait pas recevoir cette commade !"); break; } } @@ -125,6 +145,8 @@ public class MicServer { ByteBuffer audioPacket = ByteBuffer.wrap(packetData); audioPacket.put(AudioServer.AUDIO_STREAM); audioPacket.putInt(micId); + audioPacket.position(audioPacket.position()+packetLength); + micLine.start(); while(shouldStream) { micLine.read(packetData, 5, packetLength); try { @@ -134,11 +156,24 @@ public class MicServer { } } + micLine.stop(); }); streamingThread.start(); } public void dispose() { + if(masterAddress!=null) { + ByteBuffer decoPacket = ByteBuffer.allocate(AudioServer.packetMaxSize); + decoPacket.put(AudioServer.DISCONNECTING); + decoPacket.put(AudioServer.MIC_DEVICE); + decoPacket.putInt(micId); + try { + serveur.sendData(decoPacket,masterAddress); + } catch (IOException e) { + e.printStackTrace(); + } + } + shouldStream=false; micLine.close(); serveur.dispose(); } @@ -147,4 +182,12 @@ public class MicServer { this.serverAnswered = serverAnswered; } + public void addDisconnectListener(Runnable listener) { + disconnectListener.add(listener); + } + public void removeDisconnectListener(Runnable listener) { + disconnectListener.remove(listener); + } + + } diff --git a/src/main/java/com/bernard/murder/audio/Serveur.java b/src/main/java/com/bernard/murder/audio/Serveur.java index c88a0a0..f1f4345 100755 --- a/src/main/java/com/bernard/murder/audio/Serveur.java +++ b/src/main/java/com/bernard/murder/audio/Serveur.java @@ -14,6 +14,7 @@ import java.util.UUID; import java.util.function.BiConsumer; import java.util.function.Consumer; +import com.bernard.murder.BytesUtils; import com.bernard.murder.ParseUtils; public class Serveur { @@ -88,7 +89,10 @@ public class Serveur { receivedSlicesArray.remove(uuid); } }else { - ByteBuffer dataBuffer = ByteBuffer.wrap(data,paquet.getOffset(),paquet.getLength()); + byte[] realData = new byte[paquet.getLength()]; + System.arraycopy(data, paquet.getOffset(), realData, 0, paquet.getLength()); + ByteBuffer dataBuffer = ByteBuffer.wrap(realData); + BytesUtils.dump(dataBuffer); consumer.accept(dataBuffer,paquet.getSocketAddress()); } @@ -98,23 +102,23 @@ public class Serveur { } } - }); + },"Receveur de paquets"); isReceiving = true; packetReceiver.start(); } public void sendData(ByteBuffer buffer,SocketAddress address) throws IOException { byte[] data = new byte[buffer.position()]; - buffer.clear(); + buffer.flip(); buffer.get(data); sendData(data,address); } public void sendData(byte[] data, SocketAddress address) throws IOException { if(data.length < packetMaxLength) { DatagramPacket packet = new DatagramPacket(data, data.length,address); - System.out.println("Sent "+packet.getLength()+" bytes to "+packet.getAddress()); socket.send(packet); }else { + //XXX Ça, ca ne marche pas ! short packetCount = (short) (data.length / (packetMaxLength-42)); short packetLength = (short) (data.length / packetCount); short lastPacketLength = (short) (data.length - (packetCount-1)*packetLength); diff --git a/src/main/java/com/bernard/murder/audio/SpeakerServer.java b/src/main/java/com/bernard/murder/audio/SpeakerServer.java index 1883b62..cc7cbe0 100755 --- a/src/main/java/com/bernard/murder/audio/SpeakerServer.java +++ b/src/main/java/com/bernard/murder/audio/SpeakerServer.java @@ -6,8 +6,11 @@ import java.net.SocketException; import java.net.UnknownHostException; import java.nio.ByteBuffer; import java.util.HashMap; +import java.util.HashSet; import java.util.Map; +import java.util.Set; import java.util.UUID; +import java.util.function.Consumer; import javax.sound.sampled.LineUnavailableException; import javax.sound.sampled.SourceDataLine; @@ -19,8 +22,6 @@ public class SpeakerServer { SourceDataLine speakerLine; - int packetLength = 9728; - int speakerId; String speakerName; @@ -30,7 +31,6 @@ public class SpeakerServer { Serveur serveur; Map mics; - volatile boolean isMicListUpToDate = false; int listeningTo = -1; @@ -38,11 +38,21 @@ public class SpeakerServer { Runnable serverAnswered; + + Set disconnectListener; + Set brokenMicListener; + Set>> micListUpdateListener; + public SpeakerServer(SocketAddress serveur,String speakerName,SourceDataLine speaker) { this.speakerName = speakerName; this.masterAddress = serveur; this.speakerLine = speaker; + + this.disconnectListener = new HashSet<>(); + this.brokenMicListener = new HashSet<>(); + this.micListUpdateListener = new HashSet<>(); + try { initServer(); initializeAudioId(); @@ -57,7 +67,7 @@ public class SpeakerServer { public void initializeSpeakerDevice() throws LineUnavailableException { speakerLine.open(AudioServer.formatAudio); - packetLength = speakerLine.getBufferSize()/5; + speakerLine.start(); } public void initializeAudioId() { @@ -82,10 +92,15 @@ public class SpeakerServer { } public void askForStream(int micId) { + + if(listeningTo==micId) + return; + + ByteBuffer buffer = ByteBuffer.allocate(AudioServer.packetMaxSize); - buffer.put(AudioServer.START_STREAMING); + buffer.put(AudioServer.ASK_STREAMING); buffer.putInt(micId); buffer.putInt(speakerId); @@ -97,16 +112,21 @@ public class SpeakerServer { } } - public void stopStreaming() { + public void askStopStreaming() { + if(listeningTo==-1) { + System.out.println("J'arêtte d'écouter le vide"); + return; + } ByteBuffer buffer = ByteBuffer.allocate(AudioServer.packetMaxSize); - buffer.put(AudioServer.STOP_STREAMING); + buffer.put(AudioServer.ASK_STOP_STREAMING); buffer.putInt(listeningTo); buffer.putInt(speakerId); try { serveur.sendData(buffer,masterAddress); + listeningTo=-1; } catch (IOException e) { e.printStackTrace(); } @@ -121,23 +141,6 @@ public class SpeakerServer { e.printStackTrace(); } } - public Map getAudioList() { - return getAudioList(false); - } - - public Map getAudioList(boolean invalidate) { - isMicListUpToDate = !invalidate && isMicListUpToDate; - if(!isMicListUpToDate)askAudioList(); - while(!isMicListUpToDate) { - try { - Thread.sleep(1);//XXX Can be interrupted here avec le bouton «Arreter» - } catch (InterruptedException e) { - e.printStackTrace(); - } - } - System.out.println("Voici les "+mics); - return mics; - } public void receiveCommand(ByteBuffer data) { byte commande = data.get(); @@ -146,11 +149,10 @@ public class SpeakerServer { case AudioServer.AUDIO_STREAM: int micId = data.getInt(); - if(micId != listeningTo)return; - data.compact(); + if(micId != listeningTo)break; byte[] audioData=new byte[data.remaining()]; data.get(audioData);//XXX Check wether audio data starts at position and not at 0 - speakerLine.write(audioData, 0, packetLength); + speakerLine.write(audioData, 0, audioData.length); break; case AudioServer.OK_ID: @@ -163,12 +165,14 @@ public class SpeakerServer { speakerId = deviceId; new Thread(serverAnswered).start(); - if(speakerLine==null) + if(!speakerLine.isOpen()) try { initializeSpeakerDevice(); } catch (LineUnavailableException e) { e.printStackTrace(); } + + askAudioList(); break; case AudioServer.GIVE_AUDIO_LIST: int micCount = data.getInt(); @@ -178,15 +182,51 @@ public class SpeakerServer { mics.put(thisMicId,BytesUtils.readString(data)); } System.out.println("Audio list given: "+mics); - isMicListUpToDate=true; + micListUpdateListener.forEach(c -> c.accept(mics)); break; + case AudioServer.DISCONNECTING: + byte deviceType2 = data.get(); + int deviceId2 = data.getInt(); + if(deviceType2==AudioServer.MASTER_DEVICE) { + System.out.println("Le master s'est déconnécté, on fait de même !"); + masterAddress=null; + this.dispose(); + for(Runnable toRun : disconnectListener) + toRun.run(); + }else if(deviceType2==AudioServer.MIC_DEVICE){ + if(listeningTo==deviceId2) { + System.out.println("Le micro que l'on écoutait s'est déconnécté."); + listeningTo=-1; + for(Runnable toRun : brokenMicListener) + toRun.run(); + } + }else { + System.out.println("Un appareil de type "+deviceType2+" s'est déconécté, mais je m'en fout ^^"); + } + break; default: + System.out.println("Je ne devait pas recevoir cette commade !"); break; } } public void dispose() { - speakerLine.close(); + if(masterAddress!=null) { + ByteBuffer decoPacket = ByteBuffer.allocate(AudioServer.packetMaxSize); + decoPacket.put(AudioServer.DISCONNECTING); + decoPacket.put(AudioServer.SPEAKER_DEVICE); + decoPacket.putInt(speakerId); + try { + serveur.sendData(decoPacket,masterAddress); + } catch (IOException e) { + e.printStackTrace(); + } + } + if(mics!=null)mics.clear(); + if(speakerLine!=null) { + speakerLine.stop(); + speakerLine.close(); + } serveur.dispose(); } @@ -195,7 +235,26 @@ public class SpeakerServer { } + public void addDisconnectListener(Runnable listener) { + disconnectListener.add(listener); + } + public void removeDisconnectListener(Runnable listener) { + disconnectListener.remove(listener); + } + public void addBrokenMicListener(Runnable listener) { + brokenMicListener.add(listener); + } + public void removeBrokenMicListener(Runnable listener) { + brokenMicListener.remove(listener); + } + + public void addMicListUpdateListener(Consumer> listener) { + micListUpdateListener.add(listener); + } + public void removeMicListUpdateListener(Consumer> listener) { + micListUpdateListener.remove(listener); + } } diff --git a/src/main/java/com/bernard/murder/game/GameManager.java b/src/main/java/com/bernard/murder/game/GameManager.java index 7aaad14..1a91921 100644 --- a/src/main/java/com/bernard/murder/game/GameManager.java +++ b/src/main/java/com/bernard/murder/game/GameManager.java @@ -78,6 +78,8 @@ public class GameManager { if(tempOldSave.exists())tempOldSave.delete(); } catch (IOException e) { e.printStackTrace(); + System.err.println("La sauvegarde rapide n'a pas fonctionné. Elle est donc désactivée."); + quickSaver.stop(); } } diff --git a/src/main/java/com/bernard/murder/view/EnceinteServeurFrame.java b/src/main/java/com/bernard/murder/view/EnceinteServeurFrame.java index 56674a4..d97991c 100755 --- a/src/main/java/com/bernard/murder/view/EnceinteServeurFrame.java +++ b/src/main/java/com/bernard/murder/view/EnceinteServeurFrame.java @@ -1,7 +1,9 @@ package com.bernard.murder.view; import java.awt.BorderLayout; +import java.awt.Container; import java.awt.Dimension; +import java.awt.GridLayout; import java.net.InetSocketAddress; import java.util.ArrayList; import java.util.List; @@ -13,11 +15,18 @@ import javax.sound.sampled.LineUnavailableException; import javax.sound.sampled.Mixer; import javax.sound.sampled.Mixer.Info; import javax.sound.sampled.SourceDataLine; +import javax.swing.BorderFactory; +import javax.swing.DefaultListModel; import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.JList; import javax.swing.JPanel; +import javax.swing.JScrollPane; import javax.swing.JTextField; +import javax.swing.ListSelectionModel; +import javax.swing.border.EmptyBorder; +import javax.swing.event.ListSelectionEvent; +import javax.swing.event.ListSelectionListener; import com.bernard.murder.audio.AudioServer; import com.bernard.murder.audio.SpeakerServer; @@ -28,35 +37,32 @@ public class EnceinteServeurFrame extends JFrame{ SpeakerServer serveur; String deviceName; + NamedMicrophone[] micarray; public EnceinteServeurFrame(String deviceName) { this.setSize(300, 500); this.setMinimumSize(new Dimension(100, 200)); this.setLocationRelativeTo(null); this.setDefaultCloseOperation(EXIT_ON_CLOSE); - this.setTitle("Serveur audio"); + this.setTitle("Serveur audio: Enceinte"); this.deviceName = deviceName; this.setContentPane(genContentPan()); this.setVisible(true); } - public JPanel genContentPan() { + public Container genContentPan() { JPanel panel = new JPanel(new BorderLayout()); - - + panel.setBorder(new EmptyBorder(3, 3, 3, 3)); InformedSourceDataline[] marray = getEnceinteList(); JList enceinteListe = new JList(marray); - - JPanel masterPanel = new JPanel(new BorderLayout()); - JTextField masterIP = new JTextField("192.168.1.1"); + JTextField masterIP = new JTextField("192.168.1.1",15); JButton serverControl = new JButton("Lancer"); - masterPanel.add(serverControl,BorderLayout.EAST); - masterPanel.add(masterIP,BorderLayout.CENTER); JList mics = new JList<>(); + JButton silenceButton = new JButton("Silence"); serverControl.addActionListener(e->{ if(enceinteListe.getSelectedValue()==null)return; @@ -64,25 +70,75 @@ public class EnceinteServeurFrame extends JFrame{ if(serveur!=null) { serveur.dispose(); serveur = null; + masterIP.setEnabled(true); + mics.setModel(new DefaultListModel<>()); serverControl.setText("Lancer"); }else { serveur = new SpeakerServer(new InetSocketAddress(masterIP.getText(), AudioServer.communicationPort), deviceName,enceinteListe.getSelectedValue().tdl); + masterIP.setEnabled(false); serveur.setServerAnswered(()->{ serverControl.setText("Arrêter"); - List list = serveur.getAudioList().entrySet().stream().map(et -> new NamedMicrophone(et.getKey(), et.getValue())).collect(Collectors.toList()); - NamedMicrophone[] micarray = new NamedMicrophone[list.size()]; + }); + serveur.addMicListUpdateListener(m -> { + List list = m.entrySet().stream().map(et -> new NamedMicrophone(et.getKey(), et.getValue())).collect(Collectors.toList()); + micarray = new NamedMicrophone[list.size()]; list.toArray(micarray); mics.setListData(micarray); System.out.println("Micros chargés: "+list); }); + serveur.addDisconnectListener(()->{ + // Le dispose a été fait + serveur = null; + masterIP.setEnabled(true); + serverControl.setText("Lancer"); + }); + serveur.addBrokenMicListener(()->{ + //XXX Test si ca fonctionne, si ca déclenche bien le listener. + mics.clearSelection(); + serveur.askStopStreaming(); + }); serverControl.setText("Lancement"); } serverControl.setEnabled(true); }); - panel.add(masterPanel,BorderLayout.NORTH); - panel.add(mics,BorderLayout.SOUTH); - panel.add(enceinteListe,BorderLayout.CENTER); + mics.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + + mics.getSelectionModel().addListSelectionListener(new ListSelectionListener() { + + @Override + public void valueChanged(ListSelectionEvent e) { + int i = e.getFirstIndex(); + boolean wantToListen=mics.isSelectedIndex(i); + if(wantToListen && serveur != null) + serveur.askForStream(micarray[i].micId); + } + }); + + silenceButton.addActionListener(e -> { + mics.clearSelection(); + if(serveur!=null) + serveur.askStopStreaming(); + }); + + JScrollPane jE = new JScrollPane(enceinteListe); + jE.setBorder(BorderFactory.createTitledBorder("Enceintes disponibles")); + JScrollPane jM = new JScrollPane(mics); + jM.setBorder(BorderFactory.createTitledBorder("Microphones à écouter")); + + + JPanel headP = new JPanel(new BorderLayout()); + JPanel centerP = new JPanel(new GridLayout(2, 1)); + + headP.add(serverControl,BorderLayout.EAST); + headP.add(masterIP,BorderLayout.CENTER); + centerP.add(jE); + centerP.add(jM); + + panel.add(headP,BorderLayout.NORTH); + panel.add(centerP,BorderLayout.CENTER); + panel.add(silenceButton,BorderLayout.SOUTH); + return panel; } diff --git a/src/main/java/com/bernard/murder/view/LauncherFrame.java b/src/main/java/com/bernard/murder/view/LauncherFrame.java index 1f26d21..c61723f 100755 --- a/src/main/java/com/bernard/murder/view/LauncherFrame.java +++ b/src/main/java/com/bernard/murder/view/LauncherFrame.java @@ -3,10 +3,15 @@ package com.bernard.murder.view; import java.awt.GridLayout; import java.io.File; import java.text.DateFormat; +import java.util.Arrays; import java.util.Calendar; import java.util.List; import java.util.Map; +import javax.sound.sampled.AudioSystem; +import javax.sound.sampled.DataLine; +import javax.sound.sampled.LineUnavailableException; +import javax.sound.sampled.TargetDataLine; import javax.swing.JButton; import javax.swing.JFileChooser; import javax.swing.JFrame; @@ -15,6 +20,7 @@ import javax.swing.JPanel; import javax.swing.UIManager; import javax.swing.UnsupportedLookAndFeelException; +import com.bernard.murder.audio.AudioServer; import com.bernard.murder.game.GameCreator; import com.bernard.murder.game.GameCreator.QuicksavedPartie; import com.bernard.murder.game.GameManager; @@ -26,6 +32,24 @@ public class LauncherFrame extends JFrame{ private static final long serialVersionUID = 5831232688024137883L; + public static void main2(String[] args) throws LineUnavailableException { + DataLine.Info ifM = new DataLine.Info(TargetDataLine.class, AudioServer.formatAudio); + TargetDataLine mic = (TargetDataLine) AudioSystem.getLine(ifM); + //DataLine.Info ifS = new DataLine.Info(SourceDataLine.class, AudioServer.formatAudio); + //SourceDataLine spk = (SourceDataLine) AudioSystem.getLine(ifS); + + mic.open(AudioServer.formatAudio); + byte[] thedata = new byte[3512]; + mic.start(); + for(int i=0;i<500;i++) { + System.out.println("Read :"+mic.read(thedata, 0, thedata.length)); + System.out.println(Arrays.toString(thedata)); + } + mic.close(); + + + } + public static void main(String[] args) { new LauncherFrame(); } diff --git a/src/main/java/com/bernard/murder/view/MicServeurFrame.java b/src/main/java/com/bernard/murder/view/MicServeurFrame.java index 4da967e..974894e 100755 --- a/src/main/java/com/bernard/murder/view/MicServeurFrame.java +++ b/src/main/java/com/bernard/murder/view/MicServeurFrame.java @@ -12,11 +12,14 @@ import javax.sound.sampled.LineUnavailableException; import javax.sound.sampled.Mixer; import javax.sound.sampled.Mixer.Info; import javax.sound.sampled.TargetDataLine; +import javax.swing.BorderFactory; import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.JList; import javax.swing.JPanel; +import javax.swing.JScrollPane; import javax.swing.JTextField; +import javax.swing.border.EmptyBorder; import com.bernard.murder.audio.AudioServer; import com.bernard.murder.audio.MicServer; @@ -29,11 +32,11 @@ public class MicServeurFrame extends JFrame{ String deviceName; public MicServeurFrame(String deviceName) { - this.setSize(300, 500); + this.setSize(800, 300); this.setMinimumSize(new Dimension(100, 200)); this.setLocationRelativeTo(null); this.setDefaultCloseOperation(EXIT_ON_CLOSE); - this.setTitle("Serveur audio"); + this.setTitle("Serveur audio: Microphone"); this.deviceName = deviceName; this.setContentPane(genContentPan()); @@ -41,7 +44,9 @@ public class MicServeurFrame extends JFrame{ } public JPanel genContentPan() { + JPanel panel = new JPanel(new BorderLayout()); + panel.setBorder(new EmptyBorder(3, 3, 3, 3)); @@ -61,19 +66,37 @@ public class MicServeurFrame extends JFrame{ if(serveur!=null) { serveur.dispose(); serveur = null; + masterIP.setEnabled(true); serverControl.setText("Lancer"); }else { + masterIP.setEnabled(false); serveur = new MicServer(new InetSocketAddress(masterIP.getText(), AudioServer.communicationPort), deviceName,micListe.getSelectedValue().tdl); serveur.setServerAnswered(()->{ serverControl.setText("Arrêter"); }); + serveur.addDisconnectListener(()->{ + // Le dispose a été fait + serveur = null; + masterIP.setEnabled(true); + serverControl.setText("Lancer"); + }); serverControl.setText("Lancement"); } serverControl.setEnabled(true); }); - panel.add(masterPanel,BorderLayout.NORTH); - panel.add(micListe,BorderLayout.CENTER); + JScrollPane jE = new JScrollPane(micListe); + jE.setBorder(BorderFactory.createTitledBorder("Microphones disponibles")); + + + JPanel headP = new JPanel(new BorderLayout()); + + headP.add(serverControl,BorderLayout.EAST); + headP.add(masterIP,BorderLayout.CENTER); + + panel.add(headP,BorderLayout.NORTH); + panel.add(jE,BorderLayout.CENTER); + return panel; } diff --git a/src/main/java/com/bernard/murder/view/minel/ServeurMinel.java b/src/main/java/com/bernard/murder/view/minel/ServeurMinel.java index 6e11cb9..94de057 100755 --- a/src/main/java/com/bernard/murder/view/minel/ServeurMinel.java +++ b/src/main/java/com/bernard/murder/view/minel/ServeurMinel.java @@ -1,7 +1,23 @@ package com.bernard.murder.view.minel; +import java.awt.BorderLayout; +import java.awt.Font; +import java.awt.GridLayout; +import java.awt.font.TextAttribute; +import java.net.SocketException; +import java.net.UnknownHostException; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.Vector; +import java.util.stream.Collectors; + +import javax.swing.BorderFactory; import javax.swing.JLabel; +import javax.swing.JList; +import javax.swing.JOptionPane; import javax.swing.JPanel; +import javax.swing.JScrollPane; import com.amihaiemil.eoyaml.Yaml; import com.amihaiemil.eoyaml.YamlMapping; @@ -15,20 +31,67 @@ public class ServeurMinel extends Minel { public ServeurMinel(GameManager manager) { super(manager); - serveur = new AudioServer(); + try { + serveur = new AudioServer(); + }catch(SocketException | UnknownHostException ex) { + JOptionPane.showMessageDialog(null, "Lancement du serveur audio impossible !\n"+ex.getMessage(), "Impossible de lancer le serveur audio", JOptionPane.ERROR_MESSAGE, null); + } } public ServeurMinel(GameManager manager,YamlMapping ym) { super(manager); - serveur = new AudioServer(); + try { + serveur = new AudioServer(); + }catch(SocketException | UnknownHostException ex) { + JOptionPane.showMessageDialog(null, "Lancement du serveur audio impossible !\n"+ex.getMessage(), "Impossible de lancer le serveur audio", JOptionPane.ERROR_MESSAGE, null); + } } @Override public JPanel genContentPane() { - JPanel pan = new JPanel(); - JLabel label = new JLabel("Rien pour l'instant"); - pan.add(label); - return pan; + JPanel panel = new JPanel(new BorderLayout()); + + JLabel titre = new JLabel("Status du serveur",JLabel.CENTER); + Font ft = titre.getFont(); + Map ftAttrs = new HashMap<>(ft.getAttributes()); + ftAttrs.put(TextAttribute.UNDERLINE, TextAttribute.UNDERLINE_ON); + titre.setFont(ft.deriveFont(Font.BOLD).deriveFont(ftAttrs)); + + JList microList = new JList<>(); + JList enceinteListe = new JList<>(); + + serveur.addChangeListener(new Runnable() { + + @Override + public void run() { + // Updating lists content + Collection micsData = serveur.getMics().values(); + microList.setListData(new Vector(micsData)); + + Collection spksData = serveur.getSpeakers().entrySet().stream() + .map(e -> e.getValue() + ( + (serveur.listens(e.getKey())!=-1) + ?(" <- "+serveur.getMics().get(serveur.listens(e.getKey()))) + :"")) + .collect(Collectors.toSet()); + enceinteListe.setListData(new Vector(spksData)); + } + }); + + JScrollPane jE = new JScrollPane(enceinteListe); + jE.setBorder(BorderFactory.createTitledBorder("Enceintes connéctées")); + JScrollPane jM = new JScrollPane(microList); + jM.setBorder(BorderFactory.createTitledBorder("Microphones connéctés")); + + JPanel centerP = new JPanel(new GridLayout(2, 1)); + + centerP.add(jE); + centerP.add(jM); + + panel.add(titre,BorderLayout.NORTH); + panel.add(centerP,BorderLayout.CENTER); + + return panel; } @Override diff --git a/src/test/resources/uneMurder.bernard.tmurder b/src/test/resources/uneMurder.bernard.tmurder index 7beef50..7d87fd5 100644 --- a/src/test/resources/uneMurder.bernard.tmurder +++ b/src/test/resources/uneMurder.bernard.tmurder @@ -5,8 +5,7 @@ actions: status: - Mort - Paralysie - - NoKill: - onStart: true + - NoKill inventaire: # Tout le monde en a un au départ, sera nommé Portefeuille_Bernard, Portefeuille_Jach @@ -43,4 +42,4 @@ joueurs: - "PB ENS" espacePerso: - "Guide Richard" - # Par défaut, se place dans le seul espace personnel (Si il y en a deux, pas bon) \ No newline at end of file + # Par défaut, se place dans le seul espace personnel (Si il y en a deux, pas bon)