Skip to main content
Inspiring
July 20, 2011
Question

Possible Memory Leak with stageVideo?

  • July 20, 2011
  • 4 replies
  • 2748 views

I have an actionscript application the plays in a fullscreen browser. It loops through three H.264 movies, employing stageVideo. The stageVideo is working fine, however, over the course of several hours the application rapidly consumes a lot of memory (1.5GB approx). At that point memory consumption stabilizes (see attached graph -- the high point is where I re-started the app) While sometimes the application continues to play, at other times it hangs. If I use software rendering rather than the gpu accelerated stageVideo, the memory utilization remains constant.

*Note: The above graph is of the application running in Safari 32bit mode, with the normal (non-debugger) flash player.

Has anyone seen anything similar? If you have time, please read on an let me know if you spot any issues with my approach:

Here are the particulars:

Platform: Mac Mini, Intel Core 2 Duo (2.4 Ghz)

OS: Mac OSX 10.6.8

Installed memory:4GB

Video card: Nvidia 320M

Cuda driver version: 4.0.19

GPU driver version: 1.6.37.0 (256.02.25f01)

Browsers tested: Safari 5.05, Firefox 5.0.1, Google Chrome 12.0.742.122

Flash player version: 10.3.181.34

Movies: (2) are 10MB in size and (1) is 5 MB. They are encoded in mainline H.264. Their native resolution is 1024X768, and their bit rates vary between 2.5-4 Mbps

I have tested the swf on three different browsers listed above and get the same behavior.

I ran the application through the Flex Profiler to look for memory leaks in the application itself. After a few hours, according to the Flex Profiler the application memory consumption remains stable at around 40K. Analyzing memory snapshots taking at the start of the application and a few hours in shows that there are no unexpected loitering objects. Nonetheless, a look at the system memory shows that the memory being consumed by the kernel_task process has ballooned to nearly 1.4GB (see attached image of the output for "top" -- note processes are sorted by rsize of memory)

Note: While the flex profiler requires a debugger version of the flash player -- which can inflate real memory statistics (which are not displayed in the profiler itself) the above "top" output is from a normal (non-debugger) version of the player with the application running in a 32bit version of Safari. This output is not from the Flex Profiler experiment.

The above images show the result of using a 32bit (as that is Adobe's recommendation). Still, I have tested with 64bit browsers and I get the same memory consumption patterns. In those cases the output of "top" shows that both the kernel_task and the WebKitPlugin processes balloon to roughly 1.4GB each.

Now onto the actionscript:

I took 2 approaches each of which result in the same memory consumption patters:

1) re-using the VideoConnection and NetStream objects with each new movie

2) closing the VideoConnection and NetStream objects and setting them to null and then creating new objects with each movie

The code below shows the second approach.

My gut feeling is that whatever process handles the GPU decoding is allocating memory which is never reclaimed. I don't think I've done anything wrong with the code as evidenced by the result of the Flex Profiler output (unless the profiler is not yet able to analyze stageVideo events?!). If anyone could prove me wrong I would be overjoyed.

Thanks for your help and feedback!

- Josh

CODE:

