/* 
 * CaptionBox
 * Interactive Transcript jQuery Plugin
 *
 * Version 1.2.7
 * September 21, 2011
 *
 * For install help, please see http://speakertext.com/captionbox
 * For license, please see http://speakertext.com/tos#license
 *
 * Copyright (c) SpeakerText 2010-2011
 * Original Development by Dan Schultz, 2010
 */

// Constants
var STCONST_FILENAME = "jquery.captionbox.js";

/* Web Service Locations */
var STCONST_WS_UPDATE_TRANSCRIPT = "/transactions/update_from_editor";

/* Web Service API Keys*/
var STCONSTS_WSCREDS_BITLY_LOGIN = "speakertext";
var STCONSTS_WSCREDS_BITLY_APIKEY = "R_2274af29d71349021399a6ee1191ab6b";

/* Social Media DOM Element Classes */
var STCONST_CLASS_SHARE_BUTTON = "STxShare";
var STCONST_CLASS_SHARE_FACEBOOK = "STfacebookShare";
var STCONST_CLASS_SHARE_TWITTER = "STtwitterShare";
var STCONST_CLASS_SHARE_URL = "STurlShare";

/* Querystring Variables */
var STCONST_QUERY_PLAYER_QUOTELINK = "STQL";

/* Player Text */
var STCONST_TEXT_BAR_SEARCH = "Search inside this video...";
var STCONST_TEXT_BAR_LOGOLINK = "http://speakertext.com/?ref=itp";
var STCONST_TEXT_BAR_AUTOSCROLL = "AUTO SCROLL";
var STCONST_TEXT_BAR_SHOWTEXT = "EXPAND TRANSCRIPT";

var STCONST_TEXT_EDIT_SAVE = "Save";
var STCONST_TEXT_EDIT_CANCEL = "Cancel";

var STCONST_TEXT_MESSAGE_EDIT = "<p>Just start typing to edit the transcript.</p>";
var STCONST_TEXT_MESSAGE_SAVING = "<p>Saving Transcript...</p>";
var STCONST_TEXT_MESSAGE_SAVEOK = "<h1>Transcript Received!</h1><p>Your transcript has been queued for processing and should be visible in a few moments. <a href='/library'>Return to library &raquo</a></p>";
var STCONST_TEXT_MESSAGE_SAVEERROR = "<h1>Error</h1><p>Your transcript could not be saved at this time. Please try again in a few moments.</p>";

var STCONST_TEXT_SHARE_UNSELECTED = "<strong>Link to a video quote</strong><br>To start, select some text from the transcript<br>";
var STCONST_TEXT_SHARE_HIGHLIGHTED = "<strong>Share the selected quote</strong><br>The video will start playing at this quote</a><br>";
var STCONST_TEXT_SHARE_SELECTED = "<strong>Share the current quote</strong><br>The video will start playing at this quote</a><br>";

var STCONST_TEXT_SHARE_BUTTONS = "<a class='" + STCONST_CLASS_SHARE_TWITTER + " " + STCONST_CLASS_SHARE_BUTTON + "' onmousedown=\"stjQuery(this).STcaptureQuoteLink()\" onclick=\"if(stjQuery(this).data(STCONST_DATAKEY_QUOTELINK_TEXT) != '') { temp = window.open('','_blank','width=550,height=420'); stjQuery(this).STtwitterShare(temp); }\"></a><a class='" + STCONST_CLASS_SHARE_FACEBOOK + " " + STCONST_CLASS_SHARE_BUTTON + "' onmousedown=\"stjQuery(this).STcaptureQuoteLink()\" onclick=\"if(stjQuery(this).data(STCONST_DATAKEY_QUOTELINK_TEXT) != '') { temp = window.open('','_blank'); stjQuery(this).STfacebookShare(temp); }\"></a><a class='" + STCONST_CLASS_SHARE_URL + " " + STCONST_CLASS_SHARE_BUTTON + "' onmousedown=\"stjQuery(this).STcaptureQuoteLink()\" onclick=\"if(stjQuery(this).data(STCONST_DATAKEY_QUOTELINK_TEXT) != '') { stjQuery(this).STurlShare(); }\"></a><br>";

var STCONST_TEXT_MESSAGE_SHAREURL_BEGIN = "<h1>";
var STCONST_TEXT_MESSAGE_SHAREURL_END = "</h1><p>Copy and share this URL to link back to this moment of the video.</p>";

var STCONST_TEXT_MESSAGE_COPY = "<strong>QuoteLink Created!</strong><p>When you paste this text, it will link back to this moment in the video. <a href='http://speakertext.com/what' target='_blank'>More&nbsp;&raquo;</a></p>";

var STCONST_TEXT_TRANSCRIPT_LOADING = "<div>The transcript for this video is loading.</div>";
var STCONST_TEXT_TRANSCRIPT_UNAVAILABLE = "<div>The transcript for this video is unavailable.</div>";

/* Transcript DOM Element Prefixes */
var STCONST_PLAYER_TRANSCRIPTCONTENT_PREFIX = "STtranscriptContent";
var STCONST_PLAYER_TRANSCRIPTEMBED_PREFIX = "STtranscriptEmbed";

/* Ignore threshold when seeking (in milliseconds) */
var  STCONST_PLAYER_IGNORE_THRESHOLD = 3000;

/* Core Player DOM Element Classes */
var STCONST_CLASS_PLAYER = "STplayer";
var STCONST_CLASS_UNINITIALIZED = "STplayerUninitialized";
var STCONST_CLASS_EMBEDDED = "STplayerEmbedded";
var STCONST_CLASS_NICEBUTTON = "nicebutton";
var STCONST_CLASS_EDITOR = "STeditor";
var STCONST_CLASS_VIDEOPLAYER = "STvideoPlayer";
var STCONST_CLASS_PLAYER_DISPLAYWITHTRANSCRIPT = "STdisplayWithTranscript";
var STCONST_CLASS_PLAYER_TRANSCRIPTPANE = "STtranscriptPane";
var STCONST_CLASS_PLAYER_TRANSCRIPTWRAPPER = "STtranscriptWrapper";
var STCONST_CLASS_PLAYER_BAREBONES = "STplayerBarebones";
var STCONST_CLASS_PLAYER_SHAREPANE = "STsharePane";
var STCONST_CLASS_PLAYER_SHAREPANE_ACTIVE = "STsharePaneActive";
var STCONST_CLASS_PLAYER_SHAREPANE_HIGHLIGHT = "STsharePaneHighlight";
var STCONST_CLASS_PLAYER_SHAREPANE_TEXT = "STsharePaneText";
var STCONST_CLASS_PLAYER_COPYFEEDBACK = "STsharePaneCopyFeedback";
var STCONST_CLASS_PLAYER_TRANSCRIPTDISPLAY = "STtranscriptDisplay";
var STCONST_CLASS_PLAYER_TRANSCRIPTSTATUS = "STtranscriptStatus";
var STCONST_CLASS_PLAYER_TRANSCRIPT_CONTENT = "STtranscriptContent";
var STCONST_CLASS_PLAYER_SEARCH = "STsearch";
var STCONST_CLASS_PLAYER_SEARCHFIELD = "STsearchField";
var STCONST_CLASS_PLAYER_SEARCHPREVBUTTON = "STsearchPrevButton";
var STCONST_CLASS_PLAYER_SEARCHNEXTBUTTON = "STsearchNextButton";
var STCONST_CLASS_PLAYER_AUTOSCROLL = "STautoScroll";
var STCONST_CLASS_PLAYER_SHOWTEXT = "STshowText";
var STCONST_CLASS_PLAYER_LOGO = "STlogo";
var STCONST_CLASS_PLAYER_CURRENTCONTENT = "STcurrentContent";
var STCONST_CLASS_PLAYER_SEARCHMATCH = "STsearchMatch";
var STCONST_CLASS_PLAYER_BASE =  "STplayerBase";
var STCONST_CLASS_PLAYER_BASELEFT =  "STplayerBaseLeft";
var STCONST_CLASS_PLAYER_BASERIGHT =  "STplayerBaseRight";
var STCONST_CLASS_PLAYER_HANDLE =  "STplayerHandle";
var STCONST_CLASS_PLAYER_SCROLLBAR = "STscrollBar";
var STCONST_CLASS_PLAYER_SCROLLUP = "STscrollUp";
var STCONST_CLASS_PLAYER_SCROLLDOWN = "STscrollDown";
var STCONST_CLASS_PLAYER_SCROLLVISIBLE = "STscrollVisible";
var STCONST_CLASS_PLAYER_SCROLLTOP = "STscrollTop";
var STCONST_CLASS_PLAYER_SCROLLBOTTOM = "STscrollBottom";
var STCONST_CLASS_PLAYER_BAR = "STbar";
var STCONST_CLASS_MESSAGEPANE = "STmessagePane";
var STCONST_CLASS_COPY_FEEDBACK = "STcopyFeedback";
var STCONST_CLASS_PLAYER_FONTSIZEBUTTON = "STfontSizeButton";
var STCONST_CLASS_EDIT_SAVE = "STsaveButton";
var STCONST_CLASS_EDIT_CANCEL = "STcancelButton";
var STCONST_CLASS_EDIT_EMPTY = "STemptyContentEdit";
var STCONST_CLASS_PLAYER_EMBEDOVERLAY = "SToverlay";
var STCONST_CLASS_PLAYER_EMBEDWRAPPER = "STembedWrapper";

/* Core Player State Classes */
var STCONST_CLASS_PLAYER_AUTOSCROLL_ON = "on";
var STCONST_CLASS_PLAYER_AUTOSCROLL_OFF = "off";
var STCONST_CLASS_PLAYER_TRANSCRIPT_LOADING = "STtranscriptLoading";
var STCONST_CLASS_PLAYER_TRANSCRIPT_VISIBLE = "visibleTranscript";
var STCONST_CLASS_PLAYER_SEARCHEMPTY = "empty";
var STCONST_CLASS_PLAYER_SMALLFONT = "smallFont";
var STCONST_CLASS_PLAYER_MEDIUMFONT = "mediumFont";
var STCONST_CLASS_PLAYER_LARGEFONT = "largeFont";

/* Core Player Events */
var STCONST_EVENT_TRANSCRIPT_LOADED = "transcriptLoaded";
var STCONST_EVENT_PLAYER_SHOW = "showPlayer";
var STCONST_EVENT_PLAYER_HIDE = "hidePlayer";
var STCONST_EVENT_PLAYER_SEARCH = "search";
var STCONST_EVENT_PLAYER_SEARCHNEXT = "searchNext";
var STCONST_EVENT_PLAYER_SEARCHPREV = "searchPrev";
var STCONST_EVENT_CONTENT_CLICKED = "contentClicked";
var STCONST_EVENT_CONTENT_SELECT = "contentSelect";
var STCONST_EVENT_CONTENT_COPY = "contentCopy";
var STCONST_EVENT_CONTENT_DESELECT = "contentDeselect";

/* Core Player Data Keys */
var STCONST_DATAKEY_VIDEOPLAYERID = "STvideoplayerID";
var STCONST_DATAKEY_VIDEOPLAYERTYPE = "STVPtype";
var STCONST_DATAKEY_TRANSCRIPTID = "STtranscriptID";
var STCONST_DATAKEY_PLAYERQUEUE = "STplayerQueue";
var STCONST_DATAKEY_PLAYER_DISABLED = "STplayerDisabled";
var STCONST_DATAKEY_TRANSCRIPT_AUTOSCROLL = "STautoScroll";
var STCONST_DATAKEY_TRANSCRIPT_CURRENTCONTENTID = "STcurrentContentID";
var STCONST_DATAKEY_TRANSCRIPT_SEARCHMATCHES = "STsearchMatches";
var STCONST_DATAKEY_TRANSCRIPT_SEARCHTERMS = "STsearchTerms";
var STCONST_DATAKEY_TRANSCRIPT_IGNOREPROGRESS = "STtranscriptIgnoreProgress";
var STCONST_DATAKEY_CONFIG = "STconfig";
var STCONST_DATAKEY_QUOTELINK_TEXT = "STquoteLinkText";
var STCONST_DATAKEY_QUOTELINK_CONTENTID = "STquoteLinkContentID";
var STCONST_DATAKEY_SHAREURL_TIMEOUT = "STshareUrlTimeout";
var STCONST_DATAKEY_EDITMESSAGE_SHOWN = "STeditMessageShown";

/* Core Player Flags */
var STCONST_FLAG_TRANSCRIPT_AUTOSCROLL_OFF = 0;
var STCONST_FLAG_TRANSCRIPT_AUTOSCROLL_ON = 1;

/* Configuration Settings */
var STCONST_CONFIG_INITIALSTATE_OPEN = "open";
var STCONST_CONFIG_INITIALSTATE_CLOSED = "closed";
var STCONST_CONFIG_EDITENABLED_OFF = "off";
var STCONST_CONFIG_EDITENABLED_ON = "on";
var STCONST_CONFIG_EMBEDSTYLE_OVERLAY = "overlay";
var STCONST_CONFIG_EMBEDSTYLE_EMBED = "embed";
  
// Sandbox
if( typeof( window.jQuery ) != "undefined" ) {
  var versionString = window.jQuery().jquery;
  if( versionString.indexOf("1.3") == 0 || versionString.indexOf("1.4") == 0 || versionString.indexOf("1.5") == 0 || versionString.indexOf("1.6") == 0) {
    window.stjQuery = window.jQuery;
  } else {
    // Later on we might want to load jQuery dynamically and use a callback to execute
    // the stuff below. Especially if we eventually allow people to embed just one ST.
    //
    // This will of course require us doing all of this initialization more smartly.
    // We'll want to check whether or not jQuery has been loaded, but also whether or
    // not the SpeakerText plugin has been initialized or not...because if the plugin is
    // embedded more than once on the page, it would be bad to reinitialize.

    if( console && console.error )
      console.error("CAPTIONBOX: Must load jQuery 1.3+ before loading CaptionBox!");
  }
} else {
  if( console && console.error )
    console.error("CAPTIONBOX: Must load jQuery 1.3+ before loading CaptionBox!");
}


