1 /*********************************************************************************
  2  *
  3  *   Copyright (c) 2012, American Public Media Group
  4  *
  5  *   All rights reserved.
  6  *
  7  *   Redistribution and use in source and binary forms, with or without
  8  *   modification, are permitted provided that the following conditions are met:
  9  *
 10  *   - Redistributions of source code must retain the above copyright notice,
 11  *     this list of conditions and the following disclaimer.
 12  *
 13  *   - Redistributions in binary form must reproduce the above copyright notice,
 14  *     this list of conditions and the following disclaimer in the documentation
 15  *     and/or other materials provided with the distribution.
 16  *
 17  *   - Neither the name of the American Public Media Group nor the names of
 18  *     its contributors may be used to endorse or promote products derived from
 19  *     this software without specific prior written permission.
 20  *
 21  *   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 22  *   AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 23  *   IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 24  *   ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 25  *   LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
 26  *   OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 27  *   SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 28  *   INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 29  *   CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 30  *   ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 31  *   POSSIBILITY OF SUCH DAMAGE.
 32  *
 33  **********************************************************************************/
 34 /**
 35  * @name apmplayer.js
 36  * @fileOverview
 37  * @description the file contains APMPlayerFactory, APMPlayer, Playlist, Playable etc.
 38  * All methods are revealed via API through the use of 'revealing' or 'module' js pattern.
 39  */
 40 if (typeof APMPlayerFactory === 'undefined') {
 41 
 42     /**
 43      * @name APMPlayerFactory
 44      * @description factory pattern used to prohibit multiple copies of APMPlayer (singleton) + provides access to Playlist and Playable creation
 45      * @class
 46      */
 47     var APMPlayerFactory = (function () {
 48 
 49         'use strict';
 50 
 51         /**
 52          * @name MediaTypes
 53          * @description contains the names of all valid Media Types that APMPlayer handles -- also, see {@link Playable}.
 54          * @class
 55          * @ignore
 56          */
 57         var MediaTypes = {
 58             type : {
 59                 AUDIO : 'audio',
 60                 LIVE_AUDIO : 'live_audio'
 61                 //VIDEO : 'video',
 62             },
 63             isValid : function (type) {
 64                 var key;
 65                 for (key in MediaTypes.type) {
 66                     if (MediaTypes.type[key] === type) {
 67                         return true;
 68                     }
 69                 }
 70                 return false;
 71             }
 72         };
 73 
 74         /**
 75          * @name Debug
 76          * @description static debugging utility.  To enable debug messages from browser session, simply add '?debug=1' or 'debug=true' to URL when loading any APMPlayer instance (enables console logging).  To disable Console + onScreen debug trace, use 'debug=all'.
 77          * @class
 78          */
 79         var Debug = function () {};
 80         /**
 81          * @name enabled
 82          * @description used to mark if debugging logs should output or not.  to enable, add ?debug=1 to URL when loading player.  by default, it logs to the console only; to enable a debug div, do debug=all
 83          * @default false
 84          * @fieldOf Debug
 85          */
 86         Debug.enabled = false;
 87         /**
 88          * @name consoleOnly
 89          * @description used to mark if debugging logs should output to console only; true by default.  use debug=all to log to both the console AND a helper div on screen.
 90          * @default true
 91          * @fieldOf Debug
 92          */
 93         Debug.consoleOnly = true;
 94         /**
 95          * @name log
 96          * @description writes to the log
 97          * @param {string} message the information to log
 98          * @param {string} type level of severity to log (see type)
 99          * @param {string} object_name optional object name to pass (if logging outside of APMPlayer -- APMPlayer_UI for instance)
100          *
101          * @methodOf Debug
102          */
103         Debug.log = function (message, type, object_name) {
104             if (Debug.enabled === false) {
105                 return;
106             }
107             if (typeof soundManager !== 'undefined') {
108                 if (typeof object_name === 'undefined') {
109                     object_name = 'APMPlayer';
110                 }
111                 soundManager._writeDebug(object_name + '::' + message, type.id, false);
112             } else {
113                 console.log(object_name + '::' + message + '[' + type.name + ']');
114             }
115         };
116         /**
117          * @name type
118          * @description object that holds the three different logging levels
119          * @example
120          * Debug.type.info
121          * Debug.type.warn
122          * Debug.type.error
123          * @fieldOf Debug
124          */
125         Debug.type = {
126             'info' : {'id' : 1, 'name' : 'info'},
127             'warn' : {'id' : 2, 'name' : 'warning'},
128             'error': {'id' : 3, 'name' : 'error'}
129         };
130 
131         /**
132          * @name Events
133          * @description creates a new Events object.  Currently used within {@link APMPlayer}, internal and external version + {@link Playlist}
134          * @constructor
135          */
136         var Events = function () {
137             /**
138              * @name type
139              * @description holds all valid event types -- See Event Summary for details
140              * @type Object
141              * @fieldOf Events
142              */
143             this.type = {
144                 /**
145                  * @name AUDIO_LIB_READY
146                  * @event
147                  * @description fires after Audio library has initialized (only used internally)
148                  * @example Events.type.AUDIO_LIB_READY
149                  *
150                  * @memberOf Events
151                  */
152                 AUDIO_LIB_READY : 'AUDIO_LIB_READY',
153                 /**
154                  * @name MEDIA_READY
155                  * @event
156                  * @description fires when media has successfully loaded and is ready to be played
157                  * @example Events.type.MEDIA_READY
158                  *
159                  * @memberOf Events
160                  */
161                 MEDIA_READY : 'MEDIA_READY',
162                 /**
163                  * @name PLAYER_READY
164                  * @event
165                  * @description  fires when player has completely initialized
166                  * @example Events.type.PLAYER_READY
167                  * @fieldOf Events
168                  */
169                 PLAYER_READY : 'PLAYER_READY',
170                 /**
171                  * @name PLAYER_FAILURE
172                  * @event
173                  * @description fires when no suitable playback mechanism can be determined (final failure)
174                  * @example Events.type.PLAYER_FAILURE
175                  * @fieldOf Events
176                  */
177                 PLAYER_FAILURE : 'PLAYER_FAILURE',
178                 /**
179                  * @name CONNECTION_LOST
180                  * @event
181                  * @description fires if the network connection is lost
182                  * @example Events.type.CONNECTION_LOST
183                  * @fieldOf Events
184                  */
185                 CONNECTION_LOST : 'CONNECTION_LOST',
186                 /**
187                  * @name PLAYLIST_CURRENT_CHANGE
188                  * @event
189                  * @description fires when current item in playlist has updated
190                  * @example Events.type.PLAYLIST_CURRENT_CHANGE
191                  * @fieldOf Events
192                  */
193                  MISSING_FILE : 'MISSING_FILE',
194                 /**
195                  * @name MISSING_FILE
196                  * @event
197                  * @description fires if a file that was attempted to be played is missing
198                  * @example Events.type.MISSING_FILE
199                  * @fieldOf Events
200                  */
201                 PLAYLIST_CURRENT_CHANGE : 'PLAYLIST_CURRENT_CHANGE',
202                 /**
203                  * @name POSITION_UPDATE
204                  * @event
205                  * @description fires each time player head updates while playing-- a number in miliseconds usually accompanies the event to report current position in miliseconds.
206                  * @example Events.type.POSITION_UPDATE
207                  * @fieldOf Events
208                  */
209                 POSITION_UPDATE : 'POSITION_UPDATE',
210                 /**
211                  * @name PLAYING
212                  * @event
213                  * @description fires when playable begins playback
214                  * @example Events.type.PLAYING
215                  * @fieldOf Events
216                  */
217                 PLAYING : 'PLAYING',
218                 /**
219                  * @name PAUSED
220                  * @event
221                  * @description fires when playable is paused
222                  * @example Events.type.PAUSED
223                  * @fieldOf Events
224                  */
225                 PAUSED : 'PAUSED',
226                 /**
227                  * @name FINISHED
228                  * @event
229                  * @description fires when playable finishes playing
230                  * @example Events.type.FINISHED
231                  * @fieldOf Events
232                  */
233                 FINISHED : 'FINISHED',
234                 /**
235                  * @name UNLOADED
236                  * @event
237                  * @description fires when a playable is stopped or unloaded from player
238                  * @example Events.type.UNLOADED
239                  * @fieldOf Events
240                  */
241                 UNLOADED : 'UNLOADED',
242                 /**
243                  * @name BUFFER_START
244                  * @event
245                  * @description fires when player starts buffering
246                  * @example Events.type.BUFFER_START
247                  * @fieldOf Events
248                  */
249                 BUFFER_START : 'BUFFER_START',
250                 /**
251                  * @name BUFFER_END
252                  * @event
253                  * @description fires when player buffering ends
254                  * @example Events.type.BUFFER_END
255                  * @fieldOf Events
256                  */
257                 BUFFER_END : 'BUFFER_END',
258                 /**
259                  * @name METADATA
260                  * @event
261                  * @description important for statistic tracking (web trends) fires each time a new playable is encountered, or when a new item begins playing in a live stream
262                  * A {@link Playable} is always the data for each METADATA event
263                  * @example Events.type.METADATA
264                  * @fieldOf Events
265                  */
266                 METADATA : 'METADATA',
267                 /**
268                  * @name VOLUME_UPDATED
269                  * @event
270                  * @description fires when volume is updated
271                  * @example Events.type.VOLUME_UPDATED
272                  * @fieldOf Events
273                  */
274                 VOLUME_UPDATED : 'VOLUME_UPDATED'
275             };
276             /**
277              * @name handlers
278              * @description array of event handler objects in form of { 'handler_name', function() {} }.
279              * @type Array[Object]
280              * @fieldOf Events
281              */
282             this.handlers = [];
283         };
284         Events.prototype = {
285             /**
286              * @name trigger
287              * @description fires all events handlers that match 'name' and passes eventArgs to each handler.
288              * @param {string} name name of event to fire
289              * @param {Object} eventArgs object literal to pass to all functon handlers
290              *
291              * @example APMPlayer.events.trigger(player.events.type.MEDIA_READY, { 'identifier' : this.ID });
292              *
293              * @methodOf Events
294              */
295             trigger : function (name, eventArgs) {
296                 var i;
297                 for (i = 0; i < this.handlers.length; i += 1) {
298                     if (this.handlers[i].eventName === name) {
299                         this.handlers[i].eventHandler.call(this, eventArgs);
300                     }
301                 }
302             },
303             /**
304              * @name addListener
305              * @description adds an event listener
306              * @param {string} name the name of the event to listen for
307              * @param {Object} handler function to fire when event is called.
308              *
309              * @example APMPlayer.events.addListener(APMPlayer.events.type.PLAYER_READY, function() {});
310              * @methodOf Events
311              */
312             addListener : function (name, handler) {
313                 if (typeof (name) !== 'string' || typeof (handler) !== 'function') {
314                     Debug.log("Invalid parameters when creating listener with the following arguments: 'Name': " + name + ", 'Handler': " + handler, Debug.type.error);
315                 }
316                 this.handlers.push({ "eventName" : name, "eventHandler" : handler });
317             },
318             /**
319              * @name removeListeners
320              * @description clears out all listeners in this events objec
321              *
322              * @example APMPlayer.events.removeListeners();
323              * @methodOf Events
324              */
325             removeListeners : function () {
326                 this.handlers = [];
327             }
328         };
329 
330 
331 
332         /**
333          * @name PlaybackState
334          * @constructor
335          *
336          * @description holds all PlaybackStates (PLAYING, STOPPED, PAUSED);
337          *
338          * @property {Object} type all possible PlaybackState types (PLAYING, STOPPED, PAUSED);
339          */
340         var PlaybackState = function () {
341             this.type = {
342                 PLAYING : 'PLAYING',
343                 STOPPED : 'STOPPED',
344                 PAUSED : 'PAUSED'
345             };
346             this._current = this.type.STOPPED;  //default
347         };
348         PlaybackState.prototype = {
349             /**
350              * @name current
351              * @description gets current state (PLAYING, STOPPED, or PAUSED)
352              *
353              * @returns {string} state (PLAYING, STOPPED, or PAUSED)
354              * @public
355              * @methodOf PlaybackState
356              */
357             current : function () {
358                 return this._current;
359             },
360             /**
361              * @name set
362              *
363              * @description sets current state + sets state in playable, if playable is passed.
364              * @param state the state to set
365              * @param playable optional {@link Playable} to update copy of state.
366              *
367              * @public
368              * @methodOf PlaybackState
369              */
370             set : function (state, playable) {
371                 this._current = state;
372 
373                 if (typeof (playable) === 'object'
374                         && playable.hasOwnProperty('state')) {
375                     playable.state = this._current;
376                 }
377             }
378         };
379 
380         /**
381          * @name PlaybackMechanism
382          * @constructor
383          *
384          * @description holds an ordered-array of all possible playback mechanisms supported
385          *
386          * @property {Object} type all possible playback mechanism types (currently FLASH and HTML5)
387          * @property {Array[Object]} solutions array of playback mechanisms, ordered by priority to use as a solution.  Currently FLASH is the primary playback mechanism.  If the primary solution is deemed unacceptable for given platform, that first solution is removed and the next solution becomes first and primary.
388          */
389         var PlaybackMechanism = function () {
390             this.type = {
391                 FLASH : 'FLASH',
392                 HTML5 : 'HTML5'
393             };
394             this.solutions = [ this.type.FLASH, this.type.HTML5 ];  //defaults
395         };
396         PlaybackMechanism.prototype = {
397             /**
398              * @name getCurrentSolution
399              * @description returns current primary playback mechanism
400              *
401              * @public
402              * @returns {string|null} returns null of no playback mechanisms exist
403              * @methodOf PlaybackMechanism
404              */
405             getCurrentSolution : function () {
406                 if (this.solutions.length > 0) {
407                     return this.solutions[0];
408                 }
409                 return null;
410             },
411             /**
412              * @name removeCurrentSolution
413              * @description removes current primary playback solution
414              *
415              * @public
416              * @returns {boolean} success returns true for successful removal, false if nothing left to remove (no more solutions exist)
417              * @methodOf PlaybackMechanism
418              */
419             removeCurrentSolution : function () {
420                 if (this.solutions.length > 0) {
421                     this.solutions.shift();
422                     return true;
423                 }
424                 Debug.log('PlaybackMechanism.removeCurrentSolution() no playback solutions remain to remove!', Debug.type.error);
425                 return false;
426             },
427             /**
428              * @name setSolutions
429              * @description sets an array of playback solutions
430              *
431              * @public
432              * @returns {boolean} success
433              * @methodOf PlaybackMechanism
434              */
435             setSolutions : function (args) {
436                 if (args instanceof Array) {
437                     var valid_mechanisms = [];
438                     var mechanism;
439                     while (args.length > 0) {
440                         mechanism = args.shift();
441                         if (this.isValid(mechanism)) {
442                             valid_mechanisms.push(mechanism);
443                         } else {
444                             Debug.log('PlaybackMechanism.setSolutions() passed mechanism \'' + mechanism + '\' is invalid.', Debug.type.error);
445                         }
446                     }
447                     this.solutions = valid_mechanisms;
448                     return true;
449                 }
450 
451                 Debug.log('PlaybackMechanism.setSolutions() argument passed is not an array!', Debug.type.error);
452                 return false;
453             },
454             /**
455              * @name isValid
456              * @description returns whether passed mechanism is a valid PlaybackMechanism
457              * @param {string} mechanism the playback mechanism to validate
458              *
459              * @public
460              * @returns {boolean}
461              * @methodOf PlaybackMechanism
462              */
463             isValid : function (mechanism) {
464                 var key;
465                 for (key in this.type) {
466                     if (this.type[key] === mechanism) {
467                         return true;
468                     }
469                 }
470                 return false;
471             }
472         };
473 
474 
475         /**
476          * @name CustomSchemes
477          * @constructor
478          *
479          * @description allows the developer to create shortcuts or custom schemes for common patterns or common {@link Playable} objects in-use;
480          * NOTE: The main thing to understand about CustomSchemes is that any value set in the scheme_map definition will override anything passed
481          * in on {@link Playable} creation, if the identifier of the Playable happens to qualify as a registered CustomScheme.
482          */
483         var CustomSchemes = function () {
484             this.schemes = [];
485             this.scheme_map = {};
486             this.playable_attrs = [];
487         };
488         CustomSchemes.prototype = {
489             /**
490              * @name init
491              * @description initializes the map of custom schemes -- when a new {@link Playable} object is created, a look-up of the identifier on the CustomSchemes object will happen to determine if the Playable matches one of the CustomSchemes.  If so, all attributes defined in the scheme_map will override the {@link Playable}
492              * @param {Object} scheme_map object literal holding configuration, map of schemes-- each scheme can hold any number attributes that exist in {@link Playable} .. PLUS, the two below:
493              * @param {Object} scheme_map.flash_file_prefix prefix to be pre-pended to flash filepath when the {@link Playable} is instantiated.
494              * @param {Object} scheme_map.http_file_prefix prefix to be pre-pended to http filepath when the {@link Playable} is instantiated.
495              *
496              * @example
497              * var scheme_map = {
498              *    apm_audio : {
499              *      flash_server_url  : 'rtmp://flash.server.org/music',
500              *      flash_file_prefix : 'mp3:flashprefix',
501              *      http_file_prefix  : 'http://download.org',
502              *      buffer_time : 3,
503              *      type : 'audio'
504              *    },
505              *    live_audio : {
506              *          mpr_news : {
507              *              flash_server_url : 'rtmp://flash.server.org/news',
508              *              flash_file_path : 'news.stream',
509              *              http_file_path : 'http://newsstream1.publicradio.org:80/',
510              *              buffer_time : 6,
511              *              type : 'live_audio'
512              *          },
513              *          mpr_current : {
514              *              flash_server_url : 'rtmp://flash.server.org/kcmp',
515              *              flash_file_path : 'kcmp.stream',
516              *              http_file_path : 'http://currentstream1.publicradio.org:80/',
517              *              buffer_time : 6,
518              *              type : 'live_audio'
519              *          }
520              *     }
521              * };
522              * custom_schemes.init(scheme_map);
523              *
524              * ** in this example above,
525              * ** a Playable w/ identifer 'apm_audio:/marketplace/2012/04/18/morning_report.mp3'
526              * ** would translate to:
527              *
528              * var playable = {
529              *      identifier : 'apm_audio:/marketplace/2012/04/18/morning_report.mp3',
530              *      flash_server_url  : 'rtmp://flash.server.org/music',
531              *      flash_file_path : 'mp3:flashprefix/marketplace/2012/04/18/morning_report.mp3',
532              *      http_file_path  : 'http://download.org/marketplace/2012/04/18/morning_report.mp3',
533              *      buffer_time : 3,
534              *      type : 'audio',
535              *      // + all other attributes in a Playable, with defaults
536              * };
537              *
538              * while a Playable w/ identifer 'live_audio:/mpr_current' would translate to:
539              * var playable = {
540              *      identifier : 'live_audio:/mpr_current',
541              *      flash_server_url : 'rtmp://flash.server.org/kcmp',
542              *      flash_file_path : 'kcmp.stream',
543              *      http_file_path : 'http://currentstream1.publicradio.org:80/',
544              *      buffer_time : 6,
545              *      type : 'live_audio',
546              *      // + all other attributes in a Playable, with defaults
547              * };
548              * @public
549              * @methodOf CustomSchemes
550              */
551             init : function (scheme_map) {
552                 this.scheme_map = scheme_map;
553                 this.initSchemeTypes();
554                 this.initPlayableAttrs();
555             },
556             initSchemeTypes : function () {
557                 this.schemes = [];
558                 for (var type in this.scheme_map) {
559                     this.schemes.push(type);
560                 }
561             },
562             initPlayableAttrs : function () {
563                 this.playable_attrs = [];
564                 var playable = new Playable({});
565                 for(var propertyName in playable) {
566                     if(typeof playable[propertyName] !== 'function') {
567                         this.playable_attrs.push(propertyName);
568                     }
569                 }
570             },
571             hasSchemes : function () {
572                 if (this.schemes.length > 0) {
573                     return true;
574                 }
575                 return false;
576             },
577             isValid : function (scheme) {
578                 if (this.schemes.indexOf(scheme) !== -1) {
579                     return true;
580                 }
581                 return false;
582             },
583             isScheme : function (identifier, scheme) {
584                 var result = this.parse(identifier);
585                 if (result !== null
586                         && result.scheme === scheme) {
587                     return true;
588                 }
589                 return false;
590             },
591             parse : function (identifier) {
592                 var pattern = '^(' + this.schemes.join('|') + '){1}(:/){1}([/\\w.-]+$)';
593                 var regex =  new RegExp(pattern);
594                 var result = identifier.match(regex);
595 
596                 if (result !== null && result.length === 4) {
597                     return {
598                         scheme : result[1],
599                         path  : result[3]
600                     };
601                 }
602                 return null;
603             },
604             getValues : function (identifier) {
605                 var values = {};
606 
607                 var result = this.parse(identifier);
608                 if (result !== null
609                         && this.isValid(result.scheme)) {
610                     var type_map = this.scheme_map[result.scheme];
611 
612                     //check for aliases (next level down)
613                     if (type_map.hasOwnProperty(result.path)) {
614                         type_map = type_map[result.path];
615                     }
616 
617                     for (var prop in type_map) {
618                         if (this.playable_attrs.indexOf(prop) !== -1) {
619                             values[prop] = type_map[prop];
620                         } else if (prop === 'flash_file_prefix') {
621                             values.flash_file_path = type_map[prop] + '/' + result.path;
622                         } else if (prop === 'http_file_prefix') {
623                             values.http_file_path = type_map[prop] + '/' + result.path;
624                         }
625                     }
626                 }
627                 return values;
628             }
629         };
630         var custom_schemes = new CustomSchemes();
631 
632 
633         /**
634          * @name Playable
635          * @class
636          *
637          * @description holds all specific information about each media item to be played in APMPlayer -- To play audio/video in APMPlayer, the creation of a valid Playable is first required.
638          * to create a playable, see {@link APMPlayerFactory.getPlayable}.  Also holds complete metadata for a single playable.
639          * @param {Object} params object literal used to create a Playable object -- each of the passed object's attributes needs to match a particular item in Field Detail to be included w/ the Playable.
640          * note that the indentifier is the only required field for APM-specific audio.. However for basic playables (non-APM), type is also required.
641          * @property {string} identifier [REQUIRED] the ID for the playable (must be unique when creating a playlist).  Note that this is really the only required field when creating an APM-specific Playable.
642          * @property {string} type [REQUIRED] either 'audio' or 'live_audio' -- Note that this is typically set automatically when using a {@link CustomScheme}
643          * @property {string} flash_server_url server url to connect to to stream flash (eg  rtmp://archivemedia.publicradio.org/)
644          * @property {string} flash_file_path file to play over flash_server_url (eg  'mp3:filename_64.mp3')
645          * @property {string} http_file_path used for HTML5 audio playback (eg  http://ondemand.publicradio.org/filename.mp3) -OR- progressive download pseudo HTML5 playback via flash.
646          * @property {string} title optional metadata field.
647          * @property {string} description optional metadata field.
648          * @property {string} detail optional metadata field.
649          * @property {string} program optional metadata field.
650          * @property {string} host optional metadata field.
651          * @property {string} date optional metadata field.
652          * @property {string} image_sm image associated w/ playable (eg used to display in info window when playable plays) -- a small and large were necessary to deal w/ re-sizing issues in IE 7,8
653          * @property {string} image_lg image associated w/ playable (eg used to display in info window when playable plays)
654          * @property {boolean} downloadable (default true) flag to permit the file to be downloaded.  Shows download option and/or presents the file for download if no suitable {@link PlaybackMechanism} is present.
655          * @property {number} buffer_time amount of time (in seconds) the player should buffer -- default = 3.
656          * @property {number} duration the length, in miliseconds of this Playable
657          * @property {number} position the current position, in miliseconds for the Playable
658          * @property {number} percent_played percentage of the playable that's been played (position/duration)
659          * @property {number} percent_loaded percentage of the playable that's been loaded (percent of total duration)
660          *
661          */
662         var Playable = function (params) {
663             var playable_self = this;
664 
665             this.identifier = null;
666             this.type = null;
667 
668             //playback config
669             this.flash_file_path = '';
670             this.flash_server_url = '';
671             this.http_file_path = '';
672             this.buffer_time = 3;
673             this.downloadable = true;
674 
675             //metadata
676             this.title = '';
677             this.description = '';
678             this.detail = '';
679             this.date = '';
680             this.program = '';
681             this.host = '';
682 
683             //images
684             this.image_sm = '';
685             this.image_lg = '';
686 
687             //playback statii
688             this.duration = 0;
689             this.position = 0;
690             this.percent_played = 0;
691             this.percent_loaded = 0;
692 
693             //state
694             this.state = 'STOPPED';   //provided as courtesy to webTrends tracking.
695 
696             //identifier is required.
697             //return empty object if not passed
698             if (typeof params.identifier === 'undefined'
699                     || params.identifier === null
700                     || params.identifier === '') {
701                 return playable_self;
702             }
703 
704             (function () {  //set members
705 
706                 //first, set any values passed in via the playable.
707                 playable_self.setMembers(params);
708 
709                 //second, set everything that exists in custom scheme.
710                 //note that customScheme trumps all default data and strictly uses each scheme.
711                 if (custom_schemes.hasSchemes()) {
712                     var configParams = custom_schemes.getValues(playable_self.identifier);
713                     playable_self.setMembers(configParams);
714                 }
715 
716             }());
717         };
718         Playable.prototype = {
719             /**
720              * @name isValid
721              *
722              * @description a Playable isValid if it has an identifier and its type matches one of the valid MediaTypes (either 'audio' or 'live_audio')
723              * @example playable.isValid()
724              * @returns {boolean} true or false
725              * @methodOf Playable
726              */
727             isValid : function () {
728                 if (this.identifier !== null
729                         && this.type !== null
730                         && MediaTypes.isValid(this.type)) {
731                     return true;
732                 }
733                 return false;
734             },
735             /**
736              * @name isCustomScheme
737              *
738              * @description a Playable isCustomScheme if it has an identifier that matches one of the custom schemes defined
739              * @example playable.isCustomScheme('scheme_name')
740              * @returns {boolean} true or false
741              * @methodOf Playable
742              */
743             isCustomScheme : function (scheme) {
744                 if (custom_schemes.isScheme(this.identifier, scheme) === true) {
745                     return true;
746                 }
747                 return false;
748             },
749             /**
750              * @name isEOF
751              *
752              * @description a Playable is at the end of the file, return true.
753              * @example playable.isEOF()
754              * @returns {boolean} true or false
755              * @methodOf Playable
756              */
757             isEOF : function () {
758                 if (this.percent_played > 0.99995) {
759                     return true;
760                 }
761                 return false;
762             },
763             /**
764              * @name reset
765              *
766              * @description resets playable's position, percent_played, and percent_loaded back to 0
767              * @example playable.reset()
768              * @methodOf Playable
769              */
770             reset : function () {
771                 this.position = 0;
772                 this.percent_played = 0;
773                 this.percent_loaded = 0;
774             },
775             /**
776              * @name setEmptyMembers
777              *
778              * @description finds and sets any provided members that are either '', 0, or null -- nothing will be set if passed params are empty or null
779              * @example playable.setEmptyMembers({object})
780              * @methodOf Playable
781              */
782             setEmptyMembers : function (params) {
783                 var prop;
784                 for (prop in params) {
785                     if(params.hasOwnProperty(prop)
786                         && this.hasOwnProperty(prop)
787                         && params[prop] !== null
788                         && params[prop] !== ''
789                         && (this[prop] === null
790                             || this[prop] === ''
791                             || this[prop] === 0)) {
792 
793                         this[prop] = params[prop];
794                     }
795                 }
796             },
797             /**
798              * @name setMembers
799              *
800              * @description finds and sets any valid key, value pair-- will override anything that currently exists in that field.
801              * @example playable.setMembers({object})
802              * @methodOf Playable
803              */
804             setMembers : function (params) {
805                 for (var prop in params) {
806                     if (params.hasOwnProperty(prop)
807                             && this.hasOwnProperty(prop)) {
808                         this[prop] = params[prop];
809                     }
810                 }
811             },
812             /**
813              * @name clearFlashProperties
814              *
815              * @description removes the flash_server_url + flash_file_path
816              * so that only the progressive http mechanism is possible.
817              * @example playable.clearFlashProperties()
818              * @methodOf Playable
819              */
820             clearFlashProperties : function () {
821                 this.flash_server_url = '';
822                 this.flash_file_path = '';
823             },
824             /**
825              * @name isFlashStreamable
826              *
827              * @description returns whether or not flash_server_url + flash_file_path is avail.  used to allow/disallow seeking before item loads.
828              * @returns {boolean} true or false
829              * @example playable.isFlashStreamable()
830              * @methodOf Playable
831              */
832             isFlashStreamable : function () {
833                 if (this.flash_server_url !== ''
834                         && this.flash_file_path !== '') {
835                     return true;
836                 }
837                 return false;
838             }
839         };
840 
841 
842         /**
843          * @name APMPlayer
844          * @description main container for Audio/Video playback.
845          * @class
846          */
847         var APMPlayer = function () {
848             var player = this;
849 
850             var Audio = function () {
851                 this.lib = soundManager;
852                 this.init_status = false;
853             };
854             Audio.prototype = {
855                 init : function () {
856                     if (player.audio.init_status === false) {
857 
858                         //soundManager2 settings
859                         this.lib.flashVersion = 9;
860                         this.lib.preferFlash = true;
861                         this.lib.useHTML5Audio = true;
862                         this.lib.consoleOnly = Debug.consoleOnly;
863                         this.lib.debugMode = Debug.enabled;
864                         this.lib.flashPollingInterval = 150;  //helps improve seeker ui experience
865                         this.lib.url = player.util.getLoadedScriptPathByFileName('soundmanager2') + 'swf/';  // used to dynamically determine lib location for flash lib dependency
866 
867                         this.lib.onready(function () {
868                             if (player.audio.lib.html5Only === true) {  //SM2 will know onready() if HTML5 is the only option avail.
869                                 player.mechanism.setSolutions([ player.mechanism.type.HTML5 ]);
870                                 Debug.log('Audio.init() -- setting to HTML5-only', Debug.type.info);
871                             }
872                             Debug.log('Audio.init() success', Debug.type.info);
873                             player.audio.init_status = true;
874                             player.internal_events.trigger(player.events.type.AUDIO_LIB_READY, {});
875                         });
876                         this.lib.ontimeout(function (status) {           //(note: ontimeout typically only happens w/ flash-block, or flash not installed)
877                             if(!player.audio.lib.canPlayMIME('audio/mp3')
878                                     && !player.audio.lib.canPlayMIME('audio/mpeg')) {
879                                 player.mechanism.setSolutions([]);       //no possible playback solutions if no support for mp3 in HTML5 (eg Firefox).
880                             } else {
881                                 player.mechanism.setSolutions([ player.mechanism.type.HTML5 ]);
882                             }
883                             player.audio.reset();
884                         });
885                     } else {
886                         player.audio.reset();
887                         Debug.log('Audio.init() -- audio lib has already been initialized once, attempting reset', Debug.type.info);
888                     }
889                 },
890                 reset : function () {
891                     player.audio.init_status = false;
892 
893                     var current_solution = player.mechanism.getCurrentSolution();
894                     switch (current_solution) {
895                     case player.mechanism.type.FLASH:
896                         player.audio.lib.preferFlash = true;
897                         player.audio.lib.html5Only = false;
898                         break;
899                     case player.mechanism.type.HTML5:
900                         player.audio.lib.preferFlash = false;
901                         player.audio.lib.html5Only = true;
902                         break;
903                     default:
904                         Debug.log('Audio.reset() no playback solution exists.', Debug.type.error);
905                         player.events.trigger(player.events.type.PLAYER_FAILURE, null);
906                         return false;
907                     }
908 
909                     player.audio.lib.reboot();
910                 },
911                 load : function (playable) {
912                     if (this.init_status === true) {
913                         try {
914                             var sound;
915                             switch (player.mechanism.getCurrentSolution()) {
916                             case player.mechanism.type.FLASH:
917 
918                                 //1. create basic http-based sound first (http path, no serverURL)
919                                 sound = {
920                                     id: playable.identifier,
921                                     url: playable.http_file_path,
922                                     bufferTime: playable.buffer_time,
923                                     onconnect: function () {
924                                         Debug.log('Audio.load.lib.createSound.onConnect() - successfully connected over RTMP (' + playable.flash_server_url + ')', Debug.type.info);
925                                         player.internal_events.trigger(player.events.type.MEDIA_READY, playable);
926                                     },
927                                     onfailure : function (sound) {
928                                         var playable = player.current_playable;
929                                         if (playable.position > 0) {
930                                             Debug.log('Audio.load.createSound.onfailure() -- network connection has been lost', Debug.type.info);
931                                             player.state.set(player.state.type.STOPPED, playable);
932                                             player.events.trigger(player.events.type.CONNECTION_LOST, playable);
933                                         } else if (sound.connected === true) {
934                                             Debug.log('Audio.load.createSound.onfailure() - requested file \'' + playable.flash_file_path + '\' w/ identifier \'' + playable.identifier + '\' could not be found in flash/RTMP mode.', Debug.type.error);
935                                             player.state.set(player.state.type.STOPPED, playable);
936                                             player.events.trigger(player.events.type.MISSING_FILE, playable);
937                                             player.events.trigger(player.events.type.FINISHED, playable);
938                                         } else {
939                                             //in this case, there was an issue connecting to our flash wowza server. as a back-up, falls-back to using HTML5-mode
940                                             Debug.log('Audio.load.createSound.onfailure() - could not connect to \'' + playable.flash_server_url + '\' ... falling back to HTML5-mode.', Debug.type.error);
941                                             player.state.set(player.state.type.STOPPED, playable);
942                                             player.mechanism.removeCurrentSolution();
943                                             player.audio.reset();
944                                         }
945                                     },
946                                     onload : function(success) {
947                                         if(success === false) {   //progressive download file failed to be loaded..
948                                             var playable = player.current_playable;
949                                             player.events.trigger(player.events.type.MISSING_FILE, playable);
950                                             Debug.log('Audio.load.createSound.onload() - could not load \'' + playable.http_file_path + '\' over progressive download', Debug.type.error);
951                                         }
952                                     }
953                                 };
954 
955                                 //2. if flash server + file is present, stream it from flash server by
956                                 //overriding these settings before sound invocation
957                                 if (playable.flash_file_path !== null && playable.flash_file_path !== ''
958                                         && playable.flash_server_url !== null && playable.flash_server_url !== '') {
959                                     sound.serverURL = playable.flash_server_url;
960                                     sound.url = playable.flash_file_path;
961                                 }
962 
963                                 this.lib.createSound(sound);
964                                 break;
965 
966                             case player.mechanism.type.HTML5:
967 
968                                 //1. html5: much simpler
969                                 sound = this.lib.createSound({
970                                     id: playable.identifier,
971                                     url: playable.http_file_path,
972                                     onload : function (success) {
973                                         var playable = player.current_playable;
974 
975                                         if (!success) {
976                                             player.state.set(player.state.type.STOPPED, playable);
977                                             player.events.trigger(player.events.type.MISSING_FILE, playable);
978                                             player.events.trigger(player.events.type.FINISHED, playable);
979                                             Debug.log('Audio.load.createSound.onload():  requested file \'' + playable.http_file_path + '\' w/ identifier \'' + playable.identifier + '\' could not be found in HTML5 mode.', Debug.type.error);
980                                         } else {
981                                             if (this.duration) {
982                                                 playable.duration = this.duration;
983                                                 Debug.log('Audio.load.createSound.onload(): duration found after start', Debug.type.info);
984                                             } else {
985                                                 Debug.log('Audio.load.createSound.onload(): duration unknown', Debug.type.info);
986                                             }
987                                         }
988                                     }
989                                 });
990 
991                                 player.internal_events.trigger(player.events.type.MEDIA_READY, playable);
992                                 break;
993 
994                             default:
995                                 Debug.log('Audio.load() no playback solution exists.', Debug.type.error);
996                                 player.events.trigger(player.events.type.PLAYER_FAILURE, playable);
997                                 break;
998                             }
999 
1000                         } catch (e) {
1001                             Debug.log('Exception thrown in APMPlayer.Audio.load : ' + e.toString(), Debug.type.error);
1002                         }
1003                     } else {
1004                         if(player.mechanism.getCurrentSolution() === null) {
1005                             player.events.trigger(player.events.type.PLAYER_FAILURE, playable);
1006                         } else {
1007                             Debug.log('Audio.lib.load - audio lib not initialized.  load() will be called again when player is finally initialized.', Debug.type.info);
1008                         }
1009                     }
1010                 },
1011                 unload : function (playable) {
1012                     if (player.current_playable !== null) {
1013                         Debug.log('Audio.unload() about to stop, drop and roll current sound.', Debug.type.info);
1014                         this.lib.destroySound(playable.identifier);
1015                         playable.reset();
1016                         player.state.set(player.state.type.STOPPED, playable);
1017                         player.events.trigger(player.events.type.UNLOADED, playable);
1018                     }
1019                 },
1020                 play : function (playable) {
1021                     if (!this.lib.getSoundById(playable.identifier)) {
1022                         this.load(playable);
1023                     } else {
1024                         Debug.log('Audio.play() attempting to play from lib.', Debug.type.info);
1025 
1026                         this.lib.play(playable.identifier, {
1027                             volume : (player.settings.volume * 100),
1028                             position : playable.position,
1029                             onplay : function () {
1030                                 player.state.set(player.state.type.PLAYING, playable);
1031                                 player.events.trigger(player.events.type.PLAYING, playable);
1032                                 player.events.trigger(player.events.type.METADATA, playable);
1033                                 if (player.settings.muted) {
1034                                     this.mute();
1035                                 }
1036                                 Debug.log('Audio.play.onplay() PLAYING fired', Debug.type.info);
1037                             },
1038                             onpause: function () {
1039                                 player.state.set(player.state.type.PAUSED, playable);
1040                                 player.events.trigger(player.events.type.PAUSED, playable);
1041                                 Debug.log('Audio.play.onpause() PAUSED fired', Debug.type.info);
1042                             },
1043                             onresume : function () {
1044                                 player.state.set(player.state.type.PLAYING, playable);
1045                                 player.events.trigger(player.events.type.PLAYING, playable);
1046                                 Debug.log('Audio.play.onresume() PLAYING fired', Debug.type.info);
1047                             },
1048                             onfinish : function () {
1049                                 player.current_playable.reset();
1050                                 player.state.set(player.state.type.STOPPED, playable);
1051                                 player.events.trigger(player.events.type.FINISHED, playable);
1052                                 Debug.log('Audio.play.onfinish() FINISHED fired; playable reset.', Debug.type.info);
1053                             },
1054                             onbufferchange : function () {
1055                                 if (this.isBuffering === true) {
1056                                     player.events.trigger(player.events.type.BUFFER_START, playable);
1057                                     Debug.log('Audio.play.onbufferchange() BUFFER_START fired ', Debug.type.info);
1058                                 } else {
1059                                     player.events.trigger(player.events.type.BUFFER_END, playable);
1060                                     Debug.log('Audio.play.onbufferchange() BUFFER_END fired ', Debug.type.info);
1061                                 }
1062                             },
1063                             whileplaying : function () {
1064 
1065                                 var playable = player.current_playable;
1066                                 if (playable.type === MediaTypes.type.LIVE_AUDIO) {
1067                                     playable.percent_played = 1;
1068                                     playable.duration = 0;
1069                                     playable.position = this.position;
1070 
1071                                     player.events.trigger(player.events.type.POSITION_UPDATE, playable);
1072 
1073                                 } else {  //STATIC AUDIO
1074                                     if (this.position !== 0) {      //SM2 returns position 0 once after restarting a piece in middle, skip this if/when it happens.
1075                                         playable.position = this.position;
1076                                     }
1077 
1078                                     if (this.duration !== 0
1079                                             && this.duration > playable.duration) {  //helps w/ HTTP progressive download and duration display.
1080                                         playable.duration = this.duration;
1081                                     }
1082 
1083                                     if (this.durationEstimate > this.duration) {
1084                                         //loading over HTTP progressive download, add percent loaded.
1085                                         playable.percent_loaded = this.duration / this.durationEstimate;
1086                                     } else if (playable.percent_loaded > 0 && playable.percent_loaded < 1) {
1087                                         playable.percent_loaded = 1;
1088                                     }
1089 
1090                                     if (playable.duration > 0) {
1091                                         playable.percent_played = playable.position / playable.duration;
1092                                     }
1093 
1094                                     //SM2 'work-around'-- the onfinish() event does not always fire if scrubbing to end.
1095                                     //this catches that scenario
1096                                     if (playable.isEOF()) {
1097                                         playable.percent_played = 1;
1098                                         playable.position = playable.duration;
1099                                         //player.events.trigger(player.events.type.FINISHED, playable);  5/21/12 -- aml removed this due to double-advances in iOS.
1100                                                                                                        //onfinish() now appears to fire normally in all cases.
1101                                         //Debug.log('Audio.play.whileplaying() FINISHED fired. ' + playable.title, Debug.type.info);
1102                                     } else {
1103                                         //Debug.log('Audio.play.whileplaying() POSITION_UPDATE fired. ' + playable.percent_loaded + '% ' + playable.percent_played, Debug.type.info);
1104                                         player.events.trigger(player.events.type.POSITION_UPDATE, playable);
1105                                     }
1106                                 }
1107                             },
1108                             onmetadata : function () {
1109                                 if (this.hasOwnProperty('metadata')) {
1110                                     if (this.metadata.hasOwnProperty('adw_ad')  //ads wizz add
1111                                             && this.metadata.adw_ad === 'true'
1112                                             && this.metadata.hasOwnProperty('metadata')
1113                                             && this.metadata.metadata.indexOf("adswizzContext") !== -1) {
1114 
1115                                         Debug.log('onmetadata() received adw_ad of insertionType: \'' + this.metadata.insertionType + '\'', Debug.type.info);
1116 
1117                                         playable.title = 'adw_ad_' + this.metadata.insertionType;
1118                                         playable.adw_context = this.metadata.metadata.substr(15);  //hack via AdsWizz. need to request a better solution
1119                                         player.events.trigger(player.events.type.METADATA, playable);
1120                                     }
1121                                     else if (this.metadata.hasOwnProperty('StreamTitle')  //APM-specific
1122                                             && typeof this.metadata.StreamTitle !== 'undefined') {
1123                                         Debug.log('onmetadata() received metadata w/ title: \'' + this.metadata.StreamTitle + '\'', Debug.type.info);
1124                                         playable.title = this.metadata.StreamTitle;
1125                                         player.events.trigger(player.events.type.METADATA, playable);
1126                                     }
1127                                 }
1128                             }
1129                         });
1130                     }
1131                 },
1132 
1133                 pause : function (playable) {
1134                     var sound = this.lib.getSoundById(playable.identifier);
1135                     if (sound) {
1136                         sound.pause();
1137                         return true;
1138                     }
1139                     Debug.log('Audio.pause() Error.  Could not pause.  \'' + playable.identifier + '\' is unknown.', Debug.type.warn);
1140                     return false;
1141                 },
1142 
1143                 unpause : function (playable) {
1144                     var sound = this.lib.getSoundById(playable.identifier);
1145                     if (sound && sound.paused === true) {
1146                         sound.resume();
1147                         return true;
1148                     }
1149                     Debug.log('Audio.unpause() Error.  Could not unpause.  \'' + playable.identifier + '\' is unknown.', Debug.type.warn);
1150                     return false;
1151                 },
1152 
1153                 seek : function (playable, percent_decimal) {
1154                     var sound = this.lib.getSoundById(playable.identifier);
1155                     if (sound) {
1156                         if (playable.duration) {
1157                             Debug.log('Audio.seek() seeking to \'' + percent_decimal + '\' of sound \'' + playable.identifier + '\'', Debug.type.info);
1158                             var msec = percent_decimal * playable.duration;
1159                             sound.setPosition(msec);
1160                             return true;
1161                         }
1162                         Debug.log('Audio.seek() Error.  Could not seek. duration of \'' + playable.identifier + '\' is unknown.', Debug.type.warn);
1163                         return false;
1164                     }
1165 
1166                     Debug.log('Audio.seek() sound \'' + playable.identifier + '\' is unknown.', Debug.type.warn);
1167                     return false;
1168                 },
1169 
1170                 mute : function (playable) {
1171                     var sound = this.lib.getSoundById(playable.identifier);
1172                     if (sound) {
1173                         sound.mute();
1174                     }
1175                 },
1176 
1177                 unmute : function (playable) {
1178                     var sound = this.lib.getSoundById(playable.identifier);
1179                     if (sound) {
1180                         sound.unmute();
1181                     }
1182                 },
1183 
1184                 setVolume :  function (playable, percent_decimal) {
1185                     var percent = percent_decimal * 100;
1186                     var sound = this.lib.getSoundById(playable.identifier);
1187                     if (sound) {
1188                         Debug.log('Audio.setVolume() setting volume to ' + percent + '% (out of 100)', Debug.type.info);
1189                         sound.setVolume(percent);
1190                     } else {
1191                         Debug.log('Audio.setVolume() sound is not loaded.  volume will be set to ' + percent + '% once audio begins playing', Debug.type.info);
1192                     }
1193                 }
1194 
1195             }; //end Audio()
1196 
1197             //var Video = function () {};
1198 
1199             //default settings
1200             this.settings = {
1201                 volume : 0.9,
1202                 muted : false,
1203                 debug : false
1204             };
1205 
1206             this.current_playable = null;       //only one single playable can be in use at a given time
1207 
1208             this.events = new Events();
1209             this.internal_events = new Events();
1210             this.mechanism = new PlaybackMechanism();
1211             this.state = new PlaybackState();
1212             this.audio = new Audio();
1213             this.internal_event_handlers = {
1214                 checkReady : function () {
1215                     if (player.audio.init_status === true) {
1216                         Debug.log('checkReady() player ready.  all dependencies loaded.', Debug.type.info);
1217                         player.events.trigger(player.events.type.PLAYER_READY, {});
1218                     } else {
1219                         //will be useful when multiple dependencies are required on init
1220                         Debug.log('checkReady() not quite ready -- waiting for other dependencies to load...', Debug.type.info);
1221                     }
1222                 },
1223                 onMediaReady : function (playable) {
1224                     player.audio.play(playable);
1225 
1226                 }
1227             };
1228             this.internal_events.addListener(player.events.type.AUDIO_LIB_READY, player.internal_event_handlers.checkReady);
1229             this.internal_events.addListener(player.events.type.MEDIA_READY, player.internal_event_handlers.onMediaReady);
1230 
1231             this.util = {
1232                 getParameterByName : function (name) {
1233                     name = name.replace(/[\[]/, "\\[").replace(/[\]]/, "\\]");
1234                     var regexS = "[\\?&]" + name + "=([^&#]*)";
1235                     var regex = new RegExp(regexS);
1236                     var results = regex.exec(window.location.href);
1237 
1238                     if (results === null) {
1239                         return "";
1240                     }
1241 
1242                     return decodeURIComponent(results[1].replace(/\+/g, " "));
1243                 },
1244                 getLoadedScriptPathByFileName : function (name) {
1245                     var scripts = document.getElementsByTagName("script");
1246                     var numScripts = scripts.length;
1247                     var index;
1248                     for (index = 0; index < numScripts; index += 1) {
1249                         var match = scripts[index].src.indexOf(name, 0);
1250                         if (match !== -1) {
1251                             return scripts[index].src.slice(0, match);
1252                         }
1253                     }
1254                 },
1255                 getProjectBasePath : function () {
1256                     var path = player.util.getLoadedScriptPathByFileName('script/apmplayer-all.min.js');
1257                     if (typeof path === 'undefined') {
1258                         path = player.util.getLoadedScriptPathByFileName('script/apmplayer.js');
1259                     }
1260                     return path;
1261                 },
1262                 mergeSettings : function (settings) {
1263                     var prop;
1264                     for (prop in settings) {
1265                         if (settings.hasOwnProperty(prop)
1266                                 && player.settings.hasOwnProperty(prop)) {
1267                             player.settings[prop] = settings[prop];
1268                         }
1269                     }
1270                 },
1271                 checkDebug : function () {
1272                     if (player.util.getParameterByName('debug')) {
1273                         Debug.enabled = true;
1274                     }
1275 
1276                     if (player.util.getParameterByName('debug') === 'all') {
1277                         Debug.consoleOnly = false;
1278                     }
1279                 },
1280                 addIEFixes : function () {
1281                     if (!Array.prototype.indexOf) {
1282                         Array.prototype.indexOf = function(elt /*, from*/) {
1283                             var len = this.length >>> 0;
1284 
1285                             var from = Number(arguments[1]) || 0;
1286                             from = (from < 0)
1287                                  ? Math.ceil(from)
1288                                  : Math.floor(from);
1289                             if (from < 0)
1290                               from += len;
1291 
1292                             for (; from < len; from++) {
1293                               if (from in this &&
1294                                   this[from] === elt)
1295                                 return from;
1296                             }
1297                             return -1;
1298                             };
1299                      }
1300                 }
1301             };
1302 
1303             return {  //APMPlayer public methods
1304                 /**
1305                  * @name init
1306                  * @description initializes APMPlayer library;  NOTE: init() must be called immediately after instantiation to ensure SoundManager2 init doesn't timeout
1307                  * @methodOf APMPlayer
1308                  */
1309                 init : function () {
1310                     player.util.addIEFixes();
1311                     player.util.checkDebug();
1312                     player.audio.init();
1313                 },
1314                 /**
1315                  * @name reset
1316                  * @description resets APMPlayer -- sets {@link PlaybackMechanism} solutions + resets Audio playback library
1317                  *
1318                  * @param {string[]} playback_solutions array of accepted playback mechanisms to use (ie FLASH, HTML5)
1319                  *
1320                  * @methodOf APMPlayer
1321                  */
1322                 reset : function (playback_solutions) {
1323                     if (playback_solutions instanceof Array) {
1324                         player.mechanism.setSolutions(playback_solutions);
1325                     }
1326                     player.audio.reset();
1327                 },
1328                 /**
1329                  * @name play
1330                  * @description loads item (if not loaded), then plays it--  automatically unloads any previous {@link Playable} that was loaded/playing
1331                  * @fires Events.type.PLAYING will fire when audio successfully starts playing. (see {@link Events}).
1332                  * @methodOf APMPlayer
1333                  */
1334                 play : function (playable, settings) {
1335                     if (playable instanceof Playable) {
1336                         if (playable === player.current_playable
1337                                 && player.state.current() !== player.state.type.PLAYING) {
1338                             switch (playable.type) {
1339                             case MediaTypes.type.AUDIO:
1340                             case MediaTypes.type.LIVE_AUDIO:
1341                                 player.audio.play(playable);
1342                                 break;
1343                             case MediaTypes.type.VIDEO:
1344                                 break;
1345                             default:
1346                                 Debug.log('play() unsupported type: \'' + playable.type + '\'', Debug.type.error);
1347                             }
1348                         } else if (playable !== player.current_playable) {
1349 
1350                             //1. check if playback solutions exist.
1351                             if(player.mechanism.getCurrentSolution() === null) {
1352                                 Debug.log('play()  insufficient playback mechanism for platform.  Triggered PLAYER_FAILURE', Debug.type.error);
1353                                 player.events.trigger(player.events.type.PLAYER_FAILURE, playable);
1354                                 return false;
1355                             }
1356 
1357                             //2. unload item if a current one exists.
1358                             if (player.current_playable !== null) {
1359                                 player.audio.unload(player.current_playable);
1360                             }
1361 
1362                             player.util.mergeSettings(settings);
1363                             player.current_playable = playable;
1364 
1365                             switch (playable.type) {
1366                             case MediaTypes.type.AUDIO:
1367                             case MediaTypes.type.LIVE_AUDIO:
1368                                 player.audio.load(playable);
1369                                 break;
1370                             case MediaTypes.type.VIDEO:
1371                                 break;
1372                             default:
1373                                 Debug.log('load() unsupported type: \'' + playable.type + '\'', Debug.type.error);
1374                                 break;
1375                             }
1376                         }
1377                     } else {
1378                         Debug.log('play() invalid playable passed.  must of of type Playable.  did nothing.', Debug.type.error);
1379                         return false;
1380                     }
1381                 },
1382                 /**
1383                  * @name pause
1384                  * @description if static audio, pauses currently loaded {@link Playable}; However, if type LIVE_AUDIO, the playable is automatically unloaded.
1385                  * @fires Events.type.PAUSED, or Events.type.UNLOADED will fire depending on type, on success. (see {@link Events}).
1386                  * @methodOf APMPlayer
1387                  */
1388                 pause : function () {
1389                     var playable = player.current_playable;
1390                     if (playable !== null) {
1391                         switch (playable.type) {
1392                         case MediaTypes.type.AUDIO:
1393                             player.audio.pause(playable);
1394                             break;
1395                         case MediaTypes.type.LIVE_AUDIO:
1396                             player.audio.unload(playable);
1397                             break;
1398                         case MediaTypes.type.VIDEO:
1399                             break;
1400                         default:
1401                             Debug.log('pause() unsupported type: \'' + playable.type + '\'', Debug.type.error);
1402                         }
1403                     } else {
1404                         Debug.log('pause() no current playable loaded.  nothing to pause.', Debug.type.warn);
1405                     }
1406                 },
1407                 /**
1408                  * @name unload
1409                  * @description stops, unloads, and destroys the current {@link Playable}
1410                  * @fires Events.type.UNLOADED will be fired if audio successfully unloads (see {@link Events}).
1411                  * @methodOf APMPlayer
1412                  */
1413                 unload : function () {
1414                     var playable = player.current_playable;
1415                     if (playable !== null) {
1416                         switch (playable.type) {
1417                         case MediaTypes.type.AUDIO:
1418                         case MediaTypes.type.LIVE_AUDIO:
1419                             player.audio.unload(playable);
1420                             break;
1421                         case MediaTypes.type.VIDEO:
1422                             break;
1423                         default:
1424                             Debug.log('unload() unsupported type: \'' + playable.type + '\'', Debug.type.error);
1425                         }
1426                     } else {
1427                         Debug.log('unload() no current playable loaded.  nothing to stop/unload.', Debug.type.info);
1428                     }
1429                 },
1430                 /**
1431                  * @name seek
1432                  * @description moves play head to current percentage of media {@link Playable}.
1433                  * @fires Events.type.POSITION_UPDATE will continue to fire after position is updated by seek() (see {@link Events}).
1434                  * @param {number} percent_decimal the point in the media file to seek to.  (eg, 0 is beginning / .50 is half-way / 1 is end)
1435                  * @methodOf APMPlayer
1436                  */
1437                 seek : function (percent_decimal) {
1438                     var playable = player.current_playable;
1439                     if (playable !== null) {
1440                         switch (playable.type) {
1441                         case MediaTypes.type.AUDIO:
1442                             player.audio.seek(playable, percent_decimal);
1443                             break;
1444                         case MediaTypes.type.LIVE_AUDIO:
1445                             Debug.log('seek() sorry, this item is not seekable \'' + playable.identifier + '\', type: \'' + playable.type + '\'', Debug.type.info);
1446                             break;
1447                         case MediaTypes.type.VIDEO:
1448                             break;
1449                         default:
1450                             Debug.log('seek() unsupported type: \'' + playable.type + '\'', Debug.type.error);
1451                         }
1452                     } else {
1453                         Debug.log('seek() no current playable loaded.  nothing to seek.', Debug.type.info);
1454                     }
1455                 },
1456                 /**
1457                  * @name mute
1458                  * @description mutes sound; returns nothing, triggers nothing.
1459                  * @methodOf APMPlayer
1460                  */
1461                 mute : function () {
1462                     player.settings.muted = true;
1463 
1464                     var playable = player.current_playable;
1465                     if (playable !== null) {
1466                         switch (playable.type) {
1467                         case MediaTypes.type.AUDIO:
1468                         case MediaTypes.type.LIVE_AUDIO:
1469                             player.audio.mute(playable);
1470                             break;
1471                         case MediaTypes.type.VIDEO:
1472                             break;
1473                         default:
1474                             Debug.log('mute() unsupported type: \'' + playable.type + '\'', Debug.type.error);
1475                             break;
1476                         }
1477                     }
1478                     Debug.log('mute() -- player is now muted.', Debug.type.info);
1479                 },
1480                /**
1481                  * @name unmute
1482                  * @description unmutes sound;  returns nothing, triggers nothing.
1483                  * @methodOf APMPlayer
1484                  */
1485                 unmute : function () {
1486                     player.settings.muted = false;
1487 
1488                     var playable = player.current_playable;
1489                     if (playable !== null) {
1490                         switch (playable.type) {
1491                         case MediaTypes.type.AUDIO:
1492                         case MediaTypes.type.LIVE_AUDIO:
1493                             player.audio.unmute(playable);
1494                             break;
1495                         case MediaTypes.type.VIDEO:
1496                             break;
1497                         default:
1498                             Debug.log('unmute() unsupported type: \'' + playable.type + '\'', Debug.type.error);
1499                             break;
1500                         }
1501                     }
1502                     Debug.log('unmute() -- player is now unmuted.', Debug.type.info);
1503                 },
1504                 /**
1505                  * @name setVolume
1506                  * @description sets volume.
1507                  * @fires Events.type.VOLUME_UPDATED fires event after sucessfully setting volume (see {@link Events}).
1508                  * @param {number} percent_decimal the percentage to set the volume to 0 to 1
1509                  * @methodOf APMPlayer
1510                  */
1511                 setVolume : function (percent_decimal) {
1512                     if (percent_decimal < 0) {
1513                         percent_decimal = 0;
1514                         Debug.log('setVolume() invalid percent_decimal passed: \'' + percent_decimal + '\' is less than 0.  percent_decimal set to 0.  percentages must be represented as a decimal from 0 to 1 (eg .45)', Debug.type.warn);
1515                     } else if (percent_decimal > 1) {
1516                         percent_decimal = 1;
1517                         Debug.log('setVolume() invalid percent_decimal passed: \'' + percent_decimal + '\' is greater than 1.  percent_decimal set to 1.00 by default.  percentages must be represented as a decimal from 0.00 to 1.00 (eg .45)', Debug.type.warn);
1518                     }
1519 
1520                     var playable = player.current_playable;
1521                     if (playable !== null) {
1522                         switch (playable.type) {
1523                         case MediaTypes.type.AUDIO:
1524                         case MediaTypes.type.LIVE_AUDIO:
1525                             player.audio.setVolume(playable, percent_decimal);
1526                             break;
1527                         case MediaTypes.type.VIDEO:
1528                             break;
1529                         default:
1530                             Debug.log('setVolume() unsupported type: \'' + playable.type + '\'', Debug.type.error);
1531                         }
1532                     } else {
1533                         Debug.log('setVolume() no playable loaded.  VOLUME_UPDATED event still fired. new vox : \'' + percent_decimal + '\'', Debug.type.info);
1534                     }
1535                     player.settings.volume = percent_decimal;
1536                     player.events.trigger(player.events.type.VOLUME_UPDATED, { 'percent_decimal' : percent_decimal });
1537                 },
1538                 /**
1539                  * @name debug
1540                  * @description reference to APMPlayer {@link Debug} logger
1541                  *
1542                  * @example APMPlayer.debug.log('debug message', APMPlayer.debug.type.info, 'ObjectName');
1543                  *
1544                  * Debug types:
1545                  * APMPlayer.debug.type.info
1546                  * APMPlayer.debug.type.warn
1547                  * APMPlayer.debug.type.error
1548                  *
1549                  * @static
1550                  * @fieldOf APMPlayer
1551                  */
1552                 debug : Debug,
1553                 /**
1554                  * @name events
1555                  * @description reference to the main {@link Events} object for APMPlayer
1556                  * @example APMPlayer.events.addListener(APMPlayer.events.type.PLAYER_READY, function() {} );
1557                  * @fieldOf APMPlayer
1558                  */
1559                 events : player.events,
1560                 /**
1561                  * @name mechanism
1562                  * @description reference to internal {@link PlaybackMechanism} object.
1563                  * @example APMPlayer.mechanism.getCurrentSolution();
1564                  * @fieldOf APMPlayer
1565                  */
1566                 mechanism : player.mechanism,
1567                  /**
1568                  * @name mediaTypes
1569                  * @description reference to supported MediaTypes (currently AUDIO, LIVE_AUDIO)
1570                  * @example player_ui.playlist.current().type == APMPlayer.mediaTypes.LIVE_AUDIO
1571                  * @fieldOf APMPlayer
1572                  */
1573                  mediaTypes : MediaTypes.type,
1574                 /**
1575                  * @name state
1576                  * @description reference to current {@link PlaybackState} object
1577                  * @example APMPlayer.state.getCurrent() === APMPlayer.state.type.PLAYING;
1578                  * @fieldOf APMPlayer
1579                  */
1580                  state : player.state,
1581                 /**
1582                  * @name base_path
1583                  * @description returns the base_path of the project, relative to this file.
1584                  * checks for both apmplayer-all.min.js and apmplayer.js
1585                  * @example http://localhost/apmplayer/1.2/
1586                  * @fieldOf APMPlayer
1587                  */
1588                  base_path : player.util.getProjectBasePath()
1589             };
1590         };  //end APMPlayer()
1591 
1592         /**
1593          * @name Playlist
1594          * @description structured, organized playlist made-up of {@link Playable} objects.
1595          * @constructor
1596          * */
1597         var Playlist = function () {
1598             /**
1599              * @name events
1600              * @description object reference to specific {@link Events} object for Playlist
1601              * @example Playlist.events.addListener(APMPlayer.events.type.PLAYLIST_CURRENT_CHANGE, function() {} );
1602              * @fieldOf Playlist
1603              */
1604             this.events = new Events();
1605             this._items = [];
1606             this._current_index = null;
1607         };
1608         Playlist.prototype = {
1609             /**
1610              * @name add
1611              * @description adds a valid {@link Playable} to the playlist.
1612              * @param {Playable} playable the playable to add.
1613              * @example Playlist.add(playable);
1614              * @returns {boolean} success or failure.
1615              * @methodOf Playlist
1616              */
1617             add : function (playable) {
1618                 if (playable instanceof Playable
1619                         && playable.isValid()) {
1620 
1621                     if (this.item(playable.identifier) !== null) {
1622                         Debug.log('add() could not add \'' + playable.identifier + '\' to playlist because it already exists!', Debug.type.warn, 'Playlist');
1623                         return false;
1624                     }
1625 
1626                     this._items.push(playable);
1627                     if (this._current_index === null) {
1628                         this._current_index = 0;
1629                         this.events.trigger(this.events.type.PLAYLIST_CURRENT_CHANGE, null);
1630                     }
1631                     Debug.log('add() new playable successfully added to playlist: \'' + playable.identifier + '\'',  Debug.type.info, 'Playlist');
1632                     return true;
1633                 }
1634 
1635                 Debug.log('add() -- error: nothing added to playlist.  either object passed was not of type Playable or identifier \'' + playable.identifier + '\' is invalid.', Debug.type.warn, 'Playlist');
1636                 return false;
1637             },
1638             /**
1639              * @name _count
1640              * @description returns number of items in playlist
1641              * @private
1642              * @ignore
1643              */
1644             _count : function () {
1645                 return this._items.length;
1646             },
1647             /**
1648              * @name current
1649              * @description returns the current {@link Playable} in the playlist.
1650              * @returns {Playable} returns a valid playable, or null if nothing in playlist.
1651              * @example Playlist.current();
1652              * @methodOf Playlist
1653              */
1654             current : function () {
1655                 if (this._current_index !== null) {
1656                     return this._items[this._current_index];
1657                 }
1658                 return null;
1659             },
1660             /**
1661              * @name item
1662              * @description returns specific {@link Playable} by identifier, if it exists in playlist
1663              * @param {string} identifier of a specific {@link Playable}.
1664              * @returns {Playable} if found, returns a valid playable, or null if non-existant.
1665              * @example Playlist.item('kick_ass_rock_song');
1666              * @methodOf Playlist
1667              */
1668             item : function (identifier) {
1669                 var j, total_items = this._count();
1670                 for (j = 0; j < total_items; j += 1) {
1671                     if (this._items[j].identifier === identifier) {
1672                         return this._items[j];
1673                     }
1674                 }
1675                 return null;
1676             },
1677             /**
1678              * @name goto
1679              * @description finds appropriate {@link Playable} in playlist and switches current pointer to that playable, if it exists in the playlist.
1680              * @param {string} identifier of a specific {@link Playable}.
1681              * @returns {boolean} success or failure.
1682              * @example Playlist.goto('boring_bluegrass_song');
1683              * @fires Events.type.PLAYLIST_CURRENT_CHANGE fires upon successful update of current playlist item (see {@link Events}).
1684              * @methodOf Playlist
1685              */
1686             goto : function (identifier) {
1687                 var j, total_items = this._count();
1688                 for (j = 0; j < total_items; j += 1) {
1689                     if (this._items[j].identifier === identifier) {
1690                         var previous_playable = this.current();
1691                         this._current_index = j;
1692                         this.events.trigger(this.events.type.PLAYLIST_CURRENT_CHANGE, previous_playable);
1693                         return true;
1694                     }
1695                 }
1696                 Debug.log('goto() - invalid identifier passed \'' + identifier + '\'.  This was not found in the current playlist!', Debug.type.warn, 'Playlist');
1697                 return false;
1698             },
1699             /**
1700              * @name hasNext
1701              * @description returns whether or not there is at least one more item before end of playlist
1702              * @returns {boolean} true if playlist has another item, false if at end of playlist
1703              * @example Playlist.hasNext();
1704              * @methodOf Playlist
1705              */
1706             hasNext : function () {
1707                 if (this._current_index + 1 < this._count()) {
1708                     return true;
1709                 }
1710                 return false;
1711             },
1712             /**
1713              * @name remove
1714              * @description finds and removes a specifc {@link Playable} in playlist by identifier.  The current playlist item may not be removed.
1715              * @param {string} identifier of a specific {@link Playable}.
1716              * @returns {boolean} success or failure.
1717              * @example Playlist.remove('brittany_spears_theft_of_the_dial');
1718              * @methodOf Playlist
1719              */
1720             remove : function (identifier) {
1721                 var j, num_items_before_remove = this._count();
1722                 for (j = 0; j < num_items_before_remove; j += 1) {
1723 
1724                     if (this._items[j].identifier === identifier) {
1725 
1726                         if (this.current().identifier === identifier) {
1727                             Debug.log('remove() -- sorry, you may not remove the current item in the playlist. returning false.)', Debug.type.warn, 'Playlist');
1728                             return false;
1729                         }
1730 
1731                         this._items.splice(j, 1);              //remove from array, while resetting array indexes
1732 
1733                         if (this._current_index > 0
1734                                 && j <= this._current_index) {
1735                             this._current_index -= 1;          //move current index back by 1, if we purged something before current item
1736                         }
1737 
1738                         return true;
1739                     }
1740                 }
1741                 return false;
1742             },
1743             /**
1744              * @name next
1745              * @description advances current() point to next {@link Playable} in playlist.  Also moves current to beginning if at end of playlist.
1746              * @returns {boolean} false if no items exist in playlist.
1747              * @example Playlist.next();
1748              * @fires Events.type.PLAYLIST_CURRENT_CHANGE fires upon successful change of current playlist item (see {@link Events}).  Also passes previous_playable back with the Event.
1749              * @methodOf Playlist
1750              */
1751             next : function () {
1752                 if (this._current_index !== null) {
1753                     Debug.log('next() advancing to next position in playlist (or to beginning if at last)', Debug.type.info, 'Playlist');
1754                     var previous_playable = this.current();
1755                     this._current_index = (this._current_index + 1 < this._count()) ? this._current_index + 1 : 0;
1756                     this.events.trigger(this.events.type.PLAYLIST_CURRENT_CHANGE, previous_playable);
1757                 } else {
1758                     return false;
1759                 }
1760             },
1761             /**
1762              * @name previous
1763              * @description sets current() point to back one {@link Playable} in playlist.  Also moves current to end if at beginning of playlist.
1764              * @returns {boolean} false if no items exist in playlist.
1765              * @example Playlist.previous();
1766              * @fires Events.type.PLAYLIST_CURRENT_CHANGE fires upon successful change of current playlist item (see {@link Events}).  Also passes previous_playable back with the Event.
1767              * @methodOf Playlist
1768              */
1769             previous : function () {
1770                 if (this._current_index !== null) {
1771                     Debug.log('previous() moving to previous position in playlist (or to last if at beginning)', Debug.type.info, 'Playlist');
1772                     var previous_playable = this.current();
1773                     this._current_index = (this._current_index - 1 >= 0) ? this._current_index - 1 : this._count() - 1;
1774                     this.events.trigger(this.events.type.PLAYLIST_CURRENT_CHANGE, previous_playable);
1775                 } else {
1776                     return false;
1777                 }
1778             }
1779         };  //end Playlist
1780 
1781 
1782         var apmplayer_instance;  //singleton
1783         return {
1784             /**
1785              * @name getPlayer
1786              * @description returns singleton instance of {@link APMPlayer}.
1787              * @returns {APMPlayer}
1788              * @methodOf APMPlayerFactory
1789              */
1790             getPlayer: function () {
1791                 if (typeof apmplayer_instance === 'undefined') {
1792                     apmplayer_instance = new APMPlayer();
1793                     apmplayer_instance.constructor = null;  //prohibit new's
1794                 }
1795                 return apmplayer_instance;
1796             },
1797             /**
1798              * @name getPlayable
1799              * @description validates params argument and constructs a new {@link Playable} object upon each call, if valid.  Returns an empty {@link Playable} if passed arguments are invalid.  see also {@link Playable.isValid}
1800              * @param {Object} params object literal used to build Playable.
1801              * @returns {Playable}
1802              *
1803              * @example regular example:
1804              * APMPlayerFactory.getPlayable(
1805              * {
1806              *   type: 'audio',
1807              *   identifier: 'my_audio',
1808              *   flash_server_url: 'rtmp://server/',
1809              *   flash_file_path: 'mp3:path/file.mp3',
1810              *   http_file_path: 'http://server/file.mp3',
1811              *   buffer_time: 5
1812              * });
1813              *
1814              * @example CustomScheme example:
1815              * APMPlayerFactory.getPlayable(
1816              * {
1817              *   identifier: 'apm_audio:/being/programs/2011/12/15/20111222_prophetic_imagination_128.mp3'
1818              * });
1819              *
1820              * (note that in this example, the CustomScheme provides attributes that are pre-defined
1821              * (eg, flash_server_url, flash_file_path, http_file_path, type, buffer_time)
1822              *
1823              *
1824              * @methodOf APMPlayerFactory
1825              */
1826             getPlayable : function (params) {
1827                 return new Playable(params);
1828             },
1829             /**
1830              * @name getPlaylist
1831              * @description constructs and returns a new {@link Playlist}.
1832              * @returns {Playlist}
1833              * @methodOf APMPlayerFactory
1834              */
1835             getPlaylist : function () {
1836                 return new Playlist();
1837             },
1838             /**
1839              * @name getCustomSchemes
1840              * @description returns a CustomSchemes object to initialize the map of {@link CustomSchemes}, used as a short-cut for constructing {@link Playable} objects -- see {@link CustomSchemes} for more detail.
1841              * @param {Object} params object literal used to initialize {@link CustomSchemes} -- the object can contain any number of attributes already defined for {@link Playable} objects.
1842              * @returns {CustomSchemes}
1843              *
1844              * @methodOf APMPlayerFactory
1845              */
1846             getCustomSchemes : function () {
1847                 return custom_schemes;
1848             }
1849         };
1850     }()); //end APMPlayerFactory
1851 }
1852 
1853 //initialization w/ custom configuration short-cuts (not required)
1854 var scheme_map = {
1855     apm_audio : {
1856         flash_server_url  : 'rtmp://archivemedia.publicradio.org/music',
1857         flash_file_prefix : 'mp3:ondemand',
1858         http_file_prefix  : 'http://ondemand.publicradio.org',
1859         buffer_time : 3,
1860         type : 'audio'
1861     },
1862     apm_live_audio : {
1863         mpr_news : {
1864             flash_server_url : 'rtmp://archivemedia.publicradio.org/news',
1865             flash_file_path : 'news.stream',
1866             http_file_path : 'http://newsstream1.publicradio.org:80/',
1867             buffer_time : 6,
1868             type : 'live_audio'
1869         },
1870         mpr_current : {
1871             flash_server_url : 'rtmp://archivemedia.publicradio.org/kcmp',
1872             flash_file_path : 'kcmp.stream',
1873             http_file_path : 'http://currentstream1.publicradio.org:80/',
1874             buffer_time : 6,
1875             type : 'live_audio'
1876         },
1877         csf : {
1878             flash_server_url : 'rtmp://archivemedia.publicradio.org/csf',
1879             flash_file_path : 'csf.stream',
1880             http_file_path : 'http://204.93.222.85:80/csf-nopreroll',
1881             buffer_time : 6,
1882             type : 'live_audio'
1883         },
1884         wpbi : {
1885             flash_server_url : 'rtmp://archivemedia.publicradio.org/wpbistream',
1886             flash_file_path : 'wpbi.stream',
1887             http_file_path : 'http://wpbistream1.lbdns-streamguys.com:80/wpbistream',
1888             buffer_time : 6,
1889             type : 'live_audio'
1890         }
1891     }
1892 };
1893 var custom_schemes = APMPlayerFactory.getCustomSchemes();
1894 custom_schemes.init(scheme_map);
1895 
1896 //initialize player lib
1897 var APMPlayer = APMPlayerFactory.getPlayer();
1898 APMPlayer.init();
1899 
1900