Skip to content
Guillaume Pellerin edited this page Oct 11, 2013 · 37 revisions

Table of contents

<wiki:toc max_depth="4" />

Introduction

The TimeSide UI features a powerful DHTML-based interactive player with time marking capabilities. It can be used as a standalone client-side component. As of June 2011, it is supported in all major browsers: Internet Explorer (IE) 7+, Firefox (FF) 3.5+ and Chrome 9+

The player can be created and manipulated using a !JavaScript object oriented API, customized using CSS and skins, and dynamically resized:

  1. play
  2. pause
  3. rewind (move pointer to previous marker
    - or sound start if no marker)
  4. forward (move pointer to next marker
    - or sound end if no marker)
  5. add marker (at sound - i.e., pointer - position)
  6. volume (toggles volume on/off)
  7. volume bar (sets volume level)
  8. wait panel (visible only on sound loading/buffering or image refreshing)

See here for a live example with enhanced marker objects, as explained in the Marker section.

For reference, here is the structure of the dynamically generated markup structure:

Markup structure

<div class="ts-ruler">           <!-- upper <div> (ruler) -->
    <svg class="ts-ruler-lines">        <!-- The ruler lines -->
    <a class="ts-pointer"><span/></a>   <!-- pointer label -->
    <a class="ts-marker"><span/></a>    <!-- marker label -->
    ...
</div>
<div class="ts-wave">            <!-- central <div> (wave) -->
    <div class="image-canvas">         <!-- svgs container. Should not be modified by custom css-->
        <svg class="ts-pointer-canvas">    <!-- Pointer vertical line -->
        <svg class="ts-marker-canvas">     <!-- Marker vertical line -->
        ...
    </div>
    <div class="image-container">      <!-- wave image container. Should not be modified by custom css-->
        <img class="image"/>              <!-- wave image. Should not be modified by custom css-->
    </div>
</div>
<div class="ts-control">         <!-- bottom <div> (control) -->
    <a class='ts-play ts-button'></a>
    <a class='ts-pause ts-button'></a>
    <a class='ts-rewind ts-button'></a>
    <a class='ts-forward ts-button'></a>
    <a class='ts-set-marker ts-button'></a>
    <a class='ts-volume-speaker ts-button'></a>
    <div class='ts-volume-wrapper-div'>
        <a class='ts-volume-bar-container'>
            <span class='ts-volume-bar'></span>
        </a>
    </div>
    <div class='ts-wait'></div>
</div>
<!-- Note that all svg's might be vml object if svg is not supported (thanks to Raphael library, see doc below -->

Setup and Dependencies

The easiest way to get started is to copy the content of the TimeSide UI package on your webserver, for example in a timeside-ui/ folder.

A few external !JavaScript packages are required, and must be loaded before TimeSide UI:

For conveniency these libraries are bundled in the TimeSide UI package. So settings things up should be easily done within the <head/> tag:

    <script type="text/javascript" src="timeside-ui/lib/jquery-1.6.min.js"></script>
    <script type="text/javascript" src="timeside-ui/lib/soundmanager2-nodebug-jsmin.js"></script>
    <script type="text/javascript" src="timeside-ui/timeside.js"></script>

Of course, you are free to load your preferred versions of the dependencies, provided they meet the requirements outlined above.

As svg is not supported in all browsers (specifically, IE7 and IE8), the TimeSide library first checks for svg support and, in negative case, loads and delegates the Raphael Graphics Library for building vml graphics (an old format of IE). The library is also bundled in in the TimeSide UI package.

Getting started

Few notes before getting started: the whole TimeSide UI API resides under the Timeside !JavaScript global object (The goal is to strictly avoid polluting the global namespace). To initialize the TimeSide player it is sufficient to call the method Timeside.load as described below. The method is executed once the document is ready, so that it can be safely called everywhere in the code; it first loads all necessary timeside scripts (including Raphael library if necessary) whose path is retrieved by searching the src attribute of the timeside.js <script>: therefore, move your timeside-ui/ folder wherever you want but do not move any !JavaScript file within it!

SoundManager has properties which configure debug mode, flash movie path and other behaviours. At minimum, the soundManager.url property must be assigned a path used to look for the necessary flash movie. Therefore, before loading the player it is necessary to write something like:

    <script type="text/javascript">
       soundManager.url = '/path/to/swfs/';  // path (directory) where SM2 .SWF files will be found.
    </script>

where '/path/to/swfs/' is the path of the '/swf' folder inside the downloaded TimeSide package. Please refer to soundManager configuration for more properties.

We are aware that in !JavaScript Integer and Float types are treated as a numeric type. However, in the remainder of the document (especially in code examples) we will avoid formality in order to be exhaustive. Therefore, when possible we will use the notations int and float.

A simple (static) player

A simple graphical player can be embedded in your HTML page by calling, e.g.:

    Timeside.load({
        container: "#player",
        sound: "/path-to/my_sound.mp3",
        soundDuration: 149264,
        soundImage: "/path-to/my_image.png",
       }
    );

