12.10Tutorial - Playing Video with SubTitles in AS3 - Part 1
Hi Everyone,
The series on Playing FLV files in AS3 was probably the most popular post on this blog. So I thought I would take it one step further: show SubTitles from an .SRT file over the FLV while it is playing.
First, you need to read this post by Jankees van Woezik. He did a great job at making a SRT parser, although; well, I wouldn't convert everything right away but rather do it on-demand; but that is a small detail.
Then you need to find yourself a SRT file. There are plenty on the internet, I took the Star Wars subtitles, changed the time a little bit, and ended up with this SRT file.
The video I chose I got using TubeGrip, it is basically a Telemark skiing video, which I love and miss. This video is a mp4 format using H.264 codec, which is supported by Flash Player 10 and over. Much more pretty. You can get those from TubeGrip off YouTube.
Now, that being said, I will use
- a
flash.display.Videoobject to display Video - a
newcommerce.media.FLVPlayerobject to control & play the Video - a
nl.inlet42.data.subtitles.SubTitleParserto parse the SRT file - a
flash.net.URLLoaderobject to load the .srt file - a an array of
nl.inlet42.data.subtitles.SubTitleDataobjects where the subtitle data is stored in a usable form
We need to create a component, or Control as I like to call them, which will take SubTitleData objects one at a time and display it.
SubTitleViewer
This component needs to do the following:
- Position itself appropriately on the stage, taking for granted the video is taking full
stageWidth&stageHeight. - Show a dark background on which a textfield containing white text will appear
- Render the subtitle text appropriately
First, I derive this control from newcommerce.controls.BaseControl which I started to dissect here but unfortunately never finished.. (I will one day; hopefully). Basically it implements a frame to work in, which streamlines the main operations you need to deal with when making a visual component.
Then for the creation of the control, I use the example.SubtitleViewerProperties class which contains all the information needed to create the component. Here is the source:
-
package example
-
{
-
import flash.display.Stage;
-
import flash.text.Font;
-
/**
-
* ...
-
* @author Martin Legris
-
*/
-
public class SubtitleViewerProperties
-
{
-
protected var _stage:Stage;
-
protected var _width:Number;
-
protected var _height:Number;
-
protected var _font:Font;
-
-
public function get font():Font { return _font; }
-
public function get height():Number { return _height; }
-
public function get width():Number { return _width; }
-
public function get stage():Stage { return _stage; }
-
-
public function SubtitleViewerProperties(stage:Stage, width:Number = 200, height:Number = 90, font:Font = null)
-
{
-
_stage = stage;
-
_width = width;
-
_height = height;
-
_font = font;
-
}
-
-
}
-
}
So basically, Font, stage reference, width and height.
SubtitleViewer Code
Description is inside of the code. Please read comments for details..
-
package example
-
{
-
import ascb.drawing.Pen;
-
import flash.display.Shape;
-
import flash.display.Sprite;
-
import flash.display.Stage;
-
import flash.text.Font;
-
import flash.text.TextField;
-
import flash.text.TextFieldAutoSize;
-
import flash.text.TextFormat;
-
import flash.text.TextFormatAlign;
-
import newcommerce.controls.BaseControl;
-
import nl.inlet42.data.subtitles.SubTitleData;
-
-
/**
-
* ...
-
* @author Martin Legris
-
*/
-
public class SubtitleViewer extends BaseControl
-
{
-
// properties
-
protected var _props:SubtitleViewerProperties;
-
-
// container, to contain the textfield and has a mask applied, for eventual tweening between subTitltes
-
protected var _container:Sprite;
-
-
// two textfields, for eventual tweening..
-
protected var _oldField:TextField;
-
protected var _curField:TextField;
-
-
// a bg shape, that contains the dark semi-transparent area underneath the subtitles
-
protected var _bg:Shape;
-
// the mask, to make sure text doesn't come out of the drawn bg..
-
protected var _mask:Shape;
-
-
// the current subtitledata
-
protected var _curSub:SubTitleData;
-
-
public function get props():SubtitleViewerProperties { return _props; }
-
public function set props(value:SubtitleViewerProperties):void { _props = value; }
-
-
// constructor takes a SubtitleViewerProperties instance, that contains the parameters needed for construction.
-
public function SubtitleViewer(props:SubtitleViewerProperties)
-
{
-
super({_width:props.width, _height:props.height, props:props});
-
}
-
-
// init function called at construction time before the creation of children
-
override protected function init():void
-
{
-
_props.stage.addChild(this);
-
super.init();
-
}
-
-
// this is where the visual childs are created
-
override protected function createChilds():void
-
{
-
super.createChilds();
-
-
// first create the bg
-
_bg = new Shape();
-
addChild(_bg);
-
// I like to name all displayObjects appropriately..
-
_bg.name = "bg";
-
-
// create the container
-
_container = new Sprite();
-
_container.name = "container";
-
addChild(_container);
-
-
// create the mask
-
_mask = new Shape();
-
_mask.name = "mask";
-
addChild(_mask);
-
-
// apply the mask
-
_container.mask = _mask;
-
-
// create the two textfields
-
createField("curField");
-
createField("oldField");
-
-
// put fake text... for testing purposes.
-
createFakeContent();
-
}
-
-
// this function creates a textField using the font in the _props member of type SubTitleViewerProperties.
-
protected function createField(name:String = ""):TextField
-
{
-
// create the textfield
-
var field:TextField = new TextField();
-
-
// add it to the container
-
_container.addChild(field);
-
-
// name it
-
field.name = name;
-
-
// assign it to the member property of this class
-
this["_" + name] = field;
-
-
// we want multi-line text, no auto-sizing, and wordwrap
-
field.multiline = true;
-
field.autoSize = TextFieldAutoSize.NONE;
-
field.wordWrap = true;
-
-
// get a reference to the font.. to make code cleaner
-
var font:Font = _props.font;
-
-
// get the default TextFormat on the newly created textfield
-
var tf:TextFormat = field.defaultTextFormat;
-
-
// if we did get a font, use it's name & style + make the TextField use embedded fonts
-
if (font != null)
-
{
-
tf.font = font.fontName;
-
tf.bold = (font.fontStyle.toLowerCase().indexOf("bold") != -1);
-
tf.italic = (font.fontStyle.toLowerCase().indexOf("italic") != -1);
-
field.embedFonts = true;
-
}
-
-
// set text size to 18
-
tf.size = 18;
-
-
// align text in the center (this seems to be the standard for subtitles..)
-
tf.align = TextFormatAlign.CENTER;
-
-
// we want white colored subtitles
-
tf.color = 0xFFFFFF;
-
-
field.defaultTextFormat = tf;
-
try { field.setTextFormat(tf, 0, field.text.length); } catch (e:RangeError) { };
-
-
return field;
-
}
-
-
// simple function to create fake content.. just put text in the _curField and set it's height
-
protected function createFakeContent():void
-
{
-
_curField.text = "my text is original";
-
_curField.height = _curField.textHeight + 5;
-
}
-
-
// function called to position the child elements inside of the component.. usually called at resize
-
override protected function arrange():void
-
{
-
super.arrange();
-
-
// position the viewer on the stage, in the middle (x axis) and 80 pixel from the bottom (y axis)
-
x = (stage.stageWidth - _width) / 2;
-
y = (stage.stageHeight - _height - 80);
-
-
// position the textfield, leaving a 7 pixel padding at the top, bottom, left, right
-
_curField.x = 7;
-
_oldField.x = 7;
-
_curField.width = _width - 14;
-
_oldField.width = _width - 14;
-
}
-
-
// function called to redraw the content of the component.. usually when it is resize & at creation time
-
override protected function redraw():void
-
{
-
super.redraw();
-
-
// draw the bg
-
drawBg();
-
-
// draw the mask
-
drawMask();
-
}
-
-
// I don't think I need to explain this..
-
protected function drawBg():void
-
{
-
var p:Pen = new Pen(_bg.graphics);
-
p.clear();
-
p.lineStyle(0, 0, 0);
-
p.beginFill(0x000000, 0.5);
-
p.drawRoundRect(0, 0, _width, _height, 9);
-
p.endFill();
-
}
-
-
protected function drawMask():void
-
{
-
var p:Pen = new Pen(_mask.graphics);
-
p.clear();
-
p.lineStyle(0, 0, 0);
-
p.beginFill(0x000000, 1);
-
p.drawRoundRect(0, 0, _width, _height, 9);
-
p.endFill();
-
}
-
-
// function which sets the SubTitleData
-
public function setSubTitle(sub:SubTitleData):void
-
{
-
// check to see if it is a new one
-
if (_curSub != sub)
-
{
-
// if it is, keep a reference to it
-
_curSub = sub;
-
-
// then render it's text
-
renderSubTitle();
-
}
-
}
-
-
// render the text
-
protected function renderSubTitle():void
-
{
-
// make sure it isn't null
-
if (_curSub != null)
-
{
-
// assign the text to the currentField
-
_curField.text = _curSub.text;
-
-
// show the background (not used yet)
-
show();
-
}
-
else
-
{
-
// otherwise set text to nothing.. and hide
-
_curField.text = "";
-
hide();
-
}
-
}
-
-
protected function show():void
-
{
-
-
}
-
-
protected function hide():void
-
{
-
-
}
-
}
-
}
There we got for part 1. Second part (tomorrow), I will show how to put it all together. Full source code is here if you are interested..
cheers!
Martin

Thanks for mentioning me.. I am glad you could use my code.
December 11th, 2009 at 12:52 pm
[...] intense creativity ยป Tutorial – Playing Video with SubTitles in … [...]
December 26th, 2009 at 6:05 pm