Ignore:
Timestamp:
01/16/09 07:00:07 (4 years ago)
Author:
jeroen
Message:

several bugfixes and the ability to restrict files from start to duration

File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/as3/com/jeroenwijering/models/HTTPModel.as

    r132 r135  
    11/** 
    2 * Wrapper for playback of http 'streaming' video. 
     2* Manages playback of http streaming flv. 
    33**/ 
    44package com.jeroenwijering.models { 
     
    66 
    77import com.jeroenwijering.events.*; 
    8 import com.jeroenwijering.models.ModelInterface; 
     8import com.jeroenwijering.models.BasicModel; 
    99import com.jeroenwijering.player.Model; 
    1010import com.jeroenwijering.utils.NetClient; 
     11 
    1112import flash.events.*; 
    12 import flash.display.DisplayObject; 
    13 import flash.media.SoundTransform; 
    14 import flash.media.Video; 
     13import flash.media.*; 
    1514import 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; 
     15import flash.utils.*; 
     16 
     17 
     18public 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; 
    2525        /** 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; 
    3127        /** Sound control object. **/ 
    32         private var transform:SoundTransform; 
    33         /** Interval ID for the time. **/ 
    34         private var timeinterval:Number; 
     28        protected var transform:SoundTransform; 
    3529        /** 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; 
    3733        /** 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; 
    4741 
    4842 
    4943        /** Constructor; sets up the connection and display. **/ 
    5044        public function HTTPModel(mod:Model):void { 
    51                 model = mod; 
     45                super(mod); 
    5246                connection = new NetConnection(); 
    53                 connection.addEventListener(NetStatusEvent.NET_STATUS,statusHandler); 
    54                 connection.addEventListener(SecurityErrorEvent.SECURITY_ERROR,errorHandler); 
    55                 connection.addEventListener(AsyncErrorEvent.ASYNC_ERROR,errorHandler); 
    5647                connection.connect(null); 
    5748                stream = new NetStream(connection); 
    5849                stream.addEventListener(NetStatusEvent.NET_STATUS,statusHandler); 
    5950                stream.addEventListener(IOErrorEvent.IO_ERROR,errorHandler); 
    60                 stream.addEventListener(AsyncErrorEvent.ASYNC_ERROR,metaHandler); 
     51                stream.addEventListener(AsyncErrorEvent.ASYNC_ERROR,errorHandler); 
    6152                stream.bufferTime = model.config['bufferlength']; 
    6253                stream.client = new NetClient(this); 
    6354                video = new Video(320,240); 
     55                video.smoothing = model.config['smoothing']; 
    6456                video.attachNetStream(stream); 
    6557                transform = new SoundTransform(); 
    66                 stream.soundTransform = transform; 
    6758                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; 
    7073        }; 
    7174 
    7275 
    7376        /** Catch security errors. **/ 
    74         private function errorHandler(evt:ErrorEvent):void { 
     77        protected function errorHandler(evt:ErrorEvent):void { 
     78                stop(); 
    7579                model.sendEvent(ModelEvent.ERROR,{message:evt.text}); 
    7680        }; 
     
    7882 
    7983        /** 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++) { 
    8389                        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                } 
    9298        }; 
    9399 
    94100 
    95101        /** Returns a key to add to the stream. **/ 
    96         private function getToken():String { 
     102        protected function getToken():String { 
    97103                return model.config['token']; 
    98104        }; 
    99105 
    100106 
     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 
    101125        /** 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                } 
    104131                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); 
    129135                clearInterval(loadinterval); 
    130136                loadinterval = setInterval(loadHandler,200); 
    131                 clearInterval(timeinterval); 
    132                 timeinterval = setInterval(timeHandler,100); 
    133                 model.sendEvent(ModelEvent.STATE,{newstate:ModelStates.BUFFERING}); 
    134137        }; 
    135138 
    136139 
    137140        /** Interval for the loading progress **/ 
    138         private function loadHandler():void { 
     141        protected function loadHandler():void { 
    139142                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}); 
    143151                        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                } 
    155155        }; 
    156156 
     
    158158        /** Get metadata information from netstream class. **/ 
    159159        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}); 
    169250                                } 
    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; 
    250259                } 
    251260        }; 
     
    253262 
    254263        /** Destroy the HTTP stream. **/ 
    255         public function stop():void { 
     264        override public function stop():void { 
     265                super.stop(); 
     266                stream.close(); 
    256267                clearInterval(loadinterval); 
    257                 clearInterval(timeinterval); 
    258                 offset = timeoffset = 0; 
    259                 h264 = false; 
     268                byteoffset = timeoffset = 0; 
    260269                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; 
    284271        }; 
    285272 
    286273 
    287274        /** Set the volume level. **/ 
    288         public function volume(vol:Number):void { 
     275        override public function volume(vol:Number):void { 
    289276                transform.volume = vol/100; 
    290277                stream.soundTransform = transform; 
Note: See TracChangeset for help on using the changeset viewer.