blob: 8bd2d35371d10d3006e8f37a7b70a81ede9c43d9 [file] [log] [blame]
J. Duke319a3b92007-12-01 00:00:00 +00001/*
2 * Copyright 2002-2007 Sun Microsystems, Inc. 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. Sun designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
22 * CA 95054 USA or visit www.sun.com if you need additional information or
23 * have any questions.
24 */
25
26package com.sun.media.sound;
27
28import javax.sound.sampled.*;
29
30/**
31 * Class to write an AudioInputStream to a SourceDataLine.
32 * Was previously an inner class in various classes like JavaSoundAudioClip
33 * and sun.audio.AudioDevice.
34 * It auto-opens and closes the SourceDataLine.
35 *
36 * @author Kara Kytle
37 * @author Florian Bomers
38 */
39
40public class DataPusher implements Runnable {
41
42 private static final int AUTO_CLOSE_TIME = 5000;
43 private static final boolean DEBUG = false;
44
45 private SourceDataLine source = null;
46 private AudioFormat format = null;
47
48 // stream as source data
49 private AudioInputStream ais = null;
50
51 // byte array as source data
52 private byte[] audioData = null;
53 private int audioDataByteLength = 0;
54 private int pos;
55 private int newPos = -1;
56 private boolean looping;
57
58 private Thread pushThread = null;
59 private int wantedState;
60 private int threadState;
61
62 private final int STATE_NONE = 0;
63 private final int STATE_PLAYING = 1;
64 private final int STATE_WAITING = 2;
65 private final int STATE_STOPPING = 3;
66 private final int STATE_STOPPED = 4;
67 private final int BUFFER_SIZE = 16384;
68
69 public DataPusher(SourceDataLine sourceLine, AudioFormat format, byte[] audioData, int byteLength) {
70 this.audioData = audioData;
71 this.audioDataByteLength = byteLength;
72 this.format = format;
73 this.source = sourceLine;
74 }
75
76 public DataPusher(SourceDataLine sourceLine, AudioInputStream ais) {
77 this.ais = ais;
78 this.format = ais.getFormat();
79 this.source = sourceLine;
80 }
81
82 public synchronized void start() {
83 start(false);
84 }
85
86 public synchronized void start(boolean loop) {
87 if (DEBUG || Printer.debug) Printer.debug("> DataPusher.start(loop="+loop+")");
88 try {
89 if (threadState == STATE_STOPPING) {
90 // wait that the thread has finished stopping
91 if (DEBUG || Printer.trace)Printer.trace("DataPusher.start(): calling stop()");
92 stop();
93 }
94 looping = loop;
95 newPos = 0;
96 wantedState = STATE_PLAYING;
97 if (!source.isOpen()) {
98 if (DEBUG || Printer.trace)Printer.trace("DataPusher: source.open()");
99 source.open(format);
100 }
101 if (DEBUG || Printer.trace)Printer.trace("DataPusher: source.flush()");
102 source.flush();
103 if (DEBUG || Printer.trace)Printer.trace("DataPusher: source.start()");
104 source.start();
105 if (pushThread == null) {
106 if (DEBUG || Printer.debug) Printer.debug("DataPusher.start(): Starting push");
107 pushThread = JSSecurityManager.createThread(this,
108 null, // name
109 false, // daemon
110 -1, // priority
111 true); // doStart
112 }
113 notifyAll();
114 } catch (Exception e) {
115 if (DEBUG || Printer.err) e.printStackTrace();
116 }
117 if (DEBUG || Printer.debug) Printer.debug("< DataPusher.start(loop="+loop+")");
118 }
119
120
121 public synchronized void stop() {
122 if (DEBUG || Printer.debug) Printer.debug("> DataPusher.stop()");
123 if (threadState == STATE_STOPPING
124 || threadState == STATE_STOPPED
125 || pushThread == null) {
126 if (DEBUG || Printer.debug) Printer.debug("DataPusher.stop(): nothing to do");
127 return;
128 }
129 if (DEBUG || Printer.debug) Printer.debug("DataPusher.stop(): Stopping push");
130
131 wantedState = STATE_WAITING;
132 if (source != null) {
133 if (DEBUG || Printer.trace)Printer.trace("DataPusher: source.flush()");
134 source.flush();
135 }
136 notifyAll();
137 int maxWaitCount = 50; // 5 seconds
138 while ((maxWaitCount-- >= 0) && (threadState == STATE_PLAYING)) {
139 try {
140 wait(100);
141 } catch (InterruptedException e) { }
142 }
143 if (DEBUG || Printer.debug) Printer.debug("< DataPusher.stop()");
144 }
145
146 synchronized void close() {
147 if (source != null) {
148 if (DEBUG || Printer.trace)Printer.trace("DataPusher.close(): source.close()");
149 source.close();
150 }
151 }
152
153 /**
154 * Write data to the source data line.
155 */
156 public void run() {
157 byte[] buffer = null;
158 boolean useStream = (ais != null);
159 if (useStream) {
160 buffer = new byte[BUFFER_SIZE];
161 } else {
162 buffer = audioData;
163 }
164 while (wantedState != STATE_STOPPING) {
165 //try {
166 if (wantedState == STATE_WAITING) {
167 // wait for 5 seconds - maybe the clip is to be played again
168 if (DEBUG || Printer.debug)Printer.debug("DataPusher.run(): waiting 5 seconds");
169 try {
170 synchronized(this) {
171 threadState = STATE_WAITING;
172 wantedState = STATE_STOPPING;
173 wait(AUTO_CLOSE_TIME);
174 }
175 } catch (InterruptedException ie) {}
176 if (DEBUG || Printer.debug)Printer.debug("DataPusher.run(): waiting finished");
177 continue;
178 }
179 if (newPos >= 0) {
180 pos = newPos;
181 newPos = -1;
182 }
183 threadState = STATE_PLAYING;
184 int toWrite = BUFFER_SIZE;
185 if (useStream) {
186 try {
187 pos = 0; // always write from beginning of buffer
188 // don't use read(byte[]), because some streams
189 // may not override that method
190 toWrite = ais.read(buffer, 0, buffer.length);
191 } catch (java.io.IOException ioe) {
192 // end of stream
193 toWrite = -1;
194 }
195 } else {
196 if (toWrite > audioDataByteLength - pos) {
197 toWrite = audioDataByteLength - pos;
198 }
199 if (toWrite == 0) {
200 toWrite = -1; // end of "stream"
201 }
202 }
203 if (toWrite < 0) {
204 if (DEBUG || Printer.debug) Printer.debug("DataPusher.run(): Found end of stream");
205 if (!useStream && looping) {
206 if (DEBUG || Printer.debug)Printer.debug("DataPusher.run(): setting pos back to 0");
207 pos = 0;
208 continue;
209 }
210 if (DEBUG || Printer.debug)Printer.debug("DataPusher.run(): calling drain()");
211 wantedState = STATE_WAITING;
212 source.drain();
213 continue;
214 }
215 if (DEBUG || Printer.debug) Printer.debug("> DataPusher.run(): Writing " + toWrite + " bytes");
216 int bytesWritten = source.write(buffer, pos, toWrite);
217 pos += bytesWritten;
218 if (DEBUG || Printer.debug) Printer.debug("< DataPusher.run(): Wrote " + bytesWritten + " bytes");
219 }
220 threadState = STATE_STOPPING;
221 if (DEBUG || Printer.debug)Printer.debug("DataPusher: closing device");
222 if (Printer.trace)Printer.trace("DataPusher: source.flush()");
223 source.flush();
224 if (DEBUG || Printer.trace)Printer.trace("DataPusher: source.stop()");
225 source.stop();
226 if (DEBUG || Printer.trace)Printer.trace("DataPusher: source.flush()");
227 source.flush();
228 if (DEBUG || Printer.trace)Printer.trace("DataPusher: source.close()");
229 source.close();
230 threadState = STATE_STOPPED;
231 synchronized (this) {
232 pushThread = null;
233 notifyAll();
234 }
235 if (DEBUG || Printer.debug)Printer.debug("DataPusher:end of thread");
236 }
237
238} // class DataPusher