// Plugin Definition
(function($) {
  // Transcript Cache -- keys are transcript IDs
  var STtranscriptCache = Object();
  
  // Player Cache -- connects the ST players to a hash to enable communication between Video Player and the ST Player
  var STplayerCache = Object();
  
  // Player Count -- used to generate a unique player ID (when needed)
  var STplayerCount = 0;
  
  // Content Object
  function STContent() {
    this.id = 0;
    this.timestamp = 0;
    this.text = "";
  }
  
  
  // Transcript Object
  function STTranscript() {
    this.transcriptID = 0;
    this.videoID = 0;
    this.task = 0;
    this.hash = "";
    this.content = new Array();
  }
  
  STTranscript.prototype.getContentByTimestamp = function(timestamp) {
    if(this.content.length == 0)
      return null;
    
    previousContent = this.content[0];
    for (var i = 0; i < this.content.length; i++) {
      var a = this.content[i];
      if(a.timestamp == timestamp)
        return a;
      else if(a.timestamp > timestamp)
        return previousContent;
      previousContent = a;
    }
    return previousContent;
  }
  
  STTranscript.prototype.getContentByID = function(id) {
    for (var i = 0; i < this.content.length; i++) {
      var a = this.content[i];
      if(a.id == id)
        return a;
    }
    return null;
  }
  
  STTranscript.prototype.updateContentByID = function(id, newText) {
    for (var i = 0; i < this.content.length; i++) {
      var a = this.content[i];
      if(a.id == id) {
        this.content[i].text = newText;
        return;
      }
    }
    return null;
  }
  
  STTranscript.prototype.search = function(string) {
    var contentArray = Array();
    string = string.toLowerCase();
    for (var i = 0; i < this.content.length; i++) {
      var a = this.content[i];
      if(a.text.toLowerCase().search(string) != -1)
        contentArray.push(a);
    }
    return contentArray;
  }
  
  
  // ST Plugin Methods
  $.fn.extend({
    // Setup Methods
      /* Returns whether or not this item has been set up with SpeakerText */
      hasSpeakerText: function() {
        return $(this).hasClass(STCONST_CLASS_VIDEOPLAYER);
      },
      
      /* Sets up and loads a speakertext player */
      speakerText: function(playerType, settings) {
        // Set up the configuration array
        var config = {
                minHeight: 30,
                defaultHeight: 210,
                videoPlayerID: "",      // HTML ID of video player on page
                preEmbedID: "",         // HTML ID of CaptionBox that matches this video player
                transcriptPath: "",     // where to load AJAX transcripts from
                matchVideoWidth: true,  // adjust the width of CaptionBox to match the width of the video player
                initialState: STCONST_CONFIG_INITIALSTATE_OPEN,
                editEnabled: STCONST_CONFIG_EDITENABLED_OFF,
                embedStyle: STCONST_CONFIG_EMBEDSTYLE_EMBED};
        if(typeof(STglobalSettings) != "undefined") $.extend(config, STglobalSettings);
        if(settings) $.extend(config, settings);
        
        // Initialize these players
        this.each(function() {
          // If this item already has a ST player associated with it, don't associate a second one
          if($(this).hasSpeakerText())
            return true; // like a loop continue
            
          // Generate a unique speakertext ID
          ++STplayerCount;
          var playerID;
          if(config.videoPlayerID != "")
            playerID = STCONST_CLASS_PLAYER + config.videoPlayerID;
          else if($(this).attr("id") != "")
            playerID = STCONST_CLASS_PLAYER + $(this).attr("id");
          else
            playerID = STCONST_CLASS_PLAYER + STplayerCount;
          
          // Set up the DOM (unless the player is already embedded)
          $(this).addClass(STCONST_CLASS_VIDEOPLAYER);
          
          // If the user already embedded an ST player, use the embed.  Otherwise generate one.
          if(config.preEmbedID != "")
            var STplayer = $("#" + config.preEmbedID);
          
          if(STplayer.length == 0) {
            //console.error("CaptionBox: No transcript found for video player with id " + config.videoPlayerID);
            return false;
          } else {
            
            // Remove Barebones Styling
            STplayer.removeClass(STCONST_CLASS_PLAYER_BAREBONES);
            
            // Add all the stuff that we leave out of the default embed
            var transcriptContent = STplayer.STembedWrapper().html();
            
            var h = "";
            h += "<div class='" + STCONST_CLASS_PLAYER_EMBEDWRAPPER + "'>";
            h += "  <div class='" + STCONST_CLASS_PLAYER_SHAREPANE + " " + STCONST_CLASS_PLAYER_DISPLAYWITHTRANSCRIPT + "'>";
            h += "    <span class='" + STCONST_CLASS_PLAYER_SHAREPANE_TEXT + "'>" + STCONST_TEXT_SHARE_UNSELECTED + "</span>" + STCONST_TEXT_SHARE_BUTTONS;
            h += "  </div>";
            
            h += "  <div class='" + STCONST_CLASS_PLAYER_TRANSCRIPTPANE + "'>";
            h += "    <div class='" + STCONST_CLASS_PLAYER_SCROLLBAR + "'>";
            h += "      <div class='" + STCONST_CLASS_PLAYER_SCROLLUP + "'></div>";
            h += "      <div class='" + STCONST_CLASS_PLAYER_SCROLLVISIBLE + "'>";
            h += "        <div class='" + STCONST_CLASS_PLAYER_SCROLLTOP + "'></div>";
            h += "        <div class='" + STCONST_CLASS_PLAYER_SCROLLBOTTOM + "'></div>";
            h += "      </div>";
            h += "      <div class='" + STCONST_CLASS_PLAYER_SCROLLDOWN + "'></div>";
            h += "    </div>";
            h += "    <div class='" + STCONST_CLASS_PLAYER_TRANSCRIPTWRAPPER + "'>";
            h += "     <div class='" + STCONST_CLASS_PLAYER_TRANSCRIPTDISPLAY + "'>";
            h += transcriptContent;
            h += "      </div>";
            h += "      <div class='" + STCONST_CLASS_PLAYER_TRANSCRIPTSTATUS + "'></div>";
            h += "    </div>";
            h += "  </div>";
            
            h += "  <div class='" + STCONST_CLASS_PLAYER_BASE + "'>";
            h += "    <div class='" + STCONST_CLASS_PLAYER_BASERIGHT + "'></div>";
            h += "    <div class='" + STCONST_CLASS_PLAYER_BASELEFT + "'></div>";
            h += "    <div class='" + STCONST_CLASS_PLAYER_HANDLE + "'></div>"
            h += "  </div>";
            
            h += "  <div class='" + STCONST_CLASS_MESSAGEPANE + "'></div>";
            
            h += "</div>";
            
            STplayer.STembedWrapper().remove(); // CSS bug due to overflow: auto not ever being removed
            STplayer.append(h);
            
            h = "";
            h += "<input type='text' class='" + STCONST_CLASS_PLAYER_SEARCHFIELD + " " + STCONST_CLASS_PLAYER_SEARCHEMPTY + "'/>";
            h += "<div class='" + STCONST_CLASS_PLAYER_SEARCHPREVBUTTON + " " + STCONST_CLASS_PLAYER_SEARCHEMPTY + " " + STCONST_CLASS_PLAYER_DISPLAYWITHTRANSCRIPT + "'></div>";
            h += "<div class='" + STCONST_CLASS_PLAYER_SEARCHNEXTBUTTON + " " + STCONST_CLASS_PLAYER_SEARCHEMPTY + " " + STCONST_CLASS_PLAYER_DISPLAYWITHTRANSCRIPT + "'></div>";
            h += "<div class='" + STCONST_CLASS_PLAYER_FONTSIZEBUTTON + " " + STCONST_CLASS_PLAYER_DISPLAYWITHTRANSCRIPT + "'></div>";
            h += "<div class='" + STCONST_CLASS_PLAYER_AUTOSCROLL + " " + STCONST_CLASS_PLAYER_DISPLAYWITHTRANSCRIPT + "'>" + STCONST_TEXT_BAR_AUTOSCROLL + "</div>";
            h += "<div class='" + STCONST_CLASS_PLAYER_SHOWTEXT + "'>" + STCONST_TEXT_BAR_SHOWTEXT + "</div>";
            STplayer.STbar().append(h);
            
            if(config.editEnabled == STCONST_CONFIG_EDITENABLED_ON) {
              h = "<a class='" + STCONST_CLASS_EDIT_SAVE + " " + STCONST_CLASS_NICEBUTTON + "' value='" + STCONST_TEXT_EDIT_SAVE + "'>" + STCONST_TEXT_EDIT_SAVE + "</a>";
              //h += "<a class='" + STCONST_CLASS_EDIT_CANCEL + " " + STCONST_CLASS_NICEBUTTON + "' value='" + STCONST_TEXT_EDIT_CANCEL + "' href=''>" + STCONST_TEXT_EDIT_CANCEL + "</a>";
              STplayer.append(h);
              
              STplayer.STtranscriptWrapper().find("." + STCONST_CLASS_PLAYER_TRANSCRIPT_CONTENT).attr("contentEditable", "true");
              
            }
          }
          
          // Set the embed style
          if(config.embedStyle == STCONST_CONFIG_EMBEDSTYLE_OVERLAY)
            STplayer.addClass(STCONST_CLASS_PLAYER_EMBEDOVERLAY);
          
          // Make sure the video player has an ID
          if(!$(this).attr("id"))
            $(this).attr("id",STCONST_CLASS_VIDEOPLAYER + STplayerCount);
          
          // Register this ST player
          STplayerCache[$(this).attr("id")] = STplayer;
          if(config.videoPlayerID == "")
            STplayer.data(STCONST_DATAKEY_VIDEOPLAYERID, $(this).attr("id"));
          else
            STplayer.data(STCONST_DATAKEY_VIDEOPLAYERID, config.videoPlayerID);
          
          STplayer.data(STCONST_DATAKEY_VIDEOPLAYERTYPE, playerType);
          
          // Load the DOM elements
          var STtranscriptDisplay = STplayer.STtranscriptDisplay();
          var STtranscriptPane = STplayer.STtranscriptPane();
          var STtranscriptWrapper = STplayer.STtranscriptWrapper();
          
          var STsharePane = STplayer.STsharePane();
          
          var STscrollVisible = STplayer.STscrollVisible();
          var STplayerHandle = STplayer.STplayerHandle();
          var STplayerBase = STplayer.STplayerBase();
          var STautoScrollButton = STplayer.STautoScrollButton();
          var STtranscriptShowButton = STplayer.STtranscriptShowButton();
          
          var STsearchField = STplayer.STsearchField();
          var STsearchNextButton = STplayer.STsearchNextButton();
          var STsearchPrevButton = STplayer.STsearchPrevButton();
          
          var STfontSizeButton = STplayer.STfontSizeButton();
          
          var STmessagePane = STplayer.STmessagePane();
          
          // Set up default values
          STsearchField.val(STCONST_TEXT_BAR_SEARCH);
          
          // Remove share pane if edit mode, fix css
          if( config.editEnabled == STCONST_CONFIG_EDITENABLED_ON ) {
            STsharePane.remove();
            STplayer.addClass(STCONST_CLASS_EDITOR);
          }
          
          // Add tabindex to transcript wrapper to enable keydown events
          STtranscriptWrapper.attr("tabindex", 100);
          
          // Trigger the content select event if content is selected
          $(STtranscriptDisplay).bind("mouseup", function(e) {
            if(STgetSelectedText() != "") {
              $(this).trigger(STCONST_EVENT_CONTENT_SELECT);
            }
          });
          
          if(config.editEnabled == STCONST_CONFIG_EDITENABLED_OFF) {
            // Override copy so that it copies a quotelink
            STtranscriptPane.bind("copy", function(e) {
              var STplayer = $(this).parents("." + STCONST_CLASS_PLAYER).eq(0);
              var transcript = $(STplayer).STtranscript();
              var selectedContent = null;
            
              // Wrap the selection with a QuoteLink
              if (typeof window.getSelection != "undefined") {
                var userSelection = window.getSelection();
                if (userSelection.toString()) if (typeof userSelection.setBaseAndExtent != "undefined") {
                  // Safari
                  var mainRange = userSelection.getRangeAt(0);
                
                  // Look up the earliest selected content ID
                  var tempRange = mainRange.cloneRange();
                  tempRange.collapse(true);
                  var selectedElement = tempRange.startContainer;
                  if(typeof($(selectedElement).attr("id")) == "undefined")
                    selectedElement = $(selectedElement).parent();
                  var contentID = $(selectedElement).attr("id").slice(STCONST_PLAYER_TRANSCRIPTCONTENT_PREFIX.length);
                  selectedContent = transcript.getContentByID(contentID);
                  var transcriptID = transcript.transcriptID;
                
                  var quoteLinkURL = STsetURLParameterByName(window.location.href, STCONST_QUERY_PLAYER_QUOTELINK + transcriptID, selectedContent.timestamp);
                
                  var container = document.createElement("div");
                  $(container).css("position","absolute");
                  $(container).css("left","-10000px");
                
                  var frontText = "<a href='" + quoteLinkURL + "'>";
                  var backText = "</a>";
                  var h = frontText + $(mainRange.cloneContents()).text() + backText;
                  $(container).html(h);
                
                  $(document.body).append(container);
                  var copyRange = document.createRange();
                  copyRange.selectNode(container);
                
                  userSelection.removeAllRanges();
                  userSelection.addRange(copyRange);
                
                  window.setTimeout(function () {
                    $(container).remove();
                    window.getSelection().setBaseAndExtent(mainRange.startContainer, mainRange.startOffset, mainRange.endContainer, mainRange.endOffset);
                  }, 0);
                }
                else {                
                  // Firefox
                  // TODO -- support Opera
                  var userSelection = window.getSelection();
                  var mainRange = userSelection.getRangeAt(0);
                
                  // Get the ID of the selected element
                  var tempRange = mainRange.cloneRange();
                  tempRange.collapse(true);
                  var selectedElement = tempRange.startContainer;
                  if(typeof($(selectedElement).attr("id")) == "undefined")
                    selectedElement = $(selectedElement).parent();
                  var contentID = $(selectedElement).attr("id").slice(STCONST_PLAYER_TRANSCRIPTCONTENT_PREFIX.length);
                  selectedContent = transcript.getContentByID(contentID);
                  var transcriptID = transcript.transcriptID;
                
                  var quoteLinkURL = STsetURLParameterByName(window.location.href, STCONST_QUERY_PLAYER_QUOTELINK + transcriptID, selectedContent.timestamp);
                
                  var container = document.createElement("a");
                  $(container).attr("href", quoteLinkURL);
                  $(container).text($(mainRange.cloneContents()).text());
                
                  $(document.body).append(container);
                  var copyRange = document.createRange();
                  copyRange.selectNode(container);
                
                  userSelection.removeAllRanges();
                  userSelection.addRange(copyRange);
                
                  window.setTimeout(function () {
                    $(container).remove();
                    userSelection.removeAllRanges();
                    userSelection.addRange(mainRange);
                  }, 0);
                }
              } else if (document.selection) {
                // Internet Explorer
                mainRange = document.selection.createRange();
                tempRange = document.selection.createRange();
                tempRange.collapse(true); 
                var selectedElement = tempRange.parentElement();
                var contentID = $(selectedElement).attr("id").slice(STCONST_PLAYER_TRANSCRIPTCONTENT_PREFIX.length);
                selectedContent = transcript.getContentByID(contentID);
                var transcriptID = transcript.transcriptID;
              
                var quoteLinkURL = STsetURLParameterByName(window.location.href, STCONST_QUERY_PLAYER_QUOTELINK + transcriptID, selectedContent.timestamp);
              
                var container = document.createElement("div");
                $(container).css("visibility","hidden");
              
                var frontText = "<a href='" + quoteLinkURL + "'>";
                var backText = "</a>";
                var h = frontText + mainRange.htmlText + backText;
                $(container).html(h);
                $(document.body).append(container);
              
                var newRange = document.body.createTextRange()
                newRange.moveToElementText(container);
                newRange.select();
                window.setTimeout(function () {
                  mainRange.select();
                  $(container).remove();
                }, 0)
              }
              $(this).trigger(STCONST_EVENT_CONTENT_COPY);
            
            });
          } // end if edit off
          
          if(config.editEnabled == STCONST_CONFIG_EDITENABLED_ON) {
            
            // Enable the ability to save changes (if edits are enabled)
            var STsaveButton = STplayer.STsaveButton();
            STsaveButton.click(function(e) {
              // disable save button
              $(this).addClass('disabled');
              $(this).html('Saving...');
              
              STplayer.STshowMessage(STCONST_TEXT_MESSAGE_SAVING);
              var transcript = $(this).STtranscript();
              $.ajax({
                type: "PUT",
                url: STCONST_WS_UPDATE_TRANSCRIPT,
                data: ({hash: transcript.hash,
                        content: transcript.content}),
                success: function(xml, status, request) {
                  STplayer.STsaveButton().removeClass('disabled');
                  STplayer.STsaveButton().html('Save');
                  STplayer.STshowMessage(STCONST_TEXT_MESSAGE_SAVEOK);
                  window.setTimeout(function() {$(STplayer).SThideMessage()}, 8000);
                }, 
                error: function(e) {
                  STplayer.STsaveButton().removeClass('disabled');
                  STplayer.STsaveButton().html('Save');
                  STplayer.STshowMessage(STCONST_TEXT_MESSAGE_SAVEERROR);
                  window.setTimeout(function() {$(STplayer).SThideMessage()}, 5000);
                }
              });
            });
          }
          
          // Enable the ability to change font size 
          STfontSizeButton.click(function(e) {
            if(STplayer.hasClass(STCONST_CLASS_PLAYER_SMALLFONT)) {
              STplayer.removeClass(STCONST_CLASS_PLAYER_SMALLFONT);
              STplayer.addClass(STCONST_CLASS_PLAYER_MEDIUMFONT);
            }
            else if(STplayer.hasClass(STCONST_CLASS_PLAYER_MEDIUMFONT)) {
              STplayer.removeClass(STCONST_CLASS_PLAYER_MEDIUMFONT);
              STplayer.addClass(STCONST_CLASS_PLAYER_SMALLFONT);
            }
            else if(STplayer.hasClass(STCONST_CLASS_PLAYER_LARGEFONT)) {
              STplayer.removeClass(STCONST_CLASS_PLAYER_LARGEFONT);
              STplayer.addClass(STCONST_CLASS_PLAYER_SMALLFONT);
            }
            else {
              STplayer.addClass(STCONST_CLASS_PLAYER_MEDIUMFONT);
            }
            STplayer.STredraw();
          });
          
          // Enable the ability to open and close the transcript by double clicking the handle
          STplayerBase.dblclick (function(e) {
            if(STplayer.hasClass(STCONST_CLASS_PLAYER_TRANSCRIPT_VISIBLE))
              $(this).trigger(STCONST_EVENT_PLAYER_HIDE);
            else
              $(this).trigger(STCONST_EVENT_PLAYER_SHOW);
          });
          
          // Enable the ability to open and close the transcript by clicking the "click to expand" text
          STtranscriptShowButton.click(function(e) {
            $(this).trigger(STCONST_EVENT_PLAYER_SHOW);
          });
            
          // Enable the ability to resize the transcript pane using the handle
          var transcriptResizeMouseStart = 0;
          var transcriptResizeTranscriptStart = 0;
          var transcriptResizeTranscriptVisible = STplayer.hasClass(STCONST_CLASS_PLAYER_TRANSCRIPT_VISIBLE);
          var startHandleDrag = function(e) {
            transcriptResizeMouseStart = e.pageY;
            transcriptResizeTranscriptStart = STtranscriptWrapper.height();
            transcriptResizeTranscriptVisible = STplayer.hasClass(STCONST_CLASS_PLAYER_TRANSCRIPT_VISIBLE);
            $("html").bind("mousemove",followHandleDrag);
            $("html").bind("mouseleave",stopHandleDrag);
            $("html").bind("mouseup",stopHandleDrag);
            return false;
          }
          var followHandleDrag = function(e) {
            var newHeight = Math.max(transcriptResizeTranscriptStart + e.pageY - transcriptResizeMouseStart,0);
            STtranscriptWrapper.height(newHeight);
            
            if(newHeight > 0 && !transcriptResizeTranscriptVisible) {
              transcriptResizeTranscriptVisible = true;
              STplayer.addClass(STCONST_CLASS_PLAYER_TRANSCRIPT_VISIBLE);
              STtranscriptPane.show();
              STsharePane.show();
            }
            else if(newHeight <=0 && transcriptResizeTranscriptVisible) {
              transcriptResizeTranscriptVisible = false;
              STplayer.removeClass(STCONST_CLASS_PLAYER_TRANSCRIPT_VISIBLE);
              STtranscriptPane.hide();
              STsharePane.hide();
            }
            
            STplayer.STredraw("handleDrag");
            return false;
          }
          var stopHandleDrag = function(e) {
            $("html").unbind("mouseup",stopHandleDrag);
            $("html").unbind("mouseleave",stopHandleDrag);
            $("html").unbind("mousemove",followHandleDrag);
            
            // Make sure the transcript is at least the min height
            if(STtranscriptWrapper.height() < config.minHeight && e.pageY != transcriptResizeMouseStart) {
              STtranscriptWrapper.animate({height: config.defaultHeight}, 200, function() {STplayer.STredraw()});
            }
          }
          STplayerBase.mousedown(startHandleDrag);
          
          
          // Create a styled scroll bar
          var scrollBarPaneHeight = 0;
          var scrollBarTranscriptHeight = 0;
          var scrollBarHandleHeight = 0;
          var scrollBarAutoScrollDisabled = 0;
          var transcriptStart = 0;
          var mouseStart = 0;
          
          var multiplier = 0;
          
          var scrollElemStyle = STtranscriptDisplay.get(0).style;
          
          var startScrollDrag = function(e) {
            var scrollBarPanePadding = STtranscriptPane.css('padding-top');
            scrollBarPanePadding = parseInt(scrollBarPanePadding.substring(0, scrollBarPanePadding.length - 2));
            transcriptStart = STtranscriptPane.offset().top + scrollBarPanePadding;
            mouseStart = e.pageY - STscrollVisible.offset().top;
            scrollBarPaneHeight = STtranscriptPane.height();
            scrollBarTranscriptHeight = STtranscriptDisplay.height() - scrollBarPaneHeight;
            scrollBarHandleHeight = STscrollVisible.height();
            
            multiplier = -1 * scrollBarTranscriptHeight / (scrollBarPaneHeight - scrollBarHandleHeight);
            
            // Turn off autoscroll
            STplayer.data(STCONST_DATAKEY_TRANSCRIPT_AUTOSCROLL, STCONST_FLAG_TRANSCRIPT_AUTOSCROLL_OFF);
            
            $(document).bind("mousemove",followScrollDrag);
            $(document).bind("mouseleave",stopScrollDrag);
            $(document).bind("mouseup",stopScrollDrag);
            return false;
          }
          var followScrollDrag = function(e) {
            var offset = e.pageY - transcriptStart - mouseStart;
            var newTranscriptPosition = Math.max(-scrollBarTranscriptHeight, Math.min(offset * multiplier, 0));
            // going straight up js gets us slightly better performance here I think
            scrollElemStyle.top = newTranscriptPosition + "px"; //.css("top", newTranscriptPosition);
            STplayer.STredraw("scroll");
            return false;
          }
          var stopScrollDrag = function(e) {
            $(document).unbind("mouseup",stopScrollDrag);
            $(document).unbind("mouseleave",stopScrollDrag);
            $(document).unbind("mousemove",followScrollDrag);
          }
          STscrollVisible.mousedown(startScrollDrag);
          
          // Make it so the mouse wheel scrolls the transcript
          STtranscriptPane.mousewheel(function(e, delta) {
            var paneHeight = STtranscriptPane.height();
            var transcriptHeight = STtranscriptDisplay.height() - paneHeight;
            if(transcriptHeight < 0)
              return;
              
            var coefficient = 1;
            if( isChrome() || isFirefox() || isMSIE() )
              coefficient = 16;
            
            var newTranscriptPosition = Math.max(-transcriptHeight, Math.min(STtranscriptDisplay.position().top + coefficient * delta, 0));
            
            scrollElemStyle.top = newTranscriptPosition + "px";
            STplayer = $(this).parents("." + STCONST_CLASS_PLAYER).eq(0);
            
            // Turn off autoscroll
            STplayer.data(STCONST_DATAKEY_TRANSCRIPT_AUTOSCROLL, STCONST_FLAG_TRANSCRIPT_AUTOSCROLL_OFF);
            
            STplayer.STredraw("scroll");
          }, true);
          
          // Make it so that up and down arrows scroll the transcript by one line
          var scrollUp = function(scrollType) {
            // Turn off autoscroll
            STplayer.data(STCONST_DATAKEY_TRANSCRIPT_AUTOSCROLL, STCONST_FLAG_TRANSCRIPT_AUTOSCROLL_OFF);
            
            var newTranscriptPosition = 0;
            if( scrollType == "line" )
              newTranscriptPosition = Math.min(STtranscriptDisplay.position().top + 18, 0);
            else
              newTranscriptPosition = Math.min(STtranscriptDisplay.position().top + STtranscriptPane.height() - 20, 0);
              
            STtranscriptDisplay.css("top", newTranscriptPosition);
            STplayer.STredraw("scroll");
          }
          
          var scrollDown = function(scrollType) {
            var transcriptHeight = STtranscriptDisplay.height() - STtranscriptPane.height();
            if(transcriptHeight < 0)
              return;
            
            // Turn off autoscroll
            STplayer.data(STCONST_DATAKEY_TRANSCRIPT_AUTOSCROLL, STCONST_FLAG_TRANSCRIPT_AUTOSCROLL_OFF);
            
            var newTranscriptPosition = 0;
            if( scrollType == "line" )
              newTranscriptPosition = Math.max(STtranscriptDisplay.position().top - 18, -transcriptHeight);
            else
              newTranscriptPosition = Math.max(STtranscriptDisplay.position().top - STtranscriptPane.height() + 20, -transcriptHeight);
              
            STtranscriptDisplay.css("top", newTranscriptPosition);
            STplayer.STredraw("scroll");
          }
          
          STtranscriptPane.keydown( function(e) {
            switch(e.keyCode) {
              case 38:
                scrollUp("line");
                break;
              
              case 33:
                scrollUp("page");
                break;
                
              case 34:
                scrollDown("page");
                break;
                  
              case 40:
                scrollDown("line");
                break;
            }
            
            if( e.keyCode == 38 || e.keyCode == 33 || e.keyCode == 40 || e.keyCode == 34 )
              e.preventDefault();
          });
          
          
            /* Triggered when the autoscroll button is pressed */
            STautoScrollButton.click(function(e) {
              var STplayer = $(this).parents("." + STCONST_CLASS_PLAYER).eq(0);
              if(STplayer.data(STCONST_DATAKEY_TRANSCRIPT_AUTOSCROLL) == STCONST_FLAG_TRANSCRIPT_AUTOSCROLL_ON)
                STplayer.data(STCONST_DATAKEY_TRANSCRIPT_AUTOSCROLL, STCONST_FLAG_TRANSCRIPT_AUTOSCROLL_OFF);
              else
                STplayer.data(STCONST_DATAKEY_TRANSCRIPT_AUTOSCROLL, STCONST_FLAG_TRANSCRIPT_AUTOSCROLL_ON);
              STplayer.STredraw();
            });
            
            /* Triggered when the user types in the search field */
            STsearchField.keyup(function(e) {
              $(this).trigger(STCONST_EVENT_PLAYER_SEARCH);
              
              if((e.which && e.which == 13) || (e.keyCode && e.keyCode == 13))
                $(this).trigger(STCONST_EVENT_PLAYER_SEARCHNEXT);
                
              if( $(this).val() != '' ) {
                STsearchNextButton.removeClass(STCONST_CLASS_PLAYER_SEARCHEMPTY);
                STsearchPrevButton.removeClass(STCONST_CLASS_PLAYER_SEARCHEMPTY);
              } else {
                STsearchNextButton.addClass(STCONST_CLASS_PLAYER_SEARCHEMPTY);
                STsearchPrevButton.addClass(STCONST_CLASS_PLAYER_SEARCHEMPTY);
              }
                
            });
            STsearchField.focus(function(e) {
              if($(this).hasClass(STCONST_CLASS_PLAYER_SEARCHEMPTY)) {
                $(this).removeClass(STCONST_CLASS_PLAYER_SEARCHEMPTY);
                $(this).val("");
                $(this).trigger(STCONST_EVENT_PLAYER_SEARCH);
              }
            });
            
            STsearchField.blur(function() {
              if($(this).val() == "") {
                $(this).val(STCONST_TEXT_BAR_SEARCH);
                $(this).addClass(STCONST_CLASS_PLAYER_SEARCHEMPTY);
                STsearchNextButton.addClass(STCONST_CLASS_PLAYER_SEARCHEMPTY);
                STsearchPrevButton.addClass(STCONST_CLASS_PLAYER_SEARCHEMPTY);
              }
            });
            
            /* Triggered when the user clicks "next" in search results */
            STsearchNextButton.click(function(e) {
              $(this).trigger(STCONST_EVENT_PLAYER_SEARCHNEXT);
            });
            
            /* Triggered when the user clicks "prev" in search results */
            STsearchPrevButton.click(function(e) {
              $(this).trigger(STCONST_EVENT_PLAYER_SEARCHPREV);
            });
            
            /* Triggered when a user triggers the display of the transcript */
            STplayer.bind(STCONST_EVENT_PLAYER_SHOW, function(event) {
              STplayer.addClass(STCONST_CLASS_PLAYER_TRANSCRIPT_VISIBLE);
              
              STtranscriptPane.slideDown(500);
              STsharePane.slideDown(500, function() {
                STtranscriptPane.height('');
                STplayer.STredraw();
              });
            });
            
            /* Triggered when a user wants the hiding of the  transcript */
            STplayer.bind(STCONST_EVENT_PLAYER_HIDE, function(event) {
              STtranscriptPane.slideUp(500, function() { STplayer.removeClass(STCONST_CLASS_PLAYER_TRANSCRIPT_VISIBLE); } );
              STsharePane.slideUp(500);
            });
            
            /* Triggered when a user clicks on a piece of content in a ST transcript */
            STplayer.bind(STCONST_EVENT_CONTENT_CLICKED, function(event, clickedContent) {
              STplayer.onSTVPprogress(clickedContent.timestamp, true);
              STplayer.STVPseekToAndPlay(clickedContent.timestamp);
              
              // Turn ON autoscroll
              STplayer.data(STCONST_DATAKEY_TRANSCRIPT_AUTOSCROLL, STCONST_FLAG_TRANSCRIPT_AUTOSCROLL_ON);
              STplayer.STredraw();
            });
            
            /* Triggered when a user searches */
            STplayer.bind(STCONST_EVENT_PLAYER_SEARCH, function(event) {
              var searchTerm = STsearchField.val();
              var transcript = STplayer.STtranscript();
              
              if(!STplayer.hasClass(STCONST_CLASS_PLAYER_TRANSCRIPT_VISIBLE))
                $(this).trigger(STCONST_EVENT_PLAYER_SHOW);
                
              // Get and store the content matches
              if(searchTerm == "") 
                var contentMatches = Array();
              else 
                var contentMatches = transcript.search(searchTerm);
              $(this).data(STCONST_DATAKEY_TRANSCRIPT_SEARCHMATCHES, contentMatches);
              
              // Show the prev / next buttons iff there is matched content
              var STsearchPrevButton = $(this).STsearchPrevButton();
              var STsearchNextButton = $(this).STsearchNextButton();
              
              // Hilight the content matches
              $(transcript.content).each(function() {
                var contentDOM = $("#" + STCONST_PLAYER_TRANSCRIPTCONTENT_PREFIX + this.id);
                var h = this.text;
                if(searchTerm != "")
                  h = h.replace(new RegExp("(" + searchTerm.replace(/([\\\.\+\*\?\[\^\]\$\(\)\{\}\=\!\<\>\|\:])/g, "\\$1")+")",'i'), "<span class='" + STCONST_CLASS_PLAYER_SEARCHMATCH + "'>$1</span>");
                contentDOM.html(h);
              });
            });
            
            /* Triggered when a user wants to jump to the "next" content match from the search results*/
            STplayer.bind(STCONST_EVENT_PLAYER_SEARCHNEXT, function(event) {
              var searchMatches = $(this).data(STCONST_DATAKEY_TRANSCRIPT_SEARCHMATCHES);
              var currentContentID = $(this).data(STCONST_DATAKEY_TRANSCRIPT_CURRENTCONTENTID);
              var nextContent;
              
              // Analytics (do these before we check for matches because we want to track all
              // search terms, not just ones found)
              var searchTerm = $(this).STsearchField().val();
                
              // If there are no matches, there is nothing to look for
              if(searchMatches.length == 0)
                return;
              
              // if we're already past the last one, jump back to the first match
              if(currentContentID >= searchMatches[searchMatches.length-1].id)
                nextContent = searchMatches[0];
              else {
                // Find the first match after the current content ID
                for(var x in searchMatches) {
                  var content = searchMatches[x];
                  if(currentContentID < content.id) {
                    nextContent = content;
                    break;
                  }
                }
              }
              
              STplayer.trigger(STCONST_EVENT_CONTENT_CLICKED,[nextContent]);
            });
            
            /* Triggered when a user clicks "prev" on the search results*/
            STplayer.bind(STCONST_EVENT_PLAYER_SEARCHPREV, function(event) {
              var searchMatches = $(this).data(STCONST_DATAKEY_TRANSCRIPT_SEARCHMATCHES);
              var currentContentID = $(this).data(STCONST_DATAKEY_TRANSCRIPT_CURRENTCONTENTID);
              var prevContent;
              
              // if we're already before the last one, jump to the last match
              if(currentContentID <= searchMatches[0].id)
                prevContent = searchMatches[searchMatches.length-1];
              else {
                for(var x in searchMatches) {
                  var content = searchMatches[x];
                  if(currentContentID <= content.id)
                    break;
                  prevContent = content;
                }
              }
              
              $(this).trigger(STCONST_EVENT_CONTENT_CLICKED,[prevContent]);
            });
            
            /* Triggered when a user selects text on a ST player */
            var deselectWatcher = function() {
              // make sure the text has time to get deselected
              setTimeout(deselectAction, 50);
            }
            
            var deselectAction = function() {
              if(STgetSelectedText() == "") {
                STplayer.trigger(STCONST_EVENT_CONTENT_DESELECT);
                $('html').unbind("mouseup", deselectWatcher);
                $('html').unbind("keyup", deselectWatcher);
              }
            }
            
            STplayer.bind(STCONST_EVENT_CONTENT_SELECT, function (event) {
              // Don't display quotelink messages if editing is enabled
              if(config.editEnabled == STCONST_CONFIG_EDITENABLED_ON)
                return;
              
              $(this).STsharePane().addClass(STCONST_CLASS_PLAYER_SHAREPANE_HIGHLIGHT).find("." + STCONST_CLASS_PLAYER_SHAREPANE_TEXT).html(STCONST_TEXT_SHARE_HIGHLIGHTED);
              
              $('html').bind("mouseup", deselectWatcher);
              $('html').bind("keyup", deselectWatcher);
            });
            
            /* Triggered when a user deselects text on a ST player */
            STplayer.bind(STCONST_EVENT_CONTENT_DESELECT, function (event) {
              $(this).STsharePane().removeClass(STCONST_CLASS_PLAYER_SHAREPANE_HIGHLIGHT).find("." + STCONST_CLASS_PLAYER_SHAREPANE_TEXT).html(STCONST_TEXT_SHARE_SELECTED);
              
              // only remove the sharepane text if it's not active
              if( ! $(this).STsharePane().hasClass(STCONST_CLASS_PLAYER_SHAREPANE_ACTIVE) ) {
                $(this).STsharePane().removeClass(STCONST_CLASS_COPY_FEEDBACK).find("." + STCONST_CLASS_PLAYER_SHAREPANE_TEXT).html(STCONST_TEXT_SHARE_UNSELECTED);
                $(this).STsharePane().find("." + STCONST_CLASS_SHARE_BUTTON).show();
              }
            });
            
            
            /* Triggered when a user copies text */
            STplayer.bind(STCONST_EVENT_CONTENT_COPY, function(event) {
              // Don't display quotelink messages if editing is enabled
              if(config.editEnabled == STCONST_CONFIG_EDITENABLED_ON)
                return;
              
              STplayer.STshowCopyMessage();
              window.setTimeout(function() {STplayer.SThideCopyMessage()}, 5000);
            });
            
            /* Triggered to handle the server response for a transcript request */
            STplayer.bind(STCONST_EVENT_TRANSCRIPT_LOADED, function(event, transcriptID) {
              // Render the transcript for every element that was waiting for the transcript to load
              $("." + STCONST_CLASS_PLAYER_TRANSCRIPT_LOADING + transcriptID).each(function() {
                $(this).removeClass(STCONST_CLASS_PLAYER_TRANSCRIPT_LOADING + transcriptID);
                $(this).STrenderTranscript(transcriptID);
                
              });
            });
          
          // Initialize Player Settings
          STplayer.data(STCONST_DATAKEY_TRANSCRIPT_AUTOSCROLL, STCONST_FLAG_TRANSCRIPT_AUTOSCROLL_ON);
          STplayer.data(STCONST_DATAKEY_TRANSCRIPT_CURRENTCONTENTID,0);
          STplayer.data(STCONST_DATAKEY_CONFIG, config);
          STplayer.data(STCONST_DATAKEY_TRANSCRIPT_IGNOREPROGRESS, false);
          
          if( config.editEnabled == STCONST_CONFIG_EDITENABLED_ON )
            STplayer.data(STCONST_DATAKEY_EDITMESSAGE_SHOWN, false);
            
          if( STplayer.hasClass(STCONST_CLASS_EMBEDDED) ) {
            STplayer.STtranscriptWrapper().height(config.defaultHeight);
            STplayer.STsharePane().height(config.defaultHeight + 14);
          }
          
          if(config.initialState == STCONST_CONFIG_INITIALSTATE_OPEN) {
            STplayer.trigger(STCONST_EVENT_PLAYER_SHOW);
          }
          else {
            STplayer.trigger(STCONST_EVENT_PLAYER_HIDE);
          }
          
          // LOAD THE TRANSCRIPT
          STplayer.STloadTranscriptFromPage(config.preEmbedID);
             
        });
        
        return this;
      },
      
      //////////////////////////////////
      //// END OF ST INITIALIZATION ////
      //////////////////////////////////
      
      STcaptureQuoteLink: function() {
        var selectedText = STgetSelectedText() + "";
        var selectedContentID = STgetSelectedContentID();
        
        if( selectedText == "" ) {
          var STplayer = $(this).parents("." + STCONST_CLASS_PLAYER).eq(0);
          selectedContentID = STplayer.data(STCONST_DATAKEY_TRANSCRIPT_CURRENTCONTENTID);
          selectedText = STplayer.STtranscriptDisplay().find("#" + STCONST_PLAYER_TRANSCRIPTCONTENT_PREFIX + selectedContentID).text();
        }
        $(this).data(STCONST_DATAKEY_QUOTELINK_TEXT, selectedText);
        $(this).data(STCONST_DATAKEY_QUOTELINK_CONTENTID, selectedContentID);
      },
      
      STtwitterShare: function(popupWindow) {
        var selectedText = $(this).data(STCONST_DATAKEY_QUOTELINK_TEXT);
        var selectedContentID = $(this).data(STCONST_DATAKEY_QUOTELINK_CONTENTID);
        var popup = popupWindow;
        
        var STplayer = $(this).parents("." + STCONST_CLASS_PLAYER).eq(0);
        
        var transcript = $(STplayer).STtranscript();
        var selectedContent = transcript.getContentByID(selectedContentID);
        var transcriptID = transcript.transcriptID;
        
        var quoteLinkURL = STsetURLParameterByName(window.location.href, STCONST_QUERY_PLAYER_QUOTELINK + transcriptID, selectedContent.timestamp);
        $.ajax({
          type: "GET",
          url: "http://api.bit.ly/v3/shorten",
          dataType: "jsonp",
          data: ({login: STCONSTS_WSCREDS_BITLY_LOGIN,
              apiKey: STCONSTS_WSCREDS_BITLY_APIKEY,
              longUrl: quoteLinkURL,
              format: "json"}),
          success: function(data, status, request) {
            // Create the transcript object
            var transcript = new STTranscript()
            quoteLinkURL =  " - " + data.data.url;
            var status = selectedText.replace("\n","");
            if(status.length + quoteLinkURL.length > 140)
              status = status.slice(0,138 - quoteLinkURL.length);
            
            status = '"' + status + '"' + quoteLinkURL;
            var shareURL = "http://twitter.com/intent/tweet?text=" + encodeURIComponent(status);
            popup.document.location = shareURL;
          }, 
          error: function(e) {
          }
        });
        
      },
      
      STfacebookShare: function(popupWindow) {
        var selectedText = $(this).data(STCONST_DATAKEY_QUOTELINK_TEXT);
        var selectedContentID = $(this).data(STCONST_DATAKEY_QUOTELINK_CONTENTID);
        var popup = popupWindow;
                
        var STplayer = $(this).parents("." + STCONST_CLASS_PLAYER).eq(0);
        
        var transcript = $(STplayer).STtranscript();
        var selectedContent = transcript.getContentByID(selectedContentID);
        var transcriptID = transcript.transcriptID;
        
        var quoteLinkURL = STsetURLParameterByName(window.location.href, STCONST_QUERY_PLAYER_QUOTELINK + transcriptID, selectedContent.timestamp);
        
        $.ajax({
          type: "GET",
          url: "http://api.bit.ly/v3/shorten",
          dataType: "jsonp",
          data: ({login: STCONSTS_WSCREDS_BITLY_LOGIN,
              apiKey: STCONSTS_WSCREDS_BITLY_APIKEY,
              longUrl: quoteLinkURL,
              format: "json"}),
          success: function(data, status, request) {
            // Create the transcript object
            var transcript = new STTranscript()
            quoteLinkURL =  data.data.url;
            var shareURL = "http://facebook.com/sharer.php?u=" + encodeURIComponent(quoteLinkURL) + "&t=" + encodeURIComponent(selectedText);
            popup.document.location = shareURL;
          }, 
          error: function(e) {
          }
        });
        
      },
      
      STurlShare: function() {
        var selectedText = $(this).data(STCONST_DATAKEY_QUOTELINK_TEXT);
        var selectedContentID = $(this).data(STCONST_DATAKEY_QUOTELINK_CONTENTID);
                
        var STplayer = $(this).parents("." + STCONST_CLASS_PLAYER).eq(0);
        
        var transcript = $(STplayer).STtranscript();
        var selectedContent = transcript.getContentByID(selectedContentID);
        var transcriptID = transcript.transcriptID;
        
        var quoteLinkURL = STsetURLParameterByName(window.location.href, STCONST_QUERY_PLAYER_QUOTELINK + transcriptID, selectedContent.timestamp);
        
        $.ajax({
          type: "GET",
          url: "http://api.bit.ly/v3/shorten",
          dataType: "jsonp",
          data: ({login: STCONSTS_WSCREDS_BITLY_LOGIN,
              apiKey: STCONSTS_WSCREDS_BITLY_APIKEY,
              longUrl: quoteLinkURL,
              format: "json"}),
          success: function(data, status, request) {
            // Create the transcript object
            var transcript = new STTranscript()
            quoteLinkURL =  data.data.url;
            STplayer.STshowMessage(STCONST_TEXT_MESSAGE_SHAREURL_BEGIN + quoteLinkURL + STCONST_TEXT_MESSAGE_SHAREURL_END);
            
            timeout = STplayer.data(STCONST_DATAKEY_SHAREURL_TIMEOUT);
            window.clearTimeout(timeout);
            STplayer.data(STCONST_DATAKEY_SHAREURL_TIMEOUT, window.setTimeout(function() {$(STplayer).SThideMessage()}, 10000));
          }, 
          error: function(e) {
          }
        });
        
      },
      
      /* Redraws the tool spaces for a ST player */
      
      STredraw: function(redrawType) {
        // redraw share pane
        var transcriptPane = this.STtranscriptPane();
        var transcriptDisplay = this.STtranscriptDisplay();
        var sharePane = this.STsharePane();
        
        var currentContentID = this.data(STCONST_DATAKEY_TRANSCRIPT_CURRENTCONTENTID);
        
        var transcriptPosition = transcriptDisplay.position().top;
        var newTranscriptPosition = transcriptPosition;
        
        var transcriptHeight = transcriptDisplay.height();
        var visibleHeight = transcriptPane.height();
        
        var autoScrollFlag = this.data(STCONST_DATAKEY_TRANSCRIPT_AUTOSCROLL);
        
        if( redrawType != "scroll" ) {
          
          if( visibleHeight <= 0 )
            sharePane.height(0);
          else
            sharePane.height(visibleHeight + 14);
        
          // Redraw Transcript Location
        
          if(currentContentID != 0 && autoScrollFlag == STCONST_FLAG_TRANSCRIPT_AUTOSCROLL_ON) {
            var currentContent = transcriptDisplay.find("#" + STCONST_PLAYER_TRANSCRIPTCONTENT_PREFIX + currentContentID);
            // If the content is in the last visible "block" don't display it on the top -- just move to the end of the transcript
            var contentPosition = Math.min(currentContent.position().top, transcriptHeight - visibleHeight);
            var contentHeight = currentContent.height();
          
            var relativeContentPosition = contentPosition + contentHeight + transcriptPosition;
            
            if(relativeContentPosition < -1 || relativeContentPosition > visibleHeight) {
              newTranscriptPosition = -contentPosition;
            }
          }
        
          // Cancel out edge cases (where the user changes height to "below transcript base"
          var bottomHeight = -(transcriptHeight - visibleHeight);
          if(transcriptPosition < bottomHeight)
            newTranscriptPosition = Math.min(0, bottomHeight)
        
          STplayer = this;
          if( newTranscriptPosition != transcriptPosition ) {
            if( redrawType == "handleDrag" )
              transcriptDisplay.css("top", newTranscriptPosition);
            else
              transcriptDisplay.animate({top: newTranscriptPosition}, {step: function() {STplayer.STredrawScrollBar();}});
          }
        }
        
        // Redraw Auto Scroll
        var autoScrollSpace = this.find("." + STCONST_CLASS_PLAYER_AUTOSCROLL);
        var autoScrollFlag = this.data(STCONST_DATAKEY_TRANSCRIPT_AUTOSCROLL);
      
        autoScrollSpace.removeClass(STCONST_CLASS_PLAYER_AUTOSCROLL_ON + " " + STCONST_CLASS_PLAYER_AUTOSCROLL_OFF);
      
        switch(autoScrollFlag) {
          case STCONST_FLAG_TRANSCRIPT_AUTOSCROLL_OFF:
            autoScrollSpace.addClass(STCONST_CLASS_PLAYER_AUTOSCROLL_OFF);
            break;
          case STCONST_FLAG_TRANSCRIPT_AUTOSCROLL_ON:
            autoScrollSpace.addClass(STCONST_CLASS_PLAYER_AUTOSCROLL_ON);
            break;
        }

        // Redraw Scroll Bar
        this.STredrawScrollBar();
        
      },
      
      STredrawScrollBar: function() {
        var scrollBarVisible = this.STscrollVisible();
        
        var transcriptPane = this.STtranscriptPane();
        var transcriptDisplay = this.STtranscriptDisplay();
        var transcriptHeight = transcriptDisplay.height();
        var visibleHeight = transcriptPane.height();
        var transcriptPosition = transcriptDisplay.position().top;        
        
        if(transcriptHeight > 0 && visibleHeight < transcriptHeight) {
          var visibleBarHeight = Math.max((visibleHeight / transcriptHeight) * visibleHeight, 0) + 10;
          
          if(visibleHeight < visibleBarHeight)
            scrollBarVisible.hide();
          else if( scrollBarVisible.is(":hidden") ) {
            scrollBarVisible.show();
          }
          
          var displayPosition = -1 * transcriptPosition;
          var visibleBarPosition = (displayPosition / (transcriptHeight - visibleHeight)) * (visibleHeight - visibleBarHeight);
          
          scrollBarVisible.height(visibleBarHeight).css("top", visibleBarPosition);
        } else {
          scrollBarVisible.hide();
        }
      },
      
      /* Renders a transcript that has been loaded into the transcript cache */
      STrenderTranscript: function(transcriptID) {
        // Make sure the transcript exists in the cache
        if(!(transcriptID in STtranscriptCache))
          return this;
          
        // Make sure the transcript isn't already rendered
        if($(this).data(STCONST_DATAKEY_TRANSCRIPTID) == transcriptID)
          return;
        
        // Load the transcript from the cache
        var transcript = STtranscriptCache[transcriptID];
        var STtranscriptStatus = $(this).STtranscriptStatus();
        //if(transcript.content.length == 0) {
        //  STtranscriptStatus.html(STCONST_TEXT_TRANSCRIPT_UNAVAILABLE);
        //  return;
        //}
        //else
        
        STtranscriptStatus.html("");
        
        // Get the config for this player
        var playerConfig = $(this).data(STCONST_DATAKEY_CONFIG);
        
        // Link this ST Player to this transcript on the data level
        $(this).data(STCONST_DATAKEY_TRANSCRIPTID, transcriptID);
        
        // Generate the transcript HTML
        var editString = "";
        if(playerConfig.editEnabled == STCONST_CONFIG_EDITENABLED_ON)
          editString = " contenteditable='true'";
        
        // Render the transcript HTML
        var transcriptPane = $(this).STtranscriptPane();
        var transcriptDisplay = $(this).STtranscriptDisplay();
        
        // Only redraw the transcript if it was not embedded
        if(!$(this).hasClass(STCONST_CLASS_EMBEDDED)) {
          var sb = new Array();
          $(transcript.content).each(function() {
            sb.push("<span id='" + STCONST_PLAYER_TRANSCRIPTCONTENT_PREFIX + this.id + "' class='" + STCONST_CLASS_PLAYER_TRANSCRIPT_CONTENT + "'" + editString + ">" + this.text + "</span>");
          });
          var transcriptHTML = sb.join("");
          transcriptDisplay.html(transcriptHTML);
        } else {
          // remove embedded designation so we can reuse this container for other transcripts
          $(this).removeClass(STCONST_CLASS_EMBEDDED);
        }
        
        $(this).removeClass(STCONST_CLASS_UNINITIALIZED);
        
        var STplayer = this;
        
        // Add content events
        $(transcriptDisplay).find("." + STCONST_CLASS_PLAYER_TRANSCRIPT_CONTENT)
        .click(function(e) {
          if(playerConfig.editEnabled == STCONST_CONFIG_EDITENABLED_ON) {
            // Don't do anything if this is the currently playing segment
            if( $(this).hasClass( STCONST_CLASS_PLAYER_CURRENTCONTENT ) ) {
              return;
            }
            
            if( !STplayer.data(STCONST_DATAKEY_EDITMESSAGE_SHOWN) ) {
              STplayer.data(STCONST_DATAKEY_EDITMESSAGE_SHOWN, true);
              STplayer.STshowMessage(STCONST_TEXT_MESSAGE_EDIT);
              window.setTimeout(function() {$(STplayer).SThideMessage()}, 5000);
            }
          }
                
          // Was this a true click? or was there dragging involved
          if(STgetSelectedText() != "")
            return;
          
          // Load the content object that was clicked
          var transcript = $(this).STtranscript();
          var contentID = $(this).attr("id").slice(STCONST_PLAYER_TRANSCRIPTCONTENT_PREFIX.length);
          var clickedContent = transcript.getContentByID(contentID);
          if(!clickedContent)
            return;
          
          // Trigger the content clicked event
          $(this).trigger(STCONST_EVENT_CONTENT_CLICKED,[clickedContent]);
        });
        
        // If editing is enabled, watch for content changes
        if(playerConfig.editEnabled == STCONST_CONFIG_EDITENABLED_ON) {
          $(transcriptDisplay).find("." + STCONST_CLASS_PLAYER_TRANSCRIPT_CONTENT)
          .blur(function(e) {
            // Load the content object that was clicked
            var transcript = $(this).STtranscript();
            var contentID = $(this).attr("id").slice(STCONST_PLAYER_TRANSCRIPTCONTENT_PREFIX.length);
            
            $(this).removeClass(STCONST_CLASS_EDIT_EMPTY);
            
            if($(this).html() == "") {
              $(this).html(" ");
              $(this).addClass(STCONST_CLASS_EDIT_EMPTY);
            }
            
            transcript.updateContentByID(contentID, $(this).html());
          })
          .bind('keydown', function(e) {
            // Fix the damn backspace
            if( e.keyCode == 8 ) {
              var prevNode = $(this).prev().get(0);
              // the previous chunk exists
              if( typeof( prevNode ) !== "undefined" ) {
                // the last character of the previous chunk is a <br> tag
                if( prevNode.lastChild.nodeType === Node.ELEMENT_NODE && prevNode.lastChild.nodeName.toLowerCase() === "br" ) {
                  // we're at the first character of the current chunk
                  var selObj = window.getSelection();
                  if( selObj.anchorOffset == 0 && (selObj.anchorNode == this || selObj.anchorNode == this.firstChild) ) {
                    prevNode.removeChild(prevNode.lastChild);
                  }
                }
              }
            }
          })
          .bind('keypress', function(e) {
            // Fix enter in firefox to insert line break
            if( isFirefox() && e.keyCode == 13 ) {
              var sel = window.getSelection();
              var range = sel.getRangeAt(0);
              range.deleteContents();
              var newNode = document.createElement("br");
              range.insertNode(newNode);
              range.setStartAfter(newNode);
            }
          });
        }
        
        // Check to see if there were any QuoteLink requests
        timestamp = STgetURLParameterByName(window.location.href, STCONST_QUERY_PLAYER_QUOTELINK + transcriptID);
        if(timestamp != "") {
          timestamp = new Number(timestamp);
          var quoteLinkContent = transcript.getContentByTimestamp(timestamp);
          $(this).trigger(STCONST_EVENT_PLAYER_SHOW);
          $(this).trigger(STCONST_EVENT_CONTENT_CLICKED,[quoteLinkContent]);
        }

        
        $(this).STredraw();
        return this;
      },
    
    
    // DOM Methods
      /* Returns the video player DOM element associated with this ST player */
      STvideoPlayer: function() {
        return $("#" + $(this).data(STCONST_DATAKEY_VIDEOPLAYERID));
      },
      
      /* Returns the header bar DOM element associated with this ST player */
      STbar: function() {
        return $(this).find("." + STCONST_CLASS_PLAYER_BAR);
      },
      
      /* Returns the transcript DOM element associated with this ST player */
      STtranscriptPane: function() {
        return $(this).find("." + STCONST_CLASS_PLAYER_TRANSCRIPTPANE);
      },
      
      STsharePane: function() {
        return $(this).find("." + STCONST_CLASS_PLAYER_SHAREPANE);
      },
      
      STtranscriptDisplay: function() {
        return $(this).find("." + STCONST_CLASS_PLAYER_TRANSCRIPTDISPLAY);
      },
      
      STtranscriptWrapper: function() {
        return $(this).find("." + STCONST_CLASS_PLAYER_TRANSCRIPTWRAPPER);
      },
      
      STtranscriptStatus: function() {
        return $(this).find("." + STCONST_CLASS_PLAYER_TRANSCRIPTSTATUS);
      },
      
      /* Returns the player handle */
      STplayerHandle: function() {
        return $(this).find("." + STCONST_CLASS_PLAYER_HANDLE);
      },
      STplayerBase: function() {
        return $(this).find("." + STCONST_CLASS_PLAYER_BASE);
      },
      
      
      /* Returns the autoscroll button DOM element associated with this ST player */
      STautoScrollButton: function() {
        return $(this).find("." + STCONST_CLASS_PLAYER_AUTOSCROLL);
      },
      
      /* Returns the click to show text button DOM element associated with this ST player */
      STtranscriptShowButton: function() {
        return $(this).find("." + STCONST_CLASS_PLAYER_SHOWTEXT);
      },
      
      /* Returns the autoscroll button DOM element associated with this ST player */
      STscrollBar: function() {
        return $(this).find("." + STCONST_CLASS_PLAYER_SCROLLBAR);
      },
      
      /* Returns the autoscroll button DOM element associated with this ST player */
      STscrollUp: function() {
        return $(this).find("." + STCONST_CLASS_PLAYER_SCROLLUP);
      },
      
      /* Returns the autoscroll button DOM element associated with this ST player */
      STscrollDown: function() {
        return $(this).find("." + STCONST_CLASS_PLAYER_SCROLLDOWN);
      },
      
      /* Returns the autoscroll button DOM element associated with this ST player */
      STscrollVisible: function() {
        return $(this).find("." + STCONST_CLASS_PLAYER_SCROLLVISIBLE);
      },
      
      /* Returns the search area DOM element associated with this ST player */
      STsearch: function() {
        return $(this).find("." + STCONST_CLASS_PLAYER_SEARCH);
      },
      
      /* Returns the search field area DOM element associated with this ST player */
      STsearchField: function() {
        return $(this).find("." + STCONST_CLASS_PLAYER_SEARCHFIELD);
      },
      
      /* Returns the embed wrapper DOM element associated with this ST player */
      STembedWrapper: function() {
        return $(this).find("." + STCONST_CLASS_PLAYER_EMBEDWRAPPER);
      },
      
      /* Returns the search prev button DOM element associated with this ST player */
      STsearchPrevButton: function() {
        return $(this).find("." + STCONST_CLASS_PLAYER_SEARCHPREVBUTTON);
      },
      
      /* Returns the search next button DOM element associated with this ST player */
      STsearchNextButton: function() {
        return $(this).find("." + STCONST_CLASS_PLAYER_SEARCHNEXTBUTTON);
      },
      
      /* Returns the font size button DOM element associated with this ST player */
      STfontSizeButton: function() {
        return $(this).find("." + STCONST_CLASS_PLAYER_FONTSIZEBUTTON);
      },
      
      /* Returns the message pane DOM element associated with this ST player */
      STmessagePane: function() {
        return $(this).find("." + STCONST_CLASS_MESSAGEPANE);
      },
      
      /* Returns the transcript object associated with this ST player */
      STtranscript: function() {
        var transcriptKey;
        
        if($(this).hasClass(STCONST_CLASS_PLAYER))
          transcriptKey = $(this).data(STCONST_DATAKEY_TRANSCRIPTID);
        else
          transcriptKey = $(this).parents("." + STCONST_CLASS_PLAYER).eq(0).data(STCONST_DATAKEY_TRANSCRIPTID)
        
        var transcript = STtranscriptCache[transcriptKey];
        if(transcript)
          return STtranscriptCache[transcriptKey];
        else
          return null;
      },
      
      /* Returns the save button DOM element associated with this ST player */
      STsaveButton: function() {
        return $(this).find("." + STCONST_CLASS_EDIT_SAVE);
      },
      
      /* Returns the cancelbutton DOM element associated with this ST player */
      STcancelButton: function() {
        return $(this).find("." + STCONST_CLASS_EDIT_CANCEL);
      },
      
      /* Returns the ST player object associated with this item */
      STplayer: function() {
        var currentID = $(this).attr("id");
        if(currentID in STplayerCache)
          return STplayerCache[currentID];
        return null;
      },
      
      /* Returns the ST player autoscroll indicator */
      STautoScroll: function() {
        return $(this).find("." + STCONST_DATAKEY_TRANSCRIPT_AUTOSCROLL);
      },
      
      /* Loads transcript from where it was embedded on the page */
      STloadTranscriptFromPage: function(preEmbedID) {
        var transcriptID = preEmbedID.replace(STCONST_PLAYER_TRANSCRIPTEMBED_PREFIX + '_', '');
        var transcriptEmbed = $("#" + preEmbedID);
        
        // Mark this element as "waiting for the transcript to load"
        $(this).addClass(STCONST_CLASS_PLAYER_TRANSCRIPT_LOADING + transcriptID);

        // Let the user know what's going on
        var STtranscriptStatus = $(this).STtranscriptStatus();
        STtranscriptStatus.html(STCONST_TEXT_TRANSCRIPT_LOADING);
        
        var contentID = 1;
        var transcript = new STTranscript()
        $(transcriptEmbed).find("." + STCONST_CLASS_PLAYER_TRANSCRIPT_CONTENT).each(function() {
          var content = new STContent()
          content.id = parseInt($(this).attr("id").slice(STCONST_PLAYER_TRANSCRIPTCONTENT_PREFIX.length));
          content.timestamp = parseInt($(this).attr("name"));
          content.text = $(this).html();
          transcript.content.push(content);
        });
        transcript.transcriptID = transcriptID;
        transcript.hash = $(this).attr("data-hash");
        STtranscriptCache[transcriptID] = transcript;
        
        $(this).trigger(STCONST_EVENT_TRANSCRIPT_LOADED, [transcriptID]);
        return this;
      },
      
      STloadTranscriptFromServer: function(transcriptFilename) {
        // clear old tscript
        $(this).STtranscriptDisplay().html("");
        
        var transcriptID = transcriptFilename.replace(/-/g, '_');
        
        // If transcript has already been loaded -- render it
        if((transcriptID in STtranscriptCache) && STtranscriptCache[transcriptID] instanceof STTranscript) {
          $(this).STrenderTranscript(transcriptID);
          return this;
        }
        // If the transcript is in the process of being loaded -- mark it as "waiting for the transcript to load"
        else if((transcriptID in STtranscriptCache) && STtranscriptCache[transcriptID] instanceof XMLHttpRequest) {
          $(this).addClass(STCONST_CLASS_PLAYER_TRANSCRIPT_LOADING + transcriptID);
          return this;
        }
        
        // Mark this element as "waiting for the transcript to load"
        $(this).addClass(STCONST_CLASS_PLAYER_TRANSCRIPT_LOADING + transcriptID);

        // Let the user know what's going on
        var STtranscriptStatus = $(this).STtranscriptStatus();
        STtranscriptStatus.html(STCONST_TEXT_TRANSCRIPT_LOADING);
        
        var config = $(this).data(STCONST_DATAKEY_CONFIG);
        var url = config.transcriptPath;
        
        // add trailing slash
        if( url.length > 0 && url.charAt(url.length - 1) != '/' )
          url += '/';
        
        url += transcriptFilename + '.html';
        
        STplayer = $(this);

        STtranscriptCache[transcriptID] = $.ajax({
          type: "GET",
          cache: false,
          url: url,
          dataType: "html",
          success: function(html, status, request) {
            STtranscriptStatus.html("");
            var transcript = new STTranscript();
            
            $(html).find("." + STCONST_CLASS_PLAYER_TRANSCRIPT_CONTENT).each(function() {
              var content = new STContent()
              content.id = parseInt($(this).attr("id").slice(STCONST_PLAYER_TRANSCRIPTCONTENT_PREFIX.length));
              content.timestamp = parseInt($(this).attr("name"));
              content.text = $(this).html();
              transcript.content.push(content);
            });
            
            transcript.transcriptID = transcriptID;
            transcript.hash = $(html).attr("data-hash");
            STtranscriptCache[transcriptID] = transcript;

            STplayer.trigger(STCONST_EVENT_TRANSCRIPT_LOADED, [transcriptID]);
          }, 
          error: function(request, status, error) {
            // Display the loading message
            STtranscriptStatus.html(STCONST_TEXT_TRANSCRIPT_UNAVAILABLE);
          }
        });
        
        return this;
      },
      
    // Message Handlers
      STshowMessage: function(message) {
        var STmessagePane = $(this).STmessagePane();
        STmessagePane.html(message);
        STmessagePane.fadeIn(200);
      },
      
      STshowCopyMessage: function() {
        var STsharePane = $(this).STsharePane();
        STsharePane.addClass(STCONST_CLASS_COPY_FEEDBACK);
        STsharePane.find("." + STCONST_CLASS_PLAYER_SHAREPANE_TEXT).html(STCONST_TEXT_MESSAGE_COPY);
        STsharePane.find("." + STCONST_CLASS_SHARE_BUTTON).hide();
      },
      
      SThideMessage: function() {
        var STmessagePane = $(this).STmessagePane();
        STmessagePane.fadeOut(200);
      },
      
      SThideCopyMessage: function() {
        var STsharePane = $(this).STsharePane().removeClass(STCONST_CLASS_COPY_FEEDBACK);
        if( STsharePane.hasClass(STCONST_CLASS_PLAYER_SHAREPANE_HIGHLIGHT) ) {
          STsharePane.find("." + STCONST_CLASS_PLAYER_SHAREPANE_TEXT).html( STCONST_TEXT_SHARE_HIGHLIGHTED );
        }
        else if( STsharePane.hasClass(STCONST_CLASS_PLAYER_SHAREPANE_HIGHLIGHT) ) {
          STsharePane.find("." + STCONST_CLASS_PLAYER_SHAREPANE_TEXT).html( STCONST_TEXT_SHARE_SELECTED );
        }
        else {
          STsharePane.find("." + STCONST_CLASS_PLAYER_SHAREPANE_TEXT).html( STCONST_TEXT_SHARE_UNSELECTED );
        }
        
        STsharePane.find("." + STCONST_CLASS_SHARE_BUTTON).show();
        
      },
    
    // Video Player Event Handlers
      onSTVPprogress: function(timestamp, setIgnore) {
        // timestamp is time in milliseconds
        // if setIgnore == true, we will ignore all future progress events until the video player
        // actually tells us that we are at the content we wished to go to
        if(typeof(timestamp) == 'undefined' || isNaN(timestamp) || timestamp <= 0)
          return;
        
        var transcript = $(this).STtranscript();
        if(transcript == null)
          return;
        
        var currentContent = transcript.getContentByTimestamp(timestamp);
        var ignoreUntil = $(this).data(STCONST_DATAKEY_TRANSCRIPT_IGNOREPROGRESS);
        
        if( ignoreUntil !== false && !setIgnore) {
          // ignore all updates until we see a timestamp within x seconds after the requested timestamp
          if( currentContent.id < ignoreUntil || currentContent.timestamp - STCONST_PLAYER_IGNORE_THRESHOLD > transcript.getContentByID(ignoreUntil).timestamp )
            return;
          else
            $(this).data(STCONST_DATAKEY_TRANSCRIPT_IGNOREPROGRESS, false);
        }
        
        var transcriptDisplay = $(this).STtranscriptDisplay();
        var oldContentID = $(this).data(STCONST_DATAKEY_TRANSCRIPT_CURRENTCONTENTID);
        
        
        // We don't want to redo a bunch of work
        if(oldContentID == currentContent.id)
          return;
        
        $(transcriptDisplay).find("#" + STCONST_PLAYER_TRANSCRIPTCONTENT_PREFIX + oldContentID).removeClass(STCONST_CLASS_PLAYER_CURRENTCONTENT);
        $(this).data(STCONST_DATAKEY_TRANSCRIPT_CURRENTCONTENTID, currentContent.id);
        $(transcriptDisplay).find("#" + STCONST_PLAYER_TRANSCRIPTCONTENT_PREFIX + currentContent.id).addClass(STCONST_CLASS_PLAYER_CURRENTCONTENT);
        
        if( setIgnore && $(this).data(STCONST_DATAKEY_VIDEOPLAYERTYPE) != STVP_HOST_BLIP ) {
          // We'll ignore progress in the video player until we hit this content piece
          // This will fix the jumpiness associated with the inaccuracy of the YouTube player
          $(this).data(STCONST_DATAKEY_TRANSCRIPT_IGNOREPROGRESS, currentContent.id);
        }
        
        if( ! $(this).STsharePane().hasClass(STCONST_CLASS_PLAYER_SHAREPANE_ACTIVE) )
          $(this).STsharePane().addClass(STCONST_CLASS_PLAYER_SHAREPANE_ACTIVE).find("." + STCONST_CLASS_PLAYER_SHAREPANE_TEXT).html(STCONST_TEXT_SHARE_SELECTED);
          
        // Reposition if needed
        $(this).STredraw();
      },
      
      STVPseekTo: function(timestamp) {
        if( typeof( timestamp ) == "undefined" )
          return;
        
        // timestamp is in milliseconds, convert to seconds
        timestamp /= 1000.0;
        
        var playerType = $(this).data(STCONST_DATAKEY_VIDEOPLAYERTYPE);
        switch(playerType) {
          case STVP_HOST_YT:
            var ytPlayer = document.getElementById($(this).STvideoPlayer().attr("id"));
            
            if( $(ytPlayer).is("iframe") ) {
              ytPlayer = new YT.Player( $(ytPlayer).attr('id') );
            }
            
            ytPlayer.seekTo(timestamp, true);
            break;
          case STVP_HOST_VIMEO:
            var vimeoPlayer = Froogaloop($(this).STvideoPlayer().attr("id"));
            vimeoPlayer.api('seekTo', timestamp);
            break;
          case STVP_HOST_BC:
            var bcExp = brightcove.getExperience($(this).STvideoPlayer().attr("id"));
            var modVP = bcExp.getModule(APIModules.VIDEO_PLAYER);
            modVP.seek(timestamp);
            break;
          case STVP_HOST_BLIP:
            var blipPlayer = document.getElementById($(this).STvideoPlayer().attr("id"));
            blipPlayer.sendEvent("seek",timestamp);
            break;
          case STVP_HOST_OOYALA:
            var ooyalaPlayer = document.getElementById($(this).STvideoPlayer().attr("id"));
            ooyalaPlayer.setPlayheadTime(timestamp);
            break;
          case STVP_HOST_SC:
            var scPlayer = soundcloud.getPlayer($(this).STvideoPlayer().attr("id"));
            scPlayer.api_seekTo(timestamp); 
            break;
          case STVP_HOST_WISTIA:
            var wistPlayer = document.getElementById($(this).STvideoPlayer().attr("id"));
            wistPlayer.videoSeek(timestamp);
            break;
          case STVP_PLAYER_JW:
            var jwPlayer = document.getElementById($(this).STvideoPlayer().attr("id"));
            jwPlayer.sendEvent("SEEK", timestamp);
            break;
          default:
            break;
        }
      },
      
      STVPseekToAndPlay: function(timestamp) {
        
        var playerType = $(this).data(STCONST_DATAKEY_VIDEOPLAYERTYPE);
        switch(playerType) {
          case STVP_HOST_YT:
            var ytPlayer = document.getElementById($(this).STvideoPlayer().attr("id"));
            
            if( $(ytPlayer).is("iframe") ) {
              ytPlayer = new YT.Player( $(ytPlayer).attr('id') );
            }

            ytPlayer.seekTo(timestamp / 1000.0, true);
            ytPlayer.playVideo();
            break;
          case STVP_HOST_VIMEO:
            var vimeoPlayer = Froogaloop($(this).STvideoPlayer().attr("id"));
            var STplayer = $(this);
            
            vimeoPlayer.api('paused', function( paused ) { 
              if( paused ) {
                STplayer.data(STCONST_DATAKEY_PLAYERQUEUE, timestamp);
                vimeoPlayer.api('play');
              } else {
                vimeoPlayer.api('seekTo', timestamp / 1000.0);
              }
            });
            break;
          case STVP_HOST_BC:
            var bcExp = brightcove.getExperience($(this).STvideoPlayer().attr("id"));
            var modVP = bcExp.getModule(APIModules.VIDEO_PLAYER);
            
            if(!modVP.isPlaying()) {
              $(this).data(STCONST_DATAKEY_PLAYERQUEUE, timestamp);
              modVP.pause(false);
            }
            else {
              modVP.seek(timestamp / 1000.0);
            }
            break;
          case STVP_HOST_BLIP:
            var blipPlayer = document.getElementById($(this).STvideoPlayer().attr("id"));
            $(this).data(STCONST_DATAKEY_PLAYERQUEUE, timestamp);
            blipPlayer.sendEvent("play");
            break;
          case STVP_HOST_OOYALA:
            var ooyalaPlayer = document.getElementById($(this).STvideoPlayer().attr("id"));
            if(ooyalaPlayer.getState() != ST_CONST_OOYALASTATE_PLAYING || $(this).data(STCONST_DATAKEY_PLAYER_DISABLED)) {
              $(this).data(STCONST_DATAKEY_PLAYERQUEUE, timestamp);
              ooyalaPlayer.playMovie();
            }
            else {
              ooyalaPlayer.setPlayheadTime(timestamp / 1000.0);
            }
            break;
          case STVP_HOST_SC:
            var scPlayer = soundcloud.getPlayer($(this).STvideoPlayer().attr("id"));
            // Always store the timestamp to skip to later because soundcloud has no player state method
            $(this).data(STCONST_DATAKEY_PLAYERQUEUE, timestamp);
            scPlayer.api_play();
            break;
          case STVP_HOST_WISTIA:
            var wistPlayer = document.getElementById($(this).STvideoPlayer().attr("id"));
            wistPlayer.videoSeek(timestamp/1000);
            wistPlayer.videoPlay();
            break;
          case STVP_PLAYER_JW:
            var jwPlayer = document.getElementById($(this).STvideoPlayer().attr("id"));
            if( jwPlayer.getConfig().state == "IDLE" ) {
              jwPlayer.sendEvent("PLAY");
              $(this).data(STCONST_DATAKEY_PLAYERQUEUE, timestamp);
            } else {
              jwPlayer.sendEvent("SEEK", timestamp / 1000.0); // also plays the video (if it's paused)
            }
            break;
          default:
            break;
        }
      }
  });
})(stjQuery);
  
  
/* No man's land */