where:

  • container: string or object

    is the player containing <div>, in one of the following formats:

    1. jQuery string format, e.g.: '#player'
    2. jQuery element, e.g.: jQuery('#player')
    3. HTML DOMelement, e.g.: document.getElementById('player')

    Note that

    • container must be already attached to the document, either by means of standard html or dynamically with JavaScript. The content of container, if any, will be erased and replaced by the player elements.
    • At first, we suggest to use one of the TimeSide stylesheets to properly display the player. In this case, the markup structure within container must be embedded in (not necessarily as a child of) a <div/> of class "ts-player".
  • sound: string or object

    is the sound to be played, in one of the following formats:

    1. string denoting the url of the sound,
    2. the sound object created via soundManager.createSound function (see SoundManager documentation for details)
    3. the argument to soundManager.createSound function (!JavaScript object). Note that the object has two minimum required fields, id and url, both strings. It is useful to specify some additional properties of the sound object, e.g.:
        {
          id: 'mysound',              //required
          url: 'path_to/my_url.mp3',  //required
          multiShot: false,           // avoid sound "restart" or "chorus" when played multiple times
                                      // (by default it is true)
        }

    Please refer to the section "SoundManager Global Sound Object Defaults" of this page for a list of available properties. Adding properties denoting functions fired while playing should be done with care: first, some of them are already attached to the sound object by the TimeSide API internally (eg, whileplaying, onfinish), thus, depending on the sound object behaviour (not tested) they could be overridden or interfere with the player performances. Second, the TimeSide player has a lot of notification methods which cover almost all user needs.

    Finally, note that in the first case, i.e. when the parameter sound is an url string, the argument of soundManager.createSound function defaults to:

      {
         id: 'ts-sound',
         autoLoad: false,
         url: sound,
         multiShot: false
      }
  • duration: int

is the sound duration in milliseconds. A typical example is t calculate it server-side by means of TimeSide analysers and sent to the page. This parameter is mandatory, otherwise the sound object should be first loaded in order to know its duration, and then the player (and thus, the HTML page) rendered and displayed.

In any case, it is possible to query the sound object (at the cost of slowing down page rendering), but in this case the sound object must be preloaded:

   var config = {...}; //the object that will be passed as argument to Timeside.load
                       //populated with our properties
   soundManager.createSound({
       id: 'my_id',
       url : 'path_to/my_url.mp3',
       autoLoad: true,
       onload = function(){
           var d = this.duration; //this refers to the sound object
                                  //note that duration might be zero or undefined if the load failed
           config.sound = this; //set the sound (already loaded)
           config.soundDuration = d; //set the duration
           TimeSide.load(config);
       }
   })
  • soundImage: string

is the wave image URL, in string format

A more complex (dynamic) player

To fully take advantage of all player capabilities (e.g., dynamic resizing, control over the loading process, marker management, player management once loaded) we can create a player by calling:

    Timeside.load({
        container:        "#player",
        sound:            "/path-to/my_sound.mp3"
        soundDuration:    149264,
        soundImage:       function(width,height){...},
        imageSize:        {width: 170,height: 30},       //optional
        markersArray:     [marker_1, ...., marker_N],    //optional
        newMarker:        function(offset){...},         //optional
        onError:          function(errorMsgString){...}, //optional
        onReady:          function(player){...},         //optional
        onReadyWithImage: function(player){...},         //optional
        messages:         {loading: 'loading', ...}      //optional
       }
    );

where container, sound and duration are specified above, and:

  • soundImage: string or function

is the wave image URL. It can be:

  1. a string (see abve), to denote a static image
  2. a callback (!JavaScript function). The callback takes as argument two numbers, the width and height of the <div/> housing the new image and must return the image URL in string format. This allows, e.g., to perform high quality server-side resizing/resampling by queryng, e.g., the TimeSide graphers for the sound image with given width and height. Obviously, a callback returning a constant string, such as function(w,h){return 'mywave.png';} has the same effect than specifying 'mywave.png' as soundImage parameter: actually, this parameter will override the function player.imageCallback (see dynamic resizing and player layout methods): if soundImage is specified as string, it will be converted to a function always returning soundImage, regardeless of the parameters width and height
  • imageSize: object

is a !JavaScript object with two properties, 'width' and 'height', which specifiy the initial default size of the wave <div>. Any other value (undefined, null, non-!JavaScript object) will be ignored and has no effect on the initial size (which will be thus calculated according to the css rules). Accordingly, an object with only one property 'width' or 'height' will skip the missing one and set only the existing one.

Note: Usually the player is embedded in a web page with components interacting with each other. Thus it might be more convenient to skip this parameter and specify player dimensions properties in a separate stylesheet (see CSS and skins section for details) as for all other page elements. Keep in mind however, that if no css at all is provided on the player elements, imageSize must be specified to properly display at least the wave image.

  • markersArray: Array

is an array of markers to be visualized on the player ruler when the player loads. In the remainder of this doc, a marker is a !JavaScript object with only one required key, offset, which indicates the marker time position as a float in the format seconds.milliseconds, e.g.: {offset: 57.300156}

If markersArray is empty, undefined or any other non-array value, no marker will be displayed at startup.

When the player is fully loaded, markers are stored in an Array-like object (markerMap) instance of TimesideArray. The map is sorted in ascending order according to markers offset. An (unique) id is automatically set on each marker in order to manage the case of two markers with the same offset (see notes on newMarker below for details). Adding, removal and getting marker elements is performed via a binary search algorithm which is substantially faster as the number of markers N grows large (log2(N) maximum iterations)

  • newMarker: boolean or function

