ONLamp.com    
 Published on ONLamp.com (http://www.onlamp.com/)
 See this if you're having trouble printing code examples


Creating MyTube with Flex and PHP

by Jack Herrington
05/24/2007

With widespread broadband adoption, the continued shrinking cost of disk space, and the availability of the Adobe® Flash® Player and Flash video, it's no wonder that video sharing on the Internet is insanely popular. Sites like Google video and YouTube got the ball rolling, but now there are niche video sites everywhere. So, how can you get in on the action? How can you use technologies such as PHP, Flash, and Adobe Flex™ to build your own video sharing site? Turns out, it's a lot easier than you think.

This article walks you through building the PHP portion of the site as well as building a movie viewer in Flash using the Flex framework. To build a simple version of YouTube (or a MyTube, as it were), you should have a couple of tools in place.

On the server side, you first need PHP and MySQL. You'll use MySQL to store the data about the movies (for example, the file name for the movie, the thumbnail file, the height and width of the thumbnail, the title, and the description). PHP will do the work of formatting pages, both HTML and XML, depending on how you want to do it.

You also need an open source utility called ffmpeg to do the work of converting the video from whatever format your user uploads into a Flash Video (FLV) file. The ffmpeg utility can also generate a thumbnail snapshot of a frame from the movie for use when you present the list of available movies. Lest there be any doubt, ffmpeg is your new best friend in the world of video sharing. It's an amazing utility that is powerful, easy to use, and well documented.

On the frontend, I show several different user interface approaches. The first uses a hybrid HTML/Flash approach similar to YouTube. The second approach uses a completely Flash-based interface. For both of these approaches, I use the Flex framework to build the Flash applications that view the video and later to list the available videos and provide navigation.

Building the PHP Backend

To start building the backend, you must put together the database schema for MySQL. First, create the database. To do so, you use the mysqladmin command:

mysqladmin create movies

When that's done, load the database with the schema. The schema file is shown in Listing 1.

Listing 1. movies.sql
DROP TABLE IF EXISTS movies;

CREATE TABLE movies (
    movieId INTEGER NOT NULL AUTO_INCREMENT,
    title VARCHAR( 255 ),
    source VARCHAR( 255 ),
    thumb VARCHAR( 255 ),
    width INTEGER,
    height INTEGER,
    PRIMARY KEY( movieId )    
);

You load the schema file into the database like this:

mysql movies < movies.sql

To get data into the database, you need an HTML upload facility that takes the uploaded movies, converts them into Flash video, gets a thumbnail, and adds that to the database.

Building the Upload Page

The HTML page for uploading videos is actually quite simple, as Listing 2 shows.

Listing 2. addmovie.html
<html>
<body>
<form enctype="multipart/form-data" method="post" action="upload.php">
<input type="hidden" name="MAX_FILE_SIZE" value="300000" />
<table>
<tr><td>Title</td><td><input type="text" name="title"></td></tr>
<tr><td>Movie</td><td><input type="file" name="movie"></td></tr>
</table>
<input type="submit" value="Upload" />
</form>
</body>
</html>

The form on this page goes to the upload.php page, which processes the movie, grabs a thumbnail, and adds the data to the database. This PHP script is shown in Listing 3.

Listing 3. upload.php
<html><body>
<?php
require "DB.php";

function converttoflv( $in, $out )
{
  unlink( $out );
  $cmd = "ffmpeg -v 0 -i $in -ar 11025 $out 2>&1";
  $fh = popen( $cmd, "r" );
  while( fgets( $fh ) ) { }
  pclose( $fh );
}

function getthumbnail( $in, $out )
{
  unlink( $out );
  $cmd = "ffmpeg -i $in -pix_fmt rgb24 -vframes 1 -s 300x200 $out 2>&1";
  $fh = popen( $cmd, "r" );
  while( fgets( $fh ) ) { }
  pclose( $fh );
}

function flv_import( $upfile, $fname, $title )
{
  $fname = preg_replace( '/\..*$/', '', basename( $fname ) );
  $flvpath = "$fname.flv";
  $thumbpath = "$fname.gif";

  converttoflv( $upfile, "movies\\$flvpath" );
  getthumbnail( $upfile, "movies\\$thumbpath" );

  $dsn = 'mysql://root@localhost/movies';
  $db =& DB::connect( $dsn );
  if ( PEAR::isError( $db ) ) { die($db->getMessage()); }

  $sth = $db->prepare( 'INSERT INTO movies VALUES ( 0, ?, ?, ?, ?, ? )' );
  $db->execute( $sth, array( $title, $flvpath, $thumbpath, 300, 200 ) );
}

flv_import( $_FILES['movie']['tmp_name'], $_FILES['movie']['name'], $_POST['title'] );
?>
File sucessfully uploaded
</body></html>

The flv_import() function is the heart of the script. It calls out to the converttoflv() and getthumbnail() functions to convert the movie to a Flash video file and create a thumbnail. It then adds a record to the database that references the movie. Both the FLV and thumbnail functions use the ffmpeg command with various command-line options to convert the work with the video.

When I navigate to the addmovie.html page, I see the screenshot shown in Figure 1.

Figure 1. The page for uploading movies
Figure 1. The page for uploading movies

From there, you can click Upload to send the movie to the server for processing.

The upload.php script is pretty rudimentary. To put it into production, you would need to add some error checking. The biggest problem with this script is in its ability to handle large movies. Large movies take a while to convert—way too long to have the user wait for the page to return.

To support large movies (more than 10 seconds of video or so), I recommend simply copying the movie into a batch directory, then informing the user that the movie will appear later on the site. Then, have another script that processes the movies in that directory.

It's worth stepping back here a moment to cover why I'm doing the conversion to Flash video. Sure, I need Flash video to view movies in the Flash Player. But were it not for that, were I to keep everything in its original format, I would need to have code that would show each movie with its own native player as well as allow users to find and install the player that's appropriate to their system. That would be a lot of work and a major pain. The advantage of normalizing all the video to Flash video—and to use a Flash Player written in Flex—is that it runs almost anywhere.

The next step is to build a simple HTML/Flash interface that's similar in form to YouTube.

Building the HTML/Flash Interface

Building a Flash movie that plays a selected movie provided to it through a URL starts with creating a new Flex project in Adobe Flex Builder™ 2. Then, you create a Flex application called simplemovie.mxml. The contents of this file are shown in Listing 4.

Listing 4. simplemovie.mxml
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute">
<mx:VBox backgroundColor="white" width="400" height="335">
  <mx:VideoDisplay width="400" height="300" id="videoPlayer"
    source="{Application.application.parameters.movie}" />
  <mx:HBox width="100%" horizontalAlign="center">
    <mx:Button label="Play" click="videoPlayer.play()" />
  </mx:HBox>
</mx:VBox>
</mx:Application>

This simple Flex application has two active elements: a VideoDisplay element that plays the video and a button labeled Play that users can click to restart the video after it's finished.

The VideoDisplay element takes a source attribute that contains the URL for the FLV video of the movie. In this case, it takes an application parameter provided using the FlashVars attribute on the <object> or <embed> tag within the HTML.

Using Flex Builder, you compile the simplemovie.mxml application into its corresponding simplemovie.swf file and move it from the bin directory into the PHP documents directory. From there, you build the PHP page that will host the movie. This page is shown in Listing 5.

Listing 5. simptest.php
<?php
require "DB.php";

$moviebase = 'http://localhost:8080/movies/';

$dsn = 'mysql://root@localhost/movies';
$db =& DB::connect( $dsn );
if ( PEAR::isError( $db ) ) { die($db->getMessage()); }

$source = null;
$movieId = 1;
if ( array_key_exists( 'movie', $_GET ) )
  $movieId = $_GET['movie'];

$movies = array();
$res = $db->query( 'SELECT movieId, source, title FROM movies' );
while( $row = $res->fetchrow( ) ) {
  $movies []= $row;
  if ( $row[0] == $movieId )
    $source = $row[1];
}

if ( $source == null )
    $source = $movies[0][1];
?>
<html>
<body>
<table>
<tr><td valign="top">
<object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" width="400" 
  height="335"
  codebase="http://fpdownload.macromedia.com/get/flashplayer/current/swflash.cab">
<param name="movie" value="simplemovie.swf" />
<param name="quality" value="high" />
<param name="flashVars" value="movie=<?php echo( $moviebase.$source ) ?>">
<embed src="simplemovie.swf" quality="high"
  width="400" height="335" play="true"
  loop="false"
  quality="high"
  flashVars="movie=<?php echo( $moviebase.$source ) ?>"
  type="application/x-shockwave-flash"
  pluginspage="http://www.adobe.com/go/getflashplayer">
</embed>
</object>
</td><td valign="top">
<?php
foreach( $movies as $movie ) {
?>
<a href="simptest.php?movie=<?php echo( $movie[0] )?>"><?php echo( $movie[2] )?></a><br/>
<?php
}
?>
</td></tr></table>
</body>
</html>

The script starts by connecting to the database and getting the list of movies. While it's doing that, it also looks to see whether any of the movie IDs match the one passed into the URL. If it finds a match, it remembers that as the movie variable that will be passed to the simplemovie.swf file through the flashVars parameter.

The next section starts the HTML and creates the <object> and <embed> tags that host the simplemovie.swf player; it also provides it with the correct URL of the movie. After that, a small list of the available movies with links back to this page are shown. The links have the correct ID for each movie embedded in them.

When I bring this page up in my browser, I see something like Figure 2.

Figure 2. The simple movie player and the list of movies
Figure 2. The simple movie player and the list of movies

The first video starts right when I open the page. As I select each movie from the list on the right, the page reloads and shows the movie I selected.

How sweet and simple was that? One Flex file, one PHP file, and a little database magic for the backend, and viola! Video sharing!

The next step is to see whether you can enhance the user experience a bit by doing more of the work in Flex.

The Flex Interface, Part 1

If you want to provide a mechanism for Flex to show any movie, you must provide the Flex application with the list of movies. The most convenient way to do that is through XML. So, going back to PHP again, you need a page exports the movie list from the database as XML. This movies.php page is shown in Listing 6.

Listing 6. movies.php
<?php
require "DB.php";

$moviebase = 'http://localhost:8080/movies/';

header( 'content-type: text/xml' );

$dsn = 'mysql://root@localhost/movies';
$db =& DB::connect( $dsn );
if ( PEAR::isError( $db ) ) { die($db->getMessage()); }
?>
<movies>
<?php
$res = $db->query( 'SELECT title, source, thumb, width, height FROM movies' );
while( $row = $res->fetchrow( ) ) {
?>
  <movie title="<?php echo( $row[0] ) ?>" source="<?php echo( $moviebase.$row[1] ) ?>" 
   thumb="<?php echo( $moviebase.$row[2] ) ?>" width="<?php echo( $row[3] ) ?>"
   height="<?php echo( $row[4] ) ?>" />
<?php
}
?>
</movies>

You can run that from the command line and see the XML. Or, you can point any modern browser at it and actually see the XML in a nice tree form, as shown in Figure 3.

Figure 3. The XML list of movies
Figure 3. The XML list of movies

With the XML list of movies in hand, it's time to create a Flex application that extends the simplemovie.mxml player with the list of movies. This upgraded Flex application is shown in Listing 7.

Listing 7. mytube1.mxml
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" creationComplete="movieXmlData.send()">

<mx:HTTPService method="get" url="http://localhost:8080/movies.php" id="movieXmlData" result="onGetMovies( event )" />

<mx:Script>
import mx.rpc.events.ResultEvent;
import mx.controls.VideoDisplay;
import mx.controls.List;
import mx.rpc.http.HTTPService;
import mx.collections.ArrayCollection;

[Bindable]
private var movies : ArrayCollection = new ArrayCollection();

public function onGetMovies( event : ResultEvent ) : void
{
  var firstMovie : String = event.result.movies.movie[0].source.toString();
  videoPlayer.source = firstMovie;

  movies = event.result.movies.movie;
  movieList.selectedIndex = 0;
}

public function onPrevious() : void
{
  if ( movieList.selectedIndex == 0 )
    movieList.selectedIndex = movies.length - 1;
  else
    movieList.selectedIndex -= 1;
  videoPlayer.source = this.movieList.selectedItem.source.toString();
}

public function onPlay() : void
{
  videoPlayer.source = this.movieList.selectedItem.source.toString();
  videoPlayer.play();
}

public function onNext() : void
{
  if ( movieList.selectedIndex >= ( movies.length - 1 ) )
    movieList.selectedIndex = 0;
  else
    movieList.selectedIndex += 1;
  videoPlayer.source = this.movieList.selectedItem.source.toString();
}

public function onChange() : void
{
  videoPlayer.source = this.movieList.selectedItem.source.toString();
}
</mx:Script>

<mx:HBox width="100%" paddingLeft="10" paddingTop="10" paddingRight="10">
  <mx:VBox>
    <mx:VideoDisplay width="400" height="300" id="videoPlayer" complete="onNext()" />
    <mx:HBox width="100%" horizontalAlign="center">
       <mx:Button label="<<" click="onPrevious()" />
       <mx:Button label="Play" click="onPlay()" />
       <mx:Button label=">>" click="onNext()" />
    </mx:HBox>
    </mx:VBox>
    <mx:List width="100%" height="340" id="movieList"
      dataProvider="{movies}"
      change="onChange()"
      labelField="title"></mx:List>
</mx:HBox>

</mx:Application>

The big change is the addition of all the ActionScript code at the top of the file. This code manages the interface. It first reads in the XML from the movies.php page using the HTTPService mechanism in the onGetMovies() function. The HTTPService class is smart enough to recognize XML when it sees it and immediately gives back an XML Document Object Model (DOM) that you then use to get the first movie and start it playing. The onGetMovies() method also sets up the local movies variable for the list box to display. The other methods in the ActionScript handle the different events from the interface, the user clicking the list of movies, using the Next or Previous buttons, and so on.

On the bottom of the file are the Flex objects that comprise the user interface. There are a few more buttons—the left and right arrows—that move to the next and previous movies. A list of movies appears on the right side of the video display; in this case, the list just shows the title of the movie.

When I use Flex Builder to compile and run the application, I see what appears in Figure 4 in my browser.

Figure 4. The first version of the Flex user interface
Figure 4. The first version of the Flex user interface

I can use the list on the right to select a movie, or press the left and right buttons to navigate from movie to movie. Now that's pretty nice, but what about the thumbnails you have?

The Flex Interface, Part 2—Now with Thumbnails

To use the thumbnails in the list, you need to change the list so that it displays both the thumbnail and the title of the image. Thankfully, Flex makes this so simple it's almost sinful. Start by altering the <List> tag to add an itemRenderer. This is shown in Listing 8.

Listing 8. mytube2.mxml
...
  <mx:List width="100%" height="340" id="movieList"
    dataProvider="{movies}"
    change="onChange()"
    itemRenderer="MovieItem"></mx:List>
...

That item renderer is an MXML component that you must create called MovieItem. You create that by first selecting the New > MXML Component, then putting the component into the code from Listing 9.

Listing 9. MovieItem.mxml
<?xml version="1.0" encoding="utf-8"?>
<mx:Canvas xmlns:mx="http://www.adobe.com/2006/mxml" height="80">
  <mx:Image source="{data.thumb}" width="{data.width/3}"
     height="{data.height/3}" rotation="5" left="10" top="0" />
   <mx:Label text="{data.title}" fontWeight="bold" top="10" left="100" fontSize="18" />
</mx:Canvas>

I used a Canvas container so that I can position the elements around myself. You can use whatever container makes the most sense for your layout. Then, use an <mx:Image> tag for the image and an <mx:label> tag for the title. To make it a bit more interesting, rotate the image slightly. The end effect is shown in Figure 5.

Figure 5. The upgraded list box with the thumbnails
Figure 5. The upgraded list box with the thumbnails

Okay, it's not the most amazing thing in the world, but it's cooler than just a text list. And you can make the component as complex as you like by adding descriptions, running times, links, rating buttons, or whatever else you like.

Storage and Bandwidth

While putting together the database and the frontend is relatively easy, it's not the only problem that you face when setting up a video sharing site. The primary, ongoing problem is bandwidth. Movies, even with the tight encoding of Flash video, are still rather large files. Figuring out how to serve up video without breaking the bank on bandwidth charges can be interesting.

Certainly, one bandwidth solution is to buy a bigger connection or get into a hosted data center with a fat pipe to the Internet. Another option is to have your sharing site hold references to the data but store and host the video files somewhere else. Along that line, Amazon's S3 service provides an easy way to store and share large files in a redundant and scalable way while paying fairly little money for the service. Using S3 to host the video in the early phases of the site can keep you from paying a lot of infrastructure costs early on, holding off on those expenses until the site is popular enough to pay for its own infrastructure.

Conclusion

With the introduction of Flash video and the ubiquity of broadband access, it's now conceivable to run a video sharing site on a budget. Hopefully, this example shows you how easy it is to get a Flex/PHP video sharing solution together and will motivate you to take it even further.

This article was produced with funding provided by Adobe Corporation

Jack Herrington is an engineer, author and presenter who lives and works in the Bay Area. His mission is to expose his fellow engineers to new technologies. That covers a broad spectrum, from demonstrating programs that write other programs in the book Code Generation in Action. Providing techniques for building customer centered web sites in PHP Hacks. All the way writing a how-to on audio blogging called Podcasting Hacks.


Return to ONJava.com.

Copyright © 2009 O'Reilly Media, Inc.