Monday, April 16, 2012

XML Soundboard


The XML Soundboard creates an 800x600 pixel swf with an array of buttons that play samples of your choosing. The swf can display 100 samples per page, with multiple pages. Each page is navigated to via the buttons in the top right.

The sample buttons are created in the order they appear in soundboard.xml, which must exist in the same location as the swf. After the swf is built, buttons can be dragged to new positions. The buttons can also be dragged into a “custom” page. When buttons are dragged to the “Drag to Custom Page” button and released, the buttons appear on the custom page.

The button in the bottom left --“Fever Dream Mode” -- plays one randomly chosen soundfile after another until pressed again.

The background image and button state colors can be configured via the soundboard.xml. Additionally, text colors can be configured via AppCSS.css, or whatever css file is linked in soundboard.xml.

The demonstration files create a mock version of a soundboard for the 2003 movie "The Room." The xml generates three pages of buttons; however, the actual samples have been replaced with dummy samples.

Download XML Soundboard Demo


Friday, March 4, 2011

Adding Accessibility to the REOPS Player

OSMF and the REOPS player

REOPS uses the dynamic streaming capabilities of the Adobe Open Source Media Framework (OSMF) to deploy a configurable, re-skinnable video player for the web. At runtime, an XML file is used to configure layout and streaming video locations while custom skins are loaded through an exported swf. I chose to implement this application for a recent government project, but had to re-engineer the code to make the player compliant with section 508 -- specifically, making it accessible for people with assisting devices. Additionally, I added a component within the REOPS skinning framework to dynamically load a "preroll" or "poster" image prior to video play. The entire project can be downloaded here. It can also be deployed with Adobe Flash by creating a 768 x 600 fla with "Main" as the class for the stage.

Implementing the REOPS player

My first step was to implement and familiarize myself with the REOPS player. I used FlashDevelop to create a project based on the demos provided in the REOPS download. I chose to use the Lunar skin, but had to fuss with it to export an swf that worked correctly for me. Most of my troubles were solved by making sure the fla is in the right place when exporting, and re-exporting the skin when the REOPS code changes.

I used aDesigner to do a quick check on the player's accessibility and found that none of the controls were labeled. I talked about using this tool in my previous post. This is the script aDesigner said the screen reader gets for my basic REOPS implementation with the Lunar skin:

Flash movie start
2 Button
6 Button
0:02
7:50
9 Button
/
11 Button
12 Button
Flash movie end

For a person who has no visual reference, this is not very useful. Having properly labeled controls is just as vital for a non-sighted user to manipulate the content as for a sighted person. With the Flash Accessibility class, the buttons can be labeled for those using a screen reader to fulfill 508 requirements. Since these objects are supplied by a skin swf, that is where I started my redesign.

Updating the Lunar Skin

The REOPS skinning system allows you to create different visual "skins" for the same components (like the play/pause button) which are then loaded in from a swf that is referenced in the XML config file. This is a separate swf from the final swf which will be embedded in an HTML page. I had some fits and starts getting the Lunar skin that came with the download to work correctly in my project. After repairing a few TextFields (whose alphas were "0" or had no characters embedded), I ended up discarding the "_code_" component from the Lunar.fla library and set a relative path to the src folder by going to Publish Settings -> Flash (tab) -> Settings (actionscript 3.0) -> source path (tab). There, I added a folder for the relative path "../../../src" -- allowing the fla to find the objects' classes when exporting from the skins folder.

I adjusted some of the Properties class export settings for the movie clips in the Lunar.fla library. For instance, "PlayButton" was given a class of com.realeyes.osmfplayer.controls.PlayButton with a base class of com.realeyes.osmfplayer.controls.ToggleButton, because that's what PlayButton.as is extending. I did this for the other buttons (including the scrubber) that extended from ToggleButton as well. It is essential to remember that, when you alter certain classes, the skin must be re-exported before you create the final swf. Otherwise, instantiations of the skin elements won't have the same methods and variables.

Adding a "Poster Image"

