| 1 | /** |
|---|
| 2 | * Process all input from the views and modifies the model. |
|---|
| 3 | **/ |
|---|
| 4 | package com.jeroenwijering.player { |
|---|
| 5 | |
|---|
| 6 | |
|---|
| 7 | import com.jeroenwijering.events.*; |
|---|
| 8 | import com.jeroenwijering.parsers.*; |
|---|
| 9 | import com.jeroenwijering.utils.Configger; |
|---|
| 10 | import com.jeroenwijering.utils.Randomizer; |
|---|
| 11 | import com.jeroenwijering.utils.Strings; |
|---|
| 12 | |
|---|
| 13 | import flash.display.MovieClip; |
|---|
| 14 | import flash.events.*; |
|---|
| 15 | import flash.geom.*; |
|---|
| 16 | import flash.net.*; |
|---|
| 17 | import flash.system.Capabilities; |
|---|
| 18 | |
|---|
| 19 | |
|---|
| 20 | public class Controller extends EventDispatcher { |
|---|
| 21 | |
|---|
| 22 | |
|---|
| 23 | /** Configuration object **/ |
|---|
| 24 | private var config:Object; |
|---|
| 25 | /** Reference to the skin (for stage access). **/ |
|---|
| 26 | private var skin:MovieClip; |
|---|
| 27 | /** Reference to the SPLoader (for layouting plugins). **/ |
|---|
| 28 | private var sploader:SPLoader; |
|---|
| 29 | /** Playlist of the player. **/ |
|---|
| 30 | public var playlist:Array; |
|---|
| 31 | /** Reference to the player's model. **/ |
|---|
| 32 | private var model:Model; |
|---|
| 33 | /** Reference to the player's view. **/ |
|---|
| 34 | private var view:View; |
|---|
| 35 | /** Object that manages loading of XML playlists. **/ |
|---|
| 36 | private var loader:URLLoader; |
|---|
| 37 | /** object that provides randomization. **/ |
|---|
| 38 | private var randomizer:Randomizer; |
|---|
| 39 | /** File extensions of all supported mediatypes. **/ |
|---|
| 40 | private var EXTENSIONS:Object = { |
|---|
| 41 | '3g2':'video', |
|---|
| 42 | '3gp':'video', |
|---|
| 43 | 'aac':'video', |
|---|
| 44 | 'f4b':'video', |
|---|
| 45 | 'f4p':'video', |
|---|
| 46 | 'f4v':'video', |
|---|
| 47 | 'flv':'video', |
|---|
| 48 | 'gif':'image', |
|---|
| 49 | 'jpg':'image', |
|---|
| 50 | 'm4a':'video', |
|---|
| 51 | 'm4v':'video', |
|---|
| 52 | 'mov':'video', |
|---|
| 53 | 'mp3':'sound', |
|---|
| 54 | 'mp4':'video', |
|---|
| 55 | 'png':'image', |
|---|
| 56 | 'rbs':'sound', |
|---|
| 57 | 'sdp':'video', |
|---|
| 58 | 'swf':'image', |
|---|
| 59 | 'vp6':'video' |
|---|
| 60 | }; |
|---|
| 61 | /** Elements of config that are part of the playlist. **/ |
|---|
| 62 | private var ELEMENTS:Object = { |
|---|
| 63 | author:undefined, |
|---|
| 64 | date:undefined, |
|---|
| 65 | description:undefined, |
|---|
| 66 | duration:0, |
|---|
| 67 | file:undefined, |
|---|
| 68 | image:undefined, |
|---|
| 69 | link:undefined, |
|---|
| 70 | start:0, |
|---|
| 71 | streamer:undefined, |
|---|
| 72 | tags:undefined, |
|---|
| 73 | title:undefined, |
|---|
| 74 | type:undefined |
|---|
| 75 | }; |
|---|
| 76 | |
|---|
| 77 | |
|---|
| 78 | /** Constructor, set up stage and playlist listeners. **/ |
|---|
| 79 | public function Controller(cfg:Object,skn:MovieClip,ldr:SPLoader):void { |
|---|
| 80 | config = cfg; |
|---|
| 81 | skin = skn; |
|---|
| 82 | sploader = ldr; |
|---|
| 83 | loader = new URLLoader(); |
|---|
| 84 | loader.addEventListener(Event.COMPLETE,loaderHandler); |
|---|
| 85 | loader.addEventListener(SecurityErrorEvent.SECURITY_ERROR,errorHandler); |
|---|
| 86 | loader.addEventListener(IOErrorEvent.IO_ERROR,errorHandler); |
|---|
| 87 | }; |
|---|
| 88 | |
|---|
| 89 | |
|---|
| 90 | /** Register view and model with controller, start loading playlist. **/ |
|---|
| 91 | public function closeMVC(mdl:Model,vie:View):void { |
|---|
| 92 | model= mdl; |
|---|
| 93 | model.addEventListener(ModelEvent.META,metaHandler); |
|---|
| 94 | model.addEventListener(ModelEvent.STATE,stateHandler); |
|---|
| 95 | view = vie; |
|---|
| 96 | view.addEventListener(ViewEvent.FULLSCREEN,fullscreenHandler); |
|---|
| 97 | view.addEventListener(ViewEvent.ITEM,itemHandler); |
|---|
| 98 | view.addEventListener(ViewEvent.LINK,linkHandler); |
|---|
| 99 | view.addEventListener(ViewEvent.LOAD,loadHandler); |
|---|
| 100 | view.addEventListener(ViewEvent.MUTE,muteHandler); |
|---|
| 101 | view.addEventListener(ViewEvent.NEXT,nextHandler); |
|---|
| 102 | view.addEventListener(ViewEvent.PLAY,playHandler); |
|---|
| 103 | view.addEventListener(ViewEvent.PREV,prevHandler); |
|---|
| 104 | view.addEventListener(ViewEvent.REDRAW,redrawHandler); |
|---|
| 105 | view.addEventListener(ViewEvent.SEEK,seekHandler); |
|---|
| 106 | view.addEventListener(ViewEvent.STOP,stopHandler); |
|---|
| 107 | view.addEventListener(ViewEvent.VOLUME,volumeHandler); |
|---|
| 108 | }; |
|---|
| 109 | |
|---|
| 110 | |
|---|
| 111 | /** Catch errors dispatched by the playlister. **/ |
|---|
| 112 | private function errorHandler(evt:ErrorEvent):void { |
|---|
| 113 | dispatchEvent(new ControllerEvent(ControllerEvent.ERROR,{message:evt.text})); |
|---|
| 114 | }; |
|---|
| 115 | |
|---|
| 116 | |
|---|
| 117 | /** Switch fullscreen state. **/ |
|---|
| 118 | private function fullscreenHandler(evt:ViewEvent):void { |
|---|
| 119 | if(skin.stage['displayState'] == 'fullScreen') { |
|---|
| 120 | skin.stage['displayState'] = 'normal'; |
|---|
| 121 | } else { |
|---|
| 122 | try { fullscreenrect(); } catch (err:Error) {} |
|---|
| 123 | skin.stage['displayState'] = 'fullScreen'; |
|---|
| 124 | } |
|---|
| 125 | }; |
|---|
| 126 | |
|---|
| 127 | |
|---|
| 128 | /** Set the fullscreen rectangle **/ |
|---|
| 129 | private function fullscreenrect():void { |
|---|
| 130 | var srx:Number = Capabilities.screenResolutionX; |
|---|
| 131 | var asr:Number = srx/Capabilities.screenResolutionY; |
|---|
| 132 | var pnt:Point = skin.parent.localToGlobal(new Point(skin.x,skin.y)); |
|---|
| 133 | try { |
|---|
| 134 | var wid:Number = playlist[config['item']]['width']; |
|---|
| 135 | } catch (err:Error) {} |
|---|
| 136 | if(wid && wid > srx) { |
|---|
| 137 | skin.stage["fullScreenSourceRect"] = new Rectangle(pnt.x,pnt.y,srx,Capabilities.screenResolutionY); |
|---|
| 138 | } else if(wid && wid > srx/2) { |
|---|
| 139 | skin.stage["fullScreenSourceRect"] = new Rectangle(pnt.x,pnt.y,wid,Math.round(wid/asr)); |
|---|
| 140 | } else { |
|---|
| 141 | skin.stage["fullScreenSourceRect"] = new Rectangle(pnt.x,pnt.y,srx/2,Capabilities.screenResolutionY/2); |
|---|
| 142 | } |
|---|
| 143 | }; |
|---|
| 144 | |
|---|
| 145 | |
|---|
| 146 | /** Return the type of specific playlistitem (the Model to play it with) **/ |
|---|
| 147 | private function getModelType(itm:Object,sub:Boolean):String { |
|---|
| 148 | if(!itm['file']) { |
|---|
| 149 | return null; |
|---|
| 150 | } else if(itm['type']) { |
|---|
| 151 | return itm['type']; |
|---|
| 152 | } else if (itm['streamer'] && sub) { |
|---|
| 153 | if(itm['streamer'].substr(0,4) == 'rtmp') { |
|---|
| 154 | return 'rtmp'; |
|---|
| 155 | } else if(itm['streamer'].substr(0,4) == 'http') { |
|---|
| 156 | return 'http'; |
|---|
| 157 | } else { |
|---|
| 158 | return itm['streamer']; |
|---|
| 159 | } |
|---|
| 160 | // this is a small hack to enable youtube playlists to work by default. |
|---|
| 161 | } else if(itm['file'].indexOf('youtube.com/w') > -1 || itm['file'].indexOf('youtube.com/v') > -1) { |
|---|
| 162 | return 'youtube'; |
|---|
| 163 | } else { |
|---|
| 164 | return EXTENSIONS[itm['file'].substr(-3).toLowerCase()]; |
|---|
| 165 | } |
|---|
| 166 | }; |
|---|
| 167 | |
|---|
| 168 | |
|---|
| 169 | /** Jump to a userdefined item in the playlist. **/ |
|---|
| 170 | private function itemHandler(evt:ViewEvent):void { |
|---|
| 171 | var itm:Number = evt.data.index; |
|---|
| 172 | if (itm < 0) { |
|---|
| 173 | playItem(0); |
|---|
| 174 | } else if (itm > playlist.length-1) { |
|---|
| 175 | playItem(playlist.length-1); |
|---|
| 176 | } else if (!isNaN(itm)) { |
|---|
| 177 | playItem(itm); |
|---|
| 178 | } |
|---|
| 179 | }; |
|---|
| 180 | |
|---|
| 181 | |
|---|
| 182 | /** Jump to the link of a playlistitem. **/ |
|---|
| 183 | private function linkHandler(evt:ViewEvent):void { |
|---|
| 184 | var itm:Number = config['item']; |
|---|
| 185 | if(evt.data.index) { itm = evt.data.index; } |
|---|
| 186 | var lnk:String = playlist[itm]['link']; |
|---|
| 187 | if(lnk != null) { |
|---|
| 188 | navigateToURL(new URLRequest(lnk),config['linktarget']); |
|---|
| 189 | } |
|---|
| 190 | playHandler(new ViewEvent(ViewEvent.PLAY,{state:false})); |
|---|
| 191 | }; |
|---|
| 192 | |
|---|
| 193 | |
|---|
| 194 | /** Load a new playlist. **/ |
|---|
| 195 | private function loadHandler(evt:ViewEvent):void { |
|---|
| 196 | if(config['state'] != 'IDLE') { |
|---|
| 197 | stopHandler(); |
|---|
| 198 | } |
|---|
| 199 | var obj:Object = new Object(); |
|---|
| 200 | if(typeof(evt.data.object) == 'string') { |
|---|
| 201 | obj['file'] = evt.data.object; |
|---|
| 202 | } else { |
|---|
| 203 | for(var itm:String in ELEMENTS) { |
|---|
| 204 | obj[itm] = Strings.serialize(evt.data.object[itm]); |
|---|
| 205 | } |
|---|
| 206 | } |
|---|
| 207 | if(obj['file']) { |
|---|
| 208 | if(getModelType(obj,false) == null) { |
|---|
| 209 | loader.load(new URLRequest(obj['file'])); |
|---|
| 210 | return; |
|---|
| 211 | } else { |
|---|
| 212 | playlistHandler(new Array(obj)); |
|---|
| 213 | } |
|---|
| 214 | } else { |
|---|
| 215 | var arr:Array = new Array(); |
|---|
| 216 | for each(var val:Object in obj) { |
|---|
| 217 | arr.push(val); |
|---|
| 218 | } |
|---|
| 219 | playlistHandler(arr); |
|---|
| 220 | } |
|---|
| 221 | }; |
|---|
| 222 | |
|---|
| 223 | |
|---|
| 224 | /** Translate the XML object to the feed array. **/ |
|---|
| 225 | private function loaderHandler(evt:Event):void { |
|---|
| 226 | try { |
|---|
| 227 | var dat:XML = XML(evt.target.data); |
|---|
| 228 | var fmt:String = dat.localName().toLowerCase(); |
|---|
| 229 | } catch (err:Error) { |
|---|
| 230 | dispatchEvent(new ControllerEvent(ControllerEvent.ERROR,{message:'This playlist is not a valid XML file.'})); |
|---|
| 231 | return; |
|---|
| 232 | } |
|---|
| 233 | switch (fmt) { |
|---|
| 234 | case 'rss': |
|---|
| 235 | playlistHandler(RSSParser.parse(dat)); |
|---|
| 236 | break; |
|---|
| 237 | case 'playlist': |
|---|
| 238 | playlistHandler(XSPFParser.parse(dat)); |
|---|
| 239 | break; |
|---|
| 240 | case 'asx': |
|---|
| 241 | playlistHandler(ASXParser.parse(dat)); |
|---|
| 242 | break; |
|---|
| 243 | case 'smil': |
|---|
| 244 | playlistHandler(SMILParser.parse(dat)); |
|---|
| 245 | break; |
|---|
| 246 | case 'feed': |
|---|
| 247 | playlistHandler(ATOMParser.parse(dat)); |
|---|
| 248 | break; |
|---|
| 249 | default: |
|---|
| 250 | dispatchEvent(new ControllerEvent(ControllerEvent.ERROR,{message:'Unknown playlist format: '+fmt})); |
|---|
| 251 | return; |
|---|
| 252 | } |
|---|
| 253 | }; |
|---|
| 254 | |
|---|
| 255 | |
|---|
| 256 | /** Update playlist item duration. **/ |
|---|
| 257 | private function metaHandler(evt:ModelEvent):void { |
|---|
| 258 | if(evt.data.duration && playlist[config['item']]['duration'] == 0) { |
|---|
| 259 | playlist[config['item']]['duration'] = evt.data.duration; |
|---|
| 260 | } |
|---|
| 261 | if(evt.data.width) { |
|---|
| 262 | playlist[config['item']]['width'] = evt.data.width; |
|---|
| 263 | playlist[config['item']]['height'] = evt.data.height; |
|---|
| 264 | } |
|---|
| 265 | }; |
|---|
| 266 | |
|---|
| 267 | |
|---|
| 268 | /** Save new state of the mute switch and send volume. **/ |
|---|
| 269 | private function muteHandler(evt:ViewEvent):void { |
|---|
| 270 | if(evt.data.state) { |
|---|
| 271 | if(evt.data.state == config['mute']) { |
|---|
| 272 | return; |
|---|
| 273 | } else { |
|---|
| 274 | config['mute'] = evt.data.state; |
|---|
| 275 | } |
|---|
| 276 | } else { |
|---|
| 277 | config['mute'] = !config['mute']; |
|---|
| 278 | } |
|---|
| 279 | Configger.saveCookie('mute',config['mute']); |
|---|
| 280 | dispatchEvent(new ControllerEvent(ControllerEvent.MUTE,{state:config['mute']})); |
|---|
| 281 | }; |
|---|
| 282 | |
|---|
| 283 | |
|---|
| 284 | /** Jump to the next item in the playlist. **/ |
|---|
| 285 | private function nextHandler(evt:ViewEvent=null):void { |
|---|
| 286 | if(playlist && config['shuffle'] == true) { |
|---|
| 287 | playItem(randomizer.pick()); |
|---|
| 288 | } else if (playlist && config['item'] == playlist.length-1) { |
|---|
| 289 | playItem(0); |
|---|
| 290 | } else if (playlist) { |
|---|
| 291 | playItem(config['item']+1); |
|---|
| 292 | } |
|---|
| 293 | }; |
|---|
| 294 | |
|---|
| 295 | |
|---|
| 296 | /** Change the playback state. **/ |
|---|
| 297 | private function playHandler(evt:ViewEvent):void { |
|---|
| 298 | if(playlist) { |
|---|
| 299 | if(evt.data.state != false && config['state'] == ModelStates.PAUSED) { |
|---|
| 300 | dispatchEvent(new ControllerEvent(ControllerEvent.PLAY,{state:true})); |
|---|
| 301 | } else if (evt.data.state != false && config['state'] == ModelStates.COMPLETED) { |
|---|
| 302 | dispatchEvent(new ControllerEvent(ControllerEvent.SEEK,{position:0})); |
|---|
| 303 | } else if(evt.data.state != false && config['state'] == ModelStates.IDLE) { |
|---|
| 304 | playItem(); |
|---|
| 305 | } else if (evt.data.state != true && |
|---|
| 306 | (config['state'] == ModelStates.PLAYING || config['state'] == ModelStates.BUFFERING)) { |
|---|
| 307 | dispatchEvent(new ControllerEvent(ControllerEvent.PLAY,{state:false})); |
|---|
| 308 | } |
|---|
| 309 | } |
|---|
| 310 | }; |
|---|
| 311 | |
|---|
| 312 | |
|---|
| 313 | /** Direct the model to play a new item. **/ |
|---|
| 314 | private function playItem(nbr:Number=undefined):void { |
|---|
| 315 | if(!isNaN(nbr)) { |
|---|
| 316 | config['item'] = nbr; |
|---|
| 317 | } |
|---|
| 318 | dispatchEvent(new ControllerEvent(ControllerEvent.ITEM,{index:config['item']})); |
|---|
| 319 | }; |
|---|
| 320 | |
|---|
| 321 | |
|---|
| 322 | /** Check new playlist for playeable files and setup randomizing/autostart. **/ |
|---|
| 323 | private function playlistHandler(ply:Array):void { |
|---|
| 324 | for(var i:Number = ply.length-1; i > -1; i--) { |
|---|
| 325 | if(!ply[i]['duration']) { ply[i]['duration'] = 0; } |
|---|
| 326 | if(!ply[i]['start']) { ply[i]['start'] = 0; } |
|---|
| 327 | if(!ply[i]['streamer']) { ply[i]['streamer'] = config['streamer']; } |
|---|
| 328 | if(config['replace']) { |
|---|
| 329 | var arr:Array = config['replace'].split('|'); |
|---|
| 330 | ply[i]['file'] = ply[i]['file'].replace(RegExp(arr[0]),arr[1]); |
|---|
| 331 | } |
|---|
| 332 | ply[i]['type'] = getModelType(ply[i],true); |
|---|
| 333 | if(!ply[i]['type']) {ply.splice(i,1);} |
|---|
| 334 | } |
|---|
| 335 | if(ply.length > 0) { |
|---|
| 336 | playlist = ply; |
|---|
| 337 | } else { |
|---|
| 338 | dispatchEvent(new ControllerEvent(ControllerEvent.ERROR,{message:'No valid filetypes found in this playlist'})); |
|---|
| 339 | return; |
|---|
| 340 | } |
|---|
| 341 | if(config['shuffle'] == true) { |
|---|
| 342 | randomizer = new Randomizer(playlist.length); |
|---|
| 343 | config['item'] = randomizer.pick(); |
|---|
| 344 | } else if (config['item'] > playlist.length) { |
|---|
| 345 | config['item'] = playlist.length-1; |
|---|
| 346 | } |
|---|
| 347 | dispatchEvent(new ControllerEvent(ControllerEvent.PLAYLIST,{playlist:playlist})); |
|---|
| 348 | if(config['autostart'] == true) { |
|---|
| 349 | playItem(); |
|---|
| 350 | } |
|---|
| 351 | }; |
|---|
| 352 | |
|---|
| 353 | |
|---|
| 354 | /** Jump to the previous item in the playlist. **/ |
|---|
| 355 | private function prevHandler(evt:ViewEvent):void { |
|---|
| 356 | if (config['item'] == 0) { |
|---|
| 357 | playItem(playlist.length-1); |
|---|
| 358 | } else { |
|---|
| 359 | playItem(config['item']-1); |
|---|
| 360 | } |
|---|
| 361 | }; |
|---|
| 362 | |
|---|
| 363 | |
|---|
| 364 | /** Manage a resizing of the stage. **/ |
|---|
| 365 | private function redrawHandler(evt:ViewEvent=null):void { |
|---|
| 366 | try { |
|---|
| 367 | var dps:String = skin.stage['displayState']; |
|---|
| 368 | } catch (err:Error) {} |
|---|
| 369 | if(dps == 'fullScreen' && config['resizing']) { |
|---|
| 370 | config['fullscreen'] = true; |
|---|
| 371 | sploader.layoutFullscreen(); |
|---|
| 372 | } else { |
|---|
| 373 | if(config['resizing']) { |
|---|
| 374 | config['width'] = skin.stage.stageWidth; |
|---|
| 375 | config['height'] = skin.stage.stageHeight; |
|---|
| 376 | } |
|---|
| 377 | config['fullscreen'] = false; |
|---|
| 378 | sploader.layoutNormal(); |
|---|
| 379 | } |
|---|
| 380 | dispatchEvent(new ControllerEvent(ControllerEvent.RESIZE,{ |
|---|
| 381 | fullscreen:config['fullscreen'],width:config['width'],height:config['height']})); |
|---|
| 382 | }; |
|---|
| 383 | |
|---|
| 384 | |
|---|
| 385 | /** Seek to a specific part in a mediafile. **/ |
|---|
| 386 | private function seekHandler(evt:ViewEvent):void { |
|---|
| 387 | if(config['state'] != ModelStates.IDLE && playlist[config['item']]['duration'] > 0) { |
|---|
| 388 | var pos:Number = evt.data.position; |
|---|
| 389 | if(pos < 1) { |
|---|
| 390 | pos = 0; |
|---|
| 391 | } else if (pos > playlist[config['item']]['duration']-1) { |
|---|
| 392 | pos = playlist[config['item']]['duration']-1; |
|---|
| 393 | } |
|---|
| 394 | dispatchEvent(new ControllerEvent(ControllerEvent.SEEK,{position:pos})); |
|---|
| 395 | } |
|---|
| 396 | }; |
|---|
| 397 | |
|---|
| 398 | |
|---|
| 399 | /** Stop all playback and buffering. **/ |
|---|
| 400 | private function stopHandler(evt:ViewEvent=undefined):void { |
|---|
| 401 | dispatchEvent(new ControllerEvent(ControllerEvent.STOP)); |
|---|
| 402 | }; |
|---|
| 403 | |
|---|
| 404 | |
|---|
| 405 | /** Manage playback state changes. **/ |
|---|
| 406 | private function stateHandler(evt:ModelEvent):void { |
|---|
| 407 | if(evt.data.newstate == ModelStates.COMPLETED) { |
|---|
| 408 | switch (config['repeat']) { |
|---|
| 409 | case 'single': |
|---|
| 410 | playHandler(new ViewEvent(ViewEvent.PLAY,{state:true})); |
|---|
| 411 | break; |
|---|
| 412 | case 'always': |
|---|
| 413 | if(playlist.length == 1) { |
|---|
| 414 | playHandler(new ViewEvent(ViewEvent.PLAY,{state:true})); |
|---|
| 415 | } else { |
|---|
| 416 | nextHandler(); |
|---|
| 417 | } |
|---|
| 418 | break; |
|---|
| 419 | case 'list': |
|---|
| 420 | if((config['shuffle'] == true && randomizer.length > 0) || |
|---|
| 421 | (config['shuffle'] == false && config['item'] < playlist.length-1)) { |
|---|
| 422 | nextHandler(); |
|---|
| 423 | } |
|---|
| 424 | break; |
|---|
| 425 | } |
|---|
| 426 | } |
|---|
| 427 | }; |
|---|
| 428 | |
|---|
| 429 | |
|---|
| 430 | /** Save new state of the mute switch and send volume. **/ |
|---|
| 431 | private function volumeHandler(evt:ViewEvent):void { |
|---|
| 432 | var vol:Number = evt.data.percentage; |
|---|
| 433 | if (vol < 1) { |
|---|
| 434 | muteHandler(new ViewEvent(ViewEvent.MUTE,{state:true})); |
|---|
| 435 | } else if (!isNaN(vol) && vol < 101) { |
|---|
| 436 | if(config['mute'] == true) { |
|---|
| 437 | muteHandler(new ViewEvent(ViewEvent.MUTE,{state:false})); |
|---|
| 438 | } |
|---|
| 439 | config['volume'] = vol; |
|---|
| 440 | Configger.saveCookie('volume',config['volume']); |
|---|
| 441 | dispatchEvent(new ControllerEvent(ControllerEvent.VOLUME,{percentage:vol})); |
|---|
| 442 | } |
|---|
| 443 | }; |
|---|
| 444 | |
|---|
| 445 | |
|---|
| 446 | } |
|---|
| 447 | |
|---|
| 448 | |
|---|
| 449 | } |
|---|