1/*
2 * Copyright (c) 2002, 2013, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.  Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26package com.sun.media.sound;
27
28import java.util.Arrays;
29
30import javax.sound.sampled.AudioFormat;
31import javax.sound.sampled.AudioInputStream;
32import javax.sound.sampled.SourceDataLine;
33
34/**
35 * Class to write an AudioInputStream to a SourceDataLine.
36 * Was previously an inner class in various classes like JavaSoundAudioClip
37 * and sun.audio.AudioDevice.
38 * It auto-opens and closes the SourceDataLine.
39 *
40 * @author Kara Kytle
41 * @author Florian Bomers
42 */
43
44public final class DataPusher implements Runnable {
45
46    private static final int AUTO_CLOSE_TIME = 5000;
47    private static final boolean DEBUG = false;
48
49    private final SourceDataLine source;
50    private final AudioFormat format;
51
52    // stream as source data
53    private final AudioInputStream ais;
54
55    // byte array as source data
56    private final byte[] audioData;
57    private final int audioDataByteLength;
58    private int pos;
59    private int newPos = -1;
60    private boolean looping;
61
62    private Thread pushThread = null;
63    private int wantedState;
64    private int threadState;
65
66    private final int STATE_NONE = 0;
67    private final int STATE_PLAYING = 1;
68    private final int STATE_WAITING = 2;
69    private final int STATE_STOPPING = 3;
70    private final int STATE_STOPPED = 4;
71    private final int BUFFER_SIZE = 16384;
72
73    public DataPusher(SourceDataLine sourceLine, AudioFormat format, byte[] audioData, int byteLength) {
74        this(sourceLine, format, null, audioData, byteLength);
75    }
76
77    public DataPusher(SourceDataLine sourceLine, AudioInputStream ais) {
78        this(sourceLine, ais.getFormat(), ais, null, 0);
79    }
80
81    private DataPusher(final SourceDataLine source, final AudioFormat format,
82                       final AudioInputStream ais, final byte[] audioData,
83                       final int audioDataByteLength) {
84        this.source = source;
85        this.format = format;
86        this.ais = ais;
87        this.audioDataByteLength = audioDataByteLength;
88        this.audioData = audioData == null ? null : Arrays.copyOf(audioData,
89                                                                  audioData.length);
90    }
91
92    public synchronized void start() {
93        start(false);
94    }
95
96    public synchronized void start(boolean loop) {
97        if (DEBUG || Printer.debug) Printer.debug("> DataPusher.start(loop="+loop+")");
98        try {
99            if (threadState == STATE_STOPPING) {
100                // wait that the thread has finished stopping
101                if (DEBUG || Printer.trace)Printer.trace("DataPusher.start(): calling stop()");
102                stop();
103            }
104            looping = loop;
105            newPos = 0;
106            wantedState = STATE_PLAYING;
107            if (!source.isOpen()) {
108                if (DEBUG || Printer.trace)Printer.trace("DataPusher: source.open()");
109                source.open(format);
110            }
111            if (DEBUG || Printer.trace)Printer.trace("DataPusher: source.flush()");
112            source.flush();
113            if (DEBUG || Printer.trace)Printer.trace("DataPusher: source.start()");
114            source.start();
115            if (pushThread == null) {
116                if (DEBUG || Printer.debug) Printer.debug("DataPusher.start(): Starting push");
117                pushThread = JSSecurityManager.createThread(this,
118                                                            null,   // name
119                                                            false,  // daemon
120                                                            -1,    // priority
121                                                            true); // doStart
122            }
123            notifyAll();
124        } catch (Exception e) {
125            if (DEBUG || Printer.err) e.printStackTrace();
126        }
127        if (DEBUG || Printer.debug) Printer.debug("< DataPusher.start(loop="+loop+")");
128    }
129
130    public synchronized void stop() {
131        if (DEBUG || Printer.debug) Printer.debug("> DataPusher.stop()");
132        if (threadState == STATE_STOPPING
133            || threadState == STATE_STOPPED
134            || pushThread == null) {
135            if (DEBUG || Printer.debug) Printer.debug("DataPusher.stop(): nothing to do");
136            return;
137        }
138        if (DEBUG || Printer.debug) Printer.debug("DataPusher.stop(): Stopping push");
139
140        wantedState = STATE_WAITING;
141        if (source != null) {
142            if (DEBUG || Printer.trace)Printer.trace("DataPusher: source.flush()");
143            source.flush();
144        }
145        notifyAll();
146        int maxWaitCount = 50; // 5 seconds
147        while ((maxWaitCount-- >= 0) && (threadState == STATE_PLAYING)) {
148            try {
149                wait(100);
150            } catch (InterruptedException e) {  }
151        }
152        if (DEBUG || Printer.debug) Printer.debug("< DataPusher.stop()");
153    }
154
155    synchronized void close() {
156        if (source != null) {
157                if (DEBUG || Printer.trace)Printer.trace("DataPusher.close(): source.close()");
158                source.close();
159        }
160    }
161
162    /**
163     * Write data to the source data line.
164     */
165    @Override
166    public void run() {
167        byte[] buffer = null;
168        boolean useStream = (ais != null);
169        if (useStream) {
170            buffer = new byte[BUFFER_SIZE];
171        } else {
172            buffer = audioData;
173        }
174        while (wantedState != STATE_STOPPING) {
175            //try {
176                if (wantedState == STATE_WAITING) {
177                    // wait for 5 seconds - maybe the clip is to be played again
178                    if (DEBUG || Printer.debug)Printer.debug("DataPusher.run(): waiting 5 seconds");
179                    try {
180                        synchronized(this) {
181                                threadState = STATE_WAITING;
182                                wantedState = STATE_STOPPING;
183                                wait(AUTO_CLOSE_TIME);
184                        }
185                    } catch (InterruptedException ie) {}
186                    if (DEBUG || Printer.debug)Printer.debug("DataPusher.run(): waiting finished");
187                    continue;
188                }
189                if (newPos >= 0) {
190                        pos = newPos;
191                        newPos = -1;
192                }
193                threadState = STATE_PLAYING;
194                int toWrite = BUFFER_SIZE;
195                if (useStream) {
196                    try {
197                        pos = 0; // always write from beginning of buffer
198                        // don't use read(byte[]), because some streams
199                        // may not override that method
200                        toWrite = ais.read(buffer, 0, buffer.length);
201                    } catch (java.io.IOException ioe) {
202                        // end of stream
203                        toWrite = -1;
204                    }
205                } else {
206                    if (toWrite > audioDataByteLength - pos) {
207                        toWrite = audioDataByteLength - pos;
208                    }
209                    if (toWrite == 0) {
210                        toWrite = -1; // end of "stream"
211                    }
212                }
213                if (toWrite < 0) {
214                    if (DEBUG || Printer.debug) Printer.debug("DataPusher.run(): Found end of stream");
215                        if (!useStream && looping) {
216                            if (DEBUG || Printer.debug)Printer.debug("DataPusher.run(): setting pos back to 0");
217                            pos = 0;
218                            continue;
219                        }
220                    if (DEBUG || Printer.debug)Printer.debug("DataPusher.run(): calling drain()");
221                    wantedState = STATE_WAITING;
222                    source.drain();
223                    continue;
224                }
225                if (DEBUG || Printer.debug) Printer.debug("> DataPusher.run(): Writing " + toWrite + " bytes");
226                    int bytesWritten = source.write(buffer, pos, toWrite);
227                    pos += bytesWritten;
228                if (DEBUG || Printer.debug) Printer.debug("< DataPusher.run(): Wrote " + bytesWritten + " bytes");
229        }
230        threadState = STATE_STOPPING;
231        if (DEBUG || Printer.debug)Printer.debug("DataPusher: closing device");
232        if (Printer.trace)Printer.trace("DataPusher: source.flush()");
233        source.flush();
234        if (DEBUG || Printer.trace)Printer.trace("DataPusher: source.stop()");
235        source.stop();
236        if (DEBUG || Printer.trace)Printer.trace("DataPusher: source.flush()");
237        source.flush();
238        if (DEBUG || Printer.trace)Printer.trace("DataPusher: source.close()");
239        source.close();
240        threadState = STATE_STOPPED;
241        synchronized (this) {
242                pushThread = null;
243                notifyAll();
244        }
245        if (DEBUG || Printer.debug)Printer.debug("DataPusher:end of thread");
246    }
247} // class DataPusher
248