amplitude.js 107 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143
  1. /*
  2. Amplitude.js
  3. Version: 2.2
  4. */
  5. var Amplitude = (function () {
  6. /*
  7. |--------------------------------------------------------------------------
  8. | Initializers
  9. |--------------------------------------------------------------------------
  10. | When the document is ready, Amplitude goes through and finds the elements
  11. | that should have event listeners and binds them. Next it will set up all
  12. | of the song time visualizations. These visualizations simply show the
  13. | proportion of the song time that has elapsed with respect to the container
  14. | element. These are NOT the visualizations for audio frequencies that are
  15. | artistic. It's a div that fills with another div representing the song time.
  16. */
  17. document.onreadystatechange = function () {
  18. if( document.readyState == "complete" ){
  19. privateInitializeEventHandlers();
  20. privateInitializeSongTimeVisualizations();
  21. }
  22. }
  23. /*
  24. |--------------------------------------------------------------------------
  25. | Module Variables
  26. |--------------------------------------------------------------------------
  27. | These variables make Amplitude run. The config is the most important
  28. | containing active settings and parameters. The Web Audio API variables
  29. | for visualizations are below.
  30. */
  31. /*--------------------------------------------------------------------------
  32. The config JSON is the global settings for ALL of Amplitude functions.
  33. This is global and contains all of the user preferences. The default
  34. settings are set, and the user overwrites them when they initialize
  35. Amplitude.
  36. --------------------------------------------------------------------------*/
  37. var config = {
  38. /*
  39. The audio element we will be using to handle all of the audio. This
  40. is the javascript version of the HTML5 audio element.
  41. */
  42. active_song: new Audio(),
  43. /*
  44. JSON object that contains the active metadata for the song.
  45. */
  46. active_metadata: {},
  47. /*
  48. String to hold the active album name. Used to check and see if the
  49. album changed and run the album changed callback.
  50. */
  51. active_album: '',
  52. /*
  53. Contains the index of the actively playing song.
  54. */
  55. active_index: 0,
  56. /*
  57. Set to true to autoplay the song
  58. */
  59. autoplay: false,
  60. /*
  61. Used to determine if the album has changed and run the callback if it
  62. has.
  63. */
  64. album_change: false,
  65. /*
  66. The user can set dynamic_mode to true and dynamic mode will be enabled
  67. allowing the user to pass a JSON representation of a song when they
  68. want to play it and utilize the Amplitude events to control it. The
  69. user also doesn't have to config songs to use dynamic mode.
  70. */
  71. dynamic_mode: false,
  72. /*
  73. The user can pass a JSON object with a key => value store of callbacks
  74. to be run at certain events.
  75. */
  76. callbacks: {},
  77. /*
  78. Object containing all of the songs the user has passed to Amplitude
  79. to use.
  80. */
  81. songs: {},
  82. /*
  83. When repeat is on, when the song ends the song will replay itself.
  84. */
  85. repeat: false,
  86. /*
  87. When shuffled, this gets populated with the songs the user provided
  88. in a random order.
  89. */
  90. shuffle_list: {},
  91. /*
  92. When shuffled is turned on this gets set to true so when traversing
  93. through songs Amplitude knows whether or not to use the songs object
  94. or the shuffle_list.
  95. */
  96. shuffle_on: false,
  97. /*
  98. When shuffled, this index is used to let Amplitude know where it's
  99. at when traversing.
  100. */
  101. shuffle_active_index: 0,
  102. /*
  103. The user can set default album art to be displayed if the song they
  104. set doesn't contain album art.
  105. */
  106. default_album_art: '',
  107. /*
  108. When set to true, Amplitude will print to the console any errors
  109. that it runs into providing helpful feedback to the user.
  110. */
  111. debug: false,
  112. /*
  113. When Amplitude finishes initializing, this is set to true. When set
  114. to true, Amplitude cannot be initialized again preventing double
  115. event handlers.
  116. */
  117. initialized: false,
  118. /*
  119. By default this is true, but if the user wanted to hard code elements
  120. with song data, they could set this to false and Amplitude wouldn't
  121. edit the now playing information in elements.
  122. */
  123. handle_song_elements: true,
  124. /*
  125. The user can set the initial volume to a number between 0 and 1
  126. overridding a default of .5.
  127. */
  128. volume: .5,
  129. /*
  130. This is set on mute so that when a user un-mutes Amplitude knows
  131. what to restore the volume to.
  132. */
  133. pre_mute_volume: .5,
  134. /*
  135. This is an integer between 1 and 100 for how much the volume should
  136. increase when the user presses a volume up button.
  137. */
  138. volume_increment: 5,
  139. /*
  140. This is an integer between 1 and 100 for how much the volume should
  141. decrease when the user presses a volume down button.
  142. */
  143. volume_decrement: 5,
  144. /*
  145. To use visualizations with Amplitude, the user will set this to true.
  146. It is assumed that the user has thoroughly tested thier songs and have
  147. a back up plan in place if the browser doesn't support the Web Audio API.
  148. By doing this, we bypass a lot of unforseen errors with auto binding
  149. the web audio API to songs that don't need visualizations.
  150. */
  151. use_visualizations: false,
  152. /*
  153. Handles all of the user registered visualizations if the user chooses
  154. to use visualizations in their design.
  155. */
  156. visualizations: new Array(),
  157. /*
  158. Is set to the active visualization so Amplitude can detect changes
  159. per song if necessary.
  160. */
  161. active_visualization: '',
  162. /*
  163. Holds the information the user defined about the current visualization,
  164. such as preferences.
  165. */
  166. current_visualization: {},
  167. /*
  168. When the visualization is started, this is set to true.
  169. */
  170. visualization_started: false,
  171. /*
  172. If the browser doesn't support visualizations, the user can provide
  173. a back up. 'nothing' is the default which removes the visualization
  174. element from the document. 'album-art' has Amplitude inject the now
  175. playing album art into the element that would have contained the
  176. visualization.
  177. */
  178. visualization_backup: '',
  179. /*
  180. When using SoundCloud, the user will have to provide their API Client
  181. ID
  182. */
  183. soundcloud_client: '',
  184. /*
  185. The user can set this to true and Amplitude will use the album art
  186. for the song returned from the Soundcloud API
  187. */
  188. soundcloud_use_art: false,
  189. /*
  190. Used on config to count how many songs are from soundcloud and
  191. compare it to how many are ready for when to move to the rest
  192. of the configuration.
  193. */
  194. soundcloud_song_count: 0,
  195. /*
  196. Used on config to count how many songs are ready so when we get
  197. all of the data from the SoundCloud API that we need this should
  198. match the SoundCloud song count meaning we can move to the rest
  199. of the config.
  200. */
  201. soundcloud_songs_ready: 0,
  202. /*--------------------------------------------------------------------------
  203. These are web audio API variables. They connect the web audio
  204. api to the audio source allowing for visualization extensions.
  205. These variables are public and to be used for extensions.
  206. Initializes the variables if they are available.
  207. --------------------------------------------------------------------------*/
  208. context: '',
  209. analyser: '',
  210. source: ''
  211. };
  212. /*--------------------------------------------------------------------------
  213. Used with SoundCloud to copy over the user config and add
  214. extra data so it doesn't interfere with the actual user
  215. config.
  216. --------------------------------------------------------------------------*/
  217. var temp_user_config = {};
  218. /*
  219. |--------------------------------------------------------------------------
  220. | PUBLIC METHODS
  221. |--------------------------------------------------------------------------
  222. | These methods are available to the developer. They allow the developer
  223. | to change certain attributes if needed and configure the library.
  224. */
  225. /*--------------------------------------------------------------------------
  226. The main init function. The user will call this through
  227. Amplitude.init({}) and pass in their settings.
  228. Public Accessor: Amplitude.init( user_config_json );
  229. @param user_config A JSON object of user defined values that help
  230. configure and initialize AmplitudeJS.
  231. --------------------------------------------------------------------------*/
  232. function publicInit( user_config ){
  233. /*
  234. Checks to see if Amplitude has been initialized.
  235. If it hasn't then we can initialize AmplitudeJS.
  236. The reason we check is so the same event handler
  237. isn't bound twice to the same element.
  238. */
  239. if( !config.initialized ){
  240. /*
  241. Initializes debugging right away so we can use it for the rest
  242. of the configuration.
  243. */
  244. config.debug = ( user_config.debug != undefined ? user_config.debug : false );
  245. /*
  246. Checks for dynamic mode right away. This will determine whether
  247. not having songs is a critical error or not since dynamic mode
  248. allows you to play songs by passing them in dynamically.
  249. */
  250. config.dynamic_mode = ( user_config.dynamic_mode != undefined ? user_config.dynamic_mode : false );
  251. /*
  252. To use visualizations with Amplitude, the user will have to explicitly state
  253. that their player uses visualizations. Reason being is that the AudioContext
  254. and other filters can really mess up functionality if the user is not prepared
  255. to have them operate on their audio element. If set to true, then the
  256. AudioContext and the other necessary elements will be bound for the Web Audio API
  257. to handle the visualization processing.
  258. */
  259. config.use_visualizations = ( user_config.use_visualizations != undefined ? user_config.use_visualizations : false );
  260. /*
  261. If the browser supports it and the user wants to use
  262. visualizations, then they can run visualizations. If
  263. the browser does not support the Web Audio API and the
  264. user has debug turned on, write to the console.
  265. */
  266. if( window.AudioContext && config.use_visualizations ){
  267. config.context = new AudioContext();
  268. config.analyser = config.context.createAnalyser();
  269. config.source = config.context.createMediaElementSource( config.active_song );
  270. config.source.connect( config.analyser );
  271. config.analyser.connect( config.context.destination );
  272. config.active_song.crossOrigin = "anonymous";
  273. }else{
  274. if( !window.AudioContext ){
  275. privateWriteDebugMessage( 'This browser does not support the Web Audio API' );
  276. }
  277. }
  278. /*
  279. The first step in setting up Amplitude is copying over all of
  280. the song objects that the user wants to use.
  281. */
  282. var ready = false;
  283. /*
  284. This copies over all of the user defined songs and adds them
  285. to the amplitude config.
  286. First check is to see if Amplitude is in Dynamic Mode which
  287. means that the user will be selecting sending songs dynamically
  288. and using a global control set to control the functionality.
  289. This is the ONLY scenario that doesn't require song(s) object.
  290. */
  291. if( !user_config.dynamic_mode ){
  292. /*
  293. Checks to see if the user defined any songs.
  294. If there are no song definitions, then it's
  295. a critical error since Amplitude needs that to
  296. run.
  297. */
  298. if( user_config.songs ){
  299. /*
  300. Makes sure the songs length is not 0, meaning
  301. that there is at least 1 song.
  302. */
  303. if( user_config.songs.length != 0 ){
  304. /*
  305. Copies over the user defined songs. and prepares
  306. Amplitude for the rest of the configuration.
  307. */
  308. config.songs = user_config.songs;
  309. ready = true;
  310. }else{
  311. privateWriteDebugMessage( 'Please add some songs, to your songs object!' );
  312. }
  313. }else{
  314. privateWriteDebugMessage( 'Please provide a songs object for AmplitudeJS to run!' );
  315. }
  316. }else{
  317. /*
  318. We are ready to copy over the rest of the information
  319. since we are in dynamic mode.
  320. */
  321. ready = true;
  322. }
  323. /*
  324. When the preliminary config is ready, we are rady to proceed.
  325. */
  326. if( ready ){
  327. /*
  328. Copies over the soundcloud information to the global config
  329. which will determine where we go from there.
  330. */
  331. config.soundcloud_client = ( user_config.soundcloud_client != undefined ? user_config.soundcloud_client : '' );
  332. config.soundcloud_use_art = ( user_config.soundcloud_use_art != undefined ? user_config.soundcloud_use_art : '' );
  333. /*
  334. If the user provides a soundcloud client then we assume that
  335. there are URLs in their songs that will reference SoundcCloud.
  336. We then copy over the user config they provided to the
  337. temp_user_config so we don't mess up the global or their configs
  338. and load the soundcloud information.
  339. */
  340. if( config.soundcloud_client != '' ){
  341. temp_user_config = user_config;
  342. /*
  343. Load up SoundCloud for use with AmplitudeJS.
  344. */
  345. privateLoadSoundcloud();
  346. }else{
  347. /*
  348. The user is not using Soundcloud with Amplitude at this point
  349. so we just finish the configuration with the users's preferences.
  350. */
  351. privateSetConfig( user_config );
  352. }
  353. }
  354. }
  355. }
  356. /*--------------------------------------------------------------------------
  357. Allows the user to turn on debugging.
  358. Public Accessor: Amplitude.setDebug( bool );
  359. @param BOOL state Turns debugging on and off.
  360. --------------------------------------------------------------------------*/
  361. function publicSetDebug( state ){
  362. /*
  363. Sets the global config debug on or off.
  364. */
  365. config.debug = state;
  366. }
  367. /*--------------------------------------------------------------------------
  368. Returns the active song meta data for the user to do what is
  369. needed.
  370. Public Accessor: Amplitude.getActiveSongMetadata();
  371. @returns JSON Object with the active song information
  372. --------------------------------------------------------------------------*/
  373. function publicGetActiveSongMetadata(){
  374. return config.active_metadata;
  375. }
  376. /*--------------------------------------------------------------------------
  377. Registers a visualization and sets that visualization's
  378. preferences. When creating a visualization, you can set certain
  379. preferences that the user can overwrite similar to Amplitude.
  380. Public Accessor: Amplitude.registerVisualization( visualization, preferences )
  381. @param visualzation A visualization object that gets registered
  382. with Amplitude
  383. @param preferences A JSON object of preferences relating to the
  384. visualization
  385. --------------------------------------------------------------------------*/
  386. function publicRegisterVisualization( visualization, preferences ){
  387. /*
  388. Adds the visualization to the global config so it knows
  389. it can be used when playing songs.
  390. getID is a public function for getting a visualization's id.
  391. It becomes the key to access the visualization.
  392. */
  393. config.visualizations[ visualization.getID ] = visualization;
  394. /*
  395. If defined, set the visualization preferences.
  396. setPreferences is a public function for connecting
  397. to a user defined visualization.
  398. */
  399. if( preferences != undefined ){
  400. visualization.setPreferences( preferences );
  401. }
  402. }
  403. /*--------------------------------------------------------------------------
  404. Changes the active visualization. Could be called from a
  405. user defined dropdown or whatever way the user wants to change a
  406. visualization dynamically.
  407. Public Accessor: Amplitude.changeVisualization( visualization )
  408. @param string visualization The name of the visualization
  409. that should be used.
  410. --------------------------------------------------------------------------*/
  411. function publicChangeActiveVisualization( visualization ){
  412. /*
  413. First we stop the active visualization. If the visualization
  414. is set up correctly, it should halt all callbacks, and clear
  415. the amplitude-visualization element.
  416. */
  417. privateStopVisualization();
  418. /*
  419. Next we set the active visualization in the config.
  420. */
  421. config.active_visualization = visualization;
  422. /*
  423. We then start the visualization hooks again. This should
  424. insert itself into the amplitude-visualization element
  425. and bind the proper hooks.
  426. */
  427. privateStartVisualization();
  428. }
  429. /*--------------------------------------------------------------------------
  430. Checks to see if the current browser is capable of running
  431. visualizations. If the AudioContext is available, then the browser
  432. can play the visualization.
  433. Public Accessor: Amplitude.visualizationCapable()
  434. @returns BOOL true if the browser can play the visualization and false
  435. if the browser cannot.
  436. --------------------------------------------------------------------------*/
  437. function publicVisualizationCapable(){
  438. if ( !window.AudioContext ) {
  439. return false;
  440. }else{
  441. return true;
  442. }
  443. }
  444. /*--------------------------------------------------------------------------
  445. Returns a song in the songs array at that index
  446. Public Accessor: Amplitude.getSongByIndex( song_index )
  447. @param int index The integer for the index of the
  448. song in the songs array.
  449. @returns JSON representation for the song at a specific index.
  450. --------------------------------------------------------------------------*/
  451. function publicGetSongByIndex( index ){
  452. return config.songs[index];
  453. }
  454. /*--------------------------------------------------------------------------
  455. Adds a song to the end of the config array. This will allow Amplitude
  456. to play the song in a playlist type setting.
  457. Public Accessor: Amplitude.addSong( song_json )
  458. @param song JSON representation of a song.
  459. @returns int New index of the song.
  460. --------------------------------------------------------------------------*/
  461. function publicAddSong( song ){
  462. config.songs.push( song );
  463. return config.songs.length - 1;
  464. }
  465. /*--------------------------------------------------------------------------
  466. When you pass a song object it plays that song right awawy. It sets
  467. the active song in the config to the song you pass in and synchronizes
  468. the visuals.
  469. Public Accessor: Amplitude.playNow( song_json )
  470. @param song JSON representation of a song.
  471. --------------------------------------------------------------------------*/
  472. function publicPlayNow( song ){
  473. /*
  474. Makes sure the song object has a URL associated with it
  475. or there will be nothing to play.
  476. */
  477. if( song.url ){
  478. config.active_song.src = song.url;
  479. config.active_metadata = song;
  480. config.active_album = song.album;
  481. }else{
  482. privateWriteDebugMessage('The song needs to have a URL!');
  483. }
  484. /*
  485. Sets the main song control status visual
  486. */
  487. privateChangePlayPauseState('playing');
  488. /*
  489. Calls the song change method that configures everything necessary for
  490. Amplitude when the song changes.
  491. */
  492. privateAfterSongChanges();
  493. }
  494. /*--------------------------------------------------------------------------
  495. Allows the user to play whatever the active song is directly
  496. through Javascript. Normally ALL of Amplitude functions that access
  497. the core features are called through event handlers. However when in
  498. dynamic mode is enabled, you will need to call this directly.
  499. WARNING: Accessible ONLY if dynmaic mode is turned on.
  500. Public Accessor: Amplitude.play();
  501. --------------------------------------------------------------------------*/
  502. function publicPlay(){
  503. if( config.dynamic_mode ){
  504. privatePlay();
  505. }
  506. }
  507. /*--------------------------------------------------------------------------
  508. Allows the user to pause whatever the active song is directly
  509. through Javascript. Normally ALL of Amplitude functions that access
  510. the core features are called through event handlers. However when in
  511. dynamic mode is enabled, you will need to call this directly.
  512. WARNING: Accessible ONLY if dynmaic mode is turned on.
  513. Public Accessor: Amplitude.pause();
  514. --------------------------------------------------------------------------*/
  515. function publicPause(){
  516. if( config.dynamic_mode ){
  517. privatePause();
  518. }
  519. }
  520. function publicGetAnalyser(){
  521. return config.analyser;
  522. }
  523. /*
  524. |--------------------------------------------------------------------------
  525. | INITIALIZATION FUNCTIONS
  526. |--------------------------------------------------------------------------
  527. | CALLED ON INITIALIZATION
  528. |
  529. | These functions are called on initialization and configure the base
  530. | functionality for Amplitude. They init event handlers and set up the
  531. | song time visualizations.
  532. */
  533. /*--------------------------------------------------------------------------
  534. Binds all AmplitudeJS event handlers to their respective elements.
  535. This is a special function that is ONLY called once the document
  536. is initialized. If it is called more than once than more than one
  537. event handler will be bound to an individual element causing chaos.
  538. If the navigator is a mobile device, the event bound for user
  539. interaction is 'touchstart', otherwise it is 'click' for the majority
  540. of the AmplitudeJS elements.
  541. The 'input' interaction is used for the HTML5 Range song sliders.
  542. The events 'timeupdate' and 'ended' are bound to the javascript
  543. audio element.
  544. --------------------------------------------------------------------------*/
  545. function privateInitializeEventHandlers(){
  546. /*
  547. On time update for the audio element, update visual displays that
  548. represent the time on either a visualized element or time display.
  549. */
  550. config.active_song.addEventListener('timeupdate', privateUpdateTime );
  551. /*
  552. When the audio element has ended playing, we handle the song
  553. ending. In a single song or multiple modular song instance,
  554. this just synchronizes the visuals for time and song time
  555. visualization, but for a playlist it determines whether
  556. it should play the next song or not.
  557. */
  558. config.active_song.addEventListener('ended', privateHandleSongEnded );
  559. /*
  560. Binds handlers for play classes
  561. */
  562. var play_classes = document.getElementsByClassName("amplitude-play");
  563. for( var i = 0; i < play_classes.length; i++ ){
  564. if( /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent) ) {
  565. play_classes[i].addEventListener('touchstart', privatePlayClickHandle );
  566. }else{
  567. play_classes[i].addEventListener('click', privatePlayClickHandle );
  568. }
  569. }
  570. /*
  571. Binds handlers for pause classes
  572. */
  573. var pause_classes = document.getElementsByClassName("amplitude-pause");
  574. for( var i = 0; i < pause_classes.length; i++ ){
  575. if( /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent) ) {
  576. pause_classes[i].addEventListener('touchstart', privatePauseClickHandle );
  577. }else{
  578. pause_classes[i].addEventListener('click', privatePauseClickHandle );
  579. }
  580. }
  581. /*
  582. Binds handlers for stop classes
  583. */
  584. var stop_classes = document.getElementsByClassName("amplitude-stop");
  585. for( var i = 0; i < stop_classes.length; i++ ){
  586. if( /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent) ) {
  587. stop_classes[i].addEventListener('touchstart', privateStopClickHandle );
  588. }else{
  589. stop_classes[i].addEventListener('click', privateStopClickHandle );
  590. }
  591. }
  592. /*
  593. Binds handlers for play/pause classes
  594. */
  595. var play_pause_classes = document.getElementsByClassName("amplitude-play-pause");
  596. for( var i = 0; i < play_pause_classes.length; i++ ){
  597. if( /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent) ) {
  598. play_pause_classes[i].addEventListener('touchstart', privatePlayPauseClickHandle );
  599. }else{
  600. play_pause_classes[i].addEventListener('click', privatePlayPauseClickHandle );
  601. }
  602. }
  603. /*
  604. Binds handlers for mute classes
  605. WARNING: If iOS, we don't do anything because iOS does not allow the
  606. volume to be adjusted through anything except the buttons on the side of
  607. the device.
  608. */
  609. var mute_classes = document.getElementsByClassName("amplitude-mute");
  610. for( var i = 0; i < mute_classes.length; i++ ){
  611. if( /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent) ) {
  612. /*
  613. Checks for an iOS device and displays an error message if debugging
  614. is turned on.
  615. */
  616. if( /iPhone|iPad|iPod/i.test(navigator.userAgent) ) {
  617. privateWriteDebugMessage( 'iOS does NOT allow volume to be set through javascript: https://developer.apple.com/library/safari/documentation/AudioVideo/Conceptual/Using_HTML5_Audio_Video/Device-SpecificConsiderations/Device-SpecificConsiderations.html#//apple_ref/doc/uid/TP40009523-CH5-SW4' );
  618. }else{
  619. mute_classes[i].addEventListener('touchstart', privateMuteClickHandle );
  620. }
  621. }else{
  622. mute_classes[i].addEventListener('click', privateMuteClickHandle );
  623. }
  624. }
  625. /*
  626. Binds handlers for volume up classes
  627. WARNING: If iOS, we don't do anything because iOS does not allow the
  628. volume to be adjusted through anything except the buttons on the side of
  629. the device.
  630. */
  631. var volume_up_classes = document.getElementsByClassName("amplitude-volume-up");
  632. for( var i = 0; i < volume_up_classes.length; i++ ){
  633. if( /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent) ) {
  634. /*
  635. Checks for an iOS device and displays an error message if debugging
  636. is turned on.
  637. */
  638. if( /iPhone|iPad|iPod/i.test(navigator.userAgent) ) {
  639. privateWriteDebugMessage( 'iOS does NOT allow volume to be set through javascript: https://developer.apple.com/library/safari/documentation/AudioVideo/Conceptual/Using_HTML5_Audio_Video/Device-SpecificConsiderations/Device-SpecificConsiderations.html#//apple_ref/doc/uid/TP40009523-CH5-SW4' );
  640. }else{
  641. volume_up_classes[i].addEventListener('touchstart', privateVolumeUpClickHandle );
  642. }
  643. }else{
  644. volume_up_classes[i].addEventListener('click', privateVolumeUpClickHandle );
  645. }
  646. }
  647. /*
  648. Binds handlers for volume down classes
  649. WARNING: If iOS, we don't do anything because iOS does not allow the
  650. volume to be adjusted through anything except the buttons on the side of
  651. the device.
  652. */
  653. var volume_down_classes = document.getElementsByClassName("amplitude-volume-down");
  654. for( var i = 0; i < volume_down_classes.length; i++ ){
  655. if( /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent) ) {
  656. /*
  657. Checks for an iOS device and displays an error message if debugging
  658. is turned on.
  659. */
  660. if( /iPhone|iPad|iPod/i.test(navigator.userAgent) ) {
  661. privateWriteDebugMessage( 'iOS does NOT allow volume to be set through javascript: https://developer.apple.com/library/safari/documentation/AudioVideo/Conceptual/Using_HTML5_Audio_Video/Device-SpecificConsiderations/Device-SpecificConsiderations.html#//apple_ref/doc/uid/TP40009523-CH5-SW4' );
  662. }else{
  663. volume_down_classes[i].addEventListener('touchstart', privateVolumeDownClickHandle );
  664. }
  665. }else{
  666. volume_down_classes[i].addEventListener('click', privateVolumeDownClickHandle );
  667. }
  668. }
  669. /*
  670. Binds handlers for song slider classes. The song sliders are HTML 5
  671. Range Elements. This event fires everytime a slider has changed.
  672. */
  673. var song_sliders = document.getElementsByClassName("amplitude-song-slider");
  674. for( var i = 0; i < song_sliders.length; i++ ){
  675. song_sliders[i].addEventListener('input', privateSongStatusBarInputHandle );
  676. }
  677. /*
  678. Binds handlers for volume slider classes. The volume sliders are HTML 5
  679. Range Elements. This event fires everytime a slider has changed.
  680. WARNING: If iOS, we don't do anything because iOS does not allow the
  681. volume to be adjusted through anything except the buttons on the side of
  682. the device.
  683. */
  684. var volume_sliders = document.getElementsByClassName("amplitude-volume-slider");
  685. for( var i = 0; i < volume_sliders.length; i++ ){
  686. /*
  687. Checks for an iOS device and displays an error message if debugging
  688. is turned on.
  689. */
  690. if( /iPhone|iPad|iPod/i.test(navigator.userAgent) ) {
  691. privateWriteDebugMessage( 'iOS does NOT allow volume to be set through javascript: https://developer.apple.com/library/safari/documentation/AudioVideo/Conceptual/Using_HTML5_Audio_Video/Device-SpecificConsiderations/Device-SpecificConsiderations.html#//apple_ref/doc/uid/TP40009523-CH5-SW4' );
  692. }else{
  693. volume_sliders[i].addEventListener('input', privateVolumeInputHandle );
  694. }
  695. }
  696. /*
  697. Binds handlers for next button classes.
  698. */
  699. var next_classes = document.getElementsByClassName("amplitude-next");
  700. for( var i = 0; i < next_classes.length; i++ ){
  701. if( /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent) ) {
  702. next_classes[i].addEventListener('touchstart', privateNextClickHandle );
  703. }else{
  704. next_classes[i].addEventListener('click', privateNextClickHandle );
  705. }
  706. }
  707. /*
  708. Binds handlers for previous button classes.
  709. */
  710. var prev_classes = document.getElementsByClassName("amplitude-prev");
  711. for( var i = 0; i < prev_classes.length; i++ ){
  712. if( /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent) ) {
  713. prev_classes[i].addEventListener('touchstart', privatePrevClickHandle );
  714. }else{
  715. prev_classes[i].addEventListener('click', privatePrevClickHandle );
  716. }
  717. }
  718. /*
  719. Binds handlers for shuffle button classes.
  720. */
  721. var shuffle_classes = document.getElementsByClassName("amplitude-shuffle");
  722. for( var i = 0; i < shuffle_classes.length; i++ ){
  723. shuffle_classes[i].classList.remove('amplitude-shuffle-on');
  724. shuffle_classes[i].classList.add('amplitude-shuffle-off');
  725. if( /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent) ) {
  726. shuffle_classes[i].addEventListener('touchstart', privateShuffleClickHandle );
  727. }else{
  728. shuffle_classes[i].addEventListener('click', privateShuffleClickHandle );
  729. }
  730. }
  731. /*
  732. Binds handlers for repeat button classes.
  733. */
  734. var repeat_classes = document.getElementsByClassName("amplitude-repeat");
  735. for( var i = 0; i < repeat_classes.length; i++ ){
  736. repeat_classes[i].classList.remove('amplitude-repeat-on');
  737. repeat_classes[i].classList.add('amplitude-repeat-off');
  738. if( /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent) ) {
  739. repeat_classes[i].addEventListener('touchstart', privateRepeatClickHandle );
  740. }else{
  741. repeat_classes[i].addEventListener('click', privateRepeatClickHandle );
  742. }
  743. }
  744. }
  745. /*--------------------------------------------------------------------------
  746. Sets up all of the song time visualizations. This is the only time
  747. that AmplitudeJS will add an element to the page. AmplitudeJS will
  748. add an element inside of the song time visualization element that will
  749. expand proportionally to the amount of time elapsed on the active
  750. audio, thus visualizing the song time. This element is NOT user
  751. interactive. To have the user scrub the time, they will have to
  752. style and implement a song time slider with an HTML 5 Range Element.
  753. --------------------------------------------------------------------------*/
  754. function privateInitializeSongTimeVisualizations(){
  755. /*
  756. Sets up song time visualizations
  757. */
  758. var song_time_visualizations = document.getElementsByClassName("amplitude-song-time-visualization");
  759. /*
  760. Iterates through all of the amplitude-song-time-visualization
  761. elements adding a new div with a class of
  762. 'amplitude-song-time-visualization-status' that will expand
  763. inside of the 'amplitude-song-time-visualization' element.
  764. */
  765. for( var i = 0; i < song_time_visualizations.length; i++ ){
  766. /*
  767. Creates new element
  768. */
  769. var status = document.createElement('div');
  770. /*
  771. Adds class and attributes
  772. */
  773. status.classList.add('amplitude-song-time-visualization-status');
  774. status.setAttribute( 'style', 'width: 0px' );
  775. /*
  776. Appends the element as a child element.
  777. */
  778. song_time_visualizations[i].appendChild( status );
  779. }
  780. }
  781. /*
  782. |--------------------------------------------------------------------------
  783. | EVENT HANDLER FUNCTIONS
  784. |--------------------------------------------------------------------------
  785. | These functions handle the events that we bound to each element and
  786. | prepare for a function to be called. For example, if a click is on a song
  787. | that doens't equal the index of the active song, it sets the active song
  788. | to be what is needed and then play is called. These kind of act
  789. | like filters/middleware.
  790. */
  791. /*--------------------------------------------------------------------------
  792. HANDLER FOR: 'amplitude-play-pause';
  793. Handles a click on a play/pause element. This element toggles
  794. functionality based on the state of the song.
  795. TODO: Clean up this function and break out into helper functions
  796. --------------------------------------------------------------------------*/
  797. function privatePlayPauseClickHandle(){
  798. /*
  799. If there are multiple play/pause buttons on the screen,
  800. you can have them assigned to an individual song.
  801. With the amplitude-song-index attribute, you can assign
  802. the play buttons a song to play out of the songs array.
  803. */
  804. var playing_song_index = this.getAttribute('amplitude-song-index');
  805. /*
  806. If there is a new song to be played. Since this handler
  807. handles ALL events on a play-pause element, we have
  808. to check to see if there is a song that is being
  809. played already and the user clicked a different element
  810. of the same type that is supposed to play a new song.
  811. */
  812. if( privateCheckNewSong( playing_song_index) ){
  813. privateChangeSong( playing_song_index );
  814. /*
  815. Sets all song status and play/pause button visuals
  816. to sync with the newly played song.
  817. */
  818. privateSetPlayPauseButtonsToPause();
  819. privateResetSongStatusSliders();
  820. privateChangePlayPauseState('playing');
  821. /*
  822. Starts the song visualization if there is one.
  823. */
  824. privateStartVisualization();
  825. privatePlay();
  826. }else{
  827. if( config.active_song.paused ){
  828. privateChangePlayPauseState('playing');
  829. /*
  830. Starts the song visualization if there is one.
  831. */
  832. privateStartVisualization();
  833. privatePlay( this.getAttribute('amplitude-song-index') );
  834. }else{
  835. privateChangePlayPauseState('paused');
  836. privatePause();
  837. }
  838. }
  839. }
  840. /*--------------------------------------------------------------------------
  841. HANDLER FOR: 'amplitude-play'
  842. Handles a click on a play element.
  843. TODO: Check to see if we should start the visualizations and run the
  844. play or check to see if the song clicked is not new and paused before
  845. we run those otherwise it might cause an error like re-starting the
  846. song if play is clicked twice.
  847. --------------------------------------------------------------------------*/
  848. function privatePlayClickHandle(){
  849. /*
  850. Gets the attribute for song index so we can check if
  851. there is a need to change the song. In some scenarios
  852. there might be multiple play classes on the page. In that
  853. case it is possible the user could click a different play
  854. class and change the song.
  855. */
  856. var playing_song_index = this.getAttribute('amplitude-song-index');
  857. /*
  858. We set the new song if the user clicked a song with a different
  859. index. If it's the same as what's playing then we don't set anything.
  860. If it's different we reset all song sliders.
  861. */
  862. if( privateCheckNewSong( playing_song_index ) ){
  863. privateChangeSong( playing_song_index );
  864. privateResetSongStatusSliders();
  865. }
  866. /*
  867. Start the visualizations for the song.
  868. */
  869. privateStartVisualization();
  870. /*
  871. Play the song through the core play function.
  872. */
  873. privatePlay();
  874. }
  875. /*--------------------------------------------------------------------------
  876. HANDLER FOR: 'amplitude-pause'
  877. Handles a click on a pause element.
  878. TODO: Check to see that the pause element has an index and if that
  879. index matches the current song being played. If it's different then
  880. we should disable it? If the user clicks on song-index=1 pause and
  881. song-index=2 is being played, is it right to pause?
  882. --------------------------------------------------------------------------*/
  883. function privatePauseClickHandle(){
  884. /*
  885. Calls the core function for pause
  886. */
  887. privatePause();
  888. }
  889. /*--------------------------------------------------------------------------
  890. HANDLER FOR: 'amplitude-stop'
  891. Handles a click on a stop element.
  892. --------------------------------------------------------------------------*/
  893. function privateStopClickHandle(){
  894. /*
  895. Calls the helper function to stop
  896. the visualization.
  897. */
  898. privateStopVisualization();
  899. /*
  900. Calls the core function for stop
  901. */
  902. privateStop();
  903. }
  904. /*--------------------------------------------------------------------------
  905. HANDLER FOR: 'amplitude-mute'
  906. Handles a click on a mute element.
  907. TODO: Add a class if muted to this element of amplitude-mute. That way
  908. the designer can style the element if amplitude is muted like the typical
  909. volume with a line through it.
  910. TODO: Standardize the privateSetVolume parameter so it doesn't need
  911. to be converted by the privateSetVolume function. Right now it converts
  912. up then down again which makes no sense.
  913. --------------------------------------------------------------------------*/
  914. function privateMuteClickHandle(){
  915. /*
  916. If the current volume in the config is 0, we set the volume to the
  917. pre_mute level. This means that the audio is already muted and
  918. needs to be restored to the pre_mute level.
  919. Otherwise, we set pre_mute volume to the current volume
  920. and set the config volume to 0, muting the audio.
  921. */
  922. if( config.volume == 0 ){
  923. config.volume = config.pre_mute_volume;
  924. }else{
  925. config.pre_mute_volume = config.volume;
  926. config.volume = 0;
  927. }
  928. /*
  929. Calls the core function to set the volume to the computed value
  930. based on the user's intent.
  931. */
  932. privateSetVolume( config.volume * 100 );
  933. /*
  934. Syncs the volume sliders so the visuals align up with the functionality.
  935. If the volume is at 0, then the sliders should represent that so the user
  936. has the right starting point.
  937. */
  938. privateSyncVolumeSliders();
  939. }
  940. /*--------------------------------------------------------------------------
  941. HANDLER FOR: 'amplitude-volume-up'
  942. Handles a click on a volume up element.
  943. TODO: Standardize the privateSetVolume parameter so it doesn't need
  944. to be converted by the privateSetVolume function. Right now it converts
  945. up then down again which makes no sense.
  946. --------------------------------------------------------------------------*/
  947. function privateVolumeUpClickHandle(){
  948. /*
  949. The volume range is from 0 to 1 for an audio element. We make this
  950. a base of 100 for ease of working with.
  951. If the new value is less than 100, we use the new calculated
  952. value which gets converted to the proper unit for the audio element.
  953. If the new value is greater than 100, we set the volume to 1 which
  954. is the max for the audio element.
  955. */
  956. if( ( ( config.volume * 100 ) + config.volume_increment ) <= 100 ){
  957. config.volume = config.volume + ( config.volume_increment / 100 );
  958. }else{
  959. config.volume = 1;
  960. }
  961. /*
  962. Calls the core function to set the volume to the computed value
  963. based on the user's intent.
  964. */
  965. privateSetVolume( config.volume * 100 );
  966. /*
  967. Syncs the volume sliders so the visuals align up with the functionality.
  968. If the volume is at 0, then the sliders should represent that so the user
  969. has the right starting point.
  970. */
  971. privateSyncVolumeSliders();
  972. }
  973. /*--------------------------------------------------------------------------
  974. HANDLER FOR: 'amplitude-volume-down'
  975. Handles a click on a volume down element.
  976. TODO: Standardize the privateSetVolume parameter so it doesn't need
  977. to be converted by the privateSetVolume function. Right now it converts
  978. up then down again which makes no sense.
  979. --------------------------------------------------------------------------*/
  980. function privateVolumeDownClickHandle(){
  981. /*
  982. The volume range is from 0 to 1 for an audio element. We make this
  983. a base of 100 for ease of working with.
  984. If the new value is less than 0, we use the new calculated
  985. value which gets converted to the proper unit for the audio element.
  986. If the new value is greater than 0, we set the volume to 0 which
  987. is the min for the audio element.
  988. */
  989. if( ( ( config.volume * 100 ) - config.volume_decrement ) > 0 ){
  990. config.volume = config.volume - ( config.volume_decrement / 100 );
  991. }else{
  992. config.volume = 0;
  993. }
  994. /*
  995. Calls the core function to set the volume to the computed value
  996. based on the user's intent.
  997. */
  998. privateSetVolume( config.volume * 100 );
  999. /*
  1000. Syncs the volume sliders so the visuals align up with the functionality.
  1001. If the volume is at 0, then the sliders should represent that so the user
  1002. has the right starting point.
  1003. */
  1004. privateSyncVolumeSliders();
  1005. }
  1006. /*--------------------------------------------------------------------------
  1007. HANDLER FOR: 'amplitude-volume-slider'
  1008. Handles an input change for a volume slider.
  1009. TODO: Standardize the privateSetVolume parameter so it doesn't need
  1010. to be converted by the privateSetVolume function. Right now it converts
  1011. up then down again which makes no sense.
  1012. --------------------------------------------------------------------------*/
  1013. function privateVolumeInputHandle(){
  1014. /*
  1015. The range slider has a range of 1 to 100 so we get the value and
  1016. convert it to a range of 0 to 1 and set the volume.
  1017. */
  1018. config.volume = ( this.value / 100 );
  1019. /*
  1020. Calls the core function to set the volume to the computed value
  1021. based on the user's intent.
  1022. */
  1023. privateSetVolume( this.value );
  1024. /*
  1025. Syncs the volume sliders so the visuals align up with the functionality.
  1026. If the volume is at 0, then the sliders should represent that so the user
  1027. has the right starting point.
  1028. */
  1029. privateSyncVolumeSliders();
  1030. }
  1031. /*--------------------------------------------------------------------------
  1032. HANDLER FOR: 'amplitude-song-slider'
  1033. Handles an input change for a song slider.
  1034. TODO: Make an attribute that allows for multiple main song sliders
  1035. allowing the active playing song to be scrubbed from multiple locations
  1036. on the page and is always in sync.
  1037. --------------------------------------------------------------------------*/
  1038. function privateSongStatusBarInputHandle(){
  1039. /*
  1040. We only adjust the time if the song is playing. It wouldn't make
  1041. sense if we adjusted the time while it was paused.
  1042. */
  1043. if( !config.active_song.paused ){
  1044. /*
  1045. We first check if the song slider is the only one on the page.
  1046. If it is, we can safely assume that the slider is synced with
  1047. the song's progression and adjust the song.
  1048. */
  1049. if( this.getAttribute('amplitude-singular-song-slider') ){
  1050. privateSetSongLocation( this.value );
  1051. }
  1052. /*
  1053. If the song slider has a song index, we check to see if it matches
  1054. the active song index. If it does, then adjust the song location.
  1055. We do this so we can have multiple Amplitude players on the same page
  1056. and have the slider relate to the song playing.
  1057. */
  1058. if( this.getAttribute('amplitude-song-index') == config.active_index ){
  1059. privateSetSongLocation( this.value );
  1060. }
  1061. }
  1062. }
  1063. /*--------------------------------------------------------------------------
  1064. HANDLER FOR: 'amplitude-next'
  1065. Handles a click for the next song.
  1066. --------------------------------------------------------------------------*/
  1067. function privateNextClickHandle(){
  1068. /*
  1069. Runs the before_next callback for the user to hook into.
  1070. */
  1071. privateRunCallback('before_next');
  1072. /*
  1073. Stop active song since we are moving to the next song.
  1074. */
  1075. privateStop();
  1076. /*
  1077. First we check if shuffle is on. If it is we use the shuffle array
  1078. to select the next song. Otherwise, we go with the standard song
  1079. array.
  1080. Loop around songs array if at the end. We need to check if the next
  1081. song is within array. Otherwise we reset it to 0.
  1082. Set new song
  1083. */
  1084. if( config.shuffle_on ){
  1085. if( parseInt( config.shuffle_active_index ) + 1 < config.shuffle_list.length ){
  1086. /*
  1087. Gets the new index in the shuffle array for the song we need.
  1088. */
  1089. var newIndex = parseInt( config.shuffle_active_index ) + 1;
  1090. /*
  1091. Check new album
  1092. */
  1093. privateCheckNewAlbum( config.suffle_list[ newIndex ].album );
  1094. /*
  1095. Sets the new song information in the config, so everything
  1096. is ready to be changed.
  1097. */
  1098. privateSetActiveSongInformation( newIndex, config.shuffle_on );
  1099. /*
  1100. Checks to see if there is a new album to be played. If there
  1101. is we fire the after_album_change callback which allows the
  1102. developer to handler album changes which could also mean multiple
  1103. playlists.
  1104. */
  1105. if( config.album_change ){
  1106. privateRunCallback('after_album_change');
  1107. config.album_change = false;
  1108. }
  1109. /*
  1110. Sets the new shuffle active index to be used in the shuffled songs object.
  1111. */
  1112. config.shuffle_active_index = newIndex;
  1113. }else{
  1114. /*
  1115. Check new album
  1116. */
  1117. privateCheckNewAlbum( config.suffle_list[0].album );
  1118. /*
  1119. Sets the new song information in the config, so everything
  1120. is ready to be changed.
  1121. */
  1122. privateSetActiveSongInformation( 0, config.shuffle_on );
  1123. /*
  1124. Checks to see if there is a new album to be played. If there
  1125. is we fire the after_album_change callback which allows the
  1126. developer to handler album changes which could also mean multiple
  1127. playlists.
  1128. */
  1129. if( config.album_change ){
  1130. privateRunCallback('after_album_change');
  1131. config.album_change = false;
  1132. }
  1133. /*
  1134. Sets the new shuffle active index to be used in the shuffled songs object.
  1135. */
  1136. config.shuffle_active_index = 0;
  1137. }
  1138. }else{
  1139. if( parseInt(config.active_index) + 1 < config.songs.length ){
  1140. var newIndex = parseInt( config.active_index ) + 1;
  1141. /*
  1142. Check new album
  1143. */
  1144. privateCheckNewAlbum( config.songs[newIndex].album );
  1145. /*
  1146. Sets the new song information in the config, so everything
  1147. is ready to be changed.
  1148. */
  1149. privateSetActiveSongInformation( newIndex, config.shuffle_on );
  1150. /*
  1151. Checks to see if there is a new album to be played. If there
  1152. is we fire the after_album_change callback which allows the
  1153. developer to handler album changes which could also mean multiple
  1154. playlists.
  1155. */
  1156. if( config.album_change ){
  1157. privateRunCallback('after_album_change');
  1158. config.album_change = false;
  1159. }
  1160. /*
  1161. Sets the new active index to be used with the songs object
  1162. */
  1163. config.active_index = newIndex;
  1164. }else{
  1165. /*
  1166. Check new album
  1167. */
  1168. privateCheckNewAlbum( config.songs[0].album );
  1169. /*
  1170. Sets the new song information in the config, so everything
  1171. is ready to be changed.
  1172. */
  1173. privateSetActiveSongInformation( 0, config.shuffle_on );
  1174. /*
  1175. Checks to see if there is a new album to be played. If there
  1176. is we fire the after_album_change callback which allows the
  1177. developer to handler album changes which could also mean multiple
  1178. playlists.
  1179. */
  1180. if( config.album_change ){
  1181. privateRunCallback('after_album_change');
  1182. config.album_change = false;
  1183. }
  1184. /*
  1185. Sets the new active index to be used with the songs object
  1186. */
  1187. config.active_index = 0;
  1188. }
  1189. }
  1190. /*
  1191. Sets the main song control status to be in sync with the current state
  1192. of the song.
  1193. */
  1194. privateChangePlayPauseState( 'playing' );
  1195. /*
  1196. Runs the song change method to sync everything necessary.
  1197. */
  1198. privateAfterSongChanges();
  1199. /*
  1200. Fires the after_next callback for users to hook into.
  1201. */
  1202. privateRunCallback('after_next');
  1203. }
  1204. /*--------------------------------------------------------------------------
  1205. HANDLER FOR: 'amplitude-prev'
  1206. Handles a click for the previous song.
  1207. --------------------------------------------------------------------------*/
  1208. function privatePrevClickHandle(){
  1209. /*
  1210. Runs the before_prev callback for the user to hook into.
  1211. */
  1212. privateRunCallback('before_prev');
  1213. /*
  1214. Stop active song since we are moving to the previous song.
  1215. */
  1216. privateStop();
  1217. /*
  1218. First we check if shuffle is on. If it is we use the shuffle array to
  1219. select the previous song. Otherwise, we go with the sandard song array.
  1220. Loop around songs array if at the beginning. We need to check if the next
  1221. song is within array. Otherwise we reset it to the end of the songs.
  1222. Set new song
  1223. */
  1224. if( config.shuffle_on ){
  1225. if( ( parseInt( config.shuffle_active_index ) - 1 ) >= 0 ){
  1226. /*
  1227. Gets the new index in the shuffle array for the song we need.
  1228. */
  1229. var newIndex = parseInt( config.shuffle_active_index ) - 1;
  1230. /*
  1231. Check new album
  1232. */
  1233. privateCheckNewAlbum( config.suffle_list[ newIndex ].album );
  1234. /*
  1235. Sets the new song information in the config, so everything
  1236. is ready to be changed.
  1237. */
  1238. privateSetActiveSongInformation( newIndex, config.shuffle_on );
  1239. /*
  1240. Checks to see if there is a new album to be played. If there
  1241. is we fire the after_album_change callback which allows the
  1242. developer to handler album changes which could also mean multiple
  1243. playlists.
  1244. */
  1245. if( config.album_change ){
  1246. privateRunCallback('after_album_change');
  1247. config.album_change = false;
  1248. }
  1249. /*
  1250. Sets the new shuffle active index to be used in the shuffled songs object.
  1251. */
  1252. config.shuffle_active_index = newIndex;
  1253. }else{
  1254. /*
  1255. Gets the new index in the shuffle array for the song we need.
  1256. */
  1257. var newIndex = parseInt( config.shuffle_list.length - 1 );
  1258. /*
  1259. Check new album
  1260. */
  1261. privateCheckNewAlbum( config.suffle_list[ newIndex ].album );
  1262. /*
  1263. Sets the new song information in the config, so everything
  1264. is ready to be changed.
  1265. */
  1266. privateSetActiveSongInformation( newIndex, config.shuffle_on );
  1267. /*
  1268. Checks to see if there is a new album to be played. If there
  1269. is we fire the after_album_change callback which allows the
  1270. developer to handler album changes which could also mean multiple
  1271. playlists.
  1272. */
  1273. if( config.album_change ){
  1274. privateRunCallback('after_album_change');
  1275. config.album_change = false;
  1276. }
  1277. /*
  1278. Sets the new shuffle active index to be used in the shuffled songs object.
  1279. */
  1280. config.shuffle_active_index = newIndex;
  1281. }
  1282. }else{
  1283. if( ( parseInt( config.active_index ) - 1 ) >= 0 ){
  1284. var newIndex = parseInt( parseInt(config.active_index) - 1 );
  1285. /*
  1286. Check new album
  1287. */
  1288. privateCheckNewAlbum( config.songs[ newIndex ].album );
  1289. /*
  1290. Sets the new song information in the config, so everything
  1291. is ready to be changed.
  1292. */
  1293. privateSetActiveSongInformation( newIndex, config.shuffle_on );
  1294. /*
  1295. Checks to see if there is a new album to be played. If there
  1296. is we fire the after_album_change callback which allows the
  1297. developer to handler album changes which could also mean multiple
  1298. playlists.
  1299. */
  1300. if( config.album_change ){
  1301. privateRunCallback('after_album_change');
  1302. config.album_change = false;
  1303. }
  1304. /*
  1305. Sets the new active index to be used with the songs object
  1306. */
  1307. config.active_index = newIndex;
  1308. }else{
  1309. var newIndex = config.songs.length - 1;
  1310. /*
  1311. Check new album
  1312. */
  1313. privateCheckNewAlbum( config.songs[ newIndex ].album );
  1314. /*
  1315. Sets the new song information in the config, so everything
  1316. is ready to be changed.
  1317. */
  1318. privateSetActiveSongInformation( newIndex, config.shuffle_on );
  1319. /*
  1320. Checks to see if there is a new album to be played. If there
  1321. is we fire the after_album_change callback which allows the
  1322. developer to handler album changes which could also mean multiple
  1323. playlists.
  1324. */
  1325. if( config.album_change ){
  1326. privateRunCallback('after_album_change');
  1327. config.album_change = false;
  1328. }
  1329. /*
  1330. Sets the new active index to be used with the songs object
  1331. */
  1332. config.active_index = newIndex;
  1333. }
  1334. }
  1335. /*
  1336. Sets the main song control status to be in sync with the current state
  1337. of the song.
  1338. */
  1339. privateChangePlayPauseState( 'playing' );
  1340. /*
  1341. Runs the song change method to sync everything necessary.
  1342. */
  1343. privateAfterSongChanges();
  1344. /*
  1345. Fires the after_prev callback for users to hook into.
  1346. */
  1347. privateRunCallback('after_prev');
  1348. }
  1349. /*--------------------------------------------------------------------------
  1350. HANDLER FOR: 'ended' on main audio element.
  1351. When the song has ended, this method gets called.
  1352. If it's a one song instance, then we don't do anything.
  1353. If there are multiple songs, we check if shuffle is on
  1354. or if we should use the original songs array. Then we set
  1355. the next song and play it.
  1356. --------------------------------------------------------------------------*/
  1357. function privateHandleSongEnded(){
  1358. /*
  1359. Checks to see if repeat is on. If it's on, then we re-play the
  1360. current song. Otherwise we begin the process of playing the
  1361. next song in the list whether it's shuffle or regular list or
  1362. single song.
  1363. */
  1364. if( config.repeat ){
  1365. /*
  1366. Confirms stop of the active song
  1367. */
  1368. privateStop();
  1369. /*
  1370. Without changing the index, just prepares the
  1371. next song to play.
  1372. */
  1373. privateAfterSongChanges();
  1374. }else{
  1375. /*
  1376. Checks to see if there is more than one song.
  1377. */
  1378. if( config.songs.length > 1 ){
  1379. /*
  1380. Stops the active song
  1381. */
  1382. privateStop();
  1383. /*
  1384. Checks to see if shuffle mode is turned on.
  1385. */
  1386. if( config.shuffle_on ){
  1387. /*
  1388. Loop around shuffle array if at the end. We need to check if the next
  1389. song is within array. Otherwise we reset it to 0.
  1390. Set new song
  1391. */
  1392. if( parseInt( config.shuffle_active_index) + 1 < config.shuffle_list.length ){
  1393. var newIndex = parseInt( config.shuffle_active_index) + 1;
  1394. /*
  1395. Sets the active song information.
  1396. */
  1397. privateSetActiveSongInformation( newIndex, config.shuffle_on );
  1398. config.shuffle_active_index = parseInt(config.shuffle_active_index) + 1;
  1399. }else{
  1400. /*
  1401. Sets the active song information to the beginning of the
  1402. shuffle list
  1403. */
  1404. privateSetActiveSongInformation( 0, config.shuffle_on );
  1405. config.shuffle_active_index = 0;
  1406. }
  1407. }else{
  1408. /*
  1409. Loop around songs array if at the end. We need to check if the next
  1410. song is within array. Otherwise we reset it to 0.
  1411. Sets new song
  1412. */
  1413. if( parseInt(config.active_index) + 1 < config.songs.length ){
  1414. var newIndex = parseInt( config.active_index ) + 1;
  1415. /*
  1416. Sets the active song information
  1417. */
  1418. privateSetActiveSongInformation( newIndex, config.shuffle_on );
  1419. config.active_index = parseInt(config.active_index) + 1;
  1420. }else{
  1421. /*
  1422. Sets the active song information to the beginning of the
  1423. songs list
  1424. */
  1425. privateSetActiveSongInformation( 0, config.shuffle_on );
  1426. config.active_index = 0;
  1427. }
  1428. }
  1429. /*
  1430. Runs the song change function.
  1431. */
  1432. privateAfterSongChanges();
  1433. }else{
  1434. /*
  1435. If there is nothing coming up, pause the play
  1436. button and sync the current times. This will set the play pause
  1437. buttons to paused (stopped) state and the current times to
  1438. 0:00
  1439. */
  1440. privateSetPlayPauseButtonsToPause();
  1441. privateSyncCurrentTimes();
  1442. }
  1443. }
  1444. /*
  1445. Fire song ended event handler.
  1446. */
  1447. privateRunCallback('after_song_ended');
  1448. }
  1449. /*--------------------------------------------------------------------------
  1450. HANDLER FOR: 'timeupdate' on main audio element.
  1451. Everytime the active audio time updates, this function is called.
  1452. It will update the visual display for everything related to the time
  1453. and location compared to the duration of the song.
  1454. TODO: Change all querySelector functions to querySelectorAll functions
  1455. --------------------------------------------------------------------------*/
  1456. function privateUpdateTime(){
  1457. /*
  1458. Gets the current time of the song in seconds and minutes.
  1459. */
  1460. var current_seconds = ( Math.floor( config.active_song.currentTime % 60 ) < 10 ? '0' : '' ) +
  1461. Math.floor( config.active_song.currentTime % 60 );
  1462. var current_minutes = Math.floor( config.active_song.currentTime / 60 );
  1463. /*
  1464. Gets the current song's duration in seconds and minutes.
  1465. */
  1466. var song_duration_seconds = ( Math.floor( config.active_song.duration % 60 ) < 10 ? '0' : '' ) +
  1467. Math.floor( config.active_song.duration % 60 );
  1468. var song_duration_minutes = Math.floor( config.active_song.duration / 60 );
  1469. /*
  1470. Finds the current slider that represents the active song's time.
  1471. If there is only one song slider, than that's the one we adjust
  1472. to represent the song's current time, otherwise we find the one
  1473. with the same song index as the active song.
  1474. */
  1475. var current_slider = '';
  1476. if( document.querySelector('[amplitude-singular-song-slider="true"]') ){
  1477. current_slider = document.querySelector('[amplitude-singular-song-slider="true"]');
  1478. }else{
  1479. current_slider = document.querySelector('input[amplitude-song-index="'+config.active_index+'"]');
  1480. }
  1481. /*
  1482. When we find the right visualization, we set the value to the percentage of
  1483. completion only if the song isn't live. Since it's 'infinite' if
  1484. it's a live source, it wouldn't make sense to update the time
  1485. display.
  1486. */
  1487. if( !config.active_metadata.live ){
  1488. /*
  1489. Finds the current song time visualization. If there is only one
  1490. song time visualization, then that's the one we adjust to represent
  1491. the current song time's visualization. Otherwise we find the one
  1492. with the same song index as the active song.
  1493. */
  1494. if( document.querySelectorAll('[amplitude-single-song-time-visualization="true"]').length > 0 ){
  1495. current_song_time_visualization = document.querySelectorAll('[amplitude-single-song-time-visualization="true"]');
  1496. for( var i = 0; i < current_song_time_visualization.length; i++ ){
  1497. var current_song_time_visualization_status = current_song_time_visualization[i].querySelectorAll('.amplitude-song-time-visualization-status');
  1498. var visualization_width = current_song_time_visualization[i].offsetWidth;
  1499. current_song_time_visualization_status[0].setAttribute('style', 'width:' +( visualization_width * ( config.active_song.currentTime / config.active_song.duration ) ) + 'px');
  1500. }
  1501. }
  1502. if( document.querySelectorAll('.amplitude-song-time-visualization[amplitude-song-index="'+config.active_index+'"]').length > 0 ){
  1503. current_song_time_visualization = document.querySelectorAll('.amplitude-song-time-visualization[amplitude-song-index="'+config.active_index+'"]');
  1504. for( var i = 0; i < current_song_time_visualization.length; i++ ){
  1505. var current_song_time_visualization_status = current_song_time_visualization[i].querySelectorAll('.amplitude-song-time-visualization-status');
  1506. var visualization_width = current_song_time_visualization[i].offsetWidth;
  1507. current_song_time_visualization_status[0].setAttribute('style', 'width:' +( visualization_width * ( config.active_song.currentTime / config.active_song.duration ) ) + 'px');
  1508. }
  1509. }
  1510. }
  1511. /*
  1512. Looks for an element with attributes that define which part of
  1513. the time should be inserted into the html. If there is a singlular
  1514. display, we update the singular. If there are multiple, we only do
  1515. the first one on the query, it wouldn't make sense to do all of them.
  1516. */
  1517. if( document.querySelectorAll('[amplitude-single-current-minutes="true"]').length > 0 ){
  1518. var mainCurrentMinuteSelectors = document.querySelectorAll('[amplitude-single-current-minutes="true"]');
  1519. for( var i = 0; i < mainCurrentMinuteSelectors.length; i++ ){
  1520. mainCurrentMinuteSelectors[i].innerHTML = current_minutes;
  1521. }
  1522. }
  1523. if( document.querySelectorAll('.amplitude-current-minutes[amplitude-song-index="'+config.active_index+'"]').length > 0 ){
  1524. var currentMinuteSelectors = document.querySelectorAll('.amplitude-current-minutes[amplitude-song-index="'+config.active_index+'"]');
  1525. for( var i = 0; i < currentMinuteSelectors.length; i++ ){
  1526. currentMinuteSelectors[i].innerHTML = current_minutes;
  1527. }
  1528. }
  1529. if( document.querySelectorAll('[amplitude-single-current-seconds="true"]').length > 0 ){
  1530. var mainCurrentSecondSelectors = document.querySelectorAll('[amplitude-single-current-seconds="true"]');
  1531. for( var i = 0; i < mainCurrentSecondSelectors.length; i++ ){
  1532. mainCurrentSecondSelectors[i].innerHTML = current_seconds;
  1533. }
  1534. }
  1535. if( document.querySelectorAll('.amplitude-current-seconds[amplitude-song-index="'+config.active_index+'"]').length > 0 ){
  1536. var currentSecondSelectors = document.querySelectorAll('.amplitude-current-seconds[amplitude-song-index="'+config.active_index+'"]');
  1537. for( var i = 0; i < currentSecondSelectors.length; i++ ){
  1538. currentSecondSelectors[i].innerHTML = current_seconds;
  1539. }
  1540. }
  1541. if( !config.active_metadata.live ){
  1542. if( document.querySelectorAll('[amplitude-single-duration-minutes="true"]').length > 0 ){
  1543. var mainDurationMinuteSelectors = document.querySelectorAll('[amplitude-single-duration-minutes="true"]');
  1544. for( var i = 0; i < mainDurationMinuteSelectors.length; i++ ){
  1545. mainDurationMinuteSelectors[i].innerHTML = song_duration_minutes;
  1546. }
  1547. }
  1548. if( document.querySelectorAll('.amplitude-duration-minutes[amplitude-song-index="'+config.active_index+'"]').length > 0 ){
  1549. var durationMinuteSelectors = document.querySelectorAll('.amplitude-duration-minutes[amplitude-song-index="'+config.active_index+'"]');
  1550. for( var i = 0; i < durationMinuteSelectors.length; i++ ){
  1551. durationMinuteSelectors[i].innerHTML = song_duration_minutes;
  1552. }
  1553. }
  1554. if( document.querySelectorAll('[amplitude-single-duration-seconds="true"]').length > 0 ){
  1555. var mainDurationSecondSelectors = document.querySelectorAll('[amplitude-single-duration-seconds="true"]');
  1556. for( var i = 0; i < mainDurationSecondSelectors.length; i++ ){
  1557. mainDurationSecondSelectors[i].innerHTML = song_duration_seconds;
  1558. }
  1559. }
  1560. if( document.querySelectorAll('.amplitude-duration-seconds[amplitude-song-index="'+config.active_index+'"]').length > 0 ){
  1561. var durationSecondSelectors = document.querySelectorAll('.amplitude-duration-seconds[amplitude-song-index="'+config.active_index+'"]');
  1562. for( var i = 0; i < durationSecondSelectors.length; i++ ){
  1563. durationSecondSelectors[i].innerHTML = song_duration_seconds;
  1564. }
  1565. }
  1566. }
  1567. /*
  1568. When we find the right slider, we set the value to the percentage of
  1569. completion only if the song isn't live. Since it's 'infinite' if
  1570. it's a live source, it wouldn't make sense to update the time
  1571. display.
  1572. */
  1573. if( !config.active_metadata.live && current_slider ){
  1574. if( config.active_metadata.duration != undefined ){
  1575. current_slider.value = ( ( config.active_song.currentTime / config.active_metadata.duration ) * 1000 ) * 100;
  1576. }else{
  1577. current_slider.value = ( config.active_song.currentTime / config.active_song.duration ) * 100;
  1578. }
  1579. }
  1580. }
  1581. /*--------------------------------------------------------------------------
  1582. HANDLER FOR: 'amplitude-shuffle'
  1583. Handles a click for the shuffle element.
  1584. --------------------------------------------------------------------------*/
  1585. function privateShuffleClickHandle(){
  1586. /*
  1587. If the shuffle is already on, then swe turn it off
  1588. and clear out the existing shuffle list. We also
  1589. restore the active index back to 0.
  1590. */
  1591. if( config.shuffle_on ){
  1592. config.shuffle_on = false;
  1593. config.shuffle_list = {};
  1594. config.shuffle_active_index = 0;
  1595. }else{
  1596. /*
  1597. If the shuffle is not on then we turn on shuffle
  1598. and re-shuffle the songs.
  1599. */
  1600. config.shuffle_on = true;
  1601. privateShuffleSongs();
  1602. }
  1603. /*
  1604. We then sync the visual shuffle button so it has the proper
  1605. class representing the state of the shuffle functionality.
  1606. */
  1607. privateSyncVisualShuffle();
  1608. }
  1609. /*--------------------------------------------------------------------------
  1610. HANDLER FOR: 'amplitude-repeat'
  1611. Handles a click for the repeat element.
  1612. --------------------------------------------------------------------------*/
  1613. function privateRepeatClickHandle(){
  1614. /*
  1615. If repeat is on, we turn it off. Othwerwise we turn repeat on.
  1616. */
  1617. if( config.repeat ){
  1618. config.repeat = false;
  1619. }else{
  1620. config.repeat = true;
  1621. }
  1622. privateSyncVisualRepeat();
  1623. }
  1624. /*
  1625. |--------------------------------------------------------------------------
  1626. | HELPER FUNCTIONS
  1627. |--------------------------------------------------------------------------
  1628. | For the sake of code clarity, these functions perform helper tasks
  1629. | assisting the logical functions with what they need such as setting
  1630. | the proper song index after an event has occured.
  1631. */
  1632. /*--------------------------------------------------------------------------
  1633. Finishes the initalization of the config. Takes all of the user defined
  1634. parameters and makes sure they override the defaults. The important
  1635. config information is assigned in the publicInit() function.
  1636. This function can be called from 2 different locations:
  1637. 1. Right away on init after the important settings are defined.
  1638. 2. After all of the Soundcloud URLs are resolved properly and
  1639. soundcloud is configured. We will need the proper URLs from Soundcloud
  1640. to stream through Amplitude so we get those right away before we
  1641. set the information and the active song
  1642. @param user_config JSON representation of the user's settings when
  1643. they init Amplitude.
  1644. TODO: In all functions that call privateSetActiveSongInformation, have
  1645. the active_index set there as well.
  1646. TODO: Make sure that if the user sends a start_song that it's an integer
  1647. and nothing else. Debug if NOT an integer.
  1648. TODO: Make the user enter a volume between 0 and 100. It's a little
  1649. easier sounding.
  1650. TODO: Make sure the user enters a volume increment or decrement between
  1651. 1 and 100 to ensure something happens when they click the increment
  1652. or decrement button.
  1653. --------------------------------------------------------------------------*/
  1654. function privateSetConfig( user_config ){
  1655. /*
  1656. If Amplitude is not in dynamic mode, we determine what the
  1657. start song should be. Dynamic mode doesn't have any songs on
  1658. config because the user will be sending them to Amplitude
  1659. dynamically.
  1660. */
  1661. if( !config.dynamic_mode ){
  1662. /*
  1663. If the user provides a starting song index then we set
  1664. the active song information to the song at that index.
  1665. */
  1666. if( user_config.start_song != undefined ){
  1667. privateSetActiveSongInformation( user_config.start_song, false );
  1668. /*
  1669. TODO: REMOVE Sets the user defined index.
  1670. */
  1671. config.active_index = user_config.start_song;
  1672. }else{
  1673. privateSetActiveSongInformation( 0, false );
  1674. /*
  1675. TODO: REMOVE Sets the active index to the first song.
  1676. */
  1677. config.active_index = 0;
  1678. }
  1679. }
  1680. /*
  1681. If live is not defined, assume it is false. The reason for
  1682. this definition is if we play/pause we disconnect
  1683. and re-connect to the stream so we don't fill up our cache
  1684. with unused audio and we aren't playing outdated audio upon
  1685. resume.
  1686. */
  1687. if( config.active_metadata.live == undefined ){
  1688. config.active_metadata.live = false;
  1689. }
  1690. /*
  1691. If the user wants the song to be pre-loaded for instant
  1692. playback, they set it to true. By default it's set to just
  1693. load the metadata.
  1694. */
  1695. config.active_song.preload = ( user_config.preload != undefined ? user_config.preload : "metadata" );
  1696. /*
  1697. Initializes the user defined callbacks. This should be a JSON
  1698. object that contains a key->value store of the callback name
  1699. and the name of the function the user needs to call.
  1700. */
  1701. config.callbacks = ( user_config.callbacks != undefined ? user_config.callbacks : {} );
  1702. /*
  1703. The user can define a starting volume in a range of 0-1 with
  1704. 0 being muted and 1 being the loudest. After the config is set
  1705. Amplitude sets the active song's volume to the volume defined
  1706. by the user.
  1707. */
  1708. config.volume = ( user_config.volume != undefined ? user_config.volume : .5 );
  1709. config.active_song.volume = config.volume;
  1710. /*
  1711. The user can set the volume increment and decrement values between 1 and 100
  1712. for when the volume up or down button is pressed. The default is an increase
  1713. or decrease of 5.
  1714. */
  1715. config.volume_increment = ( user_config.volume_increment != undefined ? user_config.volume_increment : 5 );
  1716. config.volume_decrement = ( user_config.volume_decrement != undefined ? user_config.volume_decrement : 5 );
  1717. /*
  1718. The user can turn off Amplitude handling the song elements (putting the meta data into
  1719. certain fields when the song is playing or changed). This would be if the user wanted
  1720. to hard code this information which would probably be most popular in single song
  1721. instances.
  1722. */
  1723. config.handle_song_elements = ( user_config.handle_song_elements != undefined ? user_config.handle_song_elements : true );
  1724. /*
  1725. If the user defines default album art, this image will display if the active
  1726. song doesn't have album art defined.
  1727. */
  1728. config.default_album_art = ( user_config.default_album_art != undefined ? user_config.default_album_art : '' );
  1729. /*
  1730. The user can define a visualization backup to use if they are using
  1731. visualizations (song visualizations not song time visualizations) and the
  1732. browser doesn't support it. This can be "nothing" meaning that the
  1733. visualization element is removed otherwise it can be the album art
  1734. of the song being played.
  1735. */
  1736. config.visualization_backup = ( user_config.visualization_backup != undefined ? user_config.visualization_backup : 'nothing' );
  1737. /*
  1738. Sets initialized to true, so the user can't re-initialize
  1739. and mess everything up.
  1740. */
  1741. config.initialized = true;
  1742. /*
  1743. Since the user can define a start volume, we want our volume
  1744. sliders to sync with the user defined start value.
  1745. */
  1746. privateSyncVolumeSliders();
  1747. /*
  1748. Sets up the player if the browser doesn't have the audio context
  1749. */
  1750. privateSyncNoAudioContext();
  1751. /*
  1752. Set all of the current time elements to 0:00 upon initialization
  1753. */
  1754. privateSyncCurrentTimes();
  1755. /*
  1756. Syncs all of the song status sliders so the user can't set the
  1757. HTML 5 range element to be something invalid on load like half
  1758. way through the song by default.
  1759. */
  1760. privateResetSongStatusSliders();
  1761. privateCheckSongVisualization();
  1762. /*
  1763. Initialize the visual elements for the song if the user
  1764. wants Amplitude to handle the changes. This is new
  1765. compared to previous versions where Amplitude automatically
  1766. handled the song elements.
  1767. */
  1768. if( config.handle_song_elements ){
  1769. privateDisplaySongMetadata();
  1770. }
  1771. /*
  1772. Removes any classes set by the user so any inconsistencies
  1773. with start song and actual song are displayed correctly.
  1774. */
  1775. privateSyncVisualPlayingContainers();
  1776. /*
  1777. Sets the active song container for the song that will be
  1778. played. This adds a class to an element containing the
  1779. visual representation of the active song .
  1780. */
  1781. privateSetActiveContainer();
  1782. /*
  1783. Sets the temporary user conifg back to empty. We are done
  1784. using it.
  1785. */
  1786. temp_user_config = {};
  1787. /*
  1788. Run after init callback
  1789. */
  1790. privateRunCallback("after_init");
  1791. /*
  1792. If the user turns on autoplay the song will play automatically.
  1793. */
  1794. if( user_config.autoplay ){
  1795. /*
  1796. Gets the attribute for song index so we can check if
  1797. there is a need to change the song. In some scenarios
  1798. there might be multiple play classes on the page. In that
  1799. case it is possible the user could click a different play
  1800. class and change the song.
  1801. */
  1802. var playing_song_index = config.start_song;
  1803. /*
  1804. We set the new song if the user clicked a song with a different
  1805. index. If it's the same as what's playing then we don't set anything.
  1806. If it's different we reset all song sliders.
  1807. */
  1808. if( privateCheckNewSong( playing_song_index ) ){
  1809. privateChangeSong( playing_song_index );
  1810. privateResetSongStatusSliders();
  1811. }
  1812. /*
  1813. Start the visualizations for the song.
  1814. */
  1815. privateStartVisualization();
  1816. /*
  1817. If there are any play pause buttons we need
  1818. to sync them to playing for auto play.
  1819. */
  1820. privateChangePlayPauseState('playing');
  1821. /*
  1822. Play the song through the core play function.
  1823. */
  1824. privatePlay();
  1825. }
  1826. }
  1827. /*--------------------------------------------------------------------------
  1828. Handles the back up functionality for visualizations. This happens
  1829. if there is no AudioContext available or the song is live.
  1830. The two settings are:
  1831. 1. "nothing" DEFAULT. It will remove the visualization element from the
  1832. page.
  1833. 2. "album-art" Instead of the visualization, the element that would have
  1834. container for the visualization will instead display the album
  1835. art for the now playing song.
  1836. TODO: Make sure this is only run if the user is using visualizations
  1837. in their design.
  1838. TODO: Change querySelector to querySelectorAll once again justifying
  1839. the use of a global query all function for visual syncing.
  1840. --------------------------------------------------------------------------*/
  1841. function privateHandleVisualizationBackup(){
  1842. switch( config.visualization_backup ){
  1843. /*
  1844. Removes the visualization element from the page.
  1845. */
  1846. case "nothing":
  1847. if( document.getElementById('amplitude-visualization') ){
  1848. document.getElementById('amplitude-visualization').remove();
  1849. }
  1850. break;
  1851. /*
  1852. Sets up the old visualization element to contain the
  1853. album art.
  1854. */
  1855. case "album-art":
  1856. /*
  1857. Gets the old visualizationelement.
  1858. */
  1859. var old_visualization = document.getElementById('amplitude-visualization');
  1860. /*
  1861. If there is a visualization element then we proceed.
  1862. */
  1863. if( old_visualization ){
  1864. /*
  1865. Gets the parent node to append the inner node to containing
  1866. the album art.
  1867. */
  1868. var parent_old_visualization = old_visualization.parentNode;
  1869. var new_album_art = document.createElement('img');
  1870. /*
  1871. Sets the attribute to be the song infor for the cover
  1872. art on the new element. Also apply the class 'amplitude-album-art'
  1873. */
  1874. new_album_art.setAttribute('amplitude-song-info', 'cover');
  1875. new_album_art.setAttribute('class', 'amplitude-album-art');
  1876. /*
  1877. TODO: is this the right place to do this? Shouldn't this happen
  1878. AFTER we replace the visualization?
  1879. */
  1880. if( document.querySelector('[amplitude-song-info="cover"]') ){
  1881. if( config.active_metadata.cover_art_url != undefined){
  1882. new_album_art.setAttribute( 'src', config.active_metadata.cover_art_url );
  1883. document.querySelector('[amplitude-song-info="cover"]').setAttribute('src', config.active_metadata.cover_art_url);
  1884. }else if( config.default_album_art != '' ){
  1885. new_album_art.setAttribute( 'src', config.default_album_art );
  1886. }else{
  1887. new_album_art.setAttribute( 'src', '' );
  1888. }
  1889. }
  1890. parent_old_visualization.replaceChild( new_album_art, old_visualization );
  1891. }
  1892. break;
  1893. }
  1894. }
  1895. /*--------------------------------------------------------------------------
  1896. Sets information relating to the active song.
  1897. --------------------------------------------------------------------------*/
  1898. function privateSetActiveSongInformation( index, shuffle ){
  1899. if( shuffle ){
  1900. /*
  1901. If the current state is in shuffle mode, we set the
  1902. active song to the song at the index in the shuffle list.
  1903. Amplitude will also grab specific meta data from the song
  1904. in the shuffle list and set the attributes in the config
  1905. accordingly.
  1906. */
  1907. config.active_song.src = config.shuffle_list[index].url;
  1908. config.active_metadata = config.shuffle_list[index];
  1909. config.active_album = config.shuffle_list[index].album;
  1910. }else{
  1911. /*
  1912. If the current state is not in shuffle mode, we will
  1913. grab the index from the songs array and set the active
  1914. song to the source of that song. Amplitude will also
  1915. grab certain attributes from the song and use them
  1916. in the config accordingly.
  1917. */
  1918. config.active_song.src = config.songs[index].url;
  1919. config.active_metadata = config.songs[index];
  1920. config.active_album = config.songs[index].album;
  1921. }
  1922. }
  1923. /*--------------------------------------------------------------------------
  1924. Writes out debug message to the console if enabled.
  1925. @param string message The string that gets printed to
  1926. alert the user of a debugging error.
  1927. --------------------------------------------------------------------------*/
  1928. function privateWriteDebugMessage( message ){
  1929. if( config.debug ){
  1930. console.log( message );
  1931. }
  1932. }
  1933. /*--------------------------------------------------------------------------
  1934. Checks to see if a new song should be prepared for playing
  1935. @param int new_song_index The integer index of the song
  1936. that will be played.
  1937. --------------------------------------------------------------------------*/
  1938. function privateCheckNewSong( new_song_index ){
  1939. /*
  1940. If the new song index is null, then nothing was defined,
  1941. so we assume it's in a single song scenario or a main control click
  1942. and we don't need to worry about changing the song. If there was an
  1943. index defined and the index and active_index are different then we
  1944. adjust the song.
  1945. */
  1946. if( new_song_index != null && ( new_song_index != config.active_index ) ){
  1947. return true;
  1948. }else{
  1949. return false;
  1950. }
  1951. }
  1952. /*--------------------------------------------------------------------------
  1953. Gets Amplitude ready for a song change. Syncs elements and runs
  1954. necessary callbacks.
  1955. @param int new_song_index The integer index of the song
  1956. that will be played.
  1957. --------------------------------------------------------------------------*/
  1958. function privateChangeSong( new_song_index ){
  1959. /*
  1960. Stops the currently playing song.
  1961. */
  1962. privateStop();
  1963. /*
  1964. Checks to see if the new song is a different album.
  1965. */
  1966. privateCheckNewAlbum( config.songs[new_song_index].album );
  1967. /*
  1968. Changes the active song index that is being played.
  1969. */
  1970. config.active_index = new_song_index;
  1971. /*
  1972. Sets the active song information for the new song that will
  1973. be played.
  1974. */
  1975. privateSetActiveSongInformation( new_song_index, config.shuffle_on );
  1976. if( config.album_change ){
  1977. privateRunCallback('after_album_change');
  1978. config.album_change = false;
  1979. }
  1980. /*
  1981. If it's a new song and the user wants amplitude to handle
  1982. the song elements, we need to set the information for
  1983. the song.
  1984. */
  1985. if( config.handle_song_elements ){
  1986. privateDisplaySongMetadata();
  1987. }
  1988. /*
  1989. We set the current times to 0:00 when song changes
  1990. so all of the pages players will be synchronized.
  1991. */
  1992. privateSyncCurrentTimes();
  1993. privateCheckSongVisualization();
  1994. privateSetActiveContainer();
  1995. }
  1996. /*--------------------------------------------------------------------------
  1997. Checks to see if a new album is playing. This allows for
  1998. multiple albums to be initialized on the same page.
  1999. Through CSS you can show and hide albums and simulate
  2000. multiple playlists. This method is called after there is a for
  2001. sure change to see if the next song's album is different than
  2002. the song that will soon to be previous' album.
  2003. @param string new_album The string of the new album
  2004. to see if it has changed.
  2005. TODO: Research if we should return true/false instead of setting the
  2006. config.
  2007. TODO: Makes sure the song actually has an album before running.
  2008. --------------------------------------------------------------------------*/
  2009. function privateCheckNewAlbum( new_album ){
  2010. /*
  2011. If the new album isn't the same as the
  2012. active album, we set the change to true
  2013. and run the before_album_change callback.
  2014. */
  2015. if( config.active_album != new_album ){
  2016. config.album_change = true;
  2017. privateRunCallback('before_album_change');
  2018. }
  2019. }
  2020. /*--------------------------------------------------------------------------
  2021. Called when a song changes. Synchronizes everything necessary on
  2022. the page to handle the song change.
  2023. TODO: Check to see if using visualizations.
  2024. --------------------------------------------------------------------------*/
  2025. function privateAfterSongChanges(){
  2026. /*
  2027. After the new song is set, we see if we need to change
  2028. the visualization. If it's different, we need to start
  2029. the new one.
  2030. */
  2031. if( privateCheckSongVisualization() ){
  2032. privateStartVisualization();
  2033. }
  2034. /*
  2035. After the new song is set, Amplitude will update the
  2036. visual elements containing information about the
  2037. new song if the user wants Amplitude to.
  2038. */
  2039. if( config.handle_song_elements ){
  2040. privateDisplaySongMetadata();
  2041. }
  2042. /*
  2043. Sync song status sliders. Sets them back to 0 because
  2044. when the song is changing there won't be any songs currently
  2045. playing.
  2046. */
  2047. privateResetSongStatusSliders();
  2048. /*
  2049. We set the current times to 0:00 when song changes
  2050. so all of the page's players will be synchronized.
  2051. */
  2052. privateSyncCurrentTimes();
  2053. /*
  2054. Remove class from all containers
  2055. */
  2056. privateSyncVisualPlayingContainers();
  2057. /*
  2058. Set new active song container by applying a class
  2059. to the visual element containing the visual representation
  2060. of the song that is actively playing.
  2061. */
  2062. privateSetActiveContainer();
  2063. /*
  2064. Plays the new song.
  2065. */
  2066. privatePlay();
  2067. }
  2068. /*--------------------------------------------------------------------------
  2069. Shuffles songs.
  2070. Based off of: http://www.codinghorror.com/blog/2007/12/the-danger-of-naivete.html
  2071. --------------------------------------------------------------------------*/
  2072. function privateShuffleSongs(){
  2073. var shuffle_temp = new Array( config.songs.length );
  2074. for( i = 0; i < config.songs.length; i++ ){
  2075. shuffle_temp[i] = config.songs[i];
  2076. }
  2077. for( i = config.songs.length - 1; i > 0; i-- ){
  2078. rand_num = Math.floor( ( Math.random() * config.songs.length ) + 1 );
  2079. privateShuffleSwap( shuffle_temp, i, rand_num - 1 );
  2080. }
  2081. config.shuffle_list = shuffle_temp;
  2082. }
  2083. /*--------------------------------------------------------------------------
  2084. Swaps and randomizes the song shuffle.
  2085. @param JSON shuffle_list The list of songs that is going to
  2086. be shuffled
  2087. @param int original The original index of the song in the
  2088. songs array.
  2089. @param int random The randomized index that will be the
  2090. new index of the song in the shuffle array.
  2091. --------------------------------------------------------------------------*/
  2092. function privateShuffleSwap( shuffle_list, original, random ){
  2093. var temp = shuffle_list[ original ];
  2094. shuffle_list[ original ] = shuffle_list[ random ];
  2095. shuffle_list[ random ] = temp;
  2096. }
  2097. /*--------------------------------------------------------------------------
  2098. Runs callback for specific function
  2099. @param string The name of the call back. Also used as the index that
  2100. the user can use in the callback array to define their callback method.
  2101. --------------------------------------------------------------------------*/
  2102. function privateRunCallback( callback_name ){
  2103. if( config.callbacks[callback_name] ){
  2104. var callback_function = window[ config.callbacks[ callback_name ] ];
  2105. callback_function();
  2106. }
  2107. }
  2108. /*--------------------------------------------------------------------------
  2109. If there is a visualization specifically for a song, we set that
  2110. as the active visualization. Only if one is specified, otherwise
  2111. nothing changes and we continue using the active visualization.
  2112. @returns BOOL Returns true if there is a specific visualization for
  2113. the song.
  2114. --------------------------------------------------------------------------*/
  2115. function privateCheckSongVisualization(){
  2116. var changed = false;
  2117. /*
  2118. Checks to see if the song actually has a specific visualization
  2119. defined.
  2120. */
  2121. if( config.active_metadata.visualization ){
  2122. /*
  2123. If the visualization is different and there is an active
  2124. visualization. We must stop the active visualization
  2125. before setting the new one.
  2126. */
  2127. if( config.active_metadata.visualization != config.active_visualization && config.active_visualization != '' ){
  2128. privateStopVisualization();
  2129. /*
  2130. Set the visualization changed to true
  2131. so we return the status change.
  2132. */
  2133. changed = true;
  2134. /*
  2135. Sets the active visualization to the new
  2136. visualization that the song uses.
  2137. */
  2138. config.active_visualization = config.active_metadata.visualization;
  2139. }
  2140. }
  2141. /*
  2142. Returns the status of the new song visualization.
  2143. If there is a change it returns true and we will
  2144. have to start the the visualization.
  2145. */
  2146. return changed;
  2147. }
  2148. /*--------------------------------------------------------------------------
  2149. Sets the visual elements containg the active song
  2150. metadata
  2151. --------------------------------------------------------------------------*/
  2152. function privateDisplaySongMetadata(){
  2153. /*
  2154. Sets all elements that will contain the active song's name metadata
  2155. */
  2156. if( document.querySelectorAll('[amplitude-song-info="name"]') ){
  2157. var metaNames = document.querySelectorAll('[amplitude-song-info="name"]');
  2158. for( i = 0; i < metaNames.length; i++ ){
  2159. metaNames[i].innerHTML = config.active_metadata.name;
  2160. }
  2161. }
  2162. /*
  2163. Sets all elements that will contain the active song's artist metadata
  2164. */
  2165. if( document.querySelectorAll('[amplitude-song-info="artist"]') ){
  2166. var metaArtist = document.querySelectorAll('[amplitude-song-info="artist"]');
  2167. for( i = 0; i < metaArtist.length; i++ ){
  2168. metaArtist[i].innerHTML = config.active_metadata.artist;
  2169. }
  2170. }
  2171. /*
  2172. Sets all elements that will contain the active song's album metadata
  2173. */
  2174. if( document.querySelectorAll('[amplitude-song-info="album"]') ){
  2175. var metaAlbum = document.querySelectorAll('[amplitude-song-info="album"]');
  2176. for( i = 0; i < metaAlbum.length; i++ ){
  2177. metaAlbum[i].innerHTML = config.active_metadata.album;
  2178. }
  2179. }
  2180. /*
  2181. Sets all elements that will contain the active song's cover art metadata
  2182. */
  2183. if( document.querySelectorAll('[amplitude-song-info="cover"]') ){
  2184. var coverImages = document.querySelectorAll('[amplitude-song-info="cover"]');
  2185. for( i = 0; i < coverImages.length; i++ ){
  2186. /*
  2187. Checks to see if first, the song has a defined cover art and uses
  2188. that. If it does NOT have defined cover art, checks to see if there
  2189. is a default. Otherwise it just sets the src to '';
  2190. */
  2191. if( config.active_metadata.cover_art_url != undefined){
  2192. coverImages[i].setAttribute('src', config.active_metadata.cover_art_url);
  2193. }else if( config.default_album_art != '' ){
  2194. coverImages[i].setAttribute('src', config.default_album_art);
  2195. }else{
  2196. coverImages[i].setAttribute('src', '');
  2197. }
  2198. }
  2199. }
  2200. /*
  2201. Station information for live streams
  2202. */
  2203. /*
  2204. Sets all of the elements that will contain the live stream's call sign metadata
  2205. */
  2206. if( document.querySelectorAll('[amplitude-song-info="call-sign"]') ){
  2207. var metaCallSign = document.querySelectorAll('[amplitude-song-info="call-sign"]');
  2208. for( i = 0; i < metaCallSign.length; i++ ){
  2209. metaCallSign[i].innerHTML = config.active_metadata.call_sign;
  2210. }
  2211. }
  2212. /*
  2213. Sets all of the elements that will contain the live stream's station name metadata
  2214. */
  2215. if( document.querySelectorAll('[amplitude-song-info="station-name"]') ){
  2216. var metaStationName = document.querySelectorAll('[amplitude-song-info="station-name"]');
  2217. for( i = 0; i < metaStationName.length; i++ ){
  2218. metaStationName[i].innerHTML = config.active_metadata.station_name;
  2219. }
  2220. }
  2221. /*
  2222. Sets all of the elements that will contain the live stream's location metadata
  2223. */
  2224. if( document.querySelectorAll('[amplitude-song-info="location"]') ){
  2225. var metaStationLocation = document.querySelectorAll('[amplitude-song-info="location"]');
  2226. for( i = 0; i < metaStationLocation.length; i++ ){
  2227. metaStationLocation[i].innerHTML = config.active_metadata.location;
  2228. }
  2229. }
  2230. /*
  2231. Sets all of the elements that will contain the live stream's frequency metadata
  2232. */
  2233. if( document.querySelectorAll('[amplitude-song-info="frequency"]') ){
  2234. var metaStationFrequency = document.querySelectorAll('[amplitude-song-info="frequency"]');
  2235. for( i = 0; i < metaStationFrequency.length; i++ ){
  2236. metaStationFrequency[i].innerHTML = config.active_metadata.frequency;
  2237. }
  2238. }
  2239. /*
  2240. Sets all of the elements that will contain the live stream's station art metadata
  2241. TODO: Rename coverImages to stationArtImages
  2242. */
  2243. if( document.querySelectorAll('[amplitude-song-info="station-art"]') ){
  2244. var coverImages = document.querySelectorAll('[amplitude-song-info="station-art"]');
  2245. /*
  2246. Checks to see if first, the song has a defined station art and uses
  2247. that. If it does NOT have defined station art, checks to see if there
  2248. is a default. Otherwise it just sets the src to '';
  2249. */
  2250. for( i = 0; i < coverImages.length; i++ ){
  2251. if( config.active_metadata.cover_art_url != undefined){
  2252. coverImages[i].setAttribute('src', config.active_metadata.station_art_url);
  2253. }else if( config.default_album_art != '' ){
  2254. coverImages[i].setAttribute('src', config.default_album_art);
  2255. }else{
  2256. coverImages[i].setAttribute('src', '');
  2257. }
  2258. }
  2259. }
  2260. }
  2261. /*--------------------------------------------------------------------------
  2262. Applies the class 'amplitude-active-song-container' to the element
  2263. containing visual information regarding the active song.
  2264. TODO: Make sure that when shuffling, this changes accordingly.
  2265. --------------------------------------------------------------------------*/
  2266. function privateSetActiveContainer(){
  2267. var songContainers = document.getElementsByClassName('amplitude-song-container');
  2268. /*
  2269. Removes all of the active song containrs.
  2270. */
  2271. for( i = 0; i < songContainers.length; i++ ){
  2272. songContainers[i].classList.remove('amplitude-active-song-container');
  2273. }
  2274. /*
  2275. Finds the active index and adds the active song container to the element
  2276. that represents the song at the index.
  2277. */
  2278. if( document.querySelectorAll('.amplitude-song-container[amplitude-song-index="'+config.active_index+'"]') ){
  2279. var songContainers = document.querySelectorAll('.amplitude-song-container[amplitude-song-index="'+config.active_index+'"]');
  2280. for( i = 0; i < songContainers.length; i++ ){
  2281. songContainers[i].classList.add('amplitude-active-song-container');
  2282. }
  2283. }
  2284. }
  2285. /*--------------------------------------------------------------------------
  2286. Calls the start method on the active visualization.
  2287. --------------------------------------------------------------------------*/
  2288. function privateStartVisualization(){
  2289. /*
  2290. If the visualization is not started, and there are visualizations
  2291. ready to be activated, we check to see if the user defined a
  2292. starting visualization. If there is a starting visualization,
  2293. then we start that one, otherwise we grab the first visualization
  2294. defined and start that one.
  2295. */
  2296. if( !config.visualization_started && Object.keys(config.visualizations).length > 0){
  2297. if( config.active_visualization != '' ){
  2298. config.visualizations[config.active_visualization].startVisualization(config.active_song);
  2299. config.current_visualization = config.visualizations[config.active_visualization];
  2300. }else{
  2301. for(first_visualization in config.visualizations);
  2302. config.visualizations[first_visualization].startVisualization(config.active_song);
  2303. config.current_visualization = config.visualizations[first_visualization];
  2304. }
  2305. config.visualization_started = true;
  2306. }
  2307. }
  2308. /*--------------------------------------------------------------------------
  2309. Calls the stop method of the active visualization.
  2310. If the visualization is started, we stop it.
  2311. --------------------------------------------------------------------------*/
  2312. function privateStopVisualization(){
  2313. if( config.visualization_started && Object.keys(config.visualizations).length > 0){
  2314. config.current_visualization.stopVisualization();
  2315. config.visualization_started = false;
  2316. }
  2317. }
  2318. /*
  2319. |--------------------------------------------------------------------------
  2320. | SOUNDCLOUD SPECIFIC HELPERS
  2321. |--------------------------------------------------------------------------
  2322. | These helpers wrap around the basic functions of the Soundcloud API
  2323. | and get the information we need from SoundCloud to make the songs
  2324. | streamable through Amplitude
  2325. */
  2326. /*--------------------------------------------------------------------------
  2327. Loads the soundcloud SDK for use with Amplitude so the user doesn't have
  2328. to load it themselves.
  2329. With help from: http://stackoverflow.com/questions/950087/include-a-javascript-file-in-another-javascript-file
  2330. --------------------------------------------------------------------------*/
  2331. function privateLoadSoundcloud(){
  2332. var head = document.getElementsByTagName('head')[0];
  2333. var script = document.createElement('script');
  2334. script.type = 'text/javascript';
  2335. /*
  2336. URL to the remote soundcloud SDK
  2337. */
  2338. script.src = 'https://connect.soundcloud.com/sdk.js';
  2339. script.onreadystatechange = privateInitSoundcloud;
  2340. script.onload = privateInitSoundcloud;
  2341. head.appendChild( script );
  2342. }
  2343. /*--------------------------------------------------------------------------
  2344. Initializes soundcloud with the key provided.
  2345. --------------------------------------------------------------------------*/
  2346. function privateInitSoundcloud(){
  2347. /*
  2348. Calls the SoundCloud initialize function
  2349. from their API and sends it the client_id
  2350. that the user passed in.
  2351. */
  2352. SC.initialize({
  2353. client_id: config.soundcloud_client
  2354. });
  2355. /*
  2356. Gets the streamable URLs to run through Amplitue. This is
  2357. VERY important since Amplitude can't stream the copy and pasted
  2358. link from the SoundCloud page, but can resolve the streaming
  2359. URLs from the link.
  2360. */
  2361. privateGetSoundcloudStreamableURLs();
  2362. }
  2363. /*--------------------------------------------------------------------------
  2364. Gets the streamable URL from the URL provided for
  2365. all of the soundcloud links. This will loop through
  2366. and set all of the information for the soundcloud
  2367. urls.
  2368. --------------------------------------------------------------------------*/
  2369. function privateGetSoundcloudStreamableURLs(){
  2370. var soundcloud_regex = /^https?:\/\/(soundcloud.com|snd.sc)\/(.*)$/;
  2371. for( var i = 0; i < config.songs.length; i++ ){
  2372. /*
  2373. If the URL matches soundcloud, we grab
  2374. that url and get the streamable link
  2375. if there is one.
  2376. */
  2377. if( config.songs[i].url.match( soundcloud_regex ) ){
  2378. config.soundcloud_song_count++;
  2379. privateSoundcloudResolveStreamable(config.songs[i].url, i);
  2380. }
  2381. }
  2382. }
  2383. /*--------------------------------------------------------------------------
  2384. Due to Soundcloud SDK being asynchronous, we need to scope the
  2385. index of the song in another function. The privateGetSoundcloudStreamableURLs
  2386. function does the actual iteration and scoping.
  2387. --------------------------------------------------------------------------*/
  2388. function privateSoundcloudResolveStreamable(url, index){
  2389. SC.get('/resolve/?url='+url, function( sound ){
  2390. /*
  2391. If streamable we get the url and bind the client ID to the end
  2392. so Amplitude can just stream the song normally. We then overwrite
  2393. the url the user provided with the streamable URL.
  2394. */
  2395. if( sound.streamable ){
  2396. config.songs[index].url = sound.stream_url+'?client_id='+config.soundcloud_client;
  2397. /*
  2398. If the user want's to use soundcloud art, we overwrite the
  2399. cover_art_url with the soundcloud artwork url.
  2400. */
  2401. if( config.soundcloud_use_art ){
  2402. config.songs[index].cover_art_url = sound.artwork_url;
  2403. }
  2404. /*
  2405. Grab the extra metadata from soundcloud and bind it to the
  2406. song. The user can get this through the public function:
  2407. getActiveSongMetadata
  2408. */
  2409. config.songs[index].soundcloud_data = sound;
  2410. }else{
  2411. /*
  2412. If not streamable, then we print a message to the user stating
  2413. that the song with name X and artist X is not streamable. This
  2414. gets printed ONLY if they have debug turned on.
  2415. */
  2416. privateWriteDebugMessage( config.songs[index].name +' by '+config.songs[index].artist +' is not streamable by the Soundcloud API' );
  2417. }
  2418. /*
  2419. Increments the song ready counter.
  2420. */
  2421. config.soundcloud_songs_ready++;
  2422. /*
  2423. When all songs are accounted for, then amplitude is ready
  2424. to rock and we set the rest of the config.
  2425. */
  2426. if( config.soundcloud_songs_ready == config.soundcloud_song_count ){
  2427. privateSetConfig( temp_user_config );
  2428. }
  2429. });
  2430. }
  2431. /*
  2432. |--------------------------------------------------------------------------
  2433. | VISUAL SYNCHRONIZATION METHODS
  2434. |--------------------------------------------------------------------------
  2435. | These methods keep the screen in sync. For example if there are multiple
  2436. | play/pause buttons and a song changes, we need to set all of the other
  2437. | play/pause buttons to paused state.
  2438. |
  2439. */
  2440. /*--------------------------------------------------------------------------
  2441. Sets all of the play/pause buttons to the not playing state. The
  2442. click handler will set the actual playing button to the playing state.
  2443. --------------------------------------------------------------------------*/
  2444. function privateSetPlayPauseButtonsToPause(){
  2445. var play_pause_classes = document.getElementsByClassName("amplitude-play-pause");
  2446. /*
  2447. Iterates over all of the play pause classes removing
  2448. the playing class and adding the paused class.
  2449. */
  2450. for( var i = 0; i < play_pause_classes.length; i++ ){
  2451. play_pause_classes[i].classList.add('amplitude-paused');
  2452. play_pause_classes[i].classList.remove('amplitude-playing');
  2453. }
  2454. }
  2455. /*--------------------------------------------------------------------------
  2456. Changes the play pause state for all classes that need it. This
  2457. iterates through all of the amplitude-play-pause classes for the
  2458. active index and all of the amplitude-main-play-puase attributes
  2459. making sure everything stays in sync.
  2460. --------------------------------------------------------------------------*/
  2461. function privateChangePlayPauseState( state ){
  2462. /*
  2463. If the state is playing we set all of the classes accordingly.
  2464. */
  2465. if( state == 'playing' ){
  2466. if( document.querySelectorAll('.amplitude-play-pause[amplitude-song-index="'+config.active_index+'"]').length > 0 ){
  2467. var currentPlayPauseControls = document.querySelectorAll('.amplitude-play-pause[amplitude-song-index="'+config.active_index+'"]');
  2468. /*
  2469. Iterates over all of the play pause controls adding the
  2470. 'amplitude-playing' classes and removing the 'amplitude-paused'
  2471. classes.
  2472. */
  2473. for( var i = 0; i < currentPlayPauseControls.length; i++ ){
  2474. currentPlayPauseControls[i].classList.add('amplitude-playing');
  2475. currentPlayPauseControls[i].classList.remove('amplitude-paused');
  2476. }
  2477. }
  2478. /*
  2479. Sets the main song control statuses to playing by removing the
  2480. 'amplitude-paused' class and adding the 'amplitude-playing' class.
  2481. */
  2482. if( document.querySelectorAll('[amplitude-main-play-pause="true"]').length > 0 ){
  2483. var mainControls = document.querySelectorAll('[amplitude-main-play-pause="true"]');
  2484. for( var i = 0; i < mainControls.length; i++ ){
  2485. mainControls[i].classList.add('amplitude-playing');
  2486. mainControls[i].classList.remove('amplitude-paused');
  2487. }
  2488. }
  2489. }
  2490. /*
  2491. If the state is paused, we set all of the classes accordingly.
  2492. */
  2493. if( state == 'paused' ){
  2494. if( document.querySelectorAll('.amplitude-play-pause[amplitude-song-index="'+config.active_index+'"]').length > 0 ){
  2495. var currentPlayPauseControls = document.querySelectorAll('.amplitude-play-pause[amplitude-song-index="'+config.active_index+'"]');
  2496. /*
  2497. Iterates over all of the play pause controls adding the
  2498. 'amplitude-paused' classes and removing the 'amplitude-playing'
  2499. classes.
  2500. */
  2501. for( var i = 0; i < currentPlayPauseControls.length; i++ ){
  2502. currentPlayPauseControls[i].classList.remove('amplitude-playing');
  2503. currentPlayPauseControls[i].classList.add('amplitude-paused');
  2504. }
  2505. }
  2506. /*
  2507. Sets the main song control statuses to paused by removing the
  2508. 'amplitude-playing' class and adding the 'amplitude-paused' class.
  2509. */
  2510. if( document.querySelectorAll('[amplitude-main-play-pause="true"]').length > 0 ){
  2511. var mainControls = document.querySelectorAll('[amplitude-main-play-pause="true"]');
  2512. for( var i = 0; i < mainControls.length; i++ ){
  2513. mainControls[i].classList.add('amplitude-paused');
  2514. mainControls[i].classList.remove('amplitude-playing');
  2515. }
  2516. }
  2517. }
  2518. }
  2519. /*--------------------------------------------------------------------------
  2520. Sets all of the song status sliders to 0, the time update event
  2521. handler will adjust the one that represents the current song
  2522. that is playing.
  2523. --------------------------------------------------------------------------*/
  2524. function privateResetSongStatusSliders(){
  2525. var amplitude_song_sliders = document.getElementsByClassName("amplitude-song-slider");
  2526. /*
  2527. Iterate over all of the song sliders and sets them to
  2528. 0 essentially resetting them.
  2529. */
  2530. for( var i = 0; i < amplitude_song_sliders.length; i++ ){
  2531. amplitude_song_sliders[i].value = 0;
  2532. }
  2533. }
  2534. /*--------------------------------------------------------------------------
  2535. Sets all of the volume sliders to the active song's volume.
  2536. --------------------------------------------------------------------------*/
  2537. function privateSyncVolumeSliders(){
  2538. var amplitude_volume_sliders = document.getElementsByClassName("amplitude-volume-slider");
  2539. /*
  2540. Iterates over all of the volume sliders for the song, setting the value
  2541. to the config value.
  2542. */
  2543. for( var i = 0; i < amplitude_volume_sliders.length; i++ ){
  2544. amplitude_volume_sliders[i].value = config.active_song.volume * 100;
  2545. }
  2546. }
  2547. /*--------------------------------------------------------------------------
  2548. Handles the situation if there is no audio context
  2549. available
  2550. --------------------------------------------------------------------------*/
  2551. function privateSyncNoAudioContext(){
  2552. if( !window.AudioContext ){
  2553. privateHandleVisualizationBackup();
  2554. }
  2555. }
  2556. /*--------------------------------------------------------------------------
  2557. Syncs the current time displays so you can have multiple song time
  2558. displays. When a song changes, we need the current minutes and seconds
  2559. to go to 0:00
  2560. --------------------------------------------------------------------------*/
  2561. function privateSyncCurrentTimes(){
  2562. var current_minute_times = document.getElementsByClassName("amplitude-current-minutes");
  2563. for( var i = 0; i < current_minute_times.length; i++ ){
  2564. current_minute_times[i].innerHTML = '0';
  2565. }
  2566. var current_second_times = document.getElementsByClassName("amplitude-current-seconds");
  2567. for( var i = 0; i < current_second_times.length; i++ ){
  2568. current_second_times[i].innerHTML = '00';
  2569. }
  2570. }
  2571. /*--------------------------------------------------------------------------
  2572. For visual playing containers, we find all containers that
  2573. have a class of 'amplitude-song-container' and remove all of
  2574. the additional 'amplitude-active-song-container' classes.
  2575. When a new song is activated, it will find the parameter
  2576. 'amplitude-song-index' and the class of 'amplitude-song-container'
  2577. and give it the additional class 'amplitude-active-song-container'.
  2578. --------------------------------------------------------------------------*/
  2579. function privateSyncVisualPlayingContainers(){
  2580. var visual_playing_containers = document.getElementsByClassName("amplitude-song-container");
  2581. for( var i = 0; i < visual_playing_containers.length; i++ ){
  2582. visual_playing_containers[i].classList.remove('amplitude-active-song-container');
  2583. }
  2584. }
  2585. /*--------------------------------------------------------------------------
  2586. Sets shuffle on for all of the shuffle buttons. Users
  2587. can apply styles to the amplitude-shuffle-on and
  2588. amplitude-shuffle-off classes. They represent the state
  2589. of the playlist.
  2590. --------------------------------------------------------------------------*/
  2591. function privateSyncVisualShuffle(){
  2592. var shuffle_classes = document.getElementsByClassName("amplitude-shuffle");
  2593. for( var i = 0; i < shuffle_classes.length; i++ ){
  2594. if( config.shuffle_on ){
  2595. shuffle_classes[i].classList.add('amplitude-shuffle-on');
  2596. shuffle_classes[i].classList.remove('amplitude-shuffle-off');
  2597. }else{
  2598. shuffle_classes[i].classList.remove('amplitude-shuffle-on');
  2599. shuffle_classes[i].classList.add('amplitude-shuffle-off');
  2600. }
  2601. }
  2602. }
  2603. /*--------------------------------------------------------------------------
  2604. Sets repeat on for all of the repeat buttons. Users
  2605. can apply styles to the amplitude-repeat-on and
  2606. amplitude-repeat-off classes. They represent the state
  2607. of the player.
  2608. --------------------------------------------------------------------------*/
  2609. function privateSyncVisualRepeat(){
  2610. var repeat_classes = document.getElementsByClassName("amplitude-repeat");
  2611. for( var i = 0; i < repeat_classes.length; i++ ){
  2612. if( config.repeat ){
  2613. repeat_classes[i].classList.add('amplitude-repeat-on');
  2614. repeat_classes[i].classList.remove('amplitude-repeat-off');
  2615. }else{
  2616. repeat_classes[i].classList.remove('amplitude-repeat-on');
  2617. repeat_classes[i].classList.add('amplitude-repeat-off');
  2618. }
  2619. }
  2620. }
  2621. /*
  2622. |--------------------------------------------------------------------------
  2623. | CORE FUNCTIONAL METHODS
  2624. |--------------------------------------------------------------------------
  2625. | Interacts directly with native functions of the Audio element. Logic
  2626. | leading up to these methods are handled by click handlers which call
  2627. | helpers and visual synchronizers. These are the core functions of AmplitudeJS.
  2628. | Every other function that leads to these prepare the information to be
  2629. | acted upon by these functions.
  2630. */
  2631. /*--------------------------------------------------------------------------
  2632. Plays the active song. If the current song is live, it reconnects
  2633. the stream before playing.
  2634. --------------------------------------------------------------------------*/
  2635. function privatePlay(){
  2636. privateRunCallback('before_play');
  2637. if( config.active_metadata.live ){
  2638. privateReconnectStream();
  2639. }
  2640. config.active_song.play();
  2641. privateRunCallback('after_play');
  2642. }
  2643. /*--------------------------------------------------------------------------
  2644. Pauses the active song. If it's live, it disconnects the stream.
  2645. --------------------------------------------------------------------------*/
  2646. function privatePause(){
  2647. config.active_song.pause();
  2648. if( config.active_metadata.live ){
  2649. privateDisconnectStream();
  2650. }
  2651. }
  2652. /*--------------------------------------------------------------------------
  2653. Stops the active song by setting the current song time to 0.
  2654. When the user resumes, it will be from the beginning.
  2655. If it's a live stream it disconnects.
  2656. */
  2657. function privateStop(){
  2658. privateRunCallback('before_stop');
  2659. config.active_song.currentTime = 0;
  2660. config.active_song.pause();
  2661. if( config.active_metadata.live ){
  2662. privateDisconnectStream();
  2663. }
  2664. privateRunCallback('after_stop');
  2665. }
  2666. /*--------------------------------------------------------------------------
  2667. Sets the song volume.
  2668. @param int volume_level A number between 1 and 100 as a percentage of
  2669. min to max for a volume level.
  2670. --------------------------------------------------------------------------*/
  2671. function privateSetVolume( volume_level ){
  2672. config.active_song.volume = volume_level / 100;
  2673. }
  2674. /*--------------------------------------------------------------------------
  2675. Sets the song percentage. If it's a live song, we ignore this because
  2676. we can't skip ahead. This is an issue if you have a playlist with
  2677. a live source.
  2678. @param int song_percentage A number between 1 and 100 as a percentage of
  2679. song completion.
  2680. --------------------------------------------------------------------------*/
  2681. function privateSetSongLocation( song_percentage ){
  2682. if( !config.active_metadata.live ){
  2683. config.active_song.currentTime = ( config.active_song.duration ) * ( song_percentage / 100 );
  2684. }
  2685. }
  2686. /*--------------------------------------------------------------------------
  2687. Disconnects the live stream
  2688. --------------------------------------------------------------------------*/
  2689. function privateDisconnectStream(){
  2690. config.active_song.src = '';
  2691. config.active_song.load();
  2692. }
  2693. /*--------------------------------------------------------------------------
  2694. Reconnects the live stream
  2695. --------------------------------------------------------------------------*/
  2696. function privateReconnectStream(){
  2697. config.active_song.src = config.active_metadata.url;
  2698. config.active_song.load();
  2699. }
  2700. /*
  2701. Defines which methods and variables are public.
  2702. */
  2703. return {
  2704. init: publicInit,
  2705. setDebug: publicSetDebug,
  2706. getActiveSongMetadata: publicGetActiveSongMetadata,
  2707. getSongByIndex: publicGetSongByIndex,
  2708. playNow: publicPlayNow,
  2709. play: publicPlay,
  2710. pause: publicPause,
  2711. registerVisualization: publicRegisterVisualization,
  2712. visualizationCapable: publicVisualizationCapable,
  2713. changeVisualization: publicChangeActiveVisualization,
  2714. addSong: publicAddSong,
  2715. analyser: publicGetAnalyser,
  2716. active: config.active_song
  2717. };
  2718. })();