Saturnboy
 11.4
Code
off

Continuing my Quest for Cool, one of my favorite aspects of data visualization is user interaction. Interactive visualizations are not only cool, but they can be extremely useful getting information to the user.

Here is a screenshot from Google Analytics showing the main line chart of visitors:

google-analytics

When you rollover one of the points, you get a popup telling you the exact number of visitors on that date. This simple rollover, while cool, is not particularly useful to me. If I wanted to know exact numbers, I’d look at an Excel printout. Thankfully, we can use Axiis to provide something much better.

Column Stack Chart

I’ll use Axiis to show two different visualization options for the same data. I made some artificial visitor data per day. Here it is as a vanilla line chart I grabbed from Excel:

data

Not very exciting, but the classic weekend lulls make our dataset a little hard to follow. In XML, it looks like this:

<visitors>
    <week label="A">
        <day label="Sun" val="0.2" />
        <day label="Mon" val="1.6" />
        <day label="Tue" val="2.0" />
        <day label="Wed" val="2.4" />
        <day label="Thu" val="1.8" />
        <day label="Fri" val="1.6" />
        <day label="Sat" val="0.4" />
    </week>
    <week label="B">
        <day label="Sun" val="0.3" />
        <day label="Mon" val="1.2" />
        <day label="Tue" val="1.8" />
        ...
    </week>
    ...
</visitors>

Since the data per day is a little annoying, let’s try to smooth it out by collecting up the day data into weeks. This is easy to do in Axiis by using the ColumnStack layout. Recall that Layouts in Axiis specify how incoming data will be rendered to the display. Here’s an abbreviated snippet of code showing the relevant parts:

<axiis:DataCanvas id="dc" ...>
    <axiis:layouts>
        <axiis:HBoxLayout id="myLayout" ...>
 
            <axiis:layouts>
                <axiis:ColumnStack id="myStack" ...
                    dataProvider="{myLayout.currentDatum.day}"
                    dataField="val" />
            </axiis:layouts>
 
            <axiis:drawingGeometries ... />
        </axiis:HBoxLayout>
    </axiis:layouts>
</axiis:DataCanvas>

First, we use an HBoxLayout layout to render our weeks into columns (the dataProvider is the visitors.week array), and inside that we use a ColumnStack layout to render our days stacked one on top of another (the dataProvider is the current week’s day array).

Here is the result (view source enabled):

Flash is required. Get it here!

The column stack chart is nice because it enables us to distinguish the macro trend over the weeks, traffic is obviously dying off from week A to week H. But it does a poor job illuminating intra-week data. For example, it’s hard to tell how Monday of week A compares to Monday of week B.

Interactive Column-in-Column Chart

Another option to visualize our simulated visitor data is empower the user. Start with a super simple column chart of the aggregated week data, and allow the user to drill down into the day data via some interaction. The implementation I’ve chosen below uses an inset column chart to show the day data when the user mouses over a week column.

In order to display week data, we first need to pre-process our day data to aggregate it into weeks. Axiis makes this trivial with DataSet:

var ds:DataSet = new DataSet();
ds.processXmlString(myXML.toXMLString());
ds.aggregateData(ds.data.object.visitors, "week.day", ["val"]);

The aggregateDate() method walks our XML hierarchy and rolls up aggregates of day.val at every level. When processing is complete, visitors has a new aggregates object that contains day_val_sum (and min, max, and average) for all the days in the entire data set. And one level down, week also has a new aggregates object that contains day_val_sum for just the days in that week.

Next, we construct our visualization using a pair of DataCanvas objects and a pair of BaseLayout layouts, one for the main column chart showing aggregated week data, and the other for an inset column chart showing just the current week’s day data. Here I’m using the same column-chart-the-long-way code as I used last post.

