Requirements

Prerequisite knowledge
This article targets intermediate to experienced web developers. Knowledge of Flex, PHP, and server configuration is recommended.


User level
Intermediate

Required products

Flex Builder(Download trial)

Sample files
video_sharing_application.zip (413 KB)


Adobe Flash has revolutionized the way people consume video today, and traditional media is just a small part of it. The revolution started with user-generated content—the dancing bird, the barking puppy, the latest news from the person who just happened to be at an event. The key for success of user-generated content is the ability to offer a very user-friendly experience to upload, share videos, and watch them without hassle. All of this makes the Adobe Flash platform—with Adobe Flash Player penetration at 99%—an ideal choice for video sharing web applications.

This article demonstrates how to create your own social media application in just a few steps, hopefully providing you with a sound foundation to build your own experience. It's important to note that Adobe Flash Media Server allows capturing directly from your webcam while you're online. Instead of focusing on this feature, however, this article describes an easy-to-use upload/transcode/playback workflow.

The goal of this article is to demonstrate how to create a video sharing application as follows:

  1. Using Adobe Flex, display video thumbnails of videos.
  2. Using Flex, select a video file from the user's local hard drive.
  3. Using Flex and PHP, upload the file to the web server.
  4. Using Flash Media Encoding Server, transcode the video.
  5. Using Flex and Adobe Flash Media Server, update the video thumbnail list.
  6. Using Flex and Flash Media Server, enable the user to click on a thumbnail to play the video in the video player.

Figure 1 provides an architectural diagram of the application discussed in this article.

Video sharing application architecture
Figure 1. Video sharing application architecture

It's possible to combine the web server, Flash Media Encoding Server, and Flash Media Server on a single machine, but it can affect performance. Ideally the Flash Media Server is on a dedicated machine to allow maximum throughput.

Note: I chose to build the front end using Flex Builder, but it is also possible to build this in Adobe Flash.

Creating the video upload component

You can use Flex or Flash technology (or even an HTML web form) to build a front end for uploading files to a web server. Either way, you'll need a server-side script to receive and save the files. In this case, I'll use a simple PHP script.

Please note that PHP limits the upload capacity to 2MB in the default configuration. If you plan to use this example to build and deploy your own application, I recommend changing the PHP configuration to increase the maximum upload limit to allow users to upload larger files if desired.

Building the upload interface in Flex

As a first step, you'll create a Flex-based upload interface using Flex UI components and the built-in upload feature of the FileReference class. Open your Flex authoring environment and create a new project; name it videoshare. Use the code below to display a simple upload interface with a progress bar:

<?xml version="1.0" encoding="utf-8"?> <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute"> <mx:HBox id="mainui" height="330" autoLayout="false"> <mx:Form id="uploadform" horizontalScrollPolicy="off" verticalScrollPolicy="off"> <mx:FormItem label="Choose your file:" id="formitem"> <mx:TextInput id="videofile" editable="false" x="10" y="358"/> </mx:FormItem> <mx:FormItem id="browsearea" > <mx:Button label="Browse" click="onBrowse()" x="180" y="359" id="browse_btn"/> <mx:Button id="upload_btn" click="onUpload()" label="Upload" width="68" enabled="false" x="256" y="360"/> </mx:FormItem> </mx:Form> </mx:HBox> </mx:Application>


This MXML structure displays the upload component with a progress bar shown in Figure 2.

User interface for browsing and uploading a video file
Figure 2. User interface for browsing and uploading a video file

To enable the button functionality, you'll need to define the onBrowse() and onUpload() functions for handling the click events:

// Event handler for the browse button private function onBrowse() : void { fileRef = new FileReference(); fileRef.addEventListener(Event.SELECT, selectHandler); try { var success:Boolean = fileRef.browse(); } catch (error:Error) { trace("Unable to browse for files."); } function selectHandler(event:Event):void { videofile.text = fileRef.name; status.text = "Ready for upload"; uploadedFile = fileRef.name; upload_btn.enabled = true; } } // Event handler for the upload button // Triggers an upload to the server private function onUpload():void { progressbar.label == ""; var request:URLRequest = new URLRequest("http://" + phpserver + "/videoscript/upload.php") // Add an event listener to monitor the progress of the upload fileRef.addEventListener(ProgressEvent.PROGRESS, progressHandler); try { fileRef.upload(request); status.text = "Uploading " + fileRef.name + " ... "; browse_btn.enabled = false; upload_btn.enabled = false; } catch (error:Error) { status.text = "Upload error"; } }