function isChrome() {
  return navigator.userAgent.toLowerCase().indexOf("chrome") != -1;
}

function isFirefox() {
  return navigator.userAgent.toLowerCase().indexOf("firefox") != -1;
}

function isMSIE() {
  return navigator.userAgent.toLowerCase().indexOf("msie") != -1;
}

function STgetSelectedText() {
  if(window.getSelection){
    return window.getSelection();
  }else if (document.getSelection){
    return document.getSelection();
  }else if (document.selection){
    return document.selection.createRange().text;
  }
  return "";
}

function STgetSelectedContentID() {
  var contentID;
  if (typeof window.getSelection != "undefined") {
    var userSelection = window.getSelection();
    if (userSelection.toString()) if (typeof userSelection.setBaseAndExtent != "undefined") {
      // Safari
      var mainRange = userSelection.getRangeAt(0);
      var tempRange = mainRange.cloneRange();
      tempRange.collapse(true);
      var selectedElement = tempRange.startContainer;
      if(typeof(stjQuery(selectedElement).attr("id")) == "undefined")
        selectedElement = stjQuery(selectedElement).parent();
      return stjQuery(selectedElement).attr("id").slice(STCONST_PLAYER_TRANSCRIPTCONTENT_PREFIX.length);
    }
    else {
      // Firefox
      var userSelection = window.getSelection();
      var mainRange = userSelection.getRangeAt(0);
      var tempRange = mainRange.cloneRange();
      tempRange.collapse(true);
      var selectedElement = tempRange.startContainer;
      if(typeof(stjQuery(selectedElement).attr("id")) == "undefined")
        selectedElement = stjQuery(selectedElement).parent();
      return contentID = stjQuery(selectedElement).attr("id").slice(STCONST_PLAYER_TRANSCRIPTCONTENT_PREFIX.length);
    }
  } else if (document.selection) {
    // Internet Explorer
    mainRange = document.selection.createRange();
    tempRange = document.selection.createRange();
    tempRange.collapse(true); 
    var selectedElement = tempRange.parentElement();
    return stjQuery(selectedElement).attr("id").slice(STCONST_PLAYER_TRANSCRIPTCONTENT_PREFIX.length);
  }
}