<axiis:DataCanvas id="dc" ...>
    <axiis:layouts>
        <axiis:BaseLayout id="myLayout" ...
            itemMouseOver="mouseOverHandler(event)">
 
            <axiis:drawingGeometries>
                <degrafa:RegularRectangle id="myBar" ... />
                <degrafa:RasterText id="myBarLabel"... />
            </axiis:drawingGeometries>
 
            <axiis:referenceRepeater ... />
 
            <axiis:states>
                <axiis:State
                    enterStateEvent="mouseOver"
                    exitStateEvent="mouseOut" ... />
            </axiis:states>
        </axiis:BaseLayout>
    </axiis:layouts>
</axiis:DataCanvas>
 
<axiis:DataCanvas id="dc2" ...>
    <axiis:layouts>
        <axiis:BaseLayout id="myLayout2" ...>
 
            <axiis:drawingGeometries>
                <degrafa:RegularRectangle ... />
                <degrafa:RasterText ... />
            </axiis:drawingGeometries>
 
            <axiis:referenceRepeater ... />
        </axiis:BaseLayout>
    </axiis:layouts>
</axiis:DataCanvas>

The interaction is achieved by the itemMouseOver event on the main column chart’s layout. Note that we are not listening for events on the columns themselves, rather we listen on the parent BaseLayout. The layout is responsible for attaching individual listeners to all of its children as they are rendered to the display. Layouts have events for all possible mouse interactions, including click, double-click, mouse over, mouse out, etc. This is one of the nicest features of Axiis. Since user interaction is managed by the layout and not by the rendered geometry, I change whatever I want about the geometry or how it is rendered and user interactions are preserved. The code is also pleasantly clean.

What’s more, the main column chart uses an Axiis State to create a visual rollover effect on the columns. The State is used to trigger some visual changes (column stroke weight is increased and label font is bolded) on mouseOver and then reverts the changes on mouseOut.

Here is the event handler attached to the itemMouseOver event on the layout:

private function mouseOverHandler(e:LayoutItemEvent):void {
    //change dataset to current mouseOver'd column
    myLayout2.dataProvider = e.item.data.day;
 
    //set color
    var color:uint = myPalette.colors[e.item.index];
    myPalette2.colorFrom = color;
    myPalette2.colorTo = PaletteUtils.darker(PaletteUtils.darker(color));
 
    //hide text
    insetText.visible = false;
}

The LayoutItemEvent event contains all kinds of useful information when it arrives at our handler. First, we set the dataProvider of our inset chart using the day data from the highlighted week column. Next, we use some Degrafa color magic to set the colors of the inset’s columns from the color of the highlighted week column. And finally, we hide the reminder text.

Here is the result (view source enabled):

Flash is required. Get it here!

The end result is a fairly cool visualization that does a good job initially hiding unnecessary information, but later reveals it upon user interaction. For example, toggle back and forth between week B and week C. You can easily see a shift in traffic to the first half of week C compared to week B.

Postscript

I recently discovered another amazing data visualization framework. Protovis is an awesome Javascript library created by some hella smart dudes at Stanford. If I ever needed to do any visualizations on the web, and was banned from using Flex, I’d definitely use Protovis.

Files

 10.26

Axiis is an advanced data visualization framework built on top of Degrafa. And when I say advanced, I mean really advanced. I found my way to Axiis because I wanted the maximum amount of visual control that I could get. Axiis is designed to support any kind of visualization you could possibly imagine, but I don’t really care about that part of the framework. Instead, I just want to take your average boring graph and make it way cool.

Lately, I’ve been working on a project at work that places a real premium on cool. This post has nothing to do with that project, of course. It’s all about the quest for cool and my personal journey with Axiis.

Simple Ass Column Chart

In the beginning, I had a simple data set and just wanted create a basic column chart. I tried to follow the Intro and Tutorial, but I must admit I got a little lost the first time through. Hopefully, this will be an even easier introduction to the Axiis framework.

Here is our data in MXML:

<mx:XML id="myXML">
    <columns>
        <col label="A" val="10" />
        <col label="B" val="9" />
        <col label="C" val="7" />
        <col label="D" val="5.5" />
        <col label="E" val="6" />
        <col label="F" val="3" />
        <col label="G" val="4" />
        <col label="H" val="2.5" />
    </columns>
