Why doesn't SOUND_COMPLETE event fired when starting playback from sound.length position? - actionscript-3

I realized that Event.SOUND_COMPLETE event isn't fired when starting playback from the sound.length position (it can be used when implementing scrubber: user can seek to 100% and start playing).
Is this bug? What is the common practice to workaround it?
Sample code reproducing this behavior:
import flash.media.Sound;
import flash.media.SoundChannel;
private var sound:Sound;
private var channel:SoundChannel;
public function test():void {
sound = new Sound();
sound.addEventListener(Event.COMPLETE, testLoadingCompleteHandler);
sound.load(new URLRequest("/song.mp3"));
}
private function testLoadingCompleteHandler(event:Event):void {
// SOUND_COMPLETE doesn't fire when we start playing from sound.length position
trace("# testSoundLoadingCompleteHandler()");
channel = sound.play(sound.length);
channel.addEventListener(Event.SOUND_COMPLETE, testPlaybackCompleteHandler);
}
private function testPlaybackCompleteHandler(event:Event):void {
// This message won't be printed because SOUND_COMPLETE event isn't fired
trace("# testPlaybackCompleteHandler()");
}

Maybe sound.play(sound.length) does not starts playback, or even returns error. I suppose it may be because sound.length is bigger than allowed value. Like in arrays you cannot use array[array.length], because it will be out of range.
I recomend triyng array.length-1

Related

How to STOP looping sound when going into next frame/Errors

I have a flash project broken up into multiple frames, with a button on each frame that goes to play the next frame. (And a movieclip on each frame that plays until you hit next frame button)
On each frame, I want audio to play, and loop.
But, I want the audio from one frame to stop when I click the button to go to the next.
On frame 4, I have this code:
import flash.media.SoundChannel;
var sound:Sound = new firt2();
var soundChannel:SoundChannel;
sound.addEventListener(Event.COMPLETE, onSoundLoadComplete);
sound.play();
function onSoundLoadComplete(e:Event):void{
sound.removeEventListener(Event.COMPLETE, onSoundLoadComplete);
soundChannel = sound.play();
soundChannel.addEventListener(Event.SOUND_COMPLETE, onSoundChannelSoundComplete);
}
function onSoundChannelSoundComplete(e:Event):void{
e.currentTarget.removeEventListener(Event.SOUND_COMPLETE, onSoundChannelSoundComplete);
}
And it works. However, I want to stop it once I click the button to go to the next frame. I have tried:
soundChannel.stop();
On the next frame.
However, whenever I do that, the output reads:
TypeError: Error #1009: Cannot access a property or method of a null object reference.
at hhh4_fla::MainTimeline/frame5()
at flash.display::MovieClip/gotoAndPlay()
at hhh4_fla::MainTimeline/fl_ClickToGoToAndPlayFromFrame()
All of my buttons and movieclip have instance names.
Rather than figuring why it doesn't work with all these frames and timelines, I think it's better to compose a centralized sound manager class that handles these things.
Implementation. Keep in mind that I didn't test that so please excuse me for occasional typo if any. The logic of it all should be correct.
package
{
import flash.system.ApplicationDomain;
import flash.media.SoundChannel;
import flash.media.Sound;
import flash.events.Event;
public class Audio
{
// Container to cache Sound objects.
static private const cache:Object = new Object;
// Variables to hold the current values.
static private var currentChannel:SoundChannel;
static private var currentSound:String;
// Stops the current sound playing. If you pass the sound name, it
// will stop the audio track only if it is the exact one playing.
// Otherwise it will stop any one currently playing.
static public function stop(value:String = null):void
{
// Do nothing if nothing is playing right now,
// or if the specific sound requested to stop does not match.
if (currentSound == null) return;
if (value) if (value != currentSound) return;
// Unsubscribe from event and stop the audio.
currentChannel.removeEventListener(Event.SOUND_COMPLETE, onComplete);
currentChannel.stop();
// Final clean-up.
currentChannel = null;
currentSound = null;
}
// Plays the embedded sound by its class name.
static public function play(value:String):void
{
// Do nothing if the requested sound is already playing.
if (value == currentSound) return;
// Stop the current audio track playing.
stop();
// Check if that one sound is valid and/or was previously requested.
if (!cache[value])
{
try
{
// Obtain class definition from the project.
var aClass:Class = ApplicationDomain.currentDomain.getDefinition(value) as Class;
// Try instantiating the Sound.
if (aClass) cache[value] = new aClass as Sound;
}
catch (fail:Error)
{
// Well, do nothing, yet.
}
}
if (cache[value])
{
// Store the id of audio track that is going to be playing.
currentSound = value;
// Play the track and subscribe to it for the SOUND_COMPLETE event.
currentChannel = (cache[value] as Sound).play();
currentChannel.addEventListener(Event.SOUND_COMPLETE, onComplete);
}
else
{
// If there's no such class, or it is not a Sound,
trace("ERROR: there's no sound <<" + value + ">> is embedded into the project.");
}
}
// Event handler to clean up once the current audio track is complete.
static private function onComplete(e:Event):void
{
// Sanity check.
if (e.target != currentChannel) return;
stop();
}
}
}
Usage.
import Audio;
// Any time you want different sound to play.
// Pass the class name as Sting as an argument.
Audio.play("firt2");
// Any time you just want to stop the sound;
Audio.stop();