package
{

    import flash.display.*;
    import flash.events.*;
    import flash.net.*;
    import flash.utils.*;
    import flash.media.*;
    import flash.geom.*;
   
    public class stageVideoMemoryTest extends MovieClip
    {
        var duration:uint=0;
        var reload:Boolean = false;   
        var timer4:Timer;
        var movieArray:Array;
        var urlArray:Array;
        var curr:uint = 0;
        var playmenow:String;
        var orig_width:Number;       
        var orig_height:Number;
        var aspect:Number;
        var containerAspect:Number;       
        var netStream:NetStream;
        var containerWidth:Number;
        var containerHeight:Number;
        var videoConnection:NetConnection;
        var videoFinished:uint = 0;
        var stageVideoAvailable:Boolean;
        var stageVideo:StageVideo;
        var videoWidth:uint;
        var videoHeight:uint;
        var videoX:uint = 0;
        var videoY:uint = 0;
        var v:Vector.<StageVideo>;
       
        public function stageVideoMemoryTest()
        {

        trace("stageVideoMemoryTest has been instantiated");
        curr = 0;
        // screen size
        containerWidth = 1080;
        containerHeight = 1920;
       
        this.blendMode = BlendMode.LAYER;
        stage.scaleMode = StageScaleMode.NO_SCALE;
        stage.align = StageAlign.TOP_LEFT;
       
        v = stage.stageVideos;
        if ( v.length >= 1 ) {
        stageVideoAvailable = true;       
        stage.color = 0x000000;
        trace("StageVideo available. Number of instances: " + v.length);        
        } else {
        stageVideoAvailable = false;
        trace("StageVideo is unavailable.");
        }

        movieArray = new Array();
        movieArray.push("http://localhost/movie1.mov");
        movieArray.push("http://localhost/movie2.mov");
        movieArray.push("http://localhost/movie3.mov");
        playMovies();
        // end Constructor
        }
               
       
        public function playMovies():void
        {
        trace("playMovies called");
        try {
        videoConnection = new NetConnection ();
        videoConnection.connect(null);
        netStream = new NetStream(videoConnection);
        var metaListener:Object = new Object();
        metaListener.onMetaData = onMetaData;
        netStream.client = metaListener;   
        } catch(e:Error) {
        trace("Unable to create netstream connection");   
        }
               
        playmenow = movieArray[curr];
        trace("We are going to play the following movie: " + playmenow);
       
        stageVideo = v[0];
        stageVideo.attachNetStream(netStream);
        stageVideo.addEventListener(StageVideoEvent.RENDER_STATE, onStageVideoEvent);
        stage.addEventListener(StageVideoAvailabilityEvent.STAGE_VIDEO_AVAILABILITY, onStageVideoState);   
        netStream.play(playmenow);
        netStream.addEventListener(NetStatusEvent.NET_STATUS, netStreamStatus);
        netStream.addEventListener(AsyncErrorEvent.ASYNC_ERROR, asyncErrorHandler);

   
        function asyncErrorHandler(event:AsyncErrorEvent):void
        {
        trace("asyncErrorHandler called: " + event.text);
        }
        // close playmovie
        }
               
        function onMetaData(data:Object):void
        {
        trace("onMetaData function called!!");   
       
            duration = data.duration;
            netStream.bufferTime = 1;
            // aspect ratio corrections
            orig_width = data.width;
            orig_height = data.height;
            aspect = orig_width/orig_height;
            containerAspect = containerWidth/containerHeight;       
                       
            if ((aspect > containerAspect) && ((aspect-containerAspect)/containerAspect > 0.107)) {
            // trace("film aspect is wider by more than 10%");
            // the film has a wider aspect ration than the container by more than 10.7%. Let's take the container's width as the video's width (possible letter box)           
            videoWidth = containerWidth;
            // now set the height based on the original ratio
            videoHeight = videoWidth/aspect;
            }
           
            if ((aspect < containerAspect) && ((containerAspect-aspect)/aspect > 0.107)) {
            // trace("film is heigher by more than 10%");   
            // the film has a heigher aspect ration than the container. Let's take the container's height as the video's width (possible pill box)
            videoHeight = containerHeight;
            // now set the height based on the original ratio
            videoWidth = videoHeight*aspect;
            }

            if ( (aspect == containerAspect) || ( (aspect > containerAspect) && ( (aspect-containerAspect)/containerAspect <= 0.107) ) ||  ( (aspect < containerAspect) && ((containerAspect-aspect)/aspect <= 0.107) ) )
            {   
            // trace("video and container have essentially the same aspect ratio");
            // the video and container have the same aspect ratio, or are at least within 10% of eachother
            videoWidth = containerWidth;
            videoHeight = containerHeight;
            }
           
            if (containerWidth-videoWidth >0)
            // container is wider, we need a pill box -> center video in x axis
            {
            // trace("we need to pill box this thing");   
            videoX = ((containerWidth-videoWidth)/2);
            videoY = 0;
            }
           
            if (containerHeight-videoHeight > 0)
            {
            // trace("we need to letter box this thing");
            // container is higher, we need a letter box -> center video in y axis   
            videoY = ((containerHeight-videoHeight)/2);
            videoX = 0;
            }
            if (containerWidth == videoWidth && containerHeight == videoHeight) {
            videoX = 0;
            videoY = 0;
            }
            stageVideo.viewPort = new Rectangle(videoX,videoY,videoWidth,videoHeight);       
            trace("ViewPort params: width -> " + videoWidth + ", height -> " + videoHeight);
        // close onMetaData   
        }       
               
                   
               
        function onCuePoint(infoObject:Object):void
        {
            trace("cue point");
        }
                                       
               
       
        function onStageVideoState(event:StageVideoAvailabilityEvent):void
        {    
        trace("===============>>>>>>>><<<<<<========================");
        toggleStageVideo(event.availability == StageVideoAvailability.AVAILABLE);
        trace("===============>>>>>>>><<<<<<========================");
        }            
       
           
        function toggleStageVideo(on:Boolean):void    
        {
            if (on) {
                trace("StageVideo has become available.");
            } else {
                trace("StageVideo has become UNAVAILABLE.");
            }
        }
               
               
        function onStageVideoEvent(event:StageVideoEvent) {
        trace("===============>>>>>>>><<<<<<========================");   
        trace("STAGEVIDEO Event: " + event.status);
        trace("===============>>>>>>>><<<<<<========================");   
        // end onStageVideoEvent
        }               
               
               
               
    function netStreamStatus(e:NetStatusEvent):void
        {
                                   
            if(e.info.code == "NetStream.Play.Stop")
            {
            trace("----------------------------------------------------");   
            trace("!!!!!!!!!!!!!!! THE VIDEO IS DONE!!!! !!!!!!!!!!!!!");
            trace("----------------------------------------------------");       
                           
                curr++;
                stageVideo.removeEventListener(StageVideoEvent.RENDER_STATE, onStageVideoEvent);
                stage.removeEventListener(StageVideoAvailabilityEvent.STAGE_VIDEO_AVAILABILITY, onStageVideoState);
                stageVideo.viewPort = new Rectangle(0,0,0,0);
               
                netStream.close();
                netStream = null;
                videoConnection.close();
                videoConnection = null;
                stageVideo.attachNetStream(null);
                stageVideo = null;   
       
                if (curr >= movieArray.length)
                {
                    curr = 0;
                }
                trace ("Switching movie");   
                playMovies();
            // end stop detected (NetStream.Play.Stop)
            }
        // end netstream status   
        }           
   
    // class   
    }
// package   
}

