Copy link to clipboard
Copied
Why would the FLVPlayback class throw a null reference exception from an internal method originating from a timer event?
As far as I can tell, this code path is not originating from within any of my code, so I cannot even determine why it is happening or what is causing it. Here is the stack trace in FlashDevelop:
Copy link to clipboard
Copied
you may have an flvplayback component on stage starting to do something but the component is removed before the task is complete.
Copy link to clipboard
Copied
It's not a library issue; everything is functional under normal circumstances, but these circumstances aren't typical.
I'll investigate to see if kglad's suggestion leads anywhere, because I am stopping/unloading the FLV immediately upon the completion of its download, instead of letting it play to completion as it normally would. This is part of an automated testing program. We have schools across the U.S. using our system and have an automated test program that rapidly runs through part of the program by hooking different events to skip time-consuming tasks like actual playback of FLVs after downloading them and logging the download times. They simply visit a URL on a lab full of computers all at the same time, and the test program runs through everything (fills in and submits questions, downloads playback files, etc.), which covers all our bases like making sure they have an acceptable version of Flash, that everything is working, and that they have sufficient network bandwidth.
So, I'm not removing anything from the stage, but I am closing the video player very early, immediately after the download completes, as far as I can tell. My VideoPlayer class (custom class, not Flash's class), handles loading of SWFs and FLVs alike and unifies their events into clear and common events. I've posted the "unload" code path below, so you can see how it unloads an FLV file. I noticed I have code commented out that used to obtain the internal VideoPlayer object and call close on it, but instead I opted to use the closeVideoPlayer method of the FLVPlayback component, because that's what the documentation says to use. Perhaps I should switch back to closing the internal video player as well? Or nulling the "source" property? Or perhaps there is no way I can avoid this error.
public function unload():void
{
_videoWidth = NaN;
_videoHeight = NaN;
if (_playbackType != null)
{
switch (_playbackType)
{
case PLAYBACKTYPE_SWF:
swfPlayer_scaler.visible = false;
try { swfPlayer.unloadAndStop(); } catch (e:Error) { Tracer.write("Error unloading SWF:"); Tracer.writeError(e ); }
break;
case PLAYBACKTYPE_FLV:
flvPlayer_scaler.visible = false;
if (flvPlayer.activeVideoPlayerIndex == 1)
{
try
{
flvPlayer.closeVideoPlayer( 1 );
}
catch (e2:Error)
{
Tracer.write( "unload: error closing video player 1: " + e2.message );
}
}
/*if (!StringUtils.isEmpty( StringUtils.trim( flvPlayer.source ) ))
{
try
{
flvPlayer.stop();
}
catch (e0:Error)
{
Tracer.write( "unload: error stopping FLV playback: " + e0.message );
}
try
{
var vp:fl.video.VideoPlayer = flvPlayer.getVideoPlayer( flvPlayer.activeVideoPlayerIndex );
if (vp != null)
vp.close();
}
catch (e1:Error)
{
Tracer.write( "unload: error closing video file stream: " + e1.message );
}
//vp.source = ""; //unload the video
}
else
{
Tracer.write( "unload: flvPlayer source is empty; nothing to unload" );
}*/
break;
}
unwireLoadEvents( _playbackType );
_playbackType = null; //NOTHING LOADED
_bytesLoaded = 0;
_bytesTotal = 0;
}
}
private function unwireLoadEvents( type:String ):void
{
if (eventswired)
{
switch (type)
{
case PLAYBACKTYPE_SWF:
//SWFPlayback events
var loader_info:LoaderInfo = swfPlayer.contentLoaderInfo;
loader_info.removeEventListener( Event.OPEN, SWFPlayback_open, false ); //download started
loader_info.removeEventListener( ProgressEvent.PROGRESS, SWFPlayback_progress, false ); //download progress
loader_info.removeEventListener( IOErrorEvent.IO_ERROR, SWFPlayback_error, false ); //error
loader_info.removeEventListener( Event.COMPLETE, SWFPlayback_complete, false ); //download complete
loader_info.removeEventListener( Event.INIT, SWFPlayback_init, false ); //video ready for playback
loader_info.removeEventListener( HTTPStatusEvent.HTTP_STATUS, SWFPlayback_httpstatus, false ); //informational event
loader_info.removeEventListener( Event.UNLOAD, SWFPlayback_unload, false ); //swf unloaded
swfPlayer.removeEventListener( SWFPlaybackEvent.PLAYHEAD_UPDATE, SWFPlayback_playheadupdate, false ); //swf frame changed
swfPlayer.removeEventListener( SWFPlaybackEvent.PLAYBACK_COMPLETE, SWFPlayback_playbackcomplete, false ); //swf playback completed
swfPlayer.removeEventListener( SWFPlaybackEvent.PLAYING, SWFPlayback_playing, false ); //swf entered playing state
swfPlayer.removeEventListener( SWFPlaybackEvent.PAUSED, SWFPlayback_paused, false ); //swf entered paused state
swfPlayer.removeEventListener( SWFPlaybackEvent.STOPPED, SWFPlayback_stopped, false ); //swf playback stopped/reset
eventswired = false;
break;
case PLAYBACKTYPE_FLV:
//FLVPlayback events
flvPlayer.removeEventListener( VideoEvent.CLOSE, FLVPlayback_close, false ); //flv closed
flvPlayer.removeEventListener( VideoProgressEvent.PROGRESS, FLVPlayback_progress, false ); //flv download progress
flvPlayer.removeEventListener( VideoEvent.READY, FLVPlayback_ready, false ); //flv ready to play
flvPlayer.removeEventListener( VideoEvent.COMPLETE, FLVPlayback_complete, false ); //flv playback has completed
flvPlayer.removeEventListener( MetadataEvent.METADATA_RECEIVED, FLVPlayback_metadata, false ); //cuepoint metadata received
flvPlayer.removeEventListener( MetadataEvent.CUE_POINT, FLVPlayback_cuepoint, false ); //flv cue point encountered during playback
flvPlayer.removeEventListener( VideoEvent.PLAYHEAD_UPDATE, FLVPlayback_playheadupdate, false ); //flv playhead position changed
flvPlayer.removeEventListener( VideoEvent.PLAYING_STATE_ENTERED, FLVPlayback_playing, false ); //flv entered playing state
flvPlayer.removeEventListener( VideoEvent.PAUSED_STATE_ENTERED, FLVPlayback_paused, false ); //flv entered paused state
flvPlayer.removeEventListener( VideoEvent.STOPPED_STATE_ENTERED, FLVPlayback_stopped, false ); //flv entered stopped state
flvPlayer.removeEventListener( VideoEvent.BUFFERING_STATE_ENTERED, FLVPlayback_buffering, false ); //flv entered buffering state
flvPlayer.removeEventListener( VideoEvent.STATE_CHANGE, FLVPlayback_statechange, false ); //flv state changed to one of various available states (e.g. LOADING, CONNECTION_ERROR, DISCONNECTED, etc.
flvPlayer.removeEventListener( VideoEvent.SEEKED, FLVPlayback_seeked, false ); //flv playhead moved to new location by user
eventswired = false;
break;
}
}
}
So back to FLVPlayback... I had a difficult time trying to figure out what event signals completion of the download of an FLV, and there doesn't seem to be one at all. I'm basing the completion event off of the VideoProgressEvent, when bytesDownloaded == bytesTotal. At that point I close the video player via the unload method posted above. The odd thing is... the error doesn't occur consistently, so it may be a timing issue. Maybe calling closeVideoPlayer is not sufficient, and I should call some other method such as stop first.
Copy link to clipboard
Copied
The annoying part is, there's nothing that can be done to prevent the error, since it's occurring in a timer event in a close-source component (FLVPlayback).
The error shouldn't be occurring at all, so my only workaround is to perform a fuzzy match for this particular error on its stack trace in my UncaughtErrorEvent handler: see comment starting with "Fuzzy identification of a particular error/bug..."
Copy link to clipboard
Copied
In any case, that error is originating within the FLVPlayback.finishAutoResize( e:TimerEvent ) method, so I think someone should look into added a try/statement in there. There's no reason why some sort of resize timer event should be occurring after a video player has been closed with FLVPlayback.closeVideoPlayer.
I cannot find the FLVPlayback source-code anywhere or anything that would help me fix this error on my own. A few places I've looked:
Another thread says: "But FLVPlayback is a tricky one because it has several bugs in it that can make it act strangely, plus it's something like 4x bigger (kb-wise) than VideoLoader. It uses NetStream which is also tricky because you must close() the NetStream before you unload the content, otherwise you can run into garbage collection issues. I suspect that if you have an FLVPlayback running/playing in a sub-swf and then you just unload the sub-swf, it won't close things adequatel" unloading a swf with FLVPlayback - Loading (AS) - GreenSock Forums
In anther thread, someone suggests the proper way to unload a FLV is "first call FLVPlayback.stop() than change the FLVPlayback.source". http://www.actionscript.org/forums/showthread.php3?t=164030
Then there is this post, which is what I used to use before discovering the closeVideoPlayer method: "I have been trying to close the video connection from server but using FLVPlayback “stop()” method does not work , the video still keeps loading until complete…, and I don’t see there is “close()” method for the component. After looking through the Flash help and found out that there is a method called “getVideoPlayer()” that returns the VideoPlayer reference which can use “close” method to close the stream!" http://www.xllusion.net/ed/2008/04/26/unload-video-using-cs3-flvplayback-component/
kglad had a post from 2009 that showed using "m_flvPlayer.closeVideoPlayer(1);" and speficially commented out the call to "stop". How to unload a FlvPlayback component
There has got to be a way to stop/unload a FLVPlayback component without it throwing errors.
Copy link to clipboard
Copied
Do you know that when you hit an error and you're running in Flash Builder, sometimes you can get hold of different "contexts" to get more information about what is happening? In the debug window, where it shows your running swf, there are often multiple "threads" that you can poke around in. If you have your variables window set to show private and protected variables, you may be able to pinpoint exactly what's null, and under what circumstances, so you can remove that listener yourself at the right time if it's public. If it's on the stage, you should be able to get hold of it--it's just a matter of knowing the right moment to do it.
If that doesn't work, you should definitely be able to see what the event name is that it's responding to and listen to it at a high priority during capture phase and prevent propagation.
I know you said that you already checked your library, but it might be worthwhile double-checking that the shared assets in the "Component Assets" folder are there.
BTW, I just checked our source code, and here's what we use. It's not precisely the same scenario as yours--we run it when removing the parent MovieClip from the stage--but it might help:
if (flvPlayer) {
flvPlayer.stop(); //if skipped quickly, sometimes the audio track still plays
//on next screen.
var video:VideoPlayer = flvPlayer.getVideoPlayer(0);
if (video) {
video.close();
}
}
Copy link to clipboard
Copied
I am using FlashDevelop and Flash Professional.
In my original post, you can see the stack trace originates in an internal event registered by the fl.video.VideoPlayer component that calls it's own finishAutoResize and setState (to stopped) methods, which it turn fires and event that is handled by FLVPlayback.internal::handleVideoEvent method, which throws a null reference exception. I have no control over that code and no access to it. Even with FlashDevelop displaying all private/protected variables (the default), there was no source code to inspect.
I wasn't doing anything but using the FLVPlayback component. I loaded a file, registered some listeners, and tried to close the player when the FLV file finished downloading. There were no errors in my own code, but simply closing the FLVPlayback component via its closeVideoPlayer method is causing some kind of error, because in the meantime the VideoPlayer has a timer scheduled to run, which obviously ends up running code in the FLVPlayback component that just closed the video player. The error is definitely occurring in FLVPlayback.internal::handleVideoEvent, and it's probably occurring because, as kglad suggested, the closeVideoPlayer method is removing the video player from the stage, then trying to handle an event after the fact. FLVPlayback should detach all its listeners before it starts deconstructing the VideoPlayer in a way that's going to cause an intermittent time-sensitive error like this.
Here is the thread, stack, and local variables windows in FlashDevelop, along with a note explaining the stack position and the problem:
The code I settled on for closing the player is as follows. It stops the player, closes the underlying VideoPlayer (probably stops timers, etc.), clears the source property of the FLVPlayback component, and finally calls the closeVideoPlayer method to remove the video player. With this code, I haven't been able to reproduce the error, but I really should be able to just call closeVideoPlayer.
public static function closeActiveVideoPlayer( flvPlayer:FLVPlayback ):void
{
if (flvPlayer != null)
{
var activeIndex:uint = flvPlayer.activeVideoPlayerIndex;
try {flvPlayer.stop();} catch (e:Error){} //stop playback
try {flvPlayer.getVideoPlayer( activeIndex ).close();} catch (e2:Error){} //close internal VideoPlayer
try {flvPlayer.source = null;}catch(e3:Error){} //clear the source property
flvPlayer.closeVideoPlayer( activeIndex ); //use the recommended close method
}
}
Copy link to clipboard
Copied
I think you're looking at the wrong part of the variables tree--you need to be looking to try to find something that's null that shouldn't be. That means Display Objects of some sort. I get that you don't have access to the source code, but often you can get around that through brute force detective work.
But honestly, if you're using a static method for something like this (or anything, really) our coding practices are so far apart I don't really know how to help you.
Copy link to clipboard
Copied
I'm not looking at the wrong part of the variables tree. The null variable that's being accessed is being accessed in the closed-source method "FLVPlayback.internal::handleVideoEvent". Without the source code or a SWF decompiler or a debugger capable of displaying the specific spot in the ActionScript ByteCode where this error is occurring, I cannot know which variable to check. And even if I did, THIS IS NOT MY CODE, it's Adobe's. All I can say is someone needs to look into why FLVPlayback is trying to handle a VideoPlayer state change coming from a timer event, AFTER FLVPlayback.closeVideoPlayer has been called. The handleVideoEvent method is throwing a null reference exception, because it's obviously trying to access something that is no longer there.
As for my static method, it's not a "coding practice". FLVPlayback has a bug that prevents me from just calling "closeVideoPlayer", so instead of putting 10 lines of code where there should be one, I just created what basically is an "extension method" so I can easily close any FLVPlayback video player. It's a common pattern built into the C# specification, but ActionScript lacks that syntactic sugar.
Copy link to clipboard
Copied
Whenever you use Adobe's components, you have to bow to the way Adobe thought you'd be using them and be prepared to change the way you're using them to suit. For example, the components that have text input subcomponents may or may not have updated their values when you might reasonably expect to read them. If they don't, then you can't really come on here and uselessly whine and complain they don't work--you just have to either force them to update if possible or dig down to the text input or even its component TextField.
Adobe doesn't really update their components anymore--they're frankly not coded all that well, but if you choose to use them you get what you pay for.
As far as coding practice, yes, using a static function to manipulate the state of your program is bad practice Flaw: Brittle Global State & Singletons. You may think that you're not manipulating the state in your function, but in fact you are--this is an object that is clearly in memory and has state. Whether it's still on the display list is not clear and can't be guaranteed based on how you've made it available. All I can tell you that the code I gave you cuts with the grain of Flash by cleaning up a single FLVPlayer managed by a single parent when the parent is removed from the display list. If you choose to cut against the grain, your fingers may well get in the way.
Copy link to clipboard
Copied
Adobe expects us to call FLVPlayback.closeVideoPlayer to close a video player object. That's what I did. It throws an error, and I know precisely why and where. If I had access to the source code for FLVPlayback, I could fix it myself in a matter of seconds. The single most helpful thing anyone could do would be to point me in the direction of the source code for FLVPlayback so I can recompile it with this bug fixed:
I am not going against any grain; I have a functioning workaround, but you think it's evil because it's a static method.
In reality, all I did was move 3 consecutive public method calls "player.stop(), player.activeVideoPlayer.close(), player.closeVideoPlayer()" into another method; there's nothing wrong with that. It's quite literally the equivalent of a macro. I just don't want to have to replicate code by calling those three methods, in that order, everywhere, every time I want to close an FLVPlayback instance, since closeVideoPlayer doesn't do what it's supposed to. Your accusation of "using a static function to manipulate the state of your program" simply does not apply in this situation.
Copy link to clipboard
Copied
SourceCode for the FLVplayback Component can be found (under Win7:)
...Program Files (x86)/Adobe/Adobe Flash[...]/Common/Configuration/Component Source/AS3/FLVPlayback
Copy link to clipboard
Copied
Lo and behold, I found the source code and the problem.
There is a bug in the TIMER class!!! I could not believe my eyes when I injected a subclass of Timer into an altered version of fl.video.VideoPlayer, and traced out the fact that it was firing a TimerEvent when reset/stop was called, under the precise condition where currentCount and repeatCount were both 1. The _finishAutoResizeTimer instance is relatively unique within the class in that it's constructed with a non-zero repeat count... and that seems to be the problem.
So lets trace back what's going on here. First, here are the source code files I located, added to my project under fl.video:
http://203.113.9.121/videoPlayer/source/videoPlayer/src/fl/video/FLVPlayback.as
http://203.113.9.121/videoPlayer/source/videoPlayer/src/fl/video/VideoPlayer.as
There are actually multiple failures in this chain, in FLVPlayback, VideoPlayer, and the Timer class.
First of all, take a look at the closeVideoPlayer function.
public function closeVideoPlayer(index:uint):void {
if (index == 0) throw new VideoError(VideoError.DELETE_DEFAULT_PLAYER);
if (videoPlayers[index] == undefined) return;
var vp:VideoPlayer = videoPlayers[index];
if (_visibleVP == index) visibleVideoPlayerIndex = 0;
if (_activeVP == index) activeVideoPlayerIndex = 0;
removeChild(vp);vp.close();
delete videoPlayers[index]; //LO
delete videoPlayerStates[index]; //AND
delete videoPlayerStateDict[vp]; //BEHOLD..... IT'S GONE (the VideoPlayer instance is elegable for garbage collection here)
}
When this method completes, the VideoPlayer is available for garbage collection, since it's off state and references to it in the weak-keyed videoPlayerStateDict dictionary and videoPlayers Array have been deleted.
And that would be fine, except...
When the video player was created by "flvplayback_internal function createVideoPlayer(index:Number):void", it called "vp.addEventListener(VideoEvent.STATE_CHANGE, handleVideoEvent);"
So if closeVideoPlayer had removed its listeners properly, we wouldn't have a problem.
But wait! Let's give the programmer the benefit of the doubt, and assume that when the call to vp.close() was made, they expected that the VideoPlayer would behave itself and stop dispatching events. Personally, I would swap the calls to vp.close() and removeChild(vp), so vp remains on-stage when vp.close() is called, just to be safe, but even after this method completes, and everything is deleted, and the VideoPlayer is garbage collected.... there's still another problem; because we know _finishAutoResizeTimer is still going to fire and try to reference the no-longer-existing video player index. So I went on to verify that the vp.close() call actually tries to stop the timers.
The VideoPlayer.close method is relatively simple, and basically calls closeNS.
public function close():void
{
//ifdef DEBUG
//debugTrace("close()");
//endif
closeNS(true);
// never makes sense to close an http NetConnection, it doesn't really maintain
// any kind of network connection!
if (_ncMgr != null && _ncMgr.isRTMP)
{
_ncMgr.close();
}
setState(VideoState.DISCONNECTED);
dispatchEvent(new VideoEvent(VideoEvent.CLOSE, false, false, _state, playheadTime));
}
The code for closeNS is as follows:
/**
* Wrapper for NetStream.close(). Never call
* NetStream.close() directly, always call this
* method because it does some other housekeeping.
* @private
*/
flvplayback_internal function closeNS(updateCurrentPos:Boolean = false):void{
//ifdef DEBUG
//debugTrace("closeNS()");
//endif
if (_ns != null)
{
// do one last time update if updateCurrentPos is true
if (updateCurrentPos)
{
doUpdateTime();
_currentPos = _ns.time;
}
// shut down all the timers
_updateTimeTimer.reset();
_updateProgressTimer.reset();
_idleTimeoutTimer.reset();
_autoResizeTimer.reset();
_rtmpDoStopAtEndTimer.reset();
_rtmpDoSeekTimer.reset();
_httpDoSeekTimer.reset();
_finishAutoResizeTimer.reset();
_delayedBufferingTimer.reset();
// remove listeners from NetStream
_ns.removeEventListener(NetStatusEvent.NET_STATUS, rtmpNetStatus);
_ns.removeEventListener(NetStatusEvent.NET_STATUS, httpNetStatus);
// close and delete NetStream
_ns.close();
_ns = null;
}
}
As you can see, it, in fact, does call reset on _finishAutoResizeTimer, as it should. So, we can give the programmer the benefit of the doubt in their failure to unwire events from the video player it created (it's still wrong, but shouldn't cause an issue, because the timer is reset here).
At this point, I was baffled as to why the timer was still firing despite being properly reset. So I created a subclass of Timer, converted _finishAutoResizeTimer to that subclass, and overrode the reset, stop, and start methods to trace when they are called. I also traced out thier currentCount and repeatCount and what I found was appalling. Basically, go back and read my second sentence in this post. The Timer is firing AFTER it has been reset and stopped, and it's only doing so when currentCount and repeatCount are both 1 (i.e. when currentCount <= repeatCount. The Timer is not supposed to behave that way. Logically, once a timer is stopped, it should not continue to fire events, regardless of what it's currentCount and repeatCount are.
As you can see in VideoPlayer's constructor, most timers are created with the default repeatCount of 0, but a couple of them, including _finishAutoResizeTimer has a repeatCount of 1.
// setup intervals
_updateTimeTimer = new Timer(DEFAULT_UPDATE_TIME_INTERVAL);
_updateTimeTimer.addEventListener(TimerEvent.TIMER, doUpdateTime);
_updateProgressTimer = new Timer(DEFAULT_UPDATE_PROGRESS_INTERVAL);
_updateProgressTimer.addEventListener(TimerEvent.TIMER, doUpdateProgress);
_idleTimeoutTimer = new Timer(DEFAULT_IDLE_TIMEOUT_INTERVAL, 1);
_idleTimeoutTimer.addEventListener(TimerEvent.TIMER, doIdleTimeout);
_autoResizeTimer = new TrackedTimer(AUTO_RESIZE_INTERVAL, 0, "_autoResizeTimer");
_autoResizeTimer.addEventListener(TimerEvent.TIMER, doAutoResize);
_rtmpDoStopAtEndTimer = new Timer(RTMP_DO_STOP_AT_END_INTERVAL);
_rtmpDoStopAtEndTimer.addEventListener(TimerEvent.TIMER, rtmpDoStopAtEnd);
_rtmpDoSeekTimer = new Timer(RTMP_DO_SEEK_INTERVAL);
_rtmpDoSeekTimer.addEventListener(TimerEvent.TIMER, rtmpDoSeek);
_httpDoSeekTimer = new Timer(HTTP_DO_SEEK_INTERVAL);
_httpDoSeekTimer.addEventListener(TimerEvent.TIMER, httpDoSeek);
_httpDoSeekCount = 0;
_finishAutoResizeTimer = new TrackedTimer(FINISH_AUTO_RESIZE_INTERVAL, 1, "_finishAutoResizeTimer");
_finishAutoResizeTimer.addEventListener(TimerEvent.TIMER, finishAutoResize);
_delayedBufferingTimer = new Timer(HTTP_DELAYED_BUFFERING_INTERVAL);
_delayedBufferingTimer.addEventListener(TimerEvent.TIMER, doDelayedBuffering);
I have overridden the creation of _autoResizeTimer and _finishAutoResizeTimer to use a TrackedTimer instance with the same construction parameters so I could track their calls and output the name of the timer as passed in the third parameter.
The output traced when the error occurred was
UNLOADING FLV FILE
reset timer _autoResizeTimer; cc:0 rc:0
reset timer _finishAutoResizeTimer; cc:1 rc:1
stopped timer _finishAutoResizeTimer; cc:1 rc:1
[Fault] exception, information=TypeError: Error #1009: Cannot access a property or method of a null object reference.
The documentation for repeatCount says " If repeatCount
is set to a total that is the same or less then currentCount
the timer stops and will not fire again." Maybe that's not true or there's something else going on, because when repeatCount is the same as currentCount, the timer DOES fire again after reset/stop are called. I know this for certain, because at the point where the Fault was traced, I checked the call stack and saw that it originated from that Timer's tick.
In summary, Timer is dispatching an event after stop is called, which means VideoPlayer's attempt to clean up its timers is futile, and since FLVPlayback doesn't ever unwire its event handlers for the videoPlayers it creates, we have a huge problem with this component.
Copy link to clipboard
Copied
I updated FLVPlayback.as, so instead of having just a createVideoPlayer method and a closeVideoPlayer method that fails to unwire events...
I've created two clear methods for wiring and unwiring the event listeners.
I've created two clear methods for registering and unregistering a video player.
I've created a separate method to configure/reconfigure a new or existing player.
So now, calling "createVideoPlayer" will create a video player, configure it, save references to it, and wire events.
Meanwhile, calling "closeVideoPlayer" will close/shutdown the video player, unwire events, and finally destroy all references to it last.
Here's what it should look like:
As for the Timer issue, I've filed a bug report, since it's very intermittent and is probably a low-level or platform-specific issue with how Timer events are queued and dispatched. It's hard to reproduce the issue, but it absolutely happens; the stack trace in my original post proves the event is originating at the Timer/tick, and the error confirms that closeVideoPlayer ran beforehand, meaning all timers were already stopped. Timer events should never, ever run after Timer.reset is called within ActionScript.
Copy link to clipboard
Copied
Yes, I just always make sure to remove all listeners to Timers, even when they're supposed to be stopped, because my experience is as yours that you can't count on the fact that they're really stopped. Glad you got to the bottom of it.
Copy link to clipboard
Copied
I've seen internal 1009 errors when the component is not in the library but the class is imported and it can't find the piece it is trying to target.