



///////////////////////////////////////////
// Array.indexOf() prototype
///////////////////////////////////////////
//
// NOTE: This code alters the Array prototype if the Array object doesn't have the indexOf(...) function already defined
//   this change is needed for browsers that only support older versions of javascript ( like IE )
//
// This code taken and modified from: http://www.hunlock.com/blogs/Mastering_Javascript_Arrays#indexOf
//
// This prototype is provided by the Mozilla foundation and
// is distributed under the MIT license.
// http://www.ibiblio.org/pub/Linux/LICENSES/mit.license
if ( !Array.prototype.indexOf ) {
  Array.prototype.indexOf = function(elt /*, from*/) {
    var len = this.length;

    var from = Number(arguments[1]) || 0;
    from = (from < 0)
         ? Math.ceil(from)
         : Math.floor(from);
    if (from < 0)
      from += len;

    for (; from < len; from++)
    {
      if (from in this &&
          this[from] === elt)
        return from;
    }
    return -1;
  };
}







// keyboard and mouse callbacks
function studio_manager_mouseclick_callback ( event ) {
	 getgvar('studio-manager').handle_mouseclick( event ) ;
}

// loading video metadata json callback
//   this function is called whenever video data is returned asynchronously
function video_info_json_callback ( context, data ) {
	 getgvar('studio-manager').handle_video_data( context, data ) ;
}


function onYouTubePlayerReady ( foo ) {
	 // alert( 'onYouTubePlayerReady()!' ) ;
}




