Hack 78 An Idle Timer (Timeout Event)

figs/moderate.gif figs/hack78.gif

When performance is critical, you don't want to waste processor time checking for user interactivity. Add a "no-interaction" timeout event that does not interfere with performance-sensitive multimedia delivery during periods of interaction.

I was asked to develop a Flash-based questionnaire application that ran on the screens on the backs of aircraft passenger seats. When the questionnaire was left idle, Flash should start playing some video. It should switch back quickly to the questionnaire if the user moved or clicked the mouse, so I set up some idle-detection code. However, the video playback became jerky if the idle-detection code wasn't highly optimized.

Macromedia Director has an idle event that fires whenever Director is not doing anything, allowing you to use this spare time doing something constructive via a background task. It's normal for Flash designers to increase the frame rate until Flash doesn't have any idle time (it is significantly lower than the Director idle time in any case because Flash performance is slower than Director's). So Flash doesn't provide built-in support for an idle event to be triggered when no other processing is being performed, because it isn't really appropriate to the way Flash is used.

That set me thinking. I needed an idle event that looks for no user interaction, which would be a more appropriate time for Flash to do some different processing. If no activity is detected, the SWF could, say, display an appropriate animation or an audible prompt. (In Director, these are called timeout events, but again, Flash has no native support for detecting when the user is inactive.)

It is common for users to leave Flash sites open while rich media elements (such as video or sound) load and then do something else in another browser window. Rather than have the SWF wait on a "content loaded" message screen when the download is complete, it would be nice for the SWF to provide an audible cue if the user hasn't responded to the fact that content loading has completed.

In kiosk applications, you might want the SWF to return to an attract loop (i.e., a screen displaying a video or animation intended to attract passers-by) when no user activity has been detected within the timeout period.

The key is creating an idle-detection routine that doesn't consume processing power when the user is interacting with the SWF. The most obvious event for detecting user activity is onMouseMove (a user who is moving the mouse is presumed to be breathing). However, when the user is actively using the application, the onMouseMove event occurs often enough to affect performance of the main SWF, so we should avoid relying on this event when designing a background event handler.

A more processor-friendly way to detect interaction is to use two setInterval( ) timers as follows:

  • Use a short interval that checks for interaction every one to two seconds (not often enough to adversely affect performance).

  • Use a long interval (typically 15 to 120 seconds) that specifies the timeout period length. This interval is reset whenever the short interval detects user activity.

So, we have two intervals that occur relatively infrequently compared to events like onEnterFame, which occurs once for each Flash frame. The key is to make the code as minimal as possible to avoid a performance hit; too much background processing will make Flash sluggish.

Here's a short code listing that shows the two intervals in action. The more frequent of our two intervals executes minimal code (only one if statement). The commented trace( ) statements can be uncommented to see what the code is doing during the interval periods (be aware that trace( ) statements are time consuming, so you don't want them in your final version).

function idleTime( ) {

  // Play the attract animation

  //trace("playing the animation")

  gotoAndStop("attract");

}

function idleS( ) {

  // See if there has been any mouse movement in the last two seconds

  if ((_root._xmouse + _root._ymouse) != mousePos) {

    // If there has, restart the check for the idle timer

    //trace("resetting");

    gotoAndStop("noAttract");

    clearInterval(idle);

    idle = setInterval(idleTime, 28000);

    //} else {

    //trace("no movement this period");

  }

  // Store an integer representing the mouse position

  mousePos = _root._xmouse + _root._ymouse;

}

function startIdle( ) {

  mousePos = -100000;  // Initialize it to a dummy value

  idleSample = setInterval(idleS, 2000);

}

startIdle( );

stop( );

The first interval, idleSample, checks the mouse position every two seconds (it is better to check for mouse movement this way rather than onMouseMove, because onMouseMove happens sufficiently often to possibly slow down your Flash application). If the sum of the mouse x and y positions do not remain the same between two interval invocations, the user has moved the mouse, so we reset the second, longer interval, idle. If no interaction occurs before the idle interval reaches its timeout value (28 seconds), the idleTime( ) function executes and sends the playhead to the attract frame (which typically displays a looping animation or possibly a video).

If desired, you can modify the code so that the idleTime( ) function starts a wait animation and the idleS( ) function stops it. The wait animation would typically be a screensaver-style animation or a "where have you gone?" animation.

This preceding code carries on indefinitely until 30 seconds (2,000 + 28,000 ms) of inactivity causes Flash to invoke the idleTime( ) function, which jumps to the attract frame. Presumably, an onMouseDown( ) event handler on that frame aborts the attractor animation and starts the presentation anew when the user clicks the mouse.

Final Thoughts

To maintain Flash performance, you have to minimize background tasks operating when rich media (video, sound, or complex animation at fast frame rates) are also being delivered. This means that your background tasks have to be highly optimized. The "check for idle user" code presented doesn't run often, so that when the user is interacting with the site, it does not slow down the main content.

When the user has not been interacting for some time, the code allows switching to an attract loop or screensaver-style animation. This would be useful for kiosk applications in which you want the Flash movie to reset to the beginning if no user is presently interacting with it. One caveat: be sure that your timeout period (such as 30 seconds) is longer than, say, any animation sequence you expect the user to sit through. If the user is watching your kiosk run, you don't want it to suddenly time out. So you might have to lengthen your timeout to allow for expected periods of inactivity even when a user is present and otherwise active. If you expect a user to sit passively through something for more than, say, 90 seconds, it is probably time to rethink your kiosk design instead of merely increasing the timeout duration.

Similarly, it is a good idea to have keypress events reset the timeout period. Otherwise, if the user is entering text in a form but not moving her mouse, the movie might unceremoniously jump to the attract loop. Therefore, if your kiosk has a keyboard, use Key.addListener( ) to invoke an onKeyDown( ) event handler that resets the idle interval.