Subtitling in Brightcove 3

Written by Bob de Wit. Posted in Brightcove, Developer Blog, Flash, PEAR

This customized player shows what I believe to be the simplest and most effective approach to a basic implementation of subtitling (or captioning) in normal and full screen mode. Cue point definitions are not required, this example uses the video progression index and standard subtitling text formats. This means it’s really easy to use this with videos for which you have the subtitling text and time indexes available in a standard format like .srt. The article also shows how to use a free subtitling tool to create subtitle files you can use with this approach. The subtitling text size will automatically switch when in player or full screen mode. Working demo here. And here’s how you do it…

Update: The Caption Controls

I’ve had some questions around the Subtitle and FSTitle controls used in the code. the Subtitle and FSTitle (full-screen title) are simple MXML components that define the font size, type, alignment etc for the text to be displayed in resp. Player mode and Full Screen mode. I’ve used this markup for the subtitle component, but you can tweak it to your own color and styling easily:
<?xml version=“1.0″ encoding=“utf-8″?> <mx:Text xmlns:mx=“http://www.adobe.com/2006/mxml” enabled=“true” selectable=“false” disabledColor=“#FFFFFF” color=“#FFFFFF” fontSize=“14″ fontWeight=“bold” horizontalCenter=“0″ bottom=“0″ width=“100%” textAlign=“center” height=“100%” fontAntiAliasType=“advanced”> <mx:filters> <mx:DropShadowFilter distance=“2″ angle=“45″/> <mx:DropShadowFilter distance=“2″ angle=“315″/> </mx:filters> </mx:Text>

Creating Subtitle Files

There are several standards for subtitle text files. What they all have in common is a combination of time codes and the text to display. In this rudimentary example, I’ll be using a plain text, format, which is widely used and has the advantage of having free subtitle editing tools available. I often use a free tool called SubCreator, which you can download here. It’s a basic subtitle editor with nifty keyboard shortcuts that saves your subtitles in easily readable text files. I recommend you use Windows Media Player (WMP) as the viewer and download the FLV codec for WMP from the same site. That way, you can view Flash Streaming Video files from WMP and also in the subtitle editor. The text file for the movie shown above looks something like this:
00:00:23.0:My Son
00:00:25.5:The day you were born -
00:00:26.7:the very forests of Lordareon whispered the name -
00:00:34.0:Arthas.
00:01:21.0:My child
00:01:24.0:I watched with pride as you grew into a weapon -
00:01:29.5:Of righteousness.
00:01:39.0:Remember - our line has always ruled
00:01:43.0:with wisdom, and strength.
00:01:48.5:And I know you will show restraint
00:01:51.6:when exercising your great power.
00:02:46.3:But the truest victory, my son,
00:02:49.7:is sturring the hearts of your people.
00:03:01.4:I tell you this
00:03:04.0:for when my days have come to an end,
00:03:08.4:You shall be king.
Pretty straightforward. So now let’s get down to integrating this with BrightCove 3.

Creating a Standard Flash Embedded Player

I’ve already covered the basics for this in several other articles, so here’s just a quick rundown:
  • Create a player in the BC3 Console
  • Copy the ActionScript code for the player from the console
  • Start a new Flash project
  • Save the pasted code as BrightcovePlayer.as withing your project
Then add the standard code to embed the player into your Flash project:
import BrightcovePlayer; stage.scaleMode = StageScaleMode.NO_SCALE; stage.align = StageAlign.TOP_LEFT; Security.allowDomain(“*”); var bcp:BrightcovePlayer = new BrightcovePlayer(); bcp.addEventListener(“templateLoaded”, templateLoaded); addChild(bcp); var mPlayer:Object; var mContent:Object; var mExp:Object; function templateLoaded(evt:Event):void { mPlayer = bcp.getModule(“videoPlayer”); mContent = bcp.getModule(“content”); mExp = bcp.getModule(“experience”); mExp.addEventListener(“templateReady”, templateReady); }
For details, please refer to this BC3 Player API article.

Reading the Subtitle File

Since this is a basic example, I’ll just insert the text code into an array of strings, but if you extend on the principle, the subtitle file could be served by a web server dynamically.
var subtitles:Array = new Array(16); subtitles[0] = “00:00:23.0:My Son”; subtitles[1] = “00:00:25.5:The day you were born -”;

Setting up for displaying subtitles

