[Laszlo-checkins] r11035 - in openlaszlo/trunk: WEB-INF/lps/lfc/kernel/swf9 WEB-INF/lps/lfc/views examples/music

bargull@openlaszlo.org bargull at openlaszlo.org
Wed Sep 17 06:45:17 PDT 2008


Author: bargull
Date: 2008-09-17 06:45:08 -0700 (Wed, 17 Sep 2008)
New Revision: 11035

Added:
   openlaszlo/trunk/WEB-INF/lps/lfc/kernel/swf9/LzAsset.as
Modified:
   openlaszlo/trunk/WEB-INF/lps/lfc/kernel/swf9/Library.lzs
   openlaszlo/trunk/WEB-INF/lps/lfc/kernel/swf9/LzAudioKernel.lzs
   openlaszlo/trunk/WEB-INF/lps/lfc/kernel/swf9/LzSprite.as
   openlaszlo/trunk/WEB-INF/lps/lfc/views/LaszloView.lzs
   openlaszlo/trunk/examples/music/music.lzx
   openlaszlo/trunk/examples/music/music.mp3
Log:
Change 20080915-bargull-3QU by bargull at dell--p4--2-53 on 2008-09-15 19:28:40
    in /home/Admin/src/svn/openlaszlo/trunk
    for http://svn.openlaszlo.org/openlaszlo/trunk

Summary: implement swf9 audio

New Features: LPP-6983

Bugs Fixed:

Technical Reviewer: max
QA Reviewer: promanik
Doc Reviewer: (pending)

Documentation:

Release Notes:

Details:
implement sprite audio capability, LzAudioKernel still needs some tweaks for global sound. 
LzSprite: 
 - set "resource" after calling "unload()" otherwise you'll set "resource" to null
 - set "mouseEnabled" to true for the canvas sprite, so top-level context-menus will work (LPP-6980)
 - update "setDefaultContextMenu"
 - all other changes concern the audio implementation
Changed "music.lzx" to load mp3 unproxied for swf8, so we can read the ID3-Tag.
Needed to re-tag "music.mp3" because Flash requires UTF-8 ID3-Tags.
    

Tests:
examples/music/music.lzx?lzr=swf9 works



Modified: openlaszlo/trunk/WEB-INF/lps/lfc/kernel/swf9/Library.lzs
===================================================================
--- openlaszlo/trunk/WEB-INF/lps/lfc/kernel/swf9/Library.lzs	2008-09-17 13:33:12 UTC (rev 11034)
+++ openlaszlo/trunk/WEB-INF/lps/lfc/kernel/swf9/Library.lzs	2008-09-17 13:45:08 UTC (rev 11035)
@@ -37,6 +37,7 @@
 #include "kernel/swf9/LzScreenKernel.as"
 #include "kernel/swf9/LzContextMenuKernel.lzs"
 #include "kernel/swf9/LzAudioKernel.lzs"
+#include "kernel/swf9/LzAsset.as"
     //#include "kernel/swf9/dojo/Library.lzs"
 
 #include "kernel/swf9/DojoExternalInterface.as"

Added: openlaszlo/trunk/WEB-INF/lps/lfc/kernel/swf9/LzAsset.as


Property changes on: openlaszlo/trunk/WEB-INF/lps/lfc/kernel/swf9/LzAsset.as
___________________________________________________________________
Name: svn:mime-type
   + text/plain
Name: svn:eol-style
   + native

Modified: openlaszlo/trunk/WEB-INF/lps/lfc/kernel/swf9/LzAudioKernel.lzs
===================================================================
--- openlaszlo/trunk/WEB-INF/lps/lfc/kernel/swf9/LzAudioKernel.lzs	2008-09-17 13:33:12 UTC (rev 11034)
+++ openlaszlo/trunk/WEB-INF/lps/lfc/kernel/swf9/LzAudioKernel.lzs	2008-09-17 13:45:08 UTC (rev 11035)
@@ -13,66 +13,98 @@
   * @access private
   * 
   */
-class LzAudioKernel{
-/*    
-{
-#pragma "passThrough=true"
-LzAudioKernel.globalSound = new Sound();
+class LzAudioKernel {
+    #passthrough (toplevel:true) {
+      import flash.display.Sprite;
+      import flash.media.Sound;
+      import flash.media.SoundChannel;
+      import flash.media.SoundMixer;
+      import flash.media.SoundTransform;
+    }#
+    
+    /**
+      * Sets the current sound resource and starts playing it. 
+      * @param String snd: Name of a sound resource to play
+      * @param LzSprite t: Sprite for sound to act upon (optional)
+      */
+    static function playSound (snd:String, t:LzSprite = null){
+        if (t == null) {
+            if (LzAsset.isSoundAsset(snd)) {
+                //TODO: check for swf8 compatibility
+                var sound:Sound = new (LzResourceLibrary[snd])['assetclass']() cast Sound;
+                sound.play();
+            }
+        } else {
+            t.setResource(snd);
+        }
+    }
+    
+    /**
+      * Stop playing the current sound
+      * @param LzSprite t: Sprite for sound to act upon (optional)
+      */
+    static function stopSound (t:LzSprite = null) :void {
+        if (t == null) {
+            //TODO: check for swf8 compatibility
+            SoundMixer.stopAll();
+        } else {
+            t.stop();
+        }
+    }
+    
+    /**
+      * Start playing the current sound
+      * */
+    static function startSound (t:Sprite = null) {
+        //TODO: check for swf8 compatibility
+    }
+    
+    /**
+      * @access private
+      */
+    static function getSoundObject (t:LzSprite) :* {
+        return t ? t.soundChannel ? t.soundChannel : t : SoundMixer;
+    }
+    
+    /**
+      * Get the global volume
+      * @return Number: volume from 0 to 100 (0 is silent).
+      * @param LzSprite t: Sprite for sound to act upon (optional)
+      */
+    static function getVolume (t:LzSprite = null) :Number {
+        return getSoundObject(t).soundTransform.volume * 100;
+    }
+    
+    /**
+      * Set the global volume.
+      * @param Number v: linear volume from 0 to 100 (0 is silent).
+      * @param LzSprite t: Sprite for sound to act upon (optional)
+      */
+    static function setVolume (v:Number, t:LzSprite = null) :void {
+        var soundObj:* = getSoundObject(t);
+        var sndTransform:SoundTransform = soundObj.soundTransform;
+        sndTransform.volume = (v < 0 ? 0 : v > 100 ? 100 : v) / 100;
+        soundObj.soundTransform = sndTransform;
+    }
+    
+    /**
+      * Get the global pan.
+      * @return Number: linear pan from -100 to +100 (left to right)
+      * @param LzSprite t: Sprite for sound to act upon (optional)
+      */
+    static function getPan (t:LzSprite = null) :Number {
+        return getSoundObject(t).soundTransform.pan * 100;
+    }
+    
+    /**
+      * Set the global pan.
+      * @param Number p: linear pan from -100 to +100 (left to right)
+      * @param LzSprite t: Sprite for sound to act upon (optional)
+      */
+    static function setPan (p:Number, t:LzSprite = null) :void {
+        var soundObj:* = getSoundObject(t);
+        var sndTransform:SoundTransform = soundObj.soundTransform;
+        sndTransform.pan = (p < -100 ? -100 : p > 100 ? 100 : p) / 100;
+        soundObj.soundTransform = sndTransform;
+    }
 }
