ActionScript 3.0 Memento Design Pattern: Flash Media Server 3 Application

Jumping Out of Sequence: Memento Brings You Back

In the last installment on the Memento Design Pattern, you saw an abstract minimalist version to get an idea of how the Memento saved state and got it back again. I used a string variable as the “state” to be saved and retrieved without breaking encapsulation. This time around I used the Memento to solve a more practical and definitely real world problem. How to allow a user to jump around a multimedia online presentation without getting lost. To get an idea of how this works, take a look at a working example of this application at:

http://www.sandlight.com/memento/

When you run the application, you can jump to another level, and then just click the Return to Last button, and it will take you back to your jump point. Also, I put up the zip file to save time in getting all of the code in. Figure 1 shows what you can expect to see:


Figure 1

You can download it at:

Download the FMS Memento Zip File

All of the files are in Flash CS3 format, but the ActionScript files are pretty easy to port over to Flex.

The Player

The FMS player is unremarkable design wise, but its job is nothing more than to serve as a client to use the Memento and provide me with a practical way to create an online learning system to provide the user with optimum usability. To use this, you’ll need to download and install FMS3 (you can use FMS2 if you have it installed). You can get the Developers version of FMS3 free at Adobe.com if you don’t have it. (When you install it on your Windows or Linux box, just ignore the part about a serial number and you automatically get the Developers version.) For you Flex developers, all you’ll need is a Button component. The rest is just coded in ActionScript 3.0.

