TweetFollow Us on Twitter

Jan 02 Cover Story

Volume Number: 18 (2002)
Issue Number: 01
Column Tag: QuickTime Toolkit

The Flash

by Tim Monroe

Using Macromedia Flash with QuickTime

Introduction

Macromedia Flash is a multimedia development environment from Macromedia, Inc. for creating and delivering interactive vector-based graphics, animations, and sounds both locally and over the Internet. Because of its low bandwidth requirements and fast rendering capabilities, it has become especially popular for web-based content delivery. Indeed, Macromedia claims that almost 97% of all online users currently are able to view Flash content using the Flash web browser plug-in. In addition, users can view Flash movie files locally using the standalone Flash Player application. Flash content can even be viewed on some set-top boxes and handheld devices. Figure 1 shows a web page powered by Flash.


Figure 1: A web page using Flash

In QuickTime 4, Apple introduced the Flash media handler, which provides support for including Flash content inside of QuickTime movies. As a result, any QuickTime-savvy application can open and display Flash movie files. The Flash movie data is converted into a Flash track, which can then be played by itself or combined with other kinds of tracks. For instance, we can construct a QuickTime movie in which Flash graphic elements (such as buttons and menus) serve to control other tracks in the movie. Figure 2 illustrates this possibility.


Figure 2: A Flash track controlling video and sound tracks

This movie contains three tracks: a video track, a sound track, and a Flash track. The three buttons and the text "Flash and QuickTime" are part of the Flash track, which has a lower layer number than the video track and hence is positioned in front of the video track. The text has no interactive behaviors; the three buttons are configured (from top to bottom) to start the video and sound tracks playing, go to the beginning of the movie, and stop the video and sound tracks. The pointing-hand cursor is displayed automatically by the Flash media handler when the cursor is moved over any of the three buttons.

QuickTime 4 also added the ability to attach wired actions to elements in a Flash movie, thereby supplementing the native Flash interactivity. The Flash track in the movie in Figure 2 is able to control the play state and time of the video and sound tracks by virtue of some clever movie authoring, wherein the video track and the Flash track have the same number of frames and the same playback speed (so that jumping to a frame in the Flash track, using Flash scripting, automatically jumps to the corresponding frame in the video track). For non-linear media types, like QuickTime VR or sprites, we'll need to use wired actions to be able to control our QuickTime movies using Flash buttons and menus.

In this article and the next, we're going to investigate a number of ways to work with QuickTime and Flash. We'll see how to import Flash content into QuickTime movies and how to extract Flash tracks from QuickTime movies. Importing Flash content into QuickTime is actually pretty straightforward, but there are a few places we can enhance the work done by QuickTime's Flash movie importer. We'll also see how to combine QuickTime video with Flash, how to handle some of the application-specific actions that can be included in a Flash movie, and how to work directly with the Flash media handler APIs. Finally, we'll see how to attach wired actions to elements in a Flash track and how to send wired actions from non-Flash tracks to Flash tracks. These tasks involving wired actions will require us to learn a fair bit about the structure of Flash files and indeed will lead us into some of the most intricate code we've encountered so far in this series of articles. The payoff is that we'll be able to use any of the hundred or more QuickTime wired actions inside of a Flash track, in addition to the more limited assortment of native Flash actions.

Our sample application for these two months is called QTFlash; its Test menu is shown in Figure 3.


Figure 3: The Test menu of QTFlash

The first menu item allows us to extract a Flash track from a QuickTime movie (which is essentially the reverse operation of importing a Flash movie file into a QuickTime movie). The middle group of menu items allows us to adjust the magnification of a Flash track. The "Zoom In" menu item, for instance, doubles the magnification of the image, so that the rectangle in the center of the image that is half the width and height of the movie window is expanded to exactly fill the movie window. The last menu item allows us to attach some wired actions to a button in a Flash track. In this article, we'll see how to implement all these items but the last, which we'll reserve for extended treatment next month.

Flash Overview

Macromedia's Flash multimedia development environment is a tightly integrated set of tools for creating and displaying vector-based graphics and animations that are interactive. The most innovative feature of the predecessors of Flash was that they allowed the user to create graphics and other objects that interact with the user and with one another. (See Jonathan Gay's Viewpoint article earlier in this issue.) Flash provides this capability with a vengeance: in a Flash movie, pressing a button might cause some object to move around inside a window. Or typing some numbers into a text field might change the size of a graphic element. Or moving the cursor over an image might load a web page into the user's browser or start a sound playing. Or clicking and dragging a slider thumb might adjust the balance of that sound.

If this all sounds familiar, it's because we encountered these kinds of capabilities when we discussed QuickTime's wired sprites. What we're working with, in both cases, are graphic elements that are associated with actions initiated by user and system events. The events that trigger the actions are quite similar in the two cases; they include mouse-enter events, mouse-exit events, button-click events, and the like. And the actions triggered by those events also overlap to some degree. In both cases, we can jump to specific times in the movie, open URLs, play sounds, adjust video and sound characteristics, and so forth.

Where Flash differs from QuickTime is that Flash was developed from the ground up with web-based delivery in mind. This focus affects both the basic drawing model and the data storage format. Flash supports bitmapped graphics (for instance, JPEG images), but it was primarily designed to use vector-based images, which typically have a smaller file size than bitmapped graphics and which are also scalable without loss of quality. Moreover, the Flash file format was designed so that Flash movies are streamable over network connections. Also, the Flash playback mechanism was designed to be self-contained — that is, to have minimal reliance on services provided by the host operating system. For instance, much of the text you see in a Flash movie is rendered by the playback application from outline data contained in the Flash file and does not depend on any particular fonts being installed on the user's computer.

Let's take a look, then, at the various pieces of the Flash architecture. We usually create some Flash content using an application called Macromedia Flash. This application provides numerous tools for drawing vector shapes and for importing bitmapped graphics and sounds. These items are collected into a library, and instances of those objects can be placed on a stage at certain points in a timeline. Figure 4 shows the main window of a Flash document, which includes the stage and the timeline.


Figure 4: A Macromedia Flash document window

We can animate items on the stage by changing their sizes and locations over time. Flash supports several kinds of tweening: motion tweening (which is essentially the kind of position tweening we encountered with QuickTime sprites) and shape tweening (which is a kind of morphing of one shape into another). We can also attach executable code to frames in the timeline or to buttons in the Flash movie; this code is written in a variant of JavaScript called ActionScript.

The data in a Flash document is saved on disk in a Flash document file (or Flash project file), whose filename extension is usually ".fla". When we're ready to publish our work, we typically create a Flash movie file (or Flash file) whose filename extension is usually ".swf". The Flash movie file is a highly-optimized version of the data in the Flash project file. This optimization compacts the data and removes any unnecessary elements. For example, a Flash project file may include a complete outline font, but the Flash movie file contains the outline data for only those characters in the font that are actually displayed in some frame in the movie. As a result, a Flash movie file cannot typically be edited in any useful manner. It's designed solely for playback (unlike QuickTime movie files, which are designed both for playback and for data interchange).

On the playback side of the ledger, we have several possibilities open to us. We can embed the Flash movie file in a web page, which is then viewable by anyone who has the Flash browser plug-in installed. Or, we can open the Flash movie file from a local disk using an application called Flash Player (on Windows, FlashPla.exe). Or, as you've probably guessed, we can open the Flash movie file using any QuickTime-savvy application (for instance, QuickTime Player or our own QTShell-based sample applications). Figure 5 illustrates the authoring and playback possibilities.


Figure 5: The Flash authoring and playback architecture

The Flash movie file data is converted into a QuickTime movie by the Flash movie importer. The imported data is written into a Flash track (of type FlashMediaType) and is processed at playback time by the Flash media handler. If we save the movie, we can create a QuickTime movie file that contains the Flash track.

