33import com .spotify .metadata .proto .Metadata ;
44import javazoom .jl .decoder .BitstreamException ;
55import org .apache .log4j .Logger ;
6- import org .jetbrains .annotations .Contract ;
76import org .jetbrains .annotations .NotNull ;
87import org .jetbrains .annotations .Nullable ;
98import xyz .gianlu .librespot .common .NameThreadFactory ;
@@ -65,10 +64,7 @@ public class PlayerRunner implements Runnable, Closeable {
6564 switch (conf .output ()) {
6665 case MIXER :
6766 try {
68- SourceDataLine line = LineHelper .getLineFor (conf , Output .OUTPUT_FORMAT );
69- line .open (Output .OUTPUT_FORMAT );
70-
71- output = new Output (mixing , line , null , null );
67+ output = new Output (Output .Type .MIXER , mixing , conf , null , null );
7268 } catch (LineUnavailableException ex ) {
7369 throw new IllegalStateException ("Failed opening line!" , ex );
7470 }
@@ -78,10 +74,18 @@ public class PlayerRunner implements Runnable, Closeable {
7874 if (pipe == null || !pipe .exists () || !pipe .canWrite ())
7975 throw new IllegalArgumentException ("Invalid pipe file: " + pipe );
8076
81- output = new Output (mixing , null , pipe , null );
77+ try {
78+ output = new Output (Output .Type .PIPE , mixing , conf , pipe , null );
79+ } catch (LineUnavailableException ignored ) {
80+ throw new IllegalStateException (); // Cannot be thrown
81+ }
8282 break ;
8383 case STDOUT :
84- output = new Output (mixing , null , null , System .out );
84+ try {
85+ output = new Output (Output .Type .STREAM , mixing , conf , null , System .out );
86+ } catch (LineUnavailableException ignored ) {
87+ throw new IllegalStateException (); // Cannot be thrown
88+ }
8589 break ;
8690 default :
8791 throw new IllegalArgumentException ("Unknown output: " + conf .output ());
@@ -93,6 +97,40 @@ public class PlayerRunner implements Runnable, Closeable {
9397 new Thread (looper = new Looper (), "player-runner-looper-" + looper .hashCode ()).start ();
9498 }
9599
100+ /**
101+ * Pauses the mixer and then releases the {@link javax.sound.sampled.Line} if acquired.
102+ *
103+ * @return Whether the line was released.
104+ */
105+ public boolean pauseAndRelease () {
106+ pauseMixer ();
107+ while (!paused ) {
108+ synchronized (pauseLock ) {
109+ try {
110+ pauseLock .wait (100 );
111+ } catch (InterruptedException ignored ) {
112+ }
113+ }
114+ }
115+
116+ return output .releaseLine ();
117+ }
118+
119+ /**
120+ * Stops the mixer and then releases the {@link javax.sound.sampled.Line} if acquired.
121+ */
122+ public boolean stopAndRelease () {
123+ stopMixer ();
124+ synchronized (pauseLock ) {
125+ try {
126+ pauseLock .wait ();
127+ } catch (InterruptedException ignored ) {
128+ }
129+ }
130+
131+ return output .releaseLine ();
132+ }
133+
96134 private void sendCommand (@ NotNull Command command , int id , Object ... args ) {
97135 commands .add (new CommandBundle (command , id , args ));
98136 }
@@ -128,7 +166,7 @@ public void run() {
128166 int r = count % mixing .getFrameSize ();
129167 if (r != 0 ) count += mixing .read (buffer , count , mixing .getFrameSize () - r );
130168 output .write (buffer , 0 , count );
131- } catch (IOException ex ) {
169+ } catch (IOException | LineUnavailableException ex ) {
132170 if (closed ) break ;
133171
134172 paused = true ;
@@ -222,25 +260,46 @@ public interface Listener {
222260
223261 private static class Output implements Closeable {
224262 private static final AudioFormat OUTPUT_FORMAT = new AudioFormat (44100 , 16 , 2 , true , false );
225- private final SourceDataLine line ;
226263 private final File pipe ;
227264 private final MixingLine mixing ;
265+ private final Player .Configuration conf ;
266+ private final Type type ;
267+ private SourceDataLine line ;
228268 private OutputStream out ;
229269
230- @ Contract ("_, null, null, null -> fail" )
231- Output (@ NotNull MixingLine mixing , @ Nullable SourceDataLine line , @ Nullable File pipe , @ Nullable OutputStream out ) {
232- if (line == null && pipe == null && out == null ) throw new IllegalArgumentException ();
233-
270+ Output (@ NotNull Type type , @ NotNull MixingLine mixing , @ NotNull Player .Configuration conf , @ Nullable File pipe , @ Nullable OutputStream out ) throws LineUnavailableException {
271+ this .conf = conf ;
234272 this .mixing = mixing ;
235- this .line = line ;
273+ this .type = type ;
236274 this .pipe = pipe ;
237275 this .out = out ;
276+
277+ switch (type ) {
278+ case MIXER :
279+ acquireLine ();
280+ break ;
281+ case PIPE :
282+ if (pipe == null ) throw new IllegalArgumentException ();
283+ break ;
284+ case STREAM :
285+ if (out == null ) throw new IllegalArgumentException ();
286+ break ;
287+ default :
288+ throw new IllegalArgumentException (String .valueOf (type ));
289+ }
238290 }
239291
240292 private static float calcLogarithmic (int val ) {
241293 return (float ) (Math .log10 ((double ) val / VOLUME_MAX ) * 20f );
242294 }
243295
296+ private void acquireLine () throws LineUnavailableException {
297+ if (line != null ) return ;
298+
299+ line = LineHelper .getLineFor (conf , Output .OUTPUT_FORMAT );
300+ line .open (Output .OUTPUT_FORMAT );
301+ }
302+
244303 void stop () {
245304 if (line != null ) line .stop ();
246305 }
@@ -249,13 +308,12 @@ void start() {
249308 if (line != null ) line .start ();
250309 }
251310
252- void write (byte [] buffer , int off , int len ) throws IOException {
253- if (line != null ) {
311+ void write (byte [] buffer , int off , int len ) throws IOException , LineUnavailableException {
312+ if (type == Type .MIXER ) {
313+ acquireLine ();
254314 line .write (buffer , off , len );
255- } else {
315+ } else if ( type == Type . PIPE ) {
256316 if (out == null ) {
257- if (pipe == null ) throw new IllegalStateException ();
258-
259317 if (!pipe .exists ()) {
260318 try {
261319 Process p = new ProcessBuilder ().command ("mkfifo " + pipe .getAbsolutePath ())
@@ -274,6 +332,10 @@ void write(byte[] buffer, int off, int len) throws IOException {
274332 }
275333
276334 out .write (buffer , off , len );
335+ } else if (type == Type .STREAM ) {
336+ out .write (buffer , off , len );
337+ } else {
338+ throw new IllegalStateException ();
277339 }
278340 }
279341
@@ -306,6 +368,18 @@ void setVolume(int volume) {
306368 // Cannot set volume through line
307369 mixing .setGlobalGain (((float ) volume ) / VOLUME_MAX );
308370 }
371+
372+ boolean releaseLine () {
373+ if (line == null ) return false ;
374+
375+ line .close ();
376+ line = null ;
377+ return true ;
378+ }
379+
380+ enum Type {
381+ MIXER , PIPE , STREAM
382+ }
309383 }
310384
311385 private static class CommandBundle {
@@ -404,6 +478,10 @@ public void run() {
404478 firstHandler = null ;
405479 secondHandler = null ;
406480 loadedTracks .clear ();
481+
482+ synchronized (pauseLock ) {
483+ pauseLock .notifyAll ();
484+ }
407485 break ;
408486 case TerminateMixer :
409487 return ;
0 commit comments