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 }