-LzAudioKernel.globalSound.$xxx = "globsnd" ;
-*/
-
-/**
-  * Sets the current sound resource and starts playing it. 
-  * @param String snd: Name of a sound resource to play
-  */
-static function playSound( snd , ...t ){
-}
-
-/**
-  * Stop playing the current sound
-  * */
-static function stopSound( ...t ){
-}
-
-/**
-  * Start playing the current sound
-  * */
-static function startSound(...t) {
-}
-
-/**
-  * @access private
-  */
-static function getSoundObject(...t) {
-}
-
-/**
-  * Get the global volume
-  * @param Object t: MovieClip for sound to act upon
-  * @return Number: volume from 0 to 100 (0 is silent).
-  */
-static function getVolume(...t) {
-}
-
-/**
-  * Set the global volume.
-  * @param Number v: linear volume from 0 to 100 (0 is silent).
-  */
-static function setVolume(v, ...t) {
-}
-
-/**
-  * Get the global pan.
-  * @return Number: linear pan from -100 to +100 (left to right)
-  */
-static function getPan(...t) {
-}
-
-/**
-  * Set the global pan.
-  * @param Number p: linear pan from -100 to +100 (left to right)
-  */
-static function setPan(p, ...t) {
-}
-}

Modified: openlaszlo/trunk/WEB-INF/lps/lfc/kernel/swf9/LzSprite.as
===================================================================
--- openlaszlo/trunk/WEB-INF/lps/lfc/kernel/swf9/LzSprite.as	2008-09-17 13:33:12 UTC (rev 11034)
+++ openlaszlo/trunk/WEB-INF/lps/lfc/kernel/swf9/LzSprite.as	2008-09-17 13:45:08 UTC (rev 11035)
@@ -19,6 +19,12 @@
   import flash.utils.*;
   import mx.controls.Button;
   import flash.net.URLRequest;
+  import flash.media.Sound;
+  import flash.media.SoundChannel;
+  import flash.media.SoundMixer;
+  import flash.media.SoundTransform;
+  import flash.media.SoundLoaderContext;
+  import flash.media.ID3Info;
 }#
 
 #passthrough  {
@@ -58,7 +64,13 @@
       var resourceCache:Array = null;
 
       public var resourceLoaded:Boolean = false;
-
+      
+      /* private */ static const soundLoaderContext:SoundLoaderContext = new SoundLoaderContext(1000, true);
+      /* private */ static const MP3_FPS:Number = 30;
+      /* private */ var sound:Sound = null;
+      /* private */ var soundChannel:SoundChannel = null;
+      /* private */ var soundLoading:Boolean = false;
+      
       //@field Boolean _setrescwidth: If true, the view does not set its
       //resource to the width given in a call to
       //<method>setAttribute</method>. By default, views do not scale their
@@ -97,8 +109,10 @@
           if (owner == null) return;
           if (isroot) {
               this.isroot = true;
+              this.mouseEnabled = true;// @devnote: see LPP-6980
+          } else {
+              this.mouseEnabled = false;
           }