This topic has been closed for replies.

4 replies

xpojoshAuthor
Inspiring
October 25, 2011

I somehow figured out how to purchase a support contract from Adobe so that I could push this issue along. The support folks initially had me wait for Flash Player version 11 to see if the new netstream dispose() method would take care of the memory issue. While it does appear to actually work for traditional video objects, it does not stop the memory leak for StageVideo objects.

I have gone ahead and filed a new bug report with Adobe.

If any one else out there is battling with the same issue, please vote on the bug here:

https://bugbase.adobe.com/index.cfm?event=bug&id=3007608

Thanks!

Josh

xpojoshAuthor
Inspiring
July 25, 2011

I absolutely want to follow good OOP practices, which is why I am trying to implement strict encapsulation with my movie module:

The application as a whole dynamically loads various widgets/module in different parts of the screen at different times. When a module loads it should perform its task, clean itself up and then communicate to the application that it is ready to be removed. All the resource management logic should be self-contained within the widgets themselves.

The movie widget/module is only one type of object that will be created, used and destroyed. Other objects include slide shows, messages, rss feeds, etc. In the case of a movie module -- after the object is instatiated it should play an array of movies. From movie to movie, it absolutely re-uses its netconnection and netstream resources. However, when all the movies are complete, the object should be able to clean up its resources in preparation for being destroyed by the main application (so it may be replaced with a different type of widget).