How to stop sound in the middle of playing

I'm trying to write a bit of code that plays a sound while a buttons pressed however if the button has been pressed and the sound is playing then the sound is paused and played again rather then just playing and overlapping.
this is what I have
var sound:alarm = new alarm();
var isPlaying:Boolean = false;
public function Main()
{
button.addEventListener(MouseEvent.CLICK,playSound);
}
public function playSound(e:Event):void
{
if(isPlaying)sound.stop();
sound.play();
isPlaying=true;
}
at first glance It seemed to have worked but then I saw the following in my output
TypeError: Error #1006: stop is not a function.
at Main/playSound()
TypeError: Error #1006: stop is not a function.
at Main/playSound()
so apparently it works although stop is not a method of the Sound class. what would be the proper way of implementing this? Also I've been wondering if there is a more proper condition I can use, because with this code sound.stop() is called every time the function is entered after the first button click, is there a method that allows me to check in real time whether or not a sound is playing?
In your code, the function playSound(e:Event) should be playSound(e:MouseEvent);Also your right stop() is not a method of the Sound class, however your not using the Sound class, your using the alarm class (unless the alarm class extends the Sound class).On another note, I searched google and this popped up, Flash Play/Pause Sound
Update:
import flash.media.SoundChannel;
// Make sure to import the SoundChannel class
var sc:SoundChannel = new SoundChannel();
var sound:Sound = new alarm();
var isPlaying:Boolean = false;
var pausePos:Number = 0;
public function Main()
{
button.addEventListener(MouseEvent.CLICK,playSound);
}
public function playSound(e:MouseEvent):void
{
if(isPlaying) {
pausePos = sc.position;
sc.stop();
isPlaying = false;
} else {
sc = sound.play(pausePos);
isPlaying = true;
}
}
This code should work, however I have not tested it so if any errors are given or the desired result is not met just let me know and I'll see what I can do.
Short answer...okay, entire answer from me :). Instead of using the sound object, try the SoundChannel object. It offers more options, including volume and balance control, and most prominently, stop.
Documentation should provide enough info for using it. It's relatively common.
http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/media/SoundChannel.html

AS3 SoundChannel Position of audio in function

I am having issues with variable scope. I know this must be an easy fix but I can't find it.
I need to play sound through a sound channel in a function. Then I need another function to read the sound channel position. But it is not seeing it because of the new var in the function that plays it. If I play it outside the function I can get the position from another function but if I beging the sound in the function then another function can not read the channel position. How do I fix this.
The Code
import flash.filesystem.File;
import flash.filesystem.FileMode;
import flash.filesystem.FileStream;
var lastPosition:Number = 0; var CrossAudio = new Sound();
CrossAudio.load(new URLRequest("Cross.mp3"));
function playCross() {
PapaAudioChannel = CrossAudio.play();
}
bt_play.addEventListener(MouseEvent.CLICK, playA);
function playA(event:MouseEvent):void {
trace(PapaAudioChannel.position); // Does not work
/// It can't doesn't recognize the PapaAudioChannel because
/// it began playing in another function.
}
How can I get the second function to see the position of the audio. I need to start the audio with a play and pause button so the play has to start in a function.
Thanks
AFAIK the sound can start with a little delay, so tracing sound position just after a play() statement is not a good idea.
EDIT
By the way, where is PapaAudioChannel defined, if it is? (it should since this is AS3)
If you define a var in a function, it dies with that function, at the end of function!
So you may want to define PapaAudioChannel in the global scope:
import flash.filesystem.File;
import flash.filesystem.FileMode;
import flash.filesystem.FileStream;
var PapaAudioChannel:SoundChannel; // defines variable
var lastPosition:Number = 0; var CrossAudio = new Sound();
CrossAudio.load(new URLRequest("Cross.mp3"));
function playCross() {
PapaAudioChannel = CrossAudio.play();
}
bt_play.addEventListener(MouseEvent.CLICK, playA);
function playA(event:MouseEvent):void {
trace(PapaAudioChannel.position);
}

Sounds play delay wrong

