Implementing Pseudo Streaming for H264 in a Flash Player
Implementing Pseudo Streaming for MP4 is similar to FLV, with a few exceptions.
MetaData
Information about the available seekpoints is stored in the metadata in the fields seekpoints[keyframe][“time“] and seekpoints[keyframe][“offset”].
The first time a video is played you store the metadata. When you seek in an MP4 file, the plugin basically sends you a new MP4 file with patched headers. Remember that seeking times are always relative to the original file.
Seeking
You can specify a starting time by adding a query to the URL in the form "?start=" + metadata_.seekpoints[keyframe][“time“]; Calling the play function on the NetStream object starts playback of the video at the specified time.
The Flash Player resets the NetStream.time to zero. When you have a timeline you will see that it jumps back to the beginning. To compensate you have to adjust the NetStream.time by adding the time of your latest seekpoint. See the example code below.
Falling back to FLV
The Flash Player supports playback of H264 since majorVersion 9, revision 60. If you have both an MP4 and FLV version available of the video, it is recommended to check if the player supports H264 playback, and if not then fall back to playing FLV.
Code Snippets
Below some codesnippets on how to implement this.
compensate
How to compensate the timeline:
var net_stream:NetStream = object.net_stream_; var time_fixed:Number = net_stream.time; time_fixed += video_streamer.flv_beginning_; var percentage:Number = 100 * (time_fixed / net_stream.totalTime);
seek
An example seek funcion. Note that you can call seek on the NetStream object when the video is already buffered for that seekpoint. (The funtions get_nearest_keyframe and get_nearest_seekpoint are defined below too).
public function seek(second:Number) { trace("seek: " + second); var cached_seconds = Math.floor( (net_stream_.totalTime - flv_beginning_) * (net_stream_.bytesLoaded / net_stream_.bytesTotal)) - 1; if(second >= flv_beginning_ && second < flv_beginning_ + cached_seconds) { if(Math.abs(net_stream_.time - second) > 5) { net_stream_.seek(second); } } else if(metadata_.keyframes) { var keyframe = get_nearest_keyframe(second, metadata_.keyframes.times); trace("seek: nearest keyframe=" + keyframe); second = metadata_.keyframes.times[keyframe]; if(flv_beginning_ != second) { flv_beginning_ = second; var url = net_connection_.play_url + "?start=" + metadata_.keyframes.filepositions[keyframe]; trace("net_stream_.play(" + url + ")"); net_stream_.play(url); } } else if(metadata_.seekpoints) { var keyframe = get_nearest_seekpoint(second, metadata_.seekpoints); trace("seek: nearest keyframe=" + keyframe); second = metadata_.seekpoints[keyframe]["time"]; if(flv_beginning_ != second) { flv_beginning_ = second; var url = net_connection_.play_url + "?start=" + metadata_.seekpoints[keyframe]["time"]; trace("net_stream_.play(" + url + ")"); net_stream_.play(url); } } }
get_nearest_keyframe
private function get_nearest_keyframe(second:Number, keytimes) { var index1 = 0; var index2 = 0; // Iterate through array to find keyframes before and after scrubber second for(var i = 0; i != keytimes.length; i++) { if(keytimes[i] < second) { index1 = i; } else { index2 = i; break; } } // Calculate nearest keyframe if(second - keytimes[index1] < keytimes[index2] - second) { return index1; } else { return index2; } }
get_nearest_seekpoint
private function get_nearest_seekpoint(second:Number, seekpoints) { var index1 = 0; var index2 = 0; // Iterate through array to find keyframes before and after scrubber second for(var i = 0; i != seekpoints.length; i++) { if(seekpoints[i]["time"] < second) { index1 = i; } else { index2 = i; break; } } // Calculate nearest keyframe if(second - seekpoints[index1]["time"] < seekpoints[index2]["time"] - second) { return index1; } else { return index2; } }
Questions
If you have any questions etc, do not hesitate to ask them the on the blog