Based on how both software rendering and stageVideo appear to be working, simply closing the netconnection and netstream objects and then nulling out all references does not free up the memory associated with that object. However, my application does not simply instantiate regular objects, but rather dynamically loads and unloads pre-compiled swfs for each widget type. This is done as it allows the application to be extensible. All the possible widgets/modules do not need to be written prior to the application's compilation and distribution. Further widget may be written and incorporated into the application as needed at a later date. Moreover, by keeping strict encapsulation (requiring the objects to use their own variables and to clean themselves up), I am opening up the possibility of allowing 3rd parties to develope widgets for the application.

Because the widgets are loaded as pre-confiled swfs and unloaded using the "unloadAndStop" method -- even if someone is sloppy about cleaning up the resources internal to their widgets -- the memory resources used by those widgets will be freed up.

Prior to stageVideo, the memory occupied by the movie widget was easilty cleaned up with the unloadAndStop. Now with stageVideo, however, the unloadAndStop method does not appear to clean up the memory resources utilized by the netconnection/netstream objects. Because over time I want to dynamically create and destroy a variety of widgets (some of which may be more movie modules), the result will be that over time the application will start using more and more memory.

One solution would be to feed references to re-usable netconnection/netstream objects to the widgets as they are loaded, but then I would be breaking the principles of encapsulation (by exposing global variables to object instances -- some of which my not even be written by me or my company).

I seems logical that:

1. The netconnection and netstream objects should be able to be completely cleaned up by an object that is to be destroyed, and

2. The unloadAndStop method should function as intended and clean up all resources occupied by a dynamically loaded child swf.

Those are the 2 issues I am trying to solve. Getting either one to work would be sufficient to prevent memory bloat.

Josh

xpojoshAuthor
Inspiring
July 22, 2011

Ok, I think I've been honing in on this issue...

In my first post I mentioned that I saw the memory loss with stageVideo and not with normal software rendered video. However, that was not entirely accurate! In my second post I explained that the video component I am working on is part of a more modular system and I had modified the code to make it standalone so that I could post it here.

Since then I've gone back and re-tested both the stageVideo and software rendered video as standalone applications and also saw the memory loss. This led me to test 3 scenarios for both software rendered video and stage video:

Scenario 1: The swf is run as a standalone application and the netconnection and netstream objects are reused.

Scenario 2: The swf is run as a standalone application and the netconnection and netstream object are not reused, but rather closed and nulled out.

Scenario 3: The swf is first loaded into another parent swf. The child swf plays 1 iteration of the videos and then indicates to the parent that it is done. The parent unloads the child (with the unloadAndStop method) and then reloads the child to start the movies over again.

The above led to some interesting results:

Scenario 1: For both software and stage video, re-using the netconnection and netstream objects results in no memory loss.

Scenario 2: For both software and stage video, memory loss occurs.

BTW, For the software rendering, I am using the following to try to clean up the objects on each movie iteration:

        removeChild(vid);  // This is the video object
        vid.attachNetStream(null);   
        vid = null;
        netStream.removeEventListener(NetStatusEvent.NET_STATUS, netStreamStatus);
        netStream.removeEventListener(AsyncErrorEvent.ASYNC_ERROR, asyncErrorHandler);
        netStream.client = {};
        netStream.close();       
        netStream = null;
        metaListener  = null;
        videoConnection.close();
        videoConnection = null;

The stageVideo cleanup is shown in my previous post.

