| 1 | package { |
|---|
| 2 | |
|---|
| 3 | |
|---|
| 4 | import com.longtailvideo.jwplayer.events.*; |
|---|
| 5 | import com.longtailvideo.jwplayer.player.*; |
|---|
| 6 | import com.longtailvideo.jwplayer.plugins.*; |
|---|
| 7 | |
|---|
| 8 | import flash.display.Sprite; |
|---|
| 9 | import flash.external.ExternalInterface; |
|---|
| 10 | import flash.net.sendToURL; |
|---|
| 11 | import flash.net.URLRequest; |
|---|
| 12 | import flash.utils.setTimeout; |
|---|
| 13 | |
|---|
| 14 | |
|---|
| 15 | /** JW Player plugin that pings playback events back to server. **/ |
|---|
| 16 | public class Ping extends Sprite implements IPlugin { |
|---|
| 17 | |
|---|
| 18 | |
|---|
| 19 | /** Plugin configuration. **/ |
|---|
| 20 | private var _config:Object = { |
|---|
| 21 | interval: 30, |
|---|
| 22 | pixel: undefined, |
|---|
| 23 | playerid: undefined |
|---|
| 24 | }; |
|---|
| 25 | /** Current playlist item. **/ |
|---|
| 26 | private var _item:Object; |
|---|
| 27 | /** Error message. **/ |
|---|
| 28 | private var _message:String; |
|---|
| 29 | /** Reference to the player. **/ |
|---|
| 30 | private var _player:IPlayer; |
|---|
| 31 | /** Page referrer. **/ |
|---|
| 32 | private var _referrer:String; |
|---|
| 33 | /** Start time **/ |
|---|
| 34 | private var _startTime:Number = -1; |
|---|
| 35 | /** Last time tick **/ |
|---|
| 36 | private var _lastTime:Number = -1; |
|---|
| 37 | |
|---|
| 38 | |
|---|
| 39 | /** Video ended; ping the server. **/ |
|---|
| 40 | private function _completeHandler(evt:MediaEvent):void { |
|---|
| 41 | _sendPing('complete'); |
|---|
| 42 | }; |
|---|
| 43 | |
|---|
| 44 | |
|---|
| 45 | /** Error occured; ping the server. **/ |
|---|
| 46 | private function _errorHandler(evt:MediaEvent):void { |
|---|
| 47 | _message = evt.message; |
|---|
| 48 | _sendPing('error'); |
|---|
| 49 | }; |
|---|
| 50 | |
|---|
| 51 | |
|---|
| 52 | /** Setup the listeners and the browser unload pingback. **/ |
|---|
| 53 | public function initPlugin(player:IPlayer, cfg:PluginConfig):void { |
|---|
| 54 | // Set options |
|---|
| 55 | _player = player; |
|---|
| 56 | if(cfg.interval) { |
|---|
| 57 | _config.interval = cfg.interval; |
|---|
| 58 | } |
|---|
| 59 | _config.pixel = cfg.pixel; |
|---|
| 60 | _config.playerid = _player.config.id; |
|---|
| 61 | // Add listeners. |
|---|
| 62 | _player.addEventListener(MediaEvent.JWPLAYER_MEDIA_COMPLETE, _completeHandler); |
|---|
| 63 | _player.addEventListener(MediaEvent.JWPLAYER_MEDIA_ERROR, _errorHandler); |
|---|
| 64 | _player.addEventListener(MediaEvent.JWPLAYER_MEDIA_TIME, _timeHandler); |
|---|
| 65 | _player.addEventListener(PlayerStateEvent.JWPLAYER_PLAYER_STATE, _stateHandler); |
|---|
| 66 | // Try connecting to JavaScript |
|---|
| 67 | try { |
|---|
| 68 | // Get ID of player in HTML DOM. |
|---|
| 69 | if(ExternalInterface.objectID) { |
|---|
| 70 | _config.playerid = ExternalInterface.objectID; |
|---|
| 71 | } |
|---|
| 72 | // Grab referrer from javascript. |
|---|
| 73 | _referrer = ExternalInterface.call("function(){return window.location.href;}"); |
|---|
| 74 | // Register a JavaScript > Flash callback. |
|---|
| 75 | ExternalInterface.addCallback("navigationAlert", _unloadHandler); |
|---|
| 76 | // Use attachEvent (IE 7/8/9) to connect callback to unload. |
|---|
| 77 | ExternalInterface.call("function() {try {eval(function playerCallback(evt){try {document.getElementById('"+_config.playerid+"').navigationAlert();}catch(err){}setTimeout('true;',1000);});var r = window.attachEvent('onbeforeunload', playerCallback);return r;}catch(err){}}"); |
|---|
| 78 | // Use addListener (Chrome/Firefox/Safari) to connect callback to unload. |
|---|
| 79 | ExternalInterface.call("function() {try {window.addEventListener('beforeunload',function(){document.getElementById('"+_config.playerid+"').navigationAlert();setTimeout('true;',1000);},false);} catch(err) {}}"); |
|---|
| 80 | } catch(err:Error) {} |
|---|
| 81 | _sendPing("ready"); |
|---|
| 82 | }; |
|---|
| 83 | |
|---|
| 84 | |
|---|
| 85 | /** Let the player know what the name of your plugin is. **/ |
|---|
| 86 | public function get id():String { return "ping"; } |
|---|
| 87 | |
|---|
| 88 | |
|---|
| 89 | /** Required resize function **/ |
|---|
| 90 | public function resize(wid:Number, hei:Number):void {} |
|---|
| 91 | |
|---|
| 92 | |
|---|
| 93 | /** Event is caught; update the time **/ |
|---|
| 94 | private function _timeHandler(evt:MediaEvent):void { |
|---|
| 95 | if (_startTime == -1) { |
|---|
| 96 | _startTime = _lastTime = evt.position; |
|---|
| 97 | } else if (Math.abs(evt.position - _lastTime) > 1) { |
|---|
| 98 | // Ignore short seeks |
|---|
| 99 | if(_lastTime - _startTime > 2) { |
|---|
| 100 | _sendPing('seek'); |
|---|
| 101 | } |
|---|
| 102 | _startTime = -1; |
|---|
| 103 | _lastTime = -1; |
|---|
| 104 | } else if(_lastTime - _startTime > _config.interval) { |
|---|
| 105 | _sendPing('progress'); |
|---|
| 106 | _startTime = _lastTime = evt.position; |
|---|
| 107 | } else { |
|---|
| 108 | _lastTime = evt.position; |
|---|
| 109 | } |
|---|
| 110 | } |
|---|
| 111 | |
|---|
| 112 | |
|---|
| 113 | /** Wrap up the url generation and do the ping. **/ |
|---|
| 114 | private function _sendPing(event:String):void { |
|---|
| 115 | // compose the query |
|---|
| 116 | var query:String = '?event='+event; |
|---|
| 117 | switch(event) { |
|---|
| 118 | case 'error': |
|---|
| 119 | query += '&file='+encodeURIComponent(_item.file); |
|---|
| 120 | query += '&mediaid='+encodeURIComponent(_item.mediaid); |
|---|
| 121 | query += '&message=' + encodeURIComponent(_message); |
|---|
| 122 | break; |
|---|
| 123 | case 'complete': |
|---|
| 124 | case 'item': |
|---|
| 125 | query += '&file='+encodeURIComponent(_item.file); |
|---|
| 126 | query += '&mediaid='+encodeURIComponent(_item.mediaid); |
|---|
| 127 | break; |
|---|
| 128 | case 'ready': |
|---|
| 129 | query += '&referrer=' + encodeURIComponent(_referrer); |
|---|
| 130 | query += '&playerid='+encodeURIComponent(_config.playerid); |
|---|
| 131 | query += '&renderingmode=flash'; |
|---|
| 132 | break; |
|---|
| 133 | case 'progress': |
|---|
| 134 | case 'seek': |
|---|
| 135 | case 'stop': |
|---|
| 136 | query += '&file='+encodeURIComponent(_item.file); |
|---|
| 137 | query += '&mediaid='+encodeURIComponent(_item.mediaid); |
|---|
| 138 | query += '&start=' + Math.round(_startTime*10)/10; |
|---|
| 139 | query += '&duration=' + Math.round((_lastTime-_startTime)*10)/10; |
|---|
| 140 | break; |
|---|
| 141 | } |
|---|
| 142 | // Add random hash to prevent caching |
|---|
| 143 | query += '&r='+Math.random(); |
|---|
| 144 | // Request the pixel or send to log. |
|---|
| 145 | if(_config.pixel) { |
|---|
| 146 | sendToURL(new URLRequest(_config.pixel+query)); |
|---|
| 147 | } |
|---|
| 148 | }; |
|---|
| 149 | |
|---|
| 150 | |
|---|
| 151 | /** Event is caught; ping the server. **/ |
|---|
| 152 | private function _stateHandler(evt:PlayerStateEvent):void { |
|---|
| 153 | if(evt.newstate == PlayerState.BUFFERING && evt.oldstate == PlayerState.IDLE) { |
|---|
| 154 | _item = _player.playlist.currentItem; |
|---|
| 155 | _sendPing('item'); |
|---|
| 156 | _startTime = -1; |
|---|
| 157 | _lastTime = -1; |
|---|
| 158 | } else if (evt.newstate == PlayerState.IDLE && _startTime > -1) { |
|---|
| 159 | _sendPing('stop'); |
|---|
| 160 | }; |
|---|
| 161 | }; |
|---|
| 162 | |
|---|
| 163 | |
|---|
| 164 | /** This function is called when a browser navigation event occurs **/ |
|---|
| 165 | private function _unloadHandler():void { |
|---|
| 166 | if (_startTime > -1) { |
|---|
| 167 | _sendPing('stop'); |
|---|
| 168 | } |
|---|
| 169 | }; |
|---|
| 170 | |
|---|
| 171 | |
|---|
| 172 | } |
|---|
| 173 | |
|---|
| 174 | |
|---|
| 175 | } |
|---|