Chapter 10. State Design Pattern

A State without the means of some change is without the means of its conservation.

Edmund Burke

All modern revolutions have ended in a reinforcement of the power of the State.

Albert Camus

Design Pattern to Create a State Machine

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.

Key Features

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.

The State Model

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.

Note

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.

Simple statechart showing states
Figure 10-1. Simple statechart showing states

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.

Transitions

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.

Triggers

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.

Statechart with states, transitions, and triggers
Figure 10-2. Statechart with states, transitions, and triggers

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.

State design structure

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:

State design pattern
Figure 10-3. State design pattern

Key OOP Concepts Used with 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.

Minimalist Abstract State Pattern

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:

Example 10-1. State.as
//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.

Managing All Those States: Hardworking Context Class

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:

State Design Pattern applied to video
Figure 10-4. State Design Pattern applied to video

Creating a context class

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:

Example 10-2. VideoWorks.as
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.

Completing and testing the abstract state machine

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.

Example 10-3. 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.

Example 10-4. 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:

Example 10-5. 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:

Example 10-6. TestState.as
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.

Video Player Concrete State Application

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.

Example 10-7. State.as
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.

Example 10-8. StopState.as
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.

Example 10-9. PlayState.as
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.

Example 10-10. VideoWorks.as
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.

Example 10-11. NetBtn.as
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       }
Example 10-12. BtnState.as
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:

Example 10-13. 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.

Expanding the State Design: Adding States

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.

Adding the Pause State to the Statechart

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.

Hierarchical statechart
Figure 10-5. Hierarchical statechart

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.

Adding New Behaviors

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:

Example 10-14. State.as
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:

Example 10-15. StopState.as
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:

Example 10-16. PlayState.as
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.

Example 10-17. PauseState.as
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.

Example 10-18. VideoWorks.as
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.

Example 10-19. 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.

Player with added Pause button
Figure 10-6. Player with added Pause button

Adding More States and Streaming Capabilities

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.

Setting Up Your ActionScript 3.0 Script for FMS2

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.

Note

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 Adaptable States

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.

Statechart with five states
Figure 10-7. Statechart with five states

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:

Example 10-20. State.as
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.

Example 10-21. StopState.as
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.

Example 10-22. PlayState.as
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.

Example 10-23. PauseState.as
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.

Example 10-24. RecordState.as
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.

Example 10-25. AppendState.as
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.

Example 10-26. VideoWorks.as
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.

Example 10-27. TestFMS.as
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:

Recording video
Figure 10-8. Recording video

Summary

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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.118.166.98