Index: /trunk/ova.jwplayer.4x/src/org/openvideoads/plugin/jwplayer/streamer/playlist/JWPlaylistItem.as
===================================================================
--- /trunk/ova.jwplayer.4x/src/org/openvideoads/plugin/jwplayer/streamer/playlist/JWPlaylistItem.as	(revision 9)
+++ /trunk/ova.jwplayer.4x/src/org/openvideoads/plugin/jwplayer/streamer/playlist/JWPlaylistItem.as	(revision 9)
@@ -0,0 +1,135 @@
+/*    
+ *    Copyright (c) 2009 Open Video Ads - Option 3 Ventures Limited
+ *
+ *    This file is part of the Open Video Ads JW Player Open Ad Streamer.
+ *
+ *    The Open Ad Streamer is free software: you can redistribute it 
+ *    and/or modify it under the terms of the GNU General Public License 
+ *    as published by the Free Software Foundation, either version 3 of 
+ *    the License, or (at your option) any later version.
+ *
+ *    The Open Ad Streamer is distributed in the hope that it will be 
+ *    useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *    GNU General Public License for more details.
+ *
+ *    You should have received a copy of the GNU General Public License
+ *    along with the framework.  If not, see <http://www.gnu.org/licenses/>.
+ */
+package org.openvideoads.plugin.jwplayer.streamer.playlist {
+	import org.openvideoads.vast.playlist.DefaultPlaylistItem;
+	import org.openvideoads.util.FileUtils;
+	
+	/**
+	 * @author Paul Schulz
+	 */
+	public class JWPlaylistItem extends DefaultPlaylistItem {
+
+		public override function toString(retainPrefix:Boolean = false):String {
+			return "JW4 PlaylistItem " + super.toString(retainPrefix);
+		}
+
+		public function toJWPlaylistItemObject(retainPrefix:Boolean=true):Object {
+			var newItem:Object = new Object();
+			var today:Date = new Date();	
+			if(isAd()) {
+				if(isRTMP()) {
+					newItem.file = getFilename(false);
+					newItem.type = "rtmp";
+					newItem.provider = "rtmp";			
+					newItem.streamer = getStreamer();
+				}	
+				else {
+					if(FileUtils.isImage(url) || FileUtils.isSWF(url)) {
+						newItem.file = url;
+						newItem.type = "image";						
+					}
+					else { 
+					    // it's a stream
+						newItem.file = url;
+						newItem.type = provider;
+						newItem.provider = provider;
+						newItem.streamer = provider;
+					}
+				}	
+	   		    if((getStartTimeAsSeconds() + getDurationAsSeconds()) > 0) {
+					newItem.duration = getDurationAsSeconds();
+   			    }
+				if(hasPreviewImage()) {
+					newItem.image = getPreviewImage();
+				}
+				newItem.start = getStartTimeAsSeconds();
+//				newItem.sequenceIndex = _stream.originatingStreamIndex;
+				newItem.title = title;
+				newItem.description = "Open Video Ads served linear ad";
+				newItem.author = "OVA";
+				newItem.date = today.toTimeString();
+				newItem.link = link;
+				newItem.mediaid = guid;
+				newItem['ova.hidden'] = true;				
+				/* We are not currently setting these properties on ads
+				newItem.addLevel();
+				newItem.tags = 
+				*/
+				//doLog("file: " + newItem.file + ", type: " + newItem.type + ", provider: " + newItem.provider + ", streamer: " + newItem.streamer + ", start: " + newItem.start + ", duration: " + newItem.duration);
+				return newItem;
+			}
+			else {
+				// ok, it's not an ad but a standard JW stream so return the original JW playlist item
+				if(_stream != null) {
+					newItem.title = "No title";
+					newItem.author = "No author";
+					newItem.description = "No description";
+//					newItem.sequenceIndex = _stream.originatingStreamIndex;
+					if(_stream.hasCustomProperties()) {
+						if(_stream.hasCustomProperty("originalPlaylistItem")) {
+							var originalClip:Object = _stream.customProperties.originalPlaylistItem;
+							if(originalClip != null) {
+								originalClip.start = getStartTimeAsSeconds();
+//								originalClip.sequenceIndex = _stream.originatingStreamIndex;
+							}
+							return originalClip;					
+						}
+
+						if(_stream.hasCustomProperty("title")) {
+							newItem.title = _stream.customProperties.title;
+						}
+						if(_stream.hasCustomProperty("author")) {
+							newItem.author = _stream.customProperties.author;				
+						}
+						if(_stream.hasCustomProperty("description")) {
+							newItem.description = _stream.customProperties.description;							
+						}
+						if(_stream.hasCustomProperty("link")) {
+							newItem.link = _stream.customProperties.link;
+						}
+						if(_stream.hasCustomProperty("tags")) {
+							newItem.tags = _stream.customProperties.tags;							
+						}
+					}
+
+					// it's an internal OVA stream so create the JW playlist object manually
+					if(isRTMP()) {
+						newItem.file = getFilename(retainPrefix);
+						newItem.type = "rtmp";
+						newItem.provider = "rtmp";			
+						newItem.streamer = getStreamer();
+					}	
+					else {
+						newItem.file = url;
+						newItem.type = provider;
+						newItem.provider = provider;
+//						newItem.streamer = provider;
+					}
+					newItem.start = getStartTimeAsSeconds();
+		   		    if((getStartTimeAsSeconds() + getDurationAsSeconds()) > 0) {
+						newItem.duration = getDurationAsSeconds();
+	   			    }
+					newItem.date = today.toTimeString();				
+					return newItem;
+				}
+			}
+			return null;
+		}
+	}
+}
Index: /trunk/ova.jwplayer.4x/src/org/openvideoads/plugin/jwplayer/streamer/playlist/JWPlaylist.as
===================================================================
--- /trunk/ova.jwplayer.4x/src/org/openvideoads/plugin/jwplayer/streamer/playlist/JWPlaylist.as	(revision 9)
+++ /trunk/ova.jwplayer.4x/src/org/openvideoads/plugin/jwplayer/streamer/playlist/JWPlaylist.as	(revision 9)
@@ -0,0 +1,55 @@
+/*    
+ *    Copyright (c) 2009 Open Video Ads - Option 3 Ventures Limited
+ *
+ *    This file is part of the Open Video Ads JW Player Open Ad Streamer.
+ *
+ *    The Open Ad Streamer is free software: you can redistribute it 
+ *    and/or modify it under the terms of the GNU General Public License 
+ *    as published by the Free Software Foundation, either version 3 of 
+ *    the License, or (at your option) any later version.
+ *
+ *    The Open Ad Streamer is distributed in the hope that it will be 
+ *    useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *    GNU General Public License for more details.
+ *
+ *    You should have received a copy of the GNU General Public License
+ *    along with the framework.  If not, see <http://www.gnu.org/licenses/>.
+ */
+package org.openvideoads.plugin.jwplayer.streamer.playlist {
+    import org.openvideoads.vast.config.groupings.ProvidersConfigGroup;
+    import org.openvideoads.vast.playlist.DefaultPlaylist;
+    import org.openvideoads.vast.playlist.PlaylistItem;
+    import org.openvideoads.vast.schedule.StreamSequence;
+	
+	/**
+	 * @author Paul Schulz
+	 */
+	public class JWPlaylist extends DefaultPlaylist {		
+
+		public function JWPlaylist(streamSequence:StreamSequence=null, showProviders:ProvidersConfigGroup=null, adProviders:ProvidersConfigGroup=null) {
+			super(streamSequence, showProviders, adProviders);
+		}
+
+		public override function newPlaylistItem():PlaylistItem {
+			return new JWPlaylistItem();
+		}		
+		
+		
+		public function toJWPlaylistItemArray(retainPrefix:Boolean=true):Array {
+			var result:Array = new Array();
+			for(var i:int=0; i < _playlist.length; i++) {
+				result.push(_playlist[i].toJWPlaylistItemObject(retainPrefix));
+			}
+			return result;
+		}
+		
+		public override function toString(retainPrefix:Boolean = false):String {
+			var content:String = new String();
+			for(var i:int=0; i < _playlist.length; i++) {
+				content += _playlist[i].toString(retainPrefix) + ((i < _playlist.length) ? ", " : "");
+			}
+			return content;
+		}
+	}
+}
Index: /trunk/ova.jwplayer.4x/src/org/openvideoads/plugin/jwplayer/streamer/OpenAdStreamer.as
===================================================================
--- /trunk/ova.jwplayer.4x/src/org/openvideoads/plugin/jwplayer/streamer/OpenAdStreamer.as	(revision 9)
+++ /trunk/ova.jwplayer.4x/src/org/openvideoads/plugin/jwplayer/streamer/OpenAdStreamer.as	(revision 9)
@@ -0,0 +1,622 @@
+/*    
+ *    Copyright (c) 2009 Open Video Ads - Option 3 Ventures Limited
+ *
+ *    This file is part of the Open Video Ads JW Player Open Ad Streamer.
+ *
+ *    The Open Ad Streamer is free software: you can redistribute it 
+ *    and/or modify it under the terms of the GNU General Public License 
+ *    as published by the Free Software Foundation, either version 3 of 
+ *    the License, or (at your option) any later version.
+ *
+ *    The Open Ad Streamer is distributed in the hope that it will be 
+ *    useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *    GNU General Public License for more details.
+ *
+ *    You should have received a copy of the GNU General Public License
+ *    along with the framework.  If not, see <http://www.gnu.org/licenses/>.
+ */
+package org.openvideoads.plugin.jwplayer.streamer {
+	import com.jeroenwijering.events.*;
+	
+	import flash.display.MovieClip;
+	import flash.external.ExternalInterface;
+	
+	import json.*;
+	
+	import org.openvideoads.base.Debuggable;
+	import org.openvideoads.plugin.jwplayer.streamer.playlist.*;
+	import org.openvideoads.regions.config.StageDimensions;
+	import org.openvideoads.util.DisplayProperties;
+	import org.openvideoads.util.StringUtils;
+	import org.openvideoads.util.Timestamp;
+	import org.openvideoads.vast.VASTController;
+	import org.openvideoads.vast.events.AdNoticeDisplayEvent;
+	import org.openvideoads.vast.events.CompanionAdDisplayEvent;
+	import org.openvideoads.vast.events.LinearAdDisplayEvent;
+	import org.openvideoads.vast.events.OverlayAdDisplayEvent;
+	import org.openvideoads.vast.events.SeekerBarEvent;
+	import org.openvideoads.vast.events.TemplateEvent;
+	import org.openvideoads.vast.events.TrackingPointEvent;
+	import org.openvideoads.vast.model.CompanionAd;
+	import org.openvideoads.vast.schedule.Stream;
+	import org.openvideoads.vast.schedule.StreamSequence;
+	import org.openvideoads.vast.schedule.ads.AdSlot;
+	import org.openvideoads.vast.tracking.TimeEvent;
+	    
+	public class OpenAdStreamer extends MovieClip implements PluginInterface {
+		protected var _rawConfig:Object;
+        protected var _activeStreamIndex:Number = 0;
+        protected var _vastController:VASTController;
+        protected var _playlist:JWPlaylist;
+        protected var _resumingMainStreamPlayback:Boolean = false;
+        protected var _timeOverlayLinearVideoAdTriggered:int = -1;
+        protected var _activeOverlayAdSlotKey:int = -1;
+        protected var _lastTimeTick:Number = 0;
+        protected var _lastTickActiveIndex:Number = -1;
+        protected var _previousDivContent:Array = new Array();
+	    protected var _view:AbstractView;
+	    protected var _originalWidth:Number = -1;
+	    protected var _originalHeight:Number = -1;
+	    protected var _forcedStop:Boolean = false;
+	    protected var _needToDoStartPreProcessing:Boolean = true;
+
+		public var config:Object = {
+			title: "JWPlayer 4.x OVA Open Ad Streamer build 0.4.3.95",
+			json: null
+		};
+                
+		public function OpenAdStreamer():void {
+			doLog(config.title, Debuggable.DEBUG_ALL);
+		}
+
+		protected function loadExistingPlaylist(rawConfig:Object):void {
+			if(_view.playlist == null) return;
+			if(_view.playlist.length > 0) {
+				doLog("Importing pre-defined playlist - number of show clips is " + _view.playlist.length);
+				var newStreams:Array = new Array();
+				for(var i:int=0; i < _view.playlist.length; i++) {
+					var newShow:Object = new Object();
+					newShow = { 
+						"file": _view.playlist[i].file,
+						"duration": _view.playlist[i].duration,
+						"startTime": Timestamp.secondsToTimestamp(_view.playlist[i].start),
+						"provider": _view.playlist[i].provider,
+						"customProperties": {
+							"originalPlaylistItem": _view.playlist[i]
+						}
+					}					
+					newStreams.push(newShow);
+					doLog("+ Imported clip: " + _view.playlist[i].file);
+				}
+				rawConfig.shows = { "streams" : newStreams };				
+			}
+			else doLog("No pre-defined playlist clips to import before scheduling");
+		}
+
+		public function initializePlugin(view:AbstractView):void {	
+			doLog("Initialising plugin...", Debuggable.DEBUG_ALL);
+			_view = view;
+
+			_view.addControllerListener(ControllerEvent.RESIZE, onResizeEvent);
+
+			// Load up the config and configure the debugger
+			_vastController = new VASTController();
+			_vastController.startStreamSafetyMargin = 500;			
+			_vastController.endStreamSafetyMargin = 500;			
+			doLog("Raw config loaded as " + config.json, Debuggable.DEBUG_CONFIG);
+			_rawConfig = JParser.decode(config.json);
+   			            
+			if(_rawConfig.blockUntilOriginalPlaylistLoaded) {
+				if(_view.playlist == null) {
+					doLog("Blocking OVA plugin initialisation until the original JW flashvar specified playlist is loaded...");
+					_view.addControllerListener(ControllerEvent.PLAYLIST, onOriginalPlaylistLoad);
+				}
+				else continuePluginInitialisation();
+			}
+			else continuePluginInitialisation();
+		}
+
+		private function onOriginalPlaylistLoad(evt:ControllerEvent):void {
+			doLog("Original playlist loaded - " + ((_view.playlist != null) ? _view.playlist.length : 0) + " items loaded");
+			_view.removeControllerListener(ControllerEvent.PLAYLIST, onOriginalPlaylistLoad);
+			continuePluginInitialisation();
+		} 
+		
+		protected function continuePluginInitialisation():void {			
+			doLog("Continuing with OVA plugin initialisation...");
+			loadExistingPlaylist(_rawConfig);			
+			_vastController.initialise(_rawConfig);
+			if(!_vastController.hasStageDimensions()) {
+				_vastController.stageDimensions = new StageDimensions(5, 5);
+			}
+			if(!_vastController.config.hasProviders()) {
+				doLog("Missing providers in the user config - automatically setting the defaults where missing to http(http) and rtmp(rtmp)", Debuggable.DEBUG_CONFIG);
+				_vastController.config.setMissingProviders("video", "rtmp");
+			}
+
+			// Config the player to autostart based on the config setting for "autoPlay"
+			if(_vastController.autoPlay()) {
+				_forcedStop = true; // stops the VAST "stop" event firing incorrectly when autoStart is set
+				_view.config['autostart'] = _vastController.autoPlay();
+			}
+			
+            // Setup the playlist tracking events
+			_view.addControllerListener(ControllerEvent.ITEM, playlistSelectionHandler);			
+			_view.addModelListener(ModelEvent.STATE, streamStateHandler);
+			_view.addModelListener(ModelEvent.TIME, timeHandler);
+			_view.addModelListener(ModelEvent.META, onMetaData);
+
+			// Setup the player tracking events
+			_view.addControllerListener(ControllerEvent.STOP, onStopEvent);		
+			_view.addControllerListener(ControllerEvent.MUTE, onMuteEvent);	
+			_view.addControllerListener(ControllerEvent.VOLUME, onVolumeEvent);
+			_view.addControllerListener(ControllerEvent.PLAY, onPauseResumeEvent);
+            
+            // Setup the critical listeners for the template loading process
+            _vastController.addEventListener(TemplateEvent.LOADED, onTemplateLoaded);
+            _vastController.addEventListener(TemplateEvent.LOAD_FAILED, onTemplateLoadError);
+          
+           // Setup the companion display listeners
+            _vastController.addEventListener(CompanionAdDisplayEvent.DISPLAY, onDisplayCompanionAd);
+            _vastController.addEventListener(CompanionAdDisplayEvent.HIDE, onHideCompanionAd);
+
+            // Decide how to handle overlay displays - if through the framework, turn it on, otherwise register the event callbacks
+            _vastController.enableNonLinearAdDisplay(new DisplayProperties(this, _view.config["width"], _view.config["height"], _vastController.stageDimensions.withControlsPaddingBottom)); 
+            _vastController.addEventListener(OverlayAdDisplayEvent.DISPLAY, onDisplayOverlay);
+            _vastController.addEventListener(OverlayAdDisplayEvent.HIDE, onHideOverlay);
+            _vastController.addEventListener(OverlayAdDisplayEvent.DISPLAY_NON_OVERLAY, onDisplayNonOverlay);
+            _vastController.addEventListener(OverlayAdDisplayEvent.HIDE_NON_OVERLAY, onHideNonOverlay);
+            _vastController.addEventListener(OverlayAdDisplayEvent.CLICKED, onOverlayClicked);
+            _vastController.addEventListener(OverlayAdDisplayEvent.CLOSE_CLICKED, onOverlayCloseClicked);
+            _vastController.addEventListener(AdNoticeDisplayEvent.DISPLAY, onDisplayNotice);
+            _vastController.addEventListener(AdNoticeDisplayEvent.HIDE, onHideNotice);
+          
+            // Setup linear tracking events
+            _vastController.addEventListener(LinearAdDisplayEvent.CLICK_THROUGH, onLinearAdClickThrough);           
+            
+            // Setup the hander for tracking point set events
+            _vastController.addEventListener(TrackingPointEvent.SET, onSetTrackingPoint);
+            _vastController.addEventListener(TrackingPointEvent.FIRED, onTrackingPointFired);
+            
+            // Setup the hander for display events on the seeker bar
+            _vastController.addEventListener(SeekerBarEvent.TOGGLE, onToggleSeekerBar);
+            
+            // Ok, let's load up the VAST data from our Ad Server
+            _vastController.load();
+			doLog("Initialisation complete.", Debuggable.DEBUG_ALL);
+		}
+		
+		protected function getActiveStreamIndex():int {
+			return (_vastController.allowPlaylistControl) 
+						? _activeStreamIndex : 
+						((_playlist != null) ? _playlist.playingTrackIndex : 0);
+		}
+		
+		protected function getPlayerPlaylistIndex():int {
+			return (_vastController.allowPlaylistControl) ? _playlist.playingTrackIndex : 0;
+		}
+
+		protected function activeStreamIsLinearAd():Boolean {
+			return (_vastController.streamSequence.streamAt(getActiveStreamIndex()) is AdSlot);
+		}
+		
+		protected function createPlaylist():JWPlaylist {
+			return new JWPlaylist(_vastController.streamSequence, 
+			                      _vastController.config.showsConfig.providersConfig, 
+			                      _vastController.config.adsConfig.providersConfig);
+		}
+		
+		protected function loadJWClip(item:JWPlaylistItem):void {
+			if(item != null) {
+	            var playlist:Array = new Array();
+	            playlist.push(item.toJWPlaylistItemObject(true));
+	            if(playlist.length > 0) {
+	                doLog("Loading playlist track " + item.toString(), Debuggable.DEBUG_PLAYLIST);
+	                // always enable the control bar before loading a new clip
+					setControlBarState(false);
+	                // now load the new clip
+	                _view.sendEvent("LOAD", playlist);
+	    	    }
+	        	else doLog("Cannot convert the clip to a JW clip object - loading failed");                								
+			}
+			else doLog("Cannot load JW clip from null JWPlaylistItem", Debuggable.DEBUG_PLAYLIST);
+		}
+
+		// MetaData handler
+
+		private function onMetaData(evt:ModelEvent):void {
+			if(evt.data != null) {
+				if(evt.data.duration != undefined) {
+					var streamDuration:int = int(evt.data.duration);
+					doLog("Duration metadata received for active clip - duration is " + streamDuration, Debuggable.DEBUG_CONFIG);
+					var theScheduledStream:Stream = _vastController.streamSequence.streamAt(getActiveStreamIndex());
+					if(theScheduledStream != null) {
+						var roundedDuration:int = Math.floor(streamDuration);			
+						if(theScheduledStream is AdSlot) {
+						   	if(_vastController.deriveAdDurationFromMetaData() || theScheduledStream.hasZeroDuration()) {
+						   		if(theScheduledStream.getDurationAsInt() != roundedDuration && roundedDuration > 0) {
+						   			doLog("Ad duration requires adjustment - original duration: " + theScheduledStream.duration + ", metadata duration: " + roundedDuration, Debuggable.DEBUG_CONFIG);
+		                            var currentClip:Object = _view.playlist[getPlayerPlaylistIndex()]; //_playlist.playingTrackIndex];
+		                            if(currentClip != null) {
+			                            currentClip["duration"] = streamDuration;
+										theScheduledStream.duration = Timestamp.secondsToTimestamp(streamDuration);
+										_vastController.resetAdDurationForAdStreamAtIndex(getActiveStreamIndex(), roundedDuration);	                            	
+		                            }
+		                        	else doLog("ERROR: Failed to adjust duration/tracking table - currentClip is null - should not be", Debuggable.DEBUG_FATAL);
+						   		}
+						   		else doLog("Not changing ad duration - original matches metadata", Debuggable.DEBUG_CONFIG);
+							}
+							else doLog("Not adjusting the ad duration based on metadata - it is either non zero or config states to not use metadata", Debuggable.DEBUG_CONFIG);
+						}
+						else doLog("This clip is not an ad so we aren't doing anything with this duration metadata", Debuggable.DEBUG_CONFIG);					
+					}
+				}			
+			}
+		}
+
+		// Time point handler
+		
+		private function timeHandler(evt:ModelEvent):void {
+			var activeIndex:int = getActiveStreamIndex();
+			if((_vastController.streamSequence.streamAt(activeIndex) is AdSlot)) {
+				/* 
+				 * The next two conditions exist because for some strange reason, when changing between playlist items
+				 * as the new item is loaded, a 0 timing event is fired, but a timing event for the previous stream may
+				 * appear after that - e.g. 0 for playlist item 1 followed by 30.1 for playlist item 0 - we need to ignore
+				 * any left over timing events once the new playlist item has been loaded
+				 */
+				if(!_vastController.isActiveOverlayVideoPlaying()) {
+					if(evt.data.position > _lastTimeTick && _lastTickActiveIndex != activeIndex && _lastTickActiveIndex > -1) {
+						if(evt.data.position >= _lastTimeTick) {
+							doLog("Ignoring time event at " + evt.data.position + " - lastTimeTick = " + _lastTimeTick + ", lastTickActiveIndex = " + _lastTickActiveIndex + " but activeIndex = " + activeIndex, Debuggable.DEBUG_CUEPOINT_EVENTS);
+							return;
+						}
+					}
+					_lastTickActiveIndex = activeIndex;
+					if(evt.data.position > (_lastTimeTick + 1.2)) {
+						doLog("Ignoring time event at " + evt.data.position + " - the variation with the lastTickTime " + _lastTimeTick + " is greater than 1.2 - time event appears out of sync", Debuggable.DEBUG_CUEPOINT_EVENTS);				
+						return;
+					}				
+				}
+				if(_needToDoStartPreProcessing) {
+					// used to enforce impression sending where needed for empty Ad slots
+					_vastController.processImpressionsToForceFire();				
+					_needToDoStartPreProcessing = false;
+				}				
+			}
+			if(!_vastController.isActiveOverlayVideoPlaying()) {
+				_lastTimeTick = evt.data.position;
+				_vastController.processTimeEvent(activeIndex, new TimeEvent(evt.data.position * 1000, evt.data.duration));		
+			}
+			else {
+				_vastController.processOverlayLinearVideoAdTimeEvent(_activeOverlayAdSlotKey, new TimeEvent(evt.data.position * 1000, evt.data.duration));
+			}				
+		}
+				
+		// Tracking Point event callbacks
+		
+		protected function onSetTrackingPoint(event:TrackingPointEvent):void {
+			// Not required for JW Player because we are constantly checking the 1/10th second timed events
+			// by firing them directly through to the stream sequence to process.
+			doLog("NOTIFICATION: Request received to set a tracking point (" + event.trackingPoint.label + ") at " + event.trackingPoint.milliseconds + " milliseconds", Debuggable.DEBUG_TRACKING_EVENTS);
+		}
+
+		protected function onTrackingPointFired(event:TrackingPointEvent):void {
+			// Not required for JW Player because we are constantly checking the 1/10th second timed events
+			// by firing them directly through to the stream sequence to process.
+			doLog("NOTIFICATION: Request received that a tracking point was fired (" + event.trackingPoint.label + ") at " + event.trackingPoint.milliseconds + " milliseconds", Debuggable.DEBUG_TRACKING_EVENTS);
+		}
+		
+		// VAST data event callbacks
+		
+		protected function onTemplateLoaded(event:TemplateEvent):void {
+			doLogAndTrace("NOTIFICATION: VAST data loaded - ", event.template, Debuggable.DEBUG_VAST_TEMPLATE);
+        
+			_playlist = createPlaylist();
+			doLogAndTrace("JW playlist created: " + _playlist.toString(true), Debuggable.DEBUG_PLAYLIST, Debuggable.DEBUG_PLAYLIST);
+
+			if(_vastController.allowPlaylistControl) {
+				doLog("Loading up full playlist", Debuggable.DEBUG_PLAYLIST);
+				// load up the full playlist and play as a list
+//TO DO				_player.config.repeat="list";
+//TO DO				_player.load(_playlist.getModel());
+			}
+			else { 
+				// iterate through the playlist one clip at time, so just up the first
+                var item:JWPlaylistItem = _playlist.nextTrackAsPlaylistItem() as JWPlaylistItem;
+                if(item != null) {
+                	loadJWClip(item);
+                }
+                else doLog("No clip available in the playlist to load into the player", Debuggable.DEBUG_PLAYLIST);
+			}
+		}
+		
+		protected function onTemplateLoadError(event:TemplateEvent):void {
+			doLog("NOTIFICATION: FAILURE loading VAST template - " + event.toString(), Debuggable.DEBUG_FATAL);
+		}
+
+        // Linear ad tracking callbacks
+        
+		public function onLinearAdClickThrough(linearAdDisplayEvent:LinearAdDisplayEvent):void {
+			doLog("NOTIFICATION: Event received that linear ad click through activated", Debuggable.DEBUG_DISPLAY_EVENTS);			
+			if(_vastController.pauseOnClickThrough) _view.sendEvent(ControllerEvent.PLAY, false);
+		}
+
+        // Seekbar callbacks
+
+        protected function setControlBarState(turnOff:Boolean):void {
+			var controlbar:Object = _view.getPlugin('controlbar');
+			controlbar.block(turnOff);        	
+        }
+        
+		public function onToggleSeekerBar(event:SeekerBarEvent):void {
+			if(_vastController.disableControls) {
+ 			    doLog("NOTIFICATION: Request received to change the control bar state to " + ((event.turnOff()) ? "BLOCKED" : "ON"), Debuggable.DEBUG_DISPLAY_EVENTS);
+ 			    setControlBarState(event.turnOff());
+			}
+			else doLog("NOTIFICATION: Ignoring request to change control bar state", Debuggable.DEBUG_DISPLAY_EVENTS);
+		}
+
+        // VAST display callbacks
+
+		public function onDisplayNotice(displayEvent:AdNoticeDisplayEvent):void {	
+			doLog("NOTIFICATION: Event received to display ad notice", Debuggable.DEBUG_DISPLAY_EVENTS);
+		}
+				
+		public function onHideNotice(displayEvent:AdNoticeDisplayEvent):void {	
+			doLog("NOTIFICATION: Event received to hide ad notice", Debuggable.DEBUG_DISPLAY_EVENTS);
+		}
+				
+		public function onDisplayOverlay(displayEvent:OverlayAdDisplayEvent):void {
+			doLog("NOTIFICATION: Event received to display non-linear overlay ad", Debuggable.DEBUG_DISPLAY_EVENTS);
+		}
+
+		public function onOverlayCloseClicked(displayEvent:OverlayAdDisplayEvent):void {
+			doLog("NOTIFICATION: Event received - overlay close has been clicked", Debuggable.DEBUG_DISPLAY_EVENTS);
+		}
+
+		public function onOverlayClicked(displayEvent:OverlayAdDisplayEvent):void {
+			doLog("NOTIFICATION: Event received - overlay has been clicked - time is " + _lastTimeTick, Debuggable.DEBUG_DISPLAY_EVENTS);
+
+            if(displayEvent.ad.hasAccompanyingVideoAd()) {
+            	var overlayStreamSequence:StreamSequence = _vastController.getActiveOverlayStreamSequence();
+            	if(overlayStreamSequence != null) {
+	            	var playlist:JWPlaylist = new JWPlaylist(overlayStreamSequence, _vastController.config.providersForShows(), _vastController.config.providersForAds());
+	            	if(playlist.length > 0) {            		
+						doLog("Loading the overlay linear ad track as playlist " + playlist, Debuggable.DEBUG_PLAYLIST);
+		                var item:JWPlaylistItem = playlist.nextTrackAsPlaylistItem() as JWPlaylistItem;
+		                if(item != null) {
+		                	stopPlayback();
+							_vastController.activeOverlayVideoPlaying = true;
+		                	loadJWClip(item);
+							_activeOverlayAdSlotKey = displayEvent.adSlotKey;
+							startPlayback();
+		                }
+		                else {
+			                doLog("No clip available in the overlay linear playlist to load into the player", Debuggable.DEBUG_PLAYLIST);
+		                }							
+	            	}
+					else doLog("Cannot play the linear ad - playlist is empty: " + playlist, Debuggable.DEBUG_PLAYLIST);            		
+            	}
+            	else {
+					_vastController.activeOverlayVideoPlaying = false;
+					_activeOverlayAdSlotKey = -1;					
+					doLog("Cannot play the linear ad - playlist is empty: " + playlist, Debuggable.DEBUG_PLAYLIST);            		
+            	}
+            }
+			else {
+				if(displayEvent.ad.hasClickThroughURL()) {
+					// it's a website click through overlay so stop the video stream
+					stopPlayback();
+			 	}
+			}
+		}
+	
+		public function onHideOverlay(displayEvent:OverlayAdDisplayEvent):void {
+			doLog("NOTIFICATION: Event received to hide non-linear overlay ad", Debuggable.DEBUG_DISPLAY_EVENTS);
+		}
+		
+		public function onDisplayNonOverlay(displayEvent:OverlayAdDisplayEvent):void {
+			doLog("NOTIFICATION: Event received to display non-linear non-overlay ad", Debuggable.DEBUG_DISPLAY_EVENTS);
+		}
+		
+		public function onHideNonOverlay(displayEvent:OverlayAdDisplayEvent):void {
+			doLog("NOTIFICATION: Event received to hide non-linear non-overlay ad", Debuggable.DEBUG_DISPLAY_EVENTS);
+		}
+
+        // Companion Ad Display Events
+        
+        public function onDisplayCompanionAd(companionEvent:CompanionAdDisplayEvent):void {
+			doLogAndTrace("NOTIFICATION: Event received to display companion ad", companionEvent, Debuggable.DEBUG_DISPLAY_EVENTS);
+        }
+
+		public function onHideCompanionAd(companionEvent:CompanionAdDisplayEvent):void {
+            doLogAndTrace("NOTIFICATION: Request received to hide companion ad", companionEvent, Debuggable.DEBUG_DISPLAY_EVENTS);
+		}
+
+        // VAST tracking actions
+
+        private function onSeekEvent(evt:ControllerEvent):void {
+        	if(_vastController != null) {
+				if(_vastController.isActiveOverlayVideoPlaying()) {
+        			_vastController.onPlayerSeek(_activeOverlayAdSlotKey, true);
+        		}
+        		else _vastController.onPlayerSeek(getActiveStreamIndex());
+        	}
+        }
+        
+		private function onMuteEvent(evt:ControllerEvent):void {
+			if(evt.data.state) {
+	        	if(_vastController != null) {
+					if(_vastController.isActiveOverlayVideoPlaying()) {
+	        			_vastController.onPlayerMute(_activeOverlayAdSlotKey, true);
+	        		}
+	        		else _vastController.onPlayerMute(getActiveStreamIndex());	        	
+	        	}
+			}
+		}
+
+		private function onPlayEvent(evt:ControllerEvent):void {
+	       	if(_vastController != null) {
+				if(_vastController.isActiveOverlayVideoPlaying()) {
+	       			_vastController.onPlayerPlay(_activeOverlayAdSlotKey, true);
+	       		}
+	       		else _vastController.onPlayerPlay(getActiveStreamIndex());
+	       	}			
+		}
+
+		private function onStopEvent(evt:ControllerEvent):void {
+			if(!_forcedStop) {
+		       	if(_vastController != null) {
+					if(_vastController.isActiveOverlayVideoPlaying()) {
+		       			_vastController.onPlayerStop(_activeOverlayAdSlotKey, true);
+		       		}
+		       		else _vastController.onPlayerStop(getActiveStreamIndex());
+		       	}			
+			}
+			_forcedStop = false;
+		}
+
+		private function onPauseResumeEvent(evt:ControllerEvent):void {
+			if(evt.data.state) {
+				// we are playing again
+		       	if(_vastController != null) {
+	 				if(_vastController.isActiveOverlayVideoPlaying()) {
+						_vastController.onPlayerResume(_activeOverlayAdSlotKey, true);
+		       		}
+		       		else _vastController.onPlayerResume(getActiveStreamIndex());
+		       	}
+			}
+			else {
+				// we are pausing
+		       	if(_vastController != null) {
+					if(_vastController.isActiveOverlayVideoPlaying()) {
+		       			_vastController.onPlayerPause(_activeOverlayAdSlotKey, true);
+		       		}
+		       	}
+				else _vastController.onPlayerPause(getActiveStreamIndex());
+			}
+		}
+
+		private function onVolumeEvent(evt:ControllerEvent):void {
+			if(evt.data.percentage == 0) {
+		       	if(_vastController != null) {
+					if(_vastController.isActiveOverlayVideoPlaying()) {
+		       			_vastController.onPlayerMute(_activeOverlayAdSlotKey, true);
+		       		}
+					else _vastController.onPlayerMute(getActiveStreamIndex());
+		       	}
+			}
+		}
+
+		private function onResizeEvent(evt:ControllerEvent):void {
+			// if we haven't already stored the original dimensions, do so now
+			if(_originalWidth < 0) {
+				_originalWidth = evt.data.width; 
+			}
+			if(_originalHeight < 0) {
+				_originalHeight =  evt.data.height; 
+			}
+			if(_vastController != null) {
+				if(_vastController.stageDimensions != null) {
+					_vastController.resizeOverlays(
+							new DisplayProperties(
+									this, 
+									evt.data.width,
+									((evt.data.fullscreen) ? evt.data.height : _originalHeight),
+									_vastController.stageDimensions.withControlsPaddingBottom, 
+									_originalWidth, 
+									_originalHeight							
+							)
+					);					
+				}
+			}
+			doLog("*** current - w:" + evt.data.width + " h:" + evt.data.height + "  original - w:" + _originalWidth + " h:" + _originalHeight, Debuggable.DEBUG_DISPLAY_EVENTS);
+		}
+
+		private function playlistSelectionHandler(evt:ControllerEvent):void {
+			_activeStreamIndex = evt.data.index;
+			
+			_vastController.hideAllOverlays();
+			_vastController.closeActiveOverlaysAndCompanions();
+			_vastController.disableVisualLinearAdClickThroughCue();
+			_vastController.closeActiveAdNotice();
+			
+			if(!_vastController.isActiveOverlayVideoPlaying()) {
+//				_vastController.resetRepeatableTrackingPoints(_activeStreamIndex);
+       			_vastController.resetAllTrackingPointsAssociatedWithStream(_activeStreamIndex);
+			}
+            doLog("Active playlist stream index changed to " + _activeStreamIndex, Debuggable.DEBUG_PLAYLIST);
+		}
+
+		private function resumeMainPlaylistPlayback():void {
+			doLog("Restoring the last active main playlist clip	- seeking forward to time " + _lastTimeTick, Debuggable.DEBUG_PLAYLIST);
+			var item:JWPlaylistItem = _playlist.currentTrackAsPlaylistItem(_lastTimeTick, true) as JWPlaylistItem;
+	        if(item != null) {
+	            doLog("Reloading main playlist track at " + _lastTimeTick + " which was interrupted by an overlay linear video ad " + item.toString(), Debuggable.DEBUG_PLAYLIST);
+				_vastController.activeOverlayVideoPlaying = false;
+				_resumingMainStreamPlayback = true;
+				_activeOverlayAdSlotKey = -1;
+				loadJWClip(item);
+				startPlayback();
+	        }
+	        else doLog("Oops, no main playlist stream in the playlist to load", Debuggable.DEBUG_FATAL);
+		}
+		
+		private function startPlayback():void {
+           	_view.sendEvent(ViewEvent.PLAY, true);			
+		}
+		
+		public function stopPlayback():void {
+			_forcedStop = true;
+           	_view.sendEvent(ViewEvent.PLAY, false);						
+		}
+		
+		private function streamStateHandler(evt:ModelEvent):void {
+			if(!_vastController.isActiveOverlayVideoPlaying()) {
+			    if(!_vastController.allowPlaylistControl) {
+					// We are handling a state change on the main playlist
+					switch(evt.data.newstate) {
+						case "COMPLETED":
+			                var item:JWPlaylistItem = _playlist.nextTrackAsPlaylistItem() as JWPlaylistItem;
+			                if(item != null) {
+			                	loadJWClip(item);
+			                	startPlayback();
+			                }
+		                    else {
+		                        doLog("Rewinding and reloading the entire playlist", Debuggable.DEBUG_PLAYLIST);
+		                        _view.config['autostart'] = false;
+		                    	_playlist.rewind();
+			                	loadJWClip(_playlist.nextTrackAsPlaylistItem() as JWPlaylistItem);
+		                    }
+							break;
+					}						
+			    }
+			}
+			else {
+				// We are handling the state change of an overlay linear video ad
+				switch(evt.data.newstate) {
+					case "COMPLETED":
+						doLog("Overlay linear video ad complete - resuming normal stream", Debuggable.DEBUG_PLAYLIST);
+						resumeMainPlaylistPlayback();
+						break;				
+				}
+			}
+		}
+		
+		// DEBUG METHODS
+		
+		protected static function doLog(data:String, level:int=1):void {
+			Debuggable.getInstance().doLog(data, level);
+		}
+		
+		protected static function doTrace(o:Object, level:int=1):void {
+			Debuggable.getInstance().doTrace(o, level);
+		}
+		
+		protected static function doLogAndTrace(data:String, o:Object, level:int=1):void {
+			Debuggable.getInstance().doLogAndTrace(data, o, level);
+		}
+	}
+}
Index: /trunk/ova.jwplayer.4x/src/ova.as
===================================================================
--- /trunk/ova.jwplayer.4x/src/ova.as	(revision 9)
+++ /trunk/ova.jwplayer.4x/src/ova.as	(revision 9)
@@ -0,0 +1,27 @@
+/*    
+ *    Copyright (c) 2009 Open Video Ads - Option 3 Ventures Limited
+ *
+ *    This file is part of the Open Video Ads JW Player Open Ad Streamer.
+ *
+ *    The Open Ad Streamer is free software: you can redistribute it 
+ *    and/or modify it under the terms of the GNU General Public License 
+ *    as published by the Free Software Foundation, either version 3 of 
+ *    the License, or (at your option) any later version.
+ *
+ *    The Open Ad Streamer is distributed in the hope that it will be 
+ *    useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *    GNU General Public License for more details.
+ *
+ *    You should have received a copy of the GNU General Public License
+ *    along with the framework.  If not, see <http://www.gnu.org/licenses/>.
+ */
+package {
+	import org.openvideoads.plugin.jwplayer.streamer.OpenAdStreamer;
+
+	public class ova extends org.openvideoads.plugin.jwplayer.streamer.OpenAdStreamer {
+		public function ova():void {
+			super();
+		}
+	}
+}
