Today I was working on finalising the video player for the competition web site 1Click2Fame.com. Since this video player will be multi purpose, for example, it will be used as a “chromeless” style player but may also have a “chrome” where the user can interact by voting for acts and so on, it needs the ability to record metrics such as user interaction and more importantly cue points.
The team at 1Click2Fame.com are not only interested in seeing what video a user watched, but how long they watched it for, especially if they voted for or against it winning. So cue cue points!
Now just a little about cue points in Actionscript 3: they’re not easy. You see in Actionscript 2 you could use the “Media.addCuePoint()” class function that allowed you to define the cue point’s string and the seconds where the cue point will be fired. It seems this function slipped out of the net when they wrote Actionscript 3, but we still live on.
So how do you add cue points in Actionscript 3? Well it seems you either:
- Bake them into your FLV media during encoding
- Use the FLVPlayback component in the Flash IDE
So neither option is particularly flexible. Well until you feast your eyes on my solution!
So we understand that cue points are moments in time on a video stream, so if we can hook into that stream and dispatch events when those moments of time are hit, then we have a cue point system! Let’s look at some code:
What we want to do is create a class-wide array, let’s call it “cuePoints”, and we’ll populate this array with the seconds of our cue points, like so:
1 2 3 4 5 6 7 8 9 10 11 12 |
So you would add a cue point like so:
1 2 3 | var cuePointsTest:CuePointsTest = new CuePointsTest(); cuePointsTest.addCuePoint( 12 ); |
So now we’ve added the cue points, we need to check against the time of the stream. When loading a video in Actionscript 3 you use the “NetStream()” class, here’s a nice tutorial. Now once we’ve got the stream playing, using “NetSteam.play( url )”, we can add an event listener to the class to fire a function on ever frame. We can then get the stream’s current time and check against the cue points, like so:
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 | package { import flash.display.Sprite; import flash.events.Event; import flash.events.NetStatusEvent; import flash.media.Video; import flash.net.NetConnection; import flash.net.NetStream; public class VideoPlayer extends Sprite { private var cuePoints:Array = [ ]; private var lastFiredCuePoint:Number = 0; private var metaData:Object = { }; private var video:Video = new Video(); private var stream:NetStream; public function VideoPlayer() { var connection:NetConnection = new NetConnection(); connection.connect( null ); stream = new NetStream( connection ); stream.client = { onMetaData: handleOnMetaData }; video.smoothing = true; video.attachNetStream( stream ); addChild( video ); } public function play(url:String):void { stream.play( url ); addEventListener( Event.ENTER_FRAME, handleEnterFrame ); } private function handleEnterFrame(e:Event):void { if ( cuePoints.length > 0 ) { checkForCuePoints(); } } private function checkForCuePoints():void { var time:Number = playingTime.current; var checkTime:Number = Math.floor( time ); var test:Number = ArrayUtil.search( cuePoints, checkTime ); var cuePoint:Number; if ( test > -1 ) { cuePoint = cuePoints[ test ]; if ( cuePoint > lastFiredCuePoint ) { dispatchEvent( new VideoPlayerEvent( VideoPlayerEvent.CUE_POINT, { point: cuePoint } ) ); lastFiredCuePoint = cuePoint; } } } public function addCuePoint(seconds:Number):void { cuePoints.push( seconds ); } private function handleOnMetaData(info:Object):void { metaData = info; } public function get playingTime():Object { var time:Object = { }; time.current = stream.time; time.total = metaData.duration; return time; } } } |
So just a quick break down:
This is a standard FLV player status, the bit we’re interested in is the “checkForCuePoints()” function. What happens here is that first we get the current time of the video, we then floor it, so get it to the nearest second, and then we test it. I’m using a utility I wrote called “ArrayUtil()”, the code is below, it simply searches an array for a value and returns the index of that value.
Once we have this value we can then test to see if that cue point has been fired, if it hasn’t we then fire it. And to make sure that we don’t fire the same cue point more than once, we assign the last fired cue point as the value of the variable “lastFiredCuePoint” for our test. Simple eh?
Now of course there is some latency, about 1/10th of a second, but that’s not bad is it? Here’s the ArrayUtil class I’ve written:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
So, I hope that makes sense. Remember, I’m going to publish the full player classes soon so don’t fret if you want them all! Also, if you are confused, just comment and I’ll happily help out.




