/*
* Copyright (c) 2010, 2018, Oracle and/or its affiliates. All rights reserved.
* ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*/
package javafx.scene.media;
import java.net.
URI;
import java.io.
IOException;
import java.io.
FileNotFoundException;
import java.net.
URISyntaxException;
import javafx.beans.
NamedArg;
import javafx.beans.property.
DoubleProperty;
import javafx.beans.property.
DoublePropertyBase;
import javafx.beans.property.
IntegerProperty;
import javafx.beans.property.
IntegerPropertyBase;
/**
* An <code>AudioClip</code> represents a segment of audio that can be played
* with minimal latency. Clips are loaded similarly to <code>Media</code>
* objects but have different behavior, for example, a <code>Media</code> cannot
* play itself. <code>AudioClip</code>s are also usable immediately. Playback
* behavior is fire and forget: once one of the play methods is called the only
* operable control is {@link #stop()}. An <code>AudioClip</code> may also be
* played multiple times simultaneously. To accomplish the same task using
* <code>Media</code> one would have to create a new <code>MediaPlayer</code>
* object for each sound played in parallel. <code>Media</code> objects are
* however better suited for long-playing sounds. This is primarily because
* <code>AudioClip</code> stores in memory the raw, uncompressed audio data for
* the entire sound, which can be quite large for long audio clips. A
* <code>MediaPlayer</code> will only have enough decompressed audio data
* pre-rolled in memory to play for a short amount of time so it is much more
* memory efficient for long clips, especially if they are compressed.
* <br>
* <p>Example usage:</p>
* <pre>{@code
* AudioClip plonkSound = new AudioClip("http://somehost/path/plonk.aiff");
* plonkSound.play();
* }</pre>
*
* @since JavaFX 2.0
*/
public final class
AudioClip {
private
String sourceURL;
private com.sun.media.jfxmedia.
AudioClip audioClip;
/**
* Create an <code>AudioClip</code> loaded from the supplied source URL.
*
* @param source URL string from which to load the audio clip. This can be an
* HTTP, HTTPS, FILE or JAR source.
* @throws NullPointerException if the parameter is <code>null</code>.
* @throws IllegalArgumentException if the parameter violates
* <a href="http://www.ietf.org/rfc/rfc2396.txt">RFC 2396</a>.
* @throws MediaException if there is some other problem loading the media.
*/
public
AudioClip(@
NamedArg("source")
String source) {
URI srcURI =
URI.
create(
source);
sourceURL =
source;
try {
audioClip = com.sun.media.jfxmedia.
AudioClip.
load(
srcURI);
} catch(
URISyntaxException use) {
throw new
IllegalArgumentException(
use);
} catch(
FileNotFoundException fnfe) {
throw new
MediaException(
MediaException.
Type.
MEDIA_UNAVAILABLE,
fnfe.
getMessage());
} catch(
IOException ioe) {
throw new
MediaException(
MediaException.
Type.
MEDIA_INACCESSIBLE,
ioe.
getMessage());
} catch(com.sun.media.jfxmedia.
MediaException me) {
throw new
MediaException(
MediaException.
Type.
MEDIA_UNSUPPORTED,
me.
getMessage());
}
}
/**
* Get the source URL used to create this <code>AudioClip</code>.
* @return source URL as provided to the constructor
*/
public
String getSource() {
return
sourceURL;
}
/**
* The relative volume level at which the clip is played. Valid range is 0.0
* (muted) to 1.0 (full volume). Values are clamped to this range internally
* so values outside this range will have no additional effect. Volume is
* controlled by attenuation, so values below 1.0 will reduce the sound
* level accordingly.
*/
private
DoubleProperty volume;
/**
* Set the default volume level. The new setting will only take effect on
* subsequent plays.
* @see #volumeProperty()
* @param value new default volume level for this clip
*/
public final void
setVolume(double
value) {
volumeProperty().
set(
value);
}
/**
* Get the default volume level.
* @see #volumeProperty()
* @return the default volume level for this clip
*/
public final double
getVolume() {
return (null ==
volume) ? 1.0 :
volume.
get();
}
public
DoubleProperty volumeProperty() {
if (
volume == null) {
volume = new
DoublePropertyBase(1.0) {
@
Override
protected void
invalidated() {
if (null !=
audioClip) {
audioClip.
setVolume(
volume.
get());
}
}
@
Override
public
Object getBean() {
return
AudioClip.this;
}
@
Override
public
String getName() {
return "volume";
}
};
}
return
volume;
}
/**
* The relative left and right volume levels of the clip.
* Valid range is -1.0 to 1.0 where -1.0 gives full volume to the left
* channel while muting the right channel, 0.0 gives full volume to both
* channels and 1.0 gives full volume to right channel and mutes the left
* channel. Values outside this range are clamped internally.
*/
private
DoubleProperty balance;
/**
* Set the default balance level. The new value will only affect subsequent
* plays.
* @see #balanceProperty()
* @param balance new default balance
*/
public void
setBalance(double
balance) {
balanceProperty().
set(
balance);
}
/**
* Get the default balance level for this clip.
* @see #balanceProperty()
* @return the default balance for this clip
*/
public double
getBalance() {
return (null !=
balance) ?
balance.
get() : 0.0;
}
public
DoubleProperty balanceProperty() {
if (null ==
balance) {
balance = new
DoublePropertyBase(0.0) {
@
Override
protected void
invalidated() {
if (null !=
audioClip) {
audioClip.
setBalance(
balance.
get());
}
}
@
Override
public
Object getBean() {
return
AudioClip.this;
}
@
Override
public
String getName() {
return "balance";
}
};
}
return
balance;
}
/**
* The relative rate at which the clip is played. Valid range is 0.125
* (1/8 speed) to 8.0 (8x speed); values outside this range are clamped
* internally. Normal playback for a clip is 1.0; any other rate will affect
* pitch and duration accordingly.
*/
private
DoubleProperty rate;
/**
* Set the default playback rate. The new value will only affect subsequent
* plays.
* @see #rateProperty()
* @param rate the new default playback rate
*/
public void
setRate(double
rate) {
rateProperty().
set(
rate);
}
/**
* Get the default playback rate.
* @see #rateProperty()
* @return default playback rate for this clip
*/
public double
getRate() {
return (null !=
rate) ?
rate.
get() : 1.0;
}
public
DoubleProperty rateProperty() {
if (null ==
rate) {
rate = new
DoublePropertyBase(1.0) {
@
Override
protected void
invalidated() {
if (null !=
audioClip) {
audioClip.
setPlaybackRate(
rate.
get());
}
}
@
Override
public
Object getBean() {
return
AudioClip.this;
}
@
Override
public
String getName() {
return "rate";
}
};
}
return
rate;
}
/**
* The relative "center" of the clip. A pan value of 0.0 plays
* the clip normally where a -1.0 pan shifts the clip entirely to the left
* channel and 1.0 shifts entirely to the right channel. Unlike balance this
* setting mixes both channels so neither channel loses data. Setting
* pan on a mono clip has the same effect as setting balance, but with a
* much higher cost in CPU overhead so this is not recommended for mono
* clips.
*/
private
DoubleProperty pan;
/**
* Set the default pan value. The new value will only affect subsequent
* plays.
* @see #panProperty()
* @param pan the new default pan value
*/
public void
setPan(double
pan) {
panProperty().
set(
pan);
}
/**
* Get the default pan value.
* @see #panProperty()
* @return the default pan value for this clip
*/
public double
getPan() {
return (null !=
pan) ?
pan.
get() : 0.0;
}
public
DoubleProperty panProperty() {
if (null ==
pan) {
pan = new
DoublePropertyBase(0.0) {
@
Override
protected void
invalidated() {
if (null !=
audioClip) {
audioClip.
setPan(
pan.
get());
}
}
@
Override
public
Object getBean() {
return
AudioClip.this;
}
@
Override
public
String getName() {
return "pan";
}
};
}
return
pan;
}
/**
* The relative priority of the clip with respect to other clips. This value
* is used to determine which clips to remove when the maximum allowed number
* of clips is exceeded. The lower the priority, the more likely the
* clip is to be stopped and removed from the mixer channel it is occupying.
* Valid range is any integer; there are no constraints. The default priority
* is zero for all clips until changed. The number of simultaneous sounds
* that can be played is implementation- and possibly system-dependent.
*/
private
IntegerProperty priority;
/**
* Set the default playback priority. The new value will only affect
* subsequent plays.
* @see #priorityProperty()
* @param priority the new default playback priority
*/
public void
setPriority(int
priority) {
priorityProperty().
set(
priority);
}
/**
* Get the default playback priority.
* @see #priorityProperty()
* @return the default playback priority of this clip
*/
public int
getPriority() {
return (null !=
priority) ?
priority.
get() : 0;
}
public
IntegerProperty priorityProperty() {
if (null ==
priority) {
priority = new
IntegerPropertyBase(0) {
@
Override
protected void
invalidated() {
if (null !=
audioClip) {
audioClip.
setPriority(
priority.
get());
}
}
@
Override
public
Object getBean() {
return
AudioClip.this;
}
@
Override
public
String getName() {
return "priority";
}
};
}
return
priority;
}
/**
* When {@link #cycleCountProperty cycleCount} is set to this value, the
* <code>AudioClip</code> will loop continuously until stopped. This value is
* synonymous with {@link MediaPlayer#INDEFINITE} and
* {@link javafx.animation.Animation#INDEFINITE}, these values may be used
* interchangeably.
*/
public static final int
INDEFINITE = -1;
/**
* The number of times the clip will be played when {@link #play()}
* is called. A cycleCount of 1 plays exactly once, a cycleCount of 2
* plays twice and so on. Valid range is 1 or more, but setting this to
* {@link #INDEFINITE INDEFINITE} will cause the clip to continue looping
* until {@link #stop} is called.
*/
private
IntegerProperty cycleCount;
/**
* Set the default cycle count. The new value will only affect subsequent
* plays.
* @see #cycleCountProperty()
* @param count the new default cycle count for this clip
*/
public void
setCycleCount(int
count) {
cycleCountProperty().
set(
count);
}
/**
* Get the default cycle count.
* @see #cycleCountProperty()
* @return the default cycleCount for this audio clip
*/
public int
getCycleCount() {
return (null !=
cycleCount) ?
cycleCount.
get() : 1;
}
public
IntegerProperty cycleCountProperty() {
if (null ==
cycleCount) {
cycleCount = new
IntegerPropertyBase(1) {
@
Override
protected void
invalidated() {
if (null !=
audioClip) {
int
value =
cycleCount.
get();
if (
INDEFINITE !=
value) {
value =
Math.
max(1,
value);
audioClip.
setLoopCount(
value - 1);
} else {
audioClip.
setLoopCount(
value); // INDEFINITE is the same there
}
}
}
@
Override
public
Object getBean() {
return
AudioClip.this;
}
@
Override
public
String getName() {
return "cycleCount";
}
};
}
return
cycleCount;
}
/**
* Play the <code>AudioClip</code> using all the default parameters.
*/
public void
play() {
if (null !=
audioClip) {
audioClip.
play();
}
}
/**
* Play the <code>AudioClip</code> using all the default parameters except volume.
* This method does not modify the clip's default parameters.
* @param volume the volume level at which to play the clip
*/
public void
play(double
volume) {
if (null !=
audioClip) {
audioClip.
play(
volume);
}
}
/**
* Play the <code>AudioClip</code> using the given parameters. Values outside
* the ranges as specified by their associated properties are clamped.
* This method does not modify the clip's default parameters.
*
* @param volume Volume level at which to play this clip. Valid volume range is
* 0.0 to 1.0, where 0.0 is effectively muted and 1.0 is full volume.
* @param balance Left/right balance or relative channel volumes for stereo
* effects.
* @param rate Playback rate multiplier. 1.0 will play at the normal
* rate while 2.0 will double the rate.
* @param pan Left/right shift to be applied to the clip. A pan value of
* -1.0 means full left channel, 1.0 means full right channel, 0.0 has no
* effect.
* @param priority Audio effect priority. Lower priority effects will be
* dropped first if too many effects are trying to play simultaneously.
*/
public void
play(double
volume, double
balance, double
rate, double
pan, int
priority) {
if (null !=
audioClip) {
audioClip.
play(
volume,
balance,
rate,
pan,
audioClip.
loopCount(),
priority);
}
}
/**
* Indicate whether this <code>AudioClip</code> is playing. If this returns true
* then <code>play()</code> has been called at least once and it is still playing.
* @return true if any mixer channel has this clip queued, false otherwise
*/
public boolean
isPlaying() {
return null !=
audioClip &&
audioClip.
isPlaying();
}
/**
* Immediately stop all playback of this <code>AudioClip</code>.
*/
public void
stop() {
if (null !=
audioClip) {
audioClip.
stop();
}
}
}