Scenario 3:

A. For software rendered video, the unloadAndStop method cleans up any memory associated with the child swf!  ....but...

B. For stageVideo, the unloadAndStop method does not clean up the memory.

So to further tune my original question:

1. Is there no way to clean up all resources associated with a netconnections/netstream (for either normal video or stage video) without reusing those objects or relying on the unloadAndStop method

2. Why does the unloadAndStop method work to clean up resources associated with software rendered video while it does not do the same for stageVideo?

Any ideas would be greatly appreciated!

Josh

Inspiring
July 23, 2011

"Scenario 1: For both software and stage video, re-using the netconnection and netstream objects results in no memory loss."

So, it does look like there is no inherent to StageVideo memory leaks and everything about making sure that objects are eligible for GC

"Scenario 3: ... The parent unloads the child (with the unloadAndStop method) and then reloads the child to start the movies over again."

Why would you do that in actual application? The whole point of OOP is reusing of objects.

Inspiring
July 20, 2011

I am not sure about wider scope of the issue but I suggest you first fix code inefficiencies that may overwhelm memory.

1. Reuse NetConnection and Netstream instances

2. Reuse metaListener

3. Remove listeners from previous instances on NetStream

Again, I am not sure if it will fix the issue but this is just a start.

xpojoshAuthor
Inspiring
July 20, 2011

Andrei1,

You are absolutely right here! The best thing to do would be to re-use the netconnection, netstream and metalistener objects.

What I hadn't mentioned in my first post is that this code was taken from a larger more complex project where some swfs dynamically others, each of which performs some operation. In this case, we are loading a swf which plays one or more videos, cleans itself up and then communicates to the parent swf so the child may be completely unloaded. In order to make this post more simple, I pulled out the movie code and modified it to be a stand-alone swf. In doing so, I had missed moving some of my "removeEventListeners". I went ahead and fixed my test swf to reflect your suggestions, and sure enough the memory usage remains constant.

However, here's my challenge:

Because this project is modular, I would like a movie swf to be able to create netconnections and netstreams, use them, and then clean them up. Of course, I could store those objects in parent swfs and have the dynamically loaded swfs reference the objects contained in the parent. However, I would like to see if these can be done as atomic operations within each child swf. Again, if I do all the decoding in software, the resources clean up fine. If I use stageVideo, the memory leak starts.

So, to emulate the atomic operation of a dynamically loaded swf, I am re-creating the netconnection, netstream and metalistener object. I try cleaning them up with the following:

        stageVideo.removeEventListener(StageVideoEvent.RENDER_STATE, onStageVideoEvent);
        stage.removeEventListener(StageVideoAvailabilityEvent.STAGE_VIDEO_AVAILABILITY, onStageVideoState);
        stageVideo.viewPort = new Rectangle(0,0,0,0);
        stageVideo.attachNetStream(null);
        stageVideo = null;   
        netStream.removeEventListener(NetStatusEvent.NET_STATUS, netStreamStatus);
        netStream.removeEventListener(AsyncErrorEvent.ASYNC_ERROR, asyncErrorHandler);
        netStream.client = {};
        metaListener  = null;
        netStream.close();       
        netStream = null;
        videoConnection.close();
        videoConnection = null;

Still, on the next iteration the memory is never released. I tested the swf again with the FlexProfiler and it shows me that the flash player memory utilization is constant even though the operating system shows that memory usage is increasing.

Am I missing something?

I modified the leaky code slightly to place the resource clean up in its own function (before it was in the netstatus function). The full code follows.

Thanks again for taking the time to help me out!

Josh

