1 /* 2 * Copyright (c) 2010, 2015, 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 26 package javafx.scene.media; 27 28 import java.net.URI; 29 import java.io.IOException; 30 import java.io.FileNotFoundException; 31 import java.net.URISyntaxException; 32 33 import javafx.beans.NamedArg; 34 import javafx.beans.property.DoubleProperty; 35 import javafx.beans.property.DoublePropertyBase; 36 import javafx.beans.property.IntegerProperty; 37 import javafx.beans.property.IntegerPropertyBase; 38 39 /** 40 * An <code>AudioClip</code> represents a segment of audio that can be played 41 * with minimal latency. Clips are loaded similarly to <code>Media</code> 42 * objects but have different behavior, for example, a <code>Media</code> cannot 43 * play itself. <code>AudioClip</code>s are also usable immediately. Playback 44 * behavior is fire and forget: once one of the play methods is called the only 45 * operable control is {@link #stop()}. An <code>AudioClip</code> may also be 46 * played multiple times simultaneously. To accomplish the same task using 47 * <code>Media</code> one would have to create a new <code>MediaPlayer</code> 48 * object for each sound played in parallel. <code>Media</code> objects are 49 * however better suited for long-playing sounds. This is primarily because 50 * <code>AudioClip</code> stores in memory the raw, uncompressed audio data for 51 * the entire sound, which can be quite large for long audio clips. A 52 * <code>MediaPlayer</code> will only have enough decompressed audio data 53 * pre-rolled in memory to play for a short amount of time so it is much more 54 * memory efficient for long clips, especially if they are compressed. 55 * <br> 56 * <p>Example usage: 57 * <pre><code> 58 * AudioClip plonkSound = new AudioClip("http://somehost/path/plonk.aiff"); 59 * plonkSound.play(); 60 * </code></pre> 61 * </p> 62 * @since JavaFX 2.0 63 */ 64 65 public final class AudioClip { 66 private String sourceURL; 67 private com.sun.media.jfxmedia.AudioClip audioClip; 68 69 /** 70 * Create an <code>AudioClip</code> loaded from the supplied source URL. 71 * 72 * @param source URL string from which to load the audio clip. This can be an 73 * HTTP, HTTPS, FILE or JAR source. 74 * @throws NullPointerException if the parameter is <code>null</code>. 75 * @throws IllegalArgumentException if the parameter violates 76 * <a href="http://www.ietf.org/rfc/rfc2396.txt">RFC 2396</a>. 77 * @throws MediaException if there is some other problem loading the media. 78 */ 79 public AudioClip(@NamedArg("source") String source) { 80 URI srcURI = URI.create(source); 81 sourceURL = source; 82 try { 83 audioClip = com.sun.media.jfxmedia.AudioClip.load(srcURI); 84 } catch(URISyntaxException use) { 85 throw new IllegalArgumentException(use); 86 } catch(FileNotFoundException fnfe) { 87 throw new MediaException(MediaException.Type.MEDIA_UNAVAILABLE, fnfe.getMessage()); 88 } catch(IOException ioe) { 89 throw new MediaException(MediaException.Type.MEDIA_INACCESSIBLE, ioe.getMessage()); 90 } catch(com.sun.media.jfxmedia.MediaException me) { 91 throw new MediaException(MediaException.Type.MEDIA_UNSUPPORTED, me.getMessage()); 92 } 93 } 94 95 /** 96 * Get the source URL used to create this <code>AudioClip</code>. 97 * @return source URL as provided to the constructor 98 */ 99 public String getSource() { 100 return sourceURL; 101 } 102 103 /** 104 * The relative volume level at which the clip is played. Valid range is 0.0 105 * (muted) to 1.0 (full volume). Values are clamped to this range internally 106 * so values outside this range will have no additional effect. Volume is 107 * controlled by attenuation, so values below 1.0 will reduce the sound 108 * level accordingly. 109 */ 110 private DoubleProperty volume; 111 112 /** 113 * Set the default volume level. The new setting will only take effect on 114 * subsequent plays. 115 * @see #volume 116 * @param value new default volume level for this clip 117 */ 118 public final void setVolume(double value) { 119 volumeProperty().set(value); 120 } 121 122 /** 123 * Get the default volume level. 124 * @see #volume 125 * @return the default volume level for this clip 126 */ 127 public final double getVolume() { 128 return (null == volume) ? 1.0 : volume.get(); 129 } 130 public DoubleProperty volumeProperty() { 131 if (volume == null) { 132 volume = new DoublePropertyBase(1.0) { 133 @Override 134 protected void invalidated() { 135 if (null != audioClip) { 136 audioClip.setVolume(volume.get()); 137 } 138 } 139 140 @Override 141 public Object getBean() { 142 return AudioClip.this; 143 } 144 145 @Override 146 public String getName() { 147 return "volume"; 148 } 149 }; 150 } 151 return volume; 152 } 153 154 /** 155 * The relative left and right volume levels of the clip. 156 * Valid range is -1.0 to 1.0 where -1.0 gives full volume to the left 157 * channel while muting the right channel, 0.0 gives full volume to both 158 * channels and 1.0 gives full volume to right channel and mutes the left 159 * channel. Values outside this range are clamped internally. 160 */ 161 private DoubleProperty balance; 162 163 /** 164 * Set the default balance level. The new value will only affect subsequent 165 * plays. 166 * @see #balance 167 * @param balance new default balance 168 */ 169 public void setBalance(double balance) { 170 balanceProperty().set(balance); 171 } 172 173 /** 174 * Get the default balance level for this clip. 175 * @see #balance 176 * @return the default balance for this clip 177 */ 178 public double getBalance() { 179 return (null != balance) ? balance.get() : 0.0; 180 } 181 public DoubleProperty balanceProperty() { 182 if (null == balance) { 183 balance = new DoublePropertyBase(0.0) { 184 @Override 185 protected void invalidated() { 186 if (null != audioClip) { 187 audioClip.setBalance(balance.get()); 188 } 189 } 190 191 @Override 192 public Object getBean() { 193 return AudioClip.this; 194 } 195 196 @Override 197 public String getName() { 198 return "balance"; 199 } 200 }; 201 } 202 return balance; 203 } 204 205 /** 206 * The relative rate at which the clip is played. Valid range is 0.125 207 * (1/8 speed) to 8.0 (8x speed); values outside this range are clamped 208 * internally. Normal playback for a clip is 1.0; any other rate will affect 209 * pitch and duration accordingly. 210 */ 211 private DoubleProperty rate; 212 213 /** 214 * Set the default playback rate. The new value will only affect subsequent 215 * plays. 216 * @see #rate 217 * @param rate the new default playback rate 218 */ 219 public void setRate(double rate) { 220 rateProperty().set(rate); 221 } 222 223 /** 224 * Get the default playback rate. 225 * @see #rate 226 * @return default playback rate for this clip 227 */ 228 public double getRate() { 229 return (null != rate) ? rate.get() : 1.0; 230 } 231 public DoubleProperty rateProperty() { 232 if (null == rate) { 233 rate = new DoublePropertyBase(1.0) { 234 @Override 235 protected void invalidated() { 236 if (null != audioClip) { 237 audioClip.setPlaybackRate(rate.get()); 238 } 239 } 240 241 @Override 242 public Object getBean() { 243 return AudioClip.this; 244 } 245 246 @Override 247 public String getName() { 248 return "rate"; 249 } 250 }; 251 } 252 return rate; 253 } 254 255 /** 256 * The relative "center" of the clip. A pan value of 0.0 plays 257 * the clip normally where a -1.0 pan shifts the clip entirely to the left 258 * channel and 1.0 shifts entirely to the right channel. Unlike balance this 259 * setting mixes both channels so neither channel loses data. Setting 260 * pan on a mono clip has the same effect as setting balance, but with a 261 * much higher cost in CPU overhead so this is not recommended for mono 262 * clips. 263 */ 264 private DoubleProperty pan; 265 266 /** 267 * Set the default pan value. The new value will only affect subsequent 268 * plays. 269 * @see #pan 270 * @param pan the new default pan value 271 */ 272 public void setPan(double pan) { 273 panProperty().set(pan); 274 } 275 276 /** 277 * Get the default pan value. 278 * @see #pan 279 * @return the default pan value for this clip 280 */ 281 public double getPan() { 282 return (null != pan) ? pan.get() : 0.0; 283 } 284 public DoubleProperty panProperty() { 285 if (null == pan) { 286 pan = new DoublePropertyBase(0.0) { 287 @Override 288 protected void invalidated() { 289 if (null != audioClip) { 290 audioClip.setPan(pan.get()); 291 } 292 } 293 294 @Override 295 public Object getBean() { 296 return AudioClip.this; 297 } 298 299 @Override 300 public String getName() { 301 return "pan"; 302 } 303 }; 304 } 305 return pan; 306 } 307 308 /** 309 * The relative priority of the clip with respect to other clips. This value 310 * is used to determine which clips to remove when the maximum allowed number 311 * of clips is exceeded. The lower the priority, the more likely the 312 * clip is to be stopped and removed from the mixer channel it is occupying. 313 * Valid range is any integer; there are no constraints. The default priority 314 * is zero for all clips until changed. The number of simultaneous sounds 315 * that can be played is implementation- and possibly system-dependent. 316 */ 317 private IntegerProperty priority; 318 319 /** 320 * Set the default playback priority. The new value will only affect 321 * subsequent plays. 322 * @see #priority 323 * @param priority the new default playback priority 324 */ 325 public void setPriority(int priority) { 326 priorityProperty().set(priority); 327 } 328 329 /** 330 * Get the default playback priority. 331 * @see #priority 332 * @return the default playback priority of this clip 333 */ 334 public int getPriority() { 335 return (null != priority) ? priority.get() : 0; 336 } 337 public IntegerProperty priorityProperty() { 338 if (null == priority) { 339 priority = new IntegerPropertyBase(0) { 340 @Override 341 protected void invalidated() { 342 if (null != audioClip) { 343 audioClip.setPriority(priority.get()); 344 } 345 } 346 347 @Override 348 public Object getBean() { 349 return AudioClip.this; 350 } 351 352 @Override 353 public String getName() { 354 return "priority"; 355 } 356 }; 357 } 358 return priority; 359 } 360 361 /** 362 * When {@link #cycleCountProperty cycleCount} is set to this value, the 363 * <code>AudioClip</code> will loop continuously until stopped. This value is 364 * synonymous with {@link MediaPlayer#INDEFINITE} and 365 * {@link javafx.animation.Animation#INDEFINITE}, these values may be used 366 * interchangeably. 367 */ 368 public static final int INDEFINITE = -1; 369 370 /** 371 * The number of times the clip will be played when {@link #play()} 372 * is called. A cycleCount of 1 plays exactly once, a cycleCount of 2 373 * plays twice and so on. Valid range is 1 or more, but setting this to 374 * {@link #INDEFINITE INDEFINITE} will cause the clip to continue looping 375 * until {@link #stop} is called. 376 */ 377 private IntegerProperty cycleCount; 378 379 /** 380 * Set the default cycle count. The new value will only affect subsequent 381 * plays. 382 * @see #cycleCount 383 * @param count the new default cycle count for this clip 384 */ 385 public void setCycleCount(int count) { 386 cycleCountProperty().set(count); 387 } 388 389 /** 390 * Get the default cycle count. 391 * @see #cycleCount 392 * @return the default cycleCount for this audio clip 393 */ 394 public int getCycleCount() { 395 return (null != cycleCount) ? cycleCount.get() : 1; 396 } 397 public IntegerProperty cycleCountProperty() { 398 if (null == cycleCount) { 399 cycleCount = new IntegerPropertyBase(1) { 400 @Override 401 protected void invalidated() { 402 if (null != audioClip) { 403 int value = cycleCount.get(); 404 if (INDEFINITE != value) { 405 value = Math.max(1, value); 406 audioClip.setLoopCount(value - 1); 407 } else { 408 audioClip.setLoopCount(value); // INDEFINITE is the same there 409 } 410 } 411 } 412 413 @Override 414 public Object getBean() { 415 return AudioClip.this; 416 } 417 418 @Override 419 public String getName() { 420 return "cycleCount"; 421 } 422 }; 423 } 424 return cycleCount; 425 } 426 /** 427 * Play the <code>AudioClip</code> using all the default parameters. 428 */ 429 public void play() { 430 if (null != audioClip) { 431 audioClip.play(); 432 } 433 } 434 435 /** 436 * Play the <code>AudioClip</code> using all the default parameters except volume. 437 * This method does not modify the clip's default parameters. 438 * @param volume the volume level at which to play the clip 439 */ 440 public void play(double volume) { 441 if (null != audioClip) { 442 audioClip.play(volume); 443 } 444 } 445 446 /** 447 * Play the <code>AudioClip</code> using the given parameters. Values outside 448 * the ranges as specified by their associated properties are clamped. 449 * This method does not modify the clip's default parameters. 450 * 451 * @param volume Volume level at which to play this clip. Valid volume range is 452 * 0.0 to 1.0, where 0.0 is effectively muted and 1.0 is full volume. 453 * @param balance Left/right balance or relative channel volumes for stereo 454 * effects. 455 * @param rate Playback rate multiplier. 1.0 will play at the normal 456 * rate while 2.0 will double the rate. 457 * @param pan Left/right shift to be applied to the clip. A pan value of 458 * -1.0 means full left channel, 1.0 means full right channel, 0.0 has no 459 * effect. 460 * @param priority Audio effect priority. Lower priority effects will be 461 * dropped first if too many effects are trying to play simultaneously. 462 */ 463 public void play(double volume, double balance, double rate, double pan, int priority) { 464 if (null != audioClip) { 465 audioClip.play(volume, balance, rate, pan, audioClip.loopCount(), priority); 466 } 467 } 468 469 /** 470 * Indicate whether this <code>AudioClip</code> is playing. If this returns true 471 * then <code>play()</code> has been called at least once and it is still playing. 472 * @return true if any mixer channel has this clip queued, false otherwise 473 */ 474 public boolean isPlaying() { 475 return null != audioClip && audioClip.isPlaying(); 476 } 477 478 /** 479 * Immediately stop all playback of this <code>AudioClip</code>. 480 */ 481 public void stop() { 482 if (null != audioClip) { 483 audioClip.stop(); 484 } 485 } 486 }