Note: this tutorial is still very much in draft form. Additional instructions for those not familiar with javascript and more examples will be added soon.

The Jeroen Wijering Media Player is a widely used free, open-source Flash-based media player, available for download from Jeroenwijering.com.

The JW player has an extensive Javascript API which allows it to communicate with events and elements on the page it is embedded in. Using this feature, the player can execute javascript functions on the page whenever the video reaches a specific time point.

This is very useful for building interactive learning modules. For example, you could display a short quiz at different points in the video to test understanding of the material so far. Depending on the answer, you can direct the user to remedial content or continue on. Another use would be for interactive stories, similar to an adventure game. Or, you could merely ask the viewer where to go next. You could use timed scripts to launch an interactive Flash demonstration of relevant material, or add text popups, images, or styled captions as needed, etc.

Here is a demo that you can use as the basis for your own projects. Download demo files

This script is is based on the synched slides player created by Will.

A working demo of the player is shown here.

I’ve tested this with both streaming video and progressive download video, and it works well with both.


The files that are required are listed below:

/root/ 
 yourHTMLpage.html (this is the HTML page where the player shows up. 
   /css-local/
       videoPlayerStyles.css (styles for the tabs, scrolling menu, and look and feel of the player) 
   /images/
   
    /white_media_player/ 
        [various graphics for the player] 
   /includes/
       jw_media_player/
          various player files 
   /js/
     playerControl.js (contains all the main functions for the player)   
     swfobject.js (writes the player in the correct format for IE or other browsers) 
   /js-local/    customInteraction.js (this is where you can write your own custom functions) 
   /media/
     videoPlaylist1.xml (as many playlists as you need)
     videoPlaylist2.xml
     videoPlaylist3.xml
     videoPlaylist1.xml
     [any flv or other media files you are using if you are not using streaming video] 
     [any flash quizzes or interactive materials]     
  

  1. You may want to diagram out your player as a flow chart before starting.

    The example flowchart below shows the page state as it loads, and what happens next depending on user choices and timepoints achieved in the various video segments. Using a diagram like this you can decide how many playlists, how many tracks or "items" in each playlist you will really need and any supporting files or elements you need to build on the page.


  2. Create the HTML page with the player and any elements that the player will control. Code for an example page is given below.
  3. Note the onload statement highlighted in yellow. This performs the initial load of the playlists in both the media player and a hidden image rotator which has been left on the page because I am probably going to use it in a future version of this. Since the image rotator has been left on the page, it also loads a dummy playlist.
  4. In my example page, there are three "multimedia" divs that will be used to display various items ("div1", "div2", div3", highlighted in cyan) at different timepoints in the video.
  5. There is also small div on the right side of the player ("rtDiv," highlighted in green) that will be used to display a Flash quiz that pops up at various points in the video to ask questions based on the material. The quiz will be controlled by an xml file, so that the same swf can be used for all video segments. When the user passes or fails a question, the quiz will use Flash’s ExternalInterface to execute the appropriate javascript functions that acts on the page and the JW player.

    A typical scenario might be:

    1. User chooses a video to play (clicks tab 2, for example).
    2. The video plays for a while, then pauses, and the quiz opens off to the right.
    3. The quiz has been passed a parameter that tells it which question to load.
    4. When the viewer makes a failing choice, the quiz executes the "fail" function, that closes the quiz div, loads a remedial item, scrubs to the appropriate time-point and resumes the player.
    5. When the video reaches another specified time point, the user is questioned again, with a slightly different question based on the remedial material.
  6. <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
    <html xmlns="http://www.w3.org/1999/xhtml">
      <head>
      <link href="css/allBrowsers.css" rel="stylesheet" type="text/css" />
      <link href="css/header.css" rel="stylesheet" type="text/css" />
    
    <link href="css-local/videoPlayerStyles.css" rel="stylesheet" type="text/css" />
      <!--special styles to take into account IE 6's differences-->
      <!--[if IE]>
      <style>
      #widgetTable #td24{
      margin-left:12px;
      padding-top:9px;
      }
    #rightColumn{
      overflow:visible;
      width:850px;}
      #scrollingDiv, #itemList {width:160px;}
      #itemList li{
      left:-18px;
      width:200px;
      }
      
      </style>
      <![endif]-->
    
    <!--[if IE 6]>
        
      <![endif]-->
        
      <script type="text/javascript" src="js/menu.js"></script>
      <script type="text/javascript" src="js/swfobject.js" defer="defer"></script>
      <script type="text/javascript" src="js/toggleOpen.js"></script>
    <script type="text/javascript" src="js/playerControl.js" defer="defer"></script>
      <script type="text/javascript" src="js-local/customInteraction.js" ></script> 
      </head>
     
     
      <!--/./#include file="includes/header.htm" -->
      <!--<div id="leftColumn"><!--//#include file="includes-local/navbar.htm" -->&nbsp;</div>
      <div id="rightColumn">
      <div id="subTitleBar">
      <div align="right">
      <!--<span id="pageTitle">-->
      <span id="pageNumberHolder"></span> <a href="javascript:window.print()" id="printIcon"><img src="images/images.jpg" alt="print this page" /></a> </div>
      </div>
      <div id="content">
     <!--*********************put content below this line!!!!************-->
        
      <div id="div1" style="padding:24px">
      <div align="right"><a href="#" onclick="closeDivAndResume('mpl1','div1','player','');">
      <img src="images/white_media_player/icons/cancel.png" alt="Close"></a>
      </div> <br/>
      <div id="div1a" style="border:6px solid blue;"></div>
      </div>
      <div id="div2" >
      <div align="right"><a href="#" onclick="closeDivAndResume('mpl1','div2','player','');">
      <img src="images/white_media_player/icons/cancel.png" alt="Close"></a>
      </div> <br/></div>
      <div id="div3" class="toggle">
      <a href="javascript:loadFile('mpl1', {file:list3});thisMovie('mpl1').sendEvent('playitem', 0);thisMovie('mpl1').sendEvent('playpause');">This loads a new file and starts up player</a>       </div>
      <table width="95%" class="widgetBG" id="widgetTable" border="0" cellpadding="0" cellspacing="0">
      <tr valign="top">
      <td id="td23">&nbsp;</td>
      <td id="td24"><div class="tabBar" id="tabBar"></div></td>
      <td width="23" align="left" valign="top">&nbsp;</td>
      </tr>
      <tr valign="top">
      <td id="td1" width="234" align="center">
      <div id="scrollingDiv">
      <p><b>Sections:</b></p>
      <div id="itemList"></div>
      </div>      </td>
      <td id="td2"><div id="player"><a href="http://www.macromedia.com/go/getflashplayer">Get the Flash Plugin</a></div></td>
      <td align="left" valign="top" id="td26">
      <div id="rtDiv">This is where the quiz shows up</div>     <!--<a href="#" onclick="showDiv('player');scrubTo('mpl1',8);closeItAndPlay('mpl1','rtDiv');">Close this and start movie again</a>-->
      </td>
      </tr>
      <tr>
      <td id="td3">&nbsp;</td>
      <td id="td4">
      <table width="100%" border="0">
      <tr>
      <td><a href="#" onclick="thisMovie('mpl1').sendEvent('playpause');"><img src="images/white_media_player/Player_Play.png" alt="play" width="40" height="40"></a></td>
      <td><a href="#" onclick="delayedStop('mpl1');"><img src="images/white_media_player/Player_Stop.png" alt="stop" width="40" height="40"></a></td>
      <td>Vol: <a href="#" onclick="delayedVolume('mpl1',0);"><img src="images/white_media_player/Sound_Mute.png" alt="mute" width="40" height="40"></a> |<a href="#" onClick="delayedVolume('mpl1',50);"><img src="images/white_media_player/Sound_Decrease.png" alt="decrease" width="40" height="40"></a>|<a href="#" onclick="delayedVolume('mpl1',100);"><img src="images/white_media_player/Sound_Increase.png" alt="increase" width="40" height="40"></a></td>
      <td>captions</td>
      <td>&nbsp;</td>
      </tr>
      </table>      </td>
      <td align="left" valign="top">&nbsp;</td>
      </tr>
      </table>
      <table>
      <tr>
      <td valign="top">      </td>
      <td>      </td>
      </tr>
      </table>
      <table><tr><td>
      
      <!-- <b>Debugging Data - you can remove this</b>
      <div id="data"              style="font-family: 'Arial'; font-size: 10px; text-align: left; color: black"></div>
      <div id="filename"          style="font-family: 'Arial'; font-size: 10px; text-align: left; color: blue"></div>
      <div id="slideshowplaylist" style="font-family: 'Arial'; font-size: 10px; text-align: left; color: blue"></div>-->
      <div id="timee"             style="font-family: 'Arial'; font-size: 10px; text-align: left; color: blue"></div>
      <!-- <div id="slide"             style="font-family: 'Arial'; font-size: 10px; text-align: left; color: blue"></div>
      <div id="annotation"        style="font-family: 'Arial'; font-size: 10px; text-align: left; color: red"></div>
      <div id="numkeypairs"       style="font-family: 'Arial'; font-size: 10px; text-align: left; color: green"></div>
      <div id="debug"             style="font-family: 'Arial'; font-size: 10px; text-align: left; color: black"></div>
      <div id="syncdata"          style="font-family: 'Arial'; font-size: 10px; text-align: left; color: black"></div>
      <div id="ellen"             style="font-family: 'Arial'; font-size: 10px; text-align: left; color: black"></div>
      <div id="playerProps"></div>-->
      </td></tr></table>
      <table border="1"><tr><td>
      <form name="htmlForm" method="POST" action="javascript:formSend();">
      Sending to ActionScript:<br />
      <input type="text" name="sendField" value="" /><br />
      <input type="submit" value="Send" /><br />
      <br />
      Received from ActionScript:<br />
      <input type="text" name="receivedField">
      </form>
      
      </td></tr>
      <tr id="td40"><td> 
      
      
      </td></tr></table>
      
      
    
     <div id="rotator"><a href="http://www.macromedia.com/go/getflashplayer">Get the Flash Plugin</a> to see this gallery.</div>
        
      <!--*********put content above this line!!!!!*****-->
      </div><!--end content-->
      </div>
      <!--end rightColumn-->
      <a href="javascript:d=document.documentElement.innerHTML;var s='';for(i=0;i<d.length;i++){c=d.charCodeAt(i);if (c<128)s+=d.charAt(i); else if (c==160) s+='%C2%A0'; else s+='&#'+c+';';}void(w=window.open('view-source:data:text/html,<html>n'+s+'</html>','','menubar=yes,resizable=yes,scrollbars=yes'));">view generated source</a> 
      <div id="footer"></div>
      </div>
     
      </body>
      </html>
      <script language="JavaScript" >
      listTabs('tabBar',tabArray01);
      </script>
      <!--<script language="JavaScript" src="js/endScripts.js" type="text/javascript"></script>-->
    
      


  7. Create XSPF playlists. See this page for more information. You must include an "annotation" tag in each item, even if it will end up being empty.
    <?xml version='1.0' encoding='UTF-8'?>
    <playlist version='1' xmlns='http://xspf.org/ns/0/'>
    <trackList>
    <track>
    <title>Meet Eggdrop: Events at 7, 22, 30</title>
    <location>/videowidget2/media/Eggivation.flv</location>
    <type>flv</type>
    <identifier>media/Eggivation.flv</identifier>
    <annotation></annotation>
    <meta rel="start">0</meta>
    <meta rel="duration">00:00:35</meta>
    </track>

    </trackList>
    </playlist>

    .


  8. list the tabs needed in “customInteraction.js”
      //this is the list of all playlists you are going to want to use, whether or not they will be used as tabs.
    var list01='media/videoPlaylist1.xml';
    var list02='media/videoPlaylist2.xml';
    var list03='media/videoPlaylist3.xml';
    var list04='media/videoPlaylist4.xml';

    //this is the list of tabs you want to display above the player.
    var tabArray01 = new Array(
    {gtitle:'Eggdrop',gfile:'{file:list01}',gplayer:'mpl1' },
    {gtitle:'Gazoo',gfile:'{file:list02}',gplayer:'mpl1' },
    {gtitle:'Video3',gfile:'{file:list03}',gplayer:'mpl1' },
    {gtitle:'Video4',gfile:'{file:list04}',gplayer:'mpl1' },
    {gtitle:'not yet specified',gfile:'',gplayer:'mpl1',gurl:'',gdoFunc:'' } //not yet completed
    );


  9. Create the functions needed in “customInteraction.js”

    function createPlayer(videoPlaylist) {
    var s1 = new SWFObject('swf/mediaplayer-3.14.swf', 'mpl1', '380', '280', '7', '#000000');
    s1.addVariable('width', '380');
    s1.addVariable('height', '280');
    s1.addVariable('displayheight', '280');
    s1.addVariable('displaywidth', '420');
    s1.addVariable('file', videoPlaylist);
    s1.addVariable('overstretch', 'true'); // expands to fit h or v "false" -will stretch them to fit
    s1.addVariable('showdigits', 'true');
    s1.addVariable('autostart', 'false');
    s1.addVariable('shuffle', 'false');
    s1.addVariable('repeat', 'false');
    s1.addVariable('showicons', 'false');
    s1.addVariable('showstop', 'true');
    s1.addVariable('enablejs', 'true');
    s1.addVariable('javascriptid', 'mpl1');
    s1.addVariable('usecaptions', 'true');
    s1.addVariable('backcolor', '0xFFFFFF'); // face of buttons
    s1.addVariable('frontcolor', '0x404040'); // button symbols & playlist text
    s1.addVariable('lightcolor', '0x808080'); // highlighted playlist item
    s1.addVariable('screencolor', '0x000000'); // screen background color
    s1.write('player');
    };
    //this has been left in for future use.
    function createRotator(slidePlaylist) {
    //this is set at 10px square for the time being

    var s2 = new SWFObject('swf/imagerotator-3.14.swf', 'rot1', '1', '1', '7', '#FFFFFF');
    s2.addVariable('width', '1');
    s2.addVariable('height', '1');
    s2.addVariable('file', slidePlaylist);
    s2.addVariable('overstretch', 'true'); // expands to fit h or v "false" -will stretch them to fit
    s2.addVariable('autostart', 'false');
    s2.addVariable('shuffle', 'false');
    s2.addVariable('repeat', 'false');
    s2.addVariable('rotatetime', '999');
    s2.addVariable('showicons', 'false');
    s2.addVariable('shownavigation', 'false');
    s2.addVariable('transition', 'bgfade');
    s2.addVariable('enablejs', 'true');
    s2.addVariable('javascriptid', 'rot1');
    s2.addVariable('backcolor', '0xCCEEFF'); // face of buttons
    s2.addVariable('frontcolor', '0x404040'); // button symbols & playlist text
    s2.addVariable('lightcolor', '0x808080'); // highlighted playlist item
    s2.addVariable('screencolor', '0xFFFFFF'); // screen background color
    s2.write('rotator');

    };

    //this is the list of all playlists you are going to want to use, whether or not they will be used as tabs.
    var list01='media/videoPlaylist1.xml';
    var list02='media/videoPlaylist2.xml';
    var list03='media/videoPlaylist3.xml';
    var list04='media/videoPlaylist4.xml';

    //this is the list of tabs you want to display above the player.
    var tabArray01 = new Array(
    {gtitle:'Eggdrop',gfile:'{file:list01}',gplayer:'mpl1' },
    {gtitle:'Gazoo',gfile:'{file:list02}',gplayer:'mpl1' },
    {gtitle:'Video3',gfile:'{file:list03}',gplayer:'mpl1' },
    {gtitle:'Video4',gfile:'{file:list04}',gplayer:'mpl1' },
    {gtitle:'not yet specified',gfile:'',gplayer:'mpl1',gurl:'',gdoFunc:'' } //not yet completed
    );

    /*custom function definitions. These can be combinations of the standard player control functions that are included on playerControl.js or new ones that manipulate other elements on the page*/

    /* function doit(jsid,mydiv){ //obsolete
    window.document.getElementById('rotator').innerHTML+=('jsid='+jsid+'mydiv= '+mydiv);
    document.getElementById(mydiv).style.display="block";
    document.getElementById('player').style.visibility="hidden";
    delayedPlayPause(jsid);
    }*/

    /* function doit2(jsid,mydiv){ //obsolete
    document.getElementById(mydiv).style.display="block";
    thisMovie(jsid).sendEvent('playpause');
    }*/
    //shows player when it is hidden.

    function hidePlayer(playerdivid){ if(playerdivid){(document.getElementById(playerdivid).style.visibility='hidden')};}
    function showPlayer(playerdivid){ if(playerdivid){(document.getElementById(playerdivid).style.visibility='visible')}; }

    //shows hidden quiz or content-containing divs, hides the player if the player div id is specified, and pauses the player.
    //If your content div will overlap the player, set hidePlayerID to 'player'. This keeps player from
    //showing through your content div.
    function showDivAndPause(jsid,mydiv,hidePlayerID,myInteraction){
    document.getElementById(mydiv).style.display="block";
    pausePlayer(jsid);
    hidePlayer(hidePlayerID);
    loadInteraction("div1a", myInteraction); //the div1a is hardcoded in for testing but could be replaced by a variable.
    }
    //a function I am going to develop further to load specific quiz questions into the flash quiz
    function showDivPauseLdQuiz(jsid,mydiv,hidePlayerID){
    document.getElementById(mydiv).style.display="block";
    hidePlayer(hidePlayerID);
    pausePlayer(jsid);
    loadQuiz(mydiv);
    }

    function ExternalJSTest(div2show){
    document.getElementById('rotator').innerHTML+=('fired off externalJSTest');
    showDiv(div2show);
    scrubTo('mpl1',25);
    closeItAndPlay('mpl1','rtDiv');
    }
    function hideIt(mydiv){ document.getElementById(mydiv).style.visibility='hidden'; }//for player
    function closeIt(mydiv){ document.getElementById(mydiv).style.display='none'; }//for interaction divs
    function closeDivAndResume(jsid,closediv,playerID){
    currentPlus1 = (currentPosition+1);
    scrubTo(jsid,currentPlus1);
    delayedResumePlayer(jsid)
    showPlayer(playerID);
    closeIt(closediv);
    }
    function closeItAndPlay(jsid,closediv,playerDiv){//closes div and resumes player
    delayedResumePlayer(jsid)
    showPlayer(playerDiv);
    closeIt(closediv);
    }

    function scrubTo(jsid,sec){ thisMovie(jsid).sendEvent('scrub',sec); }

    function listTabs(tabDiv,tabArray){
    var gtabBar = document.getElementById(tabDiv);
    document.getElementById(tabDiv).innerHTML="";
    for (var m=0; m< tabArray.length; m++){
    var gplayer = tabArray[m].gplayer;
    var gtitle = tabArray[m].gtitle;
    var gfile = tabArray[m].gfile+"";
    document.getElementById(tabDiv).innerHTML+=("<li><a href='#' onclick='loadAndStart(""+ gplayer +"","+gfile+");makeActive(this.id);' class='tab tb_up_act' id='tb_01_"+ m +"' >"+gtitle+"</a></li>");
    }//end for...
    }

    function loadQuiz(mydiv) {
    var so3 = new SWFObject('media/ExternalInterfaceExample.swf', 'quiz', '200', '100', '8', '#FFFFFF');
    // var so3 = new SWFObject('media/quiz/quiz.swf', 'quiz', '200', '100', '8', '#FFFFFF')
    so3.addParam("quality", "high");
    so3.addParam("allowScriptAccess","sameDomain");
    so3.addParam("salign", "t");
    so3.write(mydiv.firstChild);
    }


    function loadInteraction(mydiv, myInteraction) {
    var so4 = new SWFObject(myInteraction, 'interaction', '400', '451', '8', '#FFFFFF');
    so4.addParam("quality", "high");
    so4.addParam("allowScriptAccess","sameDomain");
    so4.addParam("salign", "t");
    so4.write(mydiv);
    };

    function formSend() {
    var text = document.htmlForm.sendField.value;
    getFlashMovie('quiz').sendTextToFlash(text);

    }
    function getTextFromFlash(str) {
    document.htmlForm.receivedField.value = "From Flash: " + str;
    return str + " received";
    }


  10. Add time/function pairs to the annotation tags in your playlists.
    Example:

    7:showDivAndPause("mpl1","rtDiv","player","guide.swf")|22:doit("mpl1","div1")|30:showDivAndPause("mpl1","rtDiv","player","someInteraction.swf")