package
{

    import flash.display.*;
    import flash.events.*;
    import flash.net.*;
    import flash.utils.*;
    import flash.media.*;
    import flash.geom.*;
   
    public class stageVideoMemoryTest3 extends MovieClip
    {
        var duration:uint=0;
        var reload:Boolean = false;   
        var timer4:Timer;
        var movieArray:Array;
        var urlArray:Array;
        var curr:uint = 0;
        var playmenow:String;
        var orig_width:Number;       
        var orig_height:Number;
        var aspect:Number;
        var containerAspect:Number;       
        var netStream:NetStream;
        var containerWidth:Number;
        var containerHeight:Number;
        var videoConnection:NetConnection;
        var videoFinished:uint = 0;
        var stageVideoAvailable:Boolean;
        var stageVideo:StageVideo;
        var videoWidth:uint;
        var videoHeight:uint;
        var videoX:uint = 0;
        var videoY:uint = 0;
        var v:Vector.<StageVideo>;
        var metaListener:Object
       
        public function stageVideoMemoryTest3()
        {

        trace("stageVideoMemoryTest3 has been instantiated");
        curr = 0;
        // screen size
        containerWidth = 1080;
        containerHeight = 1920;
       
        this.blendMode = BlendMode.LAYER;
        stage.scaleMode = StageScaleMode.NO_SCALE;
        stage.align = StageAlign.TOP_LEFT;
       
        v = stage.stageVideos;
        if ( v.length >= 1 ) {
        stageVideoAvailable = true;       
        stage.color = 0x000000;
        trace("StageVideo available. Number of instances: " + v.length);        
        } else {
        stageVideoAvailable = false;
        trace("StageVideo is unavailable.");
        }

        movieArray = new Array();
        movieArray.push("http://localhost/cabana_arch.mov");
        movieArray.push("http://localhost/bluezoo_arch.mov");
        movieArray.push("http://localhost/fountain_burger.mov");
        playMovies();
        // end Constructor
        }
               
       
        public function playMovies():void
        {
        trace("playMovies called");
       
        try {
        videoConnection = new NetConnection ();
        videoConnection.connect(null);
        netStream = new NetStream(videoConnection);
        metaListener = new Object();
        metaListener.onMetaData = onMetaData;
        netStream.client = metaListener;   
        } catch(e:Error) {
        trace("Unable to create netstream connection");   
        }
               
        playmenow = movieArray[curr];
        trace("We are going to play the following movie: " + playmenow);
       
        stageVideo = v[0];
        stageVideo.attachNetStream(netStream);
        stageVideo.addEventListener(StageVideoEvent.RENDER_STATE, onStageVideoEvent);
        stage.addEventListener(StageVideoAvailabilityEvent.STAGE_VIDEO_AVAILABILITY, onStageVideoState);   
        netStream.play(playmenow);
        netStream.addEventListener(NetStatusEvent.NET_STATUS, netStreamStatus);
        netStream.addEventListener(AsyncErrorEvent.ASYNC_ERROR, asyncErrorHandler);

   
        // close playmovie
        }
               
               
        function asyncErrorHandler(event:AsyncErrorEvent):void
        {
        trace("asyncErrorHandler called: " + event.text);
        }        
               
               
               
               
        function onMetaData(data:Object):void
        {
        trace("onMetaData function called!!");   
       
            duration = data.duration;
            netStream.bufferTime = 1;
            // aspect ratio corrections
            orig_width = data.width;
            orig_height = data.height;
            aspect = orig_width/orig_height;
            containerAspect = containerWidth/containerHeight;       
                       
            if ((aspect > containerAspect) && ((aspect-containerAspect)/containerAspect > 0.107)) {
            // trace("film aspect is wider by more than 10%");
            // the film has a wider aspect ration than the container by more than 10.7%. Let's take the container's width as the video's width (possible letter box)           
            videoWidth = containerWidth;
            // now set the height based on the original ratio
            videoHeight = videoWidth/aspect;
            }
           
            if ((aspect < containerAspect) && ((containerAspect-aspect)/aspect > 0.107)) {
            // trace("film is heigher by more than 10%");   
            // the film has a heigher aspect ration than the container. Let's take the container's height as the video's width (possible pill box)
            videoHeight = containerHeight;
            // now set the height based on the original ratio
            videoWidth = videoHeight*aspect;
            }

            if ( (aspect == containerAspect) || ( (aspect > containerAspect) && ( (aspect-containerAspect)/containerAspect <= 0.107) ) ||  ( (aspect < containerAspect) && ((containerAspect-aspect)/aspect <= 0.107) ) )
            {   
            // trace("video and container have essentially the same aspect ratio");
            // the video and container have the same aspect ratio, or are at least within 10% of eachother
            videoWidth = containerWidth;
            videoHeight = containerHeight;
            }
           
            if (containerWidth-videoWidth >0)
            // container is wider, we need a pill box -> center video in x axis
            {
            // trace("we need to pill box this thing");   
            videoX = ((containerWidth-videoWidth)/2);
            videoY = 0;
            }
           
            if (containerHeight-videoHeight > 0)
            {
            // trace("we need to letter box this thing");
            // container is higher, we need a letter box -> center video in y axis   
            videoY = ((containerHeight-videoHeight)/2);
            videoX = 0;
            }
            if (containerWidth == videoWidth && containerHeight == videoHeight) {
            videoX = 0;
            videoY = 0;
            }
            stageVideo.viewPort = new Rectangle(videoX,videoY,videoWidth,videoHeight);       
            trace("ViewPort params: width -> " + videoWidth + ", height -> " + videoHeight);
        // close onMetaData   
        }       
               
                   
               
        function onCuePoint(infoObject:Object):void
        {
            trace("cue point");
        }
                                       
               
       
        function onStageVideoState(event:StageVideoAvailabilityEvent):void
        {    
        trace("===============>>>>>>>><<<<<<========================");
        toggleStageVideo(event.availability == StageVideoAvailability.AVAILABLE);
        trace("===============>>>>>>>><<<<<<========================");
        }            
       
           
        function toggleStageVideo(on:Boolean):void    
        {
            if (on) {
                trace("StageVideo has become available.");
            } else {
                trace("StageVideo has become UNAVAILABLE.");
            }
        }
               
               
        function onStageVideoEvent(event:StageVideoEvent) {
        trace("===============>>>>>>>><<<<<<========================");   
        trace("STAGEVIDEO Event: " + event.status);
        trace("===============>>>>>>>><<<<<<========================");   
        // end onStageVideoEvent
        }               
               
               
               
    function netStreamStatus(e:NetStatusEvent):void
        {
                                   
            if(e.info.code == "NetStream.Play.Stop")
            {
            trace("----------------------------------------------------");   
            trace("!!!!!!!!!!!!!!! THE VIDEO IS DONE!!!! !!!!!!!!!!!!!");
            trace("----------------------------------------------------");       
                           
                curr++;
                if (curr >= movieArray.length)
                {
                    curr = 0;
                }
                trace ("Switching movie");   
                switchMovie();
               
            // end stop detected (NetStream.Play.Stop)
            }
        // end netstream status   
        }           
   
   
    function switchMovie():void {
       
        stageVideo.removeEventListener(StageVideoEvent.RENDER_STATE, onStageVideoEvent);
        stage.removeEventListener(StageVideoAvailabilityEvent.STAGE_VIDEO_AVAILABILITY, onStageVideoState);
        stageVideo.viewPort = new Rectangle(0,0,0,0);
        stageVideo.attachNetStream(null);
        stageVideo = null;   
        netStream.removeEventListener(NetStatusEvent.NET_STATUS, netStreamStatus);
        netStream.removeEventListener(AsyncErrorEvent.ASYNC_ERROR, asyncErrorHandler);
        netStream.client = {};
        metaListener  = null;
        netStream.close();       
        netStream = null;
        videoConnection.close();
        videoConnection = null;
       
        playMovies();
       
       
    }
   
   
    // class   
    }
// package   
}