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.

 

Community Search:
MacTech Search:

Software Updates via MacUpdate

Latest Forum Discussions

See All

Summon your guild and prepare for war in...
Netmarble is making some pretty big moves with their latest update for Seven Knights Idle Adventure, with a bunch of interesting additions. Two new heroes enter the battle, there are events and bosses abound, and perhaps most interesting, a huge... | Read more »
Make the passage of time your plaything...
While some of us are still waiting for a chance to get our hands on Ash Prime - yes, don’t remind me I could currently buy him this month I’m barely hanging on - Digital Extremes has announced its next anticipated Prime Form for Warframe. Starting... | Read more »
If you can find it and fit through the d...
The holy trinity of amazing company names have come together, to release their equally amazing and adorable mobile game, Hamster Inn. Published by HyperBeard Games, and co-developed by Mum Not Proud and Little Sasquatch Studios, it's time to... | Read more »
Amikin Survival opens for pre-orders on...
Join me on the wonderful trip down the inspiration rabbit hole; much as Palworld seemingly “borrowed” many aspects from the hit Pokemon franchise, it is time for the heavily armed animal survival to also spawn some illegitimate children as Helio... | Read more »
PUBG Mobile teams up with global phenome...
Since launching in 2019, SpyxFamily has exploded to damn near catastrophic popularity, so it was only a matter of time before a mobile game snapped up a collaboration. Enter PUBG Mobile. Until May 12th, players will be able to collect a host of... | Read more »
Embark into the frozen tundra of certain...
Chucklefish, developers of hit action-adventure sandbox game Starbound and owner of one of the cutest logos in gaming, has released their roguelike deck-builder Wildfrost. Created alongside developers Gaziter and Deadpan Games, Wildfrost will... | Read more »
MoreFun Studios has announced Season 4,...
Tension has escalated in the ever-volatile world of Arena Breakout, as your old pal Randall Fisher and bosses Fred and Perrero continue to lob insults and explosives at each other, bringing us to a new phase of warfare. Season 4, Into The Fog of... | Read more »
Top Mobile Game Discounts
Every day, we pick out a curated list of the best mobile discounts on the App Store and post them here. This list won't be comprehensive, but it every game on it is recommended. Feel free to check out the coverage we did on them in the links below... | Read more »
Marvel Future Fight celebrates nine year...
Announced alongside an advertising image I can only assume was aimed squarely at myself with the prominent Deadpool and Odin featured on it, Netmarble has revealed their celebrations for the 9th anniversary of Marvel Future Fight. The Countdown... | Read more »
HoYoFair 2024 prepares to showcase over...
To say Genshin Impact took the world by storm when it was released would be an understatement. However, I think the most surprising part of the launch was just how much further it went than gaming. There have been concerts, art shows, massive... | Read more »

Price Scanner via MacPrices.net

You can save $300-$480 on a 14-inch M3 Pro/Ma...
Apple has 14″ M3 Pro and M3 Max MacBook Pros in stock today and available, Certified Refurbished, starting at $1699 and ranging up to $480 off MSRP. Each model features a new outer case, shipping is... Read more
24-inch M1 iMacs available at Apple starting...
Apple has clearance M1 iMacs available in their Certified Refurbished store starting at $1049 and ranging up to $300 off original MSRP. Each iMac is in like-new condition and comes with Apple’s... Read more
Walmart continues to offer $699 13-inch M1 Ma...
Walmart continues to offer new Apple 13″ M1 MacBook Airs (8GB RAM, 256GB SSD) online for $699, $300 off original MSRP, in Space Gray, Silver, and Gold colors. These are new MacBook for sale by... Read more
B&H has 13-inch M2 MacBook Airs with 16GB...
B&H Photo has 13″ MacBook Airs with M2 CPUs, 16GB of memory, and 256GB of storage in stock and on sale for $1099, $100 off Apple’s MSRP for this configuration. Free 1-2 day delivery is available... Read more
14-inch M3 MacBook Pro with 16GB of RAM avail...
Apple has the 14″ M3 MacBook Pro with 16GB of RAM and 1TB of storage, Certified Refurbished, available for $300 off MSRP. Each MacBook Pro features a new outer case, shipping is free, and an Apple 1-... Read more
Apple M2 Mac minis on sale for up to $150 off...
Amazon has Apple’s M2-powered Mac minis in stock and on sale for $100-$150 off MSRP, each including free delivery: – Mac mini M2/256GB SSD: $499, save $100 – Mac mini M2/512GB SSD: $699, save $100 –... Read more
Amazon is offering a $200 discount on 14-inch...
Amazon has 14-inch M3 MacBook Pros in stock and on sale for $200 off MSRP. Shipping is free. Note that Amazon’s stock tends to come and go: – 14″ M3 MacBook Pro (8GB RAM/512GB SSD): $1399.99, $200... Read more
Sunday Sale: 13-inch M3 MacBook Air for $999,...
Several Apple retailers have the new 13″ MacBook Air with an M3 CPU in stock and on sale today for only $999 in Midnight. These are the lowest prices currently available for new 13″ M3 MacBook Airs... Read more
Multiple Apple retailers are offering 13-inch...
Several Apple retailers have 13″ MacBook Airs with M2 CPUs in stock and on sale this weekend starting at only $849 in Space Gray, Silver, Starlight, and Midnight colors. These are the lowest prices... Read more
Roundup of Verizon’s April Apple iPhone Promo...
Verizon is offering a number of iPhone deals for the month of April. Switch, and open a new of service, and you can qualify for a free iPhone 15 or heavy monthly discounts on other models: – 128GB... Read more

Jobs Board

Relationship Banker - *Apple* Valley Financ...
Relationship Banker - Apple Valley Financial Center APPLE VALLEY, Minnesota **Job Description:** At Bank of America, we are guided by a common purpose to help Read more
IN6728 Optometrist- *Apple* Valley, CA- Tar...
Date: Apr 9, 2024 Brand: Target Optical Location: Apple Valley, CA, US, 92308 **Requisition ID:** 824398 At Target Optical, we help people see and look great - and Read more
Medical Assistant - Orthopedics *Apple* Hil...
Medical Assistant - Orthopedics Apple Hill York Location: WellSpan Medical Group, York, PA Schedule: Full Time Sign-On Bonus Eligible Remote/Hybrid Regular Apply Now Read more
*Apple* Systems Administrator - JAMF - Activ...
…**Public Trust/Other Required:** None **Job Family:** Systems Administration **Skills:** Apple Platforms,Computer Servers,Jamf Pro **Experience:** 3 + years of Read more
Liquor Stock Clerk - S. *Apple* St. - Idaho...
Liquor Stock Clerk - S. Apple St. Boise Posting Begin Date: 2023/10/10 Posting End Date: 2024/10/14 Category: Retail Sub Category: Customer Service Work Type: Part Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.