function STgetURLParameterByName(url, name) {
  name = name.replace(/[\[]/,"\\\[").replace(/[\]]/,"\\\]");
  var regexS = "[\\?&]?"+name+"=([^&#]*)";
  var regex = new RegExp( regexS );
  var results = regex.exec( url );
  
  if( results == null )
    return "";
  else
    return decodeURIComponent(results[1].replace(/\+/g, " "));
}

function STsetURLParameterByName(url, name, value) {
  var returnURL = "";
  if(url.indexOf("?") != -1) {
    qlIndex = url.indexOf(name + "=");
    if(qlIndex != -1) {
      returnURL = url.substring(0,qlIndex) + name + "=" + value;
      isThereMore = url.indexOf("&", qlIndex);
      if(isThereMore > -1)
        returnURL += url.substring(isThereMore);
    }
    else {
      returnURL = url + "&" + name + "=" + value;
    }
  } else {
    returnURL = url + "?" + name + "=" + value;
  }
  return returnURL;
}

/**
 * credits for this plugin go to brandonaaron.net
 */
stjQuery.fn.extend({
  mousewheel: function(up, down, preventDefault) {
    return this.hover(
      function() {
        stjQuery.event.mousewheel.giveFocus(this, up, down, preventDefault);
      },
      function() {
        stjQuery.event.mousewheel.removeFocus(this);
      }
    );
  },
  mousewheeldown: function(fn, preventDefault) {
    return this.mousewheel(function(){}, fn, preventDefault);
  },
  mousewheelup: function(fn, preventDefault) {
    return this.mousewheel(fn, function(){}, preventDefault);
  },
  unmousewheel: function() {
    return this.each(function() {
      stjQuery(this).unmouseover().unmouseout();
      stjQuery.event.mousewheel.removeFocus(this);
    });
  },
  unmousewheeldown: stjQuery.fn.unmousewheel,
  unmousewheelup: stjQuery.fn.unmousewheel
});


