A State without the means of some change is without the means of its conservation.
All modern revolutions have ended in a reinforcement of the power of the State.
The State design pattern focuses on the different states in an application,
transitions between states, and the different behaviors within a state. Looking at a
simple light switch application, we can see two states, On and Off. In the Off
state, the light is not illuminated, and in the On state, the light illuminates.
Further, the light switch transitions from the Off state to the On state using a
method that changes the application’s state—flipping the switch. Likewise, it
transitions from On to Off with a different transition and method. An interface
holds the transitions, and each state implements the transitions as methods unique
to the state. Each method is implemented differently depending on the context of its
use. So, a method, illuminateLight()
, for
example, would do one thing in the Off state and something entirely different in the
On state, even though illuminateLight()
method is
part of both states.
The following key features characterize the State design pattern:
States exist internally as part of an object.
Objects change in certain ways when states change. Objects may appear to change classes, but they’re changing behavior that is part of the class.
Each state’s behavior depends on the current state of other states in the object.
One application where the state pattern’s popular is device simulation. Devices that change an object’s state are subject to change as the states change. The volume knob on a radio changes the sound’s volume state. More complex simulated devices include a music sound mixer board where simulated sliders change different states to affect the overall object (sound mixer) and the resulting sound. A Flash video player has several states to manage: play, record, append, pause and stop. Each state in the video player behaves according to the state of the other states as well as its own state.
To understand and appreciate the value of the State design pattern, we need to understand something about State Machines. A State Machine is the general model of states you would be using in an application with the State design pattern. So if a video player application is designed around key states, the application would be the state machine. We can also refer to state engines that run the state machines. (Think of your automobile’s blueprints as a state machine, and the actual car as the state engine.) The programming we use to move from one state to another is the state engine. The state engine data structure defines mechanisms for handling messages and managing contexts.
Rather than beginning with the usual diagrams associated with design patterns, we’re going to start with a statechart. At its most basic level, a statechart is an illustration of an,application’s states and transitions, and as such is a model for the state machine and engine. Taking a simple video player application, we can see the Play and Stop states. When the application first runs, the application enters the Stop state and can only transition to the Play state. Figure 10-1 shows a statechart depicting this condition.
The illustration is computer-drawn. The idea when sketching statecharts is to start with a rough idea, and then refine the idea with sketches. You’ll find that the process goes quicker using hand sketches with a pencil and scratch paper instead of a drawing program.
The line going from a black dot to the Stop state shows the Application-Not-Running state, but we’ll assume that the starting point is with the application running in the Stop state. This could be illustrated in a hierarchical state with Application Running and Application-Not-Running states, or we could place the whole hierarchy into Computer-On and Computer-Off states, but that’s not too useful because we’re not coding to those states.
Before going on to discuss getting from one state to another, let’s consider what each state can actually do. In the Stop state, I can only initiate the Play state. That is, in the Stop state, I can’t stop because I’m already stopped. By the same token, if I’m in the Play state, the only thing I can do is transition to the Stop state.
The transitions in a state machine are the actions to change states. In
Figure 10-1, the line from Stop
to Play might be a startPlay()
method of
some sort. From the Play to Stop state, it might be a stopPlay()
method. As more states are added,
you might find that you can’t transition directly from one state to another,
but rather you have to go through a series of states to get where you want
to go. As we’ll see later, if you’re in the Stop state, you can’t go
directly to the Pause state. You first have to go to the Play state, and
then, once in the Play state, go to the Pause state.
To initiate a transition, you need some kind of trigger. A trigger is any event that initiates a transition from one state to another. Usually, we think of some kind of user action such as a mouse movement or button click, but in simulations certain states can be triggered by ongoing conditions such as running out of fuel, draining a battery or a collision with an object. Likewise, triggers are subject to contexts and should only work in the appropriate contexts to initiate a state. So, while you might use a Play button to initiate the Play state from the Stop state, it shouldn’t trigger a Play state from the Play state.
Triggers are often placed along with the transitions on the statecharts. This helps to identify the trigger events and the transitions they trigger. Figure 10-2 shows the statechart updated to include both the triggers and transitions they initiate.
If you’re interested in more information about using state engines, statecharts, and the more general aspects of working with Flash and states, see Flash Mx for Interactive Simulation by Jonathan Kaye and David Castillo (Thomson, 2003). While going back a few generations of Flash, the book is timeless in its concepts and shows some very smooth device simulations.
As one of the design patterns described in Design Patterns: Elements of Reusable Object-Oriented Software by the Gang of Four, the value of the State design was recognized beyond the boundaries of those who were primarily interested in state machines. Closely resembling the Strategy pattern, the State pattern is used when an application’s behavior depends on changing states at runtime, or has complex conditional statements that branch depending on a current state. When the internal states change, an object alters its behavior when designed using the State pattern. Figure 10-3 shows the general structure in the class diagram of the State pattern:
While polymorphism is a fundamental OOP concept, it doesn’t help very much if its use and purpose are not understood. It’s just one of the many basic concepts in object-oriented programming that’s used to point to different implementations of objects in multiple forms. One feature you’ll see in the State design pattern is that the polymorphism is pretty obvious. This will help you better understand how polymorphism can be useful in OO programs.
Looking at the State interface in Example 10-1, you can see the different methods that become core behaviors for different states. Each state is its own class. However, as you look at each class, you can see that the behaviors of the same methods take on different forms—polymorphism hard at work. Just look at each of the state classes, and there you see all of the same methods but in different forms.
Perhaps most significant is the ability of each state to take care of knowing
itself. For instance, in the following examples, you will see that both the Stop and
Play states (classes) have a startPlay()
method.
However, each acts differently in its context. Moving from a Stop state initiates
playing the video. If the same method, startPlay()
, is fired while the play is in progress, it does nothing.
Doing nothing is important sometimes. Let’s say that someone is looking at a video
and, for whatever reason, presses the Play Button. In a typical video player
application, the player will start over again. In that application, the startPlay()
method is as dumb as a box of rocks.
However, because polymorphism allows multiple forms of the same method, it allows us
to provide the application with multiple forms that know what to do in different
contexts. So in the Play state, the method knows it’s already playing, and so it
does nothing. A user can pound on the Play Button all he wants, and the video just
keeps on playing. He can press the Stop Button, and the video will stop, just like
it’s supposed to do.
To better appreciate polymorphism, you will see that as more states are added to
the application, there’s more to keep track of. With more methods that we
absolutely, positively do not want to modify the wrong thing,
without polymorphism, we run the risk of having the same method do something we
definitely do not want it to do. Thus, if you don’t want your application to start
playing video all over again every time the startPlay()
method is invoked, using the State design pattern, you
can structure the application to only start at the beginning when the originating
state is the Stop state. Likewise, you can structure the startPlay()
to begin playing all over again from the Play state—you
write the code, and so you control how the methods behave.
When you read the next chapter on the Strategy design pattern, you may have a major case of déjà vu. The juxtaposition of these two chapters is no accident. Once you complete the example applications in each chapter, you should definitely get a different feel for each, even though the structures look very similar. The State design pattern has its focus on the states and well-defined transitions. This is one reason we use statecharts—they help clarify and simplify the architectural work in focusing on the different states and how they transition from one to another. The transitions in the State design can be controlled by the states themselves or by the context class. Also, because the State design creates a class for each state (behavior environment), you tend to generate more classes with the State design than with the Strategy design. Determining which behavior to use is delegated to the State classes, while the Strategy pattern encapsulates a family of algorithms and allows them to vary independent of the client within the structure that uses them.
Using the State design pattern, all the behaviors (methods) for a single state are placed into single objects (concrete states), and all transition behaviors for the application (state machine) are placed into a single interface. Each state object implements the interface in a fashion appropriate for the state. Because of this structure, no conditional statements are required to branch differentially depending on the current state. Rather than writing complex conditional statements, the individual state objects define how the methods are to behave for that state.
For example, with a two-state machine (Play and Stop), the following pseudocode could direct the state behavior to start playing the video, depending on the state machine’s current state.
function doPlay():void { if(state == Play) { trace("You're already playing."); } else if (state == Stop) { trace("Go to the Play state."); } }
With a couple of states that’s not too difficult. However, as you add states, things get more complicated and you find a sea of conditional statements that have to all work in sync. The alternative is to set up “contextual” behavior using a State pattern. For example, the following code in Example 10-1 has two different objects with different implementations of behaviors from an interface:
//Interface interface State { function startPlay():void; function stopPlay():void; } //Play State object class PlayState implements State { public function startPlay():void { trace("You're already playing"); } public function stopPlay():void { trace("Go to the Stop state."); } } //Stop State object class StopState implements State { public function startPlay():void { trace("Go to the Play state."); } public function stopPlay():void { trace("You're already stopped"); } }
As you can see, the behaviors (methods) have different implementations in the different states. When you add more states, all you need to do is add their transitional behaviors to the interface and create a new concrete state (class) that implements the behaviors. Each new behavior needs to be added to the existing state classes.
To manage the states and their transitions, you need some kind of management object—the state engine for your state machine. In Figure 10-3 the box labeled “Context” is the abstraction of the state engine. The context manages the different states that make up the state machine and contain the different states. Figure 10-4 shows a more concrete representation of what needs to be transformed:
Looking at our example of creating a simple video player, we need a
context that will serve to get and set the different states. So, the next
phase will be to look at a class (object) that does just that. This context
class should be saved as VideoWorks.as
.
First, take a look at the class in Example 10-2, and then
we’ll see what’s going on:
1 package 2 { 3 //Context class 4 class VideoWorks 5 { 6 var playState:State; 7 var stopState:State; 8 var state:State; 9 public function VideoWorks() 10 { 11 trace("Video Player is On"); 12 playState = new PlayState(this); 13 stopState = new StopState(this); 14 state=stopState; 15 } 16 public function startPlay():void 17 { 18 state.startPlay(); 19 } 20 public function stopPlay():void 21 { 22 state.stopPlay(); 23 } 24 public function setState(state:State):void 25 { 26 trace("A new state is set"); 27 this.state=state; 28 } 29 public function getState():State 30 { 31 return state; 32 } 33 public function getPlayState():State 34 { 35 return this.playState; 36 } 37 public function getStopState():State 38 { 39 return this.stopState; 40 } 41 } 42 } 43
Initially, in lines 6-8, the script instantiates three State
objects—one of each of the two we
designed (PlayState
and StopState
), and one (state
) that acts as a variable to hold the current state.
Because the state machine begins in the Stop state, the state variable is
assigned the Stop state. (This works just like the light switch before you
change it from the off state to the
on state.)
Next, the two behaviors from the State interface are specified in terms of
the current state’s context (lines 16-23). We’re going to have to add some
code to the two state classes for it to work with the context class, but for
now, think of what will happen in the two different states when those
behaviors are executed. For example, in the Play state, the startPlay()
method doesn’t do anything, but in
the Stop state, it switches to the Play state.
Finally, add the getter and setter methods (lines 24-40). We need a total of six methods—a set and get function for each of the three state instances. The setters return nothing and the getters return a State object.
To get everything working, we need to revise the state classes to include
the reference to the context—VideoWorks
.
Save Example 10-3 as StopState.as
.
1 package 2 { 3 //Stop State; 4 class StopState implements State 5 { 6 var videoWorks:VideoWorks; 7 public function StopState(videoWorks:VideoWorks) 8 { 9 trace("--Stop State--"); 10 this.videoWorks=videoWorks; 11 } 12 public function startPlay():void 13 { 14 trace("Begin playing"); 15 videoWorks.setState(videoWorks.getPlayState()); 16 } 17 public function stopPlay():void 18 { 19 trace("You're already stopped"); 20 } 21 } 22 }
By adding a VideoWorks
instance, we
have a way to access the getter and setter methods in each state. Line 15
invokes the VideoWorks
instance to change
the state to the Play state.
Next, we’ll do the same thing with the Play state shown in Example 10-4. Save the following as PlayState.as
.
1 package 2 { 3 //Play State 4 class PlayState implements State 5 { 6 var videoWorks:VideoWorks; 7 public function PlayState(videoWorks:VideoWorks) 8 { 9 trace("--Play State--"); 10 this.videoWorks=videoWorks; 11 } 12 public function startPlay():void 13 { 14 trace("You're already playing"); 15 } 16 public function stopPlay():void 17 { 18 trace("Stop playing."); 19 videoWorks.setState(videoWorks.getStopState()); 20 } 21 } 22 }
To complete the state machine, we need to create the actual interface, and
because the machine has only two states and two behaviors, this is a simple
matter. Returning to the original statechart, you can see only two
transitions—one to start the play and one to stop the play. So, all we’ll
need are two functions for abstractions of those two transitions. Save the
script in Example 10-5 as State.as
:
1 package 2 { 3 //State Machine Interface 4 interface State 5 { 6 function startPlay():void; 7 function stopPlay():void; 8 } 9 }
The transition behaviors are in lines 6 and 7. Later, we’ll be adding more transitional behaviors as the project grows, but the actual machine is complete. Finally, we need to create an FLA file with ActionScript that will execute the state machine.
To both test the abstract application and show features of the State
design pattern, the test should invoke the VideoWorks
class and the two states of Play and Stop using
the primary transitions (methods)—startPlay()
and stopPlay()
. In fact, it should call both states twice. From the
Stop state (default initial state), the application should be transitioned
to the Play state. Then, it should do it a second time to make sure that the
state recognizes the new context. The same should be done to transition back
to the Stop state. Save Example 10-6 as TestState.as
in the same folder as the other
files:
1 package 2 { 3 //Test states 4 import flash.display.Sprite; 5 public class TestState extends Sprite 6 { 7 public function TestState():void 8 { 9 var test:VideoWorks = new VideoWorks(); 10 test.startPlay(); 11 test.startPlay(); 12 test.stopPlay(); 13 test.stopPlay(); 14 } 15 } 16 }
Because the application at this stage only provides traces, you will need
to use the Flash Test (Control → Test or Control → Test Project) to see the
output. Open a new Flash document, and, in the Document class window, type
in TestState
. The following should
appear in the Output window:
Video Player is On --Play State-- --Stop State-- Begin playing A new state is set You're already playing Stop playing. A new state is set You're already stopped
Both the VideoWorks
class and the
PlayState
and StopState
classes include a trace
statement to indicate their
instantiation, and appear as soon as you test the script. Because the
initial state is Stop, it changes states to the Play state when the first
startPlay()
method is invoked. Also,
because you’re changing states, a trace
statement from the VideoWorks
class
indicates a state change. However, when the second startPlay()
method is invoked a second time, the same method
in a different context recognizes that it’s already in the Play state and
simply indicates that you’re already playing. When you press the Stop
button, you move to the Stop state, setting a new state, but on the second
press, the same function in this new context recognizes that you’re already
in the Stop state, and indicates that fact.
All that you’ve seen so far has been the output as trace()
statements to help understand how a State design pattern and
State machine works. To add something useful, we need to include a reference to both
a NetStream
object and a string for referencing
an FLV file. However, we need a string reference only for playing the video, because
we can stop it simply by closing the NetStream
instance. The following four scripts set up the state machine to actually play and
stop a video. All of the trace
statements have
been left in place.
With the implementation of an application that actually plays a video, we’ll need
to import the necessary parts. Because the NetStream
class is used in the interface and the two states, each of
those files will need to import the class. However, while the VideoWorks
class uses both the Play
and Stop
classes, it does not have to import the NetStream()
class. This is because it’s already imported in the
Play
and Stop
classes.
The following five listings, Example 10-7 through Example 10-11, should be entered into an ActionScript file and saved with the captions as the filenames. Save all the files in the same folder.
1 package 2 { 3 //State Interface #1 4 import flash.net.NetStream; 5 interface State 6 { 7 function startPlay(ns:NetStream,flv:String):void; 8 function stopPlay(ns:NetStream):void; 9 } 10 }
In the following StopState
class shown in Example 10-8, you will note that the startPlay()
method has been implemented to actually play the selected
FLV file. That transition to the Play state is not to start playing the video, but
rather to set the state where the video is playing.
1 package 2 { 3 //Stop State #2 4 import flash.net.NetStream; 5 6 class StopState implements State 7 { 8 private var videoWorks:VideoWorks; 9 public function StopState(videoWorks:VideoWorks) 10 { 11 trace("--Stop State--"); 12 this.videoWorks=videoWorks; 13 } 14 public function startPlay(ns:NetStream,flv:String):void 15 { 16 ns.play(flv); 17 trace("Begin playing"); 18 videoWorks.setState(videoWorks.getPlayState()); 19 } 20 public function stopPlay(ns:NetStream):void 21 { 22 trace("You're already stopped"); 23 } 24 } 25 }
Note that in the PlayState
class shown in Example 10-9, the startPlay()
method wisely does nothing other than issue a statement to remind the user that the
video is already playing. In the test mode, the message appears in the Output
window, but when the user’s working with it, no message is issued. The feedback
issues from the playing video.
1 package 2 { 3 //Play State #3 4 import flash.net.NetStream; 5 6 class PlayState implements State 7 { 8 private var videoWorks:VideoWorks; 9 public function PlayState(videoWorks:VideoWorks) 10 { 11 trace("--Play State--"); 12 this.videoWorks=videoWorks; 13 } 14 public function startPlay(ns:NetStream,flv:String):void 15 { 16 trace("You're already playing"); 17 } 18 public function stopPlay(ns:NetStream):void 19 { 20 ns.close(); 21 trace("Stop playing."); 22 videoWorks.setState(videoWorks.getStopState()); 23 } 24 } 25 }
Next, the context class shown in Example 10-10 for this
design pattern pulls it all together. Note that there is no import of the NetStream
object. However, the listing clearly shows that the NetSteam
object is one of the parameters of both the startPlay()
and stopPlay()
function parameters. If you look closely, you’ll see that
both the functions to start and stop play are instances of the PlayState
and StopState
classes, which did import the necessary NetStream
class.
1 package 2 { 3 import flash.net.NetStream; 4 //Context Class #4 5 class VideoWorks 6 { 7 private var playState:State; 8 private var stopState:State; 9 private var state:State; 10 public function VideoWorks() 11 { 12 trace("Video Player is on"); 13 playState = new PlayState(this); 14 stopState = new StopState(this); 15 state=stopState; 16 } 17 public function startPlay(ns:NetStream,flv:String):void 18 { 19 state.startPlay(ns,flv); 20 } 21 public function stopPlay(ns:NetStream):void 22 { 23 state.stopPlay(ns); 24 } 25 public function setState(state:State):void 26 { 27 trace("A new state is set"); 28 this.state=state; 29 } 30 public function getState():State 31 { 32 return state; 33 } 34 public function getPlayState():State 35 { 36 return this.playState; 37 } 38 public function getStopState():State 39 { 40 return this.stopState; 41 } 42 } 43 } 44
In addition to the classes that make up the State design pattern, you will need a
copy of the NetBtn
and BtnState
classes shown in Example 10-11 and Example 10-12. In fact, in all the examples in this chapter, the
NetBtn.as and BtnState.as
files will be
required in the same folder as the other files. In subsequent listings, we didn’t
include the listing for the class because they’re all identical. (That means once
you’ve entered it, you don’t have to do anything else with it.) So keep the files
handy, and make sure that they’re included in the folders with your other
classes.
1 package 2 { 3 //Button for transition triggers 4 import flash.display.Sprite; 5 import flash.display.SimpleButton; 6 import flash.display.Shape; 7 import flash.text.TextFormat; 8 import flash.text.TextField; 9 import flash.text.TextFieldAutoSize; 10 public class NetBtn extends SimpleButton 11 { 12 public function NetBtn (txt:String) 13 { 14 upState = new BtnState(0xfab383, 0x9e0039,txt); 15 downState = new BtnState(0xffffff,0x9e0039, txt); 16 overState= new BtnState (0x9e0039,0xfab383,txt); 17 hitTestState=upState; 18 } 19 } 20 }
1 package 2 { 3 //States for transition buttons 4 import flash.display.Sprite; 5 import flash.display.Shape; 6 import flash.text.TextFormat; 7 import flash.text.TextField; 8 import flash.text.TextFieldAutoSize; 9 class BtnState extends Sprite 10 { 11 public var btnLabel:TextField; 12 public function BtnState (color:uint,color2:uint, btnLabelText:String) 13 { 14 btnLabel=new TextField ; 15 btnLabel.text=btnLabelText; 16 btnLabel.x=5; 17 btnLabel.autoSize=TextFieldAutoSize.LEFT; 18 var format:TextFormat=new TextFormat("Verdana"); 19 format.size=12; 20 btnLabel.setTextFormat (format); 21 var btnWidth:Number=btnLabel.textWidth + 10; 22 var bkground:Shape=new Shape; 23 bkground.graphics.beginFill (color); 24 bkground.graphics.lineStyle (2,color2); 25 bkground.graphics.drawRect (0,0,btnWidth,18); 26 addChild (bkground); 27 addChild (btnLabel); 28 } 29 } 30 } 31
To test the application, you’ll need an FLV file named test.flv
. You can convert an existing video file (e.g. avi,
mov) or use any flv file on hand. Place the file
in the same folder as the application. Finally, you’ll need a script to test the
application, so open a new ActionScript file, enter the following listing in Example 10-13, and save it as TestVid.as
:
1 package 2 { 3 //Implement FMS2 App and Test State Machine #7 4 import flash.display.Sprite; 5 import flash.net.NetConnection; 6 import flash.net.NetStream; 7 import flash.media.Video; 8 import flash.text.TextField; 9 import flash.text.TextFieldType; 10 import flash.events.MouseEvent; 11 import flash.events.NetStatusEvent; 12 13 public class TestVid extends Sprite 14 { 15 private var nc:NetConnection=new NetConnection(); 16 private var ns:NetStream; 17 private var vid:Video=new Video(320,240); 18 private var vidTest:VideoWorks; 19 private var playBtn:NetBtn; 20 private var stopBtn:NetBtn; 21 private var flv:String; 22 private var flv_txt:TextField; 23 private var dummy:Object; 24 25 public function TestVid () 26 { 27 nc.connect (null); 28 ns=new NetStream(nc); 29 addChild (vid); 30 vid.x=(stage.stageWidth/2)-(vid.width/2); 31 vid.y=(stage.stageHeight/2)-(vid.height/2); 32 33 //Instantiate State Machine 34 vidTest=new VideoWorks(); 35 36 //Play and Stop Buttons 37 playBtn=new NetBtn("Play"); 38 addChild (playBtn); 39 playBtn.x=(stage.stageWidth/2)-50; 40 playBtn.y=350; 41 stopBtn=new NetBtn("Stop"); 42 addChild (stopBtn); 43 stopBtn.x=(stage.stageWidth/2)+50; 44 stopBtn.y=350; 45 46 //Add Event Listeners 47 playBtn.addEventListener (MouseEvent.CLICK,doPlay); 48 stopBtn.addEventListener (MouseEvent.CLICK,doStop); 49 50 //Add the text field 51 flv_txt= new TextField(); 52 flv_txt.border=true; 53 flv_txt.borderColor=0x9e0039; 54 flv_txt.background=true; 55 flv_txt.backgroundColor=0xfab383; 56 flv_txt.type=TextFieldType.INPUT; 57 flv_txt.x=(stage.stageWidth/2)-45; 58 flv_txt.y=10; 59 flv_txt.width=90; 60 flv_txt.height=16; 61 addChild (flv_txt); 62 63 //This prevents a MetaData error being thrown 64 dummy=new Object(); 65 ns.client=dummy; 66 dummy.onMetaData=getMeta; 67 68 //NetStream 69 ns.addEventListener (NetStatusEvent.NET_STATUS, flvCheck); 70 } 71 //MetaData 72 private function getMeta (mdata:Object):void 73 { 74 trace (mdata.duration); 75 } 76 //Handle flv 77 private function flvCheck (event:NetStatusEvent):void 78 { 79 switch (event.info.code) 80 { 81 case "NetStream.Play.Stop" : 82 vidTest.stopPlay (ns); 83 vid.clear (); 84 break; 85 case "NetStream.Play.StreamNotFound" : 86 vidTest.stopPlay (ns); 87 flv_txt.text="File not found"; 88 break; 89 } 90 } 91 //Start play 92 private function doPlay (e:MouseEvent):void 93 { 94 if (flv_txt.text != "" && flv_txt.text != "Provide file name") 95 { 96 flv_txt.textColor=0x000000; 97 flv=flv_txt.text + ".flv"; 98 vidTest.startPlay (ns,flv); 99 vid.attachNetStream (ns); 100 } 101 else 102 { 103 flv_txt.textColor=0xcc0000; 104 flv_txt.text="Provide file name"; 105 } 106 } 107 //Stop play 108 private function doStop (e:MouseEvent):void 109 { 110 vidTest.stopPlay (ns); 111 vid.clear (); 112 } 113 } 114 } 115
Open a new Flash document file, and in the center of the stage, draw a rectangle
(W=320, H=240) with a 6-point stroke, the color value 9E0039, and the fill color
FAB383. Use the Align panel to make sure that the rectangle is perfectly centered
because it serves as a backdrop for the video that will be placed on top of it. In
the Document class window, type in TestVid
, and
test the application.
The UI is simple and related to the transitions—Stop and Start (playing video).You
can see the relationship between the video playing and the related trace
statement showing what happens when you press
the button. For example, if you press Start and the video is running, nothing new
occurs because in the play state, the startPlay()
function does nothing other than offering a trace
statement to the effect that you’re already playing.
A fundamental feature of virtually all design patterns is their ability to expand and accept change. The kind of change you’re expecting in an application determines, to some extent, the type of design pattern you select. In this particular application, we’re adding states.
The first state to be added to the state machine is a Pause state. This state only exists inside the Play state, and you cannot get to the Pause state directly from the Stop state. To get to the Pause state, you must first be in the Play state, and then you can turn the Pause state on and off. To correctly depict this new state, we need to use a hierarchical state diagram. Figure 10-5 shows a statechart with the necessary hierarchy.
The hierarchy in Figure 10-5 is a simple one. The first level is the Play and Stop states, and then, within the Play state are the Pause and No Pause states.
Because the pause function is a toggle between the Play and Pause states, the No Pause state is exactly the same as the Play state. So, rather than creating Pause Start and Pause Stop functions, a “Do Pause” behavior will be established to act differently in different states. In a Pause state, the Do Pause behavior returns to the default Play state and in the Play state, it goes to the Pause state.
The first step is to add a new behavior (method) to the current ones in the
State.as
file. This behavior is a state
transition from playing to pause and back to playing. To begin, the first
element to add is the pause behavior to the set of states. This is fairly simple
because it too has a NetStream
parameter, so
changes can be made without further imports:
function doPause(ns:NetStream):void;
The first step is to change the State.as
by
adding the new function as shown in Example 10-14 line 9:
1 package 2 { 3 //State Interface #1 4 import flash.net.NetStream; 5 interface State 6 { 7 function startPlay(ns:NetStream,flv:String):void; 8 function stopPlay(ns:NetStream):void; 9 function doPause(ns:NetStream):void; 10 } 11 }
One of the requirements for an interface is that all implementations of the interface include all the interface methods. The next step will be to add the method to the Stop and Play states as in Example 10-15:
1 package 2 { 3 //Stop State #2 4 import flash.net.NetStream; 5 6 class StopState implements State 7 { 8 private var videoWorks:VideoWorks; 9 public function StopState(videoWorks:VideoWorks) 10 { 11 trace("--Stop State--"); 12 this.videoWorks=videoWorks; 13 } 14 public function startPlay(ns:NetStream,flv:String):void 15 { 16 ns.play(flv); 17 trace("Begin playing"); 18 videoWorks.setState(videoWorks.getPlayState()); 19 } 20 public function stopPlay(ns:NetStream):void 21 { 22 trace("You're already stopped"); 23 } 24 public function doPause(ns:NetStream):void 25 { 26 trace("Cannot go to Stop from Pause."); 27 } 28 } 29 }
The doPause()
method is added to the
StopState.as
file as shown in line 24,
but it makes no sense to set the Pause state from the Stop state because then
nothing is playing to pause. So all that’s added is a trace()
statement on line 26 that indicates that such a
transition is not possible.
Working with the Play state is an entirely different proposition. Essentially,
the Pause state is a subset of the Play state. In fact, when using a toggle
pause, you can think of the play as “play without pause” and “play with pause.”
With ActionScript 3.0, another option is to use the new NetStream.pause()
and NetStream.resume()
methods. Had we done that, two, instead of
one, additional states would be required. For now, though, just change the
PlayState.as
file as shown in Example 10-16, in lines 24-29:
1 package 2 { 3 //Play State #3 4 import flash.net.NetStream; 5 6 class PlayState implements State 7 { 8 private var videoWorks:VideoWorks; 9 public function PlayState(videoWorks:VideoWorks) 10 { 11 trace("--Play State--"); 12 this.videoWorks=videoWorks; 13 } 14 public function startPlay(ns:NetStream,flv:String):void 15 { 16 trace("You're already playing"); 17 } 18 public function stopPlay(ns:NetStream):void 19 { 20 ns.close(); 21 trace("Stop playing."); 22 videoWorks.setState(videoWorks.getStopState()); 23 } 24 public function doPause(ns:NetStream):void 25 { 26 ns.togglePause(); 27 trace("Begin pause."); 28 videoWorks.setState(videoWorks.getPauseState()); 29 } 30 } 31 }
Next, the new PauseState
class implements
the State
interface. The doPause()
function in the Pause state uses the
exact same ns.togglePause()
used in the
PlayState
class as shown in Example 10-17. However, instead of getting the PauseState
, it gets the PlayState
. The interesting feature of a toggle method is that
very different functions may use the same class method.
1 package 2 { 3 //Pause State #4 4 import flash.net.NetStream; 5 6 class PauseState implements State 7 { 8 var videoWorks:VideoWorks; 9 public function PauseState(videoWorks:VideoWorks) 10 { 11 trace("--Pause State--"); 12 this.videoWorks=videoWorks; 13 } 14 public function startPlay(ns:NetStream,flv:String):void 15 { 16 trace("You have to go to unpause"); 17 } 18 public function stopPlay(ns:NetStream):void 19 { 20 trace("Don't go to Stop from Pause"); 21 } 22 public function doPause(ns:NetStream):void 23 { 24 ns.togglePause(); 25 trace("Quit pausing."); 26 videoWorks.setState(videoWorks.getPlayState()); 27 } 28 } 29 }
As with the individual state classes, you also need to add a PauseState
instance to the VideoWorks
class as shown in Example 10-18. Remember that this class contains all of the
setters and getters, and you certainly need that to invoke the pause behavior
for the application. This is easy to do because all it takes is a single
variable for the PauseState
instance, a
doPause()
function, and then adding the
pause getter.
1 package 2 { 3 //Context Class #5 4 import flash.net.NetStream; 5 6 class VideoWorks 7 { 8 private var playState:State; 9 private var stopState:State; 10 private var pauseState:State; 11 private var state:State; 12 public function VideoWorks() 13 { 14 trace("Video Player is on"); 15 playState = new PlayState(this); 16 stopState = new StopState(this); 17 pauseState = new PauseState(this); 18 state=stopState; 19 } 20 public function startPlay(ns:NetStream,flv:String):void 21 { 22 state.startPlay(ns,flv); 23 } 24 public function stopPlay(ns:NetStream):void 25 { 26 state.stopPlay(ns); 27 } 28 public function doPause(ns:NetStream):void 29 { 30 state.doPause(ns); 31 } 32 public function setState(state:State):void 33 { 34 trace("A new state is set"); 35 this.state=state; 36 } 37 public function getState():State 38 { 39 return state; 40 } 41 public function getPlayState():State 42 { 43 return this.playState; 44 } 45 public function getStopState():State 46 { 47 return this.stopState; 48 } 49 public function getPauseState():State 50 { 51 return this.pauseState; 52 } 53 } 54 } 55
To test the additional pause class, all you need to do in the test class is
add another button instance. With three buttons, a little more positioning
thought has to go into it, but expanding the State design pattern is very easy.
All of the changes for adding the additional button are on lines 45-48 and 53.
So just edit the TestVid.as
file as shown in
Example 10-19, and save it as TestPause.as
.
1 package 2 { 3 //Implement FMS2 App and Test State Machine #6 4 import flash.display.Sprite; 5 import flash.net.NetConnection; 6 import flash.net.NetStream; 7 import flash.media.Video; 8 import flash.text.TextField; 9 import flash.text.TextFieldType; 10 import flash.events.MouseEvent; 11 import flash.events.NetStatusEvent; 12 13 public class TestPause extends Sprite 14 { 15 private var nc:NetConnection=new NetConnection(); 16 private var ns:NetStream; 17 private var vid:Video=new Video(320,240); 18 private var vidTest:VideoWorks; 19 private var playBtn:NetBtn; 20 private var stopBtn:NetBtn; 21 private var flv:String; 22 private var flv_txt:TextField; 23 private var dummy:Object; 24 25 public function TestPause () 26 { 27 nc.connect (null); 28 ns=new NetStream(nc); 29 addChild (vid); 30 vid.x=stage.stageWidth / 2 - vid.width / 2; 31 vid.y=stage.stageHeight / 2 - vid.height / 2; 32 33 //Instantiate State Machine 34 vidTest=new VideoWorks ; 35 36 //Play, Stop and Pause Buttons 37 playBtn=new NetBtn("Play"); 38 addChild (playBtn); 39 playBtn.x=stage.stageWidth / 2 - 100 + playBtn.width / 2; 40 playBtn.y=350; 41 stopBtn=new NetBtn("Stop"); 42 addChild (stopBtn); 43 stopBtn.x=stage.stageWidth / 2 - stopBtn.width / 2; 44 stopBtn.y=350; 45 var pauseBtn:NetBtn=new NetBtn("Pause"); 46 addChild (pauseBtn); 47 pauseBtn.x=(stage.stageWidth / 2 + 100) - pauseBtn.width; 48 pauseBtn.y=350; 49 50 //Add Event Listeners 51 playBtn.addEventListener (MouseEvent.CLICK,doPlay); 52 stopBtn.addEventListener (MouseEvent.CLICK,doStop); 53 pauseBtn.addEventListener (MouseEvent.CLICK,pauseNow); 54 55 //Add the text field 56 flv_txt=new TextField ; 57 flv_txt.border=true; 58 flv_txt.borderColor=0x9e0039; 59 flv_txt.background=true; 60 flv_txt.backgroundColor=0xfab383; 61 flv_txt.type=TextFieldType.INPUT; 62 flv_txt.x=stage.stageWidth / 2 - 45; 63 flv_txt.y=10; 64 flv_txt.width=90; 65 flv_txt.height=16; 66 addChild (flv_txt); 67 68 //This prevents a MetaData error being thrown 69 dummy=new Object ; 70 ns.client=dummy; 71 dummy.onMetaData=getMeta; 72 73 //NetStream 74 ns.addEventListener (NetStatusEvent.NET_STATUS,flvCheck); 75 } 76 //MetaData 77 private function getMeta (mdata:Object):void 78 { 79 trace (mdata.duration); 80 } 81 //Handle flv 82 private function flvCheck (event:NetStatusEvent):void 83 { 84 switch (event.info.code) 85 { 86 case "NetStream.Play.Stop" : 87 vidTest.stopPlay (ns); 88 vid.clear (); 89 break; 90 case "NetStream.Play.StreamNotFound" : 91 vidTest.stopPlay (ns); 92 flv_txt.text="File not found"; 93 break; 94 } 95 } 96 //Start play 97 private function doPlay (e:MouseEvent):void 98 { 99 if (flv_txt.text != "" && flv_txt.text != "Provide file name") 100 { 101 flv_txt.textColor=0x000000; 102 flv=flv_txt.text + ".flv"; 103 vidTest.startPlay (ns,flv); 104 vid.attachNetStream (ns); 105 } 106 else 107 { 108 flv_txt.textColor=0xcc0000; 109 flv_txt.text="Provide file name"; 110 } 111 } 112 //Stop play 113 private function doStop (e:MouseEvent):void 114 { 115 vidTest.stopPlay (ns); 116 vid.clear (); 117 } 118 //Pause play 119 function pauseNow (e:MouseEvent):void 120 { 121 vidTest.doPause (ns); 122 } 123 } 124 } 125
Figure 10-6 shows what your video player should look like.
Now that the structure can support a simple FLV playback system, the next step will be to add two additional states and see if the state machine can be adapted to a Flash Media Server application. Keeping the focus on the design pattern, only two new states will be added—Record and Append.
Changing from a Flash application to a Flash Media Server 2 (FMS2) application requires key changes in the FLA script to include a connection to the server, and adding Camera and Microphone objects. Otherwise, you’ll find that adding the additional states of Record and Append are relatively simple.
Adding states is relatively easy, as you’ve seen. However, when you add Flash
Media Server 2, you need to take care of a few matters to make sure that your
application works correctly. Because AS 3.0 and Client-Side ActionScript (CSAS)
used in FMS2 are a bit different, you need to import the net.ObjectEncoding
class. By doing so,
ActionScript 3.0 and CSAS can work together. The ActionScript 3.0 default Action
Message Format (AMF) is AMF3, but FMS2 needs AMF0. So, you need to change the
ObjectEncoding
class to AMF0 using the
line,
NetConnection.defaultObjectEncoding=flash.net.ObjectEncoding.AMF0;
This line needs to be in your implementation of the State design pattern, but not in the classes that make up the pattern. (See Example 10-27)
You will see in the StopState
class we made
for this application (Example 10-21) we need to have a key
difference in the way that the NetStream.play()
method is employed. By adding a second parameter
to the method, it’s effectively changed into a CSAS method. However, because the
flash.net.ObjectEncoding
has been
imported, the two different versions of ActionScript can work together.
Flash Media Server 2 is an open socket media server available from Adobe. You can download the Developer’s Version free from
http://www.adobe.com/products/flashmediaserver/
You will need to set it up in a Windows or Linux server environment, or just on your own computer with Windows OS. On a Macintosh, you’ll need to be in the Windows mode to set it up.
Once installed on your system, just add a folder named flvstate
in the applications
folder of the Flash Media Server. Once that’s done,
you don’t need to do anything else with the server other than make sure that
it’s running when you use the FMS application. (You’ll need to follow the FMS2
documentation for the setup.)
The first task when working with state machine models is to update the model. Figure 10-7 shows the addition of two new states—Append and Record. The original three states are pretty much the same as before. Note that the Stop state is the central one for all transitions except for the Play-Pause toggle. To change from any state except Pause, the transition must first go to the Stop state.
As noted at the outset, statecharts make it easy to see required program changes. By adding two more states, Append and Record, all the other states and contexts need to be changed as well. However, you don’t have to change a huge number of conditional statements. The testing application also needs changes, but because that code is more a user of the state machine than an actual part of the state machine, it will be handled separately. The following scripts, Example 10-20 through Example 10-26, add all the necessary changes for the state machine:
1 package 2 { 3 //State Interface #1 4 import flash.net.NetStream; 5 interface State 6 { 7 function startPlay(ns:NetStream, flv:String):void; 8 function startRecord(ns:NetStream, flv:String):void; 9 function startAppend(ns:NetStream, flv:String):void; 10 function stopAll(ns:NetStream):void; 11 function doPause(ns:NetStream):void; 12 } 13 }
Note that instead of naming the method for stopping the video play, it’s been
changed to stopAll()
in line 10. The reason
for this is that in the different states, stopping means something different. It
can mean stop recording and appending in addition to stop playing the video. So
the change focuses on the fact that it’s not just to do one thing. It’s another
instance where polymorphism is coming in handy.
Next, the StopState
class has one NetStream
method that’s part of an older
ActionScript, Client-Side ActionScript (CSAS) from Flash Media Server 2. In
ActionScript 3.0, the NetStream.play()
method
expects only a single argument—a string for the FLV file’s URL. However, in
CSAS, you can add a second parameter to specify what type of stream to play. In
order for AS 3.0 to work with classes from prior versions of ActionScript that
serialize objects, the application will have to import the ObjectEncoding
class. However, if that’s done, AS
3.0 can fully integrate these other objects and their parameters. Line 20 shows
this second argument added to the NetStream.play()
method.
1 package 2 { 3 //Stop State #2 4 import flash.net.NetStream; 5 6 class StopState implements State 7 { 8 var videoWorks:VideoWorks; 9 public function StopState(videoWorks:VideoWorks) 10 { 11 trace("--Stop State--"); 12 this.videoWorks=videoWorks; 13 } 14 public function startPlay(ns:NetStream,flv:String):void 15 { 16 //Note: the second paramater - 0 - specifies an FLV file 17 //the NetStream method is from Client-Side 18 //ActionScript but works with AS 3.0 19 //because ObjectEncoding is imported. 20 ns.play(flv,0); 21 trace("Begin playing"); 22 videoWorks.setState(videoWorks.getPlayState()); 23 } 24 public function startRecord(ns:NetStream,flv:String):void 25 { 26 ns.publish(flv,"record"); 27 trace("Begin recording"); 28 videoWorks.setState(videoWorks.getRecordState()); 29 } 30 public function startAppend(ns:NetStream,flv:String):void 31 { 32 ns.publish(flv,"append"); 33 trace("Begin appending"); 34 videoWorks.setState(videoWorks.getAppendState()); 35 } 36 public function stopAll(ns:NetStream):void 37 { 38 trace("You're already stopped"); 39 } 40 public function doPause(ns:NetStream):void 41 { 42 trace("Must be playing to pause."); 43 } 44 } 45 } 46
It may seem ironic that the state with the most active implementations of the
video is called “stop.” However, only from the StopState
should transitions be made to Append and Record states.
The PlayState
shown in Example 10-22 can be transitioned to from both the Stop state
and the Pause state.
1 package 2 { 3 //Play State #3 4 import flash.net.NetStream; 5 6 class PlayState implements State 7 { 8 var videoWorks:VideoWorks; 9 public function PlayState(videoWorks:VideoWorks) 10 { 11 trace("--Play State--"); 12 this.videoWorks=videoWorks; 13 } 14 public function startPlay(ns:NetStream,flv:String):void 15 { 16 trace("You're already playing"); 17 } 18 public function stopAll(ns:NetStream):void 19 { 20 ns.close(); 21 trace("Stop playing."); 22 videoWorks.setState(videoWorks.getStopState()); 23 } 24 public function startRecord(ns:NetStream,flv:String):void 25 { 26 trace("You have to stop first."); 27 } 28 public function startAppend(ns:NetStream,flv:String):void 29 { 30 trace("You have to stop first."); 31 } 32 public function doPause(ns:NetStream):void 33 { 34 ns.togglePause(); 35 trace("Start pausing."); 36 videoWorks.setState(videoWorks.getPauseState()); 37 } 38 } 39 } 40
The Play state is little changed from previous versions. While playing a video, the structure won’t allow direct transitioning to either recording or appending a video. It would be possible to do so, but you’d run the risk over overwriting a video you’re watching with one you want to record. So, from the Play state, the only possible transitions are to the Stop and Pause states. The Pause state is shown in Example 10-23.
1 package 2 { 3 //Pause State #4 4 import flash.net.NetStream; 5 6 class PauseState implements State 7 { 8 var videoWorks:VideoWorks; 9 public function PauseState(videoWorks:VideoWorks) 10 { 11 trace("--Pause State--"); 12 this.videoWorks=videoWorks; 13 } 14 public function startPlay(ns:NetStream,flv:String):void 15 { 16 trace("You have to go to unpause"); 17 } 18 public function stopAll(ns:NetStream):void 19 { 20 trace("Don't go to Stop from Pause"); 21 } 22 public function startRecord(ns:NetStream,flv:String):void 23 { 24 trace("You have to stop first."); 25 } 26 public function startAppend(ns:NetStream,flv:String):void 27 { 28 trace("You have to stop first."); 29 } 30 public function doPause(ns:NetStream):void 31 { 32 ns.togglePause(); 33 trace("Quit pausing."); 34 videoWorks.setState(videoWorks.getPlayState()); 35 } 36 } 37 38 }
Like the Play state, little is changed with the Pause state shown in Example 10-23, because it only toggles between playing and not playing, and can’t pause a recording.
The RecordState
class is a whole new state.
If you look closely, it’s very close to the PlayState
class, with the exception that it can’t transition to
the Pause state. Remember that while in the Record state, the only option is to
stop recording, and that is how the stopAll()
method is implemented in the RecordState
class in Example 10-24. Save the new state with the caption
name as the filename. Be sure to save it in the same folder as the other
files.
1 package 2 { 3 //Record State #5 4 import flash.net.NetStream; 5 6 class RecordState implements State 7 { 8 var videoWorks:VideoWorks; 9 public function RecordState(videoWorks:VideoWorks) 10 { 11 trace("--Record State--"); 12 this.videoWorks=videoWorks; 13 } 14 public function startPlay(ns:NetStream,flv:String):void 15 { 16 trace("You have to stop first."); 17 } 18 public function stopAll(ns:NetStream):void 19 { 20 ns.close(); 21 trace("Stop recording."); 22 videoWorks.setState(videoWorks.getStopState()); 23 } 24 public function startRecord(ns:NetStream,flv:String):void 25 { 26 trace("You're already recording"); 27 } 28 public function startAppend(ns:NetStream,flv:String):void 29 { 30 trace("You have to stop first."); 31 } 32 public function doPause(ns:NetStream):void 33 { 34 trace("Must be playing to pause."); 35 } 36 } 37 }
The AppendState
shown in Example 10-25 is the other new class. It’s virtually identical
to the RecordState
class in its makeup.
However, because the processes of recording and appending are very similar, this
should come as no surprise. Save this class in the same folder as the others for
this application with the caption as the filename.
1 package 2 { 3 //Append State #6 4 import flash.net.NetStream; 5 6 7 class AppendState implements State 8 { 9 var videoWorks:VideoWorks; 10 public function AppendState(videoWorks:VideoWorks) 11 { 12 trace("--Append State--"); 13 this.videoWorks=videoWorks; 14 } 15 public function startPlay(ns:NetStream,flv:String):void 16 { 17 trace("You have to stop first."); 18 } 19 public function stopAll(ns:NetStream):void 20 { 21 ns.close(); 22 trace("Stop appending."); 23 videoWorks.setState(videoWorks.getStopState()); 24 } 25 public function startRecord(ns:NetStream,flv:String):void 26 { 27 trace("You have to stop first."); 28 } 29 public function startAppend(ns:NetStream,flv:String):void 30 { 31 trace("You're already appending"); 32 } 33 public function doPause(ns:NetStream):void 34 { 35 trace("Must be playing to pause."); 36 } 37 } 38 }
Other than adding instances of the new states, nothing is too different from
the previous versions in the VideoWorks
class
shown in Example 10-26. This is a case where little change
shows the strength of the State design structure. Not only has the application
been expanded to include two additional classes for record and append, it’s also
changed from a simple Flash Player using progressive download to play a video to
a Flash Media Server 2 streaming media application.
1 package 2 { 3 //Context Class #7 4 import flash.net.NetStream; 5 public class VideoWorks 6 { 7 var playState:State; 8 var stopState:State; 9 var recordState:State; 10 var appendState:State; 11 var pauseState:State; 12 var state:State; 13 public function VideoWorks () 14 { 15 trace ("Video Player is on"); 16 playState = new PlayState(this); 17 stopState = new StopState(this); 18 recordState = new RecordState(this); 19 appendState = new AppendState(this); 20 pauseState=new PauseState(this); 21 state=stopState; 22 } 23 public function startPlay (ns:NetStream,flv:String):void 24 { 25 state.startPlay (ns,flv); 26 } 27 public function startRecord (ns:NetStream,flv:String):void 28 { 29 state.startRecord (ns,flv); 30 } 31 public function startAppend (ns:NetStream,flv:String):void 32 { 33 state.startAppend (ns,flv); 34 } 35 public function stopAll (ns:NetStream):void 36 { 37 state.stopAll (ns); 38 } 39 public function doPause (ns:NetStream):void 40 { 41 state.doPause (ns); 42 } 43 public function setState (state:State):void 44 { 45 trace ("A new state is set"); 46 this.state=state; 47 } 48 public function getState ():State 49 { 50 return state; 51 } 52 public function getPlayState ():State 53 { 54 return this.playState; 55 } 56 public function getRecordState ():State 57 { 58 return this.recordState; 59 } 60 public function getAppendState ():State 61 { 62 return this.appendState; 63 } 64 public function getPauseState ():State 65 { 66 return this.pauseState; 67 } 68 public function getStopState ():State 69 { 70 return this.stopState; 71 } 72 } 73 }
The test module, Example 10-27, that follows in the TestFMS.as
class is a bit more robust than the
previous testing classes used. (In fact it would have probably been a good idea
to break it down into its own class set, but we’re focusing on using it to
demonstrate the State design pattern, and so a humbler test class emerged.) Be
sure to place a copy of the NetBtn.as
and
BtnState.as
files in the folder where
you’re saving the rest of your files for this application.
1 package 2 { 3 //Test Module #8 4 import flash.display.Sprite; 5 import flash.net.NetConnection; 6 import flash.net.NetStream; 7 import flash.net.ObjectEncoding; 8 import flash.media.Video; 9 import flash.media.Camera; 10 import flash.media.Microphone; 11 import flash.text.TextField; 12 import flash.text.TextFieldType; 13 import flash.events.MouseEvent; 14 import flash.events.NetStatusEvent; 15 16 public class TestVidFMS extends Sprite 17 { 18 private var nc:NetConnection; 19 private var ns:NetStream; 20 private var dummy:Object; 21 private var flv_txt:TextField; 22 private var cam:Camera; 23 private var mic:Microphone; 24 private var stateVid:VideoWorks; 25 private var playCheck:Boolean; 26 private var pauseCheck:Boolean; 27 private var playBtn:NetBtn; 28 private var stopBtn:NetBtn; 29 private var pauseBtn:NetBtn; 30 private var recordBtn:NetBtn; 31 private var appendBtn:NetBtn; 32 33 public function TestVidFMS () 34 { 35 //************ 36 //Add the text field 37 //************ 38 flv_txt= new TextField(); 39 flv_txt.border=true; 40 flv_txt.background=true; 41 flv_txt.backgroundColor=0xfab383; 42 flv_txt.type=TextFieldType.INPUT; 43 flv_txt.x=(550/2)-45; 44 flv_txt.y=15; 45 flv_txt.width=90; 46 flv_txt.height=18; 47 addChild (flv_txt); 48 //FMS State Machine 49 NetConnection.defaultObjectEncoding= flash.net.ObjectEncoding.AMF0; 50 nc = new NetConnection(); 51 nc.objectEncoding = flash.net.ObjectEncoding.AMF0; 52 nc.addEventListener (NetStatusEvent.NET_STATUS, checkHookupStatus); 53 //Use your own domain/IP address on RTMP 54 nc.connect ("rtmp://192.168.0.11/flvstate/flv"); 55 //OR set up a local connection 56 //nc.connect("rtmp:/flvstate/flv"); 57 //nc.connect(null); 58 59 //Camera & Microphone Settings 60 cam = Camera.getCamera(); 61 cam.setMode (320,240,15); 62 cam.setKeyFrameInterval (30); 63 cam.setQuality (0,80); 64 mic = Microphone.getMicrophone(); 65 mic.rate=11; 66 67 //Add video object 68 vid=new Video(320,240); 69 70 addChild (vid); 71 vid.x=(550/2)-(320/2); 72 vid.y=40; 73 setLocal (); 74 75 //Instantiate State Machine 76 stateVid=new VideoWorks; 77 78 //Play, Stop, Record, Append and Pause Buttons 79 playBtn=new NetBtn("Play"); 80 addChild (playBtn); 81 playBtn.x=(550/2)-(320/2); 82 playBtn.y=300; 83 var playCheck:Boolean=false; 84 85 recordBtn=new NetBtn("Record"); 86 addChild (recordBtn); 87 recordBtn.x=(550/2)+((320/2)-60); 88 recordBtn.y=300; 89 90 appendBtn=new NetBtn("Append"); 91 addChild (appendBtn); 92 appendBtn.x=(550/2)+((320/2)-60); 93 appendBtn.y=330; 94 95 stopBtn=new NetBtn("Stop"); 96 addChild (stopBtn); 97 stopBtn.x=(550/2)-25; 98 stopBtn.y=300; 99 100 pauseBtn=new NetBtn("Pause"); 101 addChild (pauseBtn); 102 pauseBtn.x=(550/2)-(320/2); 103 pauseBtn.y=330; 104 pauseCheck=true; 105 106 //Add Event Listeners 107 playBtn.addEventListener (MouseEvent.CLICK,doPlay); 108 stopBtn.addEventListener (MouseEvent.CLICK,doStop); 109 recordBtn.addEventListener (MouseEvent.CLICK,doRecord); 110 appendBtn.addEventListener (MouseEvent.CLICK,doAppend); 111 pauseBtn.addEventListener (MouseEvent.CLICK,doPause); 112 113 } 114 //Add Control Functions 115 function setNet () 116 { 117 vid.attachNetStream (ns); 118 } 119 function setLocal () 120 { 121 vid.attachCamera (cam); 122 } 123 var flv:String; 124 function doPlay (e:MouseEvent):void 125 { 126 if (flv_txt.text != "" && flv_txt.text != "Provide file name") 127 { 128 setNet (); 129 flv_txt.textColor=0x000000; 130 flv=flv_txt.text; 131 stateVid.startPlay (ns,flv); 132 if (! playCheck) 133 { 134 playCheck=true; 135 } 136 } 137 else 138 { 139 flv_txt.textColor=0xcc0000; 140 flv_txt.text="Provide file name"; 141 } 142 } 143 function doRecord (e:MouseEvent):void 144 { 145 if (flv_txt.text != "" && flv_txt.text != "Provide file name") 146 { 147 ns.attachAudio (mic); 148 ns.attachCamera (cam); 149 flv_txt.textColor=0x000000; 150 flv=flv_txt.text; 151 stateVid.startRecord (ns,flv); 152 if (! playCheck) 153 { 154 playCheck=true; 155 } 156 } 157 else 158 { 159 flv_txt.textColor=0xcc0000; 160 flv_txt.text="Provide file name"; 161 } 162 } 163 function doAppend (e:MouseEvent):void 164 { 165 if (flv_txt.text != "" && flv_txt.text != "Provide file name") 166 { 167 ns.attachAudio (mic); 168 ns.attachCamera (cam); 169 flv_txt.textColor=0x000000; 170 flv=flv_txt.text; 171 stateVid.startAppend (ns,flv); 172 if (! playCheck) 173 { 174 playCheck=true; 175 } 176 } 177 else 178 { 179 flv_txt.textColor=0xcc0000; 180 flv_txt.text="Provide file name"; 181 } 182 } 183 function doPause (e:MouseEvent):void 184 { 185 if (pauseCheck) 186 { 187 pauseCheck=false; 188 if (playCheck) 189 { 190 stopBtn.visible=false; 191 } 192 stateVid.doPause (ns); 193 } 194 else 195 { 196 pauseCheck=true; 197 stopBtn.visible=true; 198 stateVid.doPause (ns); 199 200 } 201 } 202 function doStop (e:MouseEvent):void 203 { 204 playCheck=false; 205 stateVid.stopAll (ns); 206 vid.clear (); 207 setLocal (); 208 } 209 //Check connection, instantiate stream, 210 //and set up metadata event handler 211 function checkHookupStatus (event:NetStatusEvent):void 212 { 213 if (event.info.code == "NetConnection.Connect.Success") 214 { 215 ns = new NetStream(nc); 216 dummy=new Object(); 217 ns.client=dummy; 218 dummy.onMetaData=getMeta; 219 ns.addEventListener (NetStatusEvent.NET_STATUS,flvCheck); 220 } 221 } 222 //MetaData 223 function getMeta (mdata:Object):void 224 { 225 trace (mdata.duration); 226 } 227 //Handle flv 228 private function flvCheck (event:NetStatusEvent):void 229 { 230 switch (event.info.code) 231 { 232 case "NetStream.Play.Stop" : 233 stateVid.stopAll(ns); 234 setLocal(); 235 break; 236 case "NetStream.Play.StreamNotFound" : 237 stateVid.stopAll(ns); 238 flv_txt.text="File not found"; 239 setLocal(); 240 break; 241 } 242 } 243 } 244 }
The most important setup in testing this application is making sure you have the right Real-Time Messaging Protocol (RTMP) configuration. If you’re testing the application on your system using it as a Flash Media Server platform, you can just comment out Line 38 and remove the comment slashes from Line 40. That will work where your application is on the same server as your SWF file. Otherwise, adjust Line 38 to point to your FMS location.
Figure 10-8 shows what your application will look like once it’s running correctly. As you can see, it’s not much different on the outside because all you see are two additional buttons. However, its functionality has increased significantly with the ability to record and append video:
We’re used to thinking in noun-verb pairs, and so designs using the verb element of a pair may take a bit of getting used to. You can think of a state as a “state of action” such as a video playing or recording. However, you can also think of a state as a “state of rest” or “inactivity.” Likewise, transitions between states have a verb-centric character to them.
The focus of the State design pattern has always been on what is done rather than the characteristics of the objects. Additionally, statecharts are an incredibly easy and effective way to design applications. It begins with the part that is often left to the end of a project—making all of the connections between the different elements. Once the statecharts are complete, casting the project in State design patterns terms is equally easy because the structure of the class is so simply organized. Each state will have its own class, and the classes are made up of the different transitions defined in the state interface. All that’s left is the context class to pull it all together. So if your project involves different actions that need to be organized and coordinated, consider a state machine using the State design pattern.