Can you help me please?!??!!??! I am a beginner in Flash programming, so I don’t understand it fully.
I am using Dan Carr’s example of “Web video template” code and files for this presentation. (http://www.adobe.com/devnet/flash/articles/vidtemplate_corppreso.html)
1… First, if the video hasn’t loaded fully and the user hits the next button and ends up crossing into the video portion that has not been loaded yet, the whole app hangs up and then you can’t even hit the back button to go to a previous slide. You have to reload the page to start all over!!! How can I fix this issue? Can you make it such that the user cannot scroll to the portion that hasn’t been loaded yet??
2… Second, if the user scrolls to a part of the video in the seek bar, the slide doesn’t change unless the video plays again and it encounters a cue point. Is there a way to make it understand based on the portion of the video you are on that a particular slide needs to show there without coming across a cue point???
Please look at my presentation here for reference:
http://www.betalocalcvr609.net/pres.html
I really appreciate any help soon.
Thx,
Nitasha
Here’s my script code:
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
{
import fl.video.*;
import flash.events.*;
import flash.display.*;
import flash.display.Sprite;
import flash.net.navigateToURL;
import flash.net.URLRequest;
/********************************
* SyncVideoPreso (document class)
* The SyncVideoPreso class extends MovieClip
* to create a simple synchronized video interface
* on the main timeline of the FLA file. The class
* may also be assigned to a symbol.
* ---------------------
* Developed by Dan Carr (dan@dancarrdesign.com)
* For Adobe Systems, Inc., Flash Developer Center
* Last modified: July 8, 2009
*/
public class SyncVideoPreso extends MovieClip
{
//***************************
// Properties:
private var highlight_mc:MovieClip;
//***************************
// Constructor:
public function SyncVideoPreso():void
{
// Listen for all clicks
addEventListener(MouseEvent.CLICK, clickHandler);
// Listen for cuepoints
display.addEventListener(MetadataEvent.CUE_POINT, cuePointHandler);
}
//***************************
// Event Handlers:
// Create event handler functions to catch events from
// the video component and update the interface...
private function cuePointHandler( evt:MetadataEvent ):void
{
// Get the cue name from the event object
synchVideoToInterace( evt.info.name );
}
// All button clicks pass through this event handler. Any
// button ending in _btn is assumed to be a cuepoint button.
private function clickHandler(event:MouseEvent):void
{
// Get button name
var btnName:String = event.target.name;
if( btnName.indexOf("_btn") != -1 )
{
// Seek to cuepoint
seekToCuePoint(btnName.substr(0,-4));
}
if( btnName.indexOf("_link1") != -1 )
{
navigateToURL(new URLRequest("http://www.diabetesbehaviorchange.com"));
}
}
//***************************
// Public Methods:
// Navigation buttons call this function to trigger a change
// in the video and the view of the content
public function seekToCuePoint( cueName ) : void
{
// Find the cue point in the FLVPlayback component, seek
// to time, and highlight the next button for a quick redraw
var c:Object = display.findCuePoint( cueName );
if( c ){
// Validate that the requested time has downloaded
// in progressive video to avoid stalls in the playback.
if(!display.isRTMP )
{
var dltime:Number = display.totalTime * (display.bytesLoaded / display.bytesTotal);
if( dltime < c.time ){
return;
}
}
display.seekSeconds( c.time );
findNextButton( c.name );
}
}
// The natural flow through the video or jumping by seeking
// forward and backward will trigger the following function
public function synchVideoToInterace( cueName:String ):void
{
// Play if needed
if(!display.isRTMP && !display.playing ){
display.play();
}
// Go to labeled frame
gotoAndStop( cueName );
// Update the higlight position
findNextButton( cueName );
}
// Layout the button highlight when requested
public function findNextButton( cueName:String ):void
{
// Look for buttons named with the current cue point
// name plus the letters '_btn' (i.e. introduction_btn)
var cueBtn:DisplayObject = getChildByName(cueName+"_btn");
if( cueBtn )
{
// Attach the highlight clip if needed
if( highlight_mc == null ){
highlight_mc = new ThumbOutlineSelected();
addChild(highlight_mc);
}
highlight_mc.x = cueBtn.x;
highlight_mc.y = cueBtn.y;
}
}
}
}
Wow, this is a lot of code. It looks like you’re using the FLVPlayback component, which is a shame. Nevertheless, here are some tips to your questions:
1. Ok, what you need to do is to stop the FLVPlayback comp before you load in a new video, or, and this is better, make sure that when you load a new video you clear whatever’s there.
Posted: August 18, 2009 at 1:18 pm;2. Sounds like dodgy cue point systems, that’s because the FLVPlayback burns cue points in rather than using scripted ones. Try using my component and see if it works for you.
Thanks for posting this.
I noticed a couple of things: It won’t allow integers like 2.2, the timing is off and I don’t see the VideoPlayerEvent class.
2
3
4
5
6
7
8
9
10
11
12
13
14
15
myCuePoints.addCuePoint( 3, "boat" );
myCuePoints.addCuePoint( 10, "spear");
myCuePoints.addCuePoint( 11, "bowl");
public function addCuePoint(cSeconds:Number, cEvent:String):void {
cuePoints.push( cSeconds);
cueEvents.push(cEvent);
}
var time:Number=playingTime.current;
var checkTime=Math.floor(pTime);
var test:Number = ArrayUtil.search( cuePoints, checkTime );
Tracing("time = "+ checkTime+ " , a cue @ " +cuePoint+ " for the " +cueEvent);
time = 0 , a cue @ 2 for the squid
time = 2 , a cue @ 3 for the boat
time = 3 , a cue @ 10 for the spear
time = 10 , a cue @ 11 for the bowl
I’ve replaced the enterFrame event with a set interval and that didn’t help.
Posted: August 20, 2009 at 9:41 pm;How/why is 0 ==2, 2==3, 3==10 or 10==11 ?
Well the first thing to remember is that it can’t be 100% percent accurate as the check is ran on every frame rather than every second played on the video. However, it’s never been that inaccurate for me.
Just looking at your code, you’ve got the var “time” set by the playing time of the video and then you’re flooring a new var “pTime”. Is this a typo? Would be good to see more code so I can help.
I use this in the 1Click2Fame.com player that times when the info should disappear and when the vote overlay appears, as well as posting the time played and so on, so it does work. But I’m glad to help!
Posted: August 21, 2009 at 7:17 am;