controls whether new markers can be added to the player (and graphically on the player ruler). Possible values are:

  • false: no marker can be added. The 'add marker' button will be hidden on the player control
  • true or function: markers can be added. The 'add marker' button will be visible on the player control
  1. If true, when a new marker is added, the function player.newMarker(player_current_sound_position) is called. The function returns the default marker with only one key, offset, equals to the function argument (player_current_sound_position).
  2. If function, newMarker is the function which creates user-defined marker objects by overriding the default player.newMarker described above. The function must receive as argument the marker offset (float) and return a !JavaScript object whose only requirements is to have the property offset equals to the function argument, e.g.:
  function(offset){
     return {
             offset: parseFloat(offset),
             description: "",
             title: "",
             isEditable: true,
             isSavedOnServer: false
     }
  };

Obviously, the object returned by newMarker should be consistent with the elements of the parameter markersArray, if the latter is provided as a non emtpy array.

Notes: the marker offset must be a !JavaScript number (we provided as hint the use of !JavaScript function parseFloat in the example above). It is extremely important for each offset to be a number. As !JavaScript is a weakly typed language, passing non-number offsets might lead to subtle errors to catch: imagine for instance two markers with offsets "10.5" and "9.5" (strings). The markers would be normally inserted in the map. However, as "10.5" < "9.5", their insertion order would be incorrect, and the insertion index of any other marker inserted afterwards unpredictable. As a consequence, the positions of the marker labels on the player ruler would mismatch their index, and - even worse - any custom method that relies upon markers control and management might fail).

The property isEditable, although not mandatory, determines whether or not the marker offset can be changed, i.e. if it can be moved on the player ruler. If missing, it defaults to false. Therefore, in the example above, clicking the button 'add marker' will display a dynamic marker (movable with mouse events); on the other hand, if newMarker is simply set to true, clicking the button 'add marker' will display a static marker (isEditable is missing by default). The editable property of each marker can be changed via the property player.setMarkerEditable, as explained in details in the player marker properties

Summarizing, a marker is a JavaScript object which the user must provide at least with one required key, offset, as float. However, to tell the whole story, a marker is stored in the markerMap as an object with at least three properties: offset (float, required), isEditable (boolean, if missing is set to false) and id (if missing is automatically created by the markerMap and must be unique for each marker). By default it is a string with fixed length derived from the current timestamp and a randomly generated number. A user defined id must be unique for each marker and should be consistently comparable against other ids, as for markers with the same offset, ids will determine the necessary sort order and, if equal, that the two markers are equal. **In general, there is no reason to implement a custom id.

  • onError: function(string)

a callback to be executed when an error occurs. This includes non existing container, sound duration NaN (or non-positive) etcetera. The string argument passed to the callback is the error message. It is not fired on sound errors (see notes below for onReady)

  • onReady: function(player)

a callback to be executed when the player is fully loaded. The argument player passed to the callback is the newly created player, which in any case from the execution of this callback on will be globally accessible via the variable Timeside.player.

Notes: onReady is fired when the player is fully loaded, although the wave image could still be loading. To know the status of the loading process, use the property player.isImgRefreshing (see player properties and methods for details). To execute a callback once the player is ready and the sound image is fully loaded, use the property onReadyWithImage (see below).

onReady is executed even in case of sound error (e.g. if soundManager or the player sound object is not succesfully initialized) as this does not interfere with the correct player layout. Sound error messages are stored in the property player.soundErrorMsg. Bad sound urls (eg, pointing to non existing files) are cought once the player.play is called, so that at this stage player.soundErrorMsg might be empty (no error) even if the sound is not playable.

  • onReadyWithImage: function(player)

a callback to be executed when the player is fully loaded and its image has been loaded. This callback is likely to be called after onReady. The argument player passed to the callback is the newly created player, which in any case from the execution of this callback on will be globally accessible via the variable Timeside.player

onReadyWithImage is also executed even if the sound object of the player is not succesfully initialized (see notes on onReady above).

  • messages: Object

Property setting an object with the default messages to be displayed on the wait panel. The player has three types of default messages:

 messages.loading = "loading"              //displayed when playback is started and sound is loading
 messages.buffering = "buffering"          //displayed when playback is started and sound is buffering
 messages.imgRefreshing = "refreshing img" //displayed when image is being loading and not-yet displayed

which can be customized in messages. These properties can be also set after the player is loaded via the property player.msgs. Setting a property to the empty string (or any other value which evaluates to false, e.g. undefined) will NOT display the wait panel when the corresponding event is fired

Markers control and management

With customized marker objects (i.e., when the parameter newMarker described above is a user defined function) the user might want also to interact with the player in order to add/edit/remove markers. As we will see more specifically in the last section, each class defined in the variable TimeSide (as well as the player object) is instance of Timeside.classes.TimesideClass, a function with methods for adding listeners and action notifications, the same way standard event types (e.g., 'onclick') are bound to document elements.

The marker-specific eventTypes bound to the player object are three (for a complete list, see subsection "Default event types" in Event listening and notification):

player.bind('markerAdded',callback(data));
player.bind('markerRemoved',callback(data));
player.bind('markerMoved',callback(data));