?View Code ACTIONSCRIPT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
package 
{
	import flash.media.Video;
	import flash.net.NetConnection;
	import flash.net.NetStream;
	import flash.display.Sprite;
	import flash.events.MouseEvent;
	import flash.events.NetStatusEvent;
	import fl.controls.Button;
	import flash.net.ObjectEncoding;
 
 
	public class DoLoadFMS extends Sprite
	{
		private var rtmpNow:String;
		private var good:Boolean=false;
		private var loadSlide:LoadSlide;
		private var swf:String;
		private var nc:NetConnection;
		private var ns:NetStream;
		private var vid:Video;
		private var dummy:Object;
		private var cueMe:Object;
		private var firstCue:Boolean=false;
		private var btn:Button;
		private var btnI:Button;
		private var setBtn1:Button;
		private var setBtn2:Button;
		private var setBtn3:Button;
		private var setBtn4:Button;
		private var getBtn:Button;
		private var stopVid:Button;
		private var pauseVid:Button;
		private var vidState:Number;
		private var slideState:String;
		//
		private var ct:Caretaker;
		private var orig:Originator;
		//
		private var vidNow:Number;
 
		private function firstLoad (swf:String)
		{
			loadSlide=new LoadSlide(swf);
			addChild (loadSlide);
			loadSlide.x=288, loadSlide.y=36;
		}
 
 
		public function DoLoadFMS ()
		{
			//Uncomment following line if you're using FMS2 instead of FMS3
			//NetConnection.defaultObjectEncoding = flash.net.ObjectEncoding.AMF0;
 
			nc=new NetConnection();
			rtmpNow="rtmp://192.168.0.11/memento/dp";
			nc.connect (rtmpNow);
			nc.addEventListener (NetStatusEvent.NET_STATUS, checkConnect);
 
		}
		private function checkConnect (e:NetStatusEvent)
		{
			good=e.info.code == "NetConnection.Connect.Success";
			if (good)
			{
				ns=new NetStream(nc);
				ns.addEventListener (NetStatusEvent.NET_STATUS,cleanUp);
 
				vid=new Video  ;
				vid.width=240;
				vid.height=180;
				vid.x=18,vid.y=36;
				addChild (vid);
 
				btn=new Button  ;
				btn.label="Start";
				btn.width=50;
				btn.x=54,btn.y=230;
				addChild (btn);
				btn.addEventListener (MouseEvent.CLICK,startClass);
 
				stopVid=new Button  ;
				stopVid.label="Stop";
				stopVid.width=50;
				stopVid.x=114,stopVid.y=230;
				addChild (stopVid);
				stopVid.addEventListener (MouseEvent.CLICK,stopMem);
 
				pauseVid=new Button  ;
				pauseVid.label="Pause";
				pauseVid.width=50;
				pauseVid.x=174,pauseVid.y=230;
				addChild (pauseVid);
				pauseVid.addEventListener (MouseEvent.CLICK,pauseMem);
 
				btnI=new Button  ;
				btnI.label="Introduction";
				btnI.x=24,btnI.y=295;
				addChild (btnI);
				btnI.addEventListener (MouseEvent.CLICK,setVidState);
 
				setBtn1=new Button  ;
				setBtn1.x=24,setBtn1.y=325;
				setBtn1.label="Part I";
				addChild (setBtn1);
				setBtn1.addEventListener (MouseEvent.CLICK,setVidState);
 
				setBtn2=new Button  ;
				setBtn2.x=24,setBtn2.y=355;
				setBtn2.label="Part II";
				addChild (setBtn2);
				setBtn2.addEventListener (MouseEvent.CLICK,setVidState);
 
				setBtn3=new Button  ;
				setBtn3.x=24,setBtn3.y=385;
				setBtn3.label="Part III";
				addChild (setBtn3);
				setBtn3.addEventListener (MouseEvent.CLICK,setVidState);
 
				setBtn4=new Button  ;
				setBtn4.x=24,setBtn4.y=415;
				setBtn4.label="Part IV";
				addChild (setBtn4);
				setBtn4.addEventListener (MouseEvent.CLICK,setVidState);
 
				getBtn=new Button  ;
				getBtn.x=130,getBtn.y=295;
				getBtn.label="Return to Last";
				addChild (getBtn);
				getBtn.addEventListener (MouseEvent.CLICK,getVidState);
 
				//Capture metadata from FLV file
				dummy=new Object  ;
				ns.client=dummy;
				dummy.onMetaData=getMeta;
 
				//Get cue points
				cueMe=new Object  ;
				ns.client=cueMe;
				cueMe.onCuePoint=getAcue;
 
				swf="swf0.swf";
				slideState=swf;
				firstLoad (swf);
 
				ct=new Caretaker  ;
				orig=new Originator  ;
			}
		}
 
		private function startClass (e:MouseEvent)
		{
			ns.play ("mementoU");
			vid.attachNetStream (ns);
		}
 
		private function stopMem (e:MouseEvent)
		{
			ns.close ();
			vid.clear ();
		}
 
		private function pauseMem (e:MouseEvent)
		{
			ns.togglePause();
		}
 
		private function cleanUp (e:NetStatusEvent)
		{
			good=(e.info.code=="NetStream.Play.Stop");
			if (good)
			{
				ns.close ();
				vid.clear ();
			}
		}
 
		private function getMeta (mdata:Object):void
		{
			//All metadata can be captured here.
			//e.g. mdata.duration;
		}
 
		private function getAcue (cue:Object)
		{
			swf=cue.name;
			swf+= ".swf";
			slideState=swf;
			afterLoad (swf);
		}
		private function afterLoad (swf:String)
		{
			loadSlide.undo ();
			loadSlide=new LoadSlide(swf);
			addChild (loadSlide);
			loadSlide.x=288,loadSlide.y=36;
		}
 
		private function setVidState (e:MouseEvent):void
		{
			vidState=ns.time;
			orig.setState (vidState,slideState);
			ct.addMemento (orig.createMemento());
 
			switch (e.target.label)
			{
				case "Introduction" :
					ns.seek (1);
					slideState="swf0.swf";
					afterLoad (slideState);
					break;
				case "Part I" :
					ns.seek (232.498);
					slideState="swf1.swf";
					afterLoad (slideState);
					break;
				case "Part II" :
					ns.seek (293.355);
					slideState="swf2.swf";
					afterLoad (slideState);
					break;
				case "Part III" :
					ns.seek (384.6);
					slideState="swf3.swf";
					afterLoad (slideState);
					break;
				case "Part IV" :
					ns.seek (461.04);
					slideState="swf4.swf";
					afterLoad (slideState);
			}
		}
 
		private function getVidState (e:MouseEvent)
		{
			orig.setMemento (ct.getMemento("allState"));
			vidNow=orig.useVid();
			ns.seek (vidNow);
			swf=orig.useSlide();
			afterLoad (swf);
		}
	}
}

You can see in the setVidState and getVidState methods that the Memento has been called to preserve this client’s state. The state that will be a “snapshot” of the state is the video’s current position and the swf file that is loaded as a presentation slide. To help loading everything, the following class acts as a loyal assistant:

?View Code ACTIONSCRIPT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package 
{
	import flash.display.Sprite;
	import flash.display.Loader;
	import flash.net.URLRequest;
 
	public class LoadSlide extends Sprite
	{
		private var loader:Loader;
		private var url:URLRequest;
 
		public function LoadSlide (swf:String)
		{
			loader=new Loader();
			url=new URLRequest("VideoCode/"+swf);
			loader.load (url);
			addChild (loader);
		}
 
		public function undo()
		{
			loader.unload();
		}
	}
}

That’s it for the player. All that’s left is to build a Memento pattern that it can use.

Originator with Multiple States

In making the Originator, I did not want it to be the client. It makes perfect sense for the Originator to be a client,but the client is not part of the design pattern (as is the case in many) and I wanted the flexibility to use it independent of what happens to be in the Originator. I added a couple of getters to return a saved state back to a client. It make it easier for flexible use. Also, notice that two different types of variables—a string and a number—make up the state that will be placed into a memento.

