Changeset 135 for trunk/as3/com/jeroenwijering/models/HTTPModel.as
- Timestamp:
- 01/16/09 07:00:07 (4 years ago)
- File:
-
- 1 edited
-
trunk/as3/com/jeroenwijering/models/HTTPModel.as (modified) (5 diffs)
Legend:
- Unmodified
- Added
- Removed
-
trunk/as3/com/jeroenwijering/models/HTTPModel.as
r132 r135 1 1 /** 2 * Wrapper for playback of http 'streaming' video.2 * Manages playback of http streaming flv. 3 3 **/ 4 4 package com.jeroenwijering.models { … … 6 6 7 7 import com.jeroenwijering.events.*; 8 import com.jeroenwijering.models. ModelInterface;8 import com.jeroenwijering.models.BasicModel; 9 9 import com.jeroenwijering.player.Model; 10 10 import com.jeroenwijering.utils.NetClient; 11 11 12 import flash.events.*; 12 import flash.display.DisplayObject; 13 import flash.media.SoundTransform; 14 import flash.media.Video; 13 import flash.media.*; 15 14 import flash.net.*; 16 import flash.utils.clearInterval; 17 import flash.utils.setInterval; 18 19 20 public class HTTPModel implements ModelInterface { 21 22 23 /** reference to the model. **/ 24 private var model:Model; 15 import flash.utils.*; 16 17 18 public class HTTPModel extends BasicModel { 19 20 21 /** NetConnection object for setup of the video stream. **/ 22 protected var connection:NetConnection; 23 /** NetStream instance that handles the stream IO. **/ 24 protected var stream:NetStream; 25 25 /** Video object to be instantiated. **/ 26 private var video:Video; 27 /** NetConnection object for setup of the video stream. **/ 28 private var connection:NetConnection; 29 /** NetStream instance that handles the stream IO. **/ 30 private var stream:NetStream; 26 protected var video:Video; 31 27 /** Sound control object. **/ 32 private var transform:SoundTransform; 33 /** Interval ID for the time. **/ 34 private var timeinterval:Number; 28 protected var transform:SoundTransform; 35 29 /** Interval ID for the loading. **/ 36 private var loadinterval:Number; 30 protected var loadinterval:Number; 31 /** Save whether metadata has already been sent. **/ 32 protected var meta:Boolean; 37 33 /** Object with keyframe times and positions. **/ 38 private var keyframes:Object; 39 /** Offset byteposition to start streaming. **/ 40 private var offset:Number; 41 /** Offset timeposition for lighttpd streaming. **/ 42 private var timeoffset:Number; 43 /** switch for first metadata run **/ 44 private var metadata:Boolean; 45 /** switch for h264 streaming **/ 46 private var h264:Boolean; 34 protected var keyframes:Object; 35 /** Offset in bytes of the last seek. **/ 36 protected var byteoffset:Number; 37 /** Offset in seconds of the last seek. **/ 38 protected var timeoffset:Number; 39 /** Boolean for mp4 / flv streaming. **/ 40 protected var mp4:Boolean; 47 41 48 42 49 43 /** Constructor; sets up the connection and display. **/ 50 44 public function HTTPModel(mod:Model):void { 51 model = mod;45 super(mod); 52 46 connection = new NetConnection(); 53 connection.addEventListener(NetStatusEvent.NET_STATUS,statusHandler);54 connection.addEventListener(SecurityErrorEvent.SECURITY_ERROR,errorHandler);55 connection.addEventListener(AsyncErrorEvent.ASYNC_ERROR,errorHandler);56 47 connection.connect(null); 57 48 stream = new NetStream(connection); 58 49 stream.addEventListener(NetStatusEvent.NET_STATUS,statusHandler); 59 50 stream.addEventListener(IOErrorEvent.IO_ERROR,errorHandler); 60 stream.addEventListener(AsyncErrorEvent.ASYNC_ERROR, metaHandler);51 stream.addEventListener(AsyncErrorEvent.ASYNC_ERROR,errorHandler); 61 52 stream.bufferTime = model.config['bufferlength']; 62 53 stream.client = new NetClient(this); 63 54 video = new Video(320,240); 55 video.smoothing = model.config['smoothing']; 64 56 video.attachNetStream(stream); 65 57 transform = new SoundTransform(); 66 stream.soundTransform = transform;67 58 model.config['mute'] == true ? volume(0): volume(model.config['volume']); 68 quality(model.config['quality']); 69 offset = timeoffset = 0; 59 byteoffset = timeoffset = 0; 60 }; 61 62 63 /** Convert seekpoints to keyframes. **/ 64 protected function convertSeekpoints(dat:Object):Object { 65 var kfr:Object = new Object(); 66 kfr.times = new Array(); 67 kfr.filepositions = new Array(); 68 for (var j in dat) { 69 kfr.times[j] = Number(dat[j]['time']); 70 kfr.filepositions[j] = Number(dat[j]['offset']); 71 } 72 return kfr; 70 73 }; 71 74 72 75 73 76 /** Catch security errors. **/ 74 private function errorHandler(evt:ErrorEvent):void { 77 protected function errorHandler(evt:ErrorEvent):void { 78 stop(); 75 79 model.sendEvent(ModelEvent.ERROR,{message:evt.text}); 76 80 }; … … 78 82 79 83 /** Return a keyframe byteoffset or timeoffset. **/ 80 private function getOffset(pos:Number,tme:Boolean=false):Number { 81 if(!keyframes) { return 0; } 82 for (var i=0; i< keyframes.times.length; i++) { 84 protected function getOffset(pos:Number,tme:Boolean=false):Number { 85 if(!keyframes) { 86 return 0; 87 } 88 for (var i:Number=0; i < keyframes.times.length - 1; i++) { 83 89 if(keyframes.times[i] <= pos && keyframes.times[i+1] >= pos) { 84 if(tme == true) {85 return keyframes.times[i];86 } else {87 return keyframes.filepositions[i];88 }89 }90 }91 return 0;90 break; 91 } 92 } 93 if(tme == true) { 94 return keyframes.times[i]; 95 } else { 96 return keyframes.filepositions[i]; 97 } 92 98 }; 93 99 94 100 95 101 /** Returns a key to add to the stream. **/ 96 pr ivatefunction getToken():String {102 protected function getToken():String { 97 103 return model.config['token']; 98 104 }; 99 105 100 106 107 /** Create the video request URL. **/ 108 protected function getURL():String { 109 var url:String = item['streamer']; 110 if(url.indexOf('?') > -1) { 111 url += "&file="+item['file']; 112 } else { 113 url += "?file="+item['file']; 114 } 115 if(byteoffset > 0) { 116 url += '&start='+byteoffset; 117 } 118 if(getToken()) { 119 url += '&token='+getToken(); 120 } 121 return url; 122 }; 123 124 101 125 /** Load content. **/ 102 public function load():void { 103 video.clear(); 126 override public function load(itm:Object):void { 127 super.load(itm); 128 if(stream) { 129 stream.close(); 130 } 104 131 model.mediaHandler(video); 105 if(stream.bytesLoaded != stream.bytesTotal) { 106 stream.close(); 107 } 108 var url = model.playlist[model.config['item']]['file']; 109 var str = model.playlist[model.config['item']]['streamer']; 110 if(str == "lighttpd") { 111 if(h264) { 112 url +='?start='+timeoffset; 113 } else { 114 url += '?start='+offset; 115 } 116 } else { 117 if(str.indexOf('?') > -1) { 118 url = str+"&file="+url+'&start='+offset; 119 } else { 120 url = str+"?file="+url+'&start='+offset; 121 } 122 } 123 url += '&id='+model.config['id']; 124 url += '&client='+encodeURI(model.config['client']); 125 url += '&version='+encodeURI(model.config['version']); 126 url += '&width='+model.config['width']; 127 if(getToken()) { url += '&token='+getToken(); } 128 stream.play(url); 132 stream.play(getURL()); 133 clearInterval(interval); 134 interval = setInterval(positionInterval,100); 129 135 clearInterval(loadinterval); 130 136 loadinterval = setInterval(loadHandler,200); 131 clearInterval(timeinterval);132 timeinterval = setInterval(timeHandler,100);133 model.sendEvent(ModelEvent.STATE,{newstate:ModelStates.BUFFERING});134 137 }; 135 138 136 139 137 140 /** Interval for the loading progress **/ 138 pr ivatefunction loadHandler():void {141 protected function loadHandler():void { 139 142 var ldd:Number = stream.bytesLoaded; 140 var ttl:Number = stream.bytesTotal; 141 var off:Number = offset; 142 if(ldd >= ttl && ldd > 0) { 143 var ttl:Number = stream.bytesTotal + byteoffset; 144 var off:Number = byteoffset; 145 if(meta) { 146 ttl = getOffset(item['start']+item['duration']) - getOffset(item['start']); 147 off = Math.max(0,byteoffset-getOffset(item['start'])); 148 } 149 if(ldd+off >= ttl && ldd > 0) { 150 model.sendEvent(ModelEvent.LOADED,{loaded:ttl-off,total:ttl,offset:off}); 143 151 clearInterval(loadinterval); 144 } 145 if(model.playlist[model.config['item']]['streamer'] == "lighttpd") { 146 off = 0; 147 } 148 model.sendEvent(ModelEvent.LOADED,{loaded:ldd,total:ttl+offset,offset:off}); 149 }; 150 151 152 /** Catch noncritical errors. **/ 153 private function metaHandler(evt:ErrorEvent):void { 154 model.sendEvent(ModelEvent.META,{error:evt.text}); 152 } else { 153 model.sendEvent(ModelEvent.LOADED,{loaded:ldd,total:ttl,offset:off}); 154 } 155 155 }; 156 156 … … 158 158 /** Get metadata information from netstream class. **/ 159 159 public function onData(dat:Object):void { 160 if(dat.type == 'metadata') { 161 if(dat.seekpoints && !h264) { 162 h264 = true; 163 keyframes = new Object(); 164 keyframes.times = new Array(); 165 keyframes.filepositions = new Array(); 166 for (var j in dat.seekpoints) { 167 keyframes.times[j] = Number(dat.seekpoints[j]['time']); 168 keyframes.filepositions[j] = Number(dat.seekpoints[j]['offset']); 160 if(dat.width) { 161 video.width = dat.width; 162 video.height = dat.height; 163 } 164 if(dat.duration) { 165 dat.duration -= item['start']; 166 } 167 if(dat['type'] == 'metadata' && !meta) { 168 meta = true; 169 if(dat.seekpoints) { 170 mp4 = true; 171 keyframes = convertSeekpoints(dat.seekpoints); 172 } else { 173 mp4 = false; 174 keyframes = dat.keyframes; 175 } 176 if(item['start'] > 0) { 177 seek(0); 178 } 179 } 180 model.sendEvent(ModelEvent.META,dat); 181 }; 182 183 184 /** Pause playback. **/ 185 override public function pause():void { 186 super.pause(); 187 stream.pause(); 188 }; 189 190 191 /** Resume playing. **/ 192 override public function play():void { 193 super.play(); 194 stream.resume(); 195 }; 196 197 198 /** Interval for the position progress **/ 199 override protected function positionInterval():void { 200 var pos:Number = Math.round(stream.time*10)/10; 201 if (mp4) { 202 pos += timeoffset; 203 } 204 var bfr:Number = Math.round(stream.bufferLength/stream.bufferTime*100); 205 if(bfr < 95 && pos < Math.abs(item['duration']-stream.bufferTime-1)) { 206 model.sendEvent(ModelEvent.BUFFER,{percentage:bfr}); 207 if(model.config['state'] != ModelStates.BUFFERING && bfr < 25) { 208 model.sendEvent(ModelEvent.STATE,{newstate:ModelStates.BUFFERING}); 209 } 210 } else if (bfr > 95 && model.config['state'] != ModelStates.PLAYING) { 211 model.sendEvent(ModelEvent.STATE,{newstate:ModelStates.PLAYING}); 212 } 213 if(pos-item['start'] < item['duration']) { 214 if(pos > 0) { 215 position = Math.max(0,Math.round((pos-item['start'])*10)/10); 216 model.sendEvent(ModelEvent.TIME,{position:position,duration:item['duration']}); 217 } 218 } else if (item['duration'] > 0) { 219 pause(); 220 model.sendEvent(ModelEvent.STATE,{newstate:ModelStates.COMPLETED}); 221 } 222 }; 223 224 225 /** Seek to a specific second. **/ 226 override public function seek(pos:Number):void { 227 var off = getOffset(pos+item['start']); 228 if(off < byteoffset || off > byteoffset+stream.bytesLoaded) { 229 timeoffset = getOffset(pos+item['start'],true); 230 byteoffset = off; 231 load(item); 232 } else { 233 super.seek(pos); 234 if(mp4) { 235 stream.seek(position-timeoffset); 236 } else { 237 stream.seek(position); 238 } 239 } 240 }; 241 242 243 /** Receive NetStream status updates. **/ 244 protected function statusHandler(evt:NetStatusEvent):void { 245 switch (evt.info.code) { 246 case "NetStream.Play.Stop": 247 if(model.config['state'] != ModelStates.COMPLETED) { 248 clearInterval(interval); 249 model.sendEvent(ModelEvent.STATE,{newstate:ModelStates.COMPLETED}); 169 250 } 170 } else if(dat.keyframes) { 171 keyframes = dat.keyframes; 172 } 173 if(!metadata) { 174 if(dat.width) { 175 video.width = dat.width; 176 video.height = dat.height; 177 } 178 model.sendEvent(ModelEvent.META,dat); 179 if(model.playlist[model.config['item']]['start'] > 0 && !metadata) { 180 seek(model.playlist[model.config['item']]['start']); 181 } 182 metadata = true; 183 } 184 } else { 185 model.sendEvent(ModelEvent.META,dat); 186 } 187 }; 188 189 190 /** Pause playback. **/ 191 public function pause():void { 192 clearInterval(timeinterval); 193 stream.pause(); 194 model.sendEvent(ModelEvent.STATE,{newstate:ModelStates.PAUSED}); 195 }; 196 197 198 /** Resume playing. **/ 199 public function play():void { 200 stream.resume(); 201 timeinterval = setInterval(timeHandler,100); 202 model.sendEvent(ModelEvent.STATE,{newstate:ModelStates.PLAYING}); 203 }; 204 205 206 /** Seek to a specific second. **/ 207 public function seek(pos:Number):void { 208 clearInterval(timeinterval); 209 var off = getOffset(pos); 210 if(off < offset || off > offset+stream.bytesLoaded) { 211 offset = off; 212 timeoffset = getOffset(pos,true); 213 load(); 214 } else { 215 if(h264) { 216 stream.seek(pos-timeoffset); 217 } else { 218 stream.seek(pos) 219 } 220 play(); 221 } 222 }; 223 224 225 /** Change the smoothing mode. **/ 226 public function quality(qua:Boolean):void { 227 if(qua == true) { 228 video.smoothing = true; 229 video.deblocking = 3; 230 } else { 231 video.smoothing = false; 232 video.deblocking = 1; 233 } 234 }; 235 236 237 /** Receive NetStream status updates. **/ 238 private function statusHandler(evt:NetStatusEvent):void { 239 if(evt.info.code == "NetStream.Play.Stop") { 240 if(model.config['state'] != ModelStates.COMPLETED) { 241 clearInterval(timeinterval); 242 model.sendEvent(ModelEvent.STATE,{newstate:ModelStates.COMPLETED}); 243 } 244 } else if(evt.info.code == "NetStream.Play.StreamNotFound") { 245 stop(); 246 model.sendEvent(ModelEvent.ERROR,{message:"Video stream not found: " + 247 model.playlist[model.config['item']]['file']}); 248 } else { 249 model.sendEvent(ModelEvent.META,{info:evt.info.code}); 251 break; 252 case "NetStream.Play.StreamNotFound": 253 stop(); 254 model.sendEvent(ModelEvent.ERROR,{message:'Video not found: '+item['file']}); 255 break; 256 default: 257 model.sendEvent(ModelEvent.META,{info:evt.info.code}); 258 break; 250 259 } 251 260 }; … … 253 262 254 263 /** Destroy the HTTP stream. **/ 255 public function stop():void { 264 override public function stop():void { 265 super.stop(); 266 stream.close(); 256 267 clearInterval(loadinterval); 257 clearInterval(timeinterval); 258 offset = timeoffset = 0; 259 h264 = false; 268 byteoffset = timeoffset = 0; 260 269 keyframes = undefined; 261 metadata = false; 262 if(stream.bytesLoaded != stream.bytesTotal) { 263 stream.close(); 264 } 265 stream.pause(); 266 }; 267 268 269 /** Interval for the position progress **/ 270 private function timeHandler():void { 271 var bfr = Math.round(stream.bufferLength/stream.bufferTime*100); 272 var pos = Math.round(stream.time*10)/10; 273 if (h264 || pos == 0) { pos += timeoffset; } 274 var dur = model.playlist[model.config['item']]['duration']; 275 if(bfr<95 && pos < Math.abs(dur-stream.bufferTime*2)) { 276 model.sendEvent(ModelEvent.BUFFER,{percentage:bfr}); 277 if(model.config['state'] != ModelStates.BUFFERING && bfr < 10) { 278 model.sendEvent(ModelEvent.STATE,{newstate:ModelStates.BUFFERING}); 279 } 280 } else if (model.config['state'] == ModelStates.BUFFERING) { 281 model.sendEvent(ModelEvent.STATE,{newstate:ModelStates.PLAYING}); 282 } 283 model.sendEvent(ModelEvent.TIME,{position:pos}); 270 meta = false; 284 271 }; 285 272 286 273 287 274 /** Set the volume level. **/ 288 public function volume(vol:Number):void {275 override public function volume(vol:Number):void { 289 276 transform.volume = vol/100; 290 277 stream.soundTransform = transform;
Note: See TracChangeset
for help on using the changeset viewer.