where callback is a function which must take a parameter data (JavaScript object), which reads:

  • 'markerAdded':
 data={
   marker: object, //The newly created marker
   index: int      //The index of the newly created marker
 }
  • 'markerRemoved':
 data={
   marker: object, //The marker removed
   index: int      //The index of the removed marker
 }
  • 'markerMoved':
 data={
   marker: object,   //The marker moved
   oldOffset: float, //The old offset of the marker. The new offset (new offset != old offset) is marker.offset
   fromIndex: int,   //The old index of the moved marker
   toIndex: int,     //The new index of the moved marker. It might be equal to fromIndex
                     // if the marker did not "cross" any other marker in the markermap
 }

If you are familiar with jQuery, note that the syntax is exactly the same as jQuery.bind(eventType,handler), the only difference is a third (optional) parameter which denotes the reference to the this keyword inside the callback, e.g.:

player.bind('markerAdded',function(data){
    var p = this; //refers to Timeside.player object
    },Timeside.player);

Examples

As a (quite foolish) example, if we want to alert the user every time a new marker is added, we can write:

Timeside.player.bind('markerAdded', function(data){
    alert('new marker added at time '+data.marker.offset+' and index '+data.index);
    });

and click on the player 'add marker' button to see the alert dialog displaying the message

As a more complex example, imagine we want a player where:

  • each marker has a title property which is editable
  • markers can be removed from the player ruler (note that the player by default has no control - e.g., button - to remove markers. Therefore, we can bind whatever callback to the event type 'markerRemoved', but we still miss a control calling player.removeMarker, which will fire the events of type 'markerRemoved')

To achieve this, we can imagine that each time the user presses the 'add marker' button on the player, we add a 'row' on a spearated <div> (which we created) which could look like this:

1st step: Initialize the player with newMarkerCallback returning objects of the type, e.g.:

{
 isEditable: true,
 offset: parseFloat(offset),
 title: "", //default marker title when, e.g., the add marker button is pressed
}

2nd step: add bindings to the player:

var p = Timeside.player;

p.bind('markerAdded', function(data){
 var m = data.marker; //reference to the newly created marker

 //create html elements:
 var okButton = jQuery('<input/>').attr('type','button').val('ok');
 var textInput = jQuery('<input/>').attr('type','text').val(m.title);
 var deleteButton = jQuery('<input/>').attr('type','button').val('delete');

 //do other stuff, e.g. append elements to a div or a container etcetera...

 //Now: When the user clicks the ok button, set marker title according to the text input value
 okButton.click(function(){
     m.title = textInput.val();
 });

 //When the user clicks the remove button, remove the marker
 //this will fire events of type 'markerRemoved'
 deleteButton.click(function(){
     p.removeMarker(m); //note that p.removeMarker(data.index) is safe ONLY if the markers cannot be moved
 });
});

//listen for marker removal events:
p.bind('markerRemoved', function(data){
 //remove components, e.g., the <div> containing the input, ok and delete button associated to the removed marker
});

//listen for marker move events:
p.bind('markerMoved', function(data){
 //reorder inputs and ok buttons to match the marker order
 //Eg:
 //detach div at index data.fromIndex
 //re-insert the div at index data.toIndex
});

Layout

CSS and skins

The appearance of TimeSide UI can be fully customized through CSS rules, applied to the markup structure.

Two default skins are also provided in the skins/ subfolder. They can be set in the standard way within the <head/> tag:

<link type="text/css" href="timeside-ui/skin/lab/style.css" />

or

<link type="text/css" href="timeside-ui/skin/classic/style.css" />

The default skins exploit contextual selectors (e.g., ".player .ts-ruler"), so the player must embedded in (or in a child of) a div of class 'ts-player', e.g.:

<div class="ts-player" />
   <!--player is contained here somewhere...-->
</div>

If you don't like this restriction, feel free to modify the css according to your needs. We only suggest to embed the player this way at first to see what's going on behind the scene (by exploring the css files) before providing your custom skins.

