Expose metadata from every controlled component.
[sonitus.git] / src / main / java / net / pterodactylus / sonitus / data / sink / AudioSink.java
index 689c23d..05253f3 100644 (file)
 
 package net.pterodactylus.sonitus.data.sink;
 
+import static javax.sound.sampled.FloatControl.Type.VOLUME;
+
 import java.io.IOException;
+import java.io.OutputStream;
+import java.util.Arrays;
+import java.util.List;
 import java.util.logging.Logger;
 import javax.sound.sampled.AudioFormat;
 import javax.sound.sampled.AudioSystem;
+import javax.sound.sampled.FloatControl;
 import javax.sound.sampled.LineUnavailableException;
 import javax.sound.sampled.SourceDataLine;
 
+import net.pterodactylus.sonitus.data.Controller;
 import net.pterodactylus.sonitus.data.Metadata;
 import net.pterodactylus.sonitus.data.Sink;
+import net.pterodactylus.sonitus.data.Source;
+import net.pterodactylus.sonitus.data.controller.Fader;
+import net.pterodactylus.sonitus.data.controller.Switch;
+import net.pterodactylus.sonitus.io.IntegralWriteOutputStream;
 
 import com.google.common.base.Preconditions;
 
 /**
- * {@link net.pterodactylus.sonitus.data.Sink} implementation that uses the
- * JDK’s {@link javax.sound.sampled.AudioSystem} to play all {@link
- * net.pterodactylus.sonitus.data.Source}s.
+ * {@link Sink} implementation that uses the JDK’s {@link AudioSystem} to play
+ * all {@link Source}s.
  *
  * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
@@ -41,12 +51,92 @@ public class AudioSink implements Sink {
        /** The logger. */
        private static final Logger logger = Logger.getLogger(AudioSink.class.getName());
 
+       /** The volume fader. */
+       private final Fader volumeFader;
+
+       /** The “mute” switch. */
+       private final Switch muteSwitch;
+
        /** The current metadata. */
        private Metadata metadata;
 
        /** The audio output. */
        private SourceDataLine sourceDataLine;
 
+       /** A buffered output stream to ensure correct writing to the source data line. */
+       private OutputStream sourceDataLineOutputStream = new IntegralWriteOutputStream(new OutputStream() {
+
+               @Override
+               public void write(int b) throws IOException {
+               }
+
+               @Override
+               public void write(byte[] b) throws IOException {
+                       write(b, 0, b.length);
+               }
+
+               @Override
+               public void write(byte[] b, int off, int len) throws IOException {
+                       if (sourceDataLine != null) {
+                               sourceDataLine.write(b, off, len);
+                       }
+               }
+       }, 1024);
+
+       /** Creates a new audio sink. */
+       public AudioSink() {
+               volumeFader = new Fader("Volume") {
+
+                       @Override
+                       protected void valueSet(Double value) {
+                               if (sourceDataLine != null) {
+                                       FloatControl volumeControl = (FloatControl) sourceDataLine.getControl(VOLUME);
+                                       volumeControl.setValue((float) (value * volumeControl.getMaximum()));
+                               }
+                       }
+               };
+               muteSwitch = new Switch("Mute") {
+
+                       private float previousValue;
+
+                       @Override
+                       protected void valueSet(Boolean value) {
+                               if (sourceDataLine != null) {
+                                       FloatControl volumeControl = (FloatControl) sourceDataLine.getControl(VOLUME);
+                                       if (value) {
+                                               previousValue = volumeControl.getValue();
+                                               volumeControl.setValue(0);
+                                       } else {
+                                               volumeControl.setValue(previousValue);
+                                       }
+                               }
+                       }
+               };
+       }
+
+       //
+       // CONTROLLED METHODS
+       //
+
+       @Override
+       public String name() {
+               return "Audio Output";
+       }
+
+       @Override
+       public Metadata metadata() {
+               return metadata;
+       }
+
+       @Override
+       public List<Controller<?>> controllers() {
+               return Arrays.<Controller<?>>asList(volumeFader, muteSwitch);
+       }
+
+       //
+       // SINK METHODS
+       //
+
        @Override
        public void open(Metadata metadata) throws IOException {
                Preconditions.checkArgument(metadata.encoding().equalsIgnoreCase("PCM"), "source must be PCM-encoded");
@@ -69,12 +159,13 @@ public class AudioSink implements Sink {
 
        @Override
        public void metadataUpdated(Metadata metadata) {
-               /* ignore. */
+               logger.info(String.format("Now playing %s.", metadata));
+               this.metadata = metadata;
        }
 
        @Override
-       public void process(byte[] buffer) {
-               sourceDataLine.write(buffer, 0, buffer.length);
+       public void process(byte[] buffer) throws IOException {
+               sourceDataLineOutputStream.write(buffer);
                logger.finest(String.format("AudioSink: Wrote %d Bytes.", buffer.length));
        }