Spec-Zone .ru
спецификации, руководства, описания, API
001/*
002 * Copyright (c) 2010, 2012, Oracle and/or its affiliates. All rights reserved.
003 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
004 *
005 * This code is free software; you can redistribute it and/or modify it
006 * under the terms of the GNU General Public License version 2 only, as
007 * published by the Free Software Foundation.  Oracle designates this
008 * particular file as subject to the "Classpath" exception as provided
009 * by Oracle in the LICENSE file that accompanied this code.
010 *
011 * This code is distributed in the hope that it will be useful, but WITHOUT
012 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
013 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
014 * version 2 for more details (a copy is included in the LICENSE file that
015 * accompanied this code).
016 *
017 * You should have received a copy of the GNU General Public License version
018 * 2 along with this work; if not, write to the Free Software Foundation,
019 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
020 *
021 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
022 * or visit www.oracle.com if you need additional information or have any
023 * questions.
024 */
025
026package javafx.scene.media;
027
028import java.net.URI;
029import java.io.IOException;
030import java.io.FileNotFoundException;
031import java.net.URISyntaxException;
032
033import javafx.beans.property.DoubleProperty;
034import javafx.beans.property.DoublePropertyBase;
035import javafx.beans.property.IntegerProperty;
036import javafx.beans.property.IntegerPropertyBase;
037
038/**
039 * An <code>AudioClip</code> represents a segment of audio that can be played
040 * with minimal latency. Clips are loaded similarly to <code>Media</code>
041 * objects but have different behavior, for example, a <code>Media</code> cannot
042 * play itself. <code>AudioClip</code>s are also usable immediately. Playback
043 * behavior is fire and forget: once one of the play methods is called the only
044 * operable control is {@link #stop()}. An <code>AudioClip</code> may also be
045 * played multiple times simultaneously. To accomplish the same task using
046 * <code>Media</code> one would have to create a new <code>MediaPlayer</code>
047 * object for each sound played in parallel. <code>Media</code> objects are
048 * however better suited for long-playing sounds. This is primarily because
049 * <code>AudioClip</code> stores in memory the raw, uncompressed audio data for
050 * the entire sound, which can be quite large for long audio clips. A
051 * <code>MediaPlayer</code> will only have enough decompressed audio data
052 * pre-rolled in memory to play for a short amount of time so it is much more
053 * memory efficient for long clips, especially if they are compressed.
054 * <br>
055 * <p>Example usage:
056 * <pre><code>
057 * AudioClip plonkSound = new AudioClip("http://somehost/path/plonk.aiff");
058 * plonkSound.play();
059 * </code></pre>
060 * </p>
061 */
062
063public final class AudioClip {
064    private String sourceURL;
065    private com.sun.media.jfxmedia.AudioClip audioClip;
066
067    /**
068     * Create an <code>AudioClip</code> loaded from the supplied source URL.
069     *
070     * @param source URL string from which to load the audio clip. This can be an
071     * HTTP, file or jar source.
072     * @throws NullPointerException if the parameter is <code>null</code>.
073     * @throws IllegalArgumentException if the parameter violates
074     * <a href="http://www.ietf.org/rfc/rfc2396.txt">RFC 2396</a>.
075     * @throws MediaException if there is some other problem loading the media.
076     */
077    public AudioClip(String source) {
078        URI srcURI = URI.create(source);
079        sourceURL = source;
080        try {
081            audioClip = com.sun.media.jfxmedia.AudioClip.load(srcURI);
082        } catch(URISyntaxException use) {
083            throw new IllegalArgumentException(use);
084        } catch(FileNotFoundException fnfe) {
085            throw new MediaException(MediaException.Type.MEDIA_UNAVAILABLE, fnfe.getMessage());
086        } catch(IOException ioe) {
087            throw new MediaException(MediaException.Type.MEDIA_INACCESSIBLE, ioe.getMessage());
088        } catch(com.sun.media.jfxmedia.MediaException me) {
089            throw new MediaException(MediaException.Type.MEDIA_UNSUPPORTED, me.getMessage());
090        }
091    }
092
093    /**
094     * Get the source URL used to create this <code>AudioClip</code>.
095     * @return source URL as provided to the constructor
096     */
097    public String getSource() {
098        return sourceURL;
099    }
100
101    /**
102     * The relative volume level at which the clip is played. Valid range is 0.0
103     * (muted) to 1.0 (full volume). Values are clamped to this range internally
104     * so values outside this range will have no additional effect. Volume is
105     * controlled by attenuation, so values below 1.0 will reduce the sound
106     * level accordingly.
107     */
108    private DoubleProperty volume;
109    
110    /**
111     * Set the default volume level. The new setting will only take effect on
112     * subsequent plays.
113     * @see #volume
114     * @param value new default volume level for this clip
115     */
116    public final void setVolume(double value) {
117        volumeProperty().set(value);
118    }
119
120    /**
121     * Get the default volume level.
122     * @see #volume
123     * @return the default volume level for this clip
124     */
125    public final double getVolume() {
126        return (null == volume) ? 1.0 : volume.get();
127    }
128    public DoubleProperty volumeProperty() {
129        if (volume == null) {
130            volume = new DoublePropertyBase(1.0) {
131                @Override
132                protected void invalidated() {
133                    if (null != audioClip) {
134                        audioClip.setVolume(volume.get());
135                    }
136                }
137
138                @Override
139                public Object getBean() {
140                    return AudioClip.this;
141                }
142
143                @Override
144                public String getName() {
145                    return "volume";
146                }
147            };
148        }
149        return volume;
150    }
151
152    /**
153     * The relative left and right volume levels of the clip.
154     * Valid range is -1.0 to 1.0 where -1.0 gives full volume to the left
155     * channel while muting the right channel, 0.0 gives full volume to both
156     * channels and 1.0 gives full volume to right channel and mutes the left
157     * channel. Values outside this range are clamped internally.
158     */
159    private DoubleProperty balance;
160
161    /**
162     * Set the default balance level. The new value will only affect subsequent
163     * plays.
164     * @see #balance
165     * @param balance new default balance
166     */
167    public void setBalance(double balance) {
168        balanceProperty().set(balance);
169    }
170
171    /**
172     * Get the default balance level for this clip.
173     * @see #balance
174     * @return the default balance for this clip
175     */
176    public double getBalance() {
177        return (null != balance) ? balance.get() : 0.0;
178    }
179    public DoubleProperty balanceProperty() {
180        if (null == balance) {
181            balance = new DoublePropertyBase(0.0) {
182                @Override
183                protected void invalidated() {
184                    if (null != audioClip) {
185                        audioClip.setBalance(balance.get());
186                    }
187                }
188
189                @Override
190                public Object getBean() {
191                    return AudioClip.this;
192                }
193
194                @Override
195                public String getName() {
196                    return "balance";
197                }
198            };
199        }
200        return balance;
201    }
202
203    /**
204     * The relative rate at which the clip is played. Valid range is 0.125
205     * (1/8 speed) to 8.0 (8x speed); values outside this range are clamped
206     * internally. Normal playback for a clip is 1.0; any other rate will affect
207     * pitch and duration accordingly.
208     */
209    private DoubleProperty rate;
210
211    /**
212     * Set the default playback rate. The new value will only affect subsequent
213     * plays.
214     * @see #rate
215     * @param rate the new default playback rate
216     */
217    public void setRate(double rate) {
218        rateProperty().set(rate);
219    }
220
221    /**
222     * Get the default playback rate.
223     * @see #rate
224     * @return default playback rate for this clip
225     */
226    public double getRate() {
227        return (null != rate) ? rate.get() : 1.0;
228    }
229    public DoubleProperty rateProperty() {
230        if (null == rate) {
231            rate = new DoublePropertyBase(1.0) {
232                @Override
233                protected void invalidated() {
234                    if (null != audioClip) {
235                        audioClip.setPlaybackRate(rate.get());
236                    }
237                }
238
239                @Override
240                public Object getBean() {
241                    return AudioClip.this;
242                }
243
244                @Override
245                public String getName() {
246                    return "rate";
247                }
248            };
249        }
250        return rate;
251    }
252
253    /**
254     * The relative "center" of the clip. A pan value of 0.0 plays
255     * the clip normally where a -1.0 pan shifts the clip entirely to the left
256     * channel and 1.0 shifts entirely to the right channel. Unlike balance this
257     * setting mixes both channels so neither channel loses data. Setting
258     * pan on a mono clip has the same effect as setting balance, but with a
259     * much higher cost in CPU overhead so this is not recommended for mono
260     * clips.
261     */
262    private DoubleProperty pan;
263    
264    /**
265     * Set the default pan value. The new value will only affect subsequent
266     * plays.
267     * @see #pan
268     * @param pan the new default pan value
269     */
270    public void setPan(double pan) {
271        panProperty().set(pan);
272    }
273
274    /**
275     * Get the default pan value.
276     * @see #pan
277     * @return the default pan value for this clip
278     */
279    public double getPan() {
280        return (null != pan) ? pan.get() : 0.0;
281    }
282    public DoubleProperty panProperty() {
283        if (null == pan) {
284            pan = new DoublePropertyBase(0.0) {
285                @Override
286                protected void invalidated() {
287                    if (null != audioClip) {
288                        audioClip.setPan(pan.get());
289                    }
290                }
291
292                @Override
293                public Object getBean() {
294                    return AudioClip.this;
295                }
296
297                @Override
298                public String getName() {
299                    return "pan";
300                }
301            };
302        }
303        return pan;
304    }
305    
306    /**
307     * The relative priority of the clip with respect to other clips. This value
308     * is used to determine which clips to remove when the maximum allowed number
309     * of clips is exceeded. The lower the priority, the more likely the
310     * clip is to be stopped and removed from the mixer channel it is occupying.
311     * Valid range is any integer; there are no constraints. The default priority
312     * is zero for all clips until changed. The number of simultaneous sounds
313     * that can be played is implementation- and possibly system-dependent.
314     */
315    private IntegerProperty priority;
316    
317    /**
318     * Set the default playback priority. The new value will only affect
319     * subsequent plays.
320     * @see #priority
321     * @param priority the new default playback priority
322     */
323    public void setPriority(int priority) {
324        priorityProperty().set(priority);
325    }
326
327    /**
328     * Get the default playback priority.
329     * @see #priority
330     * @return the default playback priority of this clip
331     */
332    public int getPriority() {
333        return (null != priority) ? priority.get() : 0;
334    }
335    public IntegerProperty priorityProperty() {
336        if (null == priority) {
337            priority = new IntegerPropertyBase(0) {
338                @Override
339                protected void invalidated() {
340                    if (null != audioClip) {
341                        audioClip.setPriority(priority.get());
342                    }
343                }
344
345                @Override
346                public Object getBean() {
347                    return AudioClip.this;
348                }
349
350                @Override
351                public String getName() {
352                    return "priority";
353                }
354            };
355        }
356        return priority;
357    }
358
359    /**
360     * When {@link #cycleCountProperty cycleCount} is set to this value, the
361     * <code>AudioClip</code> will loop continuously until stopped. This value is
362     * synonymous with {@link MediaPlayer#INDEFINITE} and
363     * {@link javafx.animation.Animation#INDEFINITE}, these values may be used
364     * interchangeably.
365     */
366    public static final int INDEFINITE = -1;
367
368    /**
369     * The number of times the clip will be played when {@link #play()}
370     * is called. A cycleCount of 1 plays exactly once, a cycleCount of 2
371     * plays twice and so on. Valid range is 1 or more, but setting this to
372     * {@link #INDEFINITE INDEFINITE} will cause the clip to continue looping
373     * until {@link #stop} is called.
374     */
375    private IntegerProperty cycleCount;
376
377    /**
378     * Set the default cycle count. The new value will only affect subsequent
379     * plays.
380     * @see #cycleCount
381     * @param count the new default cycle count for this clip
382     */
383    public void setCycleCount(int count) {
384        cycleCountProperty().set(count);
385    }
386
387    /**
388     * Get the default cycle count.
389     * @see #cycleCount
390     * @return the default cycleCount for this audio clip
391     */
392    public int getCycleCount() {
393        return (null != cycleCount) ? cycleCount.get() : 1;
394    }
395    public IntegerProperty cycleCountProperty() {
396        if (null == cycleCount) {
397            cycleCount = new IntegerPropertyBase(1) {
398                @Override
399                protected void invalidated() {
400                    if (null != audioClip) {
401                        int value = cycleCount.get();
402                        if (INDEFINITE != value) {
403                            value = Math.max(1, value);
404                            audioClip.setLoopCount(value - 1);
405                        } else {
406                            audioClip.setLoopCount(value); // INDEFINITE is the same there
407                        }
408                    }
409                }
410
411                @Override
412                public Object getBean() {
413                    return AudioClip.this;
414                }
415
416                @Override
417                public String getName() {
418                    return "cycleCount";
419                }
420            };
421        }
422        return cycleCount;
423    }
424    /**
425     * Play the <code>AudioClip</code> using all the default parameters.
426     */
427    public void play() {
428        if (null != audioClip) {
429            audioClip.play();
430        }
431    }
432
433    /**
434     * Play the <code>AudioClip</code> using all the default parameters except volume.
435     * This method does not modify the clip's default parameters.
436     * @param volume the volume level at which to play the clip
437     */
438    public void play(double volume) {
439        if (null != audioClip) {
440            audioClip.play(volume);
441        }
442    }
443
444    /**
445     * Play the <code>AudioClip</code> using the given parameters. Values outside
446     * the ranges as specified by their associated properties are clamped.
447     * This method does not modify the clip's default parameters.
448     *
449     * @param volume Volume level at which to play this clip. Valid volume range is
450     * 0.0 to 1.0, where 0.0 is effectively muted and 1.0 is full volume.
451     * @param balance Left/right balance or relative channel volumes for stereo
452     * effects.
453     * @param rate Playback rate multiplier. 1.0 will play at the normal
454     * rate while 2.0 will double the rate.
455     * @param pan Left/right shift to be applied to the clip. A pan value of
456     * -1.0 means full left channel, 1.0 means full right channel, 0.0 has no
457     * effect.
458     * @param priority Audio effect priority. Lower priority effects will be
459     * dropped first if too many effects are trying to play simultaneously.
460     */
461    public void play(double volume, double balance, double rate, double pan, int priority) {
462        if (null != audioClip) {
463            audioClip.play(volume, balance, rate, pan, audioClip.loopCount(), priority);
464        }
465    }
466
467    /**
468     * Indicate whether this <code>AudioClip</code> is playing. If this returns true
469     * then <code>play()</code> has been called at least once and it is still playing.
470     * @return true if any mixer channel has this clip queued, false otherwise
471     */
472    public boolean isPlaying() {
473        return null != audioClip && audioClip.isPlaying();
474    }
475
476    /**
477     * Immediately stop all playback of this <code>AudioClip</code>.
478     */
479    public void stop() {
480        if (null != audioClip) {
481            audioClip.stop();
482        }
483    }
484}