Gaius Coffey created FLEX-33410:
-----------------------------------

             Summary: NetStream jumps when playing on Android devices
                 Key: FLEX-33410
                 URL: https://issues.apache.org/jira/browse/FLEX-33410
             Project: Apache Flex
          Issue Type: Bug
          Components: Spark: VideoPlayer
         Environment: Android mobile
            Reporter: Gaius Coffey
            Priority: Blocker


When playing videos using OSMF on Android devices, NetStream will skip the 
final few seconds. This makes it impossible to use video commercially.

Steps to reproduce:
1. Create a Mobile project similar to the below
2. Run it on an Android device and watch the traces as the video gets to the end

Replicated on all of Motorola Xoom, Nexus 7, HTC Desire...

package
{
        import flash.display.Sprite;
        import flash.display.StageAlign;
        import flash.display.StageScaleMode;
        import flash.events.Event;
        import flash.events.NetDataEvent;
        import flash.events.NetStatusEvent;
        import flash.net.NetStream;
        import flash.text.TextField;
        import flash.utils.Dictionary;
        import flash.utils.getTimer;
        
        import org.osmf.elements.ProxyElement;
        import org.osmf.elements.SerialElement;
        import org.osmf.elements.VideoElement;
        import org.osmf.events.MediaErrorEvent;
        import org.osmf.events.MediaPlayerStateChangeEvent;
        import org.osmf.media.MediaElement;
        import org.osmf.media.MediaPlayerSprite;
        import org.osmf.media.URLResource;
        import org.osmf.net.NetStreamLoadTrait;
        import org.osmf.net.NetStreamSwitchManagerBase;
        import org.osmf.traits.MediaTraitType;
        import org.osmf.traits.TimeTrait;
        [SWF(backgroundColor="#272727")]
        public class DebugAdvert extends Sprite
        {
                public function DebugAdvert()
                {
                        super();
                        
                        // support autoOrients
                        stage.align = StageAlign.TOP_LEFT;
                        stage.scaleMode = StageScaleMode.NO_SCALE;
                        stage.addEventListener(Event.RESIZE,doResize);
                
                        // Textfield for logging
                        tf = new TextField;
                        tf.backgroundColor = 0xffffff;
                        tf.multiline = true;
                        tf.background = true;
                        addChild(tf);
                        
                        // MediaPlayerSprite for video display
                        mp = new MediaPlayerSprite();
                        addChild(mp);
                        
                        // Position everything for the first time.
                        doResize(null);
                        
                        // Add OEF listener to update traces
                        addEventListener(Event.ENTER_FRAME,everyFrame);
                        
                        // Start the process going by adding a video
                        var ur:URLResource = new 
URLResource(TEST_VIDEO_CONTENT);
                        var me:MediaElement = 
mp.mediaFactory.createMediaElement(ur);
                        mp.media = me;                  
                }
                public const TEST_VIDEO_CONTENT:String = 
"videos/TOYOTA_YARIS_MORE_VER2_30_TV.mp4";
                /**
                 * The threshold for a step between frames that will be logged 
as an error.
                 * This should ideally be the same as frame interval, but will
                 * vary with device performance. However, even allowing for 
that,
                 * it should NEVER be as high as half a second, let alone the 
                 * three or more seconds seen on many Android devices.
                 */
                public const ERROR_THRESHOLD:Number = 0.5;
                /**
                 * Number of log records to maintain for display in the 
textfield
                 */
                public const NUM_ERROR_RECORDS:uint = 10;
                /**
                 * On resize, keep the textfield visible and media player sized.
                 */
                protected function doResize(event:Event):void {
                        var w:Number = 
Math.min(stage.fullScreenWidth,stage.fullScreenHeight);
                        var h:Number = w;
                        if(stage.width>stage.height) {
                                mp.width = w;
                                mp.height = h;
                                tf.x = w;
                                tf.y = 0;

                        } else {
                                mp.width = w;
                                mp.height = h;
                                tf.x = 0;
                                tf.y = h;
                        }
                        tf.height = stage.fullScreenHeight-tf.y;
                        tf.width = stage.fullScreenWidth-tf.x;
                }
                // Ref to media player
                protected var mp:MediaPlayerSprite;
                // Ref to tf for logging.
                protected var tf:TextField;
                /**
                 * Ensure that, whatever the media we are playing, we can find 
                 * the relevant VideoElement to locate NetStream
                 */
                protected function 
recurseForProxies(element:MediaElement):MediaElement {
//                      if(element is RTEParallelAdTagElement) return (element 
as RTEParallelAdTagElement).displayElement;
                        if(element is ProxyElement) return 
recurseForProxies((element as ProxyElement).proxiedElement);
                        if(element is SerialElement) return 
recurseForProxies((element as SerialElement).currentChild);
                        return element;
                }
                /**
                 * On every frame, compare position to previous and log any 
jumps that are
                 * exceptional.
                 */
                protected function everyFrame(event:Event):void {
                        // Find underlying media element. Return if empty.
                        var proxiedElement:MediaElement = 
recurseForProxies(mp.media);
                        if(!proxiedElement) return;
                        // Check it has a time trait. Return if not.
                        var tt:TimeTrait = 
proxiedElement.getTrait(MediaTraitType.TIME) as TimeTrait;
                        if(!tt) return;
                        // Return if tt is not yet inited.
                        if(isNaN(tt.duration)) return;
                        // Store current times for reference.
                        var newTimes:Object = 
{currentTime:tt.currentTime,duration:tt.duration,percents:0};
                        // Show trace only if there is a previous record to 
compare against.
                        var showTrace:Boolean = false;
                        // If last times is not set, then set it to current 
record and initialise for first trace of this media
                        if(!lastTimes) {
                                showTrace = true;
                                lastTimes = newTimes;
                        }
                        // If time stamps are different to previous, then we 
have a change, so ensure showTrace is true
                        if(tt.currentTime!=lastTimes.currentTime || 
tt.duration!=lastTimes.duration) showTrace = true;
                        // If show trace, then generate a log record
                        if(showTrace) {
                                // Gap between current and previous trait values
                                var tGap:Number = 
tt.currentTime-lastTimes.currentTime;
                                var dGap:Number = 
tt.duration-lastTimes.duration;
                                
                                var remaining:Number = 
tt.duration-tt.currentTime;
                                var stepTime:Number = 
tt.currentTime-lastTimes.currentTime;
                                var gap:Number = 0;
                                var nstime:Number = -1;
                                if(proxiedElement is VideoElement) {
                                        var ve:VideoElement = proxiedElement as 
VideoElement;
                                        var nslt:NetStreamLoadTrait = 
ve.getTrait(MediaTraitType.LOAD) as NetStreamLoadTrait;
                                        var ns:NetStream = nslt ? 
nslt.netStream : null;
                                        if(ns) {
                                                if(!listenedNs[ns]) {
                                                        // Clear database and 
add listeners if found
                                                        for each(var 
nss:NetStream in listenedNs) {
                                                                
nss.removeEventListener(NetStatusEvent.NET_STATUS,traceNetStatus);
                                                                
nss.removeEventListener(NetDataEvent.MEDIA_TYPE_DATA,traceNetData);
                                                                delete 
listenedNs[nss];
                                                        }
                                                        
ns.addEventListener(NetStatusEvent.NET_STATUS,traceNetStatus,false,1,false);
                                                        
ns.addEventListener(NetDataEvent.MEDIA_TYPE_DATA,traceNetData);
                                                        
                                                        listenedNs[ns] = ns;
                                                }
                                                nstime = ns.time;
                                        }
                                }
                                // Check for an error that we need to report.
                                var errorStr:String = "";
                                if(stepTime>ERROR_THRESHOLD) errorStr = 
"\n[ERROR NETSTREAM HAS JUMPED BY "+stepTime+" SECONDS FOR NO ADEQUATELY 
EXPLAINED REASON.]";
                                // Build the log record
                                var log:String = "[trait 
d:\t"+tt.duration.toFixed(3)+"\tt:\t"+tt.currentTime.toFixed(3)+"\t]\t[NetStream
 
t:\t"+nstime.toFixed(3)+"\t]\t[Step:\t"+stepTime.toFixed(3)+"\tremaining:\t"+remaining.toFixed(3)+"]";
                                
if(previousNetStatus||previousNetData||errorStr) log += 
"\n"+previousNetStatus+"\t"+previousNetData+errorStr;
                                // Log it and update text field
                                registerLog(log,errorStr!="");
                                updateTf();
                                previousNetData = previousNetStatus = "";
                                // Store current times for comparison later.
                                lastTimes = newTimes;
                        }
                }
                /**
                 * Listens for NetDataEvent and adds a trace value when it 
occurs.
                 */
                protected function traceNetData(event:NetDataEvent):void {
                        var ns:NetStream = event.target as NetStream;
                        var addStr:String = event.info.handler+" @ 
"+getTimer()+" ms";
                        addStr += JSON.stringify(event.info.args);
                        if(previousNetData) previousNetData += " | ";
                        previousNetData += addStr;
                }
                protected var previousNetData:String = "";
                /**
                 * Listens for NetStatusEvent and adds a trace value when it 
occurs.
                 */
                protected function traceNetStatus(event:NetStatusEvent):void {
                        var addStr:String = event.info.code+" @ "+getTimer()+" 
ms";
                        if(previousNetStatus) previousNetStatus = 
previousNetStatus+","+addStr;
                        else previousNetStatus = addStr;
                }
                protected var previousNetStatus:String = "";
                /**
                 * List of NetStreams that we are monitoring.
                 */
                protected var listenedNs:Dictionary = new Dictionary;
                /**
                 * Previous time stamps for comparison.
                 */
                protected var lastTimes:Object;
                /**
                 * Display log records with errors at the top.
                 */
                protected function updateTf():void {
                        trace("latestResultsStr: "+logs[0]);
                        tf.text = 
errorLogs.join("\n")+"\n---\n"+logs.join("\n");
                }
                /**
                 * List of last NUM_ERROR_RECORDS records.
                 */
                protected var logs:Vector.<String> = new Vector.<String>;
                /**
                 * List of error records.
                 */
                protected var errorLogs:Vector.<String> = new Vector.<String>;
                protected function registerLog(s:String,isError:Boolean):void {
                        if(isError) {
                                errorLogs.unshift("ERROR "+s);
                        }
                        logs.unshift(s);
                        logs.length = NUM_ERROR_RECORDS;
                }
        }
}

--
This message is automatically generated by JIRA.
If you think it was sent incorrectly, please contact your JIRA administrators
For more information on JIRA, see: http://www.atlassian.com/software/jira

Reply via email to