// object that manages the studio interface
function studio_manager () {

	 // input map of the various input values (including optional values) for transcoding an mp3
	 //   initialize the input map with id values (they will be looked up later when the init function is called)
	 this.input_map = new Object() ;

	 // user interface element map
	 this.ui_map = new Object() ;

	 // ui element map for only the video data ui elements
	 //   this is separate from ui_map so that it can be looped over (making it easier to show one and hide the rest in one easy motion)
	 this.video_data_ui_map = new Object() ;


	 // video object of the video loaded in the studio
	 this.vidobj = null ;

	 // video data pointer
	 //   points to a returned json object containing metadata about the video
	 // this object contains 3 types of metadata retrieved from the /p/vidinfo/ page
	 //   - basic info
	 //   - related video feed
	 //   - direct video download links
	 this.videodata = null ;

	 // video metadata data pointer
	 //   pointer to the basic video information portion of data in the encompassing this.videodata structure
	 this.video_metadata = null ;

	 // related videos array
	 //   array of related video objects
	 //   this array is initialized during this.import_related_videos(...)
	 this.related_videos = new Array() ;

	 // array of video format and that format's audio bitrate ( in kbps ) array for the currently loaded video
	 //   - this array of video format data will hold the formats available for the currently loaded video
	 //   - the array will be sorted in ascending audio bitrate order
	 //   - each entry in the array is a two dimensional array with the first element being the format and the second being that format's audio bitrate
	 //
	 // example structure :
	 //   this.videoformats = [ [ 5, 64 ],
	 //                         [ 18, 128 ],
	 //                         [ 22, 256 ] 
	 //                         ]
	 //
	 // this structure is initialized in this.set_transcoder_quality_options()
	 this.videoformats = new Array() ;



	 // flag indicating whether or not javascript is in the middle of loading a new video that came from the this.check_url_input(...) function
	 //   true -> we ARE in the middle of loading a new video
	 //   false -> we are NOT currently loading a new video
	 // this flag is potentially set to true in this.check_url_input(...) and set to false in this.load_video(...)
	 this.loading_new_video_from_check_url_input_flag = false ;
	 
	 // the setTimeout id for the transcoder_download_button_disable_timeout length
	 //   this is set in the function disable_transcoder_download_button() if a ttl parameter was passed in
	 this.transcoder_download_button_disable_timeoutid = -1 ;


	 
	 ////////////////////////////////////////////////////////
	 // Various Configuration Variables
	 ////////////////////////////////////////////////////////

	 // animation speeds in milliseconds (-1 means instant)
	 this.ui_animation_speed = 300 ; // speed for opening and closing user initiated ui elements
	 this.init_animation_speed = 400 ; // speed for opening initial page elements upon load

	 // the number of related videos to display
	 this.max_related_videos = 4 ;

	 // the number of load video lookups we've attempted so far and the
	 // maximum number of lookups to attempt before failing (an
	 // infinite loop or cycle of try-other-source values is probably
	 // happening)
	 this.load_video_data_lookups = 0 ;
	 this.max_load_video_data_lookups = 3 ;

	 // the number of milliseconds to disable the transcoder submit download button before re-enabling it
	 //   this is a feature to prevent the user from pressing it multiple times and starting multiple, unnecessary downloads
	 this.transcoder_download_button_disable_ttl = 2000 ;

	 // the number of seconds for the transcoder start offset after which a note is displayed telling the user it may take a second before the download starts
	 //   this is used to inform the user that if they set a long transcoder start offset ( like 2 minutes ), it may take a while for the transcode download to actually start
	 this.transcoder_start_offset_note_threshold = 15 ;



	 // initialization function
	 this.init = function () {
		  // initialize the element maps
		  this.init_input_map() ;
		  this.init_ui_map() ;
		  this.init_video_data_ui_map() ;

		  // grab the video id if we have one and update the manager's video id
		  if ( this.input_map['studio-input-url'] != null )
				this.vidobj = vidobj_factory().vidobj( this.input_map['studio-input-url'].value ) ;

		  // if we have a video id, then load this video
		  if ( this.vidobj )
				this.load_video( this.vidobj, false ) ; // false because we want to keep any non-default information already in the input boxes that came from url parameters
		  // otherwise wrap up without a video
		  else
				this.no_video_to_load() ;
		  
		  // focus the video input box
		  this.input_map[ 'studio-input-url' ].focus() ;
	 }


	 // initializes pointers to various ui related elements
	 this.init_ui_map = function () {
		  // form elements
		  this.ui_map[ 'studio-form' ] = document.getElementById( 'studio-form' ) ;
		  this.ui_map[ 'transcoder-download-button' ] = document.getElementById( 'transcoder-download-button' ) ;

		  // divs for editing the id3 tag data
		  this.ui_map[ 'id3-closed' ] = document.getElementById( 'edit-id3-data-closed' ) ;
		  this.ui_map[ 'id3-expanded' ] = document.getElementById( 'edit-id3-data-expanded' ) ;

		  // elements for getting links to copy and paste
		  this.ui_map[ 'link-dropdown' ] = document.getElementById( 'link-dropdown' ) ;
		  this.ui_map[ 'link-input-justvideo' ] = document.getElementById( 'link-input-justvideo' ) ;	  
		  this.ui_map[ 'link-input-everything' ] = document.getElementById( 'link-input-everything' ) ;

		  // video player elements
		  this.ui_map[ 'video-player-title-text' ] = document.getElementById( 'video-player-title-text' ) ;
		  this.ui_map[ 'video-player-parent-div' ] = document.getElementById( 'video-player-parent-div' ) ;
		  this.ui_map[ 'video-embed-parent' ] = document.getElementById( 'video-embed-parent' ) ; // the first child of this node is the video embed player <object> element

		  // related videos elements
		  this.ui_map[ 'loaded-related_videos' ] = document.getElementById( 'related-videos-parent-div' ) ;
		  this.ui_map[ 'related-videos-list' ] = document.getElementById( 'related-videos-list-div' ) ;
  
		  // transcoder help and info
		  this.ui_map[ 'transcoder-approximate-filesize' ] = document.getElementById( 'transcoder-approx-dl-size' ) ;
		  this.ui_map[ 'transcoder-large-start-note' ] = document.getElementById( 'transcoder-large-start-offset-note' ) ;

		  // youtube video download anchors
		  //mobile
		  this.ui_map[ 'video-download-yt-fmt17' ] = document.getElementById( 'direct-video-download-yt-fmt17' ) ;
		  //   low
		  this.ui_map[ 'video-download-yt-fmt5' ] = document.getElementById( 'direct-video-download-yt-fmt5' ) ;
		  //this.ui_map[ 'video-download-yt-fmt6' ] = document.getElementById( 'direct-video-download-yt-fmt6' ) ;
		  this.ui_map[ 'video-download-yt-fmt34' ] = document.getElementById( 'direct-video-download-yt-fmt34' ) ;
		  //   high
		  this.ui_map[ 'video-download-yt-fmt18' ] = document.getElementById( 'direct-video-download-yt-fmt18' ) ;
		  this.ui_map[ 'video-download-yt-fmt35' ] = document.getElementById( 'direct-video-download-yt-fmt35' ) ;
		  //   hd
		  this.ui_map[ 'video-download-yt-fmt22' ] = document.getElementById( 'direct-video-download-yt-fmt22' ) ;
		  this.ui_map[ 'video-download-yt-fmt37' ] = document.getElementById( 'direct-video-download-yt-fmt37' ) ;
		  // original
		  this.ui_map[ 'video-download-yt-fmt38' ] = document.getElementById( 'direct-video-download-yt-fmt38' ) ;

		  // vimeo video download anchors
		  this.ui_map[ 'video-download-vmo-sd' ] = document.getElementById( 'direct-video-download-vmo-sd' ) ;
		  this.ui_map[ 'video-download-vmo-hd' ] = document.getElementById( 'direct-video-download-vmo-hd' ) ;

		  // share links
		  this.ui_map[ 'dirpy-studio-share-twitter' ] = document.getElementById( 'dirpy-studio-share-twitter' ) ;
		  this.ui_map[ 'dirpy-studio-share-facebook' ] = document.getElementById( 'dirpy-studio-share-facebook' ) ;

		  // advertisements
		  this.ui_map[ 'banner-ad' ] = document.getElementById( 'banner-ad-parent' ) ;
		  this.ui_map[ 'tower-ad' ] = document.getElementById( 'tower-ad' ) ;
	 }

	 // initializes pointers to various video data ui related elements
	 this.init_video_data_ui_map = function () {

		  this.video_data_ui_map[ 'video-data' ] = document.getElementById( 'video-data' ) ;
		  this.video_data_ui_map[ 'loading-video-data' ] = document.getElementById( 'loading-video-data' ) ;
		  this.video_data_ui_map[ 'embed-disabled-loading-video-data' ] = document.getElementById( 'embed-disabled-loading-video-data' ) ;
		  this.video_data_ui_map[ 'content-restricted-loading-video-data' ] = document.getElementById( 'content-restricted-loading-video-data' ) ;
		  this.video_data_ui_map[ 'copyright-restricted-video-data' ] = document.getElementById( 'copyright-restricted-video-data' ) ;
		  this.video_data_ui_map[ 'error-retrieving-video-data' ] = document.getElementById( 'error-retrieving-video-data' ) ;
		  this.video_data_ui_map[ 'no-video-data' ] = document.getElementById( 'no-video-data' ) ;
		  this.video_data_ui_map[ 'throttled-video-data' ] = document.getElementById( 'throttled-video-data' ) ;
		  this.video_data_ui_map[ 'blocked-video' ] = document.getElementById( 'blocked-video' ) ;
		  this.video_data_ui_map[ 'vevo-unsupported-video' ] = document.getElementById( 'vevo-unsupported-video' ) ;

	 }


	 // initializes all of the input_map entries
	 this.init_input_map = function () {

		  // go through all of the inputs and initialize them
		  this.init_input_map_entry( 'studio-input-url' ) ;
		  this.init_input_map_entry( 'studio-input-filename' ) ;

		  this.init_input_map_entry( 'studio-offset-start' ) ;
		  this.init_input_map_entry( 'studio-offset-end' ) ;
		  this.init_input_map_entry( 'studio-download-format' ) ;

		  this.init_input_map_entry( 'studio-input-id3title' ) ;
		  this.init_input_map_entry( 'studio-input-id3artist' ) ;
		  this.init_input_map_entry( 'studio-input-id3comment' ) ;
		  this.init_input_map_entry( 'studio-input-id3album' ) ;
		  this.init_input_map_entry( 'studio-input-id3year' ) ;
		  this.init_input_map_entry( 'studio-input-id3track' ) ;

		  this.init_input_map_entry( 'length-hidden-param' ) ;
		  this.init_input_map_entry( 'videoid-hidden-param' ) ;
		  this.init_input_map_entry( 'provider-name-hidden-param' ) ;
		  this.init_input_map_entry( 'request-ipaddr-hidden-param' ) ;
		  this.init_input_map_entry( 'redirect-domain-hidden-param' ) ;
		  this.init_input_map_entry( 'format-dl-links-hidden-param' ) ;
		  this.init_input_map_entry( 'handler-hostname-hidden-param' ) ;
		  this.init_input_map_entry( 'prev-search-query-hidden-param' ) ;

	 }


	 // initializes and sets up an entry in the input map
	 //   takes in the id of the input element to lookup
	 this.init_input_map_entry = function ( p_id ) {
		  this.input_map[ p_id ] = document.getElementById( p_id ) ;
	 }



	 // sets the length of the video in seconds
	 //   sets the length attribute of this object and sets the length parameter of the input map
	 this.set_length = function ( p_length ) { 
		  this.length = p_length ;
		  this.input_map[ 'length-hidden-param' ].value = p_length ;
	 }


	 // sets the action value of the studio <form> element
	 //   this is called to update the hostname that transcoder requests are POSTed to
	 //   before metadata has been loaded the form's action should be *do nothing*, or 'javascript: return ;'
	 this.set_form_action = function ( hostname ) {
		  this.ui_map[ 'studio-form' ].action = 'http://' + hostname + '/p/tc/' ;
	 }


	 // returns the hostname of the current browser
	 //   ex: 'http://www.dirpy.com/' -> 'www.dirpy.com'
	 //   ex: 'http://dirpy.com/studio/...' -> 'dirpy.com'
	 //   ex: 'http://dirpy.com:80/studio/...' -> 'dirpy.com:80'
	 this.get_redirect_domain = function () {
		  var hoststr = window.location.hostname ;
		  if ( window.location.port.length > 0 ) // if there's a port, add it
				hoststr += ':' + window.location.port ;
		  return hoststr ;
	 }


	 // set the hostname in use for this page
	 //   this is used so that error redirects can be correctly forwarded back to the originating hostname
	 this.set_form_redirect_domain = function () {
		  this.input_map[ 'redirect-domain-hidden-param' ].value = this.get_redirect_domain() ;
	 }



	 // loads a new video that is currently not loaded
	 //   this function is only called whenever the user indicates ( like by clicking a link ) that they want to navigate to a new video
	 this.load_new_video = function ( vidobj ) {
		  // load this video in a new studio page
		  window.location = this.construct_dirpy_studio_url( vidobj ) ;
	 }


	 // disable the transcoder download button
	 // Parameters :
	 //   ttl -> the number of milliseconds where the transcoder dialog box should be disabled before becoming re-enabled again
	 //      if one isn't provided, then the download box will be disabled until enable_transcoder_download_button() is called
	 //      if one is provided, then enable_transcoder_download_button() will be called after ttl seconds have passed
	 this.disable_transcoder_download_button = function ( ttl ) {
		  if ( ttl == null || ttl == undefined ) 
				ttl = 0 ;

		  // if another enabled timeout is being waited on, cancel it now
		  if ( this.transcoder_button_disable_timeoutid < 0 )
				clearTeimtout( this.transcoder_button_disable_timeoutid ); 

		  // disable the transcoder download button
		  this.ui_map[ 'transcoder-download-button' ].disabled = true ;
		  this.ui_map[ 'transcoder-download-button' ].value = "Processing..." ;
			
		  // set the timeout and store its timeout id
		  this.transcoder_button_disable_timeoutid = setTimeout( "getgvar('studio-manager').enable_transcoder_download_button()", ttl ) ;
	 }
	 // enables the transcoder download button
	 this.enable_transcoder_download_button = function () {
		  // enable the download box
		  this.ui_map[ 'transcoder-download-button' ].disabled = false ;
		  this.ui_map[ 'transcoder-download-button' ].value = "Record" ;

		  // reset the timeout id
		  this.transcoder_button_disable_timeoutid = 0 ;
	 }



	 // import or seek from the transcoder offset ( start and end ) inputs
	 // Parameters :
	 //   p_which_offset -> the transcoder offset key string entry of this.input_map to use
	 //      ex: 'transcoder_offset_start', 'transcoder_offset_end'
	 //   p_command -> the command to execute for the given offset
	 //      given commands: 'set', 'import', 'seek'
	 //   p_data -> optional data needed by the command indicated with p_command
	 //   p_force -> optional parameter whether or not to force overwriting a pre-existing value ( if applicable )
	 //      default: false
	 this.transcoder_offset_manager = function ( p_which_offset, p_command, p_data, p_force ) {
		  // p_force defaults to false
		  if ( p_force == undefined )
				p_force = false ;


		  // make sure a video has been loaded
		  if ( this.ui_map[ 'video-embed-parent' ] == null || this.ui_map[ 'video-embed-parent' ].childNodes[ 0 ] == undefined )
				return ;

		  // convert the float in seconds 
		  //   before overwriting, make sure we are either forcing override with p_force=true or we have default values that can be overwritten
		  if ( p_command == 'set' &&
				 ( p_force == true ||
					this.input_map[ p_which_offset ].value == '' ) )
				this.input_map[ p_which_offset ].value = this.duration_float_to_string( p_data ) ;

		  // import the offset from the embedded video player
		  else if ( p_command == 'import' ) {
				// get the currently loaded time
				var offset = this.ui_map[ 'video-embed-parent' ].childNodes[ 0 ].getCurrentTime() ;

				// check bounds
				if ( offset < 0 )
					 offset = 0 ;
				else if ( offset > this.length )
					 offset = length ;

				// format this time into a time string ( like '0:45.539' ) and set the input field's value
				this.input_map[ p_which_offset ].value = this.duration_float_to_string( offset ) ;
		  }

		  // seek to the offset in the embedded video player
		  else if ( p_command == 'seek' )
				this.ui_map[ 'video-embed-parent' ].childNodes[ 0 ].seekTo( this.duration_string_to_float( this.input_map[ p_which_offset ].value ), true ) ;

	 }

	 // converts a float in seconds into a formatted time string
	 //   ex: converts 80.21 to '1:20.21'
	 this.duration_float_to_string = function ( p_length ) {
		  // hours
		  var hours = Math.floor( p_length / 3600 ) ;
		  p_length = p_length % 3600 ;

		  // minutes
		  var minutes = this.zero_pad_float( Math.floor( p_length / 60 ), 0 ) ;
		  
		  // seconds ( with two digits of precision past the decimal )
		  var seconds = this.zero_pad_float( p_length % 60, 2 ) ;

		  // construct the string ( ignoring hours if there aren't any )
		  var retstr = minutes + ':' + seconds ;
		  if ( hours > 0 )
				retstr = hours + ':' + retstr ;
				
		  // return this formatted string
		  return retstr ;
	 }
	 // converts a formatted time string to a float duration in seconds
	 this.duration_string_to_float = function ( p_str ) {
		  var hours = 0 ;
		  var minutes = 0 ;
		  var seconds = 0 ;

		  // split the string by colons ( ':' )
		  var toks = p_str.split( ':' ) ;

		  // if we have more than 3 parts, we have a malformed string and we should return
		  if ( toks.length > 3 )
				return ;

		  // hours included
		  if ( toks.length == 3 ) {
				hours = Math.floor( parseInt( toks[ 0 ] ) ) ;
				minutes = Math.floor( parseInt( toks[ 1 ] ) ) ;
				seconds = parseFloat( toks[ 2 ] ) ;
		  }

		  // just minutes and seconds		  
		  else if ( toks.length == 2 ) {
				minutes = Math.floor( parseInt( toks[ 0 ] ) ) ;
				seconds = parseFloat( toks[ 1 ] ) ;
		  }

		  // just seconds
		  else if ( toks.length == 1 )
				seconds = parseFloat( toks[ 0 ] ) ;

		  // construct the length float and return it
		  return parseFloat( ( hours * 3600 ) + ( minutes * 60 ) + seconds ) ;
	 }


	 // zero pads a number
	 //   ex: zero_pad( 1 ) returns '01', zero_pad( 10 ) returns '10'
	 // Parametes :
	 //   p_num -> the float
	 //   p_precision_limit -> the number of digits to allow past the period ( '.' ) if one exists
	 this.zero_pad_float = function ( p_num, p_precision_limit ) {
		  // split the float by '.' to get each half
		  var toks = ( '' + p_num ).split( '.' ) ;

		  // if the length of the left side is < 2, then zero pad it
		  if ( toks[ 0 ].length == 1 )
				toks[ 0 ] = '0' + toks[ 0 ] ;
		  else if ( toks[ 0 ].length == 0 )
				toks[ 0 ] = '00' ;

		  // remove precision if we have too much
		  if ( toks[ 1 ] && toks[ 1 ].length > p_precision_limit )
				toks[ 1 ] = toks[ 1 ].substr( 0, p_precision_limit ) ;

		  // now re-combine and return the string
		  var retstr = toks[ 0 ] ;
		  if ( toks[ 1 ] && toks[ 1 ].length > 0 )
				retstr = retstr + '.' + toks[ 1 ] ;
		  return retstr ;
	 }


	 // called when dirpy studio is opened without a video to load
	 this.no_video_to_load = function () {
		  // nothing we need to do here for now...
	 }


	 // loads video video metadata
	 //   when this data is returned, this.handle_video_data(...) is called as the callback
	 //
	 // parameters:
	 //   source -> the source of video metadata to use with this provider
	 //     providers often times have different sources for metadata for videos, and this tells dirpy platform which source of video metadata to use
	 //     for example with youtube
	 //       - 'getvideoinfo' -> gets the video metadata from the youtube get_video_info page ( like: 'youtube.com/get_video_info?...', etc )
	 //       - 'watchpage' -> gets the video metadata from the actual youtube video page itself ( like: 'youtube.com/watch?' )
	 //     default is '', which lets dirpy platform decide which source should be used by default
	 //   context -> used as a binary flag whether or not to overwrite any existing data in the input fields when the video data is returned
	 //     defaults to true: overwrite existing data
	 //   callback -> the javascript callback function to be called when the data is successfully received
	 this.load_video_data = function ( vidobj, source, context, callback ) {

		  // parameter defaults
		  if ( source == null || source == '' )
				source = '' ;
		  if ( callback == undefined || callback == null || callback == '' )
				callback = 'video_info_json_callback' ;
		  if ( context == undefined || context == null )
				context = 'true' ; // default to force overwriting existing input data

		  // increment the number of times we've looked up video metadata
		  //   this is part of a mechanism to prevent infinite loop lookups with try-other-source cycles
		  this.load_video_data_lookups += 1 ;


		  // retrieve the metadata for this video
		  $.ajax ( {
				type: 'GET', // request method
				url: '/p/vidinfo/' + vidobj.id, // json data url
				cache: false, // dont have the browser save/cache this result
				data: { 'provider' : vidobj.provider.name, // url parameter data
						  'source' : source,
						  'callback' : callback,
						  'context' : context },
				timeout: 10000, // timeout in milliseconds
				success: function ( data, status ) { // callback
					 if ( status != 'success' )
						  getgvar( 'studio-manager' ).display_video_data_div( 'error-retrieving-video-data' ) ;
				},
				error: function ( xmlhttpreq, status, exception ) { // errback
					 getgvar( 'studio-manager' ).display_video_data_div( 'error-retrieving-video-data' ) ;
				}
		  } ) ;

		  // display the loading video data div
		  this.display_video_data_div( 'loading-video-data', -1 ) ; // -1 means instant, no animation

	 }
	 this.reload_video_data = function ( context, callback ) {
		  this.load_video_data( this.vidobj, null, context, callback ) ; // null means use the default video data source
	 }


	 // handles video metadata for the current video
	 this.handle_video_data = function ( context, data ) {

/*
Example 1
=======================================================
	 
	 this.handle_returned_ajax_data = function ( data ) {
		  console.log( '1' ) ;
		  if ( data[ 'status' ] == 'reload' ) {
				console.log( '2' ) ;
				this.fire_off_another_ajax_request() ; // this will ALSO call this.handle_returned_ajax_data() when it returns
				this.show_reload_bar() ; // changes the DOM
				return ;
		  }
		  console.log( '3' ) ;
		  alert('does NOT get reached when the second call returns when it should be') ;
	 }

	 console:
	 1
	 2
	 1

	 there SHOULD be an ending '3' and *alert*, but there isnt




Example 2
=======================================================


	 this.handle_returned_ajax_data = function ( data ) {
		  console.log( '1' ) ;
		  alert('this just adds a delay, somehow causing the second alert below to be reached') ;
		  if ( data[ 'status' ] == 'reload' ) {
				console.log( '2' ) ;
				this.fire_off_another_ajax_request() ; // this will ALSO call this.handle_returned_ajax_data() when it returns
				this.show_reload_bar() ; // changes the DOM
				return ;
		  }
		  console.log( '3' ) ;
		  alert('this IS reached b/c of the delay added by the above alert() call') ;
	 }

	 console:
	 1
	 *alert*
	 2
	 1
	 *alert*
	 3
	 *alert*



Example 3
=======================================================


	 this.handle_returned_ajax_data = function ( data ) {
		  console.log( '1' ) ;

		  // wait for 1 second
		  var start = new Date().getTime();
		  for (var i = 0; i < 1e7; i++)
				if ((new Date().getTime() - start) > 1000){
					 break;

		  if ( data[ 'status' ] == 'reload' ) {
				console.log( '2' ) ;
				this.fire_off_another_ajax_request() ; // this will ALSO call this.handle_returned_ajax_data() when it returns
				this.show_reload_bar() ; // changes the DOM
				return ;
		  }
		  console.log( '3' ) ;
		  alert('this IS reached b/c of the delay added by the above alert() call') ;
	 }

	 console:
	 1
	 *alert*
	 2
	 1
	 *alert*
	 3
	 *alert*



Example 4
=======================================================


	 this.handle_returned_ajax_data = function ( data ) {
		  console.log( '1' ) ;

		  // wait for 100 milliseconds
		  var start = new Date().getTime();
		  for (var i = 0; i < 1e7; i++)
				if ((new Date().getTime() - start) > 100){
					 break;

		  if ( data[ 'status' ] == 'reload' ) {
				console.log( '2' ) ;
				this.fire_off_another_ajax_request() ; // this will ALSO call this.handle_returned_ajax_data() when it returns
				this.show_reload_bar() ; // changes the DOM
				return ;
		  }
		  console.log( '3' ) ;
		  alert('this IS reached b/c of the delay added by the above alert() call') ;
	 }

	 console:
	 1
	 2
	 1

	 there SHOULD be an ending '3' and *alert*, but there isnt
*/

		  // check the return code and see what action needs to be taken
		  // 
		  // fundamental error
		  if ( ! data || ! data.hasOwnProperty('status') ) {
				this.display_video_data_div( 'error-retrieving-video-data' ) ;

				return ;
		  }
		  //
		  // embedding disabled
		  else if ( data[ 'status' ].toLowerCase() == 'noembed' ) {
				if ( data[ 'try-other-source' ] && data[ 'try-other-source' ] != '' &&
					  this.load_video_data_lookups < this.max_load_video_data_lookups ) {
					 this.load_video_data( this.vidobj, data[ 'try-other-source' ], true ) ;
					 this.display_video_data_div( 'embed-disabled-loading-video-data' ) ;
				}
				else
					 this.display_video_data_div( 'error-retrieving-video-data' ) ;

				return ;
		  }
		  // content restricted
		  else if ( data[ 'status' ].toLowerCase() == 'content-restricted' ) {
				if ( data[ 'try-other-source' ] && data[ 'try-other-source' ] != '' &&
					  this.load_video_data_lookups < this.max_load_video_data_lookups ) {
					 this.load_video_data( this.vidobj, data[ 'try-other-source' ], true ) ;
					 this.display_video_data_div( 'content-restricted-loading-video-data' ) ;
				}
				else
					 this.display_video_data_div( 'error-retrieving-video-data' ) ;

				return ;
		  }
		  // copyright restricted (at the provider level, not at the dirpy level)
		  else if ( data[ 'status' ].toLowerCase() == 'copyright-restricted' ) {
				this.display_video_data_div( 'copyright-restricted-video-data' ) ;
				return ;
		  }
		  // request throttled
		  else if ( data[ 'status' ].toLowerCase() == 'throttled' ) {
				this.display_video_data_div( 'throttled-video-data' ) ;
				return ;
		  }
		  // transcoding blocked (at the dirpy level, i.e. by a DMCA claim to dirpy)
		  else if ( data[ 'status' ].toLowerCase() == 'blocked' ) {
				this.display_video_data_div( 'blocked-video' ) ;
				return ;
		  }
		  // no video data returned
		  else if ( ! data || ! data[ 'metadata' ] ||
						// if we have metadata but no video source urls, flag this video as being coyrighted
						//   this video probably uses fmt_stream_map instead of fmt_url_map and thus loads videos from non-yt sources, like akamai over rtmpe
						this.is_obj_empty( data[ 'vidsrc-urls' ] ) == true ) {
				this.display_video_data_div( 'no-video-data' ) ;
				return ;
		  }
		  // otherwise some error occurred
		  else if ( data[ 'status' ].toLowerCase() == 'source-error' || // source related error
						data[ 'status' ].toLowerCase() == 'error' || // some internal error
						( data[ 'status' ].toLowerCase() != 'g2g' && // not 'g2g', or good to go
						  data[ 'status' ] != '' ) ) { // default status of '' means the data should be intepreted, not the status alone
				this.display_video_data_div( 'error-retrieving-video-data' ) ;
				return ;
		  }
		  // otherwise the status should be 'g2g' or ''
		  //   '' -> we should read and handle the data appropriately. i.e. no metadata means no video, etc
		  //   'g2g' -> data is good to go


		  // convert a string context of 'true' or 'false' into their respective booleans
		  if ( context.toLowerCase() == 'true' )
				context = true ;
		  else if ( context.toLowerCase() == 'false' )
				context = false ;


		  // store the videodata for this song
		  this.videodata = data ;

		  // store a link to the basic information metadata ( within the this.videodata structure )
		  this.video_metadata = data[ 'metadata' ] ;


		  // store key variable values in the <input> hidden parameters
		  //
		  // store the video id and provider input hidden parameter
		  this.input_map[ 'videoid-hidden-param' ].value = this.vidobj.id ;
		  this.input_map[ 'provider-name-hidden-param' ].value = this.vidobj.provider.name ;

		  // store the metadata requester's original IP address ( encrypted )
		  this.input_map[ 'request-ipaddr-hidden-param' ].value = this.videodata[ 'requestipaddr' ] ;

		  // set the video length ( in seconds )
		  this.set_length( this.video_metadata[ 'duration' ] ) ;

		  // set the form action to point to the transcoder handler's hostname returned in the metadata
		  this.set_form_action( this.videodata[ 'handler-hostname' ] ) ;
		  this.input_map[ 'handler-hostname-hidden-param' ].value = this.videodata[ 'handler-hostname' ] ;
		  
		  // set the domainin use for this page so that error redirects can properly redirect back to this domain
		  this.set_form_redirect_domain() ;

		  

		  // import the loaded data, where context here is the boolean flag whether or not to force overriding existing data
		  this.import_video_data( context ) ;

		  // calculate and set ( i.e. update ) the approximate download filesize
		  //   this method will be subsequently called when the transcoder start, end, and quality setting is changed
		  this.update_transcoder_download_approximate_filesize() ;
		  
		  // update the filename parameter for all of the direct download links
		  this.check_filename() ;

		  // show the video data div
		  this.display_video_data_div( 'video-data' ) ;
		  
	 }
	 // if an object is empty or not
	 // taken and modified from: http://snipplr.com/view.php?codeview&id=19186
	 this.is_obj_empty = function ( obj ) {
		  for ( var property in obj )
				if ( obj.hasOwnProperty( property ) )
					 return false;
		  return true;
	 }

	 
	 // handles the data feed received for the related videos
	 this.import_related_videos = function ( feed ) {
		  // if we didn't receive data or a response then let the user know that with the 'no related videos' div and exit
		  if ( feed == null ) {
				$(this.ui_map[ 'loaded-related_videos' ]).css( 'display', 'none' ) ;
				return ;
		  }
		  
		  // clear any previously loaded related videos
		  this.delete_ele_children( this.ui_map[ 'related-videos-list' ] )
		  
		  // parse the related videos feed to get an array of related video objects and store that array as this.related_videos
		  this.related_videos = this.vidobj.provider.parse_related_videos( feed ) ;
		  if ( this.related_videos.length > 0 ) {
				// add the related videos
				for ( var i = 0; i < this.related_videos.length && i < this.max_related_videos; i++ )
					 // create a new related video entry div and append it to the related videos list
					 this.ui_map[ 'related-videos-list' ].appendChild( this.create_related_video_entry_div( this.related_videos[ i ] ) ) ;
				
				$(this.ui_map[ 'loaded-related_videos' ]).css( 'display', 'block' ) ;
				$(this.ui_map[ 'loaded-related_videos' ]).show( 'blind', {}, this.init_animation_speed ) ;
		  }
		  // no related videos
		  else
				$(this.ui_map[ 'loaded-related_videos' ]).css( 'display', 'none' ) ;

	 }



	 // returns an array of the formats in a fmt_map string
	 //   e.g.: returns [ '18', '34', '5' ] when passed '18/512000/9/0/115,34/0/9/0/115,5/0/7/0/0'
	 //
	 // DEPRECATED: this function is no longer used
	 // DEPRECATED: this function is no longer used
	 // DEPRECATED: this function is no longer used
	 this.get_fmtmap_formats = function ( fmtmap ) {
		  // array of formats to return
		  var retarray = [ ] ;


		  // break up the format map ( looks like: '18/512000/9/0/115,34/0/9/0/115,5/0/7/0/0' )
		  var formats = fmtmap.split( ',' ) ;

		  // loop over the format tokens
		  //	 note: using "for ( var i in formats )" to loop over the
		  //	 formats causes strange behavior in IE. It considers the
		  //	 indexOf property set in Array.prototype.indexOf for IE as
		  //	 an element in the array, and hence it is included when
		  //	 that style of looping is used.
		  for ( var i = 0; i < formats.length; i++ ) {
				// split this format string ( looks like: '5/0/7/0/0' )
				var fmttoks = formats[ i ].split( '/' ) ;
				
				// get the format
				retarray.push( parseInt( fmttoks[ 0 ] ) ) ;
		  }

		  // return this array of formats
		  return retarray ;
	 }


	 // returns an array of the formats in a vidsrc-urls
	 //   e.g.: returns [ '18', '34', '5' ] when passed { "18" : "http://...", "34" : "http://...", "5" : "http://..." }
	 this.get_vidsrc_urls_formats = function ( vidsrc_urls ) {
		  // array of formats to return
		  var retarray = [ ] ;

		  // loop over the dictionary and extract the keys, which are the formats, and cast them as int before pushing them into the array
		  for ( var format in vidsrc_urls )
				retarray.push( format ) ;

		  // return this array of formats
		  return retarray ;
	 }


	 // sets all of the page related content that relies on the available video formats for the selected video
	 // Parameters :
	 //   vidsrc_urls -> a map of formats and their download links
	 //      ex: { 18 : 'http://...', 34 : 'http://...' }
	 this.import_available_video_formats = function ( vidsrc_urls ) {

		  //
		  // NOTE! These functions below could potentially be merged
		  // into one loop where the formats are broken up and decided
		  // and then called once for each of the formats, rather than
		  // that work being duplicated for each function below.
		  //

		  // set all of the transcoder quality options
		  this.set_transcoder_quality_options( vidsrc_urls ) ;

		  // set all of the direct video download links
		  this.set_video_download_links( vidsrc_urls ) ;

		  // add the format and its source url as a pair to the 'format-source-urls-hidden-param' hidden parameter
		  this.set_hidden_format_vidsrc_urls( vidsrc_urls ) ;

	 }


	 // set the available transcoder quality options
	 //
	 // Parameters :
	 //   vidsrc_urls -> the download links object dictionary of formats and their source urls
	 //      ex: { "18" : "http://...", "34" : "http://...", "5" : "http://..." }
	 this.set_transcoder_quality_options = function ( vidsrc_urls ) {
		  // clear all of the children of the transcoder quality drop down menu
		  this.delete_ele_children( this.input_map[ 'studio-download-format' ] ) ;


		  // get the formats ( order doesnt matter )
		  var formats = this.get_vidsrc_urls_formats( vidsrc_urls ) ;

		  // loop over all the formats and build an array of each of the formats and their audio bitrates in ascending order with respect to the audio bitrates
		  //   the array is sorted by audio bitrate ( not the formats like 5, 34, etc ) in ascending order
		  //      this has to be done because... some browsers ( like Chrome :-/ ) reset the map order to ascending keys instead of the defined order, and this isnt what we want
		  //
		  // this is a sorted array of only the formats for this video, not all formats potential are included
		  //
		  // first, copy the returned formats that are transcodable and their audio bitrates to an array, where the values in the array are new arrays of size 2
		  //   with the first element being the format and the second element being that format's audio bitrate
		  var sortedfmts = new Array() ;
		  // note: we can't loop over the properties here, like: for( var fmt in formats ) { ... }
		  //   this is because in IE the indexOf() function that we added to the Arrays prototype ( because IE doesnt provide it by default... ) will turn up as a property of the array
		  for ( var i = 0; i < formats.length; i++ )
				if ( this.vidobj.provider.get_format_audio_bitrate( formats[ i ] ) > 0 && this.vidobj.provider.is_format_transcodable( formats[ i ] ) )
					 sortedfmts[ sortedfmts.length ] = [ formats[ i ], // the video format
																	 this.vidobj.provider.get_format_audio_bitrate( formats[ i ] ) ] ; // this format's audio bitrate

		  // now, sort this array by the the audio bitrate ( 'abitrate' ) in ascending order
		  //   the sort function sorts by return values :
		  //      < 0 -> a comes before b
		  //      0 -> a and b have the same value and dont need to be sortetd
		  //      1 -> b comes before a
		  sortedfmts.sort( function ( a, b ) {
				var arate = a[ 1 ] ; // a's audio bitrate
				var brate = b[ 1 ] ; // b's audio bitrate

				if ( arate < brate )
					 return -1 ;
				else if ( arate == brate )
					 return 0 ;
				else
					 return 1 ;
		  } );


		  // store this sorted formats array as the available video formats for this video
		  this.videoformats = sortedfmts ;


		  // determine the quality that should be selected
		  //   if the url parameter 'srcfmt' was passed in and valid, use that
		  //   otherwise, use the highest bitrate format ( which will be the last element of the sortedfmts array )
		  var urlparam = parseInt( get_url_param( 'srcfmt' ) ) ;

		  // boolean flag whether or not the requested url parameter has been found and selected yet
		  //   if we get to the end of the loop and the requested url parameter hasnt been used yet, then this last element ( with the highest audio bitrate ) is selected
		  var urlparam_used = false ;

		  // loop through the array ( which is now ordered in audio bitrate ascending order ) and add this bitrate quality settings to the transcoder
		  //   the transcoder quality setting to have selected by default will be the last format in the array, because thats the highest quality format
		  // note: we can't loop over the properties here, like: for( var fmt in formats ) { ... }
		  //   this is because in IE the indexOf() function that we added to the Arrays prototype ( because IE doesnt provide it by default... ) will turn up as a property of the array
		  for ( var i = 0; i < sortedfmts.length; i++ ) {

				var fmt = sortedfmts[ i ][ 0 ] ;
				var abitrate = sortedfmts[ i ][ 1 ] ;

				// whether or not this quality format ( stored in the variable fmt ) should be the sole selected format or not
				//   this should only be set to true and used once
				var selected = false ;

				// if this is the format that the user requested with the url parameter
				if ( fmt == urlparam ) { // urlparam might be NaN depending on what was passed in, but this equality operator should still work fine
					 selected = true ;
					 urlparam_used = true ; // mark the url parameter the user passed in as being used
				}
				// or if we've reached the end of the array and we haven't found and used the user's requested url parameter, select this last quality with the highest audio bitrate
				else if ( fmt == sortedfmts[ sortedfmts.length - 1 ][ 0 ] && urlparam_used == false )
					 selected = true ;

				this.input_map[ 'studio-download-format' ].appendChild( 
					 this.create_option_dom_ele( fmt, // format
														  this.construct_transcoder_bitrate_quality_string( abitrate ), // audio bitrate
														  selected ) ) ; // whether or not this option is selected
		  }

	 }


	 // constructs a format bitrate listing string to be included in the transcoder quality options
	 // Parameters :
	 //   bitrate -> the bitrate of the video ( as an integer, not a string )
	 this.construct_transcoder_bitrate_quality_string = function ( bitrate ) {
		  // low
		  if ( bitrate <= 68000 )
				return 'Low ( ' + bitrate/1000 + ' kbps ) '
		  // high
		  else if ( bitrate <= 128000 )
				return 'High ( ' + bitrate/1000 + ' kbps ) '
		  // hd
		  else if ( bitrate <= 256000 )
				return 'HD ( ' + bitrate/1000 + ' kbps ) '
		  // other
		  else
				return '' + bitrate/1000 + ' kbps ' ;

	 }


	 // sets all of the video download links
	 // Parameters :
	 //   vidsrc_urls -> a map of formats and their download links
	 //      ex: { 18 : 'http://v11.lscache8.googlevideo.com/videoplayback?ip=0.0.0.0&sparams=id%2Cexpire%2Cip%2Cipbits%2Citag&itag=18&ipbits=0&sver=3&expire=1244322000&key=yt1&signature=1FBF96FB20F073DE8055269BB9441ED46CA166CA.9EE25EB19FCD0DF4EE94ECE391C39C0FA2EB6895&id=6f4bc290d713993b' } ( but usually with more format entries )
	 this.set_video_download_links = function ( vidsrc_urls ) {
		  // set all the download links to 'display: none' so that only the ones included in the format string will be displayed
		  for ( var j in this.vidobj.provider.formats )
				if ( this.ui_map[ this.vidobj.provider.get_format_uikey( j ) ] )
					 this.ui_map[ this.vidobj.provider.get_format_uikey( j ) ].style.display = 'none' ;

		  // loop over the returned formats for this video and update their download link ( href ) and make sure they are shown ( display: inline ) if they are downloadable
		  for ( var fmt in vidsrc_urls )
				if ( this.vidobj.provider.is_format_downloadable( fmt ) )
					 this.update_video_download_anchor( fmt, vidsrc_urls[ fmt ] ) ;

	 }


	 // updates a video download link anchor by format
	 // parameters:
	 //   fmt -> the video format
	 //      ex: 5, 18, 22, etc
	 //   vidurl -> the full url where the video can be downloaded from
	 //      ex: 'http://v11.lscache8.googlevideo.com/videoplayback?ip=0.0.0.0&sparams=id%2Cexpire%2Cip%2Cipbits%2Citag&itag=18&ipbits=0&sver=3&expire=1244322000&key=yt1&signature=1FBF96FB20F073DE8055269BB9441ED46CA166CA.9EE25EB19FCD0DF4EE94ECE391C39C0FA2EB6895&id=6f4bc290d713993b'
	 //   filename -> the filename url parameter to use when downloading this video
	 //      ex: 'foo' would download to something like 'foo.mp4' or 'foo.flv', etc
	 //      if no filename parameter is provided, then a default download filename is used
	 this.update_video_download_anchor = function ( fmt, vidurl, filename ) {
		  // make sure this format exists
		  if ( ! this.vidobj.provider.formats[ fmt ] )
				//alert( 'UNDEFINED FORMAT: ' + p_fmt ) ;
				return ;

		  // update the anchor for this format
		  // because some browsers count whitespace in the DOM and some don't, use this.find_first_child_of_type() to find the anchor element we want ( and not whitespace, etc )
		  this.find_first_child_of_type( this.ui_map[ this.vidobj.provider.get_format_uikey( fmt ) ], 'a' ).href = this.construct_dlvid_url( this.vidobj, fmt, vidurl ) ;
		  
		  // now display this direct download quality div
		  this.ui_map[ this.vidobj.provider.get_format_uikey( fmt ) ].style.display = 'inline' ;

	 }


	 // sets the 'format-dl-links-hidden-param' hidden parameter to contain a list of the formats and their download links
	 // for example, after calling this function the 'format-dl-links-hidden-param' input might have a value of
	 //
	 //   ex: '5:http%3A//v8.lscache1.googlevideo.com/videoplayback%3Fip%3D0.0.0.0%26sparams%3Did%252Cexpire%252Cip%252Cipbits%252Citag%252Cburst%252Cfactor%26itag%3D5%26ipbits%3D0%26signature%3D0F010E2AD7F561B974E50E03B4D321C3612D6384.2A6B935F485DC94FE3D8C5D3843E03F1369D2BAB%26sver%3D3%26expire%3D1245412800%26key%3Dyt1%26factor%3D1.25%26burst%3D40%26id%3D6426047571669371,	 18:http%3A//v1.lscache1.googlevideo.com/videoplayback%3Fip%3D0.0.0.0%26sparams%3Did%252Cexpire%252Cip%252Cipbits%252Citag%252Cburst%252Cfactor%26itag%3D18%26ipbits%3D0%26signature%3D4ADFA1457709FFC7FAA36E1413CD75844DB6FD6D.9B84A1183E4450D8DE2931852F5ED00DBFEFEE38%26sver%3D3%26expire%3D1245412800%26key%3Dyt1%26factor%3D1.25%26burst%3D40%26id%3D6426047571669371'
	 //
	 // so the general format is: '*fmt*:*dl link for fmt*,*another format*:*dl link for another format*', or '*format*:*dl link*' repeated and separated by commas
	 //
	 // Parameters :
	 //   vidsrc_urls -> a map of formats and their download links
	 //      ex: { 18 : 'http://v11.lscache8.googlevideo.com/videoplayback?ip=0.0.0.0&sparams=id%2Cexpire%2Cip%2Cipbits%2Citag&itag=18&ipbits=0&sver=3&expire=1244322000&key=yt1&signature=1FBF96FB20F073DE8055269BB9441ED46CA166CA.9EE25EB19FCD0DF4EE94ECE391C39C0FA2EB6895&id=6f4bc290d713993b' } ( but usually with more format entries )
	 this.set_hidden_format_vidsrc_urls = function ( vidsrc_urls ) {
		  // reset the hidden input's value
		  this.input_map[ 'format-dl-links-hidden-param' ].value = '' ;

		  // loop over the formats and add them and their vidsrc_urls to the list
		  for ( var fmt in vidsrc_urls )
				this.add_hidden_format_vidsrc_url( fmt, vidsrc_urls[ fmt ] ) ;

		  // remove the trailing ',' from the last link
		  this.input_map[ 'format-dl-links-hidden-param' ].value = this.input_map[ 'format-dl-links-hidden-param' ].value.substring( 0, this.input_map[ 'format-dl-links-hidden-param' ].value.length - 1 ) ;
	 }


	 // appends a format and download link to the end of the 'format-dl-links-hidden-param' hidden input's value
	 //   note: this function always leaves a trailing comma, even when there may not be another format/download link pair after this one
	 this.add_hidden_format_vidsrc_url = function ( fmt, vidsrc_url ) {
		  // the download link must be _DOUBLE_ escaped. 
		  // If not, then the url parameters in the link will show up to the server as url parameters we sent. We DONT want this.
		  // Also, if we don't double escape/encode, then any commas in the link will screw up properly splitting the key:value pairs separated with commas
		  this.input_map[ 'format-dl-links-hidden-param' ].value = this.input_map[ 'format-dl-links-hidden-param' ].value + 
				fmt + ':' + escape( escape( vidsrc_url ) ) + ',' ;
	 }




	 // creates and embeds an SWFObject for the requested video
	 //   note that for the embedding to work the swfobject.js library has to be included
	 // Parameters :
	 //   vidobj -> the video to load
	 //   replace_eleid -> the dom element id to replace with the new embed element
	 //   new_eleid -> the id of the embed element that will be created
	 this.embed_video = function ( vidobj, replace_eleid, new_eleid ) {

		  // get the 'video-embed-parent' element
		  var embedparent = this.ui_map[ 'video-embed-parent' ] ;
		  
		  // create the iframe element that will contain the video embed page
		  var iframe = document.createElement('iframe') ;
		  iframe.id = 'video-embed-iframe' ;
		  iframe.frameBorder = 0 ;
		  iframe.scrolling = 'no' ;
		  iframe.src = '/videoembed-iframe-redir/' + vidobj.urlstr() ; // vidobj.urlstr(true) means use url arguments

		  // delete all of its child nodes
		  this.delete_ele_children( embedparent ) ;

		  // set the iframe as the embed parent's child node
		  this.ui_map[ 'video-embed-parent' ].appendChild( iframe ) ;


		  // old code used before an iframe was required
		  /*
		  // embed attributes and parameters
		  // if we were passed an id
		  var attributes ;
		  if ( new_eleid != undefined && new_eleid != null && new_eleid != '' )
				attributes = { id: new_eleid } ;
		  var parameters = { 
				allowScriptAccess: 'always', 
				allowFullScreen: 'true', 
				bgcolor: '#808080', 
				wmode: 'opaque'
		  } ;
		  
		  // now embed this
		  swfobject.embedSWF( 'http://www.youtube.com/v/' + vidobj.id + '&enablejsapi=1&hl=en&fs=1&hd=1', // the source url
									 replace_eleid, // the id of the DOM element to replace
									 this.video_embed_width, // width
									 this.video_embed_height, // height
									 '8', // flash version
									 null, // rexpress install swf url
									 null, // flash variables
									 parameters,
									 attributes ) ;
       */

	 }


	 // loads a new video
	 // Parameters :
	 //   vidobj -> the video object representing the video to load
	 //   force_load -> boolean whether or not to force loading of the video ( even if the same video is already loaded, etc )
	 //      default: false ( off )
	 this.load_video = function ( vidobj, force_load ) {

		  // default is false/off for force load
		  if ( force_load == null )
				force_load = false ;

		  // make sure we have a valid video
		  if ( ! vidobj )
				return ;


		  // if this video is already loaded, don't reload it unless p_force_load is set to 1
		  //if ( vidobj.id == this.vidobj.id && force_load == false )
		  //return ;


		  // store this video object
		  this.vidobj = vidobj ;

		  // load the video data for this video
		  this.load_video_data( vidobj, null, force_load ) ; // null means use the default video metadata source

		  // show the video player div
		  this.ui_map[ 'video-player-parent-div' ].style.display = 'block' ;
		  
		  // embed the new video flash player
		  this.embed_video( vidobj, 'video-embed', 'video-embed' ) ;

		  // load the advertisements
		  //   GET THIS WORKING PROPERLY BEFORE USING!!
		  //   GET THIS WORKING PROPERLY BEFORE USING!!
		  //   GET THIS WORKING PROPERLY BEFORE USING!!
		  //this.load_advertisements() ;


		  // we have finished loading the video, so now reset the flag indicating that a video is currently being loaded
		  this.loading_new_video_from_check_url_input_flag = false ;

	 }
	 this.reload_video = function ( force_load ) {

		  // reload this page entirely
		  window.location.reload() ;
		  return ;

	 }


	 
	 ///////////////////////////////////////////
	 // Handle Loaded Video Data
	 ///////////////////////////////////////////

	 // import video data
	 // parameters :
	 //   force -> whether or not to force the loading of the new data
	 //      default: off
	 this.import_video_data = function ( force ) {
		  // handle parameter default values
		  if ( force == undefined )
				force = false ;

		  //
		  // Temporary client side fix for the issue of single quotes ("'") being imported as "&#39;"
		  //   this quick fix will no longer be necesarry when single quotes are properly decoded server side
		  //
		  this.video_metadata['title'] = this.video_metadata['title'].replace(/&#39;/g,'\'') ;
		  //
		  // Temporary client side fix for the issue of single quotes ("'") being imported as "&#39;"
		  //   this quick fix will no longer be necesarry when single quotes are properly decoded server side
		  //


		  // filename
		  this.import_loaded_filename( this.video_metadata[ 'title' ], force ) ;

		  // update the transcoder offset start and end times
		  this.transcoder_offset_manager( 'studio-offset-start', 'set', 0, force ) ; // do not force override, we don't want to clobber any url arguments that came in
		  this.transcoder_offset_manager( 'studio-offset-end', 'set', parseFloat( parseFloat( this.video_metadata[ 'duration' ] ) + 1 ), force ) ; // do not force override, we don't want to clobber any url arguments that came in

		  // load the id3 data ( using the filename, which is loaded from the title of the video )
		  this.import_id3_artist_title( this.input_map[ 'studio-input-filename' ].value, force ) ;

		  // set all of the display data that depends on what formats this video is offered in
		  this.import_available_video_formats( this.videodata[ 'vidsrc-urls' ] ) ;

		  // handle the related videos feed data
		  this.import_related_videos( this.videodata[ 'related-videos' ] ) ;

		  // buy/purchase this song links
		  //   this data is currently not returned/available
		  //this.import_loaded_purchase_song_links( this.videodata[ 'buy_links' ], force ) ;

		  // import this video's title
		  this.import_video_title( this.video_metadata[ 'title' ] ) ;
	 }


	 // imports a video title
	 this.import_video_title = function ( title ) {

		  // set the title displayed above the video player
		  this.set_video_player_title( title )
		  
		  // set the title of the page ( i.e. <head><title>... )
		  this.set_document_title( title ) ;
	 }

	 // imports a loaded filename into the video data input fields
	 this.import_loaded_filename = function ( p_filename, force ) {
		  if ( force == undefined )
				force = false ;

		  // if no filename has been specified so far, use this new filename
		  if ( force == true || this.input_map[ 'studio-input-filename' ].value == '' )
				this.input_map[ 'studio-input-filename' ].value = p_filename ;
	 }

	 // direct downloads
	 this.import_loaded_direct_video_downloads = function ( downloads, force ) {
		  if ( force == undefined )
				force = false ;
	 }

	 // purchase this song
	 this.import_loaded_purchase_song_links = function ( links, force ) {
		  if ( force == undefined )
				force = false ;
	 }

	 // imports the id3 artist and title data from a string ( this string is usually the filename )
	 // Parameters :
	 //   id3str -> a string that will be broken down into the id3 artist and title
	 //      ex: 'Foo - Blah' would be broken into 'Foo' and 'Blah for the artist and title, respectively
	 //   force defaults to false
	 this.import_id3_artist_title = function ( id3str, force ) {

		  if ( force == undefined )
				force = false ;

		  // if we are forcing new data, reset the artist and title input fields
		  if ( force ) {
				this.input_map[ 'studio-input-id3title' ].value = '' ; 
				this.input_map[ 'studio-input-id3artist' ].value = '' ;
		  }

		  // get the artist and title ( with whitespace already stripped by this.get_artist_title_from_string() )
		  var artist_title = this.get_artist_title_from_string( id3str ) ;

		  // import the artist and title into the input eles on the page 
		  //   don't overwrite values if they exist already. if force was set, they were already reset to '' above and we can go ahead and set their values now
		  if ( this.input_map[ 'studio-input-id3artist' ].value == '' )
				this.input_map[ 'studio-input-id3artist' ].value = artist_title[ 0 ] ;
		  if ( this.input_map[ 'studio-input-id3title' ].value == '' )
				this.input_map[ 'studio-input-id3title' ].value = artist_title[ 1 ] ;

		  // if we loaded an artist (title is irrelevant), show the id3 tag data div to the user so they can verify and edit it
		  // OR if id3 data was passed in through url parameters, then we also want to display the expanded id3 data
		  // OR if we forced loading of the id3 data, b/c the user should be able to verify their changes
		  if ( artist_title[ 0 ] != '' || this.id3_data_passed_in_url() || force ) {
				this.show_id3( -1 ) ;
		  }
		  // otherwise, hide the id3 tag data
		  else
				this.hide_id3( 1 ) ;

	 }


	 // automatically format the filename
	 //   - capitalize the first letter of words: 'big blast' -> 'Big Blast'
	 //   - don't capitalize common words like 'of', 'a', 'an', 'the', 'from', etc
	 //      this list taken and modified from: http://www.analphilosopher.com/posts/1090606048.shtml
	 //   - some words are always capitalized fully, like 'dj' -> 'DJ'
	 this.auto_format_filename = function () {

		  // list of words that should not be capitalized at all ( note: object attributes should be all lowercase )
		  var not_capitalized = { 'a':true, 'an':true, 'and':true,'as':true,'at':true,'but':true,'by':true,'for':true,
				'from':true,'in':true,'into':true,'nor':true,'of':true,'on':true,'or':true,'over':true,
				'per':true,'the':true,'to':true,'upon':true,'vs':true,'with':true,'etc':true,'ft':true } ;
		  // list of words that should always be fully capitalized (note: object attributes should be all lowercase )
		  var fully_capitalized = { 'dj':true, 'hd':true } ;
		  // list of punctuation that cannot be included as part of a word ( a space is included )
		  //   '\'':true, ( single quote ) removed
		  var punctuation = { '`':true, '~':true, '!':true,'@':true, '#':true,'$':true,'%':true,'^':true,'&':true,'*':true,
				'(':true,')':true,'-':true,'_':true,'=':true,'+':true,'[':true,'{':true,']':true,'}':true,
				'\\':true,'|':true,';':true,':':true,'\"':true,',':true,'<':true,'.':true,'>':true,
				'/':true,'?':true,' ':true } ;
		  

		  // final auto formatted string ( used for temporary purposes, too )
		  var finalstr = '' ;

		  // temporary substring value uses to avoid repeating substring operations
		  var substr = '' ;

		  // split the string around a hyphen to pad it with spaces
		  var toks = this.input_map[ 'studio-input-filename' ].value.split( '-', 2 ) ;
		  for ( var i = 0; i < toks.length; i++ )
				toks[ i ] = str_trim( toks[ i ] ) ;
		  if ( toks.length == 1 ) // no hyphen found
				finalstr = toks[ 0 ] ;
		  else // one or more hyphens found
				finalstr = toks[ 0 ] + ' - ' + toks[ 1 ] ;

		  // lowercase the word
		  var lower = finalstr.toLowerCase() ;

		  // reset finalstr to be built on
		  finalstr = '' ;


		  // loop through the string detecting 'words' and see if they should not be capitalized, fully capitalized, or just first letter capitalized
		  //
		  // find the start of a word ( skipping over all puncuation
		  var start = 0 ; // by default is the first character
		  for ( var j = 0; j < lower.length; j++ ) {

				// if this character is punctuation, add it to the final string and then skip over it b/c it cant be the start of a word
				substr = lower.substring( j, j + 1 )
				if ( punctuation[ substr ] ) {
					 finalstr += substr ; // add this punctuation
					 continue ; // move on to the next character
				}

				// otherwise, now loop to find the end of this word ( until another punctuation character is found or the end of the string is reached )
				start = j ; // store the starting position of this word
				var end = lower.length ; // by default is the end of the word
				for ( var k = start + 1; k < lower.length; k++ ) {
					 if ( punctuation[ lower.substring( k, k + 1 ) ] ) {
						  end = k ; // store the ending position of this word
						  k = lower.length ; // break from this loop ( break; would break from all the loops, which we dont want )
					 }
				}

				// now we have a word, so figure out what we should do with it
				var word = lower.substring( start, end ) ;
				
				// if the word should not be capitalized at all ( if it's the first word ( i==0 ) of the filename, then let it be capitalized )
				if ( not_capitalized[ word ] && i != 0 )
					 finalstr += word ;
				// if the word should be fully capitalized
				else if ( fully_capitalized[ word ] )
					 finalstr += word.toUpperCase()
				// otherwise just capitalize the first letter of the word
				else
					 finalstr += word.substring( 0, 1 ).toUpperCase() + word.substring( 1 )

				// then update the starting position to 1 past the end position and continue
				start = 0 ;
				j = end ;

				// if the end was punctuation, we need to make sure to add it here
				substr = lower.substring( end, end + 1 ) ;
				if ( punctuation[ substr ] )
					 finalstr += substr ; // add this punctuation

		  }


		  // if the last character of the string is a space ( ' ' ), remove it
		  if ( finalstr.substring( finalstr.length - 1 ) == ' ' )
				finalstr = finalstr.substring( 0, finalstr.length - 1 ) ;

		  // set the filename to this auto formatted string
		  this.input_map[ 'studio-input-filename' ].value = finalstr ;

		  // now focus this input box
		  this.input_map[ 'studio-input-filename' ].focus() ;

	 }	 


	 // returns the title and artist from a string in an array of length 2
	 //   ex: 'artist - title' would return [ 'artist', 'title' ]
	 this.get_artist_title_from_string = function ( str ) {

		  // split the filename on '-' and get the id3 title and artist, etc
		  var toks = str.split( '-' ) ;

		  // only one token, put it in the title
		  if ( toks.length == 1 )
				return [ '', toks[ 0 ] ] ; // null string artist
		  // title and artist ( extra tokens after 2 are ignored )
		  else if ( toks.length >= 2 ) {
				var artist = str_trim( toks[ 0 ] ) ; // trim the string of preceeding and trailing spaces

				// use the rest of the tokens to construct the title
				//   this is needed for names that have two hyphens in them, like: ' Authors Name - Song-Title '
				//   where we want 'Authors Name' and 'Song-Title', not 'Authors Name' and 'Song'
				var newstr = toks[ 1 ] ;
				for ( var i = 2; i < toks.length; i++ )
					 newstr += '-' + toks[ i ] ;
				var title = str_trim( newstr ) ; // trim the string of preceeding and trailing spaces

				// return the array of [ artist, title ]
				return [ artist, title ] ;
		  }

	 }


	 // exports an artist and title ( like from the id3 data ) and combines them together to form and set the filename
	 // Parameters :
	 //   artist -> the track artist, like 'Daft Punk'
	 //   title -> the track title, like 'Technologic'
	 this.export_artist_title_to_filename = function ( artist, title ) {
		  if ( artist == undefined )
				artist = '' ;
		  if ( title == undefined )
				title = '' ;

		  // whitespace strip the artist and title and then combine them around ' - '
		  artist = str_trim( artist ) ;
		  title = str_trim( title ) ;

		  // if we don't have an artist, just use the title
		  if ( artist == '' )
				this.input_map[ 'studio-input-filename' ].value = title ;
		  // if we don't have a title, just the artist
		  else if ( title == '' )
				this.input_map[ 'studio-input-filename' ].value = artist ;
		  // otherwise, combine the artist and title around ' - ' for the filename
		  else
				this.input_map[ 'studio-input-filename' ].value = artist + ' - ' + title ;
	 }


	 // whether or not id3 data was passed in through url parameters
	 this.id3_data_passed_in_url = function () {
		  // get the url parameter object map for this url
		  var params = get_urlparamobj( window.location.href ) ;
		  
		  // see if any id3 data was passed in
		  if ( params[ 'title' ] ||
				 params[ 'artist' ] ||
				 params[ 'copyright' ] ||
				 params[ 'comment' ] ||
				 params[ 'album' ] ||
				 params[ 'year' ] ||
				 params[ 'track' ] ||
				 params[ 'genre' ] )
				return true ;
		  return false ; // default return false
	 }

	 // sets the document title to include the video title
	 this.set_document_title = function ( p_title ) {
		  document.title = p_title + ' - Dirpy Studio Beta' ;
	 }

	 // sets the video title text above the video player
	 this.set_video_player_title = function ( title ) {
		  // remove any previous text entries
		  this.delete_ele_children( this.ui_map[ 'video-player-title-text' ] ) ;

		  // create a text element and add it here with the video title
		  this.ui_map[ 'video-player-title-text' ].appendChild( document.createTextNode( title ) ) ;
	 }



	 // loop over all of the direct video download links and make sure they have the correct filename
	 //   these title url parameters must be updated every time the user updates the filename box so that the filename is properly downloaded
	 this.check_filename = function () {
		  // loop over the video download links and set the 'fname' url parameter in each of the links to this filename
		  for ( var fmt in this.vidobj.provider.formats ) {
				// get the url
				var url = this.find_first_child_of_type( this.ui_map[ this.vidobj.provider.get_format_uikey( fmt ) ], 'a' ).href ;

				// change the 'fname' url parameter to the updated filename
				if ( url != '' )
					 this.find_first_child_of_type( this.ui_map[ this.vidobj.provider.get_format_uikey( fmt ) ], 'a' ).href = url_setparam( url, 'fname', escape(this.input_map[ 'studio-input-filename' ].value) ) ;
		  }
	 }



	 // checks to see if the url_input box has been updated
	 //   this function can potentially set the this.loading_new_video_from_check_url_input_flag variable to true if a new video needs to be loaded, detirmined in this function
	 this.check_url_input = function () {
		  // see if the url input represents some video
		  var vidobj = vidobj_factory().vidobj( this.input_map['studio-input-url'].value ) ;

		  // if this is indeed some video and its different from the
		  // video thats currently loaded (if one is loaded at all), and
		  // we're not already in the middle of loading another video,
		  // then load this new video
		  if ( vidobj && this.loading_new_video_from_check_url_input_flag == false &&
				 ( this.vidobj == null || ( vidobj.provider.name != this.vidobj.provider.name || vidobj.id != this.vidobj.id ) ) ) {
				// set the loading new video flag
				this.loading_new_video_from_check_url_input_flag = true ;
				
				// now load the new video
				this.load_new_video( vidobj, true ) ; // force loading of the new video's data
				
		  }

	 }



	 // sets up the links to share this dirpy studio page with 3rd party websites, like twitter, facebook, etc
	 this.set_3rd_party_share_links = function () {
		  // facebook
		  this.set_3rd_party_share_anchor( this.ui_map['dirpy-studio-share-facebook'], this.construct_share_dirpy_studio_url('facebook') ) ;
		  
		  // twitter
		  this.set_3rd_party_share_anchor( this.ui_map['dirpy-studio-share-twitter'], this.construct_share_dirpy_studio_url('twitter') ) ;
	 }
	 this.set_3rd_party_share_anchor = function ( anchorele, url ) {
		  anchorele.href = url ; // this url is only opened directly if javascript is disabled ('return false;' at the end of a.onclick)
		  anchorele.onclick = function () {
				open( url, 'share', 'toolbar=0,width=760,height=480' ) ;
				return false ;
		  }
	 }
	 

	 // constructs and opens a link to share this dirpy studio page on a 3rd party website
	 //   currently supported websites:
	 //     - facebook ( see http://www.facebook.com/facebook-widgets/share.php )
	 this.construct_share_dirpy_studio_url = function ( servicename ) {
		  // if no videos has been loaded, just share the dirpy studio link
		  var novideo = ! (this.vidobj && this.videodata ) ;// flag: true if theres a video, false otherwise

		  // values to use when no video is loaded
		  var novideo_url = 'http://dirpy.com/studio' ;
		  var novideo_text = 'Dirpy Studio - Convert your favorite YouTube videos to high quality mp3s, or download them directly.' ;

		  // values to use when a video is loaded
		  var studio_url, title = '' ;
		  if ( novideo == false ) {
				studio_url = this.construct_dirpy_studio_url( this.vidobj, false ) ; // false => construct absolute url
				title = this.video_metadata[ 'title' ] ;
		  }


		  // final url to open
		  var openurl = '' ;

		  // which 3rd party site are we opening?
		  if ( str_trim(servicename.toLowerCase()) == 'facebook' ) {
				openurl = 'http://www.facebook.com/sharer.php?u=' ; // base url
				if ( novideo )
					 openurl += encodeURIComponent( novideo_url ) + '&t=' + encodeURIComponent( novideo_text ) ;
				else
					 openurl += ( encodeURIComponent( studio_url ) +
									  '&t=' + encodeURIComponent( 'Downloaded "' + title + '" to a high quality mp3 on Dirpy.' ) ) ;
		  }
		  else if ( str_trim(servicename.toLowerCase()) == 'twitter' ) {
				openurl = 'http://twitter.com/home?status=' ; // base url
				if ( novideo )
					 openurl += encodeURIComponent( novideo_text + ' ' + novideo_url ) ;
				else {
					 // tweet message portions
					 var tweet_start = 'I just recorded "' ;
					 var tweet_middle = '" to an mp3 on Dirpy - ' ;

					 // construct the enencoded full tweet
					 var tweet = tweet_start + title + tweet_middle + studio_url ;

					 // see if this tweet is too large (>140 characters) and if so, cut the title to fit
					 var overflow = tweet.length - 140 ;
					 if ( overflow > 0 )
						  title = title.substring( 0, title.length - overflow - 3 ) + '...' ;

					 // construct the full url with title and url properly encoded
					 openurl += encodeURIComponent( tweet_start + title + tweet_middle + studio_url ) ;
				}
		  }
		  else // unknown 3rd party website, so ignore it
				return ;


		  // now open the share url
		  return openurl ;
	 }


	 // construct a dirpy studio url and open a new window with that url
	 //   constructs and opens a base studio url like '/studio/p9FnW-Pnea0' or 'http://dirpy.com/studio/p9FnW-Pnea0'
	 // parameters:
	 //   vidobj -> the video, or null for no video
	 //   relative -> whether or not to return a relative url
	 //      ex: true would return a url like: '/studio/p9FnW-Pnea0', while false would return an absolute url like 'http://dirpy.com/studio/p9FnW-Pnea0'
	 this.construct_dirpy_studio_url = function ( vidobj, relative ) {
		  if ( relative == null )
				relative = false ;

		  var baseurl = '' ;
		  if ( relative == false )
				baseurl = 'http://' + window.location.host ;

		  if ( vidobj == null )
				return baseurl + '/studio' ;
		  else
				return baseurl + '/studio' + vidobj.urlstr() ;
	 }


	 // construct a video download link
	 //   create a link to a dirpy page that will redirect to the download and log the download in the process
	 //      ex: 'dlvid?video_id=Qit3ALTelOo&provider=youtube&fmt=18&vidurl=ENCODEDGARBAGE'
	 this.construct_dlvid_url = function ( vidobj, fmt, vidurl ) {
		  // the filename ('&fname=...') isn't included here because it's added separately by this.check_filename()
		  return ( 'http://' + this.videodata[ 'handler-hostname' ] + '/p/dlvid/?vid=' + vidobj.id +
					  '&prov=' + vidobj.provider.name +
					  '&fmt=' + fmt + 
					  '&request-ipaddr=' + escape(this.videodata[ 'requestipaddr' ]) +
					  '&redirect-domain=' + this.get_redirect_domain()  +
					  '&vidurl=' + escape(vidurl) ) ;
	 }
	 

	 // construct the video only link url
	 this.construct_link_url_justvideo = function () {
		  if ( this.vidobj && this.vidobj.id != '' )
				return this.construct_dirpy_studio_url( this.vidobj, false ) ; // absolute url
		  else
				return 'No valid video URL entered.'
	 }


	 // construct the video only link url
	 this.construct_link_url_everything = function () {
		  // construct the base link with the vid
		  var link = this.construct_link_url_justvideo( this.vidobj, false ) ; // absolute url

		  if ( this.videodata != null ) {
				var urlargs = ''

				// get the id3 title and author from the title of the video to be used to check against their default values below
				//   note: the first element of the array is the id3 artist, and the second is the id3 title
				var artist_title = this.get_artist_title_from_string( this.video_metadata[ 'title' ] ) ;
				
				// now loop through and add each of the input parameters to the list
				for ( var property in this.input_map ) {
					 if ( 
						  // parameters to skip
						  //   the full url ( 'http://you'... ) because this is already included in the vid
						  property == 'studio-input-url' ||

						  // the video id ( it is already included above as 'vid', so we don't need to include the full 'videoid' )
						  property == 'videoid-hidden-param' || 
								
						  // provider name is included separately
						  property == 'provider-name-hidden-param' ||
								
						  // length ( in seconds )
						  property == 'length-hidden-param' ||

						  // the download format download links
						  property == 'format-dl-links-hidden-param' ||

						  // search query before coming to this page
						  property == 'prev-search-query-hidden-param' ||

						  // hidden request ip address
						  property == 'request-ipaddr-hidden-param' ||

						  // hidden redirect domain
						  property == 'redirect-domain-hidden-param' ||

						  // hidden redirect domain
						  property == 'handler-hostname-hidden-param' ||

						  // the filename, but only if its different from the title of the video ( which is default )
						  ( property == 'studio-input-filename' && this.input_map[ 'studio-input-filename' ].value == this.video_metadata[ 'title' ] ) ||

						  // the id3 title, but only if its the same as the title of the video _or_ the id3 title extracted from the video title
						  ( property == 'studio-input-id3title' && ( this.input_map[ 'studio-input-id3title' ].value == this.video_metadata[ 'title' ] ||
																					this.input_map[ 'studio-input-id3title' ].value == artist_title[ 1 ] ) ) ||

						  // the id3 artist, but only if its the same as the id3 artist extracted from the video title
						  ( property == 'studio-input-id3artist' && this.input_map[ 'studio-input-id3artist' ].value == artist_title[ 0 ] ) ||

						  // source format, but only if it's the default of the highest quality ( last in the list of sorted quality formats )
						  ( property == 'studio-download-format' && parseInt( this.input_map[ 'studio-download-format' ].value ) == this.videoformats[ this.videoformats.length - 1 ][ 0 ] ) ||

						  // start offset, but only if it's the default of 0 seconds
						  ( property == 'studio-offset-start' && this.duration_string_to_float( this.input_map[ 'studio-offset-start' ].value ) == 0 ) ||

						  // end ofset, but only it's the default ( length + 1 ) seconds
						  ( property == 'studio-offset-end' && this.duration_string_to_float( this.input_map[ 'studio-offset-end' ].value ) == parseInt( this.length ) + 1 ) ||
								
						  // id3 track, but only if it's the default empty string ''
						  ( property == 'studio-input-id3track' && str_trim(this.input_map[ property ].value) == '' ) ||
						  
						  // make sure the value isn't an empty string
						  this.input_map[ property ].value == '' ) {

						  // skip this property
						  continue ;
					 }
						
					 // add this property to the url
					 urlargs += '&' + this.input_map[ property ].name + '=' + escape( this.input_map[ property ].value ) ;
				}

				// now return this fully constructed link
				if ( urlargs.length > 0 ) // replace the first '&' with a '?'
					 urlargs = '?' + urlargs.substring( 1 ) ;
				return link + urlargs ;
		  }

		  else
				return link ;
	 }




	 ///////////////////////////////////////////
	 // Interface Handling Functions
	 ///////////////////////////////////////////

	 // show one video data div and hide the rest
	 //   this is used to easily show one 
	 //
	 // Parameters :
	 //   showattr    -> the this.video_data_ui_map attribute name of the div to show (the rest will be hidden)
	 //   animation   -> boolean whether or not to animate the hiding and showing of the elements
	 this.display_video_data_div = function ( showattr, speed ) {
		  if ( speed == null )
				speed = this.init_animation_speed ; // default animation speed

		  for ( var attr in this.video_data_ui_map ) {
				// data div to show. we only need to show it if it's not already being shown
				if ( attr == showattr && 
					  $(this.video_data_ui_map[ attr ]).is(':visible') == false &&
					  $(this.video_data_ui_map[ attr ]).is(':animated') == false )
					 $(this.video_data_ui_map[ attr ]).show( 'blind', {}, speed ) ;
				// not the data div to show, so hide it
				else
					 if ( $(this.video_data_ui_map[ attr ]).is(':visible') == true &&	
							$(this.video_data_ui_map[ attr ]).is(':animated') == false )
						  $(this.video_data_ui_map[ attr ]).hide( 'blind', {}, speed ) ;
		  }
	 }


	 // close the error message box ( if it exists )
	 this.close_errormsg = function () {
		  // get the error message div by id and if it exists, hide it
		  errordiv = document.getElementById( 'errormsg-div' ) ;
		  if ( errordiv )
				$(errordiv).hide( 'blind', {}, this.ui_animation_speed ) ;
	 }


	 // alternate hiding and showing the link dropdown box
	 this.showhide_link_dropdown = function () {
		  if ( $(this.ui_map['link-dropdown']).is(':visible') )
				this.hide_link_dropdown() ;
		  else
				this.show_link_dropdown() ;
	 }
	 this.hide_link_dropdown = function ( speed ) {
		  if ( speed == null )
				speed = this.ui_animation_speed ;

		  // if the link dropdown is already hidden, return
		  if ( $(this.ui_map['link-dropdown']).is(':visible') == false )
				return ;

		  $(this.ui_map['link-dropdown']).hide( 'blind', {}, speed ) ;
	 }
	 this.show_link_dropdown = function ( speed ) {
		  if ( speed == null )
				speed = this.ui_animation_speed ;

		  // if the link dropdown is already shown, return
		  if ( $(this.ui_map['link-dropdown']).is(':visible') == true )
				return ;

		  // construct the link url and set it as the input's value
		  this.ui_map['link-input-justvideo'].value = this.construct_link_url_justvideo() ;
		  this.ui_map['link-input-everything'].value = this.construct_link_url_everything() ;

		  // focus and select the input text
		  //	 for some reason when $(...).show(...) completes, it
		  //	 changes focus and hence we need to set up an animation
		  //	 callback to re-focus and re-select after the animation
		  //	 completes
		  var focus_ele = this.ui_map['link-input-justvideo'] ;
		  // show the link dropdown
		  $(this.ui_map['link-dropdown']).show( 'blind', {}, speed,
															 function(){ focus_ele.focus(); focus_ele.select(); } ) ;

		  // for some strange reason, focusing the element causes jqueryui
		  // animations to become choppy with some browsers like Firefox,
		  // IE, etc. So until this strange behavior is fixed, only focus
		  // the input element after the animation has been completed (as
		  // implemented above in the animation callback)
		  // focus_ele.focus() ;
		  focus_ele.select() ;

	 }



	 // show and hide the id3 metadata inputs
	 this.show_id3 = function ( speed ) {
		  if ( speed == null )
				speed = this.ui_animation_speed ;
		  $(this.ui_map['id3-closed']).hide( 'blind', {}, speed ) ;
		  $(this.ui_map['id3-expanded']).show( 'blind', {}, speed ) ;
	 }
	 this.hide_id3 = function ( speed ) {
		  if ( speed == null )
				speed = this.ui_animation_speed ;
		  $(this.ui_map['id3-closed']).show( 'blind', {}, speed ) ;
		  $(this.ui_map['id3-expanded']).hide( 'blind', {}, speed ) ;
	 }



	 // close the video player
	 this.close_video_player = function () {
		  // if we have a video loaded, clear it now

		  // hide the video player
		  this.ui_map[ 'video-player-parent-div' ].style.display = 'none' ;
	 }


	 // mouse handling function
	 this.handle_mouseclick = function ( event ) {

		  // get the target of the event
		  //   this code taken and modified from: http://www.quirksmode.org/js/events_properties.html
		  var target ;
		  if ( ! event ) 
				event = window.event ;
		  if ( event.target )
				target = event.target ;
		  else if ( event.srcElement )
				target = event.srcElement ;
		  if ( target.nodeType == 3 ) // defeat Safari bug
				target = target.parentNode ;

		  //alert( 'id: ' + target.id ) ;

		  // see if the user clicked outside the search suggestions box, and if so close the box
		  //   the deepest you can be is two levels below 'search-suggestions'
		  if ( target == this.ui_map[ 'link-dropdown' ] ||

				 // clicked on the link anchor to open the link dropdown
				 ( target.parentNode &&
					target.parentNode.id == 'link-anchor' ) ||

				 // child node, 1st degree
			    ( target.parentNode &&
					target.parentNode == this.ui_map[ 'link-dropdown' ] ) ||

				 // child node, 2nd degree
			    ( target.parentNode &&
					target.parentNode.parentNode &&
					target.parentNode.parentNode == this.ui_map[ 'link-dropdown' ] ) ||

				 // child node, 3rd degree
			    ( target.parentNode &&
					target.parentNode.parentNode &&
					target.parentNode.parentNode.parentNode &&
					target.parentNode.parentNode.parentNode == this.ui_map[ 'link-dropdown' ] ) )

				return ;
		  else
				this.hide_link_dropdown() ;

	 }


	 // swap the id3 title and id3 artist values
	 this.swap_id3title_id3artist = function () {

		  // store the old id3 artist value
		  var temp = this.input_map[ 'studio-input-id3title' ].value ;

		  // now swap the artist and title values
		  this.input_map[ 'studio-input-id3title' ].value = this.input_map[ 'studio-input-id3artist' ].value ;
		  this.input_map[ 'studio-input-id3artist' ].value = temp ;

	 }


	 // updates the displayed approximate transcoder download filesize
	 //    called whenever a change is made to the transcoder start and end offsets or the transcoder quality
	 this.update_transcoder_download_approximate_filesize = function () {

		  // get the transcoder start offset, end offset, and the quality ( bitrate )
		  var start = this.input_map[ 'studio-offset-start' ].value ; // transcoder start offset ( like '00:00' or '00:10' )
		  var end = this.input_map[ 'studio-offset-end' ].value ; // transcoder end offset ( like '03:21' )
		  var format = this.input_map[ 'studio-download-format' ].value ; // format ( like '5', '18', '22', etc )

		  // calculate the duration of the requested transcoded song in seconds ( as a float )
		  var duration = this.duration_string_to_float( end ) - this.duration_string_to_float( start ) ;

		  // calculate the approximate total bbytes ( NOT BITS! ) for this download
		  var totalbytes = duration * ( this.vidobj.provider.get_format_audio_bitrate( format ) ) / 8 ;

		  // set the transcoder approximate filesize
		  this.set_transcoder_download_approximate_filesize( totalbytes ) ;

	 }

	 // sets the transcoder approximate download filesize text
	 // Parameters :
	 //   numbytes -> the approximate total filesize in bytes ( NOT BITS! )
	 this.set_transcoder_download_approximate_filesize = function ( numbytes ) {

		  // if numbytes is negative, set it to 0
		  if ( numbytes < 0 )
				numbytes = 0 ;

		  // construct the string that will be displayed
		  var sizestr = ' ( approximately ' + this.numbytes_to_string( numbytes, 1, ' ' ) + ' )' ;

		  // update the element and set its child text node
		  this.replace_ele_child_text_node( this.ui_map[ 'transcoder-approximate-filesize' ], sizestr ) ;

	 }


	 // check to see if the transcoder start offset is more than this.transcoder_start_offset_note_threshold seconds
	 //   this function is called every time the transcoder start offset value is changed
	 this.check_transcoder_start_offset = function () {
		  // get the number of seconds in the transcoder start offset
		  var offset = this.duration_string_to_float( this.input_map[ 'studio-offset-start' ].value ) ;
		  
		  // if this is greater than the start note threshold, show the warning
		  if ( offset >= this.transcoder_start_offset_note_threshold )
				this.show_transcoder_large_start_offset_note() ;
		  // otherwise hide the warning
		  else
				this.hide_transcoder_large_start_offset_note() ;
	 }

	 // show and hide the transcoder large start offset note div
	 this.show_transcoder_large_start_offset_note = function () {
		  this.ui_map[ 'transcoder-large-start-note' ].style.display = 'block' ;
	 }
	 this.hide_transcoder_large_start_offset_note = function () {
		  this.ui_map[ 'transcoder-large-start-note' ].style.display = 'none' ;
	 }



	 // NOTE: Loading advertisements with javascript is currently _NOT_ used
	 // NOTE: Loading advertisements with javascript is currently _NOT_ used
	 // NOTE: Loading advertisements with javascript is currently _NOT_ used
	 // NOTE: Loading advertisements with javascript is currently _NOT_ used
	 // NOTE: Loading advertisements with javascript is currently _NOT_ used
	 // NOTE: Loading advertisements with javascript is currently _NOT_ used
	 // NOTE: Loading advertisements with javascript is currently _NOT_ used

	 // reload the advertisements on the page
	 //   this is used whenever a new video is loaded and we can refresh the ads
	 this.load_advertisements = function () {

		  // banner ad
		  this.load_banner_advertisement() ;
		  
		  // tower ad
		  this.load_tower_advertisement() ;

	 }
	 this.load_banner_advertisement = function () {

		  // delete all of the children of the advertisements parent
		  this.delete_ele_children( this.ui_map[ 'banner-ad' ] ) ;
		  /*
		  // insert the ad html
		  //   this html taken and quote backslashed from adbrite
		  this.ui_map[ 'banner-ad' ].innerHTML = "\
		  <script type=\"text/javascript\"> \
			 var AdBrite_Title_Color = '0000FF'; \
			 var AdBrite_Text_Color = '000000'; \
			 var AdBrite_Background_Color = 'FFFFFF'; \
			 var AdBrite_Border_Color = 'DCF3FD'; \
			 var AdBrite_URL_Color = '008000'; \
			 try{var AdBrite_Iframe=window.top!=window.self?2:1;var AdBrite_Referrer=document.referrer==''?document.location:document.referrer;AdBrite_Referrer=encodeURIComponent(AdBrite_Referrer);}catch(e){var AdBrite_Iframe='';var AdBrite_Referrer='';} \
		  </script> \
		  <span style=\"white-space:nowrap;\"><script type=\"text/javascript\">document.write(String.fromCharCode(60,83,67,82,73,80,84));document.write(' src=\"http://ads.adbrite.com/mb/text_group.php?sid=1182978&zs=3436385f3630&ifr='+AdBrite_Iframe+'&ref='+AdBrite_Referrer+'\" type=\"text/javascript\">');document.write(String.fromCharCode(60,47,83,67,82,73,80,84,62));</script> \
			 <a target=\"_top\" href=\"http://www.adbrite.com/mb/commerce/purchase_form.php?opid=1182978&afsid=1\"><img src=\"http://files.adbrite.com/mb/images/adbrite-your-ad-here-banner.gif\" style=\"background-color:#DCF3FD;border:none;padding:0;margin:0;\" alt=\"Your Ad Here\" width=\"11\" height=\"60\" border=\"0\" /></a> \
		  </span> \
				" ;

		  // now execute ( using eval(...) ) all of the <script> tags in the html we just added
		  var scripts = this.ui_map[ 'banner-ad' ].getElementsByTagName( 'script' ) ;
		  for ( var i = 0; i < scripts.length; i++ ) {
				eval( scripts[ i ].text ) ;
		  }

		  // now load the advertisement
		  //this.ui_map[ 'banner-ad' ].appendChild(  ) ;
        */

	 }
	 this.load_tower_advertisement = function () {

		  // delete all of the children of the advertisements parent
		  this.delete_ele_children( this.ui_map[ 'tower-ad' ] ) ;
		  /*
		  // create the element to be inserted
		  var div_0 = document.createElement('div');

		  var script_0 = document.createElement('script');
        script_0.type = "text/javascript";
        script_0.appendChild( document.createTextNode("var AdBrite_Title_Color = '0000FF';var AdBrite_Text_Color = '000000';var AdBrite_Background_Color = 'FFFFFF';var AdBrite_Border_Color = 'DCF3FD';var AdBrite_URL_Color = '008000';try{var AdBrite_Iframe=window.top!=window.self?2:1;var AdBrite_Referrer=document.referrer==''?document.location:document.referrer;AdBrite_Referrer=encodeURIComponent(AdBrite_Referrer);}catch(e){var AdBrite_Iframe='';var AdBrite_Referrer='';}\n		  ") );
		  div_0.appendChild( script_0 );

		  var script_1 = document.createElement('script');
        script_1.type = "text/javascript";
        script_1.appendChild( document.createTextNode("document.write(String.fromCharCode(60,83,67,82,73,80,84));document.write(' src=&quot;http://ads.adbrite.com/mb/text_group.php?sid=1183384&zs=3132305f363030&ifr=' AdBrite_Iframe '&ref=' AdBrite_Referrer '&quot; type=&quot;text/javascript&quot;>');document.write(String.fromCharCode(60,47,83,67,82,73,80,84,62));") );
		  div_0.appendChild( script_1 );

		  var div_1 = document.createElement('div');

        var a_0 = document.createElement('a');
        a_0.target = "_top";
        a_0.href = "http://www.adbrite.com/mb/commerce/purchase_form.php?opid=1183384&afsid=1";
        a_0.style.fontWeight = "bold";
        a_0.style.fontFamily = "Arial";
        a_0.style.fontSize = "13px";
        a_0.appendChild( document.createTextNode("Your Ad Here") );
        div_1.appendChild( a_0 );

		  div_0.appendChild( div_1 );


		  // now load the advertisement
		  this.ui_map[ 'banner-ad' ].appendChild( div_0 ) ;
        */
	 }






	 ///////////////////////////////////////////
	 // DOM Functions
	 ///////////////////////////////////////////

	 // creates an <option> element with a value( p_value ), inner text( p_text ), and whether its selected or not
	 //   handy link: http://rick.measham.id.au/paste/html2dom.htm
	 this.create_option_dom_ele = function ( p_value, p_text, p_selected ) {
		  // default value of false
		  if ( p_selected === undefined || p_selected === null )
				p_selected = false ;

		  // create the option element and return it
		  var option_0 = document.createElement( 'option' ) ;
		  option_0.value = p_value ;
		  option_0.selected = p_selected ;
		  option_0.appendChild( document.createTextNode( p_text ) ) ;

		  return option_0 ;
  	 }


	 // creates a related video div element
	 // parameters:
	 //   vidobj -> the video object
	 this.create_related_video_entry_div = function ( vidobj ) {
		  // initialize a video data formatter to use in formatting the video length and relative timestamp strings
		  var vidformatter = new video_data_formatter() ;


		  // create and return the related video entry div
		  var div_0 = document.createElement('div');
		  div_0.className = "related-video-entry";

		  var new_video_url = this.construct_dirpy_studio_url( vidobj, true ) ;  // this loads the video in a new page ( using a relative url to the studio )

		  var a_0 = document.createElement('a');
		  a_0.href = new_video_url ;

        var img_0 = document.createElement('img');
        img_0.src = vidobj.tbmap[ -1 ] ; // default thumbnail source
        a_0.appendChild( img_0 );

		  div_0.appendChild( a_0 );

		  var div_1 = document.createElement('div');
        div_1.className = "related-video-entry-title";

        var a_1 = document.createElement('a');
		  a_1.title = vidobj.title ; // video title
		  a_1.href = new_video_url ;
		  var snippetstr = vidobj.title.substring( 0, 35 ); // choose a maximum title size to prevent overflow
		  if ( snippetstr.length < vidobj.title.length )
				snippetstr += '...' ;
        a_1.appendChild( document.createTextNode( snippetstr ) );
        div_1.appendChild( a_1 );

		  div_0.appendChild( div_1 );


		  var div_2 = document.createElement('div');
        div_2.className = "related-video-entry-metadata";

        var div_3 = document.createElement('div');
        div_3.className = "related-video-entry-metadata-length";
        div_3.appendChild( document.createTextNode( 
				vidformatter.seconds_to_length_str( vidobj.duration ) ) ) ; // video duration in seconds
        div_2.appendChild( div_3 );

		  div_0.appendChild( div_2 );

		  return div_0 ;

	 }


	 // deletes all of the current children of an element and then adds one text child element
	 // Parameters :
	 //   ele -> the element whose children will be erased and a single text node added as a child
	 //   text -> the text that will be placed inside the child text node
	 this.replace_ele_child_text_node = function ( ele, text ) {
		  // delete all the current children
		  this.delete_ele_children( ele ) ;

		  // create and append a text node
		  ele.appendChild( document.createTextNode( text ) ) ;
	 }



	 ///////////////////////////////////////////
	 // Utility Functions
	 ///////////////////////////////////////////


	 // delete all of an elements children
	 this.delete_ele_children = function ( p_ele ) {
		  
		  // make sure we got a valid element
		  if ( p_ele === undefined || p_ele === null )
				return ;

		  // remove all of this elements children, one at a time
		  while ( p_ele.hasChildNodes() )
				p_ele.removeChild( p_ele.firstChild ) ;

	 }


	 // returns the first child element of an element that matches the type provided
	 // this is very useful to pass over text elements created in some browsers but not others b/c some browsers count whitespace in the DOM and others dont
	 //   this function is _not_ recursive, and hence will only look one level deep
	 //      maybe in the future it can be made recursive. would such a recursive version be useful?
	 //
	 // returns null if no child of the requested type could be found
	 //
	 // Parameters :
	 //   ele -> the element whos children we should look through to find the first requested element
	 //   type -> the type of the child element to look for
	 //      ex: 'a' for an anchor, 'p' for a paragraph, etc
	 //      NOT case sensitive
	 this.find_first_child_of_type = function ( ele, type ) {

		  // loop until we find the first child node of the requested type
		  for ( var i = 0, childnode; childnode = ele.childNodes[ i ]; i++ )
				if ( childnode.tagName && childnode.tagName.toLowerCase() == type )
					 return childnode ;

		  // return null if no child of the requested type could be found
		  return null ;
					 
	 }


	 // function taken and modified from: http://www.quirksmode.org/dom/getstyles.html
	 this.getstyle = function ( p_ele, styleProp ) {

		  var x = p_ele ;
		  if (x.currentStyle)
				var y = x.currentStyle[styleProp];
		  else if (window.getComputedStyle)
				var y = document.defaultView.getComputedStyle(x,null).getPropertyValue(styleProp) ;
		  return y;

	 }


	 
	 // convert a time offset string ( of the form hh:mm:ss.mm ) to a float in seconds
	 //    for example takes '02:34.28' and returns 154.28, or ( 60 * 2 + 34 + 0.28 )
	 //    returns -1 on error
	 //
	 // this function was copied and converted to js from the same python function in the transcoder
	 this.duration_string_to_float = function ( p_str ) {

		  var hours = 0 ;
		  var minutes = 0 ;
		  var seconds = 0 ;

		  // split the string by colons ( ':' )
		  var toks = p_str.split( ':' ) ;


		  // replace any empty strings from the split with '0' and make sure the string is a number
		  //   ex: if they submitted ':35' we want to fill that in to be the same as '0:35'
		  //   ex: '31:aa' will be turned into the same as '31:00'
		  for ( var i = 0; i < toks.length; i++ )
				if ( toks[ i ] == '' || isNaN( parseFloat( toks[ i ] ) ) == true )
					 toks[ i ] = '0' ;


		  // attempt to convert to a valid duration in seconds ( a float )
        // hours included
        if ( toks.length == 3 ) {
            hours = parseFloat( toks[ 0 ] ) ;
            minutes = parseFloat( toks[ 1 ] ) ;
            seconds = parseFloat( toks[ 2 ] ) ;
		  }

        // just minutes and seconds		  
        else if ( toks.length == 2 ) {
            minutes = parseFloat( toks[ 0 ] ) ;
            seconds = parseFloat( toks[ 1 ] ) ;
		  }

        // just seconds
        else if ( toks.length == 1 )
            seconds = parseFloat( toks[ 0 ] )



		  // make sure we have all valid numbers ( no NaN's returned from parseInt() and parseFloat() calls, etc ) and we didn't receive a malformed string ( malformed = more than 3 tokens )
		  if ( isNaN( hours ) || isNaN( minutes ) || isNaN( seconds ) || toks.length > 3 )
				return -1 ;

        // construct the length float ( in seconds ) and return it
        return parseFloat( ( hours * 3600 ) + ( minutes * 60 ) + seconds )

	 }


	 // number of bytes to representational string
	 //   ex: 1000000 returns '1mb', 1100000 returns '1.1mb', 1950 returns '1.9kb', etc
	 // 
	 // Parameters :
	 //   numbytes -> the number of bytes ( NOT BITS! )
	 //   sigfigs -> the number of significant figures to include in the number AFTER the period ( if one exists )
	 //      ex: 1 would be '2.1mb' and 2 would be '82.18mb'
	 //   padding -> a string buffer to insert between the number and its file size
	 //      ex: '' would lead to '2.1mb', while ' ' would lead to '2.1 mb'
	 this.numbytes_to_string = function ( numbytes, sigfigs, padding ) {

		  // default to 2 significant digits
		  if ( sigfigs == undefined || sigfigs == null )
				sigfigs = 1 ;

		  // default to nullstring padding
		  if ( padding == undefined || padding == null )
				padding = '' ;


		  // division result
		  var res = 0 ;
		  
		  // gigabyte ( 10^9 )
		  res = numbytes / 1000000000 ;
		  if ( res >= 1 )
				return this.float_to_string_sigfigs( res, sigfigs ) + padding + 'gb' ;

		  // megabytes ( 10^6 )
		  res = numbytes / 1000000 ;
		  if ( res >= 1 )
				return this.float_to_string_sigfigs( res, sigfigs ) + padding + 'mb' ;

		  // kilobytes ( 10^3 )
		  res = numbytes / 1000 ;
		  if ( res >= 1 ) 
				return this.float_to_string_sigfigs( res, sigfigs ) + padding + 'kb' ;

		  // bytes ( 10^0 )
		  return this.float_to_string_sigfigs( res, sigfigs ) + padding + 'bytes' ;

	 }
	 // returns a number string ( i.e. a string consisting of digits and zero or one periods ) with the requested number of significant figures
	 //   ex: ( 2.98, 2 ) would return '2.9', ( 3.18, 3 ) would return '3.18', ( 3.18, 1 ) would return '3', etc
	 this.float_to_string_sigfigs = function ( num, sigfigs ) {
		  
		  // the number as a string
		  var numstr = '' + num ;

		  // if it contains a period, split by it and then limit the rear
		  var toks = numstr.split( '.', 2 ) ;

		  // no decimal point, just return the number ( this isn't how sigfigs work, but it's ok to use here )
		  if ( toks.length == 1 ) 
				return numstr ;

		  // digits after the decimal exist, round to the sigfigs decimal and return the string
		  else if ( toks.length == 2 )
				return '' + ( Math.round( num * Math.pow( 10, sigfigs ) ) ) / Math.pow( 10, sigfigs ) ;

		  // otherwise some error occurred ( wtf number was it? )
		  else
				return 'NaN'

	 }
	 
}