Notes (in the remainder of this section, we will denote R, W and C as player ruler, wave and control <div>s, whose css class selectors are ".ts-ruler", ".ts-wave" and ".ts-control", respectively. See player figure and and markup structure in the Introduction):*

  • Css skins should customize player appearence. Some css properties might break the player layout, resulting in some components not properly positioned or hidden. Assuming this is not what you are aiming to, properties such as "position", "display", "float" and "overflow" should not be specified on any of the player elements, unless you really know what you are doing. In any case, consider that some styles properties will be always set (and thus overridden, if any) inside the !JavaScript code (e.g., R, W and C will have all 'position' = 'relative' and 'overflow' = 'hidden')

  • Player dimensions (css width and height properties) can be specified through css. As a rule of thumb keep in mind that no width ('auto') on a <div> means that the total width of the <div> is automatically computed to fit the parent, and no height ('auto') on a <div> means that the total height of the <div> is automatically computed to fit its content.

    Therefore, setting player total width is quite straighforward: you can set it by means of css width property on W (this is actually what happens when imgSize width is specified), or you can simply provide no css width property at all for any player <div>, letting the player fit the avilable parent width. In any case, according to what stated above, do not specify the same width property for W, R and C (is useless), and do not specify different width propertis for W, R and C (is dangerous). wiki:commentNote that setting width='100%' will force the content alone to be 100%, meaning the padding etc. will stick out of the div, making it larger than the parent</wiki:comment>

    Setting the player total height is done separately on W, R and C css height properties in the standard way, i.e., by means of css rules applied on them or their contained elements. Two remarks on !JavaScript code: first, most of W sub components (sound image, marker and ruler vertical lines) are "stretched" automatically to fit all the available room (which also means that some of their css can not be customized), so W must have a nonzero non 'auto' height. Second, the height of the vector-graphics ruler of R is set as the height of two lines of text, i.e., according to R 'font-size' and 'line-height' css properties (if missing they wil be inherited from the parent)

  • *Classes relative to vector graphics elements (beginning with '.ts-svg-') have specific svg css syntax. As already mentioned, svg is not supported in all browsers, and the TimeSide library delegates Raphael for building vml graphics (an old format of IE), in case. Raphael has a lot of attributes which can be set on the graphics, whatever format they are. In order to customize entirely and cross browser the player through css, when svg is not supported the TimeSide library parses stylesheet classes and converts its rules into suitable Raphael attributes. However, note that:

  • Many but not all css properties are supported for conversion. Actually, only those that have a corresponding attribute translation in Raphael. Supported css properties are:

    "clip-rect", "cursor",'fill', "fill-opacity", "opacity",
    "stroke", "stroke-dasharray", "stroke-linecap", "stroke-linejoin",
    "stroke-miterlimit","stroke-opacity","stroke-width", "text-anchor"
    
  • The TimeSide parser recognizes only stand-alone class selectors, without spaces or commas (it is not the purpose of the library to have full class parsing capabilities, even more so when the matter is an old and not standard graphics format). In other words

      .ts-svg-marker-canvas{
         stroke-width:2;
      }
    

    will set a stroke of width 2 on the elements of class 'ts-svg-marker-canvas' in all browsers, whereas

      #player .ts-svg-marker-canvas{
         stroke-width:2;
      }
    

    will not set the desired stroke width on, e.g., IE 7-8

  • Apparently, Raphael has a bug on color properties specified as predefined/Cross-browser color names (e.g.: 'white', 'yellow'). Thus this would break in IE7-8:

      .ts-svg-ruler-lines{
         stroke: white; //ERROR on non svg supporting browsers which PREVENTS the player to be loaded!!
      }
    

    Any other value seems to be safe. We suggest however to provide those properties as standard hexadecimal values:

      .ts-svg-ruler-lines{
         stroke: #ADADAD;
      }
    
  • Just keep in mind that the default skins implement contextual selectors (e.g., ".player .ts-ruler") for two main reasons: avoid css "collisions" with potential elements of the page with the same class name (e.g., .ts-ruler) and allow dynamically skinnable players, e.g. by changing the class attribute of the outer container. As we have seen, this is unfortunately not (yet) possible for backward compatibility with IE7-8. In any case, if you are really interested to dynamically skinnable players, you can achieve the goal for the moment by simply creating your own css and dynamically adding/removing them to the <head/> tag of the page.

Dynamic resizing

Unlike Flash-based media players, the DHTML-based TimeSide player can be resized in various ways (as explained in the previous section) by dynamically modifying the CSS rules of the player elements or those of one of its containers in the HTML page, or by simply resizing the browser window if the player size is relative to the window.

The TimeSide.Player class can automatically track browser window resize events if its property setDynamicsResize is called with true as argument (it is false by default). In this case, the player checks at regular intervals (!JavaScript setInterval method) potential size changes. If it is the case, it calls:

player.refreshImage()

which in turn calls player.imageCallback (which can specified when loading the player via the parameter soundImage). Obviously, for accurate high quality server-side resizing/resampling, setDynamicResize(true) should be called if player.imageCallback is a callback returning a width- and height-dependent image (where width and height are the callback arguments).

Note that in case of repeated size changes (such as, i.e., window mouse resizing while dragging) only the last resize is fired. This attempts to prevent several unnecessary refreshImage calls which might lead to an unresponsive page. Basically, each time a player size change is detected, refreshImage is called within a delay of time only if meanwhile it's size has not changed again.

wiki:comment The method is (or at least, should be) smart enough However, it can't reliably detect size changes that originate from the DOM content. In that kind of circumstance, it is recommended to call TimeSide.Player.resize() to ensure that the UI is properly redrawn.

For example, using jQuery:

$("#myplayer").width(500);
player.resize() // player is a TimeSide.Player object

Additionally, for accurate waveforms and/or spectrograms, the graph can be dynamically loaded by passing a callback to the TimeSide.Player constructor. This callback is called whenever the image width and/or height change, and allows to perform high quality server-side resizing/resampling.

function get_image(width, height) {
    // Return the dynamic image src:
    return "generate_graph?w=" + width + "&h=" + height;
}

TimeSide.load(function() {
    var player = new TimeSide.Player('#myplayer', { image: get_image });
});

</wiki:comment>

Timeside UI code

The TimeSide player is created and can be manipulated using a !JavaScript object oriented API. The !JavaScript inheritance-simulating techniques is accomplshed by means of a 'Class' function which is the ancestor class of all TimeSide classes:

Inhertance Structure

