Recipe 13.16 Program: A Sound Controller Component
This example program
implements a sound controller
component that creates a controller for the playback of sounds. The
entire component is created using ActionScript, including the drawing
of the buttons and sliders.
In this example, you'll learn how to create,
package, install, and use the component.
To create the component, do the following:
Open a new Flash document and save it as Sound
Controller.fla. Normally, it is not good practice to
include spaces in the names of your Flash documents. However,
components that are installed using the Extensions Manager show up in
the Components panel under a menu selection with the same name as the
.fla from which the components were installed.
So we want to use a readable, user-friendly name. Create a new movie clip symbol named Slider.
This movie clip will be a component used by the sound controller
component. It creates a slider controller. Although it is usually considered a best practice not to attach code
directly to a symbol, creating a reusable component mandates an
exception to the rule. Therefore, attach the following code directly
to the first frame of the Slider symbol: #initclip
// Include the .as files for getNewDepth( ), drawRectangle( ), and drawCircle( ).
// MoveClip.as is from Chapter 7, and DrawingMethods.as is from Chapter 4.
#include "MovieClip.as"
#include "DrawingMethods.as"
// The constructor does not need to do anything, but it does need to exist.
function SliderClass( ) {}
// The SliderClass class is a subclass of MovieClip.
SliderClass.prototype = new MovieClip( );
/*
The create( ) method draws the slider and adds its functionality.
Parameters:
size - The length of the slider in pixels
fill - true applies a fill to the slider and false does not
fillColor - A color for the fill, in 0xRRGGBB format (applies if fill is
true)
horizontal - true creates a horizontal slider (otherwise vertical)
*/
SliderClass.prototype.create = function (size, fill, fillColor, horizontal) {
this.size = size;
// Create the track_mc movie clip to act as the path along which the slider can
// be moved. Draw a rectangle at the specified size (minus two because the
// drawing API adds on a pixel at either end).
this.createEmptyMovieClip("track_mc", this.getNewDepth( ));
this.track_mc.lineStyle(1, 0x000000, 100);
this.track_mc.drawRectangle(size - 2, 3, 0, 0, size/2);
// If fill is true, create a trackFill_mc movie clip and draw a filled
// rectangle, filled with the color specified by fillColor.
if (fill) {
this.createEmptyMovieClip("trackFill_mc", this.getNewDepth( ));
this.trackFill_mc.lineStyle(0, 0x000000, 0);
this.trackFill_mc.beginFill(fillColor, 100);
this.trackFill_mc.drawRectangle(size - 2, 3, 0, 0, size/2);
this.trackFill_mc.endFill( );
}
// Create a thumbSlide_mc clip to be moved by the user along the track_mc clip.
// Use built-in and custom drawing methods to create a white circle.
this.createEmptyMovieClip("thumbSlide_mc", this.getNewDepth( ));
this.thumbSlide_mc.lineStyle(1, 0x000000, 100);
this.thumbSlide_mc.beginFill(0xFFFFFF, 100);
this.thumbSlide_mc.drawCircle(5);
this.thumbSlide_mc.endFill( );
// Allow the thumbSlide_mc clip to be dragged between 0 and the size of the
// track while the mouse is depressed.
this.thumbSlide_mc.onPress = function ( ) {
this._parent.isDragging = true;
this.startDrag(false, 0, 0, this._parent.size, 0);
};
this.thumbSlide_mc.onRelease = function ( ) {
this._parent.isDragging = false;
this.stopDrag( );
// Search for and invoke a function, on the same timeline as the slider
// instance, with the name that was passed to the setOnRelease( ) method.
this._parent._parent[this.onReleaseFunctionName]( );
};
// If horizontal is not true, rotate the clip to the vertical.
if (!horizontal) {
this._rotation = -90;
}
};
// The setOnReleaes( ) method allows for a callback function
// to be automatically called when the thumb slide is released.
// The method is passed a function name as a string. The function
// must reside on the same timeline as the slider instance.
SliderClass.prototype.setOnRelease = function (functionName) {
this.thumbSlide_mc.onReleaseFunctionName = functionName;
};
// Set the position of the thumb slide from 0 to 100%.
SliderClass.prototype.setPosition = function (percent) {
this.thumbSlide_mc._x = (this.size * percent) / 100;
};
// Get the percentage (0 to 100) to which the
// current thumb slide position corresponds.
SliderClass.prototype.getPercent = function ( ) {
return (this.thumbSlide_mc._x / this.size) * 100;
};
// Register the class to the linkage identifier name of SliderSymbol.
Object.registerClass("SliderSymbol", SliderClass);
#endinitclip Open the Linkage properties
of the Slider
symbol using the Library panel's pop-up Options
menu. Select the Export for ActionScript and Export in First Frame
checkboxes and specify a symbol linkage identifier of
SliderSymbol. Click OK. Create a new Font symbol by choosing New Font from the Library
panel's pop-up Options menu. In the Font Symbol Properties dialog box, enter
Verdana in the Name field and choose Verdana from
the drop-down Font list. Leave the checkboxes unchecked. Click OK. Select the Verdana font symbol in the Library and open its linkage
properties, again using the Library panel's pop-up
Options menu. Select the Export for ActionScript and Export in First
Frame checkboxes and specify a symbol linkage identifier of
VerdanaFontSymbol. Click OK. Create a new movie clip symbol named Sound
Controller using Insert New Symbol.
This is the main sound controller component. Again, following the recommended approach to create reusable
components, edit the Sound Controller symbol and
attach the following code directly to its first frame: #initclip
// Include the custom Table class from Chapter 11 and the custom Date class
// methods from Chapter 10.
#include "Table.as"
#include "Date.as"
// The SoundControllerClass constructor
function SoundControllerClass( ) {
// The creation of the buttons, sliders, and text fields is divided among three
// methods that are defined later in this class. Call these methods here to
// create the controller elements.
this.createButtons( );
this.createSliders( );
this.createTextFields( );
// Create a table that positions the elements of the controller using custom
// methods from our Table class, which is defined in Chapter 11.
var tc0 = new TableColumn(5, this.rwBtn);
var tc1 = new TableColumn(5, this.playBtn);
var tc2 = new TableColumn(5, this.pauseBtn);
var tc3 = new TableColumn(5, this.ffBtn);
var tc4 = new TableColumn(5, this.volumeBtn);
var tc5 = new TableColumn(5, this.positionSlider);
var tr0 = new TableRow(2, tc0, tc1, tc2, tc3, tc4, tc5);
var t = new Table(5, tc0._width/2, this.volumeSlider._height, tr0);
// The volumeSlider and time elements overlay other parts of the controller, so
// they must be positioned without the use of the table.
this.volumeSlider._x = this.volumeBtn._x;
this.volumeSlider._y = this.volumeBtn._y;
this.time._y += this.positionSlider._y;
this.time._x = this.positionSlider._x + (this.positionSlider._width/2) -
(this.time._width/2);
}
// The SoundControllerClass class is a subclass of MovieClip.
SoundControllerClass.prototype = new MovieClip( );
SoundControllerClass.prototype.createButtons = function ( ) {
// The makeButton( ) method is defined later in this class. It creates a movie
// clip of the specified name to use as a button. "Btn" is appended to the
// specified name, so the first clip created here is named rwBtn.
this.makeButton("rw");
this.makeButton("play");
this.makeButton("pause");
this.makeButton("ff");
this.makeButton("volume");
// When the play button is clicked, call the controller's start( ) method.
this.playBtn.onRelease = function ( ) {
this._parent.start( );
};
// When the pause button is clicked, call the stop( ) method of the Sound
// object that the controller targets.
this.pauseBtn.onRelease = function ( ) {
this._parent.snd.stop( );
};
// The fast-forward and rewind buttons work similarly. When they are pressed,
// an interval is defined that continually increments or decrements the
// currentPosition property of the controller and then tells the controller to
// begin playing the sound. This creates a fast-forward or rewind effect. When
// the buttons are released, the interval is deleted to stop the action.
this.ffBtn.onPress = function ( ) {
this.ffInterval = setInterval(this._parent, "fastForward", 100);
};
this.ffBtn.onRelease = function ( ) {
clearInterval(this.ffInterval);
};
this.rwBtn.onPress = function ( ) {
this.rwInterval = setInterval(this._parent, "rewind", 100);
};
this.rwBtn.onRelease = function ( ) {
clearInterval(this.rwInterval);
};
};
SoundControllerClass.prototype.fastForward = function ( ) {
// Fast-forward 500 milliseconds at a time
this.currentPosition += 500;
this.start( );
};
SoundControllerClass.prototype.rewind = function ( ) {
// Rewind 500 milliseconds at a time
this.currentPosition -= 500;
this.start( );
};
SoundControllerClass.prototype.createSliders = function ( ) {
// Create an instance of the slider component to control the volume. This
// slider is 75 pixels high, and the initial volume position is 100%.
this.attachMovie("SliderSymbol", "volumeSlider", this.getNewDepth( ));
this.volumeSlider.create(75);
this.volumeSlider.setPosition(100);
// The volume slider is invisible until the volume button is pressed.
this.volumeSlider._visible = false;
// When the volume button is pressed, make the button semi-transparent and make
// the volume slider visible.
this.volumeBtn.onPress = function ( ) {
this._alpha = 42;
this._parent.volumeSlider._visible = true;
};
// Set the onRelease callback for the volume slider to the volumeOnRelease( )
// method defined within this class.
this.volumeSlider.setOnRelease("volumeOnRelease");
// Create the position slider. This slider controls the playback of the sound.
// Make the slider 150 pixels wide and give it a blue fill, which is used to
// display the load progress when a sound is loaded from an external MP3.
this.attachMovie("SliderSymbol", "positionSlider", this.getNewDepth( ));
this.positionSlider.create(150, true, 0x0000FF, true);
// Set the onRelease callback for the volume slider to the positionOnRelease( )
// method defined within this class.
this.positionSlider.setOnRelease("positionOnRelease");
};
// This method is called automatically when the volume slider is released. It
// sets the volume slider to be invisible, makes the volume button fully opaque,
// and sets the volume of the sound appropriately.
SoundControllerClass.prototype.volumeOnRelease = function ( ) {
this.volumeSlider._visible = false;
this.volumeBtn._alpha = 100;
this.snd.setVolume(this.volumeSlider.getPercent( ));
};
// This method is called automatically when the position slider is released. It
// sets the current position within the sound based on the slider position.
SoundControllerClass.prototype.positionOnRelease = function ( ) {
this.moveSoundPosition(this.positionSlider.getPercent( )/100);
};
SoundControllerClass.prototype.createTextFields = function ( ) {
// This creates a text field to contain the time value for the sound in the
// format of "current position/total duration". For the sound controller to be
// semi-transparent, the text field must use an embedded font.
this.createAutoTextField("time", this.getNewDepth( ), 0, 0, 0, 0);
var tf = new TextFormat( );
tf.font = "VerdanaFontSymbol";
tf.size = 9;
this.time.embedFonts = true;
this.time.setNewTextFormat(tf);
this.time.text = "00:00 / 00:00";
this.time.selectable = false;
};
// This method starts the sound.
SoundControllerClass.prototype.start = function ( ) {
// If the sound is already playing, stop it.
this.snd.stop( );
// Start the sound at the position that is currently stored in the controller.
// This enables a sound to be paused and then started again from the same
// position. Sound.start( ) accepts the start position in seconds, but the
// current position is stored in milliseconds, so divide by 1000.
this.snd.start(this.currentPosition/1000);
// Call the monitorPosition( ) method (defined later in this class) to begin
// watching the position of the sound.
this.monitorPosition( );
};
// This method stops a sound, as opposed to just pausing it. It stops the sound
// from playing, stops monitoring the sound position, and resets the current
// position to 0.
SoundControllerClass.prototype.stop = function ( ) {
this.snd.stop( );
clearInterval(this.monitorPositionInterval);
this.currentPosition = 0;
};
// The moveSoundPosition( ) method starts playing a sound at a new point
// determined by ratio, which should be between 0 and 1.
SoundControllerClass.prototype.moveSoundPosition = function (ratio) {
this.snd.stop( );
this.snd.start((ratio * this.snd.duration)/ 1000);
};
// The setTarget( ) method sets the sound to be controlled by the component. If
// autoPlay is true, the sound plays automatically once it is loaded.
SoundControllerClass.prototype.setTarget = function (snd, autoPlay) {
this.snd = snd;
// If the sound is loaded and autoPlay is true, start the sound.
// Otherwise, if the sound is not loaded, call monitorLoad( )
// to watch the sound until it is loaded.
if (snd.isLoaded && autoPlay) {
this.start( );
} else if (!snd.isLoaded) {
this.monitorLoad(autoPlay);
}
};
// The setPositionSlider( ) method sets the position of the slider to correspond
// to the position of the playback of the sound.
SoundControllerClass.prototype.setPositionSlider = function ( ) {
// If the slider is not being dragged, set _x of the position slider's slide.
if (!this.positionSlider.isDragging) {
var positionRatio = this.snd.position / this.snd.duration;
this.positionSlider.slide._x = positionRatio * this.positionSlider.size;
}
};
// Monitor the sound position, and update the position slider and time display.
SoundControllerClass.prototype.monitorPosition = function ( ) {
this.monitorPositionInterval = setInterval(this,
"monitorPositionActions", 100);
};
SoundControllerClass.prototype.monitorPositionActions = function ( ) {
var pos = this.snd.position;
var dur = this.snd.duration;
// Set the time display.
this.time.text = Date.formatMilliseconds(pos) + " / " +
Date.formatMilliseconds(dur);
// Set the currentPosition property of the sound controller and set the
// position of the position slider.
this.currentPosition = pos;
this.setPositionSlider( );
// If the position is greater than or equal to the duration of the sound, set
// the current position back to 0.
if (pos >= dur) {
this._parent.currentPosition = 0;
this.positionSlider.setPosition (0);
}
};
// The monitorLoad( ) method monitors the load progress of a
// sound loaded from an external MP3.
SoundControllerClass.prototype.monitorLoad = function (autoPlay) {
this.autoPlay = autoPlay;
this.monitorLoadInterval = setInterval(this, "monitorLoadActions", 100);
};
SoundControllerClass.prototype.monitorLoadActions = function ( ) {
var pLded = this.snd.percentLoaded;
// Display the percentage of the sound that has loaded using a fill.
this.positionSlider.trackFill_mc._xscale = pLded;
// Once the sound finishes loading, play it if autoPlay is true.
if (this.snd.isLoaded) {
if (this.autoPlay) {
this.start( );
}
clearInterval(this.monitorLoadInterval);
}
};
// The makeButton( ) method is attached to the MovieClip class so that it can be
// called from a movie clip such as the sound controller. It automatically
// generates a new movie clip of the name nameBtn.
MovieClip.prototype.makeButton = function (name) {
this.createEmptyMovieClip(name + "Btn", this.getNewDepth( ));
// The button is composed of two parts: a circle created with drawButton( ) and
// a symbol, such as an arrow, created with drawSymbol( ).
this[name + "Btn"].drawButton( );
this[name + "Btn"].drawSymbol(name);
};
// The drawButton( ) method draws a filled and outlined circle within a clip.
MovieClip.prototype.drawButton = function ( ) {
this.createEmptyMovieClip("btn", this.getNewDepth( ));
this.btn.lineStyle(0, 0x000000, 100);
this.btn.beginFill(0xFFFFFF, 100);
this.btn.drawCircle(10);
this.btn.endFill( );
};
// The drawSymbol( ) method draws the requested shape within a clip.
MovieClip.prototype.drawSymbol = function (shape) {
this.createEmptyMovieClip("symbol", this.getNewDepth( ));
this.symbol.lineStyle(0, 0x000000, 100);
this.symbol.beginFill(0x000000, 100);
// Use a switch statement to determine the shape to draw (play, rewind, etc.).
switch (shape) {
case "play":
this.symbol.drawTriangle(10, 10, 60);
this.symbol.endFill( );
this.symbol._rotation = 30;
break;
case "pause":
this.symbol.drawRectangle(9, 3);
this.symbol.drawRectangle(9, 3, 0, 0, 0, 6);
this.symbol.endFill( );
this.symbol._rotation = 90;
this.symbol._x += 3;
break;
case "ff":
this.symbol.drawTriangle(7, 7, 60, 30);
this.symbol.drawTriangle(7, 7, 60, 30, 6);
this.symbol.endFill( );
this.symbol._rotation = 180;
this.symbol._x += 3;
break;
case "rw":
this.symbol.drawTriangle(7, 7, 60, 30);
this.symbol.drawTriangle(7, 7, 60, 30, 6);
this.symbol.endFill( );
this.symbol._x -= 3;
break;
case "volume":
this.symbol.drawrectangle(3, 5);
this.symbol.drawTriangle(9, 9, 60, 30, 4);
this.symbol.endFill( );
this.symbol.moveTo(8, 3);
this.symbol.curveTo(10, 0, 8, -3);
this.symbol.moveTo(8, 6);
this.symbol.curveTo(15, 0, 8, -6);
this.symbol._x -= 5;
}
this.symbol.endFill( );
};
Object.registerClass("SoundControllerSymbol", SoundControllerClass);
#endinitclip Drag an instance of the Slider symbol onto the
Stage within the Sound Controller component
symbol. The Slider symbol is never used, and it
does not have a visual appearance within the component. The purpose
of this is to establish a nested relationship between the two symbols
so that when the Sound Controller component is
copied into a movie's Library, the
Slider symbol is as well. Open the Linkage properties of the Sound
Controller symbol using the Library
panel's pop-up Options menu. Select the Export for
ActionScript and Export in First Frame checkboxes and specify a
symbol linkage identifier of
SoundControllerSymbol. Click OK. Select the Sound Controller symbol in the
Library and choose Component Definition from the pop-up Options menu.
Select the Display in Components Panel checkbox and click OK. Save the Sound Controller.fla document and close
it. Open a new text document and add the following code to it: <macromedia-extension name="Sound Controller Component" version="1.0.0"
type="flash component">
<description>
<![CDATA[The sound controller component]]>
</description>
<ui-access>
<![CDATA[This component is installed in the Components panel]]>
</ui-access>
<author name="Your Name" />
<products>
<product name="Flash" version="6" required="true" />
</products>
<files>
<file source="Sound Controller.fla" destination="$flash/components/" />
</files>
</macromedia-extension> Save this document as soundController.mxi in the
same directory as the Sound Controller.fla
document. Open the Macromedia Extensions Manager (using Help
Manage Extensions) and choose the Package Extension option from the
Extension Manager's File menu. Browse to and select the
soundController.mxi
document in the window that appears. Click OK. Save the package as soundController.mxp. Once you have successfully completed the packaging of the component,
choose Install Extension from the Extension
Manager's File menu, and choose the
soundController.mxp file as the package to
install. The component should successfully install. Once this is done, close
Flash if it is open. Open Flash, and open a new document within the application. Open the Components panel and select Sound Controller from the
drop-down menu. Copy an instance of the Sound Controller component into the new Flash
movie's Library by dragging an instance onto the
Stage (after doing so, you can delete the instance from the Stage). Open the Library and make sure it shows three symbols: Sound
Controller, Slider, and Verdana. On the first frame of the main timeline, add the following code: #include "Sound.as"
// Create the new Sound object and load a sound into it from an external source.
// (See Recipe 15.5 for more information about loading external MP3s.) Replace
// urlToMp3 with a valid URL to an MP3 for the sound controller to play.
mySound_sound = Sound.createNewSound( );
mySound_sound.loadSound(urlToMp3);
// Create an instance of the sound controller and set its target to mySound_sound
// with autoPlay set to true.
_root.attachMovie("SoundControllerSymbol", "sc", 1);
sc.setTarget(mySound_sound, true);
13.16.1 See Also
Recipe 15.5 and Chapter 26
|