</mx:XML>

We want to render this data to the screen as a bunch of columns (aka RegularRectangles). So, the next thing that we need to do is process our data and feed it into an Axiis Layout. A Layout is the main element in any Axiis chart; it takes incoming data and renders it to the display.

Here we process our data and set our Layout‘s dataProvider:

private function complete():void {
    var ds:DataSet = new DataSet();
    ds.processXmlString(myXML.toXMLString());
    myLayout.dataProvider = ds.data.object.columns.col;
    dc.invalidateDisplayList();
}

And here is the basic shell of our entire application:

<?xml version="1.0" encoding="utf-8"?>
<mx:Application ...>
    ...
 
    <axiis:LinearScale id="vScale" />
 
    <axiis:DataCanvas id="dc">
        <axiis:layouts>
            <axiis:BaseLayout id="myLayout">
                <axiis:drawingGeometries>
                    <degrafa:RegularRectangle id="myBar"... />
                    <degrafa:RasterText id="myBarLabel" ... />
                </axiis:drawingGeometries>
 
                <axiis:referenceRepeater>
                    <axiis:GeometryRepeater>
                        <axiis:geometry>
                            <degrafa:RegularRectangle ... />
                        </axiis:geometry>
                        <axiis:modifiers>
                            <axiis:PropertyModifier ... />
                        </axiis:modifiers>
                    </axiis:GeometryRepeater>
                </axiis:referenceRepeater>
            </axiis:BaseLayout>
        </axiis:layouts>
 
        <axiis:backgroundGeometries>
            <axiis:VAxis ... />
        </axiis:backgroundGeometries>
    </axiis:DataCanvas>
</mx:Application>

Stepping through the code element by element, we see:

  1. LinearScale – We use a LinearScale to convert from data values to screen values (aka pixels). So a data value of 5.5 might translate into 11 pixels, 42 pixels, or 55 pixels depending on the scale.
  2. DataCanvas – The DataCanvas contains all Axiis graph elements. This is analogous to Flex 4′s Graphic container for FXG elements, and Degrafa’s Surface container for drawing elements.
  3. LayoutBaseLayout is the parent of all layouts and the most flexible.
  4. drawingGeometries – We draw each column as a RegularRectangle and each label as a RasterText. Positioning and sizing is guided by the reference geometry created by the referenceRepeater. Column height is computed using the current value of the data from the Layout converted to screen coordinates by the LinearScale.
  5. referenceRepeater – The reference geometry and repeated property combine to create a visualization for the data. So, repeating rectangles horizontally gives a column chart, repeating rectangles vertically gives a bar chart, repeating line segments horizontally given a line chart, etc.
  6. VAxis – Draw a vertical axis underneath our graph layer by using the backgroundGeometries layer of the DataCanvas. I like to use a negative x value to shift the axis left to get it out from underneath the chart (and compensate by shifting the entire DataCanvas right with a positive x value).

That’s it for the high-level stuff, the dirty little details are in the code. A little trial-and-error went a long way to teach me what the hell each of the various parameters actually did. The Intro and Tutorial article does a good job covering some of the details and tricks like the vertical flip trick, and I also recommend Tom’s session from AdobeMAX.

User Interaction Coolness

Last, we’ll add a little dash of coolness to our application with a simple rollover effect on our columns. Using an Axiis State, we modify a few properties of our drawingGeometries on the mouseOver event to create a rollover effect.

The State code:

<axiis:BaseLayout id="myLayout" ...>
    ...
    <axiis:states>
        <axiis:State enterStateEvent="mouseOver"
                exitStateEvent="mouseOut"
                targets="{[barFill,barStroke,myBarLabel]}"
                properties="{['alpha','weight','fontWeight']}"
                values="{[1,2,'bold']}"/>
    </axiis:states>
</axiis:BaseLayout>
Cool Colors

