/**
* GmailAssistant 2.0 (2008-09-07)
* Copyright 2008 Zach Scrivena
* zachscrivena@gmail.com
* http://gmailassistant.sourceforge.net/
*
* Notifier for multiple Gmail and Google Apps email 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 org.freeshell.zs.gmailassistant;
import java.util.ArrayDeque;
import java.util.Queue;
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 org.freeshell.zs.common.Debug;
import org.freeshell.zs.common.SwingManipulator;
/**
* Play an audible chime and periodic bell.
*/
class ChimePlayer
{
/** refresh interval in milliseconds */
private static final long REFRESH_INTERVAL_MILLISECONDS = 200L;
/** chime audio clip */
private Clip chime = null;
/** periodic bell audio clip */
private Clip periodicBell = null;
/** 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(parent.properties.getString("alert.chime.audio.clip")));
final AudioFormat format = stream.getFormat();
chime = (Clip) AudioSystem.getLine(new DataLine.Info(
Clip.class,
format,
(int) (stream.getFrameLength() * format.getFrameSize())));
chime.open(stream);
}
catch (Exception e)
{
chime = null;
SwingManipulator.showErrorDialog(
parent,
String.format("Initialization Error - %s", parent.name),
String.format("Failed to load chime audio clip (%s).\n" +
"%s will proceed to run without playing the chime audio clip.",
e.toString(), parent.name));
}
/* load periodic bell audio clip */
try
{
final AudioInputStream stream = AudioSystem.getAudioInputStream(
ChimePlayer.class.getResource(parent.properties.getString("alert.periodic.bell.audio.clip")));
final AudioFormat format = stream.getFormat();
periodicBell = (Clip) AudioSystem.getLine(new DataLine.Info(
Clip.class,
format,
(int) (stream.getFrameLength() * format.getFrameSize())));
periodicBell.open(stream);
}
catch (Exception e)
{
periodicBell = null;
SwingManipulator.showErrorDialog(
parent,
String.format("Initialization Error - %s", parent.name),
String.format("Failed to load periodic bell audio clip (%s).\n" +
"%s will proceed to run without playing the periodic bell audio clip.",
e.toString(), parent.name));
}
/************************************
* INITIALIZE THREAD TO PLAY CHIMES *
************************************/
new Thread(new Runnable()
{
@Override
public void run()
{
while (true)
{
final ActionType action;
synchronized (actions)
{
action = actions.poll();
}
if (action == ActionType.CHIME)
{
playClipAndWait(chime);
periodicBellLastPlayed = System.currentTimeMillis();
}
else if (action == ActionType.TEST_CHIME)
{
playClipAndWait(chime);
}
else if (action == ActionType.TEST_PERIODIC_BELL)
{
playClipAndWait(periodicBell);
}
else if (action == ActionType.TERMINATE)
{
return;
}
/* play periodic bell, if necessary */
if (periodicBellPlay &&
((System.currentTimeMillis() - periodicBellLastPlayed) >= parent.properties.getLong("alert.periodic.bell.interval.milliseconds")))
{
playClipAndWait(periodicBell);
periodicBellLastPlayed = System.currentTimeMillis();
}
Debug.sleep(REFRESH_INTERVAL_MILLISECONDS);
}
}
}).start();
}
/**
* Play the chime.
*/
void playChime()
{
synchronized (actions)
{
actions.add(ActionType.CHIME);
}
}
/**
* Test the chime.
*/
void testChime()
{
synchronized (actions)
{
actions.add(ActionType.TEST_CHIME);
}
}
/**
* Test the periodic bell.
*/
void testPeriodicBell()
{
synchronized (actions)
{
actions.add(ActionType.TEST_PERIODIC_BELL);
}
}
/**
* Cancel all pending chimes.
*/
void cancelAll()
{
synchronized (actions)
{
actions.clear();
}
}
/**
* Start the periodic bell.
*/
void startPeriodicBell()
{
periodicBellPlay = true;
}
/**
* Stop the periodic bell.
*/
void stopPeriodicBell()
{
periodicBellPlay = false;
}
/**
* Terminate the thread that plays chimes.
*/
void terminate()
{
synchronized (actions)
{
actions.clear();
actions.add(ActionType.TERMINATE);
}
}
/**
* Play the specified audio clip, and wait for it to finish.
*
* @param c
* audio clip to be played
*/
private static void playClipAndWait(
final Clip c)
{
if (c == null)
{
return;
}
/* rewind the clip and start playing */
c.setFramePosition(0);
c.start();
while (c.isActive())
{
Debug.sleep(REFRESH_INTERVAL_MILLISECONDS / 10);
}
}
/******************
* NESTED CLASSES *
******************/
/**
* Types of action.
*/
private enum ActionType
{
CHIME,
TEST_CHIME,
TEST_PERIODIC_BELL,
TERMINATE
}
}