One of the requirements for my video project was not a facet of the REOPS player -- that of a "poster image," or an image presented in lieu of any video content. Instead of the video immediately loading and playing, the client requested a still image from the video accompanied by a "start" button.

To accomplish this, I created a "PosterImage" symbol in my Lunar skin fla file. This MovieClip is comprised of a VideoPlayButton and a black "background" MovieClip. The Play Button extends com.realeyes.osmfplayer.controls.ToggleButton, as you can see in its library Properties window, meaning it will fire a ToggleEvent when clicked.

The PosterImage is extended from SkinElementBase just like all the other components in the skin. In the config file (reops_config.xml), I can add XML tags for the poster image to pass on to the REOPS app:

reops_config.xml

<skinElement id="posterImage"
elementClass="com.realeyes.osmfplayer.controls.PosterImage"
initMethod="initPosterImageInstance"
hAdjust="0" vAdjust="0"
scaleMode="STRETCH"
hAlign="CENTER"
vAlign="CENTER"
autoPosition="true"
imageURL="assets/images/poster.png"
imageDescription="Picture of the National Library of Medicine" />

The properties "imageURL" and "imageDescription" are custom variables for PosterImage; the latter will be what the Accessibility class will send assisting devices as the poster image's name. This setup allows images to be swapped in with out recompiling. The other variables in the tag instruct layout of the component just like the others. I captured a still of the video to use as the poster image, poster.png.

Updating the Code for Accessibility

The REOPS class files needed additional code to include the poster image and add accessibility. The first task was to get my AccessibilityManager singleton instantiated. This class sets off a timeout to ensure an accurate check of whether an assisting device is being used -- the resulting boolean value effectively becomes a global variable.

REOPS.as
/////////////////////////////////////////////
// CONSTRUCTOR
/////////////////////////////////////////////

public function REOPS( p_loaderInfoParams:Object = null )
{
accMgr = AccessibilityManager.getInstance();// starts Accessible Device Check
...
}

Next, I dug into the the main view of the REOPS application, SkinContainer. This class instantiates components (like the control bar, loading indicator, closed-caption field -- and now the poster image) from the skin swf, informed by properties taken from the reops_config.xml file.

The control bar is the first component built. The ControlBar constructor grabs the same instance of AccessibilityManager (assigned to accMgr ), then instantiates the controls. Each control has an initialization function where its Accessibility information is created. With the ReLunar skin, the Play/Pause button is the first initialized:

ControlBar.as

protected function _initPlayPauseButton():void {
playPause_mc.addEventListener(ToggleButtonEvent.TOGGLE_BUTTON_CLICK, _onPlayPauseClick);
accMgr.addAccessibility(playPause_mc, "play pause");
_addControlItem(playPause_mc);
}

The addAcessibility method in my AccessibilityManager singleton gives an appropriate Accessibility.name property to the MovieClip . This is done for all other controls. Not all controls in ControlBar.as are featured or initialized in the Lunar (or my ReLunar) skin. Be aware that, while I added accessibility code to all skin objects, I only really tested the usability for objects instantiated with the ReLunar skin.

For instance, I realized I could improve the usability for the "current time" readout by putting currentTime_txt inside a Sprite with an Accessibility.name property to give non-sighted users context for the digits to follow:

ControlBar.as

protected function _initCurrentTimeText():void {
_addControlItem(currentTime_txt);
var spCurrentTimeContainer:Sprite = new Sprite();
spCurrentTimeContainer.x =0;
spCurrentTimeContainer.y = 0;
addChild(spCurrentTimeContainer);
spCurrentTimeContainer.addChild(currentTime_txt);
accMgr.addAccessibility(spCurrentTimeContainer, "elapsed time", "displays elapsed time of video", false);
spCurrentTimeContainer.tabEnabled = false;
}

The container of the TextField also receives a tabEnabled = false setting. Tab-enabling is also removed from progress_mc, the parent of the scrubber:

ControlBar.as

