526db3bb05
git-svn-id: svn+ssh://src.earth.threerings.net/nenya/trunk@711 ed5b42cb-e716-0410-a449-f6a68f950b19
247 lines
6.0 KiB
ActionScript
247 lines
6.0 KiB
ActionScript
//
|
|
// $Id: SoundPlayer.as 10500 2008-08-07 19:14:38Z mdb $
|
|
|
|
package com.threerings.flash.media {
|
|
|
|
import flash.errors.IOError;
|
|
|
|
import flash.events.Event;
|
|
import flash.events.EventDispatcher;
|
|
import flash.events.IOErrorEvent;
|
|
import flash.events.ProgressEvent;
|
|
import flash.events.TimerEvent;
|
|
|
|
import flash.media.Sound;
|
|
import flash.media.SoundChannel;
|
|
import flash.media.SoundLoaderContext;
|
|
import flash.media.SoundTransform;
|
|
|
|
import flash.net.URLRequest;
|
|
|
|
import flash.utils.Timer;
|
|
|
|
import com.threerings.util.Log;
|
|
import com.threerings.util.ValueEvent;
|
|
|
|
public class Mp3AudioPlayer extends EventDispatcher
|
|
implements AudioPlayer
|
|
{
|
|
public function Mp3AudioPlayer (loop :Boolean = false)
|
|
{
|
|
_loop = loop;
|
|
|
|
_positionChecker = new Timer(250);
|
|
_positionChecker.addEventListener(TimerEvent.TIMER, handlePositionCheck);
|
|
}
|
|
|
|
/**
|
|
* Load and immediately start playing some audio!
|
|
*/
|
|
public function load (url :String) :void
|
|
{
|
|
_isComplete = false;
|
|
_lastPosition = 0;
|
|
_sound = new Sound();
|
|
_sound.addEventListener(Event.ID3, handleId3);
|
|
_sound.addEventListener(IOErrorEvent.IO_ERROR, handleError);
|
|
_sound.addEventListener(Event.COMPLETE, handleLoadingComplete);
|
|
_sound.load(new URLRequest(url), new SoundLoaderContext(1000, true));
|
|
play();
|
|
}
|
|
|
|
// from AudioPlayer
|
|
public function getState () :int
|
|
{
|
|
return _state;
|
|
}
|
|
|
|
// from AudioPlayer
|
|
public function play () :void
|
|
{
|
|
play0();
|
|
updateState(
|
|
(_chan != null) ? MediaPlayerCodes.STATE_PLAYING : MediaPlayerCodes.STATE_PAUSED);
|
|
}
|
|
|
|
// from AudioPlayer
|
|
public function pause () :void
|
|
{
|
|
handlePositionCheck(); // update _lastPosition and dispatch an event
|
|
pause0();
|
|
updateState(MediaPlayerCodes.STATE_PAUSED);
|
|
}
|
|
|
|
// from AudioPlayer
|
|
public function getDuration () :Number
|
|
{
|
|
if (_sound == null) {
|
|
return NaN;
|
|
}
|
|
return _sound.length / 1000;
|
|
}
|
|
|
|
// from AudioPlayer
|
|
public function getPosition () :Number
|
|
{
|
|
if (_chan == null) {
|
|
return _lastPosition;
|
|
}
|
|
// we mod by the duration because when we loop the position is ever-growing. hahah!
|
|
return (_chan.position / 1000);
|
|
}
|
|
|
|
// from AudioPlayer
|
|
public function seek (position :Number) :void
|
|
{
|
|
_lastPosition = position;
|
|
if (_state == MediaPlayerCodes.STATE_PLAYING) {
|
|
pause0();
|
|
play0();
|
|
|
|
} else {
|
|
handlePositionCheck();
|
|
}
|
|
}
|
|
|
|
// from AudioPlayer
|
|
public function setVolume (volume :Number) :void
|
|
{
|
|
_volume = Math.max(0, Math.min(1, volume));
|
|
if (_chan != null) {
|
|
_chan.soundTransform = new SoundTransform(_volume);
|
|
}
|
|
}
|
|
|
|
// from AudioPlayer
|
|
public function getVolume () :Number
|
|
{
|
|
return _volume;
|
|
}
|
|
|
|
// from AudioPlayer
|
|
public function getMetadata () :Object
|
|
{
|
|
if (_sound != null) {
|
|
try {
|
|
return _sound.id3;
|
|
} catch (err :SecurityError) {
|
|
Log.getLog(this).warning("Can't read id3 data", err);
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
// from AudioPlayer
|
|
public function unload () :void
|
|
{
|
|
pause0();
|
|
if (_sound != null) {
|
|
try {
|
|
_sound.close();
|
|
} catch (err :IOError) {
|
|
// ignore
|
|
}
|
|
}
|
|
_positionChecker.reset();
|
|
_lastPosition = NaN;
|
|
}
|
|
|
|
/**
|
|
* Play without updating the current state.
|
|
*/
|
|
protected function play0 () :void
|
|
{
|
|
pause0();
|
|
_chan = _sound.play(_lastPosition * 1000, 0, new SoundTransform(_volume));
|
|
if (_chan != null) {
|
|
_chan.addEventListener(Event.SOUND_COMPLETE, handlePlaybackComplete);
|
|
|
|
} else {
|
|
Log.getLog(this).warning("All sound channels are in use; " +
|
|
"unable to play sound.");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Pause without saving the position or updating the state.
|
|
*/
|
|
protected function pause0 () :void
|
|
{
|
|
if (_chan != null) {
|
|
_chan.stop();
|
|
_chan = null;
|
|
}
|
|
}
|
|
|
|
protected function handlePositionCheck (event :TimerEvent = null) :void
|
|
{
|
|
if (!_isComplete) {
|
|
dispatchEvent(new ValueEvent(MediaPlayerCodes.DURATION, getDuration()));
|
|
}
|
|
_lastPosition = getPosition();
|
|
dispatchEvent(new ValueEvent(MediaPlayerCodes.POSITION, _lastPosition));
|
|
}
|
|
|
|
protected function updateState (newState :int) :void
|
|
{
|
|
_state = newState;
|
|
|
|
if (_state == MediaPlayerCodes.STATE_PLAYING) {
|
|
_positionChecker.start();
|
|
|
|
} else {
|
|
_positionChecker.reset();
|
|
// pause() ends up checking the position one last time before dispatching state..
|
|
}
|
|
|
|
dispatchEvent(new ValueEvent(MediaPlayerCodes.STATE, newState));
|
|
}
|
|
|
|
protected function handleId3 (event :Event) :void
|
|
{
|
|
dispatchEvent(new ValueEvent(MediaPlayerCodes.METADATA, getMetadata()));
|
|
}
|
|
|
|
/**
|
|
*/
|
|
protected function handleLoadingComplete (event :Event) :void
|
|
{
|
|
handlePositionCheck(); // also updates duration
|
|
_isComplete = true;
|
|
}
|
|
|
|
protected function handlePlaybackComplete (event :Event) :void
|
|
{
|
|
_lastPosition = 0;
|
|
pause0();
|
|
handlePositionCheck();
|
|
if (_loop) {
|
|
play0();
|
|
} else {
|
|
updateState(MediaPlayerCodes.STATE_PAUSED);
|
|
}
|
|
}
|
|
|
|
protected function handleError (event :IOErrorEvent) :void
|
|
{
|
|
dispatchEvent(new ValueEvent(MediaPlayerCodes.ERROR, event.text));
|
|
}
|
|
|
|
protected var _sound :Sound;
|
|
|
|
protected var _chan :SoundChannel;
|
|
|
|
protected var _loop :Boolean;
|
|
|
|
protected var _state :int = MediaPlayerCodes.STATE_UNREADY;
|
|
|
|
protected var _volume :Number = 1;
|
|
|
|
protected var _isComplete :Boolean;
|
|
|
|
protected var _lastPosition :Number = NaN;
|
|
|
|
protected var _positionChecker :Timer;
|
|
}
|
|
}
|