stjQuery.event.mousewheel = {
  giveFocus: function(el, up, down, preventDefault) {
    if (el._handleMousewheel) stjQuery(el).unmousewheel();

    if (preventDefault == window.undefined && down && down.constructor != Function) {
      preventDefault = down;
      down = null;
    }

    el._handleMousewheel = function(event) {
      if (!event) event = window.event;
      if (preventDefault)
        if (event.preventDefault) event.preventDefault();
        else event.returnValue = false;
      var delta = 0;
      if (event.wheelDelta) {
        delta = event.wheelDelta/120;
        if (window.opera) delta = -delta;
      } else if (event.detail) {
        delta = -event.detail/3;
      }
      if (up && (delta > 0 || !down))
        up.apply(el, [event, delta]);
      else if (down && delta < 0)
        down.apply(el, [event, delta]);
    };

    if (window.addEventListener)
      window.addEventListener('DOMMouseScroll', el._handleMousewheel, false);
    window.onmousewheel = document.onmousewheel = el._handleMousewheel;
  },

  removeFocus: function(el) {
    if (!el._handleMousewheel) return;

    if (window.removeEventListener)
      window.removeEventListener('DOMMouseScroll', el._handleMousewheel, false);
    window.onmousewheel = document.onmousewheel = null;
    el._handleMousewheel = null;
  }
};