The ActionScript code above creates a FileReference object, which allows the user to select the file with the browse dialog box and upload the file to the server. Since video files can be large, an important visual indicator is the progress bar. The code above already added progressHandler() as an event listener, which displays the upload progress with the progress bar: 

// Displays the current progress of the upload private function progressHandler(event:ProgressEvent):void { progressbar.setProgress(Math.floor(event.bytesLoaded/1024),Math.floor(event.bytesTotal/1024)); progressbar.label = "Uploading .. " + Math.floor(event.bytesLoaded/1024) + " of " + Math.floor(event.bytesTotal/1024) + " kbytes";''; if (event.bytesLoaded == event.bytesTotal) { completeHandler(); } }

Writing the server-side PHP script

To receive the files on a web server, you'll need a server-side component. In this case, you'll use a PHP script for this purpose. Create an upload.php file and save it at a path that is accessible through HTTP at http://[MY_SERVER_DNS]/videoscript/upload.php. Add the following script to the file:

<?php if(!is_dir("c:/videoshare/uploaded")) mkdir("c:/videoshare/uploaded", 0755); move_uploaded_file($_FILES['Filedata']['tmp_name'], "c:/videoshare/uploaded/".$_FILES['Filedata']['name']); ?>

The script receives the uploaded files and moves them to the folder C:\videoshare\uploaded.

Note: The path is Windows specific; another OS might follow a different file path pattern.

Setting up video encoding

For this part you will use the Flash Media Encoding Server, which operates as the encoding engine on the server side. There are multiple ways to add jobs to the encoding queue including simple file-based watch folders, a watch folder for XML-based jobs, a command line interface, and a socket connection for advanced communication and interaction. For this application, you will use the socket connection.

Important note: To establish a connection between a Flex application and a socket server, even on the same domain, you'll need to run a policy server on port 843 to return a socket policy file on request. It sounds complicated, but there are existing scripts that support an easy setup. For more information on this required step, see Setting up a socket policy file server.

Flash Media Encoding Server

You can use Flash Media Encoding Server to set up your own preset. To set up a preset that fits your needs, choose Tools > Preset Editor and create a preset (see Figure 3) with 800 kbps H.264 encode and 720 × 480 resolution (480p).

List of encoding profiles
Figure 3. List of encoding profiles

To identify the preset when submitting the job through the socket connection, find the GUID in the preset editor. The GUID will be a string of hexadecimal values, for example: "{85DCCE70-0B4F-4328-86D1-5FC7756D9777}".

Set up the socket connection

The socket connection logic requires additional code on the Flex side to ensure appropriate visual feedback, which includes a progress bar to display the progress of the encode and updated status messages. You may want to open the sample source code to follow the next steps.

Flash Media Encoding Server requires a valid XML command, in this case sent through a socket connection, to add a job to the rendering queue. For better maintainability, I have externalized the commands in the submit.xml and checkstatus.xml files with placeholders for the file to encode. Please also make sure you replace the PresetGUID value in the XML below with the GUI of your encoding profile.

submit.xml

<cnpsXML TaskType="JobQueue"> <Sources> <Module_0 Filename="c:\videoshare\uploaded\:::filename:::"/> </Sources> <Destinations><Module_0 PresetGUID="{85DCCE70-0B4F-4328-86D1-5FC7756D9777}" ModuleGUID="{85DCCE70-0B4F-4328-86D1-5FC7756D9777}" AssignedTags=""><ModuleData CML_P_Path="C:\Program Files\Adobe\Flash Media Server 3.5\applications\videoshare\streams\_definst_" CML_P_BaseFileName="%s"/><PostconversionTasks/><Filter_1/><Filter_0/></Module_0></Destinations> </cnpsXML>

The submit command encodes the file and then moves to it the Flash Media Server application folder to allow the playback of the file. The filename is a unique placeholder. The Flex code will replace this value with the filename of the current job.

checkstatus.xml