/*
 *Function name      defined in:        Description
 */
  Class               (`timeside.js`)    //The base javscript function with class functionalities:
  |                                      // simple inheritance and super method calling
  |
  +- TimesideClass    (`timeside.js`)    //Class implementation ancestor class of all Timeside classes
     |
     +- RulerMarker   (`rulermarker.js`) //TimesideClass for building each marker on the ruler (including pointer)
     |
     +- Player        (`player.js`)      //TimesideClass for building the player
     |
     +- TimesideArray (`timeside.js`)    //TimesideClass with array-like properties and methods
        |
        +- MarkerMap  (`markermap.js`)   //TimesideArray for managing markers
        |
        +- Ruler      (`ruler.js`)       //TimesideArray for building the ruler and managing rulermarkers

As already mentioned, the only variable created on the window object is the Timeside variable, which includes:

Timeside = {
    Class,             //The Class object whereby each element of Timeside.classes has been created,
    classes : {
        TimesideClass, //accessible with Timeside.classes.TimesideClass
        TimesideArray, //accessible with Timeside.classes.TimesideArray
        MarkerMap,     //...
        Ruler,         //...
        RulerMarker,   //...
        Player         //...
    },
    player, //The player object = new Timeside.classes.Player(...) accessible with Timeside.player
            //when the player is created:
            //player.getMarkerMap() returns the player markerMap object = new Timeside.classes.MarkerMap(...)
            //player.getRuler() returns the player ruler object = new Timeside.classes.Ruler(...)
            //player.getRuler().toArray()[i] returns the ruler i-th marker = new Timeside.classes.RulerMarker(...)
            //player.getRuler().getPointer() returns the ruler pointer = new Timeside.classes.RulerMarker(...) as well
    load(), //Function to be called in order to initialize Timeside.player (see above)
    config, //object with configuration properties to be used in load
    utils   //object with utilities functions/properties to be used by Timeside classes
}

Therefore, once fully initialized, the user can work with the accessible Timeside.player object (instance of Timeside.classes.Player)

The Player object: properties and methods

Here properties and method of the player object. player is a shorthand for Timeside.player

Playback

player.play()

Starts playback. This method is called by the 'play' button on the control <div/>

player.setSoundPosition(offset)

Sets the sound position and moves the pointer on the player ruler accordingly. This method is called by the 'play' button on the control <div/>

  • offset (float): the offset of the new sound position in the format seconds.milliseconds (e.g., 45.562)
player.playState //Read-only property. Do not modify!

Property returning an integer indicating the player play state. Read-only property. Do not modify! Possible values are:

  • 0: player not playing
  • 1: player playback started: sound loading
  • 2: player playback started: sound buffering
  • 3: player playback started, nor buffering neither loading. This condition is most likely corresponding to the "sound heard" condition
player.pause()

Pauses playback. This method is called by the 'pause' button on the control <div/> (this method is actually stopping the sound object. This allows to more reliably work with the sound.position property)

    player.rewind()

Sets the sound position (and moves the pointer) to the previous marker position (according to the current sound position), or to the start of sound if no previous marker is found. This method is called by the 'rewind' button on the control <div/>

    player.forward()

Sets the sound position (and moves the pointer) to the next marker position (according to the current sound position), or to the end of sound if no previous marker is found. This method is called by the 'forward' button on the control <div/>

    player.getSound()

Returns the sound object associated to the player. Manipulating the sound object directly is discouraged, as it might interfere with the player object. This method is intended for retrieving properties not directly accessible via the player object. Returns undefined if a sound error occurs

    player.soundErrorMsg

Returns the empty string if the sound object of the player has been succesfully initialized. Otherwise, it is a string denoting the error occurred. This includes soundManager errors (including flash errors for non-html5 supporting browsers), bad sound-object (or sound-object parameters), invalid urls. The case of urls not pointing to existing and "playable" audio files will be cought only the first time player.play is called.

    player.showSoundErrorMessage()

Callback to be executed when player.play is called and the sound object of the player has not been succesfully initialized. It can be overridden: by default, it shows an alert dialog with the content of player.soundErrorMsg

    player.soundPosition //Read-only property. Do not modify!

Returns the current sound position, in the format seconds.milliseconds (e.g., 45.672). Read-only property. Do not modify!

    player.getSoundDuration()

Returns the total duration of the sound, in the format seconds.milliseconds (e.g., 45.672)

    player.setSoundVolume(volume)

Sets the volume of the sound. This method is called by the volume-related button(s) on the control <div/>

  • volume (int): an integer between zero and 100
    player.soundVolume //Read-only property. Do not modify!

Returns a number between 0 and 100 denoting the sound volume. Read-only property. Do not modify!

    player.readyState()

Returns a numeric value indicating a sound's current load status:

  • 0 = uninitialised
  • 1 = loading
  • 2 = failed/error
  • 3 = loaded/success

Note: If the player is created by passing an already created sound object, readyState() might be 2 even if player.soundErrorMsg is empty. Otherwise, if the player is created by means of a url string or an object denoting the sound object default properties, player.soundErrorMsg might be a non-empty string even if readyState() is not 2 (e.g., it might be 0, i.e. uninitialized).

Markers

    player.newMarker(offset)

Function that builds and returns the marker object with offset property equals to offset. Can be overridden (see above) for custom markers

  • offset (float): the offset of the marker to be created in the format seconds.milliseconds (e.g., 45.562)
    player.addMarker(offset)

Adds a new marker at offset . Calls by default player.newMarker(offset) and puts in the map the returned marker. This method is called by the 'add marker' button on the control <div/> (if visible)

  • offset (float): the offset of the marker to be created in the format seconds.milliseconds (e.g., 45.562)
    player.removeMarker(identifier)

