You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@flex.apache.org by "Gaius Coffey (JIRA)" <ji...@apache.org> on 2013/02/21 12:46:14 UTC

[jira] [Created] (FLEX-33410) NetStream jumps when playing on Android devices

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