protected function _initProgressBar():void {
accMgr.addAccessibility(progress_mc, "", "", false);// make silent
progress_mc.tabEnabled = false;
progress_mc.scrubber_mc.focusRect= false;// disables tabbing with arrow keys and eliminates yellow border, which is redrawn by focus manager
accMgr.addAccessibility(progress_mc.scrubber_mc, "scrubber", "use left and right arrow key to scrub video forward and back", false);
_addControlItem(progress_mc);
}

The progress_mc is rendered "silent" to assisting devices, while the scrubber gets a name of "scrubber" and an automatic tab-enabling. The scrubber_mc also has focusRect disabled, allowing the up/down arrow keys, usually reserved for tabbing, to instead scrub the video forward or backward when the scrubber is focused. The focus rectangle is redrawn by the focusChangeHandler(), triggered by a listener added when all ControlBar controls have been checked in _checkControls(). A listener is also added here to trigger controlBarKeyHandler(), which examines keystrokes. The same strategy is used for volume_mc , with the help of _initVolumeButton() -- up/down arrows are reserved for increasing/decreasing the volume when the mute/unmute button is focused.

ControlBar.as
private var _focus:Sprite;// mc that has focus
private var focusBorder:FocusBorder; // a sprite with a yellow border created to surround _focus object

protected function focusChangeHandler(e:FocusEvent):void {
if (focusBorder) {
// remove old focusBorder before reseting _focus
focusBorder.parent.removeChild(focusBorder);
}

_focus = Sprite(e.relatedObject);
var border:FocusBorder = new FocusBorder(_focus.getBounds(this) );// create yellow rectangle around Sprite
_focus.addChild(border);
focusBorder = border;
}

protected function controlBarKeyHandler(e:KeyboardEvent):void {
var i:uint = 0;
var seekPercent:Number ;
switch (e.keyCode){
case 38: {
//vol up
if (_focus == volume_mc) {
for ( i = 0; i < 1; i++)
{
this.dispatchEvent(new ControlBarEvent(ControlBarEvent.VOLUME_UP));
}
}
break;
}
case 40: {
//vol down
if (_focus == volume_mc) {
for (i = 0; i < 5; i++)
{
this.dispatchEvent(new ControlBarEvent(ControlBarEvent.VOLUME_DOWN));
}
}
break;
}
case 37: {
//seek rewind
if (_focus ==progress_mc.scrubber_mc) {
for (i = 0; i < 1; i++)
{
seekPercent = (currentTime -5)/ _duration;
var cbSeekBackEvent:ControlBarEvent = new ControlBarEvent(ControlBarEvent.SEEK, 0, seekPercent);
this.dispatchEvent(cbSeekBackEvent);
}
}
break;
}
case 39: {
//seek forward
if (_focus ==progress_mc.scrubber_mc) {
for (i = 0; i < 1; i++)
{
seekPercent = (currentTime +5)/ _duration;
var cbSeekForwardEvent:ControlBarEvent = new ControlBarEvent(ControlBarEvent.SEEK, 0, seekPercent);
this.dispatchEvent(cbSeekForwardEvent);
}
}
break;
}
}
}

SkinContainer also runs an initialization method for each of its components if a corresponding method name is found in the config file's XML. In initControlBarInstance(), I added a superfluous reportAccessibility() call to simply trace the control bar's children and identify what accessibility properties were added in the output. Next, a checkAccessibility() call is made to see if the auto-hide property needs to be overridden. This registers the SkinContainer as a listener to be notified when the device check by the AccessibilityManager singleton is finally executed. In the event that the check already took place, found a device, and updated; accMgr.update() makes sure the accessibilityProperties are registered with the device and the auto-hide functionality is disabled.

SkinContainer.as

public function checkAccessibility():void {
if (accMgr.accCheck(this as IAccessibilityListener) == AccessibilityManager.ACTIVE){
overrideAutoHide();
} // if unchecked, this is registered as listener -- when check completes, Subject notifies via setAccessibilityActive()
}