Removes a marker

  • identifier (int or object): if number, it indicates the index of the marker to be removed. Otherwise, it must be a valid marker belonging to the marker map
    player.moveMarker(identifier, newOffset)

Moves a marker to a new offset

  • identifier (int or object): if number, it indicates the index of the marker to be removed. Otherwise, it must be a valid marker belonging to the marker map
  • newOffset (float): the new marker offset in the format seconds.milliseconds (e.g., 45.562)
    player.getMarker(index)

Returns the index-th marker of the player, or undefined if index is lower than zero or greater than len - 1, where len is the number of markers of the player

  • index (int): the marker index
    player.setMarkerEditable(identifier, value)

Sets a marker property isEditable = value. You can change also directly the property isEditable on each markerm but this will not fire events of type "markerEditStateChanged", so avoid it if you want notifications when a marker has changed its editable state (see "Default event types" in the section Event listening and notification)

  • identifier (int or object): if number, it indicates the index of the marker to be removed. Otherwise, it must be a valid marker belonging to the marker map
  • value (boolean): the value of the property isEditable to be set
player.each(callback)
player.each(startIndex, callback)
player.each(startIndex, endIndex, callback)

Iterates over the player markers. Each time callback runs, it is passed two arguments: the current loop iteration i, and the marker at index i.

  • startIndex (int) optional: the start index, inclusive (if missing, it defaults to 0)
  • endIndex (int) optional: the end index, exclusive. If missing, it defaults to the number of player markers
  • callback (function): a function accepting two arguments, the current marker index and the current marker. Example: print on the console the informations about all the current markers:
 player.each(
     function(i,m){
        console.log('marker # ' + i + ' at time: ' + m.offset);
     }
 );

Layout

    player.imageCallback(width, height)

Function that returns the wave image to be displayed according to the wave <div/> width and height. The function must return a valid URL pointing to an image. This allows, e.g., to perform high quality server-side resizing/resampling by querying Timeside grapher for a sound image with given width and height. This callback is specified as starting player parameter (see above)

  • width (number) the new image width
  • height (number) the new image height
    player.refreshImage()

Forces a refresh of the image. Set the wave image src to the url returned by imageCallback

    player.isImgRefreshing //Read-only property, do not modify!

Boolean value indicating whether or not the wave image is being loading and not-yet displayed. Read-only property. Do not modify!

    player.resize()

Forces a resize of the player. It resizes the player ruler and control and calls player.refreshImage()

    player.setDynamicResize(value)

Sets dynamic resize. If value is true, the player starts checking at regular interval potential size changes and in case, it will set the wave image src to the url returned by imageCallback

  • value (boolean): if true, the player checks for size changes at regular interval (!JavaScript setInterval method). If false, the player stops checking (!JavaScript clearInterval method) and player size changes do not call any image refresh (false is the default)
    player.getImageUrl()

Returns a string of the URL of the wave image (i.e., the src attribute of the wave <img/>)

    player.getImageSize()

Returns an object with two number properties, width an height, denoting the size of the wave image

Miscellaneous

    player.setWait(message)

Sets the content of the wait panel (<div/>) in the lower right corner of the player (see player image in the Introduction) and hides or shows the <div/> accordingly

  • message (string): If it evaluates to false (e.g., undefined, empty string) it hides the <div/>. Otherwise shows it and sets its html content to message
    player.msgs

Property returning an object with the default messages to be displayed on the wait panel (<div/> innerHTML). There are currently three types of default messages:

  msgs.loading = 'loading'              //displayed when playback is started and sound is loading
  msgs.buffering = 'buffering'          //displayed when playback is started and sound is buffering
  msgs.imgRefreshing = 'refreshing img' //displayed when image is being loading and not-yet displayed

These properties can be customized after the player is loaded. Setting a property of player.msgs to the empty string (or any other value which evaluates to false, e.g. undefined) will NOT display the wait panel when the corresponding event is fired

Event listening and notification

    player.bind(eventType, callback)
    player.bind(eventType, callback, thisArgInCalback)

Binds a callback to eventType. The function callback will be executed each time fire(eventType) will be called on the player (see below).

  • eventType (string) the event type name
  • callback (function) the function to be executed. It always takes as argument a !JavaScript object data which is populated by properties when the eventType is fired.
  • thisArgInCalback (object) optional: specifies the optional this keyword in callback
    player.fire(eventType)
    player.fire(eventType, data)

Fires (triggers) all callbacks associated to eventType by means of player.bind(eventType,...). It does nothing if no callback is bound to eventType

  • eventType (string) the event type name
  • data (object) optional: A !JavaScript object which is populated by properties to be passed as argument to all callbacks registered to eventType by means of player.bind(eventType,...) (see above). If missing it defaults to the empty !JavaScript object {}
    player.unbind()
    player.unbind(eventType)

Unbinds (i.e., removes from memory) all callbacks bound to eventType

  • eventType (string) optional: the eventType name. If missing or undefined, it removes all custon (not default) bindings for all eventTypes

