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_ui.jquery.js 36 * @fileOverview 37 * @description main user-interface integration between jQuery and APMPlayer. 38 * contains first the main object supporting integration w/ APMPlayers 39 * and second the $.fn.apmplayer_ui definition 40 */ 41 (function ($) { 42 43 'use strict'; 44 45 /** 46 * APMPlayerUI 47 * User-interface integration w/ APMPlayer that supports $.fn.apmplayer_ui 48 * Current version of $.fn.apmplayer_ui only allows a single instance of the UI at a time 49 */ 50 var APMPlayerUI = function (jq_element, jq_args) { 51 52 var player_ui = this; 53 54 this.parent_id = '#' + jq_element.attr('id'); 55 this.args = jq_args; 56 57 this.init = function () { 58 player_ui.main.init(); 59 player_ui.controls.init(); 60 player_ui.events.init(); 61 player_ui.playlist.init(); 62 }; 63 64 this.main = { 65 settings : { 66 autoplay : false, //defaults, can be overridden on player init() 67 muted : false, 68 fetchMetadata : false, 69 volume : 0.9 70 }, 71 isReady : false, 72 init : function () { 73 if (player_ui.args.hasOwnProperty('settings')) { 74 player_ui.main.mergeSettings(player_ui.args.settings); 75 } 76 if (player_ui.main.settings.autoplay === true 77 && player_ui.main.settings.fetchMetadata === true) { 78 player_ui.main.settings.autoplay = 'wait'; //this resolves the race-condition to make sure fetchMetadata call completes before autoplay occurs. 79 } 80 }, 81 mergeSettings : function (settings) { 82 var prop; 83 for (prop in settings) { 84 if (settings.hasOwnProperty(prop) 85 && player_ui.main.settings.hasOwnProperty(prop)) { 86 player_ui.main.settings[prop] = settings[prop]; 87 } 88 } 89 }, 90 canAutoPlay : function () { 91 if (player_ui.main.settings.autoplay === true 92 && APMPlayer.mechanism.getCurrentSolution() !== APMPlayer.mechanism.type.HTML5 93 && player_ui.main.isReady === true) { 94 return true; 95 } 96 return false; 97 }, 98 updateAutoPlay : function (playable) { 99 var current_playable = player_ui.playlist.current(); 100 if (current_playable.identifier === playable.identifier 101 && player_ui.main.settings.autoplay === 'wait') { 102 player_ui.main.settings.autoplay = true; 103 } 104 }, 105 fetchMetadata : function (playable) { 106 //something like this is used by APMG to get metadata, duration, other info about a playable before it starts playing. 107 //for this version, it simply by-passes the API call and passes through to the callback directly 108 109 player_ui.events.onFetchMetadata(playable); 110 111 //example similar to the API call used by APMG: 112 //$.getJSON('http://mywebservice.org/api/audio/metadata/?callback=?', 'id=' + playable.identifier, player_ui.events.onFetchMetadata); 113 } 114 }; 115 116 this.skin = { 117 /** 118 * holds map of css 119 * (potentially overridable down the road if differing id's/class names are desired) 120 */ 121 css : { 122 play: "apm_player_play", 123 pause: "apm_player_pause", 124 seeker: "apm_player_bar", 125 seekerBufferingCls: "buffering", 126 seekerLoading: "apm_player_loading", 127 liveStreamingCls: "streaming", 128 volumeWrapper: "apm_player_volume_wrapper", 129 volumeMutedCls: "muted", 130 volumeBar: "apm_volume_bar", 131 volumeBarWrapper: "apm_player_volume_slider_wrapper", 132 volumeStatus: "apm_player_volume_status", 133 info: "apm_player_info", 134 status: "apm_player_status", 135 statusWarningCls: "warning", 136 statusAlertCls: "alert", 137 playtime: "apm_player_playtime", 138 playlist: "apm_playlist", 139 playlistNowPlayingCls: "nowplaying", 140 sponsorOverlayActiveCls: "preroll-active", 141 sponsorOverlayInactiveCls: "preroll-inactive", 142 sponsorTimer: "apm_sponsor_overlay_time", 143 sharingTools: "apm_sharing_tools", 144 sharingTabControls: "apm_sharing_tab_controls", 145 sharingTabCls: "apm_sharing_tab", 146 sharingTabSharing: "apm_sharing_share", 147 sharingTabDownload: "apm_sharing_download", 148 sharingTabEmbed: "apm_sharing_embed", 149 sharingTabLink: "apm_sharing_link" 150 } 151 }; 152 153 this.controls = { 154 /** 155 * registers jQuery events, listens for interactions w/ skin + inits jQuery sliders, etc 156 */ 157 init : function () { 158 player_ui.controls.seeker.init(); 159 player_ui.controls.info.init(); 160 player_ui.controls.volume.init(); 161 player_ui.controls.volumeStatus.init(); 162 player_ui.controls.pause.init(); 163 player_ui.controls.play.init(); 164 player_ui.controls.tools.init(); 165 }, 166 play : { 167 init : function () { 168 $(player_ui.parent_id + ' #' + player_ui.skin.css.play).click(function () { 169 APMPlayer.play(player_ui.playlist.current(), player_ui.main.settings); 170 }); 171 } 172 }, 173 pause : { 174 init : function () { 175 $(player_ui.parent_id + ' #' + player_ui.skin.css.pause).click(function () { 176 APMPlayer.pause(); // note, this will force an unload of playable in APMPlayer internally 177 }); // if it's type === LIVE_AUDIO (conserves BW) 178 } 179 }, 180 seeker : { 181 status : 'NORMAL', //or USER_SLIDING 182 init : function () { 183 $(player_ui.parent_id + ' #' + player_ui.skin.css.seeker).slider({ 184 disabled: true, 185 range: "min", 186 start: function (event, ui) { 187 player_ui.controls.seeker.status = 'USER_SLIDING'; 188 }, 189 stop: function (event, ui) { 190 player_ui.events.onSeek(ui.value / 100); 191 player_ui.controls.seeker.status = 'NORMAL'; 192 }, 193 slide: function (event, ui) { 194 player_ui.controls.seeker.status = 'USER_SLIDING'; 195 var current_playable = player_ui.playlist.current(); 196 current_playable.position = (ui.value / 100) * current_playable.duration; 197 player_ui.controls.playtime.render(current_playable); 198 } 199 }); 200 }, 201 update : function (playable) { 202 if (player_ui.controls.seeker.status === 'NORMAL') { //don't slide if user_sliding 203 var percent = 100 * playable.percent_played; 204 $(player_ui.parent_id + ' #' + player_ui.skin.css.seeker).slider('value', percent); 205 } 206 if(playable.percent_loaded <= 1) { 207 var percent_num = 100 * (playable.percent_loaded); 208 $(player_ui.parent_id + ' #' + player_ui.skin.css.seekerLoading).width(percent_num + '%'); 209 } 210 }, 211 enable : function () { 212 if (player_ui.playlist.current().type !== APMPlayer.mediaTypes.LIVE_AUDIO) { 213 $(player_ui.parent_id + ' #' + player_ui.skin.css.seeker).slider('enable'); 214 } 215 }, 216 disable : function () { 217 if (player_ui.playlist.current().type === APMPlayer.mediaTypes.LIVE_AUDIO) { 218 $(player_ui.parent_id + ' #' + player_ui.skin.css.seeker).slider('disable'); 219 } 220 }, 221 reset : function () { 222 $(player_ui.parent_id + ' #' + player_ui.skin.css.seeker).slider('value', 0); 223 }, 224 configure : function (playable) { 225 player_ui.controls.seeker.reset(); 226 if(playable.type === APMPlayer.mediaTypes.LIVE_AUDIO) { 227 player_ui.controls.seeker.disable(); 228 $(player_ui.parent_id + ' #' + player_ui.skin.css.seeker).addClass(player_ui.skin.css.liveStreamingCls); 229 } else { 230 if(playable.duration > 0) { 231 player_ui.controls.seeker.enable(); 232 } 233 $(player_ui.parent_id + ' #' + player_ui.skin.css.seeker).removeClass(player_ui.skin.css.liveStreamingCls); 234 } 235 } 236 }, 237 playtime : { 238 convertToTime : function (miliseconds) { 239 var myTime = new Date(miliseconds), 240 hour = myTime.getUTCHours(), 241 min = myTime.getUTCMinutes(), 242 sec = myTime.getUTCSeconds(), 243 strHour = hour, 244 strMin = (strHour > 0 && min < 10) ? '0' + min : min, 245 strSec = (sec < 10) ? '0' + sec : sec; 246 247 return ((strHour > 0) ? strHour + ':' : '') + (strMin + ':') + (strSec); 248 }, 249 render : function (playable) { 250 var time_display; 251 if (playable.duration > 0) { 252 time_display = player_ui.controls.playtime.convertToTime(playable.position) + ' / ' + player_ui.controls.playtime.convertToTime(playable.duration); 253 } else { 254 time_display = player_ui.controls.playtime.convertToTime(playable.position); 255 } 256 $(player_ui.parent_id + ' #' + player_ui.skin.css.playtime).text(time_display); 257 } 258 }, 259 volume : { 260 init : function () { 261 $(player_ui.parent_id + ' #' + player_ui.skin.css.volumeBar).slider({ 262 range: 'min', 263 orientation: 'vertical', 264 value : player_ui.main.settings.volume * 100, 265 stop : function (event, ui) { 266 APMPlayer.setVolume(ui.value / 100); 267 if (ui.value > 0) { 268 if (player_ui.main.settings.muted) { 269 APMPlayer.unmute(); 270 player_ui.controls.volumeStatus.renderUnmuted(); 271 player_ui.main.settings.muted = false; 272 } 273 } 274 } 275 }); 276 277 $(player_ui.parent_id + ' #' + player_ui.skin.css.volumeWrapper).hover(function(){ 278 $(player_ui.parent_id + ' #' + player_ui.skin.css.volumeBarWrapper).show(); 279 }, 280 function(){ 281 $(player_ui.parent_id + ' #' + player_ui.skin.css.volumeBarWrapper).fadeOut(500); 282 }); 283 }, 284 renderMuted : function () { 285 $(player_ui.parent_id + ' #' + player_ui.skin.css.volumeBar).slider('value', 0); 286 }, 287 renderUnmuted : function () { 288 $(player_ui.parent_id + ' #' + player_ui.skin.css.volumeBar).slider('value', player_ui.main.settings.volume * 100); 289 } 290 }, 291 volumeStatus : { 292 init : function () { 293 $(player_ui.parent_id + ' #' + player_ui.skin.css.volumeStatus).click(function () { 294 if (player_ui.main.settings.muted) { 295 APMPlayer.unmute(); 296 player_ui.main.settings.muted = false; 297 player_ui.controls.volume.renderUnmuted(); 298 player_ui.controls.volumeStatus.renderUnmuted(); 299 } else { 300 APMPlayer.mute(); 301 player_ui.main.settings.muted = true; 302 player_ui.controls.volume.renderMuted(); 303 player_ui.controls.volumeStatus.renderMuted(); 304 } 305 }); 306 }, 307 renderMuted : function () { 308 $(player_ui.parent_id + ' #' + player_ui.skin.css.volumeStatus).addClass(player_ui.skin.css.volumeMutedCls); 309 }, 310 renderUnmuted : function () { 311 $(player_ui.parent_id + ' #' + player_ui.skin.css.volumeStatus).removeClass(player_ui.skin.css.volumeMutedCls); 312 } 313 }, 314 info : { 315 init : function () { 316 if (player_ui.args.hasOwnProperty('onMetadata')) { //bind custom event handler, if passed through $.args 317 player_ui.events.onMetadata = player_ui.args.onMetadata; 318 } 319 } 320 }, 321 status : { 322 displayWarning : function (html_snippet) { 323 $(player_ui.parent_id + ' #' + player_ui.skin.css.status).html(html_snippet); 324 $(player_ui.parent_id + ' #' + player_ui.skin.css.status).addClass(player_ui.skin.css.statusWarningCls); 325 }, 326 displayAlert : function (html_snippet) { 327 $(player_ui.parent_id + ' #' + player_ui.skin.css.status).html(html_snippet); 328 $(player_ui.parent_id + ' #' + player_ui.skin.css.status).addClass(player_ui.skin.css.statusAlertCls); 329 }, 330 clearAll : function () { 331 $(player_ui.parent_id + ' #' + player_ui.skin.css.status).removeClass(player_ui.skin.css.statusAlertCls); 332 $(player_ui.parent_id + ' #' + player_ui.skin.css.status).removeClass(player_ui.skin.css.statusWarningCls); 333 $(player_ui.parent_id + ' #' + player_ui.skin.css.status).html(''); 334 } 335 }, 336 tools : { 337 init : function () { 338 if (player_ui.args.hasOwnProperty('tools') 339 && player_ui.args.tools.hasOwnProperty('config')) { 340 player_ui.controls.tools.config = player_ui.args.tools.config; 341 } 342 343 player_ui.controls.tools.config(); 344 }, 345 config : function () { 346 $(player_ui.parent_id + ' #' + player_ui.skin.css.sharingTools + ' .' + player_ui.skin.css.sharingTabCls).hide(); 347 $(player_ui.parent_id + ' #' + player_ui.skin.css.sharingTools + ' .' + player_ui.skin.css.sharingTabCls + ':first').show(); 348 $(player_ui.parent_id + ' #' + player_ui.skin.css.sharingTools + ' ul#' + player_ui.skin.css.sharingTabControls + ' li:first').addClass('active'); 349 $(player_ui.parent_id + ' #' + player_ui.skin.css.sharingTools + ' ul#' + player_ui.skin.css.sharingTabControls + ' li a').click(function() { 350 $(player_ui.parent_id + ' #' + player_ui.skin.css.sharingTools + ' ul#' + player_ui.skin.css.sharingTabControls + ' li').removeClass('active'); 351 $(this).parent().addClass('active'); 352 var currentTab = $(this).attr('href'); 353 $(player_ui.parent_id + ' #' + player_ui.skin.css.sharingTools + ' .' + player_ui.skin.css.sharingTabCls).hide(); 354 $(currentTab).show(); 355 356 return false; 357 }); 358 }, 359 renderDownload : function (playable) { 360 var snippet = ''; 361 if(playable.downloadable === true 362 && player_ui.playlist.current().type === APMPlayer.mediaTypes.AUDIO) { 363 snippet = '<a href="' + APMPlayer.base_path + 'util/download.php?uri=' + playable.http_file_path +'">file download</a>'; 364 } else { 365 snippet = 'sorry, this item is not downloadable.'; 366 } 367 368 $(player_ui.parent_id + ' #' + player_ui.skin.css.sharingTools + ' #' + player_ui.skin.css.sharingTabDownload).html(snippet); 369 } 370 } 371 }; 372 373 /** 374 * player_ui.events 375 * contains all handlers + registers listeners for UI and back-end library 376 * essentially the brokers communication as a controller between display and playback mechanism 377 */ 378 this.events = { 379 init : function () { 380 APMPlayer.events.addListener(APMPlayer.events.type.PLAYER_READY, player_ui.events.onPlayerReady); 381 APMPlayer.events.addListener(APMPlayer.events.type.PLAYING, player_ui.events.onPlaying); 382 APMPlayer.events.addListener(APMPlayer.events.type.PAUSED, player_ui.events.onPaused); 383 APMPlayer.events.addListener(APMPlayer.events.type.METADATA, player_ui.events.onMetadata); 384 APMPlayer.events.addListener(APMPlayer.events.type.BUFFER_START, player_ui.events.onBufferStart); 385 APMPlayer.events.addListener(APMPlayer.events.type.BUFFER_END, player_ui.events.onBufferEnd); 386 APMPlayer.events.addListener(APMPlayer.events.type.POSITION_UPDATE, player_ui.events.onPositionUpdate); 387 APMPlayer.events.addListener(APMPlayer.events.type.FINISHED, player_ui.events.onFinished); 388 APMPlayer.events.addListener(APMPlayer.events.type.UNLOADED, player_ui.events.onUnloaded); 389 APMPlayer.events.addListener(APMPlayer.events.type.VOLUME_UPDATED, player_ui.events.onVolumeUpdated); 390 APMPlayer.events.addListener(APMPlayer.events.type.CONNECTION_LOST, player_ui.events.onConnectionLost); 391 APMPlayer.events.addListener(APMPlayer.events.type.PLAYER_FAILURE, player_ui.events.onFailure); 392 APMPlayer.events.addListener(APMPlayer.events.type.MISSING_FILE, player_ui.events.onMissingFile); 393 }, 394 onPlayerReady : function () { 395 player_ui.main.isReady = true; 396 if (player_ui.main.canAutoPlay()) { 397 APMPlayer.play(player_ui.playlist.current(), player_ui.main.settings); 398 } else { 399 var current_playable = player_ui.playlist.current(); 400 if(current_playable !== null) { 401 player_ui.events.onMetadata(current_playable); 402 } 403 } 404 }, 405 onPlaying : function (playable) { 406 player_ui.controls.seeker.enable(); 407 player_ui.controls.status.clearAll(); 408 $(player_ui.parent_id + ' #' + player_ui.skin.css.play).hide(); 409 $(player_ui.parent_id + ' #' + player_ui.skin.css.pause).show(); 410 411 player_ui.playlist.addNowPlaying(playable); 412 }, 413 onPaused : function (playable) { 414 $(player_ui.parent_id + ' #' + player_ui.skin.css.play).show(); 415 $(player_ui.parent_id + ' #' + player_ui.skin.css.pause).hide(); 416 417 if (playable.type === APMPlayer.mediaTypes.LIVE_AUDIO) { 418 player_ui.controls.seeker.reset(); 419 } 420 }, 421 onFinished : function () { 422 player_ui.controls.seeker.disable(); 423 if (player_ui.playlist.hasNext()) { 424 player_ui.playlist.next(); 425 } else { 426 APMPlayer.unload(); 427 } 428 }, 429 onPositionUpdate : function (playable) { 430 player_ui.controls.seeker.update(playable); 431 432 if(player_ui.controls.seeker.status !== 'USER_SLIDING') { 433 player_ui.controls.playtime.render(playable); 434 } 435 }, 436 onSeek : function (percent_decimal) { 437 if (APMPlayer.state.current() !== APMPlayer.state.type.STOPPED) { 438 APMPlayer.seek(percent_decimal); 439 } else { 440 var current_playable = player_ui.playlist.current(); //manually set if stopped 441 if (current_playable.duration > 0) { 442 current_playable.percent_played = percent_decimal; 443 current_playable.position = current_playable.duration * percent_decimal; 444 player_ui.controls.playtime.render(current_playable); 445 } 446 } 447 }, 448 onUnloaded : function (playable) { 449 player_ui.controls.seeker.reset(); 450 player_ui.controls.playtime.render(playable); 451 $(player_ui.parent_id + ' #' + player_ui.skin.css.play).show(); 452 $(player_ui.parent_id + ' #' + player_ui.skin.css.pause).hide(); 453 }, 454 onBufferStart : function () { 455 if(APMPlayer.state.current() === APMPlayer.state.type.PLAYING) { 456 $(player_ui.parent_id + ' #' + player_ui.skin.css.seeker).addClass(player_ui.skin.css.seekerBufferingCls); 457 } 458 }, 459 onBufferEnd : function () { 460 $(player_ui.parent_id + ' #' + player_ui.skin.css.seeker).removeClass(player_ui.skin.css.seekerBufferingCls); 461 }, 462 onFetchMetadata : function (response) { 463 //APMG-specific metadata API callback would normally appear here. 464 //instead, this is here as a place-holder so this version works if this setting is enabled. 465 466 APMPlayer.debug.log('events.onFetchMetadata() pass-through hit for \'' + response.identifier + '\'', APMPlayer.debug.type.info, 'APMPlayerUI'); 467 468 var current_playable = player_ui.playlist.current(); 469 if (response.identifier === current_playable.identifier) { 470 player_ui.events.onMetadata(current_playable); //set common display window 471 player_ui.controls.tools.renderDownload(current_playable); //make sure download link is correct 472 473 if (current_playable.duration > 0) { 474 player_ui.controls.playtime.render(current_playable); //set time 475 476 if(current_playable.isFlashStreamable()) { //make scrubbable (but only if not prog-download) 477 player_ui.controls.seeker.enable(); 478 479 if(current_playable.position > 0) { //in this situation, the current position was defaulted to start somewhere mid-track. be sure percent_played is set appropriately. 480 current_playable.percent_played = current_playable.position / current_playable.duration; 481 } 482 player_ui.controls.seeker.update(current_playable); 483 } 484 } 485 486 player_ui.main.updateAutoPlay(current_playable); 487 if (player_ui.main.canAutoPlay()) { 488 APMPlayer.play(player_ui.playlist.current(), player_ui.main.settings); 489 } 490 } 491 }, 492 onMetadata : function () {}, //typically overridden on fn.init() 493 onFailure : function () { 494 var playable = player_ui.playlist.current(); 495 var snippet = '<p>We\'re sorry, but your browser is not able to play the stream. Please try one of these options: <br /><br />1) Install or enable <a href="http://get.adobe.com/flashplayer/" target="_blank">Adobe Flash Player</a> <br />2) Use a browser that supports HTML5 and MP3 audio, such as <a href="http://www.google.com/chrome/" target="_blank">Chrome</a>, <a href="http://www.apple.com/safari/download/" target="_blank">Safari</a>, or <a href="http://www.microsoft.com/ie9" target="_blank">Internet Explorer 9</a>'; 496 497 //strange-case. if we're dealing with Safari on Windows7, quicktime is required for HTML5 (yuck!) this catches that condition. 498 if (/Safari/.test(navigator.userAgent) && /Apple Computer/.test(navigator.vendor)) { 499 snippet = '<p>We\'re sorry, but your browser is not able to play the stream. Please try one of these options: <br /><br />1) Install or enable <a href="http://get.adobe.com/flashplayer/" target="_blank">Adobe Flash Player</a> <br />2) Install <a href="http://www.apple.com/quicktime/download/" target="_blank">Quicktime</a> for HTML5 support for Safari on Windows (requires reboot) <br />2b) Use a different browser that natively supports HTML5 and MP3 audio, such as <a href="http://www.google.com/chrome/" target="_blank">Chrome</a> or <a href="http://www.microsoft.com/ie9" target="_blank">Internet Explorer 9</a>'; 500 } 501 502 if (playable.downloadable === true 503 && playable.http_file_path !== '') { 504 505 if (playable.type === APMPlayer.mediaTypes.AUDIO) { 506 snippet += '<br />3) <a href="' + APMPlayer.base_path + 'util/download.php?uri=' + playable.http_file_path + '">download the audio</a>'; 507 } else if (playable.type === APMPlayer.mediaTypes.LIVE_AUDIO) { 508 snippet += '<br />3) <a href="' + playable.http_file_path + '">Stream the audio using a third-party player (eg. iTunes)</a>'; 509 } 510 } 511 snippet += "</p>"; 512 513 player_ui.controls.status.displayWarning(snippet); 514 }, 515 onVolumeUpdated : function (eventData) { 516 player_ui.main.settings.volume = eventData.percent_decimal; 517 }, 518 onConnectionLost : function () { 519 APMPlayer.unload(); 520 var snippet = "<p>Your network connection has changed or has been lost.<br /><br />Please check your connection, then click play to resume."; 521 player_ui.controls.status.displayAlert(snippet); 522 }, 523 onMissingFile : function (playable) { 524 //display error message for missing file. 525 //unfortunately, most missing file errors are actually a problem w/ a network connection and rarely are actual missing files. 526 //the error output has been adjusted to reflect and offer better feedback. 527 var snippet = '<p>We\'re sorry, an error has occurred and your audio cannot be played at this time. <br /><br />Often, this error is a result of a poor or missing internet connection. Please check your internet connection, then click play to resume.'; 528 player_ui.controls.status.displayWarning(snippet); 529 player_ui.underwriting.removeOverlay(); 530 } 531 }; 532 533 /** 534 * playlist 535 * note, UI will always maintain an internal playist, regardless of appearance of playlist. 536 * used internally, even if only one playable. 537 */ 538 this.playlist = APMPlayerFactory.getPlaylist(); 539 player_ui.playlist.onUpdate = function (playable) {}; 540 player_ui.playlist.init = function () { 541 if (player_ui.args.hasOwnProperty('onPlaylistUpdate')) { 542 player_ui.playlist.onUpdate = player_ui.args.onPlaylistUpdate; 543 } 544 if (player_ui.args.hasOwnProperty('playables')) { 545 $.each(player_ui.args.playables, function (index, item) { 546 player_ui.playlist.addPlayable(item); 547 }); 548 } 549 }; 550 player_ui.playlist.addPlayable = function (item) { 551 var playable = APMPlayerFactory.getPlayable(item); 552 if (playable.isValid()) { 553 player_ui.playlist.add(playable); 554 player_ui.playlist.onUpdate(playable); 555 556 if (player_ui.main.settings.fetchMetadata === true) { 557 if (playable.isCustomScheme('apm_audio')) { //only look-up custom APM media items 558 player_ui.main.fetchMetadata(playable); 559 } else { 560 player_ui.main.updateAutoPlay(playable); 561 } 562 } 563 564 } else { 565 APMPlayer.debug.log('sorry, there was a problem with the parameters passed and a valid playable could not be created.', APMPlayer.debug.type.warn, 'APMPlayerUI'); 566 } 567 }; 568 player_ui.playlist.gotoItem = function (identifer) { 569 if(APMPlayer.state.current() === APMPlayer.state.type.STOPPED 570 || player_ui.playlist.current().identifier !== identifer) { 571 player_ui.controls.seeker.disable(); 572 player_ui.playlist.goto(identifer); 573 } 574 }; 575 player_ui.playlist.addNowPlaying = function (playable) { 576 $('li[ id = \'' + playable.identifier + '\']').addClass(player_ui.skin.css.playlistNowPlayingCls); 577 }; 578 player_ui.playlist.removeNowPlaying = function (playable) { 579 $(player_ui.parent_id + ' #' + player_ui.skin.css.playlist + ' li[ id = \'' + playable.identifier + '\']').removeClass(player_ui.skin.css.playlistNowPlayingCls); 580 }; 581 player_ui.playlist.onCurrentChange = function (previous_playable) { 582 if (previous_playable !== null) { //only null on first item added to playlist. 583 player_ui.playlist.removeNowPlaying(previous_playable); 584 APMPlayer.play(player_ui.playlist.current(), player_ui.main.settings); 585 } 586 var current_playable = player_ui.playlist.current(); 587 player_ui.controls.seeker.configure(current_playable); 588 player_ui.controls.tools.renderDownload(current_playable); 589 590 if (current_playable.duration > 0) { 591 player_ui.controls.playtime.render(current_playable); 592 } 593 }; 594 player_ui.playlist.events.addListener(APMPlayer.events.type.PLAYLIST_CURRENT_CHANGE, player_ui.playlist.onCurrentChange); 595 596 597 player_ui.init(); //always init on construct 598 599 600 }; //end APMPlayerUI 601 602 603 /** 604 * @name $.fn.apmplayer_ui 605 * @class 606 * 607 * @description main integration point between jQuery, APMPlayerUI, and {@link APMPlayer}. 608 * The $.fn.apmplayer_ui is tested and compliant with all versions of jQuery >= v1.4 609 * 610 * @param {Object} args object literal used to instantiate APMPlayerUI 611 * @param {Object} args.settings object literal containing initial values to set on APMPlayer initialization. 612 * @param {Number} args.settings.volume sets volume on initialization. (eg. volume : 0.5) 613 * @param {Boolean} args.settings.autoplay autoplays current {@link Playable} in {@link Playlist} after library init completes. (default : false) 614 * @param {Boolean} args.settings.fetchMetadata retrieves and sets additional metadata for each {@link Playable}. Note that this will only override a value in {@link Playable} if it is currently not set. Values passed in when adding a {@link Playable} will take precedence. (default : true) 615 * @param {Array} args.playables an array of {@link Playable} objects. 616 * @param {Function} args.onPlaylistUpdate() a method that will fire when Events.type.PLAYLIST_CURRENT_CHANGE is fired, or the playlist is updated. (see {@link Events}). 617 * @param {Function} args.onMetadata() a method that will fire when Events.type.METADATA is fired. (see {@link Events}). 618 * 619 * @example 620 * //note: see CustomSchemes for this example 621 * $('#apm_media_wrapper').apmplayer_ui({ 622 * playables : [ 623 * { 624 * identifier: 'apm_audio:/performance_today/2012/04/24/pt2_20120424_128.mp3' 625 * } 626 * ] 627 * }); 628 * 629 * @example 630 * $('#apm_media_wrapper').apmplayer_ui({ 631 * playables : [ 632 * { 633 * identifier: 'my audio', 634 * type: 'audio', 635 * description: 'more info about my audio', 636 * flash_server_url: 'rtmp://server/', 637 * flash_file_path: 'mp3:path/file.mp3', 638 * http_file_path: 'http://server/file.mp3' 639 * } 640 * ] 641 * }); 642 * 643 * 644 * @example 645 * $('#apm_media_wrapper').apmplayer_ui({ 646 * settings : { 647 * volume : 0.8, 648 * autoplay : true 649 * }, 650 * playables : [ 651 * { 652 * identifier: 'apm_live_audio:/mpr_current', 653 * description: 'live streaming from 89.3', 654 * program: '89.3 the Current', 655 * host: 'Mark Wheat', 656 * date: 'March 24, 2012', 657 * detail: 'during this hour...', 658 * image_sm: 'http://mpr.org/images/current.gif' 659 * }, 660 * { 661 * identifier: 'apm_audio:/performance_today/2012/04/24/pt2_20120424_128.mp3', 662 * program: 'on Being', 663 * downloadable: false 664 * } 665 * ], 666 * onPlaylistUpdate : function (playable) { 667 * // implement 668 * }, 669 * onMetadata : function (playable) { 670 * // implement 671 * } 672 * }); 673 */ 674 $.fn.apmplayer_ui = function (args) { 675 676 //check dependencies 677 if (typeof APMPlayer === 'undefined' 678 || typeof soundManager === 'undefined') { 679 $.error('apmplayer_ui ERROR. 1 or more dependent libraries missing. exiting.'); 680 return null; 681 } 682 683 var jq_element = this, //hold reference to jQuery $(this) 684 methods = { 685 /** 686 * @name addPlayable 687 * 688 * @description adds a {@link Playable}, if valid, to {@link Playlist}. 689 * @example $('#player_container_div').apmplayer_ui('addPlayable', playable); 690 * @methodOf $.fn.apmplayer_ui 691 */ 692 addPlayable : function (item) { 693 if (typeof window.apmplayer_ui !== 'undefined') { 694 window.apmplayer_ui.playlist.addPlayable(item); 695 } else { 696 APMPlayer.debug.log('you must first initialize apmplayer_ui before calling methods on it.', APMPlayer.debug.type.error, 'APMPlayerUI'); 697 } 698 }, 699 /** 700 * @name gotoPlaylistItem 701 * 702 * @description changes position in {@link Playlist} to the {@link Playable} that matches the passed identifier, and begins playing the item. 703 * @example $('#player_container_div').apmplayer_ui('gotoPlaylistItem', identifier); 704 * @methodOf $.fn.apmplayer_ui 705 */ 706 gotoPlaylistItem : function (identifier) { 707 if (typeof window.apmplayer_ui !== 'undefined') { 708 window.apmplayer_ui.playlist.gotoItem(identifier); 709 } else { 710 APMPlayer.debug.log('you must first initialize apmplayer_ui before calling methods on it.', APMPlayer.debug.type.error, 'APMPlayerUI'); 711 } 712 } 713 }; 714 715 if (methods[args]) { 716 return methods[args].apply(this, Array.prototype.slice.call(arguments, 1)); 717 } else if (typeof args === 'object' || !args) { 718 if (typeof window.apmplayer_ui === 'undefined') { 719 window.apmplayer_ui = new APMPlayerUI(jq_element, args); 720 APMPlayer.debug.log('instantiated apmplayer_ui', APMPlayer.debug.type.info, 'APMPlayerUI'); 721 } else { 722 APMPlayer.debug.log('sorry, only one player UI instance is currently supported.', APMPlayer.debug.type.error, 'APMPlayerUI'); 723 } 724 } else { 725 APMPlayer.debug.log('Method ' + args + ' does not exist on jQuery.apmplayer_ui', APMPlayer.debug.type.error, 'APMPlayerUI'); 726 } 727 }; 728 }(jQuery)); 729