Like most video nerds I’ve been working with MPEG-4 AVC (h.264) in Flash Player, but the release of Flash Player 10 has introduced some challenges. For example, there’s a Flash Player bug which causes Internet Explorer 6 / 7 to crash when seeking between the first two indexed keyframes in an MPEG-4 AVC encoded file (see the submitted bug on Jira). That’s a pretty nasty bug and something which wasn’t happening in Flash Player 9.0.115, but like all things there’s a silver lining. I started looking at how I might be able to write my NetStream seeking algorithm so that it eliminated, or at least mitigated the crashing.
Starting with Flash Player 9.0.115, the metadata callback that NetStream fires contains an array of seekpoint objects listing valid seekpoints and their byte offsets (a seekpoint object looks like this: { time: 0.00, offset: 36 }). This information is useful for a number of reasons, but in this case I was looking at improving seeking efficiency. A NetStream.seek operation is a relatively intense operation, so the idea was that instead of just handing NetStream a time (based on the position of a dragged progress slider) I’d find a valid time in the seekpoints array at or before the requested time. And this my friends led me to something called binary search–a fast way to find a value within a sorted array of values.
Binary search is grade school stuff for legit computer scientists and lots of languages have some form of binary search built into them. However, I’m just a Flash hacker with a poly-sci degree and ActionScript doesn’t have a binary search method built into it so I had to wrap my little melon around the concept and come up with a solution. Here it is in all its gory glory–a binary search class for finding a seekpoint or the upper and lower bounds between which a requested seek falls. Feel free to use, lose or improve.
/** * Copyright 2007-2008 (c) , Brooks Andrus * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. * */ package com.brooksandrus.utils { public class BinarySearch { private var _upperBounds:int; private var _lowerBounds:int; public function BinarySearch() { } /** * Recursively scans through an array of seekpoints to find a requested time. * If the value is found within the array of seekpoints the index at which * the value is found is returned. If an exact match is not found, -1 is returned * and the the upper and lower indices between which the value falls are set * (so you can determine the closest indice, or seek to to the nearest indexed * time behind or forward in the array of seekpoints. * * Seekpoints array must be sorted. * * @param value the requested seek time to find * @param range the array of seekpoints collected from an MPEG-4 AVC NetStream metadata event * @param the low index within the range we'd like to scan * @param the high index within the range we'd like to scan * */ public function search( value:Number, range:Array, low:int, high:int ):int { if ( high == low ) { if ( range[high].time == value ) { _lowerBounds = low; return value } else { _lowerBounds = low - 1; return -1; } } var median:int = ( low + high ) / 2; if ( range[median].time > value ) { _upperBounds = median; return search( value, range, low, median ); } else if ( range[median].time < value ) { _lowerBounds = median + 1; return search( value, range, median + 1, high ); } else { _upperBounds = median; _lowerBounds = median; return median; } } public function get lowerBounds():int { return _lowerBounds; } public function get upperBounds():int { return _upperBounds; } } }
Oh, and in case you’re wondering, it did help mitigate the IE browser crash a great deal.
*Update*
I had provided some before and after links which illustrated the difference in IE crashing. In hindsight this probably wasn’t the smartest move so I’ve removed the links. If you really want to watch IE go down in flames hit the Jira link above.
nice work,
though I don’t get why use recursive (which is known to be expensive in flash) instead of just looping through the Array.
by the way, in Kaltura we created a similar implementation for enhancing the seek experience for progressive download streams in the flv. Seeking was wired if you seek between keyframes where you’ll wait for the “not always working” invalid seek time. When you seek exactly to a kayframe the response is better and the event shows the right time.
@ Zohar – I felt like the recursive method was a bit more readable and there wasn’t any noticeable performance hit. That said its a piece of cake to implement binary search via a loop rather than recursively. Here’s an example:
:) maybe a personal preference, but it looks much cleaner.
running recursive calls is more heavy due to the need of keeping the stack and calling the function. I don’t if it’s still apply, in earlier versions of flash the recursion was also limited (though for binary search you shouldn’t reach this limit anyway).
Your -1 return value for an exact match bugs me. Just return the greatest index whose value doesn’t exceed “value”. If you really want to check for an exact match, the caller can do that easily.
Here’s my implementation:
[...] I have many video projects where NetStream breaks, and I need to fix it. Â Additionally, I have many cool video scrubbing, pausing, and DVD like controls I’d like to [...]