As in jQuery bind, If the eventType string contains a period (.) character, then the event is namespaced. The period character separates the event from its namespace. For example, in the call .bind('markerMove.name', function(data){...}), the string "markerMove" is the event type, and the string "name" is the namespace. Namespacing allows us to unbind or trigger some events of a type without affecting others. Accordingly, when a callback is bound in this fashion, we can still unbind it the normal way: .unbind('markerMove') or alternatively, if we want to avoid affecting other callbacks, we can be more specific, i.e. .unbind('markerMove.name'). Note that TimeSide namespacing does not have the whole funtionalities of jQuery namespacing, please refer only to those just described. Moreover, consider that in the TimeSide library a period character is considered a namespace separator only if it splits non-empty strings. In other words, event types such as ".name" or "name." will be considered as they are, without namespaces. As in jQuery namespacing, if a string contains more than a period character, the first period only will be considered the namespace separator.

Note: event notification is optimized for speed of the method, not for memory usage. That means, with namespacing the same function is stored more than once in the internal map storing listeners. This allows a call to fire to be faster, the drawback being more memory used

Default event types

Functions which interact with the player can bind calbacks to the following event types implemented by default:

Event type Fired when: Passed `data` argument:
"markerCrossed" a marker has been crossed while playing
    {
     index: int,                   //the index of the marker being crossed
     marker: object,               //the marker being crossed
     currentSoundPosition: float,  //self explanatory
     nextMarkerTimeInterval: array //undefined if the marker being crossed is the last in the
                                   //marker map. Otherwise, it is an array of two elements defining
                                   //the time interval around the next marker offset.
                                   //Due to the way event notification is handled in soundManager
                                   //while playing, TimeSide detects marker cross events
                                   //as soon as the sound position "falls" in an interval
                                   //(with predefined time margins) around a marker offset.
                                   //This means, e.g., that data.currentSoundPosition might be lower
                                   //than marker.offset. The property nextMarkerTimeInterval is useful
                                   //to know, e.g., the minimum time before the next marker cross
                                   //event will be fired, e.g.:
                                   //data.currentSoundPosition-nextMarkerTimeInterval[0]
    }
"playStateChanged" sound play state has changed.
    {
     player: object,        //the player object
     oldPlayState: int,     //self-explanatory. The current play state is player.playState
     endOfPlayback: boolean //true if the play state has changed from 3 to 0
                            //as a result of the end of sound reached.
                            //This property is useful to distinguish between
                            //"end of playback" and "pause click" events
    }
"markerAdded" a marker has been added to the marker map
    {
     marker: object, //the newly created marker
     index: int      //the index of the newly created marker
    }
"markerMoved" a marker has been moved in the marker map
{
 marker: object,    //The marker moved
 oldOffset: float,  //the old offset of the marker.
                    //The new offset (new offset != old offset) is marker.offset
 fromIndex: int,    //the old index of the moved marker
 toIndex: int,      //the new index of marker. It might be equal to fromIndex
                    //if the marker did not "cross" any other marker
}
"markerRemoved" a marker has been removed from the marker map
{
 marker: object, //The marker removed
 index: int      //The index of the removed marker
}
"soundPositionSet" the method setSoundPosition is called and the pointer has been set. **It is not fired while playing**
    {
     player: object,          //The player object
     oldSoundPosition: float  //The old sound position.
                              //it might be equal to player.soundPosition
    }
"markerEditStateChanged" the property isEditable of a marker has been changed via `player.setMarkerEditable`
    {
     marker: object,    //The event target marker
     oldValue: boolean, //the old value of the property marker.isEditable
     value: int,        //the new value of the property marker.isEditable
     index: int         //The event target marker index
    }
"markerMouseEvent" a mouse event has been performed on a marker **or the pointer** on the player ruler
    {
     eventName: string, //One of the following (jQuery syntax):
                          "mouseenter", "mouseleave", "mousedown", "dragstart" ,"dragend", "click"
     eventObj: object,  //The jQuery event object related to the mouse event. Fore details, see
                        //http://api.jquery.com/category/events/event-object/
     index: int,        //The marker index >=0, or -1 if the target of the mouse event is the pointer
     marker: object     //The marker object associated to the mouse event,
                        //or undefined if the mouse event target is the pointer
    }
"waitShown" the wait panel has been shown
    {} //empty object
"waitHidden" the wait panel has ben hidden
    {} //empty object
"imgRefreshing" a new wave image is being loading
    {} //empty object
"imgRefreshed" the new wave image has been loaded
    {} //empty object

Notes:

  • Events of type "markerMouseEvent" are fired on markers regardless of the property isEditable, which on the other hand dictates if mouse events move a marker on the player ruler and consequently, if the eventType "markerMoved" is fired. Due to browser differences in handling mouse events and the need to avoid multiple notifications for different events (e.g., 'click' and 'dragend'), "click", "dragstart" and "dragend" eventNames are fired according to the "mousedown" eventName following the schema:

  • mouse is pressed: fire "markerMouseEvent" with eventName="mousemove". If the mouse button is the left one, then:

  • if the mouse is moved: fire (only the first time) "markerMouseEvent" with eventName="dragstart" (eventObj thus refers to a "mousemove" jQuery event)
  • if the mouse is released:
  • if the mouse was moved: fire "markerMouseEvent" with eventName="dragend"
  • otherwise: fire "markerMouseEvent" with eventName="click" thus in both cases, when mouse is released, eventObj refers to a "mouseup" jQuery event