1/* 2 * jPlayer Plugin for jQuery JavaScript Library 3 * http://www.jplayer.org 4 * 5 * Copyright (c) 2009 - 2014 Happyworm Ltd 6 * Licensed under the MIT license. 7 * http://opensource.org/licenses/MIT 8 * 9 * Author: Robert M. Hall 10 * Date: 20th November 2014 11 * Based on JplayerMp4.as with modifications for rtmp 12 */ 13 14package happyworm.jPlayer 15{ 16 import flash.display.Sprite; 17 18 import flash.media.Video; 19 import flash.media.SoundTransform; 20 21 import flash.net.NetConnection; 22 import flash.net.NetStream; 23 import flash.net.Responder; 24 25 import flash.utils.Timer; 26 import flash.utils.getTimer; 27 28 import flash.events.NetStatusEvent; 29 import flash.events.SecurityErrorEvent; 30 import flash.events.TimerEvent; 31 import flash.events.ErrorEvent; 32 import flash.events.UncaughtErrorEvent; 33 import flash.utils.clearInterval; 34 import flash.utils.setInterval; 35 import happyworm.jPlayer.ConnectManager; 36 37 public class JplayerRtmp extends Sprite 38 { 39 40 public var myVideo:Video = new Video; 41 private var myConnection:NetConnection; 42 private var myStream:NetStream; 43 44 public var responder:Responder; 45 46 private var streamName:String; 47 48 private var connectString:Object; 49 50 private var firstTime:Boolean = true; 51 52 private var myTransform:SoundTransform = new SoundTransform ; 53 54 public var myStatus:JplayerStatus = new JplayerStatus ; 55 56 private var ConnMgr:ConnectManager=new ConnectManager(); 57 58 private var timeUpdateTimer:Timer = new Timer(250,0);// Matched to HTML event freq 59 private var progressTimer:Timer = new Timer(250,0);// Matched to HTML event freq 60 private var seekingTimer:Timer = new Timer(100,0);// Internal: How often seeking is checked to see if it is over. 61 62 private var startBuffer:Number = 3; 63 private var maxBuffer:Number = 12; 64 65 public function JplayerRtmp(volume:Number) 66 { 67 myConnection = new NetConnection ; 68 myConnection.client = this; 69 70 71 // Moved the netconnection negotiation into the ConnectManager.as class - not needed for initial connection 72 // may need to add eventHandler back in for errors only or just dispatch from the ConnectManager..revisit... 73 74 // myConnection.addEventListener(NetStatusEvent.NET_STATUS,netStatusHandler); 75 // myConnection.addEventListener(SecurityErrorEvent.SECURITY_ERROR,securityErrorHandler); 76 myVideo.smoothing = true; 77 this.addChild(myVideo); 78 79 timeUpdateTimer.addEventListener(TimerEvent.TIMER,timeUpdateHandler); 80 progressTimer.addEventListener(TimerEvent.TIMER,progressHandler); 81 seekingTimer.addEventListener(TimerEvent.TIMER,seekingHandler); 82 83 myStatus.volume = volume; 84 85 addEventListener(UncaughtErrorEvent.UNCAUGHT_ERROR, uncaughtErrorHandler); 86 87 88 } 89 90 91 92 private function uncaughtErrorHandler(event:UncaughtErrorEvent):void 93 { 94 trace("UNCAUGHT ERROR - try loading again"); 95 96 if (event.error is Error) 97 { 98 var error:Error = event.error as Error; 99 trace(error); 100 // do something with the error 101 } 102 else if (event.error is ErrorEvent) 103 { 104 var errorEvent:ErrorEvent = event.error as ErrorEvent; 105 // do something with the error 106 trace(errorEvent); 107 } 108 else 109 { 110 // a non-Error, non-ErrorEvent type was thrown and uncaught 111 } 112 load(); 113 } 114 115 116 117 private function progressUpdates(active:Boolean):void 118 { 119 if (active) 120 { 121 progressTimer.start(); 122 } 123 else 124 { 125 progressTimer.stop(); 126 } 127 } 128 129 private function progressHandler(e:TimerEvent):void 130 { 131 if (myStatus.isLoading) 132 { 133 if ((getLoadRatio() == 1)) 134 {// Close as can get to a loadComplete event since client.onPlayStatus only works with FMS 135 this.dispatchEvent(new JplayerEvent(JplayerEvent.DEBUG_MSG,myStatus,"progressHandler: loadComplete")); 136 myStatus.loaded(); 137 progressUpdates(false); 138 } 139 } 140 progressEvent(); 141 } 142 143 private function progressEvent():void 144 { 145 // temporarily disabled progress event dispatching - not really needed for rtmp 146 //this.dispatchEvent(new JplayerEvent(JplayerEvent.DEBUG_MSG,myStatus,"progressEvent:")); 147 updateStatusValues(); 148 this.dispatchEvent(new JplayerEvent(JplayerEvent.JPLAYER_PROGRESS,myStatus)); 149 } 150 151 private function timeUpdates(active:Boolean):void 152 { 153 if (active) 154 { 155 timeUpdateTimer.start(); 156 } 157 else 158 { 159 timeUpdateTimer.stop(); 160 } 161 } 162 163 private function timeUpdateHandler(e:TimerEvent):void 164 { 165 timeUpdateEvent(); 166 } 167 168 private function timeUpdateEvent():void 169 { 170 updateStatusValues(); 171 this.dispatchEvent(new JplayerEvent(JplayerEvent.JPLAYER_TIMEUPDATE,myStatus)); 172 } 173 private function seeking(active:Boolean):void 174 { 175 if (active) 176 { 177 if (! myStatus.isSeeking) 178 { 179 seekingEvent(); 180 } 181 seekingTimer.start(); 182 } 183 else 184 { 185 if (myStatus.isSeeking) 186 { 187 seekedEvent(); 188 } 189 seekingTimer.stop(); 190 } 191 } 192 private function seekingHandler(e:TimerEvent):void 193 { 194 if ((getSeekTimeRatio() <= getLoadRatio())) 195 { 196 seeking(false); 197 if (myStatus.playOnSeek) 198 { 199 myStatus.playOnSeek = false;// Capture the flag. 200 play(myStatus.pausePosition);// Must pass time or the seek time is never set. 201 } 202 else 203 { 204 pause(myStatus.pausePosition);// Must pass time or the stream.time is read. 205 } 206 } 207 else if (myStatus.metaDataReady && myStatus.pausePosition > myStatus.duration) 208 { 209 // Illegal seek time 210 seeking(false); 211 pause(0); 212 } 213 } 214 private function seekingEvent():void 215 { 216 myStatus.isSeeking = true; 217 updateStatusValues(); 218 this.dispatchEvent(new JplayerEvent(JplayerEvent.JPLAYER_SEEKING,myStatus)); 219 } 220 private function seekedEvent():void 221 { 222 myStatus.isSeeking = false; 223 updateStatusValues(); 224 this.dispatchEvent(new JplayerEvent(JplayerEvent.JPLAYER_SEEKED,myStatus)); 225 } 226 227 228 private function netStatusHandler(e:NetStatusEvent):void 229 { 230 trace(("netStatusHandler: " + e.info.code)); 231 //this.dispatchEvent(new JplayerEvent(JplayerEvent.DEBUG_MSG,myStatus,"netStatusHandler: '" + e.info.code + "'")); 232 //trace("BEFORE: bufferTime: "+myStream.bufferTime+" - bufferTimeMax: "+myStream.bufferTimeMax); 233 switch (e.info.code) 234 { 235 case "NetConnection.Connect.Success" : 236 // connectStream(); // This method did not do anything sensible anyway. 237 // Do not think this case occurs. This was for the static file connection. 238 // Which now seems to be handled by the Connection Manager. 239 break; 240 case "NetStream.Buffer.Full": 241 if(connectString.streamTYPE == "LIVE") { 242 myStream.bufferTime = startBuffer; 243 } else { 244 myStream.bufferTime = maxBuffer; 245 } 246 break; 247 case "NetStream.Buffer.Flush": 248 myStream.bufferTime = startBuffer; 249 break; 250 case "NetStream.Buffer.Empty": 251 myStream.bufferTime = startBuffer; 252 break; 253 case "NetStream.Seek.Notify": 254 myStream.bufferTime = startBuffer; 255 break; 256 case "NetStream.Play.Start" : 257 258 if (firstTime) { 259 firstTime = false; // Capture flag 260 261 myStatus.loading(); 262 this.dispatchEvent(new JplayerEvent(JplayerEvent.JPLAYER_LOADSTART,myStatus)); 263 264 // NB: With MP4 player both audio and video types get connected to myVideo. 265 // NB: Had believed it was important for the audio too, otherwise what plays it? 266 if(videoBinding) { 267 myVideo.attachNetStream(myStream); 268 } 269 270 setVolume(myStatus.volume); 271 272 // Really the progress event just needs to be generated once, and should probably happen before now. 273 progressUpdates(true); 274 275 // This is an ASSUMPTION! Made it so that the default GUI worked. 276 // Hence why this part should be refactored. 277 // Lots of commands sequences after setMedia would be corrupted by this assumption. 278 // Bascally, we assume that after a setMedia, you will play it. 279 // Doing setMedia and pause(15) cause the flag to be set incorrectly and the GUI locks up. 280 myStatus.isPlaying = true; // Should be handled elsewhere. 281 } 282 283 // Under RTMP, this event code occurs every time the media starts playing and when a new position is seeked to, even when paused. 284 285 // Since under RTMP the event behaviour is quite different, believe a refactor is best here. 286 // ie., Under RTMP we should be able to know a lot more info about the stream. 287 288 // See onMetaDataHandler() for other condition, since duration is vital. 289 // See onResult() response handler too. 290 // Appears to be some duplication between onMetaDataHandler() and onResult(), along with a race between them occuring. 291 292 break; 293 case "NetStream.Play.UnpublishNotify": 294 myStream.bufferTime = startBuffer; // was 3 295 case "NetStream.Play.Stop" : 296 myStream.bufferTime = startBuffer; // was 3 297 //this.dispatchEvent(new JplayerEvent(JplayerEvent.DEBUG_MSG,myStatus,"NetStream.Play.Stop: getDuration() - getCurrentTime() = " + (getDuration() - getCurrentTime()))); 298 299 // Check if media is at the end (or close) otherwise this was due to download bandwidth stopping playback. ie., Download is not fast enough. 300 if (Math.abs((getDuration() - getCurrentTime())) < 150) 301 {// Testing found 150ms worked best for M4A files, where playHead(99.9) caused a stuck state due to firing with ~116ms left to play. 302 //endedEvent(); 303 } 304 break; 305 case "NetStream.Seek.InvalidTime" : 306 // Used for capturing invalid set times and clicks on the end of the progress bar. 307 endedEvent(); 308 break; 309 case "NetStream.Play.StreamNotFound" : 310 myStatus.error(); 311 // Resets status except the src, and it sets srcError property.; 312 this.dispatchEvent(new JplayerEvent(JplayerEvent.JPLAYER_ERROR,myStatus)); 313 break; 314 } 315 //trace("AFTER: bufferTime: "+myStream.bufferTime+" - bufferTimeMax: "+myStream.bufferTimeMax); 316 // "NetStream.Seek.Notify" event code is not very useful. It occurs after every seek(t) command issued and does not appear to wait for the media to be ready. 317 } 318 private function endedEvent():void 319 { 320 trace("ENDED STREAM EVENT"); 321 var wasPlaying:Boolean = myStatus.isPlaying; 322 323 // timeUpdates(false); 324 // timeUpdateEvent(); 325 pause(0); 326 327 if (wasPlaying) 328 { 329 this.dispatchEvent(new JplayerEvent(JplayerEvent.JPLAYER_ENDED,myStatus)); 330 } 331 } 332 private function securityErrorHandler(event:SecurityErrorEvent):void 333 { 334 //this.dispatchEvent(new JplayerEvent(JplayerEvent.DEBUG_MSG,myStatus,"securityErrorHandler.")); 335 } 336 public function connectStream():void 337 { 338 trace("CONNECTING"); 339 //this.dispatchEvent(new JplayerEvent(JplayerEvent.DEBUG_MSG,myStatus,"connectStream.")); 340 //this.dispatchEvent(new JplayerEvent(JplayerEvent.JPLAYER_CANPLAY,myStatus)); 341 342 timeUpdates(true); 343 progressUpdates(true); 344 //myVideo.attachNetStream(myStream); 345 //setVolume(myStatus.volume); 346 } 347 348 private function onResult(result:Object):void 349 { 350 trace("OnResult EVENT FIRED!"); 351 myStatus.duration = parseFloat(result.toString()) * 1000; 352 trace((("The stream length is " + result) + " seconds")); 353 354 if(!myConnection.connected) { 355 load(); 356 } else { 357 //this.dispatchEvent(new JplayerEvent(JplayerEvent.JPLAYER_CANPLAY,myStatus,"Rockit!")); 358 359 //myStatus.loaded(); 360 //myStatus.isPlaying=true; 361 if (! myStatus.metaDataReady) 362 { 363 //this.dispatchEvent(new JplayerEvent(JplayerEvent.DEBUG_MSG,myStatus,"onMetaDataHandler: " + myStatus.duration)); 364 365 // Allow multiple onResult Handlers to affect size. As per PR #131 and #98. 366 // myStatus.metaDataReady = true; 367 368 /*var info:Object = new Object(); 369 info.duration=myStatus.duration 370 info.width=undefined; 371 info.height=undefined; 372 myStatus.metaData = info; 373 */ 374 if (myStatus.playOnLoad) 375 { 376 myStatus.playOnLoad = false;// Capture the flag 377 if (myStatus.pausePosition > 0) 378 {// Important for setMedia followed by play(time). 379 play(myStatus.pausePosition); 380 } 381 else 382 { 383 play();// Not always sending pausePosition avoids the extra seek(0) for a normal play() command. 384 } 385 386 } 387 else 388 { 389 pause(myStatus.pausePosition);// Always send the pausePosition. Important for setMedia() followed by pause(time). Deals with not reading stream.time with setMedia() and play() immediately followed by stop() or pause(0) 390 } 391 this.dispatchEvent(new JplayerEvent(JplayerEvent.JPLAYER_LOADEDMETADATA,myStatus)); 392 } 393 else 394 { 395 //this.dispatchEvent(new JplayerEvent(JplayerEvent.DEBUG_MSG,myStatus,"onMetaDataHandler: Already read (NO EFFECT)")); 396 } 397 398 myStream.play(streamName); 399 this.dispatchEvent(new JplayerEvent(JplayerEvent.JPLAYER_PLAY,myStatus)); 400 // timeUpdates(false); 401 } 402 403 } 404 405 private var overRideConnect:Boolean=false; 406 public function doneYet():void { 407 if(!myConnection.connected) { 408 // try again 409 ConnMgr.stopAll(true); 410 overRideConnect=true; 411 trace("Connected: "+myConnection.connected+" - "+myStatus.loadRequired()); 412 load(); 413 } 414 } 415 416 private var videoBinding:Boolean=false; 417 public function setFile(src:String,videoSupport:Boolean=false):void 418 { 419 // videoSupport turns on/off video - by default no video, audio only 420 videoBinding=videoSupport; 421 /* Dont close the stream or netconnection here anymore so we can recycle if host/appname are the same 422 if ((myStream != null)) 423 { 424 myStream.close(); 425 myConnection.close(); 426 } 427 */ 428 if(ConnMgr.getNegotiating() == true) { 429 //ConnMgr.stopAll(); 430 ConnMgr.setNegotiating(false); 431 } 432 433 myVideo.clear(); 434 435 progressUpdates(false); 436 timeUpdates(false); 437 438 myStatus.reset(); 439 myStatus.src = src; 440 myStatus.srcSet = true; 441 442 firstTime = true; 443 444 //myStatus.loaded(); 445 446 if(src != "") { 447 this.dispatchEvent(new JplayerEvent(JplayerEvent.JPLAYER_CANPLAY,myStatus)); 448 } 449 450 //timeUpdateEvent(); 451 } 452 453 public function shutDownNcNs():void { 454 trace("Connections Closed"); 455 timeUpdates(false); 456 progressUpdates(false); 457 myStream.close(); 458 myConnection.close(); 459 460 myStatus.reset(); 461 this.dispatchEvent(new JplayerEvent(JplayerEvent.JPLAYER_ENDED,myStatus)); 462 } 463 464 public function clearFile():void 465 { 466 if (myStream != null) 467 { 468 myStream.close(); 469 // Dont close the netConnection here any longer, as we may recycle it later 470 // may need an extra way to close manually if switching media types after an rtmp session - revisit 471 // myConnection.close(); 472 myStatus.reset(); 473 } 474 setFile(""); 475 myStatus.srcSet = false; 476 } 477 478 public function parseRTMPsrcConnect(rtmpSrc:String):Object 479 { 480 // rtmp://cp76372.live.edgefcs.net/live/Flash1Office@60204 481 var appName:String = ""; 482 var streamFileName:String = ""; 483 var startIndex:uint = 2 + rtmpSrc.indexOf("//"); 484 var streamTYPE:String = "recorded"; 485 var host:String = rtmpSrc.substr(startIndex); 486 var port:String = ""; 487 host = host.substr(0,host.indexOf("/")); 488 var endHost:Number = startIndex + host.length + 1; 489 490 // See if there is a host port specified 491 if(host.indexOf(":") != -1) { 492 port = host.substr(host.indexOf(":")+1); 493 host = host.substr(0,host.indexOf(":")); 494 } 495 496 // Akamai specific live streams 497 if (rtmpSrc.lastIndexOf("/live/") != -1) 498 { 499 trace("LIVE!"); 500 501 502 appName = rtmpSrc.substring(endHost,rtmpSrc.lastIndexOf("/live/") + 6); 503 streamFileName = rtmpSrc.substr(rtmpSrc.lastIndexOf("/live/") + 6); 504 streamTYPE="LIVE"; 505 } else { 506 streamTYPE="RECORDED"; 507 508 } 509 510 // Mp3 streams with standard appname/no instance name, mp3: prefix 511 if (rtmpSrc.indexOf("mp3:") != -1) { 512 appName = rtmpSrc.substring(endHost,rtmpSrc.indexOf("mp3:")); 513 streamFileName = rtmpSrc.substr(rtmpSrc.indexOf("mp3:")); 514 if ( streamFileName.indexOf("?") != -1 ) { 515 var tmp:String = streamFileName.substring(streamFileName.indexOf("?")) ; 516 streamFileName = streamFileName.substr(0,streamFileName.indexOf("?")) + encodeURI(tmp) ; 517 } else { 518 streamFileName = streamFileName.substr(0,streamFileName.length - 4); 519 } 520 } 521 // rtmp://cp83813.edgefcs.net/ondemand/rob_hall/bruce_campbell_oldspice.flv 522 523 // Mp4 streams with standard appname/no instance name, mp4: prefix 524 if (rtmpSrc.indexOf("mp4:") != -1) { 525 appName = rtmpSrc.substring(endHost,rtmpSrc.indexOf("mp4:")); 526 streamFileName = rtmpSrc.substr(rtmpSrc.indexOf("mp4:")); 527 if ( streamFileName.indexOf("?") != -1 ) { 528 var tmpV:String = streamFileName.substring(streamFileName.indexOf("?")) ; 529 streamFileName = streamFileName.substr(0,streamFileName.indexOf("?")) + encodeURI(tmpV) ; 530 } else { 531 streamFileName = streamFileName.substr(0,streamFileName.length - 4); 532 } 533 } 534 535 // .f4v streams with standard appname/no instance name, .flv extension 536 if (rtmpSrc.indexOf(".flv") != -1) 537 { 538 // allow use of ^ in rtmp string to indicate break point for an appname or instance name that 539 // contains a / in it where it would require multiple connection attempts or manual configuratiom 540 // of the appname/instancename 541 var endApp:Number=0; 542 if(rtmpSrc.indexOf("^") != -1) { 543 endApp=rtmpSrc.indexOf("^"); 544 rtmpSrc.replace("^", "/"); 545 } else { 546 endApp = rtmpSrc.indexOf("/",endHost); 547 } 548 appName = rtmpSrc.substring(endHost,endApp) + "/"; 549 streamFileName = rtmpSrc.substr(endApp+1); 550 } 551 552 if(port=="") { 553 port="MULTI"; 554 } 555 //rtmp, rtmpt, rtmps, rtmpe, rtmpte 556 557 558 trace(("\n\n*** HOST: " + host)); 559 trace(("*** PORT: " + port)); 560 trace(("*** APPNAME: " + appName)); 561 trace(("*** StreamName: " + streamFileName)); 562 563 var streamParts:Object = new Object; 564 streamParts.streamTYPE=streamTYPE; 565 streamParts.appName = appName; 566 streamParts.streamFileName = streamFileName; 567 streamParts.hostName = host; 568 streamName = streamFileName; 569 570 return streamParts; 571 } 572 573 public function load():Boolean 574 { 575 //trace("LOAD: "+myStatus.src); 576 if (myStatus.loadRequired() || overRideConnect==true) 577 { 578 overRideConnect=false; 579 myStatus.startingDownload(); 580 var lastAppName:String; 581 var lastHostName:String; 582 583 try{ 584 // we do a try, as these properties might not exist yet 585 if(connectString.appName != "" && connectString.appName != undefined) { 586 trace("PREVIOUS APP/HOST INFO AVAILABLE"); 587 lastAppName = connectString.appName; 588 lastHostName = connectString.hostName; 589 trace("LAST: "+lastAppName,lastHostName); 590 } 591 } catch (error:Error) { 592 //trace("*** Caught an error condition: "+error); 593 } 594 595 connectString = parseRTMPsrcConnect(myStatus.src); 596 597 598 599 trace("**** LOAD :: CONNECT SOURCE: " +connectString.hostName +" "+ connectString.appName); 600 this.dispatchEvent(new JplayerEvent(JplayerEvent.JPLAYER_WAITING, myStatus)); 601 602 if((connectString.appName == lastAppName && connectString.hostName == lastHostName) && (myConnection.connected)) { 603 // recycle the netConnection 604 trace("RECYCLING NETCONNECTION"); 605 if ((myStream != null)) 606 { 607 myStream.close(); 608 } 609 connectStream(); 610 onBWDone(null,myConnection); 611 } else { 612 // myConnection.connect(connectString.appName); 613 trace("NEW NetConnection Negotiation"); 614 if ((myStream != null)) 615 { 616 myStream.close(); 617 myConnection.close(); 618 } 619 620 ConnMgr.stopAll(true); 621 ConnMgr.negotiateConnect(this,connectString.hostName,connectString.appName); 622 } 623 624 trace("**** LOAD2 :: CONNECT SOURCE: " +connectString.hostName +" "+ connectString.appName); 625 this.dispatchEvent(new JplayerEvent(JplayerEvent.JPLAYER_WAITING, myStatus)); 626 return true; 627 } 628 else 629 { 630 return false; 631 } 632 } 633 634 635 636 public function onFCUnsubscribe(info:Object):void 637 { 638 trace(("onFCUnSubscribe worked" + info)); 639 } 640 641 public function onFCSubscribe(info:Object):void 642 { 643 trace(("onFCSubscribe worked" + info)); 644 } 645 646 public function onBWDone(info:Object,nc:NetConnection):void 647 { 648 if(nc.connected) { 649 myConnection=nc; 650 trace(((("onBWDone " + info) + " :: ") + myStatus.src)); 651 652 var customClient:Object = new Object ; 653 customClient.onMetaData = onMetaDataHandler; 654 customClient.onPlayStatus = onPlayStatusHandler;// According to the forums and my tests, onPlayStatus only works with FMS (Flash Media Server). 655 656 myStream = null; 657 myStream = new NetStream(myConnection); 658 myStream.addEventListener(NetStatusEvent.NET_STATUS,netStatusHandler); 659 myStream.client = customClient; 660 if(connectString.streamTYPE == "LIVE") { 661 myStream.bufferTime = 3; // was 3 662 myStream.bufferTimeMax = 24; 663 startBuffer = 3; 664 maxBuffer = 12; 665 666 } else { 667 myStream.bufferTime = .2; // was 3 668 myStream.bufferTimeMax = 0; 669 startBuffer = .2; 670 maxBuffer = 12; 671 } 672 673 674 //streamName=""; 675 //var connectString:Object = parseRTMPsrcConnect(myStatus.src); 676 //streamName=connectString.streamFileName; 677 678 responder = new Responder(onResult); 679 myConnection.call("getStreamLength",responder,streamName); 680 } else { 681 connectStream(); 682 } 683 684 trace("PLAY SOURCE: "+connectString); 685 686 } 687 688 public function play(time:Number = NaN):Boolean { 689 //trace("PLAY: "+time+" - isPlaying: "+myStatus.isPlaying +" - myStatus.isStartingDownload:"+myStatus.isStartingDownload); 690 var wasPlaying:Boolean = myStatus.isPlaying; 691 692 if(!isNaN(time) && myStatus.srcSet) { 693 if(myStatus.isPlaying) { 694 myStream.pause(); 695 myStatus.isPlaying = false; 696 } 697 myStatus.pausePosition = time; 698 } 699 700 if(myStatus.isStartingDownload) { 701 myStatus.playOnLoad = true; // Raise flag, captured in onMetaDataHandler() 702 return true; 703 } else if(myStatus.loadRequired()) { 704 myStatus.playOnLoad = true; // Raise flag, captured in onMetaDataHandler() 705 return load(); 706 } else if((myStatus.isLoading || myStatus.isLoaded) && !myStatus.isPlaying) { 707 if(myStatus.metaDataReady && myStatus.pausePosition > myStatus.duration && connectString.streamTYPE != "LIVE") { // The time is invalid, ie., past the end. 708 myStream.pause(); // Since it is playing by default at this point. 709 myStatus.pausePosition = 0; 710 trace("SEEKER!"); 711 myStream.seek(0); 712 timeUpdates(false); 713 timeUpdateEvent(); 714 if(wasPlaying) { // For when playing and then get a play(huge) 715 this.dispatchEvent(new JplayerEvent(JplayerEvent.JPLAYER_PAUSE, myStatus)); 716 } 717 } else if(getSeekTimeRatio() > getLoadRatio()) { // Use an estimate based on the downloaded amount 718 myStatus.playOnSeek = true; 719 seeking(true); 720 trace("SEEKER PAUSE!"); 721 myStream.pause(); // Since it is playing by default at this point. 722 } else { 723 if(!isNaN(time)) { // Avoid using seek() when it is already correct. 724 trace("SEEKER3"); 725 myStream.seek(myStatus.pausePosition/1000); // Since time is in ms and seek() takes seconds 726 } 727 myStatus.isPlaying = true; // Set immediately before playing. Could affects events. 728 trace("SHOULD GET RESUME!"); 729 myStream.resume(); 730 timeUpdates(true); 731 if(!wasPlaying) { 732 this.dispatchEvent(new JplayerEvent(JplayerEvent.JPLAYER_PLAY, myStatus)); 733 } 734 } 735 return true; 736 } else { 737 return false; 738 } 739 } 740 741 public function pause(time:Number=NaN):Boolean 742 { 743 //trace("PAUSE: "+time); 744 myStatus.playOnLoad = false;// Reset flag in case load/play issued immediately before this command, ie., before onMetadata() event. 745 myStatus.playOnSeek = false;// Reset flag in case play(time) issued before the command and is still seeking to time set. 746 747 var wasPlaying:Boolean = myStatus.isPlaying; 748 749 750 // To avoid possible loops with timeupdate and pause(time). A pause() does not have the problem. 751 var alreadyPausedAtTime:Boolean = false; 752 if(!isNaN(time) && myStatus.pausePosition == time) { 753 alreadyPausedAtTime = true; 754 } 755 756 trace("!isNaN: "+!isNaN(time) +" isNaN: "+isNaN(time)); 757 758 // Need to wait for metadata to load before ever issuing a pause. The metadata handler will call this function if needed, when ready. 759 if (((myStream != null) && myStatus.metaDataReady)) 760 {// myStream is a null until the 1st media is loaded. ie., The 1st ever setMedia being followed by a pause() or pause(t). 761 762 if(connectString.streamTYPE == "LIVE") { 763 trace("PAUSING LIVE"); 764 myStream.play(false) 765 } else { 766 trace("PAUSING RECORDED"); 767 myStream.pause(); 768 } 769 } 770 if (myStatus.isPlaying) 771 { 772 myStatus.isPlaying = false; 773 myStatus.pausePosition = myStream.time * 1000; 774 } 775 776 if (! isNaN(time) && myStatus.srcSet) 777 { 778 myStatus.pausePosition = time; 779 } 780 781 if (wasPlaying) 782 { 783 this.dispatchEvent(new JplayerEvent(JplayerEvent.JPLAYER_PAUSE,myStatus)); 784 } 785 786 if (myStatus.isStartingDownload) 787 { 788 return true; 789 } 790 else if (myStatus.loadRequired()) 791 { 792 if ((time > 0)) 793 {// We do not want the stop() command, which does pause(0), causing a load operation. 794 return load(); 795 } 796 else 797 { 798 return true;// Technically the pause(0) succeeded. ie., It did nothing, since nothing was required. 799 } 800 } 801 else if (myStatus.isLoading || myStatus.isLoaded) 802 { 803 if (myStatus.metaDataReady && myStatus.pausePosition > myStatus.duration && connectString.streamTYPE != "LIVE" ) 804 {// The time is invalid, ie., past the end. 805 myStatus.pausePosition = 0; 806 807 trace("GOT HERE!"); 808 myStream.seek(0); 809 seekedEvent();// Deals with seeking effect when using setMedia() then pause(huge). NB: There is no preceeding seeking event. 810 } 811 else if (! isNaN(time)) 812 { 813 if ((getSeekTimeRatio() > getLoadRatio())) 814 {// Use an estimate based on the downloaded amount 815 seeking(true); 816 } 817 else 818 { 819 if (myStatus.metaDataReady && connectString.streamTYPE != "LIVE") 820 {// Otherwise seek(0) will stop the metadata loading. 821 trace("GOT HERE TOO!"); 822 myStream.seek(myStatus.pausePosition / 1000); 823 } 824 } 825 } 826 timeUpdates(false); 827 // Need to be careful with timeupdate event, otherwise a pause in a timeupdate event can cause a loop. 828 // Neither pause() nor pause(time) will cause a timeupdate loop. 829 if(wasPlaying || !isNaN(time) && !alreadyPausedAtTime) { 830 timeUpdateEvent(); 831 } 832 return true; 833 } 834 else 835 { 836 return false; 837 } 838 } 839 public function playHead(percent:Number):Boolean 840 { 841 var time:Number = percent * getDuration() * getLoadRatio() / 100; 842 if (myStatus.isPlaying || myStatus.playOnLoad || myStatus.playOnSeek) 843 { 844 return play(time); 845 } 846 else 847 { 848 return pause(time); 849 } 850 } 851 public function setVolume(v:Number):void 852 { 853 myStatus.volume = v; 854 myTransform.volume = v; 855 if ((myStream != null)) 856 { 857 myStream.soundTransform = myTransform; 858 } 859 } 860 private function updateStatusValues():void 861 { 862 //myStatus.seekPercent = 100 * getLoadRatio(); 863 myStatus.seekPercent = 100; 864 myStatus.currentTime = getCurrentTime(); 865 myStatus.currentPercentRelative = 100 * getCurrentRatioRel(); 866 myStatus.currentPercentAbsolute = 100 * getCurrentRatioAbs(); 867 myStatus.duration = getDuration(); 868 } 869 public function getLoadRatio():Number 870 { 871 return 1; 872 /*trace("LoadRatio:"+myStream.bytesLoaded, myStream.bytesTotal); 873 if((myStatus.isLoading || myStatus.isLoaded) && myStream.bytesTotal > 0) { 874 return myStream.bytesLoaded / myStream.bytesTotal; 875 } else if (myStatus.isLoaded && myStream.bytesLoaded > 0) { 876 return 1; 877 } else { 878 return 0; 879 } 880 */ 881 882 } 883 public function getDuration():Number 884 { 885 return myStatus.duration;// Set from meta data. 886 } 887 public function getCurrentTime():Number 888 { 889 if (myStatus.isPlaying) 890 { 891 //trace(myStream.time * 1000); 892 return myStream.time * 1000; // was +1000 893 } 894 else 895 { 896 return myStatus.pausePosition; 897 } 898 } 899 public function getCurrentRatioRel():Number 900 { 901 902 if ((getCurrentRatioAbs() <= getLoadRatio())) 903 { 904 //if((getLoadRatio() > 0) && (getCurrentRatioAbs() <= getLoadRatio())) { 905 return getCurrentRatioAbs() / getLoadRatio(); 906 } 907 else 908 { 909 return 0; 910 } 911 } 912 public function getCurrentRatioAbs():Number 913 { 914 if ((getDuration() > 0)) 915 { 916 return getCurrentTime() / getDuration(); 917 } 918 else 919 { 920 return 0; 921 } 922 } 923 public function getSeekTimeRatio():Number 924 { 925 if ((getDuration() > 0)) 926 { 927 return myStatus.pausePosition / getDuration(); 928 } 929 else 930 { 931 return 1; 932 } 933 } 934 public function onPlayStatusHandler(infoObject:Object):void 935 { 936 trace((("OnPlayStatusHandler called: (" + getTimer()) + " ms)")); 937 for (var prop:* in infoObject) 938 { 939 trace(((("\t" + prop) + ":\t") + infoObject[prop])); 940 } 941 if (infoObject.code == "NetStream.Play.Complete") 942 { 943 endedEvent(); 944 } 945 } 946 947 public function onMetaDataHandler(info:Object):void 948 {// Used in connectStream() in myStream.client object. 949 // This event occurs when jumping to the start of static files! ie., seek(0) will cause this event to occur. 950 951 if (! myStatus.metaDataReady) 952 { 953 trace("\n\n*** METADATA FIRED! ***\n\n"); 954 //this.dispatchEvent(new JplayerEvent(JplayerEvent.DEBUG_MSG,myStatus,"onMetaDataHandler: " + info.duration + " | " + info.width + "x" + info.height)); 955 956 myStatus.metaDataReady = true;// Set flag so that this event only effects jPlayer the 1st time. 957 myStatus.metaData = info; 958 myStatus.duration = info.duration * 1000;// Only available via Meta Data. 959 if (info.width != undefined) 960 { 961 myVideo.width = myStatus.videoWidth = info.width; 962 } 963 if (info.height != undefined) 964 { 965 myVideo.height = myStatus.videoHeight = info.height; 966 } 967 968 if (myStatus.playOnLoad) 969 { 970 myStatus.playOnLoad = false;// Capture the flag 971 if (myStatus.pausePosition > 0) 972 {// Important for setMedia followed by play(time). 973 play(myStatus.pausePosition); 974 } 975 else 976 { 977 play();// Not always sending pausePosition avoids the extra seek(0) for a normal play() command. 978 } 979 } 980 else 981 { 982 pause(myStatus.pausePosition);// Always send the pausePosition. Important for setMedia() followed by pause(time). Deals with not reading stream.time with setMedia() and play() immediately followed by stop() or pause(0) 983 } 984 this.dispatchEvent(new JplayerEvent(JplayerEvent.JPLAYER_LOADEDMETADATA,myStatus)); 985 } 986 else 987 { 988 //this.dispatchEvent(new JplayerEvent(JplayerEvent.DEBUG_MSG,myStatus,"onMetaDataHandler: Already read (NO EFFECT)")); 989 } 990 } 991} 992} 993