001 /* 002 Copyright (C) 2003 Adam Olsen 003 004 This program is free software; you can redistribute it and/or modify 005 it under the terms of the GNU General Public License as published by 006 the Free Software Foundation; either version 1, or (at your option) 007 any later version. 008 009 This program is distributed in the hope that it will be useful, 010 but WITHOUT ANY WARRANTY; without even the implied warranty of 011 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 012 GNU General Public License for more details. 013 014 You should have received a copy of the GNU General Public License 015 along with this program; if not, write to the Free Software 016 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 017 */ 018 019 package com.valhalla.jbother.sound; 020 021 import java.awt.Toolkit; 022 import java.io.*; 023 024 import javax.sound.sampled.*; 025 import com.valhalla.jbother.*; 026 027 import com.valhalla.settings.Settings; 028 029 /** 030 * Plays sounds with different methods. 031 * Available methods are: with Java Sound System, a system command, or a pc speaker beep 032 * 033 * @author Adam Olsen 034 * @version 1.0 035 */ 036 public class SoundPlayer 037 { 038 private Thread thread; 039 private static SoundPlayer instance; 040 private boolean running = false; 041 042 /** 043 * Constructor is private, because this is a Singleton 044 */ 045 private SoundPlayer() { } 046 047 /** 048 * Plays a sound using the method in the settings 049 * @param settingName the setting name to play 050 */ 051 public static void play( String settingName ) 052 { 053 if( !Settings.getInstance().getBoolean( settingName + "Play" ) ) return; 054 String method = Settings.getInstance().getProperty( "soundMethod" ); 055 if( method == null ) method = ""; 056 057 if( method.equals( "Console Beep" ) ) 058 { 059 Toolkit.getDefaultToolkit().beep(); 060 return; 061 } 062 063 String strFilename = Settings.getInstance().getProperty( settingName ); 064 065 if( strFilename == null || strFilename.equals( "" ) ) 066 { 067 com.valhalla.Logger.debug( "no file to play" ); 068 return; 069 } 070 071 if( strFilename.equals( "(default)" ) ) 072 { 073 strFilename = loadDefault( settingName ); 074 if( strFilename == null ) return; 075 } 076 077 if( method.equals( "Command" ) ) 078 { 079 try { 080 Runtime.getRuntime().exec( Settings.getInstance().getProperty( "soundCommand" ) + " " + strFilename ); 081 } 082 catch( java.io.IOException e ) { } 083 084 return; 085 } 086 087 if( instance == null ) instance = new SoundPlayer(); 088 if( instance.running ) return; 089 090 instance.running = true; 091 instance.thread = new Thread( new SoundPlayerThread( instance, strFilename ) ); 092 try { 093 instance.thread.start(); 094 } 095 catch( Exception ex ) { instance.running = false; } 096 } 097 098 /** 099 * Set the "running" state to false 100 */ 101 protected void nullIt() 102 { 103 running = false; 104 } 105 106 /** 107 * Loads a default sound from the running jar file and puts it into a cache 108 * @param settingName the setting to load 109 */ 110 private static String loadDefault( String settingName ) 111 { 112 try { 113 File cacheDir = new File( Settings.getInstance().getSettingsDir().getPath() + File.separatorChar + "soundcache" ); 114 if( !cacheDir.isDirectory() && !cacheDir.mkdirs() ) 115 { 116 com.valhalla.Logger.debug( "Could not create sound cache directory." ); 117 return null; 118 } 119 120 File outPutFile = new File( cacheDir.getPath() + File.separatorChar + settingName + ".wav" ); 121 if( outPutFile.exists() ) return outPutFile.getPath(); 122 123 InputStream file = 124 BuddyList.getInstance().getClass().getClassLoader().getResourceAsStream( "sounds/default/" + settingName + ".wav" ); 125 if( file == null ) 126 { 127 com.valhalla.Logger.debug( "Could not find default sound file in resources for " + settingName ); 128 return null; 129 } 130 131 FileOutputStream out = new FileOutputStream( outPutFile ); 132 133 byte data[] = new byte[1024]; 134 while( file.available() > 0 ) 135 { 136 int size = file.read( data ); 137 out.write( data, 0, size ); 138 } 139 140 file.close(); 141 out.close(); 142 143 return outPutFile.getPath(); 144 } 145 catch( IOException ex ) 146 { 147 com.valhalla.Logger.debug( ex.getMessage() ); 148 } 149 150 return null; 151 } 152 } 153 154 /** 155 * Plays a sound using the Java Sound System 156 * @author jresources.org 157 * @version ? 158 */ 159 class SoundPlayerThread implements Runnable 160 { 161 private static final int EXTERNAL_BUFFER_SIZE = 128000; 162 163 private String strFilename; 164 private SoundPlayer player; 165 166 /** 167 * Sets up the thread with a specified sound 168 * @param player the calling player 169 * @param file the .wav file to play 170 */ 171 public SoundPlayerThread( SoundPlayer player, String file ) 172 { 173 this.player = player; 174 this.strFilename = file; 175 } 176 177 public void run() 178 { 179 File soundFile = new File(strFilename); 180 181 182 //this code taken from jseresources.org. Thanks! 183 184 /* 185 We have to read in the sound file. 186 */ 187 AudioInputStream audioInputStream = null; 188 try 189 { 190 audioInputStream = AudioSystem.getAudioInputStream(soundFile); 191 } 192 catch (Exception e) 193 { 194 /* 195 In case of an exception, we dump the exception 196 including the stack trace to the console output. 197 Then, we exit the program. 198 */ 199 com.valhalla.Logger.logException( e ); 200 } 201 202 /* 203 From the AudioInputStream, i.e. from the sound file, 204 we fetch information about the format of the 205 audio data. 206 These information include the sampling frequency, 207 the number of 208 channels and the size of the samples. 209 These information 210 are needed to ask Java Sound for a suitable output line 211 for this audio file. 212 */ 213 AudioFormat audioFormat = audioInputStream.getFormat(); 214 215 /* 216 Asking for a line is a rather tricky thing. 217 We have to construct an Info object that specifies 218 the desired properties for the line. 219 First, we have to say which kind of line we want. The 220 possibilities are: SourceDataLine (for playback), Clip 221 (for repeated playback) and TargetDataLine (for 222 recording). 223 Here, we want to do normal playback, so we ask for 224 a SourceDataLine. 225 Then, we have to pass an AudioFormat object, so that 226 the Line knows which format the data passed to it 227 will have. 228 Furthermore, we can give Java Sound a hint about how 229 big the internal buffer for the line should be. This 230 isn't used here, signaling that we 231 don't care about the exact size. Java Sound will use 232 some default value for the buffer size. 233 */ 234 SourceDataLine line = null; 235 DataLine.Info info = new DataLine.Info(SourceDataLine.class, 236 audioFormat); 237 try 238 { 239 line = (SourceDataLine) AudioSystem.getLine(info); 240 241 /* 242 The line is there, but it is not yet ready to 243 receive audio data. We have to open the line. 244 */ 245 line.open(audioFormat); 246 } 247 catch (LineUnavailableException e) 248 { 249 com.valhalla.Logger.logException( e ); 250 } 251 catch (Exception e) 252 { 253 com.valhalla.Logger.logException( e ); 254 } 255 256 if( line == null ) 257 { 258 player.nullIt(); 259 260 return; 261 } 262 263 /* 264 Still not enough. The line now can receive data, 265 but will not pass them on to the audio output device 266 (which means to your sound card). This has to be 267 activated. 268 */ 269 line.start(); 270 271 /* 272 Ok, finally the line is prepared. Now comes the real 273 job: we have to write data to the line. We do this 274 in a loop. First, we read data from the 275 AudioInputStream to a buffer. Then, we write from 276 this buffer to the Line. This is done until the end 277 of the file is reached, which is detected by a 278 return value of -1 from the read method of the 279 AudioInputStream. 280 */ 281 int nBytesRead = 0; 282 byte[] abData = new byte[EXTERNAL_BUFFER_SIZE]; 283 while (nBytesRead != -1) 284 { 285 try 286 { 287 nBytesRead = audioInputStream.read(abData, 0, abData.length); 288 } 289 catch (IOException e) 290 { 291 com.valhalla.Logger.logException( e ); 292 } 293 if (nBytesRead >= 0) 294 { 295 int nBytesWritten = line.write(abData, 0, nBytesRead); 296 } 297 } 298 299 /* 300 Wait until all data are played. 301 This is only necessary because of the bug noted below. 302 (If we do not wait, we would interrupt the playback by 303 prematurely closing the line and exiting the VM.) 304 305 Thanks to Margie Fitch for bringing me on the right 306 path to this solution. 307 */ 308 line.drain(); 309 310 /* 311 All data are played. We can close the shop. 312 */ 313 line.close(); 314 315 player.nullIt(); 316 317 try { 318 Thread.sleep( 200 ); 319 } 320 catch( InterruptedException e ) { } 321 322 return; 323 } 324 }