Using Degrafa geometry to build our chart gives us total control of the shape and design, but if we really want something cool, we need to use color. For our column chart, we’ll use an Axiis LayoutAutoPalette element to create a smooth color gradient for each bar.

Here is the code:

<axiis:LayoutAutoPalette id="myPalette" layout="{myLayout}"
        colorFrom="0xFF99FF"
        colorTo="0x6699FF"/>
 
<degrafa:SolidFill id="barFill" color="{myPalette.currentColor}" />

The LayoutAutoPalette interpolates from a starting color to an ending color for each data value in the Layout. We then feed the palette’s current color into a standard Degrafa SolidFill. Lastly, the fill is applied to the RegularRectangle in the drawingGeometries section of our Layout to create a pretty gradient of bars from left to right. Note that color is not proportional to the data value, but Axiis certainly provides the functionality to make a column chart with bars colored by height.

The Result

Here it is, rollover effect and all (view source enabled):

Flash is required. Get it here!
Files

 6.10

The dream of SVG was probably born sometime in the late 90′s. Version 1.0 arrived in 2001, followed by the current version, SVG 1.1, in 2003. For anyone keeping score at home, that’s over 6 years ago, which is like 120 years ago in internet dog years. Also for those at home, SVG is a portable XML-based vector graphics format (it also does raster graphics), but you’d probably not be reading this if you didn’t already know.

Nowadays, I’m finally seeing some SVG in use. It works natively in all the real browsers (obviously not IE, which requires a plugin), and even on some mobile devices. But most importantly to me, I see it in my day-to-day work in Degrafa, and with the beta release of Flex 4, in Catalyst and FXG.

The vast majority of my experience in SVG is with paths (SVG Spec, § 8) and to a lesser extent Transforms (§ 7) and Filters (§ 15). Thankfully, these are some of the most useful and important pieces of SVG, and they all have nice one-to-one mappings to components in Degrafa and FXG.

SVG Path Primer

In SVG, a path is the outline of some object. It is described as a series of segments, where each segment can be different, either a line, curve, or arc. Path data is most often given in shorthand syntax as a series of commands followed by coordinates (we’ll ignore the long form for now). Let’s illuminate the discussion with some examples.

SVG:

<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
    "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg" version="1.1"
    width="200" height="200">
  <path d="M 0,0 L 100,0 L 100,100 L 0,100 z"
      fill="#EECCEE"
      stroke="#FF00FF"
      stroke-width="3" />
</svg>

Degrafa in Flex 3:

<?xml version="1.0" encoding="utf-8"?>
<mx:Application
        xmlns:mx="http://www.adobe.com/2006/mxml"
        xmlns:Degrafa="http://www.degrafa.com/2007"
        layout="absolute">
 
    <Degrafa:GeometryComposition graphicsTarget="{[cnv]}">
        <Degrafa:Path data="M 0,0 L 100,0 L 100,100 L 0,100 z">
	        <Degrafa:fill>
	            <Degrafa:SolidFill color="#EECCEE" />
	        </Degrafa:fill>
	        <Degrafa:stroke>
	            <Degrafa:SolidStroke color="#FF00FF" weight="3" />
	        </Degrafa:stroke>
        </Degrafa:Path>
    </Degrafa:GeometryComposition>
 
    <mx:Canvas id="cnv" />
</mx:Application>

MXML Graphics (aka FXG) in Flex 4:

<?xml version="1.0" encoding="utf-8"?>
<s:Application
        xmlns:fx="http://ns.adobe.com/mxml/2009"
        xmlns:s="library://ns.adobe.com/flex/spark"
        xmlns:mx="library://ns.adobe.com/flex/halo">
 
    <s:Graphic>
        <s:Path data="M 0,0 L 100,0 L 100,100 L 0,100 z">
            <s:fill>
                <mx:SolidColor color="#EECCEE" />
            </s:fill>
            <s:stroke>
                <mx:SolidColorStroke color="#FF00FF" weight="3" />
            </s:stroke>
        </s:Path>
    </s:Graphic>
</s:Application>