I am writing simple metronome component using Flex + AS3. I want it playing 'tick1' sound after, for example, each 500 millisecond and each 4th time plays another sound 'tick2'. But actually delay between sound is not equivalent - sometimes lesser, sometimes greater a bit. I testing it on latest Chrome.
Here my code:
//Somewhere here button bound to the 'toggle' function
import flash.utils.Timer;
import flash.events.TimerEvent;
import flash.media.SoundTransform;
import flash.media.SoundChannel;
private var bpm:Number = 120; //2 bit per second, delay=500ms
private var period:Number = 4;
private var timer:Timer = new Timer(bpm, period);
[Embed(source='sounds/1.mp3')]
private var tickSound1Class:Class;
private var tickSound1:Sound;
[Embed(source='sounds/2.mp3')]
private var tickSound2Class:Class;
private var tickSound2:Sound;
private var trans:SoundTransform = new SoundTransform(1);
private function init():void {
....
tickSound1 = new tickSound1Class() as Sound;
tickSound2 = new tickSound2Class() as Sound;
update();
timer.addEventListener(TimerEvent.TIMER, onTimerEvent);
....
}
private function update():void {
timer.delay = 1000 * 60/bpm;
timer.repeatCount = 0;
}
private function toggle():void {
if (timer.running) {
timer.reset();
startStopButton.label = "Start";
} else {
update();
timer.start();
startStopButton.label = "Stop";
}
}
private function onTimerEvent(event:TimerEvent):void {
var t:Timer = event.currentTarget as Timer;
if (t.currentCount % period == 0)
tickSound1.play(0, 0, trans);
else
tickSound2.play(0, 0, trans);
}
I think there are two main reasons:
It is known that Timer object in Flash Player is inaccurate, delay between it's fires fluctuates.
Sound.play() method also introduces some delay before the sound actually starts to play, and theoretically this delay can fluctuate. The delay is especially noticeable in PPAPI version of Flash Player being used in Chrome.
There are several solutions. I would suggest one of these:
Use pre-composed sound of the whole metronome cycle (tick1-pause1-tick2-pause2) and just loop it using the second argument of Sound.play() method;
use dynamic sound generation.
The second option is more flexible but is more difficult to implement. Basicaly, you'll need to create a new instance of Sound object, subscribe to it's SAMPLE_DATA event and call it's play() method. In the handler you'll check event.position / 44.1, which will give you current position of sound generation in ms. Then, if you decide that it's time to play tick1 or tick2 sound, you'll call tickN.extract(event.data, ...), where tickN is tick1 or tick2 Sound object, or write the silence otherwise.
You can read more about dynamic sound generation here.
Also, notice that when you call Sound.play() it returns a SoundChannel object, which has the position property. Is it a position in ms of a sound that is being played (not generated) at the moment, and it is accurate. So, using this property, you can come up with the third approach: create a Sound object and setup a SAMPLE_DATA handler like in the dynamic sound generation solution, but write the silence (zeros) to the event.data object inside the handler all the time. This is needed to obtain a sound channel without actually playing a sound. Then, use high frame rate (60 FPS) and a Timer with the smallest possible delay (1 ms). Each time the Timer fires, check soundChannel.position to determine whether it is time to play the tick sound, and, if it so, just play it like you're doing in your example. This approach is likely to solve the problem of the Timer inaccuracy, but it cannot deal with the delay caused by tickSound.play() method.

AS3 playing audio and looping it

I'm trying to get an audio file to play in the background of my project but so far have been unsuccessful so far. Heres what I've gotten so far.
package {
import flash.display.*;
import flash.events.*;
import flash.net.*;
import flash.media.*;
public class Music extends MovieClip {
// Create a sound
private var music:Sound;
// Use a URLRequest for file path
private var request:URLRequest;
// Create a sound buffer
private var buffer:SoundLoaderContext;
// Create a sound channel
private var channel:SoundChannel;
public function Music() {
// constructor code
//Instantiate all sound objects
buffer = new SoundLoaderContext(1000);
request = new URLRequest("SMB3-Grassland-Map.mp3");
music = new Sound(request, buffer);
// Play music and assign it to a channel
channel = music.play();
// Add an event listener to the channel
channel.addEventListener(Event.SOUND_COMPLETE, loopMusic);
}
// playMusic method restarts music
private function playMusic()
{
channel = music.play();
channel.addEventListener(Event.SOUND_COMPLETE, loopMusic);
}
// Channel event listener call playMusic method
private function loopMusic(e:Event)
{
if (channel != null)
{
channel.removeEventListener(Event.SOUND_COMPLETE, loopMusic);
playMusic();
}
}
}
}
this is just to play an external audio file and have it continually loop.
Rather than having an event listener that plays the sound again upon sound complete, you can add parameters to the .play() method. The first parameter indicates at what point in the sound you would like it to begin playing, the second parameter indicates how many times you would like it to play, and the third is used if you want to apply sound transform. In your case you can do .play(0, int.MAX_VALUE); this will give you the continuous loop you are looking for.
"the problem is it's not playing anything at all."
I copied your code and created an AS3 class file (substituting my own local MP3 URL)
Then created an AS3 file with the following:
import Music;
var mus:Music = new Music();
and ran it. The sound played and looped correctly.
It's working for me, so maybe that's why there are no errors. Sorry for the question but are your speakers on; is your system playing sound from another source... cd, mp3 player?
I've used the play(0, 1000); #Goose mentioned and it worked great; simple and effective.