We’ll be creating two overlays: for normal andfull screen display. For this purpose, I’ve created two simple movieclips that contain a dynamic TextField with the appropriate font and color settings, and added these to the library. Why don’t I put them right into the Flash movie in a separate layer? Because the BC3 player is created dynamically and placed in the movie with the addChild() function. This puts the player on the top visible layer in your Flash movie and thus obscures everything that was created statically. So we’ll create the subtitle movieclips dynamically as well – after we’ve created the BC3 player. (Don’t forget to expose your movieclips with Linkage). We’ll also need to know which text is displaying, and how long it’s been on the screen, so we can set a timeout. The last thing we need is to know the timecode of the movie playing. Fortunately, all we need to do is add an event listener for the BC3 Player. The code for all of this is pretty straightforward:
var cursub:String = “”; var secs:int = 0; var st:Subtitle = new Subtitle(); addChild(st); st.visible = false; st.x = 0; st.y = 360; var ft:FSTitle = new FSTitle(); addChild(ft); ft.visible = false; mPlayer.addEventListener(“videoProgress”, videoProgress);
And now all we need to do is code the videoprogress event handler. This will check the time code and the state of the player (normal or full screen), and display the corresponding subtitle movieclip, while making the other invisible. To do so, we simply compare the time code of the stream to the timecode in the text. If they correspond, we set the Caption of the visible Subtitle movieclip and start a countdown of 4 seconds:
function videoProgress(evt:Object):void { var time : Number = evt.position; var seconds : int = time % 60; var minutes : int = ( time / 60 ) % 60; var hours : int = (time / 3600 ) % 3600; var sText:String = addLeading( “0″, String( hours ), 2 ) + “:” + addLeading( “0″, String( minutes ), 2 ) + “:” + addLeading( “0″, String( seconds ), 2 ); for(var i:int = 0; i &lt; 16; i++) { var ws:String = subtitles[i]; if( ws.indexOf(sText) == 0 ) { cursub = ws; secs = 0; break; } } secs++; if (secs &gt;= 100 ) { cursub = “”; } st.Caption.text = cursub.substr(11,255); ft.Caption.text = st.Caption.text; if( stage.displayState == StageDisplayState.FULL_SCREEN) { ft.x = 0; ft.y = stage.stageHeight - 120; ft.Caption.width = stage.stageWidth; st.visible = false; ft.visible = true; } else { st.visible = true; ft.visible = false; } }
That’s it! Full Code Listing
import BrightcovePlayer; import flash.display.Stage; import flash.display.StageDisplayState; import flash.display.*; import flash.events.*; import flash.geom.Rectangle; stage.scaleMode = StageScaleMode.NO_SCALE; stage.align = StageAlign.TOP_LEFT; Security.allowDomain(“*”); var bcp:BrightcovePlayer = new BrightcovePlayer(); bcp.addEventListener(“templateLoaded”, templateLoaded); addChild(bcp); var st:Subtitle = new Subtitle(); addChild(st); st.visible = false; st.x = 0; st.y = 360; var ft:FSTitle = new FSTitle(); addChild(ft); ft.visible = false; var mPlayer:Object; var mContent:Object; var mExp:Object; var subtitles:Array = new Array(16); subtitles[0] = “00:00:23.0:My Son”; subtitles[1] = “00:00:25.5:The day you were born -”; subtitles[2] = “00:00:26.7:the very forests of Lorderon whispered the name -”; subtitles[3] = “00:00:34.0:Arthas.”; subtitles[4] = “00:01:21.0:My child”; subtitles[5] = “00:01:24.0:I watched with pride as you grew into a weapon – “; subtitles[6] = “00:01:29.5:Of righteousness.”; subtitles[7] = “00:01:39.0:Remember – our line has always ruled”; subtitles[8] = “00:01:43.0:with wisdom, and strength.”; subtitles[9] = “00:01:48.5:And I know you will show restraint”; subtitles[10] = “00:01:51.6:when exercising your great power.”; subtitles[11] = “00:02:46.3:But the truest victory, my son,”; subtitles[12] = “00:02:49.7:is sturring the hearts of your people.”; subtitles[13] = “00:03:01.4:I tell you this”; subtitles[14] = “00:03:04.0:for when my days have come to an end,”; subtitles[15] = “00:03:08.4:You shall be king.”; var cursub:String = “”; var secs:int = 0; stop(); function templateLoaded(evt:Event):void { trace( “Template Loaded”); mPlayer = bcp.getModule(“videoPlayer”); mContent = bcp.getModule(“content”); mExp = bcp.getModule(“experience”); mExp.addEventListener(“templateReady”, templateReady); mExp.addEventListener(“enterFullScreen”, enterFullScreen); mExp.addEventListener(“exitFullScreen”, exitFullScreen); mContent.addEventListener(“videoLoad”, videoLoad); mPlayer.addEventListener(“videoStart”, videoStart); mPlayer.addEventListener(“endBuffering”, endBuffering); mPlayer.addEventListener(“videoProgress”, videoProgress); } function enterFullScreen(evt:Event):void { trace( “enterFullScreen”); } function exitFullScreen(evt:Event):void { trace( “exitFullScreen”); } function templateReady(evt:Event):void { trace( “Template Ready”); } function videoLoad(evt:Event):void { trace( “videoLoad Ready”); } function videoStart(evt:Event):void { trace( “videoStart Ready”); } function videoProgress(evt:Object):void { var time : Number = evt.position; var seconds : int = time % 60; var minutes : int = ( time / 60 ) % 60; var hours : int = (time / 3600 ) % 3600; var sText:String = addLeading( “0″, String( hours ), 2 ) + “:” + addLeading( “0″, String( minutes ), 2 ) + “:” + addLeading( “0″, String( seconds ), 2 ); for(var i:int = 0; i &lt; 16; i++) { var ws:String = subtitles[i]; //trace(ws); if( ws.indexOf(sText) == 0 ) { cursub = ws; secs = 0; break; } } secs++; if (secs &gt;= 100 ) { cursub = “”; } st.Caption.text = cursub.substr(11,255); ft.Caption.text = st.Caption.text; if( stage.displayState == StageDisplayState.FULL_SCREEN) { ft.x = 0; ft.y = stage.stageHeight - 120; ft.Caption.width = stage.stageWidth; st.visible = false; ft.visible = true; } else { st.visible = true; ft.visible = false; } } function endBuffering(evt:Event):void { trace( “endBuffering Ready”); } function addLeading( leading : String, str : String, len : int ) : String{ var limit : int = len - str.length; for( var i : int = 0 ; i &lt; limit ; i++ ){ str = leading + str; } return str; }

Trackback from your site.

Comments (6)

  • oliver

    |

    Hi attempting the above – not sure what to call the dynamic instances of the text fields and the movieclip names??

    getting following error…

    1046: Type was not found or was not a compile-time constant: Subtitle.
    1046: Type was not found or was not a compile-time constant: FSTitle.

    changes instance names to Subtitle && FSTitle

    and the movieclip names to st and ft

    not sure how to progress – any help much appreciated

    reagrds

    oli

    Reply

    • Bob de Wit

      |

      Hi Oli,

      the Subtitle and FSTitle (full-screen title) are simple MXML components that define the font size, type, alignment etc for the text to be displayed in resp. Player mode and Full Screen mode. I’ve used this for the subtitle component, but you can tweak it to your own color and styling easily. I’ve updated the article and included a sample component markup you could use.

      Reply

  • oliver

    |

    I have had no luck in flex, and am still quite new to the ide. so i moved onto flash – but the problem i get in flash is that the subtitles dont display when the they should please have a look at this code if you get chance to see if you can spot any obvious errors..
    regards

    oli

    http://dev.oliverdalton.com/bc/thecode.txt

    Reply

  • Bob de Wit

    |

    Some questions:
    I can’t see from your code how you redefined the subtitle movieclips in Flash. Did you specify the labels to be dynamic text?
    If you specify an initial text for the labels, does that show? If not, this is a layering issue.
    Do the subtitles not show up in both player and full screen mode? If not, then again this is likely to be a layering issue.

    HTH

    Reply

  • oliver

    |

    Hi Bob,

    thanks for taking the time to ans above..
    I created a movieclip and added a dynamic text field into it. I left some default text inside the text box to
    see if it would display.
    I repeated this twice and was left with below::
    ft = fullscreen with the instance name of big sub and dynamic text selected.
    st = smallscreen with instance name of smallsub and dynamic text selected.

    http://screencast.com/t/MmMxMzc0YWEt

    The problem is when the movie kicks in as you can see from the screencast it goes to the last array value in the subtitles array..

    Hope that ans your faqs..

    reagrds

    Oli

    Reply

    • Bob de Wit

      |

      Hi Oli,

      the sample code doesn’t include handling of loading a new movie and loading the corresponding text array for that movie, that’s why it “jumps” to the last element in the available array. You should add an event listener for the MediaChange event and reset the array + position.

      Assuming the subtitle file is located in a .txt file with the video ID as filename, that could be something like this:

      private function onMediaChange(evt:Object):void
      {
      captionLabel.text = “”;
      GetCaptionFile();
      }

      private function GetCaptionFile():void
      {
      Captions = new Array();
      cursub = “”;
      secs = 0;
      captionPlayerControl.text = “loading subtitle file…”;
      try
      {
      var video:Object = VideoPlayer.getCurrentVideo();

      var request:URLRequest = new URLRequest(SubtitleURL + video.id + “.txt”);

      var loader:URLLoader = new URLLoader();
      loader.dataFormat = URLLoaderDataFormat.TEXT;
      loader.addEventListener(Event.COMPLETE, handleResults);
      loader.addEventListener(IOErrorEvent.IO_ERROR, ioErrorHandler);
      loader.load(request);
      }
      catch( e:Error)
      {

      }
      }

      private function handleResults(evt:Event):void
      {
      var response:String = evt.target.data as String;
      Captions = response.split(“\n”); //assign this to your subtitles array and reset
      captionPlayerControl.text = “”;
      captionFullScreenControl.text = “”;
      }

      Reply

Leave a comment