The examples above use the shorthand path syntax to draw a 100px square that starts at the coordinate origin (0,0), which is the upper left corner in SVG and Flex. Beware the coordinate origin when translating Inkscape SVG to Flex. The other interesting thing to note is the amazing similarity between Degrafa and FXG. Who knew all my time learning Degrafa will instantly translate to Flex 4 and FXG? Awesome!

SVG Path Shorthand

Here’s a quick overview of shorthand syntax for SVG path data:


Move
M <x,y>
Move the pen to the given point.

Line
L <x,y>+
Draw a line to given point. Multiple points may be specified to draw polyline.

Horizontal Line
H <x>
Draw a horizontal line to given coordinate.

Vertical Line
V <y>
Draw a vertical line to given coordinate.

Quadratic Bezier
Q <cx,cy x,y>+
Draw a quadratic Bezier curve to given coordinate using a control point. Multiple Beziers may be specified to draw polycurve.

Cubic Bezier
C <cx1,cy1 cx2,cy2 x,y>+
Draw a cubic Bezier curve to given coordinate using two control points. Multiple Beziers may be specified to draw a polycurve.

Arc
A <rx,ry rot,lrg,swp x,y>
Draw elliptical arc to the given point.

Close
Z
Close the path.

Alas, the beta version of FXG does not support the Arc segment type, which I suspect is due to lack of support for arbitrary arcs in the underlying Flash Player rendering engine but I don’t know for sure. Thankfully, Degrafa offers full arc support (thanks Greg!). If you really need to draw arcs in FXG, for stuff like pie wedges, and are unafraid of getting into a cage match with your trigonometry textbook, you can do a good job approximating arcs with cubic Bezier curves. Alternately, you can just use Degrafa once it gets ported to Flex 4. Lastly, using uppercase for the segment type specifies absolute coordinates. This is the format commonly used by Illustrator and Inkscape when exporting drawings to SVG. One can easily switch to relative coordinates by just switching the commands to lowercase, but I would try to avoid it if at all possible as it tends to make one’s head hurt.

The Many Shapes of a Square

All of the squares above, use this shorthand data:

M 0,0 L 100,0 L 100,100 L 0,100 z

First, a Move to set the pen at the origin. Then, a Line right to (100,0), followed by a Line down to (100,100), followed by a Line left to (0,100). Then, a close (z) to return to the origin.

I can drop the commas if I want:

M 0 0 L 100 0 L 100 100 L 0 100 z

Or drop all but the first Line to make a polyline:

M 0,0 L 100,0 100,100 0,100 z

Or use Horizontal Line and Vertical Line:

M 0,0 H 100 V 100 H 0 z

Or even use relative coordinate (which makes my head hurt a little):

m 0,0 l 100,0 l 0,100 l -100,0 z
Curves

Straight lines are cool, but the real fun in life lies in the curves. Cubic Bezier curves should be very familiar to anyone who’s used a vector drawing program. Let’s replace the first segment in our square with a cubic Bezier segment. Now, the SVG shorthand becomes:

M 0,0 C 25,-25 50,25 100,0 L 100,100 L 0,100 z

When rendered, we get this:

square-funny

The shorthand command says curve to (100,0), but start out heading towards control point #1 at (25,-25) and end up coming in from control point #2 at (50,25).

Arcs

Again, let’s replace the first segment in our square with an arc segment. Now, the SVG shorthand becomes:

M 0,0 A 50,25 0 0,1 100,0 L 100,100 L 0,100 z

When rendered, we get this:

square-arc

The shorthand command says arc to (100,0), with an x-radius of 50 and a y-radius of 25, with a rotation of 0. The large-arc and sweep flags are a little confusing so you’ll want to review the SVG Spec, § 8.3.8 if you need to get down and dirty with arcs.

Conclusion