Not everything in a Flash movie is attached to the main timeline. Flash supports objects with independent timelines, called movie clips. An animation in a movie clip may continue running even if the main timeline is stopped. A Flash movie clip is therefore analogous to a QuickTime child movie (which can have a time base that's independent of the time base of its parent). Later we'll see that a movie clip is contained in a Flash file in a block of data of type stagDefineSprite; as a result, movie clips are sometimes called sprites. This is potentially confusing terminology, at least for us QuickTime programmers. Just keep in mind that a Flash sprite is not at all akin to a QuickTime sprite; rather, it's a movie clip, which is most like a QuickTime child movie.

The current version of Macromedia Flash, version 5, supports a host of additional features — far too many for us to survey here. Two features, however, deserve special mention: HTML text support and XML data transfer support. The HTML support means that text in a Flash movie can be formatted using standard HTML tags; in addition, a Flash movie can load a text file that contains HTML-formatted text and display the text in its window. The XML support is even more interesting. Flash movies can open XML-structured data files and extract pieces of information from that data, perhaps displaying some of it in text fields or perhaps altering the appearance or behavior of objects in the movie based on that data. And, a Flash movie can connect to remote servers and exchange XML-formatted data with those servers. So a Flash movie embedded in a web page could very easily serve as the front-end of an online shopping business.

Flash and Video

Macromedia Flash provides a powerful array of services indeed, but it does not provide any built-in support for including video segments inside of Flash movies. If our projects require us to combine Flash content and video, there are two general approaches we can follow. First, we can convert a video clip into a series of individual images, which can be displayed by Flash Player (or any other Flash-savvy application) as bitmapped images. Second, we can build a QuickTime movie that contains a Flash track and other kinds of tracks, including video and sound tracks; the Flash track can contain buttons and other interactive elements, which might control the operations of the other tracks (as we saw in Figure 2). Let's investigate each of these approaches briefly.

Converting QuickTime Video into an Image Sequence

There are several commercial products that can convert an existing QuickTime video movie file into a Flash file. One such product is called Flix and is developed and distributed by Wildform, Inc. Essentially, Flix renders each video frame of the QuickTime movie and writes it into a Flash file as a JPEG image; the sequence of images is drawn at a preset rate to simulate the original video clip. Flix also converts any sound tracks in the movie into streaming sounds in the Flash file. Figure 6 shows the video settings panel of the Flix conversion window.


Figure 6: The Flix video settings panel

The main advantage with this approach to combining video and Flash content is that the resulting file is a bona fide Flash movie file, which can be opened by the Flash Player application or embedded in a web page and streamed to the user's computer, where it is handled by the Flash web browser plug-in. The main disadvantage is that both image quality and playback frame rate must be sacrificed to keep the resulting Flash file size reasonable. The QuickTime movie being compressed in Figure 6 is a 20 fps, 480 by 360 movie compressed with Sorenson 2 encoding; the output Flash file contains a 12 fps, 320 by 240 JPEG-encoded stream of images. Moreover, this is really the best-case scenario for Flix, using the settings suitable for a DSL or cable-modem connection. A Flash file suitable for streaming across a 56-kbps modem is noticeably choppier and of poorer image quality than a QuickTime movie compressed for the same speed.

Including Flash Data in a QuickTime File

To combine Flash content with QuickTime video while retaining high quality and high frame rates for the video, we need to pursue a different strategy: we need to build a QuickTime movie that contains one or more video tracks, a Flash track, and perhaps other kind of tracks too. The Flash track might add graphic overlays on the video; it might also contain buttons or other interactive elements that control the other tracks in the movie. (We saw both these things in the movie shown in Figure 2 earlier.) The Flash track can control the other tracks using either its native Flash scripting (ActionScript) or QuickTime wired actions.

The easiest way to merge Flash and QuickTime in this way is to import the QuickTime movie into a layer in the Macromedia Flash authoring tool. (See Figure 4 once again.) The frame rate of the Flash movie should be set to match that of the imported QuickTime movie, and there should be one frame in the Flash timeline for each frame in the QuickTime movie. This allows us to move to specific frames in the QuickTime movie by jumping to the corresponding frame in the Flash track. We can then create Flash elements (buttons, menus, and so forth) in other layers and script them to control the QuickTime video. For instance, we can set this chunk of ActionScript to be executed whenever the user clicks and releases the top button:

on (release) {
   play();
}

The play command starts the Flash track playing, which sets the entire QuickTime movie playing.

Similarly, we can set this chunk of ActionScript to be executed when the user clicks and releases the middle button:

on (release) {
   gotoAndStop(1);
}

The gotoAndStop function jumps to the specified frame number and then pauses playback.

When we are ready to output a file, we must export the data as a QuickTime movie file. The Flash content is written into a Flash track and the QuickTime data is preserved in its original form. Note that the scripting used on the buttons must be compatible with version 4 of the Flash file format. The most recently released version of QuickTime (that is, 5.0.2) supports Flash files only up to version 4. (Later we'll see how we can dynamically determine the Flash file format versions supported by QuickTime's Flash media handler.) Any scripting that relies on the more advanced features in Flash 5 will not be recognized by the current Flash media handler.

It's important to understand that a Flash track cannot directly control other tracks in a QuickTime movie using only the native Flash scripting capabilities. The middle button in our sample movie manages to rewind the entire QuickTime movie only indirectly, by telling the Flash track to go to its first frame. This prompts the movie controller to jump to the first frame in the Flash track, which — by dint of our clever authoring — corresponds to the first frame in the video track. This scheme would not work with non-linear QuickTime media types, such as QuickTime VR movies or many sprite movies. To control those sorts of movies using Flash tracks, we need to attach QuickTime wired actions to items in the Flash track. Most often, we'll want to attach wired actions to Flash buttons. So let's take a look at how buttons operate.

Buttons

Buttons are the primary interactive elements in a Flash movie. They respond to mouse events (that is, mouse movements and mouse button clicks) and can trigger one or more actions in response to those events. Every button has three distinct states, called the up state, the over state, and the down state. A button is in the up state (or idle state) when the cursor is not within the bounds of the button image. (This is the default state of a button.) A button is in the over state when the cursor is within the bounds of the button image. A button is in the down state when the mouse button is down and the cursor is within the bounds of the button image.

Each state of a button can have an image associated with it. Figure 7 shows the images associated with a typical button. The left-hand image is the up state image, where the cursor is outside of the button. The middle image is the over state image; notice that the cursor changes and the round part of the button image reverses its colors. The right-hand image is the down state image; once again, the round part of the image changes to reflect the different state.


Figure 7: The state images for a Flash button

Some button states can share an image. Figure 8 shows a Flash movie that contains a single button (which, when pressed, plays a penguin screech). In this case, the up state and the over state use the same image. When the mouse button is pressed and the cursor is within the button image, the down state appears (Figure 9).


Figure 8: The up and over state images of a Flash button


Figure 9: The down state image of a Flash button

Flash supports two kinds of buttons. A push button (like those shown in Figures 7, 8, and 9) is what we usually think of as a button: if you press the mouse button, hold it down, and move the cursor outside of the button image, the button remains in its down state. The button is said to capture the mouse. By contrast, a menu button is a button that does not capture the mouse; if you click on a menu button, hold the mouse button down, and then drag outside of the menu button, the button returns to its up state.

Actions can be associated with button state transitions, that is, the change from one button state to another. To delineate the possible button state transitions, we need to pay attention to changes in the cursor location and the mouse button state. The cursor can be either outside or inside the button image, and the mouse button can be either up or down. This gives us 4 possible combinations of cursor location and mouse button state, and hence 8 possible transitions where either the location or mouse button state changes (but not both at the same time).

The transition from mouse button up to mouse button down when the cursor is outside a button is not particularly useful and is ignored by Flash when processing button events. On the other hand, the transition from mouse button down to mouse button up when the cursor is outside a button is in fact useful, if the mouse button was originally pressed inside the button. So there are 7 possible button transitions for a button that allows moving the cursor in or out of a button while the button is down (that is, for a push button). There are 6 possible button transitions for a menu button. Four of these 13 total transitions are shared by push buttons and menu buttons, so there are actually only 9 total button transitions.

All buttons have these four button state transitions:

  • Mouse button up, cursor enters the button. This is a button roll-over.
  • Mouse button up, cursor leaves the button. This is a button roll-out.
  • Cursor inside button, mouse button changes from up to down. This is a button press.
  • Cursor inside button, mouse button changes from down to up. This is a button release.

A push button can have these three additional button state transitions:

  • Mouse button down, cursor enters the button. This is a button drag-over.
  • Mouse button down, cursor leaves the button. This is a button drag-out.
  • Cursor outside button, mouse button changes from down to up. This is a button outside release.

A menu button can have these two additional button state transitions:

  • Mouse button down, cursor enters the button. This is a button drag-over.
  • Mouse button down, cursor leaves the button. This is a button drag-out.

It might seem like these two additional menu button state transitions are identical with two of the additional push button state transitions, but in fact they result in different visual behaviors. When the cursor is dragged outside of a menu button while the mouse button is down, the button immediately returns to the idle state; by contrast, when the cursor is dragged outside of a push button while the mouse button is down, the button changes from the down to the over state. Accordingly, Flash considers these transitions to be different.

We'll need some way to refer to these 9 button state transitions in our code. The file FlashParser.h enumerates a set of constants that specify bit positions in a 16-bit conditions value stored with the button data in a Flash file. (The high-order seven bits are currently reserved.)

enum {
   bsIdleToOverUp                = 0,   // roll-over
   bsOverUpToIdle,                     // roll-out
   bsOverUpToOverDown,               // press
   bsOverDownToOverUp,               // release

   // these transitions apply only when tracking "push" buttons
   bsOverDownToOutDown,            // drag-out
   bsOutDownToOverDown,               // drag-over
   bsOutDownToIdle,                  // outside release

   // these transitions apply only when tracking "menu" buttons
   bsIdleToOverDown,                  // drag-over
   bsOverDownToIdle                  // drag-out
};

We'll define another set of constants that we can use as masks to read those bits:

#define kIdleToOverUp               (1L << bsIdleToOverUp)
#define kOverUpToIdle               (1L << bsOverUpToIdle)
#define kOverUpToOverDown         (1L << bsOverUpToOverDown)
#define kOverDownToOverUp         (1L << bsOverDownToOverUp)

#define kOverDownToOutDown      (1L << bsOverDownToOutDown)
#define kOutDownToOverDown      (1L << bsOutDownToOverDown)
#define kOutDownToIdle            (1L << bsOutDownToIdle)

#define kIdleToOverDown            (1L << bsIdleToOverDown)
#define kOverDownToIdle            (1L << bsOverDownToIdle)

We'll also use these constants as event types when we build wired actions for Flash tracks. For example, we can build an event atom that triggers on a mouse-down event with this line of code:

myErr = QTInsertChild(*theActions, kParentAtomIsContainer, 
            kQTEventType, kOverUpToOverDown, 1, 0, NULL, 
            &myEventAtom);

But we're getting ahead of ourselves here. Now we need to delve into the structure of Flash files.

The Flash File Format

A Macromedia Flash file (SWF) is a stream of bytes that is organized as a header block followed by a series of tagged data blocks, as shown in Figure 10.


Figure 10: The format of a Flash file

The header block contains general information about the Flash file, such as its size, the dimensions of the Flash movie, and the number of frames in the movie. The tagged data blocks define the items that appear in the Flash movie (that is, the buttons, shapes, and sounds) and indicate where and when those items are to be drawn or played and possibly also animated. To work with Flash data inside of QuickTime movies, we need to understand this structure with a fair bit of detail. We'll begin by defining some functions that will allow us to read chunks of data from the stream of bytes that comprises a Flash file. Then we'll learn how to parse the header block and the tagged data blocks.

Reading Bytes from a Stream

As mentioned above, a Flash file is a stream of bytes that is divided into a series of blocks. Except for the header block, each block begins with some data that indicates what kind of block it is (that's the tag) and how large it is. A Flash file is therefore not unlike a QuickTime file, which consists of a series of atoms, each of which begins with a length and a type. (See "The Atomic Café" in MacTech, September 2000 for more details on the atom-based structure of QuickTime files.) A tagged data block can contain other tagged data blocks, but for present purposes we will not need to look inside of any hierarchical blocks. So we can accomplish what we need to do by reading the file, byte-by-byte or sometimes even bit-by-bit, from beginning to end.

Let's begin by defining some functions that allow us to read chunks of information of various sizes from a stream of data. For instance, we'll define a function GetByte that returns the next 8-bit chunk of the stream and another function GetWord that returns the next 16-bit chunk of the stream. Throughout our stream-parsing code, we'll use these basic data types:

typedef unsigned char            U8, *P_U8, **PP_U8;
typedef signed char               S8, *P_S8, **PP_S8;
typedef unsigned short         U16, *P_U16, **PP_U16;
typedef signed short            S16, *P_S16, **PP_S16;
typedef unsigned long            U32, *P_U32, **PP_U32;
typedef signed long               S32, *P_S32, **PP_S32;
typedef signed long               SCOORD, *P_SCOORD;

The SCOORD data type represents a coordinate; indeed, a point (of type SPOINT) is simply a pair of coordinates, declared like this:

typedef struct SPOINT {
   SCOORD         x;
   SCOORD         y;
} SPOINT, *P_SPOINT;

And a rectangle (of type SRECT) is a quadruple of coordinates:

typedef struct SRECT {
   SCOORD         xmin;
   SCOORD         xmax;
   SCOORD         ymin;
   SCOORD         ymax;
} SRECT, *P_SRECT;

A rectangle occupies 16 bytes of memory; as we'll see however, Flash utilizes a bit-packing scheme that can compress a rectangle to 8 bytes when it is written to a Flash file.

As we're reading through the stream of data, we need to keep track of the current position in the stream, as well as information about the start position and the length of the current tagged data block. For this, we'll use the FlashParserStruct data type, defined like this:

typedef struct FlashParserStruct {
   Handle      m_theData;

   // pointer to file contents buffer
   U8            *m_fileBuf;

   // file state information
   U32         m_filePos;
   U32         m_fileSize;
   U32         m_fileStart;
   U16         m_fileVersion;

   S32         m_frameHeight;
   S32         m_frameWidth;
   U32         m_frameRate;
   U32         m_frameCount;

   // bit handling
   S32         m_bitPos;
   U32         m_bitBuf;

   // tag parsing information
   U32         m_tagStart;
   U32         m_tagEnd;
   U32         m_tagLen;
} FlashParserStruct, *FlashParserPtr, **FlashParserHandle;

The m_theData field is a handle to the data stream itself, and m_fileBuf is a pointer to the first byte in the data stream. The m_filePos field is the offset of the next byte we need to read from the data stream. Each time we read a byte from the data stream, we need to update m_filePos. We'll describe the remaining fields of the FlashParserStruct structure a bit later.

To extract a 1-byte, 2-byte, or 4-byte chunk from the data stream, we can use the functions GetByte, GetWord, and GetDWord defined in Listing 1.

Listing 1: Reading bytes from a Flash data stream

U8 GetByte (void)
{
   return(gFlashParserData.m_fileBuf
            [gFlashParserData.m_filePos++]);
}

U16 GetWord (void)
{
   U8 *s = gFlashParserData.m_fileBuf + 
            gFlashParserData.m_filePos;

   gFlashParserData.m_filePos += 2;
   return((U16)s[0] | ((U16)s[1] << 8));
}

U32 GetDWord (void)
{
   U8 *s = gFlashParserData.m_fileBuf + 
            gFlashParserData.m_filePos;

   gFlashParserData.m_filePos += 4;
   return((U32)s[0] | ((U32)s[1] << 8) | ((U32)s[2] << 16) | 
            ((U32)s[3] << 24));
}

You'll notice that multi-byte data is stored in little-endian format. GetWord and GetDWord read each byte individually and reconstruct the value by doing the appropriate bit-shifting and logical adding.

Some data in a Flash file is stored as a C string (a sequence of bytes followed by a NULL byte). We can use the GetAString function defined in Listing 2 to read a string from the current file position. (GetAString is called GetString in the source code from which our parser is derived, but the Macintosh APIs already include a function called GetString; hence the renaming.)

Listing 2: Reading a string from a Flash data stream

char *GetAString (void)
{
   // point to the string
   char *myString = (char *)&gFlashParserData.m_fileBuf
            [gFlashParserData.m_filePos];

   // skip over the string
   while (GetByte())
      ;

   return(myString);
}

GetAString returns a pointer to the first character in the string, which is simply the location of the byte at the current file position. We need to advance the file position past the string and the terminating NULL byte, however, so that subsequent reads don't return characters in the string. That's what the while loop accomplishes. Note that GetAString does not return a copy of the string in the data stream, so we wouldn't want to call free on the pointer that it returns.

Reading Bits from a Stream

Some information in a Flash file is contained in sub-byte chunks — that is, in individual bits or in bit fields. The FlashParserStruct structure contains two fields, m_bitPos and m_bitBuf, that are used for reading one or more bits at a time. Listing 3 defines the function GetBits, which we can use to read a specified number of bits from the input stream.

Listing 3: Reading bits from a Flash data stream

U32 GetBits (S32 n)
{
   U32      v = 0;

   for (;;) {
      S32      s = n - gFlashParserData.m_bitPos;
      if (s > 0) {
         // consume the entire buffer
         v |= gFlashParserData.m_bitBuf << s;
         n -= gFlashParserData.m_bitPos;

         // get the next buffer
         gFlashParserData.m_bitBuf = GetByte();
         gFlashParserData.m_bitPos = 8;
      } else {
          // consume a portion of the buffer
         v |= gFlashParserData.m_bitBuf >> -s;
         gFlashParserData.m_bitPos -= n;
         gFlashParserData.m_bitBuf &= 0xff >> 
            (8 - gFlashParserData.m_bitPos);   // mask off the consumed bits
         return(v);
      }
   }
}

Occasionally we'll want to read a chunk of bits as a signed value; for that, we can use the GetSBits function defined in Listing 4.

Listing 4: Reading bits as a signed quantity

S32 GetSBits (S32 n)
{
   // get the number as an unsigned value
   S32 v = (S32)GetBits(n);

   // if the number is negative, extend the sign
   if (v & (1L << (n - 1)))
      v |= -1L << n;

   return(v);
}

Before we call either GetBits or GetSBits to read some bits from the data stream, we need to initialize the m_bitPos and m_bitBuf fields of our parser data structure. For that, we use the InitBits function defined in Listing 5.

Listing 5: Initializing the bit data fields

void InitBits (void)
{
   gFlashParserData.m_bitPos = 0;
   gFlashParserData.m_bitBuf = 0;
}

Reading Rectangle Data

A Flash data stream contains a number of chunks of data that are structured into points, matrices, colors, and other data types. For present purposes, we need to be able to read only one structured data type, a rectangle. We saw earlier that an SRECT structure contains four coordinates, each of which occupies 4 bytes. When a rectangle is written into a Flash data stream, the information in those 16 bytes is packed into a series of bit fields, primarily to save space in the data stream. The first five bits of the packed data indicate how many bits each of the four coordinates occupies. Then each coordinate follows those 5 initial bits, occupying the specified number of bits. The entire packed structure is expanded to the nearest byte boundary by appending bits whose value is zero.

We can use the GetRect function defined in Listing 6 to read a rectangle from a Flash data stream. As you can see, GetRect uses the InitBits, GetBits, and GetSBits functions defined just above.

Listing 6: Reading a rectangle from a Flash data stream

void GetRect (SRECT *r)
{
   int      nBits;

   InitBits();

   nBits = (int)GetBits(5);
   r->xmin = GetSBits(nBits);
   r->xmax = GetSBits(nBits);
   r->ymin = GetSBits(nBits);
   r->ymax = GetSBits(nBits);
}

Most often, each coordinate of a rectangle is packed into 14 bits. This means that the rectangle data can be stored in 61 bits (that is, 4 times 14, plus the 5-bit length specifier); expanding the chunk to the nearest byte boundary gives a total of 64 bits, or 8 bytes. So a rectangle can usually be compressed into half its original size using this simple bit-packing scheme.

Let's look at a real-life example of this. A particular rectangle might be encoded as 0x7000096000006400. The first 5 bits of this quantity are 01110, or 14. The next 14 bits are all zeros. The next 14 bits are 01001011000000, which is 0x12C0, or 4800. The third chunk of 14 bits is once again all zeros. The final chunk of 14 bits is 00110010000000, which is 0x0C80, or 3200. So the upper-left corner of this rectangle is (0, 0) and the lower-right corner is (4800, 3200).

Perhaps you are thinking that that's an awfully large rectangle. It turns out that coordinates in Flash movie files are specified in units of twentieths of a pixel (affectionately known as twips). So in pixels, this rectangle would be 240 by 160, which is not so big after all.

Parsing the Header Block

We now have sufficient tools to parse a Flash data stream. As you know, a Flash file begins with a header block, which is almost always 20 bytes in length. The first 4 bytes contain a file signature and a version byte. The next four bytes contain the size of the entire Flash file, in bytes. Following the file length field is a packed rectangle that indicates the dimensions of the Flash movie. As we've seen, this field is almost always 8 bytes long. The last 4 bytes are a 2-byte frame rate (interpreted as a Fixed data type, with one byte for the integer part and one byte for the fractional part) and a 2-byte frame count. Figure 11 shows a typical header block in hexadecimal format, with byte values or decimal values underneath.


Figure 11: A header block

You'll notice that the header block of a Flash file contains very little information about the Flash movie. It tells us the frame size, frame rate, and frame count, but not much more than that. In particular, it doesn't give us any information about some important playback characteristics, such as whether the movie should start playing all frames automatically or whether the movie should loop back to the beginning when it reaches the end. In a QuickTime file, this kind of metadata is contained in the movie user data and is available to a playback application when the movie file is opened up. If we want to know whether a Flash movie is an autoplay movie, we need to do a bit of work, as we'll see later.

Listing 7 shows some code that we can use to parse the header block of a Flash file; it assumes that myMediaData is a handle to the Flash file data that's been read into memory. (Some error handling has been removed from this code in the interests of saving space.)

Listing 7: Reading the header block in a Flash data stream

InitParser();
gFlashParserData.m_theData = myMediaData;
HLock(myMediaData);

gFlashParserData.m_fileBuf = (U8 *)*myMediaData;

// read the file header
myByte = GetByte();            // should be ‘F'
myByte = GetByte();            // should be ‘W'
myByte = GetByte();            // should be ‘S'

myByte = GetByte();
gFlashParserData.m_fileVersion = (U16)myByte;

// get the file size
gFlashParserData.m_fileSize = GetDWord();

// get the file dimensions
GetRect(&myRect);
gFlashParserData.m_frameWidth = (S32)(ceil((float)(myRect.xmax - myRect.xmin) / 
                     (float)kTwipsPerPixel));
gFlashParserData.m_frameHeight = (S32)(ceil((float)(myRect.ymax - myRect.ymin) / 
                     (float)kTwipsPerPixel));

// get the frame rate and count
gFlashParserData.m_frameRate = GetWord() >> 8;
gFlashParserData.m_frameCount = GetWord();

gFlashParserData.m_fileStart = gFlashParserData.m_filePos;

Once we've run this code, the m_filePos field of the gFlashParserData structure points at the first byte in the first tagged data block, and some of the other fields in that structure contain information about the Flash file.

For certain purposes, we might want to skip over the header block and just set the m_filePos field to the first byte in the first tagged data block. We can use the function SkipHeaderBlock, defined in Listing 8, for this purpose.

Listing 8: Skipping over the header block in a Flash data stream

void SkipHeaderBlock (void)
{
   SRECT       myRect;

   gFlashParserData.m_filePos = 8;         // skip signature and file size
   GetRect(&myRect);                              // skip frame size
   gFlashParserData.m_filePos += 4;         // skip rate and count

   gFlashParserData.m_fileStart = gFlashParserData.m_filePos;
}

Parsing the Tagged Data Blocks

A tagged data block consists of some data preceded by a tag header. A tag header begins with a 2-byte field, whose high-order 10 bits contain a tag ID and whose low-order 6 bits specify the length of the data. (See Figure 12.) If the amount of data in the tagged data block exceeds 62 bytes, then the 6-bit length field of the tag header contains the value 0x3f (that is, 63), and the 2-byte field is followed immediately by a 4-byte field that contains the length of the data. (See Figure 13.)


Figure 12: A short tag header


Figure 13: A long tag header

In short, the tag header occupies two bytes if the data in the block is 62 bytes or less, and six bytes otherwise. Listing 9 shows the definition of the GetTag function, which we'll use to read the tag ID and tag length from a tag header. When GetTag returns, the m_filePos field points to the first byte in the data of the tagged data block. In addition, the fields m_tagStart, m_tagEnd, and m_tagLen contain the starting location, ending location, and length of the tagged data block. We'll use those fields to move from block to block in the file.

Listing 9: Reading a tag

U16 GetTag (void)
{
   U16      myCode;
   U32      myLength;

   // save the start of the tag
   gFlashParserData.m_tagStart = gFlashParserData.m_filePos;

   // get the combined code and length of the tag
   myCode = GetWord();

   // the length is encoded in the low-order 6 bits of the tag
   myLength = myCode & 0x3f;

   // remove the length from the code
   myCode = myCode >> 6;

   // determine if another long word must be read to get the length
   if (myLength == 0x3f)
      myLength = (U32)GetDWord();

   // determine the end position of the tag
   gFlashParserData.m_tagEnd = gFlashParserData.m_filePos + 
            (U32)myLength;
   gFlashParserData.m_tagLen = (U32)myLength;

   return(myCode);
}

The tag ID returned by GetTag indicates the kind of data contained in the tagged data block. Here are a dozen or so tag IDs:

enum { 
   stagEnd                                  = 0,
   stagShowFrame                           = 1,
   stagDefineShape                        = 2,
   stagFreeCharacter                     = 3,
   stagPlaceObject                        = 4,
   stagRemoveObject                        = 5,
   stagDefineBits                           = 6,
   stagDefineButton                        = 7,
   stagJPEGTables                           = 8,
   stagSetBackgroundColor               = 9,
   stagDefineFont                           = 10,
   stagDefineText                           = 11,
   stagDoAction                           = 12,
   stagDefineFontInfo                     = 13
}

The first tag in a Flash file is usually stagSetBackgroundColor, and its associated data is a 3-byte value that contains the red, green, and blue components of the color. On the other end of the file, the last two tags in a file are often stagShowFrame and stagEnd. The stagShowFrame tag indicates the end of a frame; the playback application should render any characters that have been defined and placed into the movie rectangle. In addition, the movie is paused for the duration of a single frame. The stagEnd tag marks the end of a Flash file and must always be the last tag in the file.

The stagDoAction tag is of particular interest. Its associated data is an action list, a list of actions which are executed during the processing of a stagShowFrame tag. An action list can also be attached to a button, in which case the actions are executed during a specified button transition. Here's a sampling of Flash actions:

enum {
   sactionNone                           = 0x00,
   sactionGotoFrame                     = 0x81,
   sactionGetURL                        = 0x83,
   sactionNextFrame                     = 0x04,
   sactionPrevFrame                     = 0x05,
   sactionPlay                           = 0x06,
   sactionStop                           = 0x07,
   sactionToggleQuality               = 0x08,
   sactionStopSounds                  = 0x09,
   sactionWaitForFrame               = 0x8A,
   sactionSetTarget                     = 0x8B,
   sactionGotoLabel                     = 0x8C,
   sactionWiredActions               = 0xAA
};

As you can see, actions can be used to move from frame to frame, start and stop the Flash movie, stop sounds from playing, open a Flash movie at a specific URL, and so forth. Note the special action sactionWiredActions (that is, 0xAA); this is provided specifically to allow us to embed QuickTime wired actions in a Flash movie. We'll investigate this capability more fully in the next article.

Let's see how we can traverse the tagged data blocks in a Flash file. Listing 10 illustrates a very simple case, where all we want to do is count the top-level tags in a Flash file.

Listing 10: Counting the tags in a Flash file

U32 CountTags (Handle theStream)
{
   U32         myNumTags = 0;
   BOOL         isAtEnd = false;
   U16         myCode;
   U32         myTagEnd;

   if ((theStream == NULL) || (*theStream == NULL))
      goto bail;

   InitParser();
   gFlashParserData.m_theData = theStream;
   gFlashParserData.m_fileBuf = (U8 *)*theStream;

   // set the position to the start position
   SkipHeaderBlock();

   // loop through each tag
   while (!isAtEnd) {
      // get the current tag and tag-end position
      myCode = GetTag();
      myTagEnd = gFlashParserData.m_tagEnd;

      if (myCode == stagEnd)
         isAtEnd = true;

      myNumTags++;

      // increment past the tag
      gFlashParserData.m_filePos = myTagEnd;
   }

bail:
   return(myNumTags);
}

CountTags consists mainly of a while loop, which is exited only when the last tagged data block in the file (which must be of type stagEnd) is found.

Importing Flash Files

Let's use this parsing ability to help us overcome some of the limitations of the Flash movie importer. If we open a Flash file by calling NewMovieFromFile (or one of its sister functions, such as NewMovieFromDataRef), QuickTime will automatically invoke the Flash movie importer to convert the Flash movie into a QuickTime movie. It does this by creating a Flash track whose media data is simply the data in the original Flash file. However, the current Flash movie importer (up to at least QuickTime version 5.0.2) has a few annoying quirks. For one thing, it assumes that all Flash movies should automatically start playing immediately once they are opened. This is indeed the default behavior for Flash movies, but it's possible for the Flash movie author to override that behavior by inserting an sactionStop action before the first stagShowFrame tag.

It's actually quite easy to work around this limitation. Our sample application QTFlash includes the code shown in Listing 11 in the function QTFlash_InitWindowData, which is called after a Flash movie is imported but before the new movie window is displayed on the screen.

Listing 11: Setting the autoplay characteristic

if ((GetMovieTrackCount(myMovie) == 1) && 
                  ((**myAppData).fNumFlashTracks == 1)) {
   myErr = QTFlash_IsAutoPlayMovie(myMovie, &myLong);
   if (myErr == noErr) {
      myBoolean = (Boolean)myLong;
      SetUserDataItem(GetMovieUserData(myMovie), &myBoolean, 
                  sizeof(myBoolean), FOUR_CHAR_CODE(‘play'), 1);
   }
}

As you can see, the central step here is the call to QTFlash_IsAutoPlayMovie, which tells us whether the imported Flash movie should begin to play immediately or stop once the first frame has been rendered. The definition of QTFlash_IsAutoPlayMovie (Listing 12) is fairly trivial. It just calls another function, QTFlash_GetFileCharacteristic, with a selector that indicates the kind of metadata it wants retrieved from the file.

Listing 12: Determining whether a Flash file is autoplay

OSErr QTFlash_IsAutoPlayMovie (Movie theMovie, 
               UInt32 *isAutoPlay)
{
   return(QTFlash_GetFileCharacteristic(theMovie, isAutoPlay, 
               kFlashIsAutoPlayFile));
}

Listing 13 shows the complete definition of QTFlash_GetFileCharacteristic. As you can see, we use a while loop similar to the one in Listing 10 to traverse the tagged data blocks in the Flash data stream. In this case, however, we can stop once we've found the first stagShowFrame tag.

Listing 13: Reading metadata from a Flash data stream

static OSErr QTFlash_GetFileCharacteristic (Movie theMovie, 
            UInt32 *theHasIt, UInt32 theCharacteristic)
{
   Track                              myTrack = NULL;
   Media                              myMedia = NULL;
   long                                 mySize = 0L;
   Handle                              myMediaData = NULL;
   SampleDescriptionHandle      myDesc = NULL;
   Boolean                           myIsAutoPlay = true;
   Boolean                           myIsFullScreen = false;
   Boolean                           myAtEnd = false;
   U8                                    myByte;
   U8                                    myAction;
   U16                                 myCode, myLength;
   U32                                 myTagEnd;
   S32                                 myPos;
   SRECT                               myRect;
   OSErr                              myErr = paramErr;

   if ((theMovie == NULL) || (theHasIt == NULL))
      goto bail;

   // get the Flash data stream from the Flash track
   myTrack = GetMovieIndTrackType(theMovie, 1, FlashMediaType, 
            movieTrackMediaType);
   if (myTrack == NULL)
      goto bail;

   myMedia = GetTrackMedia(myTrack);
   if (myMedia == NULL)
      goto bail;

   myMediaData = NewHandle(0);
   if (myMediaData == NULL)
      goto bail;

   myDesc = (SampleDescriptionHandle)NewHandleClear
            (sizeof(SampleDescription));
   if (myDesc == NULL)
      goto bail;

#if ONLY_ONE_MEDIA_SAMPLE
   // in theory, there should be only one media sample in the Flash track;
   // report an error if we get more than one
   if (GetMediaSampleCount(myMedia) != 1) {
      myErr = invalidMedia;
      goto bail;
   }
#endif

   myErr = GetMediaSample(myMedia, myMediaData, 0, &mySize, 
      (TimeValue)0, NULL, NULL, myDesc, NULL, 1, NULL, NULL);
   if (myErr != noErr)
      goto bail;

   // parse the Flash header and file info
   InitParser();
   gFlashParserData.m_theData = myMediaData;
   HLock(myMediaData);

   gFlashParserData.m_fileBuf = (U8 *)*myMediaData;

   // verify the file header
   myByte = GetByte();
   if (myByte != ‘F') {
      myErr = invalidMedia;
      goto bail;
   }

   myByte = GetByte();
   if (myByte != ‘W') {
      myErr = invalidMedia;
      goto bail;
   }

   myByte = GetByte();
   if (myByte != ‘S') {
      myErr = invalidMedia;
      goto bail;
   }

   myByte = GetByte();
   gFlashParserData.m_fileVersion = (U16)myByte;

   // get the file size
   gFlashParserData.m_fileSize = GetDWord();

   // get the file dimensions
   GetRect(&myRect);
   gFlashParserData.m_frameWidth = 
            (S32)(ceil((float)(myRect.xmax - myRect.xmin) / 
                  (float)kTwipsPerPixel));
   gFlashParserData.m_frameHeight = 
            (S32)(ceil((float)(myRect.ymax - myRect.ymin) / 
                  (float)kTwipsPerPixel));

   // get the frame rate and count
   gFlashParserData.m_frameRate = GetWord() >> 8;
   gFlashParserData.m_frameCount = GetWord();

   gFlashParserData.m_fileStart = gFlashParserData.m_filePos;

   // look for the specified characteristic

   // initialize the end-of-frame flag
   myAtEnd = false;

// loop through each tagged data block, looking for stagDoAction and stagShowFrame tags;
   // we do NOT want to search any stagDefineSprite blocks
   while (!myAtEnd) {
      // get the current tag and tag-end position
      myCode = GetTag();
      myTagEnd = gFlashParserData.m_tagEnd;

      switch (myCode) {
         case stagDoAction:
            for (;;) {
               // get the action code
               myAction = GetByte();

               if (myAction == sactionNone)
                  // end of this list of actions
                  break;

               myLength = 0;
               if (myAction & sactionHasLength)
                  myLength = GetWord();

               myPos = gFlashParserData.m_filePos + myLength;

               if (myAction == sactionStop) {
                  // we found an sactionStop action that occurs before the first 
                  // stagShowFrame tag; we can stop looking
                  myIsAutoPlay = false;
                  myAtEnd = true;
               }

               if (myAction == sactionGetURL) {
               // look for a URL of the form "FSCommand:fullscreen" with argument 
                  // "true"
                  char *myURL = GetAString();
                  char *myArg = GetAString();

                  // (this could be better implemented; should be case-insensitive)
                  if (strcmp(myURL, "FSCommand:fullscreen") == 0)
                     if (strcmp(myArg, "true") == 0)
                        myIsFullScreen = true;
               }

               gFlashParserData.m_filePos = myPos;
            }
            break;

         case stagShowFrame:
         // we found the first stagShowFrame tag on the main timeline; we can stop 
            // looking
            myAtEnd = true;
            break;

         case stagEnd:
            // we reached the end of the file
            myAtEnd = true;
            break;

         default:
            break;
      }

      // increment past the tag
         gFlashParserData.m_filePos = myTagEnd;
   }

bail:
   if (myErr == noErr) {
      switch (theCharacteristic) {
         case kFlashIsAutoPlayFile:
            *theHasIt = myIsAutoPlay;
            break;

         case kFlashIsPlayFullScreen:
            *theHasIt = myIsFullScreen;
            break;

         case kFlashIsLoopingFile:
            myErr = unimpErr;      // not yet implemented
            break;

         default:
            myErr = paramErr;      // unknown selector
            break;
      }
   }

   return(myErr);
}

FSCommands

You'll notice that Listing 13 figures out whether a Flash file should be played full screen by looking for an action of type sactionGetURL whose data specifies the target URL "FSCommand:fullscreen" and the target window "true". To understand what's going on here, it's useful to remember that Flash was originally designed primarily for web-based content delivery and hence needed to operate within a web browser; the Flash movie sometimes also needed to communicate with that browser. The FSCommand mechanism was originally used for sending commands from Flash movies to a browser-based scripting engine, such as JavaScript or VBScript. I'm guessing, then, that "FSCommand" stands for something like "Flash-to-script command" and that the sactionGetURL action was chosen as the storage vehicle for FSCommands because it already provided a way to send text data to a web browser. (This might not be true, but it makes a pretty good story all the same.)

Macromedia has expanded the range of FSCommands by defining five commands that can be targeted at Flash playback applications, including the Flash Player application itself, standalone Flash projectors, and of course any QuickTime-savvy application (since they can open and display Flash files). In this case, the string that follows the prefix "FSCommand:" is the command and the target window string is the argument. The application-targeted commands are:

  • fullscreen. The argument must be either true or false. If the argument is true, the movie should be played full screen. If the argument is false, the movie should be played in normal window mode (that is, in a window whose original content area has the size specified in the file header block).
  • allowscale. The argument must be either true or false. If the argument is true, the Flash movie should be scaled to exactly fit the movie window content area if the window is resized. If the argument is false, the size of the Flash movie should remain unchanged even if the window is resized. This setting is useful if the movie contains bitmaps that may look pixilated or if playback performance unduly suffers when the window is resized too large. The default value for the allowscale property is true.
  • showmenu. The argument must be either true or false. If the argument is true, then a fully-enabled contextual menu is displayed when the user right-clicks (on Windows) or option-clicks (on Macintosh) in a Flash movie. (Figure 14 shows the Windows version of this contextual menu.) If the argument is false, only the "About Macromedia Flash Player 5..." menu item is enabled. The default value for the showmenu property is true.


    Figure 14: The Flash Player contextual menu
    Keep in mind that this contextual menu is provided by the playback application. Flash Player supports it, but QuickTime Player currently does not. I'll leave it as an exercise for the reader to add a menu like this to QTFlash. (I'll give you a little help, though; shortly we'll see how to handle the zooming menu items.)
  • exec. The argument must be a pathname (either full or relative to the location of the Flash playback application) of an external application. That application is launched. Note that no options or file names are passed to the application. If you need specific options or files to be opened, you can pass the pathname of a batch file (on Windows) or an AppleScript file (on Macintosh).
  • quit. The playback application should terminate. This command has no arguments.

When the Flash media handler encounters any of these FSCommands, it sends the movie controller a movie controller action of type mcActionDoScript. The parameter passed to the application's movie controller action filter function is the address of a structure of type QTDoScriptRecord, which is declared like this:

struct QTDoScriptRecord {
   long               scriptTypeFlags;
   char *            command;
   char *            arguments;
};

The scriptTypeFlags field indicates the type of script. For FSCommands, this field is set to kScriptIsUnknownType. The command field contains the command (for example, "fullscreen") and the arguments field contains the single argument (for example, "true").

If we want our applications to handle these FSCommands, we can add a new case statement to our movie controller action filter function, like this:

case mcActionDoScript:
   isHandled = QTFlash_DoFSCommand(theMC, 
               (QTDoScriptPtr)theParams, myWindowObject);
   break;

The QTFlash sample application contains the definition of QTFlash_DoFSCommand shown in Listing 14. As you can see, we handle only the quit command. It wouldn't be too hard to handle the fullscreen command, as QuickTime provides the pair of functions BeginFullScreen and EndFullScreen that allow us to enter and exit full-screen mode. We're a bit short on space, however, so I'll leave implementing the fullscreen command and the other three FSCommands as an exercise for the enterprising reader.

Listing 14: Handling FSCommands targeted at an application

Boolean QTFlash_DoFSCommand (MovieController theMC, 
      QTDoScriptPtr theScriptPtr, WindowObject theWindowObject)
{
   Boolean      isHandled = false;

   // make sure the parameters are all non-NULL
   if ((theMC == NULL) || (theScriptPtr == NULL) || 
            (theWindowObject == NULL))
      goto bail;

   // we handle scripts only of type kScriptIsUnknownType
   if (theScriptPtr->scriptTypeFlags != kScriptIsUnknownType)
      goto bail;

   // look for quit commands
   if (strcmp(theScriptPtr->command, "quit") == 0) {
      // quit the application
      QTFrame_QuitFramework();
      isHandled = true;
   }

bail:
   return(isHandled);
}

There is nothing magical about these five application-targeted FSCommands. We could easily construct a Flash movie whose interface elements issue FSCommands of our own devising. The Flash media handler will happily pass them along to our movie controller action filter function, where we can intercept and process them. This allows us, for instance, to use a Flash movie as the primary user interface for an application (which, incidentally, looks exactly the same on both Mac and Windows).

Flash Media Handler Functions

The Flash media handler supports a dozen or so functions that allow us to programmatically manipulate a Flash track and get information about a Flash track or about the Flash media handler itself. For example, if we are interested in determining which Flash file format versions are supported by the Flash media handler, we can call the FlashMediaGetSupportedSwfVersion function, like this:

myErr = FlashMediaGetSupportedSwfVersion(myHandler, &myChar);

If this call succeeds, then myChar will contain a byte that indicates the highest version number of Flash movie files supported by the Flash media handler. Here, of course, myHandler is a reference to the Flash media handler, which we can get like this:

myTrack = GetMovieIndTrackType(myMovie, 1, FlashMediaType,
            movieTrackMediaType):
myHandler = GetMediaHandler(GetTrackMedia(myTrack));

We can use the FlashMediaSetZoom function to support the "Zoom In", "Zoom Out", and "Show All" menu items provided by QTFlash. The "Zoom In" menu item doubles the current magnification of the Flash movie window, while the "Zoom Out" menu item halves the current magnification. The "Show All" menu item returns the window to its original magnification. FlashMediaSetZoom takes as a parameter an integer that indicates a relative magnification factor. To my knowledge, the precise meaning of this factor is not currently documented anywhere; a little experimentation, however, reveals that this factor is the percentage of the current window height and width that is to be scaled up or down to fill the window. For instance, to double the magnification, so that the central part of the image that occupies half the current window width and height fills the window after zooming, we would pass a factor of 50. To halve the magnification, we would pass a factor of 200. The special factor 0 returns the Flash movie to its original magnification. Listing 15 shows the code in our menu-handling function QTApp_HandleMenu that supports the zooming menu items.

Listing 15: Handling the zoom menu items

case IDM_ZOOM_IN:
   FlashMediaSetZoom(myHandler, 50);
   myIsHandled = true;
   break;

case IDM_ZOOM_OUT:
   FlashMediaSetZoom(myHandler, 200);
   myIsHandled = true;
   break;

case IDM_SHOW_ALL:
   FlashMediaSetZoom(myHandler, 0);
   myIsHandled = true;
   break;

When a Flash track is zoomed in, we can call the FlashMediaSetPan function to move the image around inside the movie window. For instance, we can move down to the right a small amount with this code:

FlashMediaSetPan(myHandler, 10, 10);

Once again the parameters here are percentages. The first parameter tells the media handler to move right by a distance that is ten percent of the window's current width. And the second parameter tells the media handler to move down by a distance that is ten percent of the window's current height.

The Flash media handler supplies a handful of additional functions, including FlashMediaGetDisplayedFrameNumber and FlashMediaSetFlashVariable. Look in the file Movies.h for a complete listing.

We've already seen (in Listing 13) that we can also use standard media-related functions like GetMediaSampleCount and GetMediaSample on Flash tracks. Listing 16 gives another example of using these functions, this time to extract a Flash track into a Flash movie file. We call QTFlash_ExtractFlashMovieFromTrack in response to the "Extract Flash Track..." menu item in the Test menu.

Listing 16: Extracting a Flash track from a movie

OSErr QTFlash_ExtractFlashMovieFromTrack (Movie theMovie, 
            long theIndex)
{
   Track                  myTrack = NULL;
   Media                  myMedia = NULL;
   long                     mySize = 0L;
   Handle                  myMediaData = NULL;
   SampleDescriptionHandle      
                           myDesc = NULL;
   FSSpec                  myFSSpec;
   Boolean               myIsSelected = false;
   Boolean               myIsReplacing = false;
   StringPtr             myPrompt = 
               QTUtils_ConvertCToPascalString(kSaveFlashPrompt);
   StringPtr             myFileName = NULL;
   char                   *myTrackName = NULL;
   char                   *myString = NULL;
   short                  myLength;
   OSErr                  myErr = noErr;

   myTrack = GetMovieIndTrackType(theMovie, theIndex, 
            FlashMediaType, movieTrackMediaType);
   if (myTrack == NULL)
      goto bail;

   myMedia = GetTrackMedia(myTrack);
   if (myMedia == NULL)
      goto bail;

   myMediaData = NewHandle(0);
   if (myMediaData == NULL)
      goto bail;

   myDesc = (SampleDescriptionHandle)NewHandleClear
            (sizeof(SampleDescription));
   if (myDesc == NULL)
      goto bail;

#if ONLY_ONE_MEDIA_SAMPLE
   // in theory, there should be only one media sample in the Flash track;
   // report an error if we get more than one
   if (GetMediaSampleCount(myMedia) != 1) {
      QTFrame_Beep();
      goto bail;
   }
#endif
   
   myErr = GetMediaSample(myMedia, myMediaData, 0, &mySize, 
      (TimeValue)0, NULL, NULL, myDesc, NULL, 1, NULL, NULL);
   if (myErr != noErr)
      goto bail;

   // get the name of the track; we'll use this as the suggested filename
   myTrackName = QTUtils_GetTrackName(myTrack);
   if (myTrackName == NULL)
      // if no existing name, synthesize a track name
      myTrackName = QTUtils_MakeTrackNameByType(myTrack);

   // suggest the track name + .swf as the filename
   myLength = strlen(myTrackName) + 
            strlen(kFlashFileExtension) + 1;
   myString = malloc(myLength);
   memcpy(myString, myTrackName, strlen(myTrackName));
   memcpy(myString + strlen(myTrackName), kFlashFileExtension, 
            strlen(kFlashFileExtension));
   myString[myLength - 1] = ‘\0';

   myFileName = QTUtils_ConvertCToPascalString(myString);

   // get a file from the user
   myErr = QTFrame_PutFile(myPrompt, myFileName, &myFSSpec, 
            &myIsSelected, &myIsReplacing);
   if (myIsSelected) {

      // delete any existing file of that name
      if (myIsReplacing) {
         myErr = FSpDelete(&myFSSpec);
         if (myErr != noErr)
            goto bail;
      }

      // write the Flash media data into a file
      myErr = QTFlash_WriteHandleToFile(myMediaData, 
            &myFSSpec);
   }

bail:
   if (myMediaData != NULL)
      DisposeHandle(myMediaData);

   if (myDesc != NULL)
      DisposeHandle((Handle)myDesc);

   free(myTrackName);
   free(myString);
   free(myPrompt);
   free(myFileName);

   return(myErr);
}

Conclusion

It's sometimes tempting to think of a full-featured interactive graphics and animation package like Macromedia Flash — which also handles sound pretty well too, by the way — as a competitor to QuickTime, but of course quite the opposite is true. Flash and QuickTime can be combined in ways that enhance the capabilities of each package. Flash brings to the table a sophisticated vector-based drawing engine that's coupled with basic mouse and keyboard interactivity and with an object-oriented scripting capability. QuickTime adds a tight integration with dozens and dozens of popular media types, ranging from video and sound to text and sprites and virtual reality. In addition, it includes a mature wiring capability that vastly surpasses the interactive repertoire of Flash. Together, Flash and QuickTime provide a very powerful content delivery tool.

In this article, we've investigated a number of ways to work with Flash data, either alone or in combination with QuickTime movies. We've built the foundation of a parser that can read through Flash files and extract information about the various objects and commands in the file. We've seen how to work with the Flash media handler APIs, and how to handle application-specific commands emitted by a Flash movie. Still, some of the best is yet to come. In the next QuickTime Toolkit article, we'll finally see how to use wired actions with Flash tracks.

Acknowledgements and Credits

Thanks are due to Kazuhisa Ohta and Troy Evans for reviewing this article and offering some helpful comments. Thanks are also due to Wildform, Inc. for providing assistance with Flix.

Much of the code for parsing Flash files is based on the file swfparse.cpp, originally developed by David Michie and available at
http://www.openswf.org. Additional information about the Macromedia Flash file format is available at
http://www.openswf.org.

Macromedia and Flash are trademarks or registered trademarks of Macromedia, Inc.


Tim Monroe is a member of the QuickTime Engineering team. You can contact him at monroe@apple.com.

 
AAPL
$105.22
Apple Inc.
+0.39
MSFT
$46.13
Microsoft Corpora
+1.11
GOOG
$539.78
Google Inc.
-4.20

MacTech Search:
Community Search:

Software Updates via MacUpdate

OS X Server 4.0 - For OS X 10.10 Yosemit...
Designed for OS X and iOS devices, OS X Server makes it easy to share files, schedule meetings, synchronize contacts, develop software, host your own website, publish wikis, configure Mac, iPhone,... Read more
TotalFinder 1.6.12 - Adds tabs, hotkeys,...
TotalFinder is a universally acclaimed navigational companion for your Mac. Enhance your Mac's Finder with features so smart and convenient, you won't believe you ever lived without them. Tab-based... Read more
BusyCal 2.6.3 - Powerful calendar app wi...
BusyCal is an award-winning desktop calendar that combines personal productivity features for individuals with powerful calendar sharing capabilities for families and workgroups. BusyCal's unique... Read more
calibre 2.7 - Complete e-library managem...
Calibre is a complete e-book library manager. Organize your collection, convert your books to multiple formats, and sync with all of your devices. Let Calibre be your multi-tasking digital... Read more
Skitch 2.7.3 - Take screenshots, annotat...
With Skitch, taking, annotating, and sharing screenshots or images is as fun as it is simple.Communicate and collaborate with images using Skitch and its intuitive, engaging drawing and annotating... Read more
Delicious Library 3.3.2 - Import, browse...
Delicious Library allows you to import, browse, and share all your books, movies, music, and video games with Delicious Library. Run your very own library from your home or office using our... Read more
Art Text 2.4.8 - Create high quality hea...
Art Text is an OS X application for creating high quality textual graphics, headings, logos, icons, Web site elements, and buttons. Thanks to multi-layer support, creating complex graphics is no... Read more
Live Interior 3D Pro 2.9.6 - Powerful an...
Live Interior 3D Pro is a powerful yet very intuitive interior designing application. View Video Tutorials It has every feature of Live Interior 3D Standard, plus some exclusive ones: Create multi... Read more
The Hit List 1.1.7 - Advanced reminder a...
The Hit List manages the daily chaos of your modern life. It's easy to learn - it's as easy as making lists. And it's powerful enough to let you plan, then forget, then act when the time is right.... Read more
jAlbum Pro 12.2.4 - Organize your digita...
jAlbum Pro has all the features you love in jAlbum, but comes with a commercial license. With jAlbum, you can create gorgeous custom photo galleries for the Web without writing a line of code!... Read more

Latest Forum Discussions

See All

Rami Ismail Opens Up distribute​() for D...
Rami Ismail Opens Up distribute​() for Developers Posted by Jessica Fisher on October 24th, 2014 [ permalink ] Rami Ismail, Chief Executive of Business and Development at indie game studio | Read more »
Great Hitman GO Goes on Sale and Gets Ne...
Great Hitman GO Goes on Sale and Gets New Update – Say That Three Times Fast Posted by Jessica Fisher on October 24th, 2014 [ permalink ] | Read more »
Rival Stars Basketball Review
Rival Stars Basketball Review By Jennifer Allen on October 24th, 2014 Our Rating: :: RESTRICTIVE BUT FUNUniversal App - Designed for iPhone and iPad Rival Stars Basketball is a fun mixture of basketball and card collecting but its... | Read more »
Rubicon Development Makes Over a Dozen o...
Rubicon Development Makes Over a Dozen of Their Games Free For This Weekend Only Posted by Jessica Fisher on October 24th, 2014 [ permalink ] | Read more »
I Am Dolphin Review
I Am Dolphin Review By Jennifer Allen on October 24th, 2014 Our Rating: :: NEARLY FIN-TASTICUniversal App - Designed for iPhone and iPad Swim around and eat nearly everything that moves in I Am Dolphin, a fun Ecco-ish kind of game... | Read more »
nPlayer looks to be the ultimate choice...
Developed by Newin Inc, nPlayer may seem like your standard video player – but is aiming to be the best in its field by providing high quality video play performance and support for a huge number of video formats and codecs. User reviews include... | Read more »
Fighting Fantasy: Caverns of the Snow Wi...
Fighting Fantasy: Caverns of the Snow Witch Review By Jennifer Allen on October 24th, 2014 Our Rating: :: CLASSY STORYTELLINGUniversal App - Designed for iPhone and iPad Fighting Fantasy: Caverns of the Snow Witch is a sterling... | Read more »
A Few Days Left (Games)
A Few Days Left 1.01 Device: iOS Universal Category: Games Price: $3.99, Version: 1.01 (iTunes) Description: Screenshots are in compliance to App Store's 4+ age rating! Please see App Preview for real game play! **Important: Make... | Read more »
Toca Boo (Education)
Toca Boo 1.0.2 Device: iOS Universal Category: Education Price: $2.99, Version: 1.0.2 (iTunes) Description: BOO! Did I scare you!? My name is Bonnie and my family loves to spook! Do you want to scare them back? Follow me and I'll... | Read more »
Intuon (Games)
Intuon 1.1 Device: iOS Universal Category: Games Price: $.99, Version: 1.1 (iTunes) Description: Join the battle with your intuition in a new hardcore game Intuon! How well do you trust your intuition? Can you find a needle in a... | Read more »

Price Scanner via MacPrices.net

Weekend sale: 13-inch 128GB MacBook Air for $...
Best Buy has the 2014 13-inch 1.4GHz 128GB MacBook Air on sale for $849.99, or $150 off MSRP, on their online store. Choose free home shipping or free local store pickup (if available). Price valid... Read more
Nimbus Note Cross=Platform Notes Utility
Nimbus Note will make sure you never forget or lose your valuable data again. Create and edit notes, save web pages, screenshots and any other type of data – and share it all with your friends and... Read more
NewerTech’s Snuglet Makes MagSafe 2 Power Con...
NewerTech has introduced the Snuglet, a precision-manufactured ring designed to sit inside your MagSafe 2 connector port, providing a more snug fit to prevent your power cable from unintentional... Read more
Apple Planning To Sacrifice Gross Margins To...
Digitimes Research’s Jim Hsiao says its analysts believe Apple is planning to sacrifice its gross margins to save its tablet business, which has recently fallen into decline. They project that Apple’... Read more
Who’s On Now? – First Instant-Connect Search...
It’s nighttime and your car has broken down on the side of the highway. You need a tow truck right away, so you open an app on your iPhone, search for the closest tow truck and send an instant... Read more
13-inch 2.5GHz MacBook Pro on sale for $949,...
Best Buy has the 13″ 2.5GHz MacBook Pro available for $949.99 on their online store. Choose free shipping or free instant local store pickup (if available). Their price is $150 off MSRP. Price is... Read more
Save up to $125 on Retina MacBook Pros
B&H Photo has the new 2014 13″ and 15″ Retina MacBook Pros on sale for up to $125 off MSRP. Shipping is free, and B&H charges NY sales tax only. They’ll also include free copies of Parallels... Read more
Apple refurbished Time Capsules available sta...
The Apple Store has certified refurbished Time Capsules available for up to $60 off MSRP. Apple’s one-year warranty is included with each Time Capsule, and shipping is free: - 2TB Time Capsule: $255... Read more
Textilus New Word, Notes and PDF Processor fo...
Textilus is new word-crunching, notes, and PDF processor designed exclusively for the iPad. I haven’t had time to thoroughly check it out yet, but it looks great and early reviews are positive.... Read more
WD My Passport Pro Bus-Powered Thunderbolt RA...
WD’s My Passport Pro RAID solution is powered by an integrated Thunderbolt cable for true portability and speeds as high as 233 MB/s. HighlightsOverviewSpecifications Transfer, Back Up And Edit In... Read more

Jobs Board

*Apple* Solutions Consultant - Apple Inc. (U...
…important role that the ASC serves is that of providing an excellent Apple Customer Experience. Responsibilities include: * Promoting Apple products and solutions Read more
Senior Event Manager, *Apple* Retail Market...
…This senior level position is responsible for leading and imagining the Apple Retail Team's global event strategy. Delivering an overarching brand story; in-store, Read more
*Apple* Solutions Consultant (ASC) - Apple (...
**Job Summary** The ASC is an Apple employee who serves as an Apple brand ambassador and influencer in a Reseller's store. The ASC's role is to grow Apple Read more
Project Manager / Business Analyst, WW *Appl...
…a senior project manager / business analyst to work within our Worldwide Apple Fulfillment Operations and the Business Process Re-engineering team. This role will work Read more
*Apple* Retail - Multiple Positions (US) - A...
Job Description: Sales Specialist - Retail Customer Service and Sales Transform Apple Store visitors into loyal Apple customers. When customers enter the store, Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.