public function setAccessibilityActive(accActive:Boolean):void {
if (accActive) {
overrideAutoHide();
} else {
autoHide = _controlBar.autoHide;
}
}

private function overrideAutoHide():void {
_controlBar.visible = true;
autoHide = false;
}

This results in the user of an assisting device always having the controls visible, no matter what the auto-hide property is in the XML configuration file.

The SkinContainer continues to instantiate the other components. The loading indicator and closed-caption elements are fine -- a device is just going to read their text, which is all that is pertinent for each. Finally, the newly-added PosterImage is instantiated. The PosterImage constructor gives accessibilityProperties and a click listener to the play button. Accessibility properties are added without the benefit of the accessibility singleton, because the AccessibilityManager instance is not going to be available when the skin is compiled. The AccessibilityManager is still going to update all properties if it finds a device.

PosterImage.as

public function PosterImage() {
super();

playBtn.addEventListener(ToggleButtonEvent.TOGGLE_BUTTON_CLICK, dispatchStartEvent);
var accProps:AccessibilityProperties = new AccessibilityProperties();
accProps.name = "Start Video";
playBtn.accessibilityProperties = accProps;
}

The reops_config.xml file also declares a method "initPosterImageInstance" for PosterImage , which in turn calls _initPosterImageListeners().


SkinContainer.as

private function _initPosterImageListeners() :void {
_controlBar.visible = false;
_posterImage.addEventListener(PosterImageEvent.IMG_LOADED, onPosterImageStateChange);
_posterImage.addEventListener(PosterImageEvent.IMG_FAIL, onPosterImageStateChange);
_posterImage.addEventListener(PosterImageEvent.LOADING, onPosterImageStateChange);
_posterImage.addEventListener(PosterImageEvent.START, startVideo );
_posterImage.loadPosterImage();
}

The _controlBar is now invisible -- both to the screen and any screen reader. In addition, the _posterImage begins to load the image specified in the XML file and sets up a play button listener to call startVideo(). This method hides _posterImage and dispatches a ControlBarEvent.PLAY event. The loadPosterImage() call results in a bitmap added in doneLoad(), which is immediately scaled to the same dimensions of the "background" MovieClip and dumped in as a child. This allows layout changes to PosterImage stretch the bitmap accordingly. The posterImage object is also given the appropriate AccessibilityProperties:

PosterImage.as

protected function doneLoad(e:Event):void {
this.dispatchEvent(new PosterImageEvent(PosterImageEvent.IMG_LOADED));
trace("PosterImage :: DONE LOADING " + imageURL);
posterImage = Bitmap(loader.content);
posterImage.width = bg.width;
posterImage.height = bg.height;
bg.addChild(posterImage);
var accManager = AccessibilityManager.getInstance();
accManager.addAccessibility(bg, imageDescription);
accManager.update();
this.setChildIndex(playBtn, (this.numChildren - 1));// put play button on top
loader.contentLoaderInfo.removeEventListener(Event.COMPLETE, doneLoad);
loader.contentLoaderInfo.removeEventListener(ProgressEvent.PROGRESS, loadingUpdate);
loader.contentLoaderInfo.removeEventListener(IOErrorEvent.IO_ERROR, loadingError);
loader = null;
}

Finally, the _onCurrentTimeChange() method is adjusted to return the poster image to "visible" just before the video completes and is wiped out:

SkinContainer.as

protected function _onCurrentTimeChange( p_evt:TimeEvent ):void{

...

if (percentPlayed > .999) {
seekTo(0);
_mediaPlayerCore.stop();
displayPosterImage();
}
}

The displayPosterImage() and hidePosterImage() methods simply swap visibility for _posterImage and _controlBar. The poster image returns at the end of the video with the button to allow replay.

Checking the New Implementation

These changes should result in a REOPS video player with accessible controls and a dynamically loaded poster image. If we deploy the re-engineered player, we'll see:

  • a poster image appears with a play button upon load
  • control bar controls are inaccessible with the poster image and start button up
  • the auto-hide functionality is disabled when an assisting device is present
  • tabbing the control bar is relugated to the play/pause, scrubber, mute/unmute, closed caption, and screen re-size buttons
  • with the scrubber focused, the left and right arrow scrubs the video forward and backward.
  • with the mute/unmute button focused, the up/down arrows control volume
  • the poster image and start button return upon video completion

Checking the accessible script with aDesigner, we see that, when the posterImage is up, our GUI summary is:

Accessible REOPS_Player Window
Flash movie start
Graphic Akami Logo: Powering a Better Internet Since 1998, Celebrating 10 Years
Start Video Button
Flash movie end

When the movie begins playing, refreshing the summary gives us:

Accessible REOPS_Player Window
Flash movie start
play pause Button
scrubber Button
Graphic elapsed time
0:03
Graphic total time
7:50
mute unmute Button
display closed caption Button
change video size Button
Flash movie end

The REOPS player now has accessible controls for people using assisting devices! Thanks to nils.thingvall@gmail.com and all the folks supporting REOPS.

Download Accessible REOPS Player project

Wednesday, February 23, 2011

An Accessibility Manager Singleton

I created a singleton to handle accessibility chores for a flash application. The goal of the class is to centralize the checking for assisting devices (like screen readers) and update accessibility properties once the existence of a device has been determined. This demonstration will also touch on a few finer points about making flash applications accessible.

You can download the project here. It is a flash develop project, but you can just create a flash file and set the application to start Main in the stage's "Properties" window inside the fla.

Before digging into the project, here is an example of assigning an object an accessible name and description for a screen reader or other device to use:

if (Accessibility.active){
var object:Sprite = new Sprite();
object.accessibilityProperties = new AccessibilityProperties;
object.accessibilityProperties.name = "name screen reader will read";
object.accessibilityProperties.description = "description screen reader will read"

Accessibility.updateProperties();
}

Sticking points with this process are:

  1. Accessibility.active can only be determined after a certain amount of time has passed for it to be accurate, as devices like screen readers take a while to communicate with flash once a page is loaded. This call is expensive -- and really need only be performed once.
  2. The method Accessibility.updateProperties() -- required to send accessible information to the assisting device -- can't be called until Accessibility.active is true.
  3. It is preferable to create as many objects as you can with their respective accessibilityProperties before triggering the Accessibility.updateProperties() method, as this method requires a bit of overhead as well.

The singleton class I created aims to delay the checking for devices and then updating the properties one time only, after device availability has been confirmed. The singleton then holds a boolean -- available to all classes -- to avoid calling the expensive check with the Accessibility class again. If further objects need to be created with accessible properties, the singleton already knows if an assisting device is present.

Quick Overview

In my demo project, the Main class creates two objects which require accessibility information. The objects are placed on the stage and accessibility properties are applied to each with the AccessibilityManager.

The demo also illustrates how to cancel functionality that may not be acceptable for use with a screen reader -- in this case, the auto-hide functionality, as you might have with a control bar on a full-screen movie. Making objects invisible is not going to serve a non-sighted user well, so this functionality is not set when an assisting device is confirmed. The observer pattern is used to notify the Main class when the accessibility check is completed.

Finally, the demo briefly introduces the tabbing manager which repositions objects for an assisting device's script, despite where they appear on the screen.

Breaking Down the Demo

The two objects constructed in our project are adAccDisplay, a dynamic TextField inside a Sprite, and btnShowAcc, a Sprite that serves as a button to make the display show whether an assisting device is active and communicating with Flash.

Before constructing any objects, however, the Main constructor creates the AccessibilityManager singleton:

Main.as

var accManager:AccessibilityManager = AccessibilityManager.getInstance();

As no instance of AccessibilityManager has been created prior, AccessibilityManager creates an instance of itself and ensures further attempts to instantiate the class will return this instance.


AccessibilityManager.as

public static function getInstance():AccessibilityManager {
if (AccessibilityManager._instance == null){
AccessibilityManager._instance = new AccessibilityManager(new PrivateClass);
trace("AccessibilityManager Instantiated");
}
return AccessibilityManager._instance;
}

The AccessibilityManager constructor calls accCheck() which will call checkAccessibility() only after 2100 milliseconds have passed (per our timeout constant). The optional IAccessibilityListener parameter registers an observer -- we'll go into that later.


AccessibilityManager.as

public function accCheck(ob:IAccessibilityListener = null):String {
if (!_accChecked) {
if (ob){
_aListeners.push(ob);
}
setTimeout(checkAccessibility, timeout);
_currentAccessibility = AccessibilityManager.UNCHECKED;
}
return _currentAccessibility;
}

We need a delay before checking accessibility, because it takes a few seconds for a device like a screen reader to be visible to the Flash application after loading a web page. The goal is to get this boolean only once to save overhead. Our singleton class AccessibilityManager keeps the result (_accessibilityActive), which can effectively be retrieved by any other class in the application. It becomes, more or less, a "global" variable.

While the timeout is counting down, Main is continuing to build components. Our AccessibilityDisplay creates a TextField which will show the accessibility status. It gets the same instance of the the AccessibilityManager and adds accessibility properties for itself with the addAccessibility() method.

AccessibilityDisplay.as

tf = new TextField();
tf.border = true;
tf.width = 200;
tf.height = 20;

addChild(tf);
accManager = AccessibilityManager.getInstance();
accManager.addAccessibility(this, "Accessibility status:");

Looking at the addAccessibility method, we see there are arguments for the InteractiveObject, the name of the object, the description of the object, and whether the object is silent.


AccessibilityManager.as

public function addAccessibility(obj:InteractiveObject, accName:String, accDesc:String = null, silent:Boolean = false):void {
var accProp:AccessibilityProperties = new AccessibilityProperties();
accProp.name = accName;
if (accDesc && accDesc != ""){
accProp.description = accDesc;
}
if (silent){
accProp.silent = true;
} else {
obj.tabEnabled = true;
}
obj.accessibilityProperties = accProp;
}

Each object is given an instantiation of AccessibilityProperties with the applicable information. The silent argument assigns whether an assisting device will tell the user it is on the stage. If the object is not silent, it will be automatically tabEnabled. This can be reset afterword by simply calling:


theObject.tabEnabled = false;

Dynamic text, unless rendered silent, is always read by the screen reader. Putting the TextField in a Sprite with a AccessibleProperties.name gives the screen reader user context of what they are looking at. In this case "Accessibility status:" gives context for what follows in the text box. Instead of just reading "active," the screen reader will now read "Accessibility status:, active." Another example of where you might want to do this is a text field reading the total time for an mp3. Instead of the screen reader just reading "3:16," a text field inside a Sprite could be made to say "Total Time, 3:16."

The assigning of names and descriptions for "display" could have been performed at the Main level instead of inside the "display" object. This is exactly how we are assigning accessibility properties for btnShowAcc :

Main.as

var btnLabel:String = "show accessibility";
btnShowAcc = new RoundButton(btnLabel);
btnShowAcc.x = 50;
btnShowAcc.y = 150;
accMgr.addAccessibility(btnShowAcc, btnLabel);
checkAccessibility();

The button (a Sprite) will be read as "show accessibility" by the screen reader when encountered. As btnShowAcc has a buttonMode of true, most screen readers will say "button" after reading its accessibilityProperties.name. In this case, the screen reader will read: "Get Accessibility Button" when we tab to it. You don't want to give the name property a String having "button" in it -- if you give this parameter the string "play button," the screen reader will read "play button button."

If you look inside RoundButton (our button's base class), you see that we're making a custom label for the button, which is essentially a TextField.

RoundButton.as

private function makeTextField(str:String):TextField {
txt=new TextField();
addChild(txt);

...

(AccessibilityManager.getInstance()).addAccessibility(txt, "", "", true); // set text field as silent
return txt;
}

Notice that the text is set to silent. If the TextField is not set to silent, the screen reader will read: "Get Accessibility Button, Get Acessibility." This is needlessly confusing. With the TextField set to silent, the screen reader will just read "Get Accessibility Button" for the object.

Another purpose of the AccessibilityManager class is to keep track of tab order. Most of this portion of code was written by Charles Brandt. The setTab, overrideTab, getTab, enableTab, and showTabList methods are pretty well documented in the class code, but here's the skinny:

  • setTab sets tabEnabled=true with a tabIndex of the next available tab. If a specific index is given, it pushes any item at that index ahead to the next available index.

  • overrideTab sets tabEnabled=true to a specified index, but writing over the index rather than reassigning what might be there.

  • getTab returns the tabIndex for an object.

  • enableTab sets tabEnabled=true for an object.

  • showTabList is a diagnostic tool to trace all the objects and their tabIndex .

This application demonstrates using setTab() in Main.as.

Main.as

accMgr.setTab(accDisplay, 1);// accDisplay will have tabIndex of 1
accMgr.setTab(btnShowAcc, 1);// button will have tabIndex of 1 and accDisplay will be pushed back to 2
accMgr.showTabList();

The accDisplay object is given a tabIndex of 1, but then is pushed back to 2 when the btnShowAcc is given the same index of 1. This can be seen in your output window thanks to showTabList(). If the objects were tab-enabled, but not given indexes, Flash would automatically assign indexes based on their position, starting at the top left and working down and across. Now, our button will still be the first object tabbed to, even though btnShowAcc is below accDisplay . The accDisplay object doesn't actually need an index... its contents will still be read by the screen reader even if it is not tab-enabled because it has an AccessibilityProperties.name. Any text in a TextField or TextBox is always going to be read unless it is silent.

Finally, we add a listener to the button to get our Accessibility.active status from the AccessibilityManager singleton. This is written in our display's TextField by the buttonClickHandler():

Main.as

private function buttonClickHandler(e:MouseEvent):void {
accDisplay.updateText((AccessibilityManager.getInstance()).accCheck());
}

While we've been constructing these objects, the accessibility check has been waiting to fire. When and if the AccessibilityManager determines there is an assisting device, it calls Accessibility.updateProperties() to register the information with the device. Any further changes you make to an object's accessibilityProperties after the 2.1-second timeout will not be received by a device like a screen reader until this method is called again. The method is expensive, so we want to limit the times we call it. Be aware that the user will not necessarily know things have changed after an update unless they refresh the view on their device or stumble upon the change while moving the cursor.

Even if you are pretty sure your objects will be built with AccessibilityProperties before the timeout expires, you can always call the AccessibilityManager.update() method to be safe. If the singleton's _accCheck() returns "unchecked" when you call it, Flash's Accessibility.updateProperties() will automatically be performed by the AccessibilityManager if and when its check finds a device. No matter when you try and update(), the singleton will never crash the application by unnecessarily calling Accessibility.updateProperties().

Main.as

var btnLabel:String = "show accessibility";
btnShowAcc = new RoundButton(btnLabel);
btnShowAcc.x = 50;
btnShowAcc.y = 150;
accMgr.addAccessibility(btnShowAcc, btnLabel);
checkAccessibility();

In this scenario, however, we need the AccessibilityManager to notify our Main class of the check's outcome. We do this through the IAccessibilityListener interface, which has a registration method called checkAccessibility():

Main.as

// register with AccessibilityManager singleton
public function checkAccessibility():void {
var accActive:Boolean = false;
if (accMgr.accCheck(this as IAccessibilityListener) == AccessibilityManager.ACTIVE) {
accMgr.update();
}
}

// AccessibilityManager notifies if accessible device is active after its timeout calls check
public function setAccessibilityActive(accActive:Boolean):void {
setAutoHide(accActive);
}

In the checkAccessibility() method, we are doing a few things. We are ensuring that our button's accessibilityProperties update if the singleton's check was already completed. We are also registering Main to be notified when the result comes in via setAccessibilityActive(), if "unchecked." Our application withholds implementing our auto hide functionality until the notification arrives from the AccessibilityManager singleton.

Checking Our Work

You should QA your work with a screen reader, like Jaws, NVDA, or WindowEyes (which has a free, time-limited demo). If you don't have a screen reader, you should download aDesigner -- a free tool and a real timesaver. When you open aDesigner, choose Flash as your mode and put the url of your html page in the Address bar. You must have an HTML page being served to test accessibility. You should notice that the button and text box do not dissappear when using aDesigner, but do hide if you are just viewing with a browser while no assistive software is running.

Look in aDesigner's GUI Event and GUI Summary panes to see what Flash is going to tell an assisting device about your swf via its Accessibility class:



In the GUI Summary window, we can see what Flash is going to tell a device like a screen reader in our application based on MSAA. Note that all devices and software are slightly different and this information is at their mercy, so to speak. Upon first viewing, it appears the screen reader is going to say something along the lines of:

AccesibilityManagerSingleton Window

Flash movie start

show accessibility Button

Graphic Accessibility status:

Flash movie end

Notice that the "show accessibility" button is read BEFORE our "Accessibility status:" field -- this is due to the tab order we set for each object. If we took out the lines of code assigning tab indexes, the script would be flipped and "Accessibility status:, graphic" would be heard before "show accessibility, Button." As a note, it is good practice to assign a low tabIndex to controls a user will want to get to quickly, no matter where on the screen they are.

If we click our "show accessibility" button before our 2.1 seconds, the "display" text is going to change to "unchecked." If it is clicked after the timeout with aDesigner running, "active" will appear in the field. Notice that our GUI summary does not change. When using a screen reader, users can refresh their view and the device will re-read the accessible items. In aDesigner, hit the opposing gold arrows and your GUI summary will refresh.


You now see the script for the device has been updated to "Flash movie start, show accessibility Button, Accessibility status: Graphic, active, Flash movie end."

Now that the "display" text field has text in it, a screen reader is going to speak it. Because we gave an accessible name to its parent container, the text has context for the non-sighted user with an assisting device.

Note that if other objects are going to appear on the stage later (e.g., if the button were to cause a MovieClip to appear), the addAccessibility() and the update() methods would need to be called again via the AccessibilityManager. I kept these methods unlinked because you may want to add properties for several objects and then update(). The main point of the AccessibilityManager is that it keeps you from calling Accessibility.active again, no matter where you are in the application.

Download project

Thursday, February 26, 2009

Accessibility Lesson: Reading order with TabOrder

Reading order for a flash file in a screenreader can be controlled by setting the tab order (item.tabIndex) on all the elements.

Elements with a specified tab order will read before static text without a tab order assigned, despite what positions the elements appear. E.G., a button with a specified tab order that sits below static text will be read before the static text, even though it sits below it.

Actual use of the "tab" key in a screenreader generally tabs only to interactive objects, like buttons. Dynamic text with a specified tab order won't necessarily be tabbed to with the tab key. The arrow keys should be able to get you to any element in the flash file that is visible to a screen reader.

If you have a button with dynamic text inside it, you may want to consider setting the tab order for the dynamic text box itself rather than the button it sits inside. Otherwise you would have to item_accProp.name on the Button as well -- then the screen reader would read the accessibility name property of the button, and then after all the tabbed elements, read what is in the text box, essentially reading the button twice. It is probably a good idea to make text inside buttons dynamic text instead of static text.

Tuesday, January 6, 2009

Hiding Object Specifically Created for Screenreader From Sighted Users

It seems pretty obvious, but just put it behind a movie clip which is a background image.

For a flash movie, screen readers will not read the following:

  • Any object off stage

  • Any object that has visible=false

  • Any object that has alpha = 0



To make an object invisible to a screen reader, you have to set its accessibility properties to "silent."

Tuesday, December 30, 2008

Accessibility Lesson: Use MouseEvent.CLICK

When you tab through buttons with a screenreader, you click the button hitting the ENTER key. The MouseUP and MouseDown are not going to work. Use MouseEvent.CLICK to capture the ENTER key event.