/* Dependency Management */
stjQuery.extend({
  loadCaptionBoxDependency: function( relativePath, callback ) {
    var captionBoxPath = stjQuery("script[src*='" + STCONST_FILENAME + "']").attr('src').replace( new RegExp("(.*)" + STCONST_FILENAME + ".*"), '$1');
    stjQuery.ajax({type: "GET", url: captionBoxPath + relativePath, success: callback, dataType: "script", cache: true});
  }  
});
  
  
/* SpeakerText Glue
 * Dan Schultz 2010
 */

/* Player ID Info */
var  STVP_PLAYER_AUTOID_PREFIX = "STautoID";

/* Video Player Hosts */
var  STVP_HOST_YT = 1;
var  STVP_HOST_VIMEO = 2;
var  STVP_HOST_BC = 3;
var  STVP_HOST_BLIP = 4;
var  STVP_HOST_OOYALA = 5;
var  STVP_HOST_SC = 6;
var  STVP_HOST_SELF = 7;
var  STVP_HOST_WISTIA = 8

var  STVP_PLAYER_JW = 1000;

/* Youtube Constants */
var  ST_CONST_YTSTATE_UNSTARTED = -1;
var  ST_CONST_YTSTATE_ENDED = 0;
var  ST_CONST_YTSTATE_PLAYING = 1;
var  ST_CONST_YTSTATE_PAUSED = 2;
var  ST_CONST_YTSTATE_BUFFERING = 3;
var  ST_CONST_YTSTATE_CUED = 5;

