///////////////////////////////////////////////////////////////////////////////// // // c) 2011 Panther Panache, LLC. // This source code is Confidential and Proprietary information of the company. // ///////////////////////////////////////////////////////////////////////////////// package com.panache.creativeplayer.video { import com.panache.creativeplayer.video.events.PanacheQuartileEvent; import com.panache.creativeplayer.video.events.PanacheVideoViewerEvent; import com.panache.debug.MediaServicesAdDebug; import fl.video.FLVPlayback; import flash.display.Sprite; import fl.video.VideoEvent; import fl.video.VideoState; import flash.events.Event; import fl.video.VideoPlayer; public class PanacheFLVPlayer extends FLVPlayback { ///////////////////////////////////////////////////////////////// // CONSTANTS private static const DEBUG_SOURCE:String = "MS_PanacheFLVPlayer"; private static const VERSION_MAJOR:Number = 2; private static const VERSION_MINOR:Number = 3; private static const VERSION_STRING:String = VERSION_MAJOR + "." + VERSION_MINOR; public static const PLAYSTATE_PAUSED:String = "videoPaused"; public static const PLAYSTATE_PLAYING:String = "videoPlaying"; public static const LINEAR_AD_STATE_NA:String = "na"; // quartiles protected static const NONE_REPORTED:uint = 0x00; protected static const VIDEO_Q0:uint = 0x01; protected static const VIDEO_Q1:uint = 0x02; protected static const VIDEO_Q2:uint = 0x04; protected static const VIDEO_Q3:uint = 0x08; protected static const VIDEO_Q4:uint = 0x10; protected static const QUARTILES_LIST:Array = new Array(VIDEO_Q0, VIDEO_Q1, VIDEO_Q2, VIDEO_Q3, VIDEO_Q4); protected static const VIDEO_START_REPORTED:uint = VIDEO_Q0; protected static const VIDEO_Q1_REPORTED:uint = VIDEO_Q0 + VIDEO_Q1; protected static const VIDEO_MID_REPORTED:uint = VIDEO_Q0 + VIDEO_Q1 + VIDEO_Q2; protected static const VIDEO_Q3_REPORTED:uint = VIDEO_Q0 + VIDEO_Q1 + VIDEO_Q2 + VIDEO_Q3; protected static const VIDEO_END_REPORTED:uint = VIDEO_Q0 + VIDEO_Q1 + VIDEO_Q2 + VIDEO_Q3 + VIDEO_Q4; ///////////////////////////////////////////////////////////////// // VARIABLES protected var _bg:Sprite; protected var _currentVideoPlayer:VideoPlayer = null; protected var _sPlayState:String = PLAYSTATE_PAUSED; protected var _sLinearAdPlayState:String = LINEAR_AD_STATE_NA; // quartiles protected var _nQuartilesReported:uint = NONE_REPORTED; // status protected var _bVideoCompleted:Boolean = false; protected var _bVideoFailed:Boolean = false; protected var _bVideoReadyToPlay:Boolean = false; protected var _bVideoPendingToPlay:Boolean = false; protected var _bPlayingOnScale:Boolean = false; // records the playstate when the video player is scaled (or resized) see handleVideoStateStopped below protected var _bAllowRewind:Boolean = false; // disallows rewind - see handleVideoStateRewinding below // pending parameters protected var _sSource:String = null; protected var _nTotalTime:Number = NaN; protected var _bIsLive:Boolean = false; ///////////////////////////////////////////////////////////////// // CONSTRUCTOR public function PanacheFLVPlayer() { MediaServicesAdDebug.reportVersion("MS_PANACHE_FLVPLAYER", VERSION_STRING); _bg = new Sprite(); addChild(_bg); super(); visible = false; fullScreenTakeOver = false; } private function resetInternal() : void { removeListeners(); // reset varibles _nQuartilesReported = NONE_REPORTED; _bVideoCompleted = false; _bVideoFailed = false; _bVideoReadyToPlay = false; _bVideoPendingToPlay = false; _sSource = null; _nTotalTime = NaN; _bIsLive = false; } ///////////////////////////////////////////////////////////////// // PROPERTIES override public function set source(url:String) : void { MediaServicesAdDebug.reportTrace("video url=" + url, DEBUG_SOURCE, "set_source"); resetInternal(); addListeners(); MediaServicesAdDebug.reportTrace("video ready to play = " + _bVideoReadyToPlay, DEBUG_SOURCE, "set_source"); super.source = url; } override public function set visible(value:Boolean) : void { _bg.visible = value; super.visible = value; } public function get linearAdPlayState() : String { return _sLinearAdPlayState; } public function set linearAdPlayState(playState:String) : void { _sLinearAdPlayState = playState; } public function get videoCompleted() : Boolean { return _bVideoCompleted; } ///////////////////////////////////////////////////////////////// // EVENT HANDLING // add event listeners needed when the video source is set // and BEFORE video is ready protected function addListeners() : void { _currentVideoPlayer = this.getVideoPlayer(this.activeVideoPlayerIndex); if (_currentVideoPlayer != null) { _currentVideoPlayer.addEventListener(VideoEvent.STATE_CHANGE, onStateChange); } addEventListener(VideoEvent.READY, handleVideoReady); } // add event listeners needed once the video is ready to play protected function addVideoReadyListeners() : void { addEventListener(VideoEvent.PLAYHEAD_UPDATE, handlePlayheadUpdate); addEventListener(VideoEvent.COMPLETE, handleVideoComplete); } // remove all listeners from the player protected function removeListeners() : void { if (_currentVideoPlayer != null) _currentVideoPlayer.removeEventListener(VideoEvent.STATE_CHANGE, onStateChange); removeEventListener(VideoEvent.READY, handleVideoReady); removeVideoCompleteListeners(); } // remove the listeners that were added when the video was ready to play // and the video is now complete protected function removeVideoCompleteListeners() : void { removeEventListener(VideoEvent.PLAYHEAD_UPDATE, handlePlayheadUpdate); removeEventListener(VideoEvent.COMPLETE, handleVideoComplete); } // event handler for the STATE_CHANGE Video Event protected function onStateChange(e:VideoEvent) : void { MediaServicesAdDebug.reportTrace("video state=" + e.state + " playingOnScale=" + _bPlayingOnScale, DEBUG_SOURCE, "onStateChange"); switch (e.state) { case VideoState.REWINDING : handleVideoStateRewinding(); break; case VideoState.STOPPED : handleVideoStateStopped(); break; case VideoState.CONNECTION_ERROR : handleConnectionError(); break; case VideoState.PLAYING: handleVideoStatePlaying(); break; default: break; } } // event handler for the STATE_CHANGE Video Event where the new state = VideoState.REWINDING // this handler is needed to get around some kind of improper behavior that occurs in FLVPlayback // when the video is resized TWO OR MORE TIMES while the video is running // After being resized two or more times while playing, FlvPlayback received a state change notice // that the player is rewinding (even though autoRewind is set to false). This causes the video // to start playing a second time ! // So, since the default behavior is to NOT support rewinding, we set a _bAllowRewind flag to false. // Then, if rewinding is NOT allowed, we stop the player (by calling super.stop()) // And if the video completed flag has not yet been set to true, we trigger a videoComplete behavior. // // NOTE: If you want to support rewinding, then you need to: // - subclass this player // - set the _bAllowRewind to true // - code around the double resize issue on your own =) protected function handleVideoStateRewinding() : void { MediaServicesAdDebug.reportTrace("Rewind allowed=" + _bAllowRewind + " videoComplete=" + _bVideoCompleted, DEBUG_SOURCE, "handleVideoStateRewinding"); if (!_bAllowRewind) { super.stop(); if (!_bVideoCompleted) handleVideoComplete(null); } } // event handler for the STATE_CHANGE Video Event where the new state = VideoState.STOPPED // this handler is needed to get around some kind of improper behavior that occurs in FLVPlayback // when the video is resized while the video is running // FLVPlayback receives a state change that the video has stopped even though there was no call to // stop made in the code. // So we need to check if the stop was a result of a resize (or scaling). // When scale() is called, its sets the _bPlayingOnScale flag to true IF the player was playing // when the size was changed. // Then here we check the _bPlayingOnScale flag and if it has been set to true // - reset the flag to false // - resume the video playback by calling play() on super (to avoid other code we // normally do on play() protected function handleVideoStateStopped() : void { MediaServicesAdDebug.reportTrace("playing state on scale=" + _bPlayingOnScale, DEBUG_SOURCE, "handleVideoStateStopped"); if (_bPlayingOnScale) { _bPlayingOnScale = false; super.play(); } } // event handler for the STATE_CHANGE Video Event where the new state = VideoState.CONNECTION_ERROR // FLVPlayback was unable to load the video from its source // So we need to set videoCompleted to true and set videoFailed to true // and dispatch a failure event protected function handleConnectionError() : void { MediaServicesAdDebug.reportErrorTrace(VideoState.CONNECTION_ERROR, null, DEBUG_SOURCE, "onStateChange"); _bVideoCompleted = true; _bVideoFailed = true; removeListeners(); // dispatch failure event var evnt:PanacheVideoViewerEvent = new PanacheVideoViewerEvent(PanacheVideoViewerEvent.MEDIA_FAILURE_EVENT); evnt.mediaUrl = this.source; evnt.mediaType = PanacheVideoViewerEvent.MEDIA_TYPE_VIDEO; MediaServicesAdDebug.reportTrace("PanacheVideoViewerEvent = " + evnt.toString(), DEBUG_SOURCE, "onStateChange"); dispatchEvent(evnt); dispatchEvent(new Event("PanacheVideoViewerVideoErrorEvent")); } protected function handleVideoStatePlaying() : void { MediaServicesAdDebug.reportTrace("", DEBUG_SOURCE, "handleVideoStatePlaying"); var evnt:PanacheVideoViewerEvent = new PanacheVideoViewerEvent(PanacheVideoViewerEvent.MEDIA_PLAYSTATE_PLAYING_EVENT); evnt.mediaUrl = this.source; evnt.mediaType = PanacheVideoViewerEvent.MEDIA_TYPE_VIDEO; dispatchEvent(evnt); } ///////////////////////////////////////////////////////////////// // METHODS public function getPlayState() : String { if (super.paused) _sPlayState = PLAYSTATE_PAUSED; else _sPlayState = PLAYSTATE_PLAYING; MediaServicesAdDebug.reportTrace("playState=" + _sPlayState, DEBUG_SOURCE, "getPlayState"); return _sPlayState; } public function getVideoProgress() : Object { var progressObject:Object = new Object(); progressObject.videoDuration = this.totalTime; progressObject.videoTime = this.playheadTime; progressObject.videoBytesLoaded = this.bytesTotal; progressObject.videoBytesTotal = this.bytesTotal; return progressObject; } public function position(xLoc:Number, yLoc:Number) : void { MediaServicesAdDebug.reportTrace("position, x=" + xLoc + " y=" + yLoc, DEBUG_SOURCE, "position", MediaServicesAdDebug.DEBUG_LEVEL_DETAIL); super.x = xLoc; super.y = yLoc; _bg.x = xLoc; _bg.y = yLoc; } public function scale(w:Number, h:Number) : void { MediaServicesAdDebug.reportTrace("scale, w=" + w + " h=" + h + " playing=" + this.playing, DEBUG_SOURCE, "scale", MediaServicesAdDebug.DEBUG_LEVEL_ON); //setProperty("playstateOnScale", this.playing); _bPlayingOnScale = this.playing; _bg.graphics.beginFill(0x000000,0); _bg.graphics.drawRect(0,0,w,h); _bg.graphics.endFill(); //super.width = w; //super.height = h; super.setSize(w, h); } // detect if the video has already completed once // if it has "replay it" override public function play(sSource:String=null, nTotalTime:Number=NaN, bIsLive:Boolean = false) : void { MediaServicesAdDebug.reportTrace("video completed=" + _bVideoCompleted, DEBUG_SOURCE, "play"); if (_bVideoFailed) return; if (_bVideoCompleted) replay(); else { MediaServicesAdDebug.reportTrace("video ready to play = " + _bVideoReadyToPlay, DEBUG_SOURCE, "play"); if (_bVideoReadyToPlay) super.play(sSource, nTotalTime, bIsLive); else { _sSource = sSource; _nTotalTime = nTotalTime; _bIsLive = bIsLive; _bVideoPendingToPlay = true; } } } // sets the playhead back to zero and resets the quartile tracking // this should be called if you are "replaying" the same video public function replay() : void { MediaServicesAdDebug.reportTrace("", DEBUG_SOURCE, "replay"); if (_bVideoReadyToPlay) playheadTime = 0; _nQuartilesReported = NONE_REPORTED; _bVideoCompleted = false; if (_bVideoFailed) return; addVideoReadyListeners(); play(); } ///////////////////////////////////////////////////////////////// // QUARTILES protected function handleVideoReady(e:Event) : void { MediaServicesAdDebug.reportTrace("Video Ready", DEBUG_SOURCE, "handleVideoReady"); dispatchEvent(new Event("PanacheVideoReadyEvent")); _nQuartilesReported = NONE_REPORTED; _bVideoReadyToPlay = true; // set up event listeners removeEventListener(VideoEvent.READY, handleVideoReady); addVideoReadyListeners(); _bVideoCompleted = false; MediaServicesAdDebug.reportTrace("video ready to play = " + _bVideoReadyToPlay, DEBUG_SOURCE, "handleVideoReady"); if (_bVideoPendingToPlay) { super.play(_sSource, _nTotalTime, _bIsLive); _bVideoPendingToPlay = false; } } protected function handlePlayheadUpdate(e:VideoEvent) : void { var pct:Number = playheadPercentage; var pTime:Number = playheadTime; var msg:String; if (_bVideoFailed) return; if ((pTime > 0) && (_nQuartilesReported < VIDEO_START_REPORTED)) { dispatchQuartileEvent(0, "video_start"); msg = "Playhead time update=" + pTime + " pct=" + pct + " dur=" + totalTime; MediaServicesAdDebug.reportTrace(msg, DEBUG_SOURCE, "handlePlayheadUpdate"); return; } if ((pct >= 25) && (_nQuartilesReported < VIDEO_Q1_REPORTED)) { dispatchQuartileEvent(1, "video_first_quartile"); msg = "Playhead time update=" + pTime + " pct=" + pct + " dur=" + totalTime; MediaServicesAdDebug.reportTrace(msg, DEBUG_SOURCE, "handlePlayheadUpdate"); return; } if ((pct >= 50) && (_nQuartilesReported < VIDEO_MID_REPORTED)) { dispatchQuartileEvent(2, "video_mid"); msg = "Playhead time update=" + pTime + " pct=" + pct + " dur=" + totalTime; MediaServicesAdDebug.reportTrace(msg, DEBUG_SOURCE, "handlePlayheadUpdate"); return; } if ((pct >= 75) && (_nQuartilesReported < VIDEO_Q3_REPORTED)) { dispatchQuartileEvent(3, "video_third_quartile"); msg = "Playhead time update=" + pTime + " pct=" + pct + " dur=" + totalTime; MediaServicesAdDebug.reportTrace(msg, DEBUG_SOURCE, "handlePlayheadUpdate"); return; } } protected function handleVideoComplete(e:Event) : void { dispatchQuartileEvent(4, "video_end"); removeVideoCompleteListeners(); _bVideoCompleted = true; } // this is going to dispatch an event named // and the p protected function dispatchQuartileEvent(quartile:uint, quartileName:String) : void { MediaServicesAdDebug.reportTrace(quartileName + " ix=" + quartile, DEBUG_SOURCE, "dispatchQuartileEvent"); var quartile_id:uint = uint(QUARTILES_LIST[quartile]); _nQuartilesReported |= quartile_id; MediaServicesAdDebug.reportTrace("_nQuartilesReported=" + _nQuartilesReported, DEBUG_SOURCE, "dispatchQuartileEvent"); // dispatch the quartile event var evnt:PanacheQuartileEvent = new PanacheQuartileEvent(quartile, this.source); MediaServicesAdDebug.reportTrace("PanacheQuartileEvent = " + evnt.toString(), DEBUG_SOURCE, "dispatchQuartileEvent"); dispatchEvent(evnt); } } }