<cnpsXML TaskType="JobStatusList"> <Filter> <Criteria_0 Parameter ="Guid" Operator="EQUAL" Value=":::guid:::" /> </Filter> </cnpsXML>

The checkstatus XML triggers the JobStatusList command. This Flash Media Encoding Server command normally returns all jobs. In this case you want to limit it to the active job initiated by the active user; therefore it contains limitation criteria with the <filter> tag.

The following code establishes the socket connection and sends commands through it. It first loads the XML submission files via loadFMESAPIConfig() (for details on this method, see the sample source code), establishes a connection to Flash Media Server (I'll cover this in more detail below), and then, once the user has successfully uploaded a video file, triggers the encoding process.

To better understand the flow of the application, here are the steps that follow a successful file upload:

  1. Establish socket connection.
  2. Once connection successful, replace filename in submit.xml and submit command to Flash Media Encoding Server.
  3. Parse the XML response returned from the socket and extract the active job GUID.
  4. Use the GUID of the active job (not the preset) to query the status of the encoding process with the XML from checkstatus.xml.
  5. Parse the XML response from the socket and extract the current status (either not started yet, in process, or finished) and update the progress bar and status text accordingly.
  6. Once the encoding is complete, close the socket connection and retrieve the latest thumbnail list from Flash Media Server (more details below).

videoshare.mxml

// Initializes the interface and connects to the Flash Media Server private function init() : void { nc = new NetConnection(); fmsserver = "myserver.com"; phpserver = "myserver.com"; fmesserver = "myserver.com";; fmesport = 1547; nc.connect("rtmp://"+fmsserver+"/videoshare/"); nc.addEventListener(NetStatusEvent.NET_STATUS,onConnect); playbackstatus = "stop"; // Loads the FMES XML command files loadFMESAPIConfig(); progressbar.mode = "manual"; progressbar.label = ""; // Event listener for the fullscreen mode Application.application.stage.addEventListener(FullScreenEvent.FULL_SCREEN, fullScreenHandler); } // Once the upload is complete, initialize the video conversion private function completeHandler(event:Event):void { status.text = "Upload complete"; fmessocket = new Socket(); configureListeners(fmessocket); sendFMESCommand("submitjob"); } // Establishes a new connection and connects to FMES private function sendFMESCommand(command:String) : void { activecommand = command; if (fmessocket.connected) fmessocket.close(); if (fmesserver && fmesport) { fmessocket.connect(fmesserver, fmesport); } } // Configures all listeners for the socket connection primarily for debug reasons private function configureListeners(dispatcher:IEventDispatcher):void { dispatcher.addEventListener( ProgressEvent.SOCKET_DATA, dataHandler ); dispatcher.addEventListener( IOErrorEvent.IO_ERROR, ioErrorHandler ); dispatcher.addEventListener( Event.CONNECT, connectHandler ); dispatcher.addEventListener( Event.CLOSE, closeHandler ); dispatcher.addEventListener( SecurityErrorEvent.SECURITY_ERROR, securityErrorHandler ); } // Triggered once connection closes private function closeHandler(event:Event):void { debug("closeHandler: " + event); } // Triggered once connection is established. This event should happen at the beginning of the encode // Replaces the filename property of the XML command template with the uploaded file name value private function connectHandler(event:Event):void { if (activecommand == "submitjob") { var replaceFileNameExp:RegExp = /:::filename:::/; submitFMESCommand(submitapixml.replace(replaceFileNameExp,uploadedFile)); } } // Submits a FMES command to the server through the established socket connect private function submitFMESCommand(commandxml:String) : void { debug(commandxml); var msg:String = commandxml; var bytes:ByteArray = new ByteArray(); bytes.writeMultiByte( msg, "iso-8859-1" ); var messageBytes:ByteArray = new ByteArray(); var messageString:String = "CarbonAPIXML1 " + bytes.length.toString() + " " + msg; messageBytes.writeMultiByte( messageString, "iso-8859-1" ); fmessocket.writeBytes( messageBytes ); fmessocket.flush(); } // Handles the response from FMES private function dataHandler(event:ProgressEvent):void { var bytesAvailable:int = fmessocket.bytesAvailable; var type:String = fmessocket.readMultiByte( 13, "utf-8" ); fmessocket.readMultiByte( 1, "utf-8" ); var ttlBytes:int = int( fmessocket.readMultiByte( 2, "utf-8" ) ); fmessocket.readMultiByte( 1, "utf-8" ); var str:String = fmessocket.readUTFBytes( fmessocket.bytesAvailable ); // Parses and assigns the response to a XML variable fmesresponse = new XML( str ); // Data can be received following the start job or the monitoring command switch (activecommand) { case "submitjob" : // Reads and stores the active job id activejobguid = (fmesresponse.attributes()[0].toString()); var replaceGuidExp:RegExp = /:::guid:::/; checkstatusxml = checkstatusapixml.replace(replaceGuidExp,activejobguid); activecommand = "checkjobstatus"; // Checks for the job status after 2 seconds setTimeout(checkEncodingStatus,2000); break; case "checkjobstatus" : // Reads the received status variable from the XML response var currentstatus:String = fmesresponse.children()[0].children()[0].attribute("Status"); // Reads the received progress from the XML response var encodingprogress:String = fmesresponse.children()[0].children()[0].attribute("Progress.DWD"); // Updates the progress bar component progressbar.setProgress(Number(encodingprogress),100); // Updates the label for the progress bar depending on the current status if (currentstatus!="STARTED" && currentstatus!="COMPLETED") { progressbar.label = "Waiting in encoding queue"; } if (encodingprogress=="0") { progressbar.label = "Preparing encoding.."; } else { progressbar.label = "Encoding .. " +encodingprogress+ "% completed"; } // Once encoding is complete, updates the UI and closes the socket connection to FMES if (currentstatus=="COMPLETED") { fmessocket.close(); progressbar.label = "Encoding complete"; refreshVideos(); browse_btn.enabled = true; upload_btn.enabled = false; } // If encoding is not complete, check again in 2 secs what the current status is else { setTimeout(checkEncodingStatus,2000); } break; } } // Triggers the FMES command private function checkEncodingStatus() : void { submitFMESCommand(checkstatusxml); } // Error handler private function ioErrorHandler(event:IOErrorEvent):void { debug("ioErrorHandler: " + event); } // Security handler private function securityErrorHandler(event:SecurityErrorEvent):void { debug("securityErrorHandler: " + event); for (var foo:String in event) { debug(foo + " " + event[foo]); } }

I won't explain the source line by line, since the concept is relatively simple and with comments the code should be fairly self-explanatory.

I have defined socket connection event handlers for I/O and security issues. If, for example, a security error occurs, the security handler will be triggered. This can happen when the security policy server on port 843 is not properly set up.

Since it employs the same socket connection for both commands with a single data handler, the application uses an activecommand state to differentiate between submitting the encoding call and querying the current status of the encoding process.

Configuring Flash Media Server

The next step is to configure Flash Media Interactive Server or Flash Media Development Server to stream the content to the audience and to generate thumbnails of the video clips in real-time.

This application will use the following features:

  • Playback of recorded video files
  • Server-side functionality to gather a list of all uploaded videos and send it to the Flex client
  • Seek and pause functionality to display thumbnails of the video files

It's also possible to use the Flash Media Streaming Server for this application. However, since it doesn't support custom server-side scripts, generating the list of uploaded videos would have use a different approach, such as a PHP script for example.

Flash Media Development Server provides all the features in Flash Media Interactive Server, but with a limit of ten simultaneous users. You can start with the free developer edition during development and then upgrade to the full version without a reinstall once the system moves into production.

Understanding the Flash Media Server application and instance architecture

Flash Media Server enables you to host several applications with multiple instances each. An instance of an application has its own memory space and executes independently. An example would be a chat application with multiple rooms. The shared logic of the application could be located in the folder of the application, while users would be able to create different, independent rooms of the chat application. When there is no instance specified, it will automatically default to _definst_, the default instance of the application. Streams need to be located in the folder \[MYAPPLICATION]\streams\[MYINSTANCENAME]\.

In this application example, the application logic is located in the videoshare application folder: C:\Program Files\Adobe\Flash Media Server 3.5\applications\videoshare\

Since instances are not required, the encoded F4V files are located in the default instance folder of the application: C:\Program Files\Adobe\Flash Media Server 3.5\applications\videoshare\streams\_definst_\

Writing the application script

Flash Media Server scripts are defined in the main.asc file of the application folder. Create a main.asc file (ASCII text file) and place it here: C:\Program Files\Adobe\Flash Media Server 3.5\applications\videoshare\main.asc

Copy the following code into the file:

application.onConnect = function(clObj) { this.acceptConnection(clObj); } Client.prototype.getFiles = function() { var fileList = new File("/streams/_defInst_/"); var temp = fileList.list(); return temp; }

The first function automatically accepts incoming connections.The getFiles() function reads all the filenames of the encoded files in the /streams/_defInst_/ folder and returns it to the client as an array. This data allows the Flex application to display thumbnails of the video files.

Building the thumbnail browser

There are several ways to display video thumbnails. The sample application for this article connects to Flash Media Server, starts playing the video, waits until the buffer fills up, seeks to a certain position, and then pauses.

As an alternative approach, you can use Flash or Flex to build an application that captures bitmap data of visual elements; with the appropriate Flash Media Server configuration the application can capture a thumbnail of the video. You could send this data to a server script and create a JPEG file on the server side. Another option would be to configure the encoder to create an additional JPEG file of the encoded video file.

Building the Flex application: data

Now you'll create a dynamic data structure in Flex and connect it to the Flash Media Server data source to display the video list. The video grid updates when the application starts up and whenever the server finishes encoding a file. Both events trigger the refreshVideos() function:

private function init() : void { nc = new NetConnection(); fmsserver = "myserver.com"; phpserver = "myserver.com"; fmesserver = "myserver.com"; fmesport = 1547; nc.connect("rtmp://"+fmsserver+"/videoshare/"); nc.addEventListener(NetStatusEvent.NET_STATUS,onConnect); playbackstatus = "stop"; // Loads the FMES XML command files loadFMESAPIConfig(); progressbar.mode = "manual"; progressbar.label = ""; // Event listener for the fullscreen mode Application.application.stage.addEventListener(FullScreenEvent.FULL_SCREEN, fullScreenHandler); } // Load the video thumbsnails after initial connect to FMS private function onConnect(status:Object) : void { refreshVideos(); } // Calls the Flash Media Server server side function private function refreshVideos() : void { nc.call("getFiles", new Responder(onThumbnailListReturned)); } // Creates an ArrayCollection for the thumbnail display list private function onThumbnailListReturned(thumblist:Array):void{ movieThumbnailList = new ArrayCollection(); movieThumbnailList.source = thumblist; // Initializes the interface and connects to the Flash Media Server }

In the code above shows how init() establishes aconnection to Flash Media Server. A successful connection triggers a call to the onConnect() function, which then calls the refreshVideos() function. The samefunction gets called once the socket connection script determines that the encodingis complete. The refreshVideos() function calls the getFiles() function onFlash Media Server using the existing NetConnection object and then populatesthe movieThumbnailList ArrayCollection once the data returns.

Building the Flex application: user interface

Now the application needs to connect the data source to theuser interface. Along the way, I'll demonstrate how to build a new component todisplay the video thumbnail.

Each time the refreshVideos() function is called, movieThumbnailList is updated with the latest video paths. You could display this data in adatagrid, but in this case you'll create and populate a grid of moviethumbnails.

To show the thumbnails in the grid, you need to add newvisual elements to the interface:

<mx:Tile autoLayout="true" id="videolist"> <mx:Repeater id="rp1" dataProvider="{movieThumbnailList}"> <local:ThumbnailViewer connection="{nc}" moviesource="{rp1.currentItem}" thumbClicked="onThumbClicked(event)" id="thumbnailviewer1"/> </mx:Repeater> </mx:Tile>

The application uses the Tile element of Flex and a new custom component called ThumbnailViewer, which requires the active NetConnection and the video path as arguments. It fires a thumbsClicked event, which then triggers onThumbsClicked(event) to display the selected video in the not-yet-defined video player.

ThumbnailViewer.mxml

<?xml version="1.0" encoding="utf-8"?> <mx:Canvas xmlns:mx="http://www.adobe.com/2006/mxml" width="200" height="175" click="onClick()" creationComplete="buildUI()" xmlns:local="*"> <mx:Metadata> [Event(name="thumbClicked", type="flash.events.Event")] </mx:Metadata> <mx:Script> <![CDATA[ import flash.events.*; import flash.media.Video; import flash.net.NetConnection; import flash.net.NetStream; import flash.display.Sprite; [Bindable] public var moviesource:Object; [Bindable] public var connection:NetConnection; private var stream:NetStream; private var tf:TextField; private var seekedtopreview; // Dispatches the click event private function onClick():void{ dispatchEvent(new Event("thumbClicked")); } // Connects to the main NetConnection public function buildUI() : void { connectStream(); seekedtopreview = false; } // Creates the NetStream and builds to user interface private function connectStream():void { stream = new NetStream(connection); stream.addEventListener(NetStatusEvent.NET_STATUS, netStreamEventHandler); stream.bufferTime = 0; var videofile:String = moviesource.name.split("/streams/_defInst_/")[1]; var video:Video = new Video(); video.attachNetStream(stream); stream.play("mp4:"+videofile); stream.pause(); description.addChild(video); video.width = 180; video.height = 120; tf = new TextField(); tf.text = videofile.split(".f4v")[0]; tf.y = -20 tf.autoSize = "left"; var format:TextFormat = new TextFormat(); format.font = "Verdana"; format.color = 0x000000; tf.setTextFormat(format); description.addChild(tf); } private function netStreamEventHandler(event:NetStatusEvent):void { if(event.info.code=="NetStream.Buffer.Full") { if (!seekedtopreview) { stream.seek(3); seekedtopreview = true; } } } ]]> </mx:Script> <mx:Panel width="200" height="160"> <mx:Label id="description"/> </mx:Panel> </mx:Canvas>

The component has a standard behavior pattern once it initializes. The event handler creationComplete() builds the user interface by connecting a new NetStream object to the existing NetConnection object. You need to attach the NetStream to a new Video object to display the video. It then pauses and once the buffer is filled up, seeks to the third second.

Additionally, it creates a text object to display the name of the video file. The onClick() function is defined to fire an event to the main application to display the actual video.

And there you go–video thumbnails appear below the upload component (see Figure 4). Of course, the main component is missing; you'll add the actual video player next.

The application showing video thumbnails
Figure 4. The application showing video thumbnails

Adding the video player

For simplicity's sake, the application uses the Flex-based VideoDisplay component. The advantage of this is much simpler code, but a drawback is the need for a second connection to the server. If you want, you can create your own custom video player component and share a global NetConnection.

You need to enhance the layout one more time:

<mx:HBox id="mainui" height="330" autoLayout="false"> <mx:Panel width="380" height="306" layout="absolute" scroll="false" id="panel1"> <mx:VideoDisplay id="mainVideo" x="0" y="0" width="360" height="240"/> <mx:Button label="Play" id="controls" click="PlayPause()" y="242" x="3"/> <mx:Button label="Zoom" id="controls0" click="zoom()" y="242" x="299"/> </mx:Panel> <mx:Form id="uploadform" horizontalScrollPolicy="off" verticalScrollPolicy="off"> <mx:FormItem label="Choose your file:" id="formitem"> <mx:TextInput id="videofile" editable="false" x="10" y="358"/> </mx:FormItem> <mx:FormItem id="browsearea" > <mx:Button label="Browse" click="onBrowse()" x="180" y="359" id="browse_btn"/> <!--<mx:Button label="refresh Videos" click="refreshVideos" x="436" y="10" enabled="true"/>--> <mx:Button id="upload_btn" label="Upload" click="onUpload()" width="68" enabled="false" x="256" y="360"/> </mx:FormItem> <mx:Text id="status"/> <mx:ProgressBar id="progressbar" width="277" enabled="true" indeterminate="false"/> </mx:Form> </mx:HBox>

This code adds another panel to the HBox with a VideoDisplay object, a Play/Pause button, and a Zoom button. Additionally a full-screen button will appear once the user switches to the zoom mode. A couple of additional functions are being triggered by the new buttons and need to be defined:

// Zoom out function. Reverses the interface to the regular view. private function zoomOut() : void { currentState = "standard"; } // Toggles between play and pause public function PlayPause() : void { if (playbackstatus == "stop") { controls.label = "Stop"; playbackstatus = "play"; mainVideo.play(); } elseif (playbackstatus == "play") { controls.label = "Play"; playbackstatus = "stop"; mainVideo.pause(); } } // Zoom in function. Rearranges the interface to display the video in full resolution. private function zoom() : void { currentState = "fullscreen"; } // Fullscreen zoom private function goFullScreen() : void { // Turn off scrollbars for fullscreen Application.application.horizontalScrollPolicy="off" Application.application.verticalScrollPolicy="off" // Convert the position of the video display object to global variables for fullscreen retangle positioning var pt:Point = new Point(mainVideo.x,mainVideo.y); var globalpt:Point = contentToGlobal(pt); // Switch to no border scaling mode for appropriate fullscreen scaling Application.application.stage.scaleMode = StageScaleMode.NO_BORDER; // Switch to fullscreen Application.application.stage.fullScreenSourceRect = new Rectangle(globalpt.x,globalpt.y,720,480); Application.application.stage.displayState = StageDisplayState.FULL_SCREEN; } private function fullScreenHandler(evt:FullScreenEvent):void { if (!evt.fullScreen) { // Reset scrollbars and stage scaling once leaving the fullscreen mode Application.application.stage.scaleMode = StageScaleMode.NO_SCALE; Application.application.horizontalScrollPolicy="auto" Application.application.verticalScrollPolicy="auto" } } }

The PlayPause() function toggles the playback. The zoom button switches the Flex application to a different state and resizes the video area. The full screen button displays the video area in full screen mode. Note that the full screen mode requires the allowFullScreen tag to be set to true in the publish template. See the sample source code for more details.

The video starts playing when a thumbnail is clicked:

// Select a video thumbnail public function onThumbClicked(event:Event):void { var thumb:ThumbnailViewer = event.target as ThumbnailViewer; mainVideo.source = "rtmp://" + server + "/mytube/"+thumb.moviesource.name.split("/streams/_defInst_/")[1].split(".flv")[0]; controls.label = "Stop"; playbackstatus = "play"; mainVideo.play(); }

Voilà! Here is your new video player (see Figure 5).

The video player
Figure 5. The video player

To define the regular and the zoom state, the following code at the top of the MXML file repositions the elements accordingly. The video area grows to a 720 × 480 region, the zoom button becomes a zoom out button and the UI has an additional Fullscreen button.

<mx:states> <!-- default view --> <mx:State name="standard"> </mx:State> <!-- fullscreen view --> <mx:State name="fullscreen" > <!-- Resize the video --> <mx:SetProperty target="{mainVideo}" name="width" value="720"/> <mx:SetProperty target="{mainVideo}" name="height" value="480"/> <mx:SetProperty target="{mainVideo}" name="y"/> <mx:SetProperty target="{mainVideo}" name="x"/> <!-- Rearrange the interface--> <mx:RemoveChild target="{mainVideo}"/> <mx:AddChild position="firstChild" target="{mainVideo}"/> <mx:RemoveChild target="{panel1}"/> <mx:RemoveChild target="{mainui}"/> <!-- Add Zoom out button --> <mx:AddChild relativeTo="{videolist}" position="before"> <mx:HBox> <mx:Button label="Zoom Out" click="zoomOut()" id="zoomout_btn"/> <mx:Button label="Fullscreen" click="goFullScreen()" id="fullscreen_btn"/> </mx:HBox> </mx:AddChild> </mx:State> </mx:states> <...existing code...> // Zoom in function. Rearranges the interface to display the video in full resolution. private function zoom() : void { currentState = "fullscreen"; } // Zoom out function. Reverses the interface to the regular view. private function zoomOut() : void { currentState = "standard"; }
The application in full-screen state
Figure 6. The application in full-screen state

Where to go from here

Targeted at intermediate to experienced web developers, this tutorial shows how powerful the combination of Flash Media Encoding Server, Flash Media Server, and Flex can be.

As a next step, you could extend the application with a management component to delete, filter, or rate submitted videos. The opportunities to add more features are almost endless. Flash Media Encoding Server delivers the scalable backbone (you can even use a grid rendering setup) to completely streamline your encoding process (for example, with a workflow to create different bitrates for dynamic streaming) and become the next Web 2.0 video superstar—or at least provide your company an easy way to store, manage, and deliver video on the web.


More Like This