/* Blip.TV Constants */
var  ST_CONST_BLIPUPDATE_STATECHANGE = "player_state_change";
var  ST_CONST_BLIPUPDATE_PROGRESS = "time";
var  ST_CONST_BLIPUPDATE_PROGRESS_2 = "current_time_change";

/* Ooyala Constants */
var  ST_CONST_OOYALASTATE_PLAYING = "playing";
var  ST_CONST_OOYALASTATE_PAUSED = "paused";
var  ST_CONST_OOYALASTATE_BUFFERING = "buffering";

  
// Embed Setups
(function($) {
  $(function() {
    var autoID = 0;
    
    // add Brightcove API script to page if main Brightcove JS is loaded and it's not there already
    if( typeof( brightcove ) != "undefined" && typeof( APIModules ) == "undefined" ) {
      var host = brightcove.cdnURL;
      if( window.location.protocol == 'https:' )
        host = brightcove.secureCDNURL;
              
      $.ajax({
        async: false,
        type: "GET",
        url: host + "/js/APIModules_all.js",
        dataType: 'script'
      });
    }
    
    // select all objects and all embeds which are not children of objects
    $("object, embed:not(object > embed)").each(function() {
      autoID++;
      var target = $(this);
      
      // Set up the swfobject parameters
      var playerID = target.attr("id");
      if(typeof(playerID) == "undefined" || playerID == "")
        playerID = STVP_PLAYER_AUTOID_PREFIX + autoID;
        
      target.attr("id",playerID);
      target.attr("name",playerID);
        
      var src = "";
      if( this.tagName.toLowerCase() == "embed" ) {
        src = target.attr("src");
      } else {
        var em = target.find("embed");
        if( em.length )
          src = em.attr("src");
        else if( target.attr("data") )
          src = target.attr("data");
        else
          return true;
      }

      var height = target.attr("height");
      var width = target.attr("width");
      var fixEmbeds = function() {

        var params = {};

        // carry over params
        if( target.get(0).tagName.toLowerCase() == "object") {
          target.find("param").each( function() {
            params[$(this).attr("name")] = $(this).attr("value");
          });
        }
        
        params["allowScriptAccess"] = 'always';
        
        var atts = { id: playerID, name: playerID };
        
        if(src.match("youtube.com")) {
          // YouTube
          if(STgetURLParameterByName(src, "enablejsapi") != 1)
            src = STsetURLParameterByName(src, "enablejsapi", 1);
          if(STgetURLParameterByName(src, "playerapiid") != 1)
            src = STsetURLParameterByName(src, "playerapiid", playerID);
          
          swfobject.embedSWF(src, playerID, width, height, '8', null, null, params, atts);
        }
        else if(src.match("blip.tv")) {
          // Blip.tv
          swfobject.embedSWF(src, playerID, width, height, '8', null, null, params, atts);
        }
        else if(src.match("wistia.com")) {
          // Wistia
          swfobject.embedSWF(src, playerID, width, height, '8', null, null, params, atts);
          
          var interval;
          function triggerWistia() {
            //wistia player api doesn't give any onReady events
            try {
              var wistiaPlayer = window.document.getElementById(playerID);
              //this will throw an exception if the player isn't loaded
              wistiaPlayer.getCurrentState();
              interval = clearInterval(interval);
              onWistiaPlayerReady(playerID);
            }
            catch(e){
              if( console && console.error )
                console.error(e); 
            }
          }
          interval = setInterval(triggerWistia,100);
        
        }
        else if(src.match("soundcloud.com")) {
          // SoundCloud
          if(STgetURLParameterByName(src, "enable_api") != "true")
            src = STsetURLParameterByName(src, "enable_api", "true");
          if(STgetURLParameterByName(src, "object_id") != playerID)
            src = STsetURLParameterByName(src, "object_id", playerID);
          
          swfobject.embedSWF(src, playerID, width, height, '8', null, null, params, atts);
          //when the script is loaded we add an event listener which calls onSoundCloudPlayerReady
          $.loadCaptionBoxDependency('soundcloud.player.api.js', 
              function(){
                  soundcloud.addEventListener('onPlayerReady', onSoundCloudPlayerReady);}
          );
        }
      }
      // make sure swfobject is loaded
      if(typeof(swfobject) == "undefined") {
        $.getScript(window.document.location.protocol + "//ajax.googleapis.com/ajax/libs/swfobject/2.2/swfobject.js", fixEmbeds);
      }
      else {
        fixEmbeds();
      }

    });
     
    function setupVimeoPlayer(element) { 
      autoID++;
    
      var iframe_src = $(element).attr('src');
      var api_enabled = STgetURLParameterByName(iframe_src, 'api');
      var playerID = STgetURLParameterByName(iframe_src, 'player_id');
    
      if( api_enabled === '1' && typeof(playerID) !== "undefined" && playerID !== "" ) {
        // Set the element ID to be the same as the iFrame Query String ID if it isn't
        if( playerID !== $(element).attr('id') ) {
          $(element).attr('id', playerID);
        }
      } else {
        // We're going to have to re-load this iFrame
        iframe_src = STsetURLParameterByName(iframe_src, 'api', '1');
        playerID = playerID || STVP_PLAYER_AUTOID_PREFIX + autoID;
        iframe_src = STsetURLParameterByName(iframe_src, 'player_id', playerID);
      
        $(element).attr('src', iframe_src);
        $(element).attr('id', playerID);
      }
      
      Froogaloop(playerID).addEvent('ready', STonVimeoReady)
    }
    
    // Deal with Vimeo player iFrames
    var vimeoPlayers = $("iframe[src*='vimeo']");
    if( vimeoPlayers.length > 0 ) {
      
      function setupVimeoPlayers() {
        vimeoPlayers.each( function() { setupVimeoPlayer(this); } );
      }
      
      if( typeof(Froogaloop) == 'undefined' ) {
        $.loadCaptionBoxDependency('froogaloop.min.js', setupVimeoPlayers );
      } else {
        setupVimeoPlayers();
      }
    }
    
    // Deal with YouTube player iFrames
    function setupYouTubeIFrame(element) {
      autoID++;
      
      // Set an id if this iframe doesn't have one
      var playerID = $(element).attr("id");
      if(typeof(playerID) == "undefined" || playerID == "") {
        playerID = STVP_PLAYER_AUTOID_PREFIX + autoID;
        $(element).attr('id', playerID);
      }
    
      // add the api enabled parameter to the source if it's not already there
      var iframe_src = $(element).attr('src');
      var api_enabled = STgetURLParameterByName(iframe_src, 'enablejsapi');
      
      if( api_enabled !== '1' ) {
        iframe_src = STsetURLParameterByName(iframe_src, 'enablejsapi', '1');
        $(element).attr('src', iframe_src);
      }
      
    }

    var youtubePlayers = $("iframe[src*='youtube']");
    if( youtubePlayers.length > 0 ) {
      youtubePlayers.each( function() { setupYouTubeIFrame(this); } );
      
      if( typeof( YT ) == "undefined" ) {
        $.getScript(window.document.location.protocol + "//www.youtube.com/player_api"); // this script calls onYouTubePlayerAPIReady when loaded
      }
    }
  });
})(stjQuery);

function setupSpeakerText(elem, player_id, opts) {
  elem.speakerText(player_id, opts);
              
  var STplayer = elem.STplayer();
  var STmessagePane = STplayer.STmessagePane();
  var STembedWrapper = STplayer.STembedWrapper();
  
  var config = STplayer.data(STCONST_DATAKEY_CONFIG);
  
  var cbWidth = Math.max(400, STplayer.width());

  if( config.matchVideoWidth )
    cbWidth = Math.max(400, elem.width());
  
  STplayer.width(cbWidth - (STplayer.outerWidth() - STplayer.innerWidth()));
  STmessagePane.width(cbWidth - (STmessagePane.outerWidth() - STmessagePane.innerWidth()));
  STembedWrapper.width(cbWidth - (STembedWrapper.outerWidth() - STembedWrapper.innerWidth()));
  
  // position speakertext directly under the video player
  if( elem.css("text-align") == "center" )
    STplayer.css("margin", "0 auto");
  else
    STplayer.css("margin-left", elem.css("margin-left"));
  
  if( STplayer.width() < 460 )
    STplayer.addClass('narrow');
    
  STplayer.STredraw();  // because we changed the dimensions!
    
  return STplayer;
}

////////////////////////////////////////
////          YOUTUBE GLUE          ////
////////////////////////////////////////

function onYouTubePlayerReady(playerID) {
  (function($) {
    if(typeof(playerID) == "undefined")
      return;
    var ytPlayer = document.getElementById(playerID);
    var ytWrapper = $(ytPlayer).parent();
    if(!ytWrapper.is("object"))
      ytWrapper = $(ytPlayer);
    
    // Initialize the Player
    if($(ytPlayer).is("object")) {
      src = $(ytPlayer).attr("data");
      if(typeof(src) == "undefined" || src == "")
        src = $(ytPlayer).find("param[name='movie']").attr("value");
    }
    else
      src = $(ytPlayer).attr("src");
    
    var videoID = src.match(/v\/[a-zA-Z0-9_\-]+/)[0].slice(2);
    var STplayer = setupSpeakerText(ytWrapper, STVP_HOST_YT, {videoPlayerID: playerID, 
                                                              preEmbedID: STCONST_PLAYER_TRANSCRIPTEMBED_PREFIX + "_" + STVP_HOST_YT + "_" + videoID });  
    
    // Add state change handler
    //ytPlayer.addEventListener("onStateChange", '(function(state) { return STYTplayerStateChange(state, "' + playerID + '"); })');
    
    // Add progress listeners
    window.setInterval(function() {
      var currentTimestamp = ytPlayer.getCurrentTime();
      var queuePoint = $(STplayer).data(STCONST_DATAKEY_PLAYERQUEUE);
      if(typeof(queuePoint) == "undefined") {
        // triggered when reembedding with swfobject
        STplayer.data(STCONST_DATAKEY_PLAYERQUEUE, -1);
      }
      else if(queuePoint != -1) {
        STplayer.data(STCONST_DATAKEY_PLAYERQUEUE, -1);
        STplayer.STVPseekTo(queuePoint);
      }
      else {
        STplayer.onSTVPprogress(currentTimestamp * 1000, false);
      }
    }, 100);
  })(stjQuery);
}

function STYTplayerStateChange(state, playerID) {
  var state = state;
  (function($) {
    var ytPlayer = document.getElementById(playerID);
    var ytWrapper = $(ytPlayer).parent();
    if(!ytWrapper.is("object"))
      ytWrapper = $(ytPlayer);
    var STplayer = ytWrapper.STplayer();
    switch(state) {
      case ST_CONST_YTSTATE_UNSTARTED:
        break;
      case ST_CONST_YTSTATE_ENDED:
        break;
      case ST_CONST_YTSTATE_PLAYING:
        // Gets triggered every time a new video is selected
        // This event also seems to be the only one triggered on autoplay videos
        //var currentVideoID = STgetURLParameterByName(ytPlayer.getVideoUrl(), "v");
        //STplayer.STloadTranscriptFromVideoID(STVP_HOST_YT, currentVideoID);
        break;
      case ST_CONST_YTSTATE_PAUSED:
        break;
      case ST_CONST_YTSTATE_BUFFERING:
        break;
      case ST_CONST_YTSTATE_CUED:
        // This seems to be the only thing triggered when swfobject embeds a video (and it doesn't autoplay)
        // hopefully its not a problem to rerender the transcript when we get the playing event as well
        // although it doesn't seem to be necessary right now so i took it out
        //var currentVideoID = STgetURLParameterByName(ytPlayer.getVideoUrl(), "v");
        //STplayer.STloadTranscriptFromVideoID(STVP_HOST_YT, currentVideoID);
        break;
    }
  })(stjQuery);
}

////////////////////////////////////////
//// YouTube-New iFrame Embed GLUE  ////
////////////////////////////////////////

function onYouTubePlayerAPIReady() {
  (function($) {
    // Construct objects for all players and attach events to them
    $("iframe[src*='youtube']").each( function() {
      var player = new YT.Player( $(this).attr('id'), {
                    events: {
                      'onReady': onYouTubeiFramePlayerReady,
                    }
                  });
           
    });
  })(stjQuery);
}

