git-svn-id: https://svn.apache.org/repos/asf/ant/core/trunk@271320 13f79535-47bb-0310-9956-ffa450edef68master
| @@ -0,0 +1,237 @@ | |||
| /* | |||
| * Copyright (C) The Apache Software Foundation. All rights reserved. | |||
| * | |||
| * This software is published under the terms of the Apache Software License | |||
| * version 1.1, a copy of which has been included with this distribution in | |||
| * the LICENSE.txt file. | |||
| */ | |||
| package org.apache.antlib.sound; | |||
| import java.io.File; | |||
| import java.io.IOException; | |||
| import javax.sound.sampled.AudioFormat; | |||
| import javax.sound.sampled.AudioInputStream; | |||
| import javax.sound.sampled.AudioSystem; | |||
| import javax.sound.sampled.Clip; | |||
| import javax.sound.sampled.DataLine; | |||
| import javax.sound.sampled.Line; | |||
| import javax.sound.sampled.LineEvent; | |||
| import javax.sound.sampled.LineListener; | |||
| import javax.sound.sampled.LineUnavailableException; | |||
| import javax.sound.sampled.UnsupportedAudioFileException; | |||
| import org.apache.avalon.framework.logger.LogEnabled; | |||
| import org.apache.avalon.framework.logger.Logger; | |||
| import org.apache.myrmidon.listeners.AbstractProjectListener; | |||
| import org.apache.myrmidon.listeners.LogEvent; | |||
| import org.apache.myrmidon.listeners.ProjectEvent; | |||
| /** | |||
| * This class is designed to be used by any AntTask that requires audio output. | |||
| * It implements the BuildListener interface to listen for BuildEvents and could | |||
| * be easily extended to provide audio output upon any specific build events | |||
| * occuring. I have only tested this with .WAV and .AIFF sound file formats. | |||
| * Both seem to work fine. | |||
| * | |||
| * @author Nick Pellow | |||
| * @version $Revision$, $Date$ | |||
| */ | |||
| public class AntSoundPlayer | |||
| extends AbstractProjectListener | |||
| implements LineListener, LogEnabled | |||
| { | |||
| private File m_fileSuccess; | |||
| private int m_loopsSuccess; | |||
| private Long m_durationSuccess; | |||
| private File m_fileFail; | |||
| private int m_loopsFail; | |||
| private Long m_durationFail; | |||
| private Logger m_logger; | |||
| /** | |||
| * Provide component with a logger. | |||
| * | |||
| * @param logger the logger | |||
| */ | |||
| public void enableLogging( final Logger logger ) | |||
| { | |||
| m_logger = logger; | |||
| } | |||
| protected final Logger getLogger() | |||
| { | |||
| return m_logger; | |||
| } | |||
| /** | |||
| * Notify listener of projectFinished event. | |||
| */ | |||
| public void projectFinished( final ProjectEvent event ) | |||
| { | |||
| success(); | |||
| } | |||
| /** | |||
| * Notify listener of log message event. | |||
| */ | |||
| public void log( final LogEvent event ) | |||
| { | |||
| if( event.getThrowable() != null ) | |||
| { | |||
| failure(); | |||
| } | |||
| } | |||
| /** | |||
| * @param fileFail The feature to be added to the BuildFailedSound attribute | |||
| * @param loopsFail The feature to be added to the BuildFailedSound | |||
| * attribute | |||
| * @param durationFail The feature to be added to the BuildFailedSound | |||
| * attribute | |||
| */ | |||
| public void addBuildFailedSound( File fileFail, int loopsFail, Long durationFail ) | |||
| { | |||
| m_fileFail = fileFail; | |||
| m_loopsFail = loopsFail; | |||
| m_durationFail = durationFail; | |||
| } | |||
| /** | |||
| * @param loops the number of times the file should be played when the build | |||
| * is successful | |||
| * @param duration the number of milliseconds the file should be played when | |||
| * the build is successful | |||
| * @param file The feature to be added to the BuildSuccessfulSound attribute | |||
| */ | |||
| public void addBuildSuccessfulSound( File file, int loops, Long duration ) | |||
| { | |||
| m_fileSuccess = file; | |||
| m_loopsSuccess = loops; | |||
| m_durationSuccess = duration; | |||
| } | |||
| /** | |||
| * This is implemented to listen for any line events and closes the clip if | |||
| * required. | |||
| */ | |||
| public void update( LineEvent event ) | |||
| { | |||
| if( event.getType().equals( LineEvent.Type.STOP ) ) | |||
| { | |||
| Line line = event.getLine(); | |||
| line.close(); | |||
| } | |||
| else if( event.getType().equals( LineEvent.Type.CLOSE ) ) | |||
| { | |||
| /* | |||
| * There is a bug in JavaSound 0.90 (jdk1.3beta). | |||
| * It prevents correct termination of the VM. | |||
| * So we have to exit ourselves. | |||
| */ | |||
| //System.exit(0); | |||
| } | |||
| } | |||
| protected void success() | |||
| { | |||
| if( null != m_fileSuccess ) | |||
| { | |||
| // build successfull! | |||
| play( m_fileSuccess, m_loopsSuccess, m_durationSuccess ); | |||
| } | |||
| } | |||
| protected void failure() | |||
| { | |||
| if( null != m_fileFail ) | |||
| { | |||
| play( m_fileFail, m_loopsFail, m_durationFail ); | |||
| } | |||
| } | |||
| /** | |||
| * Plays the file for duration milliseconds or loops. | |||
| */ | |||
| private void play( File file, int loops, Long duration ) | |||
| { | |||
| Clip audioClip = null; | |||
| AudioInputStream audioInputStream = null; | |||
| try | |||
| { | |||
| audioInputStream = AudioSystem.getAudioInputStream( file ); | |||
| } | |||
| catch( UnsupportedAudioFileException uafe ) | |||
| { | |||
| final String message = "Audio format is not yet supported: " + uafe.getMessage(); | |||
| getLogger().info( message ); | |||
| } | |||
| catch( IOException ioe ) | |||
| { | |||
| ioe.printStackTrace(); | |||
| } | |||
| if( audioInputStream != null ) | |||
| { | |||
| AudioFormat format = audioInputStream.getFormat(); | |||
| DataLine.Info info = new DataLine.Info( Clip.class, format, | |||
| AudioSystem.NOT_SPECIFIED ); | |||
| try | |||
| { | |||
| audioClip = (Clip)AudioSystem.getLine( info ); | |||
| audioClip.addLineListener( this ); | |||
| audioClip.open( audioInputStream ); | |||
| } | |||
| catch( LineUnavailableException e ) | |||
| { | |||
| final String message = "The sound device is currently unavailable"; | |||
| getLogger().info( message ); | |||
| return; | |||
| } | |||
| catch( IOException e ) | |||
| { | |||
| e.printStackTrace(); | |||
| } | |||
| if( duration != null ) | |||
| { | |||
| playClip( audioClip, duration.longValue() ); | |||
| } | |||
| else | |||
| { | |||
| playClip( audioClip, loops ); | |||
| } | |||
| audioClip.drain(); | |||
| audioClip.close(); | |||
| } | |||
| else | |||
| { | |||
| final String message = "Can't get data from file " + file.getName(); | |||
| getLogger().info( message ); | |||
| } | |||
| } | |||
| private void playClip( Clip clip, int loops ) | |||
| { | |||
| clip.loop( loops ); | |||
| while( clip.isRunning() ) | |||
| { | |||
| } | |||
| } | |||
| private void playClip( Clip clip, long duration ) | |||
| { | |||
| clip.loop( Clip.LOOP_CONTINUOUSLY ); | |||
| try | |||
| { | |||
| Thread.sleep( duration ); | |||
| } | |||
| catch( InterruptedException e ) | |||
| { | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,81 @@ | |||
| /* | |||
| * Copyright (C) The Apache Software Foundation. All rights reserved. | |||
| * | |||
| * This software is published under the terms of the Apache Software License | |||
| * version 1.1, a copy of which has been included with this distribution in | |||
| * the LICENSE.txt file. | |||
| */ | |||
| package org.apache.antlib.sound; | |||
| import java.io.File; | |||
| /** | |||
| * A class to be extended by any BuildAlert's that require the output of | |||
| * sound. | |||
| */ | |||
| public class BuildAlert | |||
| { | |||
| private File m_source; | |||
| private int m_loops; | |||
| private Long m_duration; | |||
| /** | |||
| * Sets the duration in milliseconds the file should be played. | |||
| * | |||
| * @param duration The new Duration value | |||
| */ | |||
| public void setDuration( Long duration ) | |||
| { | |||
| m_duration = duration; | |||
| } | |||
| /** | |||
| * Sets the number of times the source file should be played. | |||
| * | |||
| * @param loops the number of loops to play the source file | |||
| */ | |||
| public void setLoops( int loops ) | |||
| { | |||
| m_loops = loops; | |||
| } | |||
| /** | |||
| * Sets the location of the file to get the audio. | |||
| * | |||
| * @param source the name of a sound-file directory or of the audio file | |||
| */ | |||
| public void setSource( final File source ) | |||
| { | |||
| m_source = source; | |||
| } | |||
| /** | |||
| * Gets the duration in milliseconds the file should be played. | |||
| * | |||
| * @return The Duration value | |||
| */ | |||
| public Long getDuration() | |||
| { | |||
| return m_duration; | |||
| } | |||
| /** | |||
| * Sets the number of times the source file should be played. | |||
| * | |||
| * @return the number of loops to play the source file | |||
| */ | |||
| public int getLoops() | |||
| { | |||
| return m_loops; | |||
| } | |||
| /** | |||
| * Gets the location of the file to get the audio. | |||
| * | |||
| * @return The Source value | |||
| */ | |||
| public File getSource() | |||
| { | |||
| return m_source; | |||
| } | |||
| } | |||
| @@ -0,0 +1,4 @@ | |||
| sound.missing-success.error=No nested success element found. | |||
| sound.missing-failure.error=No nested failure element found. | |||
| sound.empty.dir.error=No files found in directory {0}. | |||
| sound.invalid-path.error={0}: invalid path. | |||
| @@ -0,0 +1,134 @@ | |||
| /* | |||
| * Copyright (C) The Apache Software Foundation. All rights reserved. | |||
| * | |||
| * This software is published under the terms of the Apache Software License | |||
| * version 1.1, a copy of which has been included with this distribution in | |||
| * the LICENSE.txt file. | |||
| */ | |||
| package org.apache.antlib.sound; | |||
| import java.io.File; | |||
| import java.util.ArrayList; | |||
| import java.util.Random; | |||
| import org.apache.myrmidon.api.AbstractTask; | |||
| import org.apache.myrmidon.api.TaskException; | |||
| import org.apache.myrmidon.interfaces.workspace.Workspace; | |||
| import org.apache.avalon.excalibur.i18n.Resources; | |||
| import org.apache.avalon.excalibur.i18n.ResourceManager; | |||
| /** | |||
| * This is an example of an AntTask that makes of use of the AntSoundPlayer. | |||
| * There are three attributes to be set: <code>source</code>: the location of | |||
| * the audio file to be played <code>duration</code>: play the sound file | |||
| * continuously until "duration" milliseconds has expired <code>loops</code>: | |||
| * the number of times the sound file should be played until stopped I have only | |||
| * tested this with .WAV and .AIFF sound file formats. Both seem to work fine. | |||
| * plans for the future: - use the midi api to define sounds (or drum beat etc) | |||
| * in xml and have Ant play them back | |||
| * | |||
| * @ant:task name="sound-listener" | |||
| * @author Nick Pellow | |||
| * @author <a href="mailto:peter@apache.org">Peter Donald</a> | |||
| * @version $Revision$, $Date$ | |||
| */ | |||
| public class SoundTask | |||
| extends AbstractTask | |||
| { | |||
| private final static Resources REZ = | |||
| ResourceManager.getPackageResources( SoundTask.class ); | |||
| private BuildAlert m_success; | |||
| private BuildAlert m_fail; | |||
| public void addFail( final BuildAlert fail ) | |||
| { | |||
| m_fail = fail; | |||
| } | |||
| public void addSuccess( final BuildAlert success ) | |||
| { | |||
| m_success = success; | |||
| } | |||
| public void execute() | |||
| throws TaskException | |||
| { | |||
| final AntSoundPlayer soundPlayer = new AntSoundPlayer(); | |||
| if( null == m_success ) | |||
| { | |||
| final String message = REZ.getString( "sound.missing-success.error" ); | |||
| getLogger().warn( message ); | |||
| } | |||
| else | |||
| { | |||
| final File source = getRandomSource( m_success ); | |||
| soundPlayer.addBuildSuccessfulSound( source, | |||
| m_success.getLoops(), | |||
| m_success.getDuration() ); | |||
| } | |||
| if( null == m_fail ) | |||
| { | |||
| final String message = REZ.getString( "sound.missing-failure.error" ); | |||
| getLogger().warn( message ); | |||
| } | |||
| else | |||
| { | |||
| final File source = getRandomSource( m_fail ); | |||
| soundPlayer.addBuildFailedSound( source, | |||
| m_fail.getLoops(), | |||
| m_fail.getDuration() ); | |||
| } | |||
| final Workspace workspace = (Workspace)getContext().getService( Workspace.class ); | |||
| workspace.addProjectListener( soundPlayer ); | |||
| } | |||
| /** | |||
| * Gets the location of the file to get the audio. | |||
| */ | |||
| private File getRandomSource( final BuildAlert alert ) | |||
| throws TaskException | |||
| { | |||
| final File source = alert.getSource(); | |||
| // Check if source is a directory | |||
| if( source.exists() ) | |||
| { | |||
| if( source.isDirectory() ) | |||
| { | |||
| // get the list of files in the dir | |||
| final String[] entries = source.list(); | |||
| final ArrayList files = new ArrayList(); | |||
| for( int i = 0; i < entries.length; i++ ) | |||
| { | |||
| final File file = new File( source, entries[ i ] ); | |||
| if( file.isFile() ) | |||
| { | |||
| files.add( file ); | |||
| } | |||
| } | |||
| if( files.size() < 1 ) | |||
| { | |||
| final String message = REZ.getString( "sound.empty.dir.error", source ); | |||
| throw new TaskException( message ); | |||
| } | |||
| final int numfiles = files.size(); | |||
| // get a random number between 0 and the number of files | |||
| final Random random = new Random(); | |||
| final int x = random.nextInt( numfiles ); | |||
| // set the source to the file at that location | |||
| return (File)files.get( x ); | |||
| } | |||
| else | |||
| { | |||
| return null; | |||
| } | |||
| } | |||
| else | |||
| { | |||
| final String message = REZ.getString( "sound.invalid-path.error", source ); | |||
| getLogger().warn( message ); | |||
| return null; | |||
| } | |||
| } | |||
| } | |||