?View Code ACTIONSCRIPT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package 
{
	public class Originator
	{
		private var mstate:String;
		private var vidstate:Number;
 
		public function setState (vidstate:Number,mstate:String):void
		{
			this.mstate=mstate;
			this.vidstate=vidstate;
		}
 
		public function createMemento ():Memento
		{
			return new Memento(vidstate,mstate);
		}
 
		public function setMemento (m:Memento)
		{
			vidstate=m.getVid();
			mstate=m.getSlide();
		}
 
		public function useVid ():Number
		{
			return vidstate;
		}
 
		public function useSlide ():String
		{
			return mstate;
		}
	}
}

If you look at the Originator in abstract Memento design pattern in the initial Memento entry, other than two variables instead of one and the added methods, it’s pretty much the same.

More Memento

The Memento class now has to deal with two different state parameters, each a different type. That’s not a problem. Two getter methods are set up and two parameters are in the constructor function instead of one. Again, it’s not very different from the abstract Memento example that appears on this blog.

?View Code ACTIONSCRIPT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package 
{
	public class Memento
	{
		private var vidstate:Number;
		private var mstate:String;
 
		//SetState()
		public function Memento (vState:Number, clipSetter:String)
		{
			vidstate=vState;
			mstate=clipSetter;
		}
 
		public function getVid():Number
		{
			return vidstate;
		}
		//GetState()
		public function getSlide ():String
		{
			return mstate;
		}
	}
}

The //GetState comment shows what replaced the single GetState method in the abstract Memento class.

Caretaker with Associative Array

To stash the dual states, I decided to use an associative array. In ActionScript, an associative array is nothing more than an instance of an Object and you can reference the elements using string names rather than numbers as is the case with the Array class. The associative array may not be as handy for working with a stack, but because I only want the application to save the last state of the online learning application, I could reuse the same element over and over. Here’s the new caretaker.

?View Code ACTIONSCRIPT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package 
{
	public class Caretaker
	{
		private var storage:Object;
 
		public function Caretaker()
		{
			storage={};
		}
 
		public function addMemento (m:Memento):void
		{
			storage["allState"]=m;
		}
 
		public function getMemento (id:String):Memento
		{
			return storage[id];
		}
	}
}

The Memento is a valuable tool for capturing state and keeping it encapsulated. It took me about two minutes to set up a state-saving application, but it was not encapsulated—exposing the data to the vagaries of other program elements. However, once you have your hands on a generic Memento, they are very easy to adjust to new state demands and what is stored in a state.

  • Share/Bookmark

Related posts:

  1. ActionScript 3.0 Memento Design Pattern: Encapsulating Saved States
  2. Let’s Get Rid of Conditionals!
  3. The ActionScript 3.0 Flyweight Saga: Part II Extrinsic States

7 Responses to “ActionScript 3.0 Memento Design Pattern: Flash Media Server 3 Application”


  • plz.. checking download link..

  • Hi Kan,

    Sorry about that. For some reason the last (Part IV) slide either doubles up or won’t leave. I was going to wait until I could fix it and the put it up in the zip file. However, I’ve been pretty swamped; so I just corrected the URL to the zip file, and as soon as I can fix it I will.

    Bill

  • thanks Bill!! :)
    Thanks a lot for your article..

  • Great article! useful tips.

  • HI,
    I gone through the whole code this is very simple and sytematic to understand.

    Thanks
    flashDuniya.com

  • Would I be correct it assuming that the caretaker could handle caching states to disk as part of it’s storage management? I’m guessing that would most likely be needed when dealing with large complex states like in an image editor where a “state” would require storing the BitmapData of an image for a particular point in time.

    Is that correct?

  • Hi Will,

    The GoF describe the intent of the Memento as follows:

    Without violating encapsulation, capture and externalize an object’s internal state so that the object can be restored to this state later.

    Given that intent, it would seem that you could provide a permanent storage of a graphic state. Since my interests are in video (as shown in the example!) I would be very interested in storing a point in the video where a user could close it and come back and resume it later. For me, it would simply be a matter of getting a point in the video and saving it to disk (probably using FMS persistent shared object), but I’m not familiar enough with what you’re doing with graphics to know how you’d go about doing the same thing.

    By the way when looking around for a suitable answer to your query, I ran across a book by Christopher Lasater, Design Patterns. I liked his description of the Memento, and you might find it useful as well. You can download it free here. The examples are in C#, but you might be able to find some useful material there. Also, if you do create a Memento for your graphic storage, perhaps you’d let us post it here as an example our readers would like.

    Kindest regards,
    Bill

Leave a Reply