function onYouTubeiFramePlayerReady(event) {
  (function($) {
    var apiObject = event.target;
    
    var ytElem = $(apiObject.a);
    var playerID = ytElem.attr('id');

    var videoUrl = ytElem.attr('src');
    var videoID = videoUrl.match(/\/embed\/(.*?)\?|\z/)[1];
    
    var STplayer = setupSpeakerText($(ytElem), STVP_HOST_YT, {videoPlayerID: playerID, 
                                                              preEmbedID: STCONST_PLAYER_TRANSCRIPTEMBED_PREFIX + "_" + STVP_HOST_YT + "_" + videoID });  
    
    // Add progress listeners
    window.setInterval(function() {
      var currentTimestamp = apiObject.getCurrentTime();
      var queuePoint = $(STplayer).data(STCONST_DATAKEY_PLAYERQUEUE);
      if(typeof(queuePoint) == "undefined") {
        // triggered when reembedding with swfobject
        STplayer.data(STCONST_DATAKEY_PLAYERQUEUE, -1);
      }
      else if(queuePoint != -1) {
        STplayer.data(STCONST_DATAKEY_PLAYERQUEUE, -1);
        STplayer.STVPseekTo(queuePoint);
      }
      else {
        STplayer.onSTVPprogress(currentTimestamp * 1000, false);
      }
    }, 100);
  })(stjQuery);
}

////////////////////////////////////////
////       BRIGHTCOVE GLUE          ////
////////////////////////////////////////

function onTemplateLoaded(experienceID) {
  (function($) {
    var bcExpID = experienceID;
    var bcExp = brightcove.getExperience(bcExpID); 
    var modVP = bcExp.getModule(APIModules.VIDEO_PLAYER);
    var modExp = bcExp.getModule(APIModules.EXPERIENCE);
    var modCon = bcExp.getModule(APIModules.CONTENT);
    var experienceObject = $("#" + experienceID);

    // Add content load listener
    var contentLoaded = function() {
      var currentVideo = modVP.getCurrentVideo();
      var videoID = currentVideo.id;

      if( experienceObject.STplayer() === null ) {
        // initialize this player
        var STplayer = setupSpeakerText(experienceObject, STVP_HOST_BC, {preEmbedID: STCONST_PLAYER_TRANSCRIPTEMBED_PREFIX + "_" + STVP_HOST_BC + "_" + videoID });
        modVP.pause(true);
      } else {
        
        // the video changed, load a new transcript
        var STplayer = experienceObject.STplayer();

        // reset current content id
        STplayer.data(STCONST_DATAKEY_TRANSCRIPT_CURRENTCONTENTID,0);

        STplayer.STloadTranscriptFromServer( STVP_HOST_BC + "-" + videoID );   
      }

    }
    
    if( modVP.getCurrentVideo() != null ) {
      // in case we missed the event
      contentLoaded();
    }
    else {
      //modExp.addEventListener(BCExperienceEvent.CONTENT_LOAD, contentLoaded);
      modVP.addEventListener(BCMediaEvent.CHANGE, contentLoaded);
    }
    
    // Add media event listeners
    modVP.addEventListener(BCMediaEvent.PROGRESS, function (evt) {
      var STplayer = experienceObject.STplayer();
      var queuePoint = $(STplayer).data(STCONST_DATAKEY_PLAYERQUEUE);
      if(queuePoint != -1) {
        STplayer.data(STCONST_DATAKEY_PLAYERQUEUE, -1);
        STplayer.STVPseekTo(queuePoint);
      }
      else {
        STplayer.onSTVPprogress(evt.position * 1000, false);
      }
    });
  })(stjQuery);
}

////////////////////////////////////////
////          BLIP.TV GLUE          ////
////////////////////////////////////////

function getUpdate(type, arg1, arg2) {
  (function($) {
    // Since there is no ID for blip we can only support the first player
    var src = '';
    var swf = $("embed[src*='blip.tv']").first();

    if( typeof(swf_id) == "undefined" ) {
      swf = $("object[data*='blip.tv']").first();
      src = $(swf).attr("data");
    }
    else {
      src = $(swf).attr("src");
    }
    
    var blipPlayer = document.getElementById($(swf).attr('id'));
    
    // Extract the episode ID
    var episodeID = src.match(/play\/[a-zA-Z0-9+]+/)[0].slice(5);
    
    var preEmbedID = STCONST_PLAYER_TRANSCRIPTEMBED_PREFIX + "_" + STVP_HOST_BLIP + "_" + episodeID;
    
    // If the Blip episode ID has any '+' characters in it, we'll sanitize it in the transcript ID,
    // because it causes massive problems with jQuery selectors
    if( episodeID.indexOf( '+' ) !== -1 ) {
      var preEmbed = $("#" + preEmbedID.replace(/\+/g, '\\+'));
      preEmbedID = preEmbedID.replace(/\+/g, 'plus');
      preEmbed.attr('id', preEmbedID);
    }
    
    // Since there is no initialization method for blip, make sure ST was set up
    if(!$(blipPlayer).hasSpeakerText()) {
      var STplayer = setupSpeakerText($(blipPlayer), STVP_HOST_BLIP, {videoPlayerID: $(swf).attr('id'), 
                                                                      preEmbedID: preEmbedID });
    } else {
      var STplayer = $(blipPlayer).STplayer();
    }
    
    switch(type) {
      case ST_CONST_BLIPUPDATE_STATECHANGE:
        break;
      
      case ST_CONST_BLIPUPDATE_PROGRESS:
      case ST_CONST_BLIPUPDATE_PROGRESS_2:
        var queuePoint = STplayer.data(STCONST_DATAKEY_PLAYERQUEUE);
        if(queuePoint != -1) {
          STplayer.data(STCONST_DATAKEY_PLAYERQUEUE, -1);
          STplayer.STVPseekTo(queuePoint);
        }
        else {
          STplayer.onSTVPprogress(arg1 * 1000, false);
        }
        break;
    }
  })(stjQuery);
}

////////////////////////////////////////
////          VIMEO GLUE            ////
////////////////////////////////////////

// Vimeo Glue
function STonVimeoReady(playerID) {
  (function($) {
   
    var vimeoPlayer = document.getElementById(playerID);
    var queryString = $(vimeoPlayer).attr('src');
    var videoID = queryString.replace(/https?:\/\/player.vimeo.com\/video\/(\d+).*/, '$1');
    
    var STplayer = setupSpeakerText( $(vimeoPlayer), STVP_HOST_VIMEO, {videoPlayerID: playerID,
                                                                       preEmbedID: STCONST_PLAYER_TRANSCRIPTEMBED_PREFIX + "_" + STVP_HOST_VIMEO + "_" + videoID });
    
    Froogaloop(playerID).addEvent('playProgress', function(data) { 
      var queuePoint = STplayer.data(STCONST_DATAKEY_PLAYERQUEUE);
      if(queuePoint != -1 && parseFloat(data.seconds) > 0) {
        STplayer.data(STCONST_DATAKEY_PLAYERQUEUE, -1);
        STplayer.STVPseekTo(queuePoint);
      }
      else {
        STplayer.onSTVPprogress( parseFloat(data.seconds) * 1000, false);
      }
    });             
    
 })(stjQuery);
}


////////////////////////////////////////
////          OOYALA GLUE           ////
////////////////////////////////////////

function st_ooyala_callback(playerId, eventName, eventParams) {
  (function($) {
    var ooyalaPlayer = document.getElementById(playerId);
    var ooyalaWrapper = $(ooyalaPlayer).parent();
    
    switch(eventName) {
      case "apiReady":

        // Look up the clip ID
        var currentItem = ooyalaPlayer.getCurrentItem();
        var videoID = currentItem.embedCode;
        
        var STplayer = setupSpeakerText(ooyalaWrapper, STVP_HOST_OOYALA, {videoPlayerID: playerId,
                                                                          preEmbedID: STCONST_PLAYER_TRANSCRIPTEMBED_PREFIX + "_" + STVP_HOST_OOYALA + "_" + videoID });
        
        STplayer.data(STCONST_DATAKEY_PLAYER_DISABLED, false);
        
        break;
      case "playheadTimeChanged":
        var STplayer = ooyalaWrapper.STplayer();
        
        // don't track any progress unless we're not in an ad
        if (!STplayer.data(STCONST_DATAKEY_PLAYER_DISABLED)) {
          var queuePoint = STplayer.data(STCONST_DATAKEY_PLAYERQUEUE);
          if(queuePoint != -1) {
            STplayer.data(STCONST_DATAKEY_PLAYERQUEUE, -1);
            STplayer.STVPseekTo(queuePoint);
          }
          else {
            STplayer.onSTVPprogress(eventParams.playheadTime * 1000, false);
          }
        }
        break;
        
      case "adStarted":
        var STplayer = ooyalaWrapper.STplayer();
        STplayer.data(STCONST_DATAKEY_PLAYER_DISABLED, true);
        break;
      
      case "adCompleted":
        var STplayer = ooyalaWrapper.STplayer();
        STplayer.data(STCONST_DATAKEY_PLAYER_DISABLED, false);
        break;
    }
  })(stjQuery);
}


////////////////////////////////////////
////     SOUNDCLOUD PLAYER GLUE     ////
////////////////////////////////////////
function onSoundCloudPlayerReady(scPlayer,data) {
  (function($) {
    var playerID = scPlayer.id;
    var scWrapper = $(scPlayer).parent();
    if(!scWrapper.is("object"))
      scWrapper = $(scPlayer);
    var src = "";
    // Initialize the Player
    if($(scPlayer).is("object")) {
      src = $(scPlayer).attr("data");
      if(typeof(src) == "undefined" || src == "")
        src = $(scPlayer).find("param[name='movie']").attr("value");
    }
    else
      src = $(scPlayer).attr("src");
    
    var videoID = data.mediaId; 
    var STplayer = setupSpeakerText(scWrapper, STVP_HOST_SC, {videoPlayerID: playerID, 
                                                              preEmbedID: STCONST_PLAYER_TRANSCRIPTEMBED_PREFIX + "_" + STVP_HOST_SC + "_" + videoID });  
    

    // Add progress listeners
    window.setInterval(function() {
      var currentTimestamp = scPlayer.api_getTrackPosition();
      var queuePoint = $(STplayer).data(STCONST_DATAKEY_PLAYERQUEUE);
      if(typeof(queuePoint) == "undefined") {
        // triggered when reembedding with swfobject
        STplayer.data(STCONST_DATAKEY_PLAYERQUEUE, -1);
      }
      else if(queuePoint != -1 && currentTimestamp > 0) {
        STplayer.data(STCONST_DATAKEY_PLAYERQUEUE, -1);
        STplayer.STVPseekTo(queuePoint);
      }
      else {
        STplayer.onSTVPprogress(currentTimestamp*1000, false);
      }
    }, 100);
  })(stjQuery);
}

////////////////////////////////////////
////         WISTIA GLUE            ////
////////////////////////////////////////

function onWistiaPlayerReady(playerID) {
  (function($) {
   
    var wistiaPlayer = document.getElementById(playerID);
    var videoID = wistiaPlayer.id.replace(/wistia_(\d+).*/, '$1');
    
    var STplayer = setupSpeakerText( $(wistiaPlayer), STVP_HOST_WISTIA, {videoPlayerID: playerID,
                                                                         preEmbedID: STCONST_PLAYER_TRANSCRIPTEMBED_PREFIX + "_" + STVP_HOST_WISTIA + "_" + videoID });
    
   
    // Add progress listeners
    window.setInterval(function() {
      var currentTimestamp = wistiaPlayer.getCurrentTime();
      var queuePoint = $(STplayer).data(STCONST_DATAKEY_PLAYERQUEUE);
      if(typeof(queuePoint) == "undefined") {
        // triggered when reembedding with swfobject
        STplayer.data(STCONST_DATAKEY_PLAYERQUEUE, -1);
      }
      else if(queuePoint != -1 && currentTimestamp > 0) {
        STplayer.data(STCONST_DATAKEY_PLAYERQUEUE, -1);
        STplayer.STVPseekTo(queuePoint);
      }
      else {
        STplayer.onSTVPprogress(currentTimestamp*1000, false);
      }
    }, 100);
 })(stjQuery);
}




////////////////////////////////////////
////        JW PLAYER GLUE          ////
////////////////////////////////////////

function playerReady(obj) {
  if( typeof( obj.id ) == "undefined" )
    return;
    
  var jwPlayer = document.getElementById(obj.id);
    
  jwPlayer.addModelListener("TIME", "st_jw_onProgress");
  jwPlayer.addControllerListener("ITEM", "st_jw_itemChanged");
}

function st_jw_itemChanged(obj) {
  (function($) {
    if( typeof( obj.id ) == "undefined" )
      return;

    var jwPlayer = document.getElementById(obj.id);
    var STplayer = $(jwPlayer).STplayer();
    var current_item = jwPlayer.getPlaylist()[obj.index];
    
    if (STplayer == null) {
      // Allow user to override the way we match video player and transcript player
      var embed_id = $(jwPlayer).attr('data-stid');
      if( typeof(embed_id) !== 'undefined' && embed_id !== false ) {
        var STplayer = setupSpeakerText($(jwPlayer), STVP_PLAYER_JW, {videoPlayerID: obj.id, preEmbedID: embed_id });
      } else if( typeof(current_item['captionbox']) !== 'undefined' ) {
          var clipID = current_item['captionbox'];
          var STplayer = setupSpeakerText($(jwPlayer), STVP_PLAYER_JW, {videoPlayerID: obj.id,
                                                                        preEmbedID: STCONST_PLAYER_TRANSCRIPTEMBED_PREFIX + "_" + clipID });
      } else {
        var finishJWInitialization = function() {
          // Look up the filename
          var filename = current_item.file;
    
          // only take the pathname
          var dummyLink = $('<a/>').attr('href',filename)[0];
          filename = dummyLink.pathname.replace(/^[^\/]/,'/') + dummyLink.search;
          var clipID = sha1Hash(filename);
    
          var STplayer = setupSpeakerText($(jwPlayer), STVP_PLAYER_JW, {videoPlayerID: obj.id,
                                                                        preEmbedID: STCONST_PLAYER_TRANSCRIPTEMBED_PREFIX + "_" + STVP_HOST_SELF + "_" + clipID });
        }
  
        if( typeof(sha1Hash) == "undefined" ) {
          $.loadCaptionBoxDependency('sha1.js', finishJWInitialization);
        } else {
          finishJWInitialization();
        }
      }
    } else {
      // reset current content id
      STplayer.data(STCONST_DATAKEY_TRANSCRIPT_CURRENTCONTENTID,0);
      
      var cb_url = current_item['captionbox'];
      STplayer.STloadTranscriptFromServer(cb_url);
    }
    
  })(stjQuery);
}

function st_jw_onProgress(obj) {
  (function($) {
    if(typeof(obj.id) == "undefined")
      return;
    
    var jwPlayer = document.getElementById(obj.id);
    var STplayer = $(jwPlayer).STplayer();
    
    if( STplayer == null )
      return;
    
    var queuePoint = $(STplayer).data(STCONST_DATAKEY_PLAYERQUEUE);

    if(queuePoint != -1 && jwPlayer.getConfig().state == "PLAYING") {
      STplayer.data(STCONST_DATAKEY_PLAYERQUEUE, -1);
      STplayer.STVPseekTo(queuePoint);
    }
    else {
      STplayer.onSTVPprogress(parseFloat(obj.position) * 1000, false);
    }
    
  })(stjQuery);
}