I’m a firm believer in “right tool for the job.” So, when in comes to getting SVG path data into Flex, I’m definitely going to use Illustrator or Inkscape as much as possible, and in the future I might just use Catalyst for everything. But there are a few important situations where the Flex developer absolutely must know SVG. First, if you want to do any kind of path morphing (like this), you’ll need precision control over your path segments. And second, if you want to do any dynamic path generation (like building a multi-level radial menu on the fly – which sounds like a good topic for a future post), you’ll need to manually construct your SVG paths.

Files

 6.3

Going from SVG data to a Degrafa Path couldn’t be easier: just copy & paste. You can watch this video tutorial or you can check out this demo.

But there is one trick for Inkscape: even though the coordinate origin on the Inkscape document is the normal cartesian origin in the bottom left and the y-axis points up, the SVG output always uses the upper left corner of the document as the origin and the y-axis points down (per the SVG Spec, § 7.3).

To demonstrate, I created a new document in Inkscape, set my dimensions to 500 x 500, and placed a simple path (which happens to be a square) in the upper left corner:

square

You can see by the rulers in Inkscape that the square’s origin is at (0,500).

If we save our square and examine the SVG output, we see:

<svg ...>
  <g ...>
    <path d="M 0,0 L 100,0 L 100,100 L 0,100 L 0,0 z" />
  </g>
</svg>

If we ignore everything in the file except the relevant path data, we can see the very first path command is M 0,0 which is path-speak for move to (0,0). This is exactly as expected from the SVG spec: upper left is the coordinate origin. The cartesian origin in Inkscape is bogus!

Next, we can just copy the path data from the SVG file and paste it into the data attribute of a Degrafa Path component.

<Degrafa:Path data="M 0,0 L 100,0 L 100,100 L 0,100 L 0,0 z">
    <Degrafa:fill>
        <Degrafa:SolidFill color="#EECCEE" />
    </Degrafa:fill>
    <Degrafa:stroke>
        <Degrafa:SolidStroke color="#FF00FF" weight="3" />
    </Degrafa:stroke>
</Degrafa:Path>

Give it a fill color and a stroke color, and we get a pretty purple square. Now I know my Degrafa Path component will have a square in the upper left, because I know my square was in the upper left in Inkscape. Nice and easy.

Files

 6.1
Code
off

The plan is simple, take the nice Degrafa-skinned components from Part 1 and assemble them into a video player powered by the OvpNetStream class from the Open Video Player project.

Design

I knew right away that the design was not going to have any right angles, but I also didn’t want to go with rounded rectangles everywhere. Modern TVs tend to have a lot of soft rounded edges, so I decided to go with a more vintage look. So I fired up Inkscape and got to work:

tv

Implementing the video player design above in Degrafa, the cabinet mapped to a RoundedRectangle, and the screen & antenna became Paths. You can read more about about translating SVG to Degrafa in my Inkscape SVG to Degrafa Path article. But for now, let’s focus on the resulting Degrafa code:

<?xml version="1.0" encoding="utf-8"?>
<mx:Application
        xmlns:mx="http://www.adobe.com/2006/mxml"
        xmlns:Degrafa="http://www.degrafa.com/2007"
        layout="absolute">
 
    <Degrafa:GeometryComposition graphicsTarget="{[box]}">
        <!-- Antenna -->
        <Degrafa:Path data="...path data...">
            <Degrafa:transform>
                <Degrafa:TranslateTransform x="15" />
            </Degrafa:transform>
        </Degrafa:Path>
 
        <!-- TV cabinet -->
        <Degrafa:RoundedRectangle y="144" width="350" height="300"
                cornerRadius="20" />
 
        <Degrafa:fill>
            <Degrafa:SolidFill color="#333333" />
        </Degrafa:fill>
        <Degrafa:stroke>
            <Degrafa:SolidStroke color="#FF00FF" weight="4" alpha="0.4" />
        </Degrafa:stroke>
    </Degrafa:GeometryComposition>
 
    <!-- TV Screen -->
    <Degrafa:GeometryComposition graphicsTarget="{[tvscreen]}">
        <Degrafa:Path data="...path data...">
            <Degrafa:fill>
                <Degrafa:SolidFill color="#FF99FF" />
            </Degrafa:fill>
            <Degrafa:stroke>
                <Degrafa:SolidStroke color="#FF00FF" weight="2" alpha="0.4" />
            </Degrafa:stroke>
            <Degrafa:filters>
                <mx:GlowFilter color="#EEEEEE" alpha="0.2" blurX="16" blurY="16" />
            </Degrafa:filters>
        </Degrafa:Path>
    </Degrafa:GeometryComposition>
 
    <mx:Canvas width="350" height="444"
            horizontalCenter="0" verticalCenter="0">
        <mx:Canvas id="box" />
        <mx:Canvas x="50" y="174" id="tvscreen" />
    </mx:Canvas>