-          this.mouseEnabled = false;
       }
 
       public function init (v:Boolean = true):void {
@@ -152,7 +166,7 @@
       */
       public function setResource (r:String):void {
           if (this.resource == r) return;
-          if ( r.indexOf('http:') == 0 || r.indexOf('https:') == 0){
+          if (r.indexOf('http:') == 0 || r.indexOf('https:') == 0) {
               this.skiponload = false;
               this.setSource( r );
               return;
@@ -168,33 +182,56 @@
           // frames: ["lps/components/lz/resources/focus/focus_bot_rt_shdw.png"], width: 9, height: 9};
 
 
-          //Debug.write('setting resource', r);  
-          this.resource = r;
+          //Debug.write('setting resource', r);
 
-          var res = LzResourceLibrary[r];
+          var res:Object = LzResourceLibrary[r];
           if (! res) {
               if ($debug) {
                   Debug.warn('Could not find resource', r);
               }
               return;
           }
-
-          this.resourcewidth = res.width;
-          this.resourceheight = res.height;
-          if (this.owner != null) {
-              this.owner.resourceevent('totalframes', res.frames.length);
+          
+          if (LzAsset.isBitmapAsset(r) 
+                || LzAsset.isMovieClipAsset(r) 
+                || LzAsset.isMovieClipLoaderAsset(r)) {
+              this.resourcewidth = res.width;
+              this.resourceheight = res.height;
+              if (this.owner != null) {
+                  this.owner.resourceevent('totalframes', res.frames.length);
+              }
+              if (imgLoader) {
+                  this.unload();
+              } else if (this.isaudio) {
+                  // unload previous sound-resource
+                  this.unloadSound();
+              }
+              
+              if (this.resourceObj == null) {
+                  this.createResourceBitmap()  
+              }
+              
+              this.resource = r;
+              // instantiate resource at frame 1
+              this.stop(1);
+              // send events, but skip onload
+              sendResourceLoad(true);
+          } else if (LzAsset.isSoundAsset(r)) {
+              // unload previous image-resource and sound-resource
+              this.unload();
+              this.resource = r;
+              
+              this.sound = new res['assetclass']() as Sound;
+              this.owner.resourceevent('totalframes', Math.floor(this.sound.length * 0.001 * MP3_FPS));
+              
+              // TODO: add condition on this
+              this.startPlay()
+              
+              // send events, but skip onload
+              this.sendResourceLoad(true);
+          } else if ($debug) {
+              Debug.warn('Unhandled asset: ', LzAsset.getAssetType(r) + " " + r);
           }
-          if (imgLoader) {
-              this.unload();   
-          }
-          if (this.resourceObj == null) {
-              this.createResourceBitmap()  
-          }
-
-          // instantiate resource at frame 1
-          this.stop(1);
-          // send events, but skip onload
-          sendResourceLoad(true);
       }
 
 
@@ -205,31 +242,54 @@
           o Loads and displays media from the specified url
           o Uses the resourceload callback method when the resource finishes loading 
       */
-      public function setSource (url:String, cache = null, headers = null, filetype = null):void {
-         if (url == null || url == 'null') {
-             return;
-         }
-
-          this.resource = url;
-          if (! imgLoader) {
-            if (this.resourceObj) this.unload();
-            imgLoader = new Loader();
-            this.resourceObj = imgLoader;
-            this.addChildAt(imgLoader, IMGDEPTH);
-            var info:LoaderInfo = imgLoader.contentLoaderInfo;
-            info.addEventListener(Event.INIT, loaderInitHandler);
-            info.addEventListener(IOErrorEvent.IO_ERROR, loaderEventHandler);
+      public function setSource (url:String, cache:String = null, headers:String = null, filetype:String = null) :void {
+          if (url == null || url == 'null') {
+              return;
+          }
+          
+          if (getFileType(url, filetype) == "mp3") {
+              // unload previous image-resource and sound-resource
+              this.unload();
+              this.resource = url;
+              this.loadSound(url);
           } else {
-            //TODO [20080911 anba] cancel current load?
-            // imgLoader.close();
+              if (this.isaudio) {
+                  // unload previous sound-resource
+                  this.unloadSound();
+              }
+            
+              if (! imgLoader) {
+                  if (this.resourceObj) {
+                      this.unload();
+                  }
+                  imgLoader = new Loader();
+                  this.resourceObj = imgLoader;
+                  this.addChildAt(imgLoader, IMGDEPTH);
+                  var info:LoaderInfo = imgLoader.contentLoaderInfo;
+                  info.addEventListener(Event.INIT, loaderInitHandler);
+                  info.addEventListener(IOErrorEvent.IO_ERROR, loaderEventHandler);
+              } else {
+                  //TODO [20080911 anba] cancel current load?
+                  // imgLoader.close();
+              }
+              
+              this.resource = url;
+              var res = this.resourceObj;
+              if (res) {
+                  res.scaleX = res.scaleY = 1.0;
+              }
+              //Debug.write('sprite setsource load ', url);
+              imgLoader.load(new URLRequest(url));
           }
-
-          var res = this.resourceObj;
-          if (res) {
-            res.scaleX = res.scaleY = 1.0;
+      }
+      
+      private function getFileType (url:String, filetype:String = null) :String {
+          if (filetype != null) {
+              return filetype.toLowerCase();
+          } else {
+              var si = url.lastIndexOf(".");
+              return si != -1 ? url.substring(si + 1).toLowerCase() : null;
           }
-          //Debug.write('sprite setsource load ', url);
-          imgLoader.load(new URLRequest(url));
       }
 
       public function loaderInitHandler(event:Event):void {
@@ -277,7 +337,7 @@
                   }
               } else if (event.type == ProgressEvent.PROGRESS) {
                   var ev:ProgressEvent = event as ProgressEvent;
-                  var lr = ev.bytesLoaded / ev.bytesTotal;
+                  var lr:Number = ev.bytesLoaded / ev.bytesTotal;
                   if (! isNaN(lr)) {
                       this.owner.resourceevent('loadratio', lr);
                   }
@@ -290,8 +350,169 @@
           }
       }
       
+      /** 
+        * <code>true</code> if a sound is attached to this sprite.
+        */
+      public function get isaudio () :Boolean {
+          return this.sound != null;
+      }
+      
+      /** 
+        * Load/Stream a sound from an URL.
+        */
+      private function loadSound (url:String) :void {
+          this.sound = new Sound();
+          this.sound.addEventListener(Event.OPEN, soundLoadHandler);
+          this.sound.addEventListener(Event.COMPLETE, soundLoadHandler);
+          this.sound.addEventListener(ProgressEvent.PROGRESS, soundLoadHandler);
+          this.sound.addEventListener(IOErrorEvent.IO_ERROR, soundLoadHandler);
+          
+          this.sound.load(new URLRequest(url), LzSprite.soundLoaderContext);
+          
+          // TODO: add condition on this
+          this.startPlay();
+      }
+      
+      /** 
+        * Stop current playback and unload sound
+        */
+      private function unloadSound () :void {
+          if (this.playing) {
+              // stop playing
+              this.stopPlay();
+          }
+          if (this.sound) {
+              if (this.soundLoading) {
+                  // stop streaming sound
+                  this.sound.close();
+                  this.soundLoading = false;
+              }
+              this.sound = null;
+          }
+      }
+      
+      /** 
+        * Start sound playback and tracking
+        * @param Number frame: frame/secs to start at playing
+        * @param Boolean isFrame: if set to false, treat 'frame' as seconds
+        */
+      private function startPlay (frame:Number = 0, isFrame:Boolean = true) :void {
+          var pos:Number = (isFrame ? (frame / MP3_FPS) : frame) * 1000;
+          
+          this.playing = true;
+          this.owner.playing = true;
+          this.soundChannel = this.sound.play(pos, 0, this.soundTransform);
+          this.addEventListener(Event.ENTER_FRAME, soundFrameHandler);
+          this.soundChannel.addEventListener(Event.SOUND_COMPLETE, soundCompleteHandler);
+      }
+      
+      /** 
+        * Stop sound playback and tracking
+        * @return Number: the current frame when playback was stopped
+        */
+      private function stopPlay () :Number {
+          var frame:Number = Math.floor(this.soundChannel.position * 0.001 * MP3_FPS);
+          
+          this.playing = false;
+          this.owner.playing = false;
+          this.removeEventListener(Event.ENTER_FRAME, soundFrameHandler);
+          this.soundChannel.stop();
+          this.soundChannel = null;
+          
+          return frame;
+      }
+      
+      /** 
+        * Update play status
+        */
+      private function updatePlay (play:Boolean, framenumber:*, rel:Boolean) :void {
+          var fr:Number;
+          if (this.playing) {
+              // stop previous playback
+              fr = this.stopPlay();
+          } else {
+              // TODO: this.frame is initialized with 1, which
+              // means we currently skip 33ms at the beginning
+              fr = this.frame;
+          }
+          
+          if (framenumber != null) {
+              framenumber += rel ? fr : 0;
+          } else {
+              framenumber = fr;
+          }
+          
+          if (play) {
+              this.startPlay(framenumber);
+          } else {
+              this.frame = framenumber;
+              this.owner.resourceevent('frame', framenumber);
+          }
+      }
+      
+      /** 
+        * Progress sound loading
+        */
+      private function soundLoadHandler (event:Event) :void {
+          try {
+              if (event.type == Event.OPEN) {
+                  this.soundLoading = true;
+                  this.owner.resourceevent('loadratio', 0);
+              } else if (event.type == Event.COMPLETE) {
+                  this.soundLoading = false;
+                  this.owner.resourceevent('loadratio', 1);
+                  this.owner.resourceevent('totalframes', Math.floor(this.sound.length * 0.001 * MP3_FPS));
+                  
+                  // send events, including onload
+                  this.sendResourceLoad();
+              } else if (event.type == ProgressEvent.PROGRESS) {
+                  var ev:ProgressEvent = event as ProgressEvent;
+                  var lr:Number = ev.bytesLoaded / ev.bytesTotal;
+                  if (! isNaN(lr)) {
+                      this.owner.resourceevent('loadratio', lr);
+                  }
+              } else if (event.type == IOErrorEvent.IO_ERROR) {
+                  this.soundLoading = false;
+                  this.owner.resourceevent('loadratio', 0);
+                  this.owner.resourceloaderror( (event as IOErrorEvent).text );
+              }
+          } catch (error:Error) {
+              trace(event.type + " " + error);
+          }
+      }
+      
+      /** 
+        * Track playback
+        */
+      private function soundFrameHandler (event:Event = null) :void {
+          // Event.ENTER_FRAME
+          var fr:Number = Math.floor(this.soundChannel.position * 0.001 * MP3_FPS);
+          this.frame = fr;
+          this.owner.resourceevent('frame', fr);
+          
+          var tfr:Number = Math.floor(this.sound.length * 0.001 * MP3_FPS);
+          this.owner.resourceevent('totalframes', tfr);
+      }
+      
+      /** 
+        * Sound complete
+        */
+      private function soundCompleteHandler (event:Event) :void {
+          // Event.SOUND_COMPLETE
+          if (this.playing) {
+              // call manually to update 'frame'
+              this.soundFrameHandler();
+              // SoundChannel.position does not stop exactly at Sound.length, 
+              // there are a few ms difference between both values. 
+              // So instead of comparing 'frame' == 'totalframes', 
+              // we'll send the 'lastframe'-event when playback stopped.
+              this.owner.resourceevent('lastframe', null, true);
+              this.stopPlay();
+          }
+      }
+      
       //// Mouse event trampoline
-      public function attachMouseEvents(dobj:DisplayObject) {
+      public function attachMouseEvents(dobj:DisplayObject) :void {
           dobj.addEventListener(MouseEvent.CLICK, __mouseEvent, false);
           dobj.addEventListener(MouseEvent.DOUBLE_CLICK, handleMouse_DOUBLE_CLICK, false);
           dobj.addEventListener(MouseEvent.MOUSE_DOWN, __mouseEvent, false);
@@ -300,7 +521,7 @@
           dobj.addEventListener(MouseEvent.MOUSE_OUT, __mouseEvent, false);
       }
 
-      public function removeMouseEvents(dobj:DisplayObject) {
+      public function removeMouseEvents(dobj:DisplayObject) :void {
           dobj.removeEventListener(MouseEvent.CLICK, __mouseEvent, false);
           dobj.removeEventListener(MouseEvent.DOUBLE_CLICK, handleMouse_DOUBLE_CLICK, false);
           dobj.removeEventListener(MouseEvent.MOUSE_DOWN, __mouseEvent, false);
@@ -309,13 +530,13 @@
           dobj.removeEventListener(MouseEvent.MOUSE_OUT, __mouseEvent, false);
       }
 
-      public function handleMouse_DOUBLE_CLICK (event:MouseEvent) {
+      public function handleMouse_DOUBLE_CLICK (event:MouseEvent) :void {
           LzMouseKernel.__sendEvent( owner, 'ondblclick');
           event.stopPropagation();
       }
 
       // called by LzMouseKernel when mouse goes up on another sprite
-      public function __globalmouseup( e:MouseEvent ){
+      public function __globalmouseup( e:MouseEvent ) :void {
           if (this.__mousedown) {
               this.__mouseEvent(e);
               this.__mouseEvent(new MouseEvent('mouseupoutside'));
@@ -323,7 +544,7 @@
           LzMouseKernel.__lastMouseDown = null;
       }
 
-      public function __mouseEvent( e:MouseEvent ){
+      public function __mouseEvent( e:MouseEvent ) :void {
             var skipevent = false;
             var eventname = 'on' + e.type.toLowerCase();
 
@@ -381,7 +602,7 @@
           this.clickable = c;
           this.buttonMode = c;
           this.tabEnabled = false;
-          this.mouseEnabled = c;
+          this.mouseEnabled = c || this.isroot;// @devnote: see LPP-6980
           attachMouseEvents(this);
           var cb:SimpleButton = this.clickbutton;
           //trace('sprite setClickable' , c, 'cb',cb);
@@ -461,7 +682,6 @@
       }
 
 
-
       /** setWidth( Number:width )
           o Sets the sprite to the specified width 
       */
@@ -569,9 +789,16 @@
           o Plays a multiframe resource starting at the specified framenumber
           o Plays from the current frame if framenumber is null 
       */
-      public function play( framenumber:Number = 1,  rel:Boolean = false ):void {
-          // TODO [hqm 2008-04] what to do about playing movies? 
-          stop(framenumber);
+      public function play (framenumber:* = null, rel:Boolean = false) :void {
+          if (! this.isaudio) {
+              // TODO [hqm 2008-04] what to do about playing movies? 
+              stop(framenumber);
+          } else {
+              // audio-resource is attached
+              this.updatePlay(true, framenumber, rel);
+              
+              this.owner.resourceevent('play', null, true);
+          }
       }
 
 
@@ -579,57 +806,69 @@
           o Stops a multiframe resource at the specified framenumber
           o Stops at the current frame if framenumber is null 
       */
-      public function stop( fn:Number = 1, rel:Boolean = false ):void {
-          if (this.resource == null || imgLoader) {
-              return;
-          }
-          var resinfo = LzResourceLibrary[this.resource];
-
-          // Frames are one based not zero based
-          var frames = resinfo.frames;
-          if (fn > frames.length) {
-            fn = frames.length
-          }
-          this.frame = fn;
-          var framenumber = fn - 1;
-
-          var assetclass;
-          // single frame resources get an entry in LzResourceLibrary which has
-          // 'assetclass' pointing to the resource Class object.
-          if (resinfo.assetclass is Class) {
-              assetclass = resinfo.assetclass;
+      public function stop (fn:* = null, rel:Boolean = false) :void {
+          if (! this.isaudio) {
+              if (this.resource == null || imgLoader) {
+                  return;
+              }
+              
+              var resinfo = LzResourceLibrary[this.resource];
+              
+              // Frames are one based not zero based
+              var frames = resinfo.frames;
+              if (fn == null) {
+                  fn = 1;
+              }
+              if (fn > frames.length) {
+                  fn = frames.length;
+              }
+              this.frame = fn;
+              var framenumber = fn - 1;
+              
+              var assetclass;
+              // single frame resources get an entry in LzResourceLibrary which has
+              // 'assetclass' pointing to the resource Class object.
+              if (resinfo.assetclass is Class) {
+                  assetclass = resinfo.assetclass;
+              } else {
+                  // Multiframe resources have an array of Class objects in frames[]
+                  assetclass = frames[framenumber];
+              }
+              
+              if (! assetclass) return;
+              if (this.resourceCache == null) {
+                  this.resourceCache = [];
+              }
+              var asset:DisplayObject = this.resourceCache[framenumber];
+              if (asset == null) {
+                  //Debug.write('CACHE MISS, new ',assetclass);
+                  asset = new assetclass();
+                  asset.scaleX = 1.0
+                  asset.scaleY = 1.0;
+                  this.resourceCache[framenumber] = asset;
+              }
+              
+              var oRect:Rectangle = asset.getBounds( asset );
+              if (oRect.width == 0 || oRect.height == 0) {
+                // it can take a while for new resources to show up.  Call back until we have a valid size.
+                setTimeout(this.__resetframe, 50);
+                return;
+              }
+              
+              var res = this.resourceObj; 
+              var rect = new Rectangle(0, 0, this.resourcewidth, this.resourceheight);
+              res.bitmapData.fillRect(rect, 0x00000000);
+              copyBitmap(asset, this.resourcewidth, this.resourceheight, res.bitmapData);
+              //Debug.write('set resource to', asset, oRect); 
+              
+              this.applyStretchResource();
           } else {
-              // Multiframe resources have an array of Class objects in frames[]
-              assetclass = frames[framenumber];
+              // audio-resource is attached
+              var p:Boolean = this.playing;
+              this.updatePlay(false, fn, rel);
+              
+              if (p) this.owner.resourceevent('stop', null, true);
           }
-
-          if (! assetclass) return;
-          if (this.resourceCache == null) {
-              this.resourceCache = [];
-          }
-          var asset:DisplayObject = this.resourceCache[framenumber];
-          if (asset == null) {
-              //Debug.write('CACHE MISS, new ',assetclass);
-              asset = new assetclass();
-              asset.scaleX = 1.0
-              asset.scaleY = 1.0;
-              this.resourceCache[framenumber] = asset;
-          }
-
-          var oRect:Rectangle = asset.getBounds( asset );
-          if (oRect.width == 0 || oRect.height == 0) {
-            // it can take a while for new resources to show up.  Call back until we have a valid size.
-            setTimeout(this.__resetframe, 50);
-            return;
-          }
-
-          var res = this.resourceObj; 
-          var rect = new Rectangle(0, 0, this.resourcewidth, this.resourceheight);
-          res.bitmapData.fillRect(rect, 0x00000000);
-          copyBitmap(asset, this.resourcewidth, this.resourceheight, res.bitmapData);
-          //Debug.write('set resource to', asset, oRect); 
-
-          this.applyStretchResource();
       }
 
       public function __resetframe():void {
@@ -745,9 +984,10 @@
           o if recursive is true, the sprite destroys all its children as well 
       */
       public function destroy( ):void {
-    //PBR
-    if (parent)
-          parent.removeChild(this);
+          //PBR
+          if (parent) {
+              parent.removeChild(this);
+          }
       }
 
 
@@ -834,10 +1074,11 @@
 
       public function unload() {
         if (this.resourceObj) this.removeChild(this.resourceObj);
+        if (this.isaudio) this.unloadSound();
         // clear out cached values
         this.lastreswidth = this.lastresheight = this.resourcewidth = this.resourceheight = 0;
         this.resource = null;
-        imgLoader = null;
+        this.imgLoader = null;
         this.resourceObj = null;
       }
 
@@ -871,11 +1112,10 @@
        * Install menu items for the right-mouse-button 
        * @param LzContextMenu cmenu: LzContextMenu to install on this view
        */
-      function  setContextMenu ( lzmenu ){
+      function setContextMenu ( lzmenu ){
           if (lzmenu == null) {
               this.__contextmenu = null;
           } else {
-              // For back compatibility, we accept either LzContextMenu or (Flash primitive) ContextMenu
               this.__contextmenu = lzmenu;
               var cmenu:ContextMenu = lzmenu.kernel.__LZcontextMenu();
 
@@ -883,6 +1123,8 @@
               // where it checks for a resource or bgcolor sprite, in order to make the clickable region
               // match what the user expects.
 
+              // TODO: [20080914 anba] blocked by LPP-6980
+
               // "contextMenu" is a swf9 property on flash.display.Sprite
               this.contextMenu = cmenu;
           }
@@ -890,9 +1132,10 @@
 
       function setDefaultContextMenu ( cmenu ){
           if (cmenu != null) {
-// LPP-5868
-// Generates: Error #2071: The Stage class does not implement this property or method
-//              LFCApplication.stage.contextMenu = cmenu.kernel.__LZcontextMenu();
+              // even though Stage extends flash.display.InterativeObject, you cannot attach 
+              // a context-menu to it, instead we attach the context-menu to application-sprite
+              // LFCApplication.stage.contextMenu = cmenu.kernel.__LZcontextMenu();
+              LFCApplication._sprite.contextMenu = cmenu.kernel.__LZcontextMenu();
           }
       }
 
@@ -948,42 +1191,97 @@
           LzMouseKernel.restoreCursorLocal();
       }
 
-      function setVolume (v) {
-          trace('setVolume not currently implemented in swf9.');
+      function setVolume (v:Number) :void {
+          LzAudioKernel.setVolume(v, this);
       }
 
-      function getVolume () {
-          trace('getVolume not currently implemented in swf9.');
+      function getVolume () :Number {
+          return LzAudioKernel.getVolume(this);
       }
 
-      function setPan (v) {
-          trace('setPan not currently implemented in swf9.');
+      function setPan (p:Number) :void {
+          LzAudioKernel.setPan(p, this);
       }
 
-      function getPan () {
-          trace('getPan not currently implemented in swf9.');
+      function getPan () :Number {
+          return LzAudioKernel.getPan(this);
       }
+      
+      /** 
+        * @param Number secs: 
+        * @param Boolean playing: 
+        */
+      function seek (secs:Number, doplay:Boolean) :void {
+          if (this.isaudio) {
+              var pos:Number = Math.max(this.getCurrentTime() + secs, 0);
+              if (this.playing) {
+                  this.stopPlay();
+              }
+              if (doplay) {
+                  this.startPlay(pos, false);
+              } else {
+                  var fr:Number = Math.floor(pos * MP3_FPS);
+                  this.frame = fr;
+                  this.owner.resourceevent('frame', fr);
+              }
+          }
+      }
+      
+      /** 
+        * @return Number: time elapsed (in seconds)
+        */
+      function getCurrentTime () :Number {
+          if (this.isaudio) {
+              if (this.playing) {
+                  // use SoundChannel if possible, it is more accurate
+                  return this.soundChannel.position * 0.001;
+              } else {
+                  return (this.frame / MP3_FPS);
+              }
+          } else {
+              return 0;
+          }
+      }
+      
+      /** 
+        * @return Number: length of the current sound (in seconds)
+        */
+      function getTotalTime () :Number {
+          return this.isaudio ? this.sound.length * 0.001 : 0;
+      }
+      
+      /** 
+        * @return ID3Info: id3-info of the current sound
+        */
+      function getID3 () :ID3Info {
+          return this.isaudio ? this.sound.id3 : null;
+      }
 
       /**
-         
-       */
+        *
+        */
       function setShowHandCursor ( s:* ){
           this.showhandcursor = s;
       }
 
       function setAAActive(s) {
+          trace('LzSprite.setAAActive not yet implemented');
       }
 
       function setAAName(s) {
+          trace('LzSprite.setAAName not yet implemented');
       }
 
       function setAADescription(s) {
+          trace('LzSprite.setAADescription not yet implemented');
       }
 
       function setAATabIndex(s) {
+          trace('LzSprite.setAATabIndex not yet implemented');
       }
 
       function setAASilent(s) {
+          trace('LzSprite.setAASilent not yet implemented');
       }
 
       function updateResourceSize(skipsend = null){

Modified: openlaszlo/trunk/WEB-INF/lps/lfc/views/LaszloView.lzs
===================================================================
--- openlaszlo/trunk/WEB-INF/lps/lfc/views/LaszloView.lzs	2008-09-17 13:33:12 UTC (rev 11034)
+++ openlaszlo/trunk/WEB-INF/lps/lfc/views/LaszloView.lzs	2008-09-17 13:45:08 UTC (rev 11035)
@@ -2714,7 +2714,11 @@
   * 
   * @param String source: The URL from which to load the resource for this view.
   * @param String cache: If set, controls caching behavior. Choices are
-  * <code>none</code> , <code>clientonly</code> , <code>serveronly</code> , <code>both</code> (the default for Flash).  DHTML applications cache media on the client only.  DHTML supports the <code>memorycache</code> option which enables in-memory resource caching.  This enhances performance when swapping resources quickly, and is used internally for multi-frame resources.  Media loaded with <code>memorycache</code> will remain in memory until the page is unloaded or unload() is called.
+  * <code>none</code> , <code>clientonly</code> , <code>serveronly</code> , <code>both</code> (the default for Flash).  
+  * DHTML applications cache media on the client only. DHTML supports the <code>memorycache</code> option which 
+  * enables in-memory resource caching. This enhances performance when swapping resources quickly, and is used 
+  * internally for multi-frame resources. Media loaded with <code>memorycache</code> will remain in memory until 
+  * the page is unloaded or unload() is called.
   * @param String headers: Headers to send with the request, if any.
   * @param String filetype: Filetype, e.g. 'mp3' or 'jpg'.  If not specified, it will be derived from the URL.
   */
@@ -2781,7 +2785,7 @@
 
 
 /** @access private */
-function $lzc$set_play(b) {
+function $lzc$set_play (b:Boolean) :void {
     if ( b ) {
         this.play();
     } else {
@@ -2794,7 +2798,7 @@
   * @param Boolean b: If true, starts playing, otherwise stops
   * @deprecated Use setAttribute('play', ...) instead.
   */
-function setPlay (b){
+function setPlay (b:Boolean) :void {
     if ($debug) Debug.deprecated(this, arguments.callee, this.setAttribute);
     this.$lzc$set_play(b);
 }
@@ -2803,9 +2807,8 @@
   * Get a reference to the control mc - may be overridden by loader
   * @access private
   */
-function getMCRef () {
+function getMCRef () :* {
     return this.sprite.getMCRef();
-    //return this.__LZmovieClipRef;
 }
 
 /**
@@ -2819,7 +2822,7 @@
   * begin playing at the current frame.
   * @param Boolean rel: If true, f is relative to the current frame.  Otherwise f is relative to the beginning of the resource.
   */
-function play (f = null, rel = null){
+function play (f/*:Number?*/ = null, rel:Boolean = false) :void {
     this.sprite.play(f, rel);
 }
 
@@ -2829,7 +2832,7 @@
   * stop at the current frame.
   * @param Boolean rel: If true, f is relative to the current frame.  Otherwise it is relative to the start position of the resource.
   */
-function stop (f = null, rel = null){
+function stop (f/*:Number?*/ = null, rel:Boolean = false) :void {
     this.sprite.stop(f, rel);
 }
 
@@ -2837,7 +2840,7 @@
   * Set the volume of the attached resource
   * @param Integer v: A number from 0 to 100 representing a volume level
   */
-function setVolume (v) {
+function setVolume (v:Number) :void {
     if (this.capabilities.audio) {
         this.sprite.setVolume(v);
     } else if ($debug) {
@@ -2849,7 +2852,7 @@
   * Get the volume of the attached resource
   * @return Integer: A number from 0 to 100 representing a volume level
   */
-function getVolume () {
+function getVolume () :Number {
     if (this.capabilities.audio) {
         return this.sprite.getVolume();
     } else if ($debug) {
@@ -2861,7 +2864,7 @@
   * Set the pan of the attached resource
   * @param Integer p: A number from -100 to 100 representing a pan level
   */
-function setPan (p) {
+function setPan (p:Number) :void {
     if (this.capabilities.audio) {
         this.sprite.setPan(p);
     } else if ($debug) {
@@ -2873,7 +2876,7 @@
   * Get the pan of the attached resource
   * @return Integer: A number from -100 to 100 representing a pan level
   */
-function getPan () {
+function getPan () :Number {
     if (this.capabilities.audio) {
         return this.sprite.getPan();
     } else if ($debug) {
@@ -2897,16 +2900,16 @@
   * 
   * @param Integer secs: Number of seconds to skip forward or backward (if negative)
   */
-function seek ( secs ){
-    var m = this.getMCRef();
+function seek (secs:Number) :void {
+    var m:* = this.getMCRef();
     if (m.isaudio == true) {
         m.seek(secs, this.playing)
     } else {
-        var f = secs*canvas.framerate;
-        if ( this.playing ) {
-            this.play( f , true);
+        var f:Number = secs * canvas.framerate;
+        if (this.playing) {
+            this.play(f, true);
         } else {
-            this.stop( f , true);
+            this.stop(f, true);
         }
     }
 }
@@ -2916,8 +2919,8 @@
   * @return Number: The number of seconds of media between the current frame and the
   * first frame
   */
-function getCurrentTime ( ){
-    var m = this.getMCRef();
+function getCurrentTime () :Number {
+    var m:* = this.getMCRef();
     if (m.isaudio == true) {
         return m.getCurrentTime();
     } else {
@@ -2926,8 +2929,8 @@
 }
 
 /** @access private */
-function $lzc$getCurrentTime_dependencies ( who, self ){
-    return [ self , "frame" ];
+function $lzc$getCurrentTime_dependencies ( who, self ) :Array {
+    return [ self, "frame" ];
 }
 
 
@@ -2935,8 +2938,8 @@
   * Returns the total amount of time the resource would take to play.
   * @return Number: Seconds of media controlled by this view.
   */
-function getTotalTime ( ){
-    var m = this.getMCRef();
+function getTotalTime () :Number {
+    var m:* = this.getMCRef();
     if (m.isaudio == true) {
         return m.getTotalTime();
     } else {
@@ -2944,8 +2947,8 @@
     }
 }
 /** @access private */
-function $lzc$getTotalTime_dependencies ( who, self ){
-    return [ self , "load" ];
+function $lzc$getTotalTime_dependencies ( who, self ) :Array {
+    return [ self, "load" ];
 }
 
 /**
@@ -2953,8 +2956,8 @@
   * with proxy == false;
   * @return Object: Object containind id3 tag data, if available.
   */
-function getID3 ( ){
-    var m = this.getMCRef();
+function getID3 () :Object {
+    var m:* = this.getMCRef();
     if (m.isaudio == true) {
         return m.getID3();
     }

Modified: openlaszlo/trunk/examples/music/music.lzx
===================================================================
--- openlaszlo/trunk/examples/music/music.lzx	2008-09-17 13:33:12 UTC (rev 11034)
+++ openlaszlo/trunk/examples/music/music.lzx	2008-09-17 13:45:08 UTC (rev 11035)
@@ -14,61 +14,83 @@
 
 <canvas bgcolor="#EAEAEA" width="640" height="140" >
     
-    <view id="audioplayer" play="true" y="40"
-        resource="http:music.mp3"
-        oninit="new LzDelegate( this, 'lastoff', this, 'onlastframe'); 
-                new LzDelegate( this, 'stopped', this, 'onstop')">
-        <attribute name="vol" value="100" />
+    <view id="audioplayer" play="true" y="40" >
+        
+        <handler name="oninit" >
+            if ($swf8) {
+                // load mp3 unproxied, so we can get the ID3-Tag
+                this.setProxyPolicy(function (url) {return false;});
+            }
+            this.setAttribute("resource", "http:music.mp3");
+        </handler>
+        
+        <handler name="onload" >
+            var info:String;
+            var id3:Object = this.getID3();
+            if (!! id3) {
+                info = "Song: " + id3.TIT2 + "\nArtist: " + id3.TPE1 + "\nAlbum: " + id3.TALB;
+            } else {
+                info = "no id3 info available"
+            }
+            this.id3info.setAttribute("text", info );
+        </handler>
+        
+        <handler name="onlastframe" >
+            Debug.write("Got last");
+        </handler>
+        
+        <handler name="onstop" >
+            Debug.write("Got stopped");
+        </handler>
+        
+        <simplelayout axis="x" spacing="10"/>
+        
         <view>
             <simplelayout axis="x" spacing="-1" />
             <button width="40" onclick="audioplayer.play(1);" >
-               <view resource="icons/rewind_all.png"  y="6" align="center"/>
+               <view resource="icons/rewind_all.png" y="7" align="center"/>
             </button>
-            <button width="40" onclick="audioplayer.seek( -2 );" >
-                <view resource="icons/rewind.png"  y="6" align="center"/>
+            <button width="40" onclick="audioplayer.seek(-2);" >
+                <view resource="icons/rewind.png" y="7" align="center"/>
             </button>        
             <button width="40" onclick="audioplayer.stop()" >
-                <view resource="icons/stop.png"  y="6" align="center"/>
+                <view resource="icons/stop.png" y="7" align="center"/>
             </button>
             <button width="40" onclick="audioplayer.play()" >
-                <view resource="icons/play.png"  y="5" align="center"/>
+                <view resource="icons/play.png" y="6" align="center"/>
             </button>
-            <button width="40" onclick="audioplayer.seek( 2 );" >
-                <view resource="icons/fastfwd.png"  y="6" align="center"/>
+            <button width="40" onclick="audioplayer.seek(2);" >
+                <view resource="icons/fastfwd.png" y="7" align="center"/>
             </button>
         </view>
         
         <view>
             <simplelayout axis="x" spacing="-1" />
-            <button width="40" onclick="audioplayer.setVolume( audioplayer.getVolume() -10)">
-                    <view resource="icons/quieter.png"  y="5" align="center"/>
-                </button>
-            <button width="40" onclick="audioplayer.setVolume( audioplayer.getVolume() +10)">
-                    <view resource="icons/louder.png"  y="5" align="center"/>
-                </button>
-            <button width="40" onclick="audioplayer.setPan( audioplayer.getPan() - 10 );" >
-                    <view resource="icons/pan_left.png"  y="5" align="center"/>
+            <button width="40" onclick="audioplayer.setVolume(audioplayer.getVolume() - 10)">
+                <view resource="icons/quieter.png" y="7" align="center"/>
             </button>
-            <button width="40" onclick="audioplayer.setPan( audioplayer.getPan() + 10 );" >
-                    <view resource="icons/pan_right.png"  y="5" align="center"/>
+            <button width="40" onclick="audioplayer.setVolume(audioplayer.getVolume() + 10)">
+                <view resource="icons/louder.png" y="7" align="center"/>
             </button>
+            <button width="40" onclick="audioplayer.setPan(audioplayer.getPan() - 10);" >
+                <view resource="icons/pan_left.png" y="7" align="center"/>
+            </button>
+            <button width="40" onclick="audioplayer.setPan(audioplayer.getPan() + 10);" >
+                <view resource="icons/pan_right.png" y="7" align="center"/>
+            </button>
         </view>
         
-        <simplelayout axis="x" spacing="10"/>
-        <method name="lastoff" args="v">
-            Debug.write("Got last");
-        </method>
-        <method name="stopped" args="v">
-            Debug.write("Got stopped");
-        </method>
+        <text name="id3info" y="-10" height="80" width="200"/>
+        
         <view name="background" bgcolor="black" width="202" height="15" y="50" options="ignorelayout;">
             <view name="loadbar" resource="icons/audio_scrubtrack.png"
-                  width="${audioplayer.loadperc * 200}" >
+                  width="${audioplayer.loadratio * 200}" >
             <view name="playbar" resource="icons/audio_scrubber.png" y="1"
                   x="${Math.round(180 * audioplayer.frame/audioplayer.totalframes)}" />
             </view>
         </view>
     </view>
+    
 </canvas>    
 <!-- * X_LZ_COPYRIGHT_BEGIN ***************************************************
 * Copyright 2001-2008 Laszlo Systems, Inc.  All Rights Reserved.              *

Modified: openlaszlo/trunk/examples/music/music.mp3
===================================================================
(Binary files differ)



More information about the Laszlo-checkins mailing list