/**
* GmailAssistant 1.1 (2008-03-16)
* Copyright 2008 Zach Scrivena
* zachscrivena@gmail.com
* http://gmailassistant.sourceforge.net/
*
* Notifier for multiple Gmail accounts.
*
* TERMS AND CONDITIONS:
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package gmailassistant;
import java.util.ArrayDeque;
import java.util.Queue;
import java.util.Timer;
import java.util.TimerTask;
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;
/**
* Play an audible chime and periodic bell.
*/
class ChimePlayer
{
/** timer refresh interval */
private static final long TIMER_REFRESH_INTERVAL_MILLISECONDS = 200L;
/** chime audio clip */
private Clip chime = null;
/** periodic bell audio clip */
private Clip periodicBell = null;
/** timer for playing the chime */
private final Timer timer;
/** is the timer cancelled? */
private volatile boolean timerCancelled = false;
/** should the periodic bell be played? */
private volatile boolean periodicBellPlay = false;
/** time at which the periodic bell was last played */
private volatile long periodicBellLastPlayed = 0L;
/** queued actions to be performed by the timer */
private final Queue<ActionType> actions = new ArrayDeque<ActionType>();
/**
* Constructor.
*
* @param parent
* parent GmailAssistant object
*/
ChimePlayer(
final GmailAssistant parent)
{
/* load chime audio clip */
try
{
final AudioInputStream stream = AudioSystem.getAudioInputStream(
ChimePlayer.class.getResource("resources/bells_call_2.wav"));
final AudioFormat format = stream.getFormat();
this.chime = (Clip) AudioSystem.getLine(new DataLine.Info(
Clip.class,
format,
(int) (stream.getFrameLength() * format.getFrameSize())));
this.chime.open(stream);
}
catch (Exception e)
{
this.chime = null;
SwingManipulator.showErrorDialog(parent, "Initialization Error - " + GmailAssistant.NAME,
"Failed to load chime audio clip (" + e + ").\n" +
GmailAssistant.NAME + " will proceed to run without playing the chime audio clip.");
}
/* load periodic bell audio clip */
try
{
final AudioInputStream stream = AudioSystem.getAudioInputStream(
ChimePlayer.class.getResource("resources/desk_bell.wav"));
final AudioFormat format = stream.getFormat();
this.periodicBell = (Clip) AudioSystem.getLine(new DataLine.Info(
Clip.class,
format,
(int) (stream.getFrameLength() * format.getFrameSize())));
this.periodicBell.open(stream);
}
catch (Exception e)
{
this.periodicBell = null;
SwingManipulator.showErrorDialog(parent, "Initialization Error - " + GmailAssistant.NAME,
"Failed to load periodic bell audio clip (" + e + ").\n" +
GmailAssistant.NAME + " will proceed to run without playing the periodic bell audio clip.");
}
/***************************
* INITIALIZE TIMER THREAD *
***************************/
this.timer = new Timer("Chime-Player-Timer", true);
this.timer.schedule(new TimerTask()
{
@Override
public void run()
{
ActionType action;
synchronized (ChimePlayer.this.actions)
{
action = ChimePlayer.this.actions.poll();
}
if (action == ActionType.CHIME)
{
playClipAndWait(ChimePlayer.this.chime);
ChimePlayer.this.periodicBellLastPlayed = System.currentTimeMillis();
}
else if (action == ActionType.TEST_CHIME)
{
playClipAndWait(ChimePlayer.this.chime);
}
else if (action == ActionType.TEST_PERIODIC_BELL)
{
playClipAndWait(ChimePlayer.this.periodicBell);
}
else if (action == ActionType.TERMINATE)
{
ChimePlayer.this.timer.cancel();
ChimePlayer.this.timerCancelled = true;
}
/* play periodic bell, if necessary */
if (ChimePlayer.this.periodicBellPlay &&
((System.currentTimeMillis() - ChimePlayer.this.periodicBellLastPlayed) >=
((Long) parent.getProperty(GmailAssistant.Property.ALERT_PERIODIC_BELL_INTERVAL_MILLISECONDS))))
{
playClipAndWait(ChimePlayer.this.periodicBell);
ChimePlayer.this.periodicBellLastPlayed = System.currentTimeMillis();
}
}
}, 0L, ChimePlayer.TIMER_REFRESH_INTERVAL_MILLISECONDS);
}
/**
* Play the chime.
*/
void playChime()
{
synchronized (this.actions)
{
this.actions.add(ActionType.CHIME);
}
}
/**
* Test the chime.
*/
void testChime()
{
synchronized (this.actions)
{
this.actions.add(ActionType.TEST_CHIME);
}
}
/**
* Test the periodic bell.
*/
void testPeriodicBell()
{
synchronized (this.actions)
{
this.actions.add(ActionType.TEST_PERIODIC_BELL);
}
}
/**
* Cancel all pending chimes.
*/
void cancelAll()
{
synchronized (this.actions)
{
this.actions.clear();
}
}
/**
* Start the periodic bell.
*/
void startPeriodicBell()
{
this.periodicBellPlay = true;
}
/**
* Stop the periodic bell.
*/
void stopPeriodicBell()
{
this.periodicBellPlay = false;
}
/**
* Terminate the timer thread.
* This method blocks until the thread is cancelled.
*/
void terminate()
{
synchronized (this.actions)
{
this.actions.clear();
}
do
{
synchronized (this.actions)
{
this.actions.add(ActionType.TERMINATE);
}
if (this.timerCancelled)
{
return;
}
try
{
Thread.sleep(ChimePlayer.TIMER_REFRESH_INTERVAL_MILLISECONDS / 10);
}
catch (Exception e)
{
/* ignore */
}
}
while (true);
}
/**
* Play the specified audio clip, and wait for it to finish.
*
* @param c
* audio clip to be played
*/
void playClipAndWait(
final Clip c)
{
if (c != null)
{
/* rewind the clip and start playing */
c.setFramePosition(0);
c.start();
while (c.isActive())
{
try
{
Thread.sleep(ChimePlayer.TIMER_REFRESH_INTERVAL_MILLISECONDS / 10);
}
catch (Exception e)
{
/* ignore */
}
}
}
}
/*****************
* INNER CLASSES *
*****************/
/**
* Types of action.
*/
private enum ActionType
{
CHIME,
TEST_CHIME,
TEST_PERIODIC_BELL,
TERMINATE
}
}