</mx:Application>

I ended up using a pair of GeometryCompositions to wrap my three shapes to help keep my fills, strokes, and filters organized. It made sense to do it this way, but I won’t claim it’s the best way. Throw the control bar on below the TV screen, and the design is done.

Backend

The backend is build on the OvpNetStream class provided by the Open Video Player project. OvpNetStream extends NetStream and smooths out some of the rough edges as I discussed previously. Basically, it provides a sane interface (no need to construct a dynamic object with function callbacks) and useful events (like metadata and progress events).

For this demo, we simply instantiate OvpNetStream on creationComplete and wire up all the event handlers:

private function complete():void {
    nc = new OvpConnection();
    nc.addEventListener(OvpEvent.ERROR, errorHandler);
    nc.addEventListener(NetStatusEvent.NET_STATUS, connStatusHandler);
    nc.connect(null);
 
    ns = new OvpNetStream(nc);
    ns.addEventListener(OvpEvent.ERROR, errorHandler);
    ns.addEventListener(NetStatusEvent.NET_STATUS, streamStatusHandler);
    ns.addEventListener(OvpEvent.NETSTREAM_METADATA, streamMetadataHandler);
    ns.addEventListener(OvpEvent.PROGRESS, streamProgressHandler);
    ns.addEventListener(OvpEvent.COMPLETE, streamCompleteHandler);
 
    vid = new Video();
    vid.attachNetStream(ns);
    vidContainer.addChild(vid);
}

The most interesting events are the metadata and progress events. The metadata event delivers the duration of the video and its size. The progress event arrives periodically (theoretically every 100ms by default, but in reality I see them come in just a couple of times per second) and delivers the current video time.

Control Bar

The control bar consists of three components: a play-pause button, a scrubber, and a volume slider. They were skinned using Degrafa in Part 1. In order to control video playback, we need to wire the control bar components to the instance of OvpNetStream created above.

Here are the event handlers for the three control bar components:

// PlayPause event handler
private function playPauseClick():void {
    if (first) {
        first = false;
        ns.play(filename);
    } else {
        ns.togglePause();
     }
}
 
// Scrub event handlers
private function scrubPress():void {
    ns.pause();
    playPause.selected = false;
}
private function scrubDrag():void {
    ns.seek(scrub.value);
}
private function scrubRelease():void {
    ns.togglePause();
    playPause.selected = true;
}
 
// Volume event handler
private function volumeChange():void {
    ns.volume = volume.value;
}

In the playPauseHandler(), the initial click calls play() which actually loads the video (and then starts playback), all subsequent clicks just toggle between play or pause. For the scrubber handlers, I chose to break them up into three separate steps: on mouse down pause the video, on mouse up restart playback, and on drag attempt to seek to the to the new time.

That’s it. Here is the resulting Degrafa-skinned video player (view source enabled):

Flash is required. Get it here!

Click Play to start playing Elephants Dream (which is the “world’s first open movie,” and pretty cool too). Right away you’ll notice some visual issues because the dimensions of the video are unknown until the metadata arrives. Also, scrubbing has some problems which I believe are related to cue points in progressive downloads. Lastly, I didn’t implement any indicators for buffering or download progress, so you’ll need to be patient. Since this is just a demo, I’ll have to leave fixing those bugs as an exercise for the reader.

Files

© 2014 saturnboy.com