Uploaded image for project: 'Apache Flex'
  1. Apache Flex
  2. FLEX-33410

NetStream jumps when playing on Android devices

    XMLWordPrintableJSON

    Details

    • Type: Bug
    • Status: Closed
    • Priority: Blocker
    • Resolution: Duplicate
    • Affects Version/s: None
    • Fix Version/s: None
    • Component/s: Spark: VideoPlayer
    • Labels:
    • Environment:
      Android mobile

      Description

      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
        Unknown macro: { var w}

        // 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
        Unknown macro: { if(isError) { errorLogs.unshift("ERROR "+s); } logs.unshift(s); logs.length = NUM_ERROR_RECORDS; }

        }
        }

        Attachments

          Activity

            People

            • Assignee:
              Unassigned
              Reporter:
              gaiuscoffey Gaius Coffey
            • Votes:
              0 Vote for this issue
              Watchers:
              1 Start watching this issue

              Dates

              • Created:
                Updated:
                Resolved: