From time to time, I've seen someone requesting the ability to change captions and/or audiodescription files on-the-fly, to change languages, etc.
Seems it's easy to modify the captions and audiodescription plugins to load a new file anytime.
Seems to work … well … see for yourself:
http://willswonders.myip.org:8074/player5/Simple_TitlePlugin_v5.html
(Note: Loading the HD file from the playlist doesn't work yet.)
Have fun! Play with the buttons — :D
Look at the page source for the modified plugins.
Here's the ActionScript — v4 & v5 compatible.
Audiodescription.as
/**
* Plugin for playing a closed audiodescription with a video.
**/
package com.jeroenwijering.plugins
{
import com.jeroenwijering.events.*;
import com.jeroenwijering.utils.Logger; import flash.display.*;
import flash.events.*;
import flash.media.*;
import flash.net.*;
//...Will
import flash.external.ExternalInterface;
import flash.ui.Keyboard;
public class Audiodescription extends MovieClip implements PluginInterface
{
[Embed(source="../../../controlbar.png")]
private const ControlbarIcon:Class;
[Embed(source="../../../dock.png")]
private const DockIcon:Class;
/** Reference to the dock button. **/
private var button:MovieClip;
/** List with configuration settings. **/
public var config:Object =
{
file:undefined,
state:true,
volume:90
}
/** Reference to the MVC view. **/
private var view:AbstractView;
/** Reference to the icon. **/
private var icon:Bitmap;
/** sound object to be instantiated. **/
private var sound:Sound;
/** Sound channel object. **/
private var channel:SoundChannel;
/** Constructor; not much going on. **/
public function Audiodescription():void {};
/** Initing the plugin. **/
public function initializePlugin(vie:AbstractView):void
{
view = vie;
view.addControllerListener(ControllerEvent.ITEM, itemHandler);
view.addModelListener(ModelEvent.TIME, timeHandler);
view.addModelListener(ModelEvent.STATE, stateHandler);
if(view.config['dock'])
{
button = view.getPlugin('dock').addButton(DisplayObject(new DockIcon()), 'is on', clickHandler);
}
else if(view.getPlugin('controlbar'))
{
icon = new ControlbarIcon();
view.getPlugin('controlbar').addButton(icon, 'audiodescription', clickHandler);
}
setState(config['state']);
//...Will
if(ExternalInterface.available)
{
ExternalInterface.addCallback('changeAudioFile', changeAudioFile);
}
};
/** Clicking the hide button. **/
private function clickHandler(evt:MouseEvent):void
{
setState(!config['state']);
};
/** Check for audiodescription with a new item. **/
private function itemHandler(evt:ControllerEvent = null):void
{
var file:String;
if(view.playlist[view.config['item']]['audiodescription.file'])
{
file = view.playlist[view.config['item']]['audiodescription.file'];
}
else if(view.playlist[view.config['item']]['audiodescription'])
{
file = view.playlist[view.config['item']]['audiodescription'];
}
else if(view.config['audiodescription.file'])
{
file = view.config['audiodescription.file'];
}
else if(view.config['audio'])
{
// Legacy support
file = view.config['audio'];
}
if(file)
{
config['file'] = file;
try
{
if(channel)
{
channel.stop();
}
sound = new Sound(new URLRequest(config['file']));
channel = sound.play();
setVolume();
//...Will - if player state is IDLE on startup, then stop the audio
//ExternalInterface.call("(function(input){alert('Player State: ' + input);})", view.config['state']);
if((view.config['state'] == 'IDLE') && (channel))
{
channel.stop();
}
}
catch (err:Error)
{
Logger.log(err.message, 'audiodescription');
}
}
};
//...Will - change audiodescription file on-the-fly
public function changeAudioFile(audiofile:String):void
{
//ExternalInterface.call("(function(input){alert('New Audiodescription File: ' + input);})", audiofile);
if(audiofile)
{
config['file'] = audiofile;
try
{
if(channel)
{
channel.stop();
}
sound = new Sound(new URLRequest(config['file']));
channel = sound.play();
setVolume();
if((view.config['state'] == 'IDLE') && (channel))
{
channel.stop();
}
}
catch (err:Error)
{
Logger.log(err.message, 'audiodescription');
}
}
};
/** Turn the audiodescription on/off. **/
public function setState(stt:Boolean):void
{
config['state'] = stt;
var cke:SharedObject = SharedObject.getLocal('com.jeroenwijering','/');
cke.data['audiodescription.state'] = stt;
cke.flush();
setVolume();
if(stt)
{
if(button)
{
button.field.text = 'is on';
}
else
{
icon.alpha = 1;
}
}
else
{
if(button)
{
button.field.text = 'is off';
}
else
{
icon.alpha = 0.3;
}
}
};
/** Set the volume level. **/
private function setVolume():void
{
var trf:SoundTransform = new SoundTransform(config['volume'] / 100);
if(!config['state'])
{
trf.volume = 0;
}
if(channel)
{
channel.soundTransform = trf;
}
};
/** The statehandler manages audio pauses. **/
private function stateHandler(evt:ModelEvent):void
{
//ExternalInterface.call("(function(input){alert('New State: ' + input);})", evt.data.newstate);
switch(evt.data.newstate)
{
case ModelStates.PAUSED:
case ModelStates.COMPLETED:
case ModelStates.IDLE:
if(channel)
{
channel.stop();
}
break;
}
};
/** Check timing of the player to sync audio if needed. **/
private function timeHandler(evt:ModelEvent):void
{
var pos:Number = evt.data.position;
if(channel && view.config['state'] == ModelStates.PLAYING && Math.abs(pos-channel.position / 1000) > 0.5)
{
channel.stop();
channel = sound.play(pos * 1000);
setVolume();
}
};
};
}
Captions.as
/*
* Plugin for playing closed captions with a video.
*/
package com.jeroenwijering.plugins
{
import com.jeroenwijering.events.*;
import com.jeroenwijering.parsers.*;
import com.jeroenwijering.utils.Logger; import flash.display.*;
import flash.events.*;
import flash.filters.DropShadowFilter;
import flash.net.*;
import flash.text.*;
//...Will
import flash.external.ExternalInterface;
import flash.ui.Keyboard;
public class Captions extends MovieClip implements PluginInterface
{
[Embed(source = '../../../controlbar.png')]
private const ControlbarIcon:Class;
[Embed(source = '../../../dock.png')]
private const DockIcon:Class;
/** List with configuration settings. **/
public var config:Object =
{
back:false,
file:undefined,
fontsize:14,
state:true,
//...Will
listener:undefined
};
/** XML connect and parse object. **/
private var loader:URLLoader;
/** Reference to the MVC view. **/
private var view:AbstractView;
/** Icon for the controlbar. **/
private var icon:Bitmap;
/** Reference to the textfield. **/
public var field:TextField;
/** Reference to the background graphic. **/
private var back:MovieClip;
/** The array the captions are loaded into. **/
private var captions:Array;
/** Textformat entry for the captions. **/
private var format:TextFormat;
/** Currently active caption. **/
private var current:Number;
/** Reference to the dock button. **/
private var button:MovieClip;
public function Captions()
{
loader = new URLLoader();
loader.addEventListener(Event.COMPLETE, loaderHandler);
};
/** Clicking the hide button. **/
private function clickHandler(evt:MouseEvent):void
{
hide(!config['state']);
};
private function drawClip():void
{
back = new MovieClip();
back.graphics.beginFill(0x000000, 0.75);
back.graphics.drawRect(0, 0, 400, 20);
addChild(back);
format = new TextFormat();
format.color = 0xFFFFFF;
format.size = config['fontsize'];
format.align = 'center';
format.font = '_sans';
format.leading = 4;
field = new TextField();
field.width = 400;
field.height = 10;
field.y = 5;
field.autoSize = 'center';
field.selectable = false;
field.multiline = true;
field.wordWrap = true;
field.defaultTextFormat = format;
addChild(field);
if(config['back'] == false)
{
back.alpha = 0;
field.filters = new Array(new DropShadowFilter(0, 45, 0, 1, 2, 2, 10, 3));
}
};
/** Show/hide the captions **/
public function hide(stt:Boolean):void
{
config['state'] = stt;
visible = config['state'];
if(config['state'])
{
if(button)
{
button.field.text = 'is on';
}
else
{
icon.alpha = 1;
}
}
else
{
if(button)
{
button.field.text = 'is off';
}
else
{
icon.alpha = 0.3;
}
}
var cke:SharedObject = SharedObject.getLocal('com.jeroenwijering', '/');
cke.data['captions.state'] = stt;
cke.flush();
};
/** Initing the plugin. **/
public function initializePlugin(vie:AbstractView):void
{
view = vie;
view.addControllerListener(ControllerEvent.ITEM, itemHandler);
view.addControllerListener(ControllerEvent.RESIZE, resizeHandler);
view.addModelListener(ModelEvent.TIME, timeHandler);
view.addModelListener(ModelEvent.STATE, stateHandler);
view.addModelListener(ModelEvent.META, metaHandler);
drawClip();
mouseEnabled = false;
mouseChildren = false;
if(view.config['dock'])
{
button = view.getPlugin('dock').addButton(new DockIcon(), 'is on', clickHandler);
}
else if(view.getPlugin('controlbar'))
{
icon = new ControlbarIcon();
view.getPlugin('controlbar').addButton(icon, 'captions', clickHandler);
}
//...Will
if(ExternalInterface.available)
{
ExternalInterface.addCallback('toggleCaptions', toggleCaptions);
}
//...Will
stage.addEventListener(KeyboardEvent.KEY_DOWN, keyDownHandler);
//...AM
if(ExternalInterface.available)
{
ExternalInterface.addCallback('showCaptions', showCaptions);
}
hide(config['state']);
//...Will
if(ExternalInterface.available)
{
ExternalInterface.addCallback('changeCaptionsFile', changeCaptionsFile);
}
};
//...Will - toggle captions from JSPAI
public function toggleCaptions(caps:Boolean):void
{
//ExternalInterface.call("(function(input){alert(input);})", "Toggle Captions");
hide(!config['state']);
};
//...Will - toggle captions with C/c
public function keyDownHandler(e:KeyboardEvent):void
{
//ExternalInterface.call("(function(input){alert(input);})", "Toggle Captions");
// C/c
if(e.charCode == 67 || e.charCode == 99)
{
hide(!config['state']);
}
};
//...AM
public function showCaptions(caps:Boolean):void
{
//ExternalInterface.call("(function(input){alert('Captions: ' + input);})", caps);
hide(caps);
};
/** Check for captions with a new item. **/
private function itemHandler(evt:ControllerEvent = null):void
{
current = 0;
captions = new Array();
config['file'] = undefined;
field.htmlText = '';
var file:String;
if(view.playlist[view.config['item']]['captions.file'])
{
file = view.playlist[view.config['item']]['captions.file'];
}
else if(view.playlist[view.config['item']]['captions'])
{
file = view.playlist[view.config['item']]['captions'];
}
else if(view.config['captions.file'])
{
file = view.config['captions.file'];
}
else if(view.config['captions'])
{
file = view.config['captions'];
}
if(file)
{
config['file'] = file;
try
{
loader.load(new URLRequest(config['file']));
}
catch (err:Error)
{
Logger.log(err.message, 'captions');
}
}
};
//...Will - change captions file on-the-fly
public function changeCaptionsFile(capsfile:String):void
{
//ExternalInterface.call("(function(input){alert('New Captions File: ' + input);})", capsfile);
if(capsfile)
{
config['file'] = capsfile;
try
{
loader.load(new URLRequest(config['file']));
}
catch (err:Error)
{
Logger.log(err.message, 'captions');
}
}
};
/** Captions are loaded; now display them. **/
private function loaderHandler(evt:Event):void
{
var ext:String = config['file'].substr(-3);
//captions = new Array();
if(ext == 'srt' || ext == 'txt')
{
captions = SRTParser.parseCaptions(String(evt.target.data));
}
else
{
captions = TTParser.parseCaptions(XML(evt.target.data));
}
if(captions.length == 0)
{
Logger.log('Not a valid TimedText or SRT file.', 'captions');
}
};
/** Check for captions in metadata. **/
private function metaHandler(evt:ModelEvent):void
{
var txt:String;
var fnd:Boolean;
if(evt.data.type == 'caption')
{
txt = evt.data.captions;
fnd = true;
}
else if(evt.data.type == 'textdata')
{
txt = evt.data.text;
fnd = true;
}
if(fnd == true && !captions)
{
field.htmlText = txt + ' ';
resizeHandler();
Logger.log(txt, 'caption');
}
};
/** Resize the captions if the display changes. **/
private function resizeHandler(evt:ControllerEvent = undefined):void
{
back.height = field.height + 10;
width = view.config['width'];
scaleY = scaleX;
y = view.config['height'] - height;
};
/** Set a caption on screen. **/
private function setCaption(pos:Number):void
{
for(var i:Number = 0; i < captions.length; i++)
{
if((captions<em>['begin'] < pos) && ((i == captions.length - 1) || (captions[i + 1]['begin'] > pos)))
{
current = i;
field.htmlText = captions<em>['text'];
resizeHandler();
Logger.log(captions<em>['text'], 'caption');
//...Will
if(config['listener'])
{
try
{
ExternalInterface.call(config['listener'], captions<em>['text']);
}
catch(err:Error)
{
}
}
return;
}
}
};
/** Check state of the player to display captions. **/
private function stateHandler(evt:ModelEvent):void
{
if(((view.config['state'] == ModelStates.PLAYING) || (view.config['state'] == ModelStates.PAUSED)) && config['state'])
{
visible = true;
}
else
{
visible = false;
}
};
/** Check timing of the player to sync captions. **/
private function timeHandler(evt:ModelEvent):void
{
var pos:Number = evt.data.position;
if(captions && ((captions[current]['begin'] > pos) || (captions[current + 1] && captions[current + 1]['begin'] < pos)))
{
setCaption(pos);
}
};
};
}


HD files from the playlist has been fixed.
See this thread:
http://www.longtailvideo.com/support/forum/Plugins/21891/v5-HD-Plugin