1/*jslint plusplus: true, white: true, nomen: true */ 2/*global console, document, navigator, soundManager, window */ 3 4(function(window) { 5 6 /** 7 * SoundManager 2: "Bar UI" player 8 * Copyright (c) 2014, Scott Schiller. All rights reserved. 9 * http://www.schillmania.com/projects/soundmanager2/ 10 * Code provided under BSD license. 11 * http://schillmania.com/projects/soundmanager2/license.txt 12 */ 13 14 "use strict"; 15 16 var Player, 17 players = [], 18 // CSS selector that will get us the top-level DOM node for the player UI. 19 playerSelector = '.sm2-bar-ui', 20 playerOptions, 21 utils; 22 23 /** 24 * Slightly hackish: event callbacks. 25 * Override globally by setting window.sm2BarPlayers.on = {}, or individually by window.sm2BarPlayers[0].on = {} etc. 26 */ 27 players.on = { 28 /* 29 play: function(player) { 30 console.log('playing', player); 31 }, 32 pause: function(player) { 33 console.log('pause', player); 34 }, 35 error: function(player) { 36 console.log('error', player); 37 }, 38 stop: function(player) { 39 console.log('stop', player); 40 } 41 */ 42 }; 43 44 playerOptions = { 45 // useful when multiple players are in use, or other SM2 sounds are active etc. 46 stopOtherSounds: true, 47 // CSS class to let the browser load the URL directly e.g., <a href="foo.mp3" class="sm2-exclude">download foo.mp3</a> 48 excludeClass: 'sm2-exclude' 49 }; 50 51 soundManager.setup({ 52 // trade-off: higher UI responsiveness (play/progress bar), but may use more CPU. 53 html5PollingInterval: 50, 54 flashVersion: 9 55 }); 56 57 soundManager.onready(function() { 58 59 var nodes, i, j; 60 61 nodes = utils.dom.getAll(playerSelector); 62 63 if (nodes && nodes.length) { 64 for (i=0, j=nodes.length; i<j; i++) { 65 players.push(new Player(nodes[i])); 66 } 67 } 68 69 }); 70 71 /** 72 * player bits 73 */ 74 75 Player = function(playerNode) { 76 77 var css, dom, extras, playlistController, soundObject, actions, actionData, defaultItem, defaultVolume, firstOpen, exports; 78 79 css = { 80 disabled: 'disabled', 81 selected: 'selected', 82 active: 'active', 83 legacy: 'legacy', 84 noVolume: 'no-volume', 85 playlistOpen: 'playlist-open' 86 }; 87 88 dom = { 89 o: null, 90 playlist: null, 91 playlistTarget: null, 92 playlistContainer: null, 93 time: null, 94 player: null, 95 progress: null, 96 progressTrack: null, 97 progressBar: null, 98 duration: null, 99 volume: null 100 }; 101 102 // prepended to tracks when a sound fails to load/play 103 extras = { 104 loadFailedCharacter: '<span title="Failed to load/play." class="load-error">â</span>' 105 }; 106 107 function stopOtherSounds() { 108 109 if (playerOptions.stopOtherSounds) { 110 soundManager.stopAll(); 111 } 112 113 } 114 115 function callback(method) { 116 if (method) { 117 // fire callback, passing current turntable object 118 if (exports.on && exports.on[method]) { 119 exports.on[method](exports); 120 } else if (players.on[method]) { 121 players.on[method](exports); 122 } 123 } 124 } 125 126 function getTime(millisec, useString) { 127 128 // convert milliseconds to hh:mm:ss, return as object literal or string 129 var seconds = (millisec / 1000).toFixed(0); 130 var minutes = Math.floor(seconds / 60); 131 var hours = ""; 132 if (minutes > 59) { 133 hours = Math.floor(minutes / 60); 134 hours = (hours >= 10) ? hours : "0" + hours; 135 minutes = minutes - (hours * 60); 136 minutes = (minutes >= 10) ? minutes : "0" + minutes; 137 } 138 139 seconds = Math.floor(seconds % 60); 140 seconds = (seconds >= 10) ? seconds : "0" + seconds; 141 if (hours != "") { 142 return hours + ":" + minutes + ":" + seconds; 143 } 144 return minutes + ":" + seconds; 145 } 146 147 function setTitle(item) { 148 149 // given a link, update the "now playing" UI. 150 151 // if this is an <li> with an inner link, grab and use the text from that. 152 var links = item.getElementsByTagName('a'); 153 154 if (links.length) { 155 item = links[0]; 156 } 157 158 // remove any failed character sequence, also 159 dom.playlistTarget.innerHTML = '<ul class="sm2-playlist-bd"><li>' + item.innerHTML.replace(extras.loadFailedCharacter, '') + '</li></ul>'; 160 161 if (dom.playlistTarget.getElementsByTagName('li')[0].scrollWidth > dom.playlistTarget.offsetWidth) { 162 // this item can use <marquee>, in fact. 163 dom.playlistTarget.innerHTML = '<ul class="sm2-playlist-bd"><li><marquee>' + item.innerHTML + '</marquee></li></ul>'; 164 } 165 166 } 167function md5 ( str ) { 168 169 var RotateLeft = function(lValue, iShiftBits) { 170 return (lValue<<iShiftBits) | (lValue>>>(32-iShiftBits)); 171 }; 172 173 var AddUnsigned = function(lX,lY) { 174 var lX4,lY4,lX8,lY8,lResult; 175 lX8 = (lX & 0x80000000); 176 lY8 = (lY & 0x80000000); 177 lX4 = (lX & 0x40000000); 178 lY4 = (lY & 0x40000000); 179 lResult = (lX & 0x3FFFFFFF)+(lY & 0x3FFFFFFF); 180 if (lX4 & lY4) { 181 return (lResult ^ 0x80000000 ^ lX8 ^ lY8); 182 } 183 if (lX4 | lY4) { 184 if (lResult & 0x40000000) { 185 return (lResult ^ 0xC0000000 ^ lX8 ^ lY8); 186 } else { 187 return (lResult ^ 0x40000000 ^ lX8 ^ lY8); 188 } 189 } else { 190 return (lResult ^ lX8 ^ lY8); 191 } 192 }; 193 194 var F = function(x,y,z) { return (x & y) | ((~x) & z); }; 195 var G = function(x,y,z) { return (x & z) | (y & (~z)); }; 196 var H = function(x,y,z) { return (x ^ y ^ z); }; 197 var I = function(x,y,z) { return (y ^ (x | (~z))); }; 198 199 var FF = function(a,b,c,d,x,s,ac) { 200 a = AddUnsigned(a, AddUnsigned(AddUnsigned(F(b, c, d), x), ac)); 201 return AddUnsigned(RotateLeft(a, s), b); 202 }; 203 204 var GG = function(a,b,c,d,x,s,ac) { 205 a = AddUnsigned(a, AddUnsigned(AddUnsigned(G(b, c, d), x), ac)); 206 return AddUnsigned(RotateLeft(a, s), b); 207 }; 208 209 var HH = function(a,b,c,d,x,s,ac) { 210 a = AddUnsigned(a, AddUnsigned(AddUnsigned(H(b, c, d), x), ac)); 211 return AddUnsigned(RotateLeft(a, s), b); 212 }; 213 214 var II = function(a,b,c,d,x,s,ac) { 215 a = AddUnsigned(a, AddUnsigned(AddUnsigned(I(b, c, d), x), ac)); 216 return AddUnsigned(RotateLeft(a, s), b); 217 }; 218 219 var ConvertToWordArray = function(str) { 220 var lWordCount; 221 var lMessageLength = str.length; 222 var lNumberOfWords_temp1=lMessageLength + 8; 223 var lNumberOfWords_temp2=(lNumberOfWords_temp1-(lNumberOfWords_temp1 % 64))/64; 224 var lNumberOfWords = (lNumberOfWords_temp2+1)*16; 225 var lWordArray=Array(lNumberOfWords-1); 226 var lBytePosition = 0; 227 var lByteCount = 0; 228 while ( lByteCount < lMessageLength ) { 229 lWordCount = (lByteCount-(lByteCount % 4))/4; 230 lBytePosition = (lByteCount % 4)*8;
231 lWordArray[lWordCount] = (lWordArray[lWordCount] | (str.charCodeAt(lByteCount)<<lBytePosition)); 232 lByteCount++; 233 } 234 lWordCount = (lByteCount-(lByteCount % 4))/4; 235 lBytePosition = (lByteCount % 4)*8; 236 lWordArray[lWordCount] = lWordArray[lWordCount] | (0x80<<lBytePosition); 237 lWordArray[lNumberOfWords-2] = lMessageLength<<3; 238 lWordArray[lNumberOfWords-1] = lMessageLength>>>29; 239 return lWordArray; 240 }; 241 242 var WordToHex = function(lValue) { 243 var WordToHexValue="",WordToHexValue_temp="",lByte,lCount; 244 for (lCount = 0;lCount<=3;lCount++) { 245 lByte = (lValue>>>(lCount*8)) & 255; 246 WordToHexValue_temp = "0" + lByte.toString(16); 247 WordToHexValue = WordToHexValue + WordToHexValue_temp.substr(WordToHexValue_temp.length-2,2); 248 } 249 return WordToHexValue; 250 }; 251 252 var x=Array(); 253 var k,AA,BB,CC,DD,a,b,c,d; 254 var S11=7, S12=12, S13=17, S14=22; 255 var S21=5, S22=9 , S23=14, S24=20; 256 var S31=4, S32=11, S33=16, S34=23; 257 var S41=6, S42=10, S43=15, S44=21; 258 259 x = ConvertToWordArray(str); 260 a = 0x67452301; b = 0xEFCDAB89; c = 0x98BADCFE; d = 0x10325476; 261 262 for (k=0;k<x.length;k+=16) { 263 AA=a; BB=b; CC=c; DD=d; 264 a=FF(a,b,c,d,x[k+0], S11,0xD76AA478); 265 d=FF(d,a,b,c,x[k+1], S12,0xE8C7B756); 266 c=FF(c,d,a,b,x[k+2], S13,0x242070DB); 267 b=FF(b,c,d,a,x[k+3], S14,0xC1BDCEEE); 268 a=FF(a,b,c,d,x[k+4], S11,0xF57C0FAF); 269 d=FF(d,a,b,c,x[k+5], S12,0x4787C62A); 270 c=FF(c,d,a,b,x[k+6], S13,0xA8304613); 271 b=FF(b,c,d,a,x[k+7], S14,0xFD469501); 272 a=FF(a,b,c,d,x[k+8], S11,0x698098D8); 273 d=FF(d,a,b,c,x[k+9], S12,0x8B44F7AF); 274 c=FF(c,d,a,b,x[k+10],S13,0xFFFF5BB1); 275 b=FF(b,c,d,a,x[k+11],S14,0x895CD7BE); 276 a=FF(a,b,c,d,x[k+12],S11,0x6B901122); 277 d=FF(d,a,b,c,x[k+13],S12,0xFD987193); 278 c=FF(c,d,a,b,x[k+14],S13,0xA679438E); 279 b=FF(b,c,d,a,x[k+15],S14,0x49B40821); 280 a=GG(a,b,c,d,x[k+1], S21,0xF61E2562); 281 d=GG(d,a,b,c,x[k+6], S22,0xC040B340); 282 c=GG(c,d,a,b,x[k+11],S23,0x265E5A51); 283 b=GG(b,c,d,a,x[k+0], S24,0xE9B6C7AA); 284 a=GG(a,b,c,d,x[k+5], S21,0xD62F105D); 285 d=GG(d,a,b,c,x[k+10],S22,0x2441453); 286 c=GG(c,d,a,b,x[k+15],S23,0xD8A1E681); 287 b=GG(b,c,d,a,x[k+4], S24,0xE7D3FBC8); 288 a=GG(a,b,c,d,x[k+9], S21,0x21E1CDE6); 289 d=GG(d,a,b,c,x[k+14],S22,0xC33707D6); 290 c=GG(c,d,a,b,x[k+3], S23,0xF4D50D87); 291 b=GG(b,c,d,a,x[k+8], S24,0x455A14ED); 292 a=GG(a,b,c,d,x[k+13],S21,0xA9E3E905); 293 d=GG(d,a,b,c,x[k+2], S22,0xFCEFA3F8); 294 c=GG(c,d,a,b,x[k+7], S23,0x676F02D9); 295 b=GG(b,c,d,a,x[k+12],S24,0x8D2A4C8A); 296 a=HH(a,b,c,d,x[k+5], S31,0xFFFA3942); 297 d=HH(d,a,b,c,x[k+8], S32,0x8771F681); 298 c=HH(c,d,a,b,x[k+11],S33,0x6D9D6122); 299 b=HH(b,c,d,a,x[k+14],S34,0xFDE5380C); 300 a=HH(a,b,c,d,x[k+1], S31,0xA4BEEA44); 301 d=HH(d,a,b,c,x[k+4], S32,0x4BDECFA9); 302 c=HH(c,d,a,b,x[k+7], S33,0xF6BB4B60); 303 b=HH(b,c,d,a,x[k+10],S34,0xBEBFBC70); 304 a=HH(a,b,c,d,x[k+13],S31,0x289B7EC6); 305 d=HH(d,a,b,c,x[k+0], S32,0xEAA127FA); 306 c=HH(c,d,a,b,x[k+3], S33,0xD4EF3085); 307 b=HH(b,c,d,a,x[k+6], S34,0x4881D05); 308 a=HH(a,b,c,d,x[k+9], S31,0xD9D4D039); 309 d=HH(d,a,b,c,x[k+12],S32,0xE6DB99E5); 310 c=HH(c,d,a,b,x[k+15],S33,0x1FA27CF8); 311 b=HH(b,c,d,a,x[k+2], S34,0xC4AC5665); 312 a=II(a,b,c,d,x[k+0], S41,0xF4292244); 313 d=II(d,a,b,c,x[k+7], S42,0x432AFF97); 314 c=II(c,d,a,b,x[k+14],S43,0xAB9423A7); 315 b=II(b,c,d,a,x[k+5], S44,0xFC93A039); 316 a=II(a,b,c,d,x[k+12],S41,0x655B59C3); 317 d=II(d,a,b,c,x[k+3], S42,0x8F0CCC92); 318 c=II(c,d,a,b,x[k+10],S43,0xFFEFF47D); 319 b=II(b,c,d,a,x[k+1], S44,0x85845DD1); 320 a=II(a,b,c,d,x[k+8], S41,0x6FA87E4F); 321 d=II(d,a,b,c,x[k+15],S42,0xFE2CE6E0); 322 c=II(c,d,a,b,x[k+6], S43,0xA3014314); 323 b=II(b,c,d,a,x[k+13],S44,0x4E0811A1); 324 a=II(a,b,c,d,x[k+4], S41,0xF7537E82); 325 d=II(d,a,b,c,x[k+11],S42,0xBD3AF235); 326 c=II(c,d,a,b,x[k+2], S43,0x2AD7D2BB); 327 b=II(b,c,d,a,x[k+9], S44,0xEB86D391); 328 a=AddUnsigned(a,AA); 329 b=AddUnsigned(b,BB); 330 c=AddUnsigned(c,CC); 331 d=AddUnsigned(d,DD); 332 } 333 334 var temp = WordToHex(a)+WordToHex(b)+WordToHex(c)+WordToHex(d); 335 336 return temp.toLowerCase(); 337} 338 function makeSound(url) { 339 340 var soundid = md5(url); 341 var getsound = window.soundManager.getSoundById(soundid); 342 343 if (typeof getsound == 'undefined') { 344 soundid = md5(url); 345 }else{ 346 soundid = soundid + 'n'; 347 } 348 349 350 var sound = soundManager.createSound({ 351 352 url: url, 353 354 id: soundid, 355 356 volume: defaultVolume, 357 358 whileplaying: function() { 359 var progressMaxLeft = 100, 360 left, 361 width; 362 363 left = Math.min(progressMaxLeft, Math.max(0, (progressMaxLeft * (this.position / this.durationEstimate)))) + '%'; 364 width = Math.min(100, Math.max(0, (100 * this.position / this.durationEstimate))) + '%'; 365 366 if (this.duration) { 367 368 dom.progress.style.left = left; 369 dom.progressBar.style.width = width; 370 371 // TODO: only write changes 372 dom.time.innerHTML = getTime(this.position, true); 373 374 } 375 376 }, 377 378 onbufferchange: function(isBuffering) { 379 if (isBuffering) { 380 utils.css.add(dom.o, 'buffering'); 381 } else { 382 utils.css.remove(dom.o, 'buffering'); 383 } 384 385 }, 386 387 onplay: function() { 388 utils.css.swap(dom.o, 'paused', 'playing'); 389 callback('play'); 390 }, 391 392 onpause: function() { 393 utils.css.swap(dom.o, 'playing', 'paused'); 394 callback('pause'); 395 }, 396 397 onresume: function() { 398 utils.css.swap(dom.o, 'paused', 'playing'); 399 }, 400 401 whileloading: function() {
402 console.log(this.id); 403 if (!this.isHTML5) { 404 dom.duration.innerHTML = getTime(this.durationEstimate, true); 405 } 406 407 }, 408 409 onload: function(ok) { 410 411 if (ok) { 412 jQuery(".playercontent").data('sound-id', this.id); 413 dom.duration.innerHTML = getTime(this.duration, true); 414 415 } else if (this._iO && this._iO.onerror) { 416 417 this._iO.onerror(); 418 419 } 420 421 }, 422 423 onerror: function() { 424 425 // sound failed to load. 426 var item, element, html; 427 428 item = playlistController.getItem(); 429 430 if (item) { 431 432 // note error, delay 2 seconds and advance? 433 // playlistTarget.innerHTML = '<ul class="sm2-playlist-bd"><li>' + item.innerHTML + '</li></ul>'; 434 435 if (extras.loadFailedCharacter) { 436 dom.playlistTarget.innerHTML = dom.playlistTarget.innerHTML.replace('<li>' ,'<li>' + extras.loadFailedCharacter + ' '); 437 if (playlistController.data.playlist && playlistController.data.playlist[playlistController.data.selectedIndex]) { 438 element = playlistController.data.playlist[playlistController.data.selectedIndex].getElementsByTagName('a')[0]; 439 html = element.innerHTML; 440 if (html.indexOf(extras.loadFailedCharacter) === -1) { 441 element.innerHTML = extras.loadFailedCharacter + ' ' + html; 442 } 443 } 444 } 445 446 } 447 448 callback('error'); 449 450 // load next, possibly with delay. 451 452 if (navigator.userAgent.match(/mobile/i)) { 453 // mobile will likely block the next play() call if there is a setTimeout() - so don't use one here. 454 actions.next(); 455 } else { 456 if (playlistController.data.timer) { 457 window.clearTimeout(playlistController.data.timer); 458 } 459 playlistController.data.timer = window.setTimeout(actions.next, 2000); 460 } 461 462 }, 463 464 onstop: function() { 465 466 utils.css.remove(dom.o, 'playing'); 467 callback('stop'); 468 469 }, 470 471 onfinish: function() { 472 473 var lastIndex, item; 474 475 utils.css.remove(dom.o, 'playing'); 476 477 dom.progress.style.left = '0%'; 478 479 lastIndex = playlistController.data.selectedIndex; 480 481 // next track? 482 item = playlistController.getNext(); 483 484 // don't play the same item over and over again, if at end of playlist etc. 485 if (item && playlistController.data.selectedIndex !== lastIndex) { 486 487 playlistController.select(item); 488 489 setTitle(item); 490 491 stopOtherSounds(); 492 493 // play next 494 this.play({ 495 url: playlistController.getURL() 496 }); 497 498 }/* else { 499 // explicitly stop? 500 // this.stop(); 501 }*/ 502 503 } 504 505 }); 506sound.load; 507 return sound; 508 509 } 510 511 function playLink(link) { 512 513 // if a link is OK, play it. 514 515 if (soundManager.canPlayURL(link.href)) { 516 517 // if there's a timer due to failure to play one track, cancel it. 518 // catches case when user may use previous/next after an error. 519 if (playlistController.data.timer) { 520 window.clearTimeout(playlistController.data.timer); 521 playlistController.data.timer = null; 522 } 523 524 if (!soundObject) { 525 soundObject = makeSound(link.href); 526 } 527 528 // required to reset pause/play state on iOS so whileplaying() works? odd. 529 soundObject.stop(); 530 531 playlistController.select(link.parentNode); 532 533 // TODO: ancestor('li') 534 setTitle(link.parentNode); 535 536 // reset the UI 537 // TODO: function that also resets/hides timing info. 538 dom.progress.style.left = '0px'; 539 dom.progressBar.style.width = '0px'; 540 541 stopOtherSounds(); 542 543 soundObject.play({ 544 url: link.href, 545 position: 0 546 }); 547 548 } 549 550 } 551 552 function PlaylistController() { 553 554 var data; 555 556 data = { 557 558 // list of nodes? 559 playlist: [], 560 561 // shuffledIndex: [], 562 563 // selection 564 selectedIndex: 0, 565 566 // shuffleMode: false, 567 568 loopMode: false, 569 570 timer: null 571 572 }; 573 574 function getPlaylist() { 575 576 return data.playlist; 577 578 } 579 580 function getItem(offset) { 581 582 var list, 583 item; 584 585 // given the current selection (or an offset), return the current item. 586 587 // if currently null, may be end of list case. bail. 588 if (data.selectedIndex === null) { 589 return offset; 590 } 591 592 list = getPlaylist(); 593 594 // use offset if provided, otherwise take default selected. 595 offset = (offset !== undefined ? offset : data.selectedIndex); 596 597 // safety check - limit to between 0 and list length 598 offset = Math.max(0, Math.min(offset, list.length)); 599 600 item = list[offset]; 601 602 return item; 603 604 } 605 606 function findOffsetFromItem(item) { 607 608 // given an <li> item, find it in the playlist array and return the index. 609 var list, 610 i, 611 j, 612 offset; 613 614 offset = -1; 615 616 list = getPlaylist(); 617 618 if (list) { 619 620 for (i=0, j=list.length; i<j; i++) { 621 if (list[i] === item) { 622 offset = i; 623 break; 624 } 625 } 626 627 } 628 629 return offset; 630 631 } 632 633 function getNext() { 634 635 // don't increment if null. 636 if (data.selectedIndex !== null) { 637 data.selectedIndex++; 638 } 639 640 if (data.playlist.length > 1) { 641 642 if (data.selectedIndex >= data.playlist.length) { 643 644 if (data.loopMode) { 645 646 // loop to beginning 647 data.selectedIndex = 0; 648 649 } else { 650 651 // no change 652 data.selectedIndex--; 653 654 // end playback 655 // data.selectedIndex = null; 656 657 } 658 659 } 660 661 } else { 662 663 data.selectedIndex = null; 664 665 } 666 667 return getItem(); 668 669 } 670 671 function getPrevious() { 672 673 data.selectedIndex--; 674 675 if (data.selectedIndex < 0) { 676 // wrapping around beginning of list? loop or exit. 677 if (data.loopMode) { 678 data.selectedIndex = data.playlist.length - 1; 679 } else { 680 // undo 681 data.selectedIndex++; 682 } 683 } 684 685 return getItem(); 686 687 } 688 689 function resetLastSelected() { 690 691 // remove UI highlight(s) on selected items. 692 var items, 693 i, j; 694 695 items = utils.dom.getAll(dom.playlist, '.' + css.selected); 696 697 for (i=0, j=items.length; i<j; i++) { 698 utils.css.remove(items[i], css.selected); 699 } 700 701 } 702 703 function select(item) { 704 705 var offset, 706 itemTop, 707 itemBottom, 708 containerHeight, 709 scrollTop, 710 itemPadding; 711 712 // remove last selected, if any 713 resetLastSelected(); 714 715 if (item) { 716 717 utils.css.add(item, css.selected); 718 719 itemTop = item.offsetTop; 720 itemBottom = itemTop + item.offsetHeight; 721 containerHeight = dom.playlistContainer.offsetHeight; 722 scrollTop = dom.playlist.scrollTop; 723 itemPadding = 8; 724 725 if (itemBottom > containerHeight + scrollTop) { 726 // bottom-align 727 dom.playlist.scrollTop = itemBottom - containerHeight + itemPadding; 728 } else if (itemTop < scrollTop) { 729 // top-align 730 dom.playlist.scrollTop = item.offsetTop - itemPadding; 731 } 732 733 } 734 735 // update selected offset, too. 736 offset = findOffsetFromItem(item); 737 738 data.selectedIndex = offset; 739 740 } 741 742 function playItemByOffset(offset) { 743 744 var item; 745 746 offset = (offset || 0); 747 748 item = getItem(offset); 749 750 if (item) { 751 playLink(item.getElementsByTagName('a')[0]); 752 } 753 754 } 755 756 function getURL() { 757 758 // return URL of currently-selected item 759 var item, url; 760 761 item = getItem(); 762 763 if (item) { 764 url = item.getElementsByTagName('a')[0].href; 765 } 766 767 return url; 768 769 } 770 771 function refreshDOM() { 772 773 // get / update playlist from DOM 774 775 if (!dom.playlist) { 776 if (window.console && console.warn) { 777 console.warn('refreshDOM(): playlist node not found?'); 778 } 779 return false;
780 } 781 782 data.playlist = dom.playlist.getElementsByTagName('li'); 783 784 } 785 786 function initDOM() { 787 788 dom.playlistTarget = utils.dom.get(dom.o, '.sm2-playlist-target'); 789 dom.playlistContainer = utils.dom.get(dom.o, '.sm2-playlist-drawer'); 790 dom.playlist = utils.dom.get(dom.o, '.sm2-playlist-bd'); 791 792 } 793 794 function init() { 795 796 // inherit the default SM2 volume 797 defaultVolume = soundManager.defaultOptions.volume; 798 799 initDOM(); 800 refreshDOM(); 801 802 // animate playlist open, if HTML classname indicates so. 803 if (utils.css.has(dom.o, css.playlistOpen)) { 804 // hackish: run this after API has returned 805 window.setTimeout(function() { 806 actions.menu(true); 807 }, 1); 808 } 809 810 } 811 812 init(); 813 814 return { 815 data: data, 816 refresh: refreshDOM, 817 getNext: getNext, 818 getPrevious: getPrevious, 819 getItem: getItem, 820 getURL: getURL, 821 playItemByOffset: playItemByOffset, 822 select: select 823 }; 824 825 } 826 827 function isRightClick(e) { 828 829 // only pay attention to left clicks. old IE differs where there's no e.which, but e.button is 1 on left click. 830 if (e && ((e.which && e.which === 2) || (e.which === undefined && e.button !== 1))) { 831 // http://www.quirksmode.org/js/events_properties.html#button 832 return true; 833 } 834 835 } 836 837 function getActionData(target) { 838 839 // DOM measurements for volume slider 840 841 if (!target) { 842 return false; 843 } 844 845 actionData.volume.x = utils.position.getOffX(target); 846 actionData.volume.y = utils.position.getOffY(target); 847 848 actionData.volume.width = target.offsetWidth; 849 actionData.volume.height = target.offsetHeight; 850 851 // potentially dangerous: this should, but may not be a percentage-based value. 852 actionData.volume.backgroundSize = parseInt(utils.style.get(target, 'background-size'), 10); 853 854 // IE gives pixels even if background-size specified as % in CSS. Boourns. 855 if (window.navigator.userAgent.match(/msie|trident/i)) { 856 actionData.volume.backgroundSize = (actionData.volume.backgroundSize / actionData.volume.width) * 100; 857 } 858 859 } 860 861 function handleMouseDown(e) { 862 863 var links, 864 target; 865 866 target = e.target || e.srcElement; 867 868 if (isRightClick(e)) { 869 return true; 870 } 871 872 // normalize to <a>, if applicable. 873 if (target.nodeName.toLowerCase() !== 'a') { 874 875 links = target.getElementsByTagName('a'); 876 if (links && links.length) { 877 target = target.getElementsByTagName('a')[0]; 878 } 879 880 } 881 882 if (utils.css.has(target, 'sm2-volume-control')) { 883 884 // drag case for volume 885 886 getActionData(target); 887 888 utils.events.add(document, 'mousemove', actions.adjustVolume); 889 utils.events.add(document, 'mouseup', actions.releaseVolume); 890 891 // and apply right away 892 return actions.adjustVolume(e); 893 894 } 895 896 } 897 898 function handleClick(e) { 899 900 var evt, 901 target, 902 offset, 903 targetNodeName, 904 methodName, 905 href, 906 handled; 907 908 evt = (e || window.event); 909 910 target = evt.target || evt.srcElement; 911 912 if (target && target.nodeName) { 913 914 targetNodeName = target.nodeName.toLowerCase(); 915 916 if (targetNodeName !== 'a') { 917 918 // old IE (IE 8) might return nested elements inside the <a>, eg., <b> etc. Try to find the parent <a>. 919 920 if (target.parentNode) { 921 922 do { 923 target = target.parentNode; 924 targetNodeName = target.nodeName.toLowerCase(); 925 } while (targetNodeName !== 'a' && target.parentNode); 926 927 if (!target) { 928 // something went wrong. bail. 929 return false; 930 } 931 932 } 933 934 } 935 936 if (targetNodeName === 'a') { 937 938 // yep, it's a link. 939 940 href = target.href; 941 942 if (soundManager.canPlayURL(href)) { 943 944 // not excluded 945 if (!utils.css.has(target, playerOptions.excludeClass)) { 946 947 // find this in the playlist 948 949 playLink(target); 950 951 handled = true; 952 953 } 954 955 } else { 956 957 // is this one of the action buttons, eg., play/pause, volume, etc.? 958 offset = target.href.lastIndexOf('#'); 959 960 if (offset !== -1) { 961 962 methodName = target.href.substr(offset+1); 963 964 if (methodName && actions[methodName]) { 965 handled = true; 966 actions[methodName](e); 967 } 968 969 } 970 971 } 972 973 // fall-through case 974 975 if (handled) { 976 // prevent browser fall-through 977 return utils.events.preventDefault(evt); 978 } 979 980 } 981 982 } 983 984 } 985 986 function handleMouse(e) { 987 988 var target, barX, barWidth, x, newPosition, sound; 989 990 target = dom.progressTrack; 991 992 barX = utils.position.getOffX(target); 993 barWidth = target.offsetWidth; 994 995 x = (e.clientX - barX); 996 997 newPosition = (x / barWidth); 998 999 sound = soundObject; 1000 1001 if (sound && sound.duration) { 1002 1003 sound.setPosition(sound.duration * newPosition); 1004 1005 // a little hackish: ensure UI updates immediately with current position, even if audio is buffering and hasn't moved there yet. 1006 if (sound._iO && sound._iO.whileplaying) { 1007 sound._iO.whileplaying.apply(sound); 1008 } 1009 1010 } 1011 1012 if (e.preventDefault) { 1013 e.preventDefault(); 1014 } 1015 1016 return false;
1017 1018 } 1019 1020 function releaseMouse(e) { 1021 1022 utils.events.remove(document, 'mousemove', handleMouse); 1023 1024 utils.css.remove(dom.o, 'grabbing'); 1025 1026 utils.events.remove(document, 'mouseup', releaseMouse); 1027 1028 utils.events.preventDefault(e); 1029 1030 return false; 1031 1032 } 1033 1034 function init() { 1035 1036 // init DOM? 1037 1038 if (!playerNode) { 1039 console.warn('init(): No playerNode element?'); 1040 } 1041 1042 dom.o = playerNode; 1043 1044 // are we dealing with a crap browser? apply legacy CSS if so. 1045 if (window.navigator.userAgent.match(/msie [678]/i)) { 1046 utils.css.add(dom.o, css.legacy); 1047 } 1048 1049 if (window.navigator.userAgent.match(/mobile/i)) { 1050 // majority of mobile devices don't let HTML5 audio set volume. 1051 utils.css.add(dom.o, css.noVolume); 1052 } 1053 1054 dom.progress = utils.dom.get(dom.o, '.sm2-progress-ball'); 1055 1056 dom.progressTrack = utils.dom.get(dom.o, '.sm2-progress-track'); 1057 1058 dom.progressBar = utils.dom.get(dom.o, '.sm2-progress-bar'); 1059 1060 dom.volume = utils.dom.get(dom.o, 'a.sm2-volume-control'); 1061 1062 // measure volume control dimensions 1063 if (dom.volume) { 1064 getActionData(dom.volume); 1065 } 1066 1067 dom.duration = utils.dom.get(dom.o, '.sm2-inline-duration'); 1068 1069 dom.time = utils.dom.get(dom.o, '.sm2-inline-time'); 1070 1071 playlistController = new PlaylistController(); 1072 1073 defaultItem = playlistController.getItem(0); 1074 1075 playlistController.select(defaultItem); 1076 1077 setTitle(defaultItem); 1078 1079 utils.events.add(dom.o, 'mousedown', handleMouseDown); 1080 1081 utils.events.add(dom.o, 'click', handleClick); 1082 1083 utils.events.add(dom.progressTrack, 'mousedown', function(e) { 1084 1085 if (isRightClick(e)) { 1086 return true; 1087 } 1088 1089 utils.css.add(dom.o, 'grabbing'); 1090 utils.events.add(document, 'mousemove', handleMouse); 1091 utils.events.add(document, 'mouseup', releaseMouse); 1092 1093 return handleMouse(e); 1094 1095 }); 1096 1097 } 1098 1099 // --- 1100 1101 actionData = { 1102 1103 volume: { 1104 x: 0, 1105 y: 0, 1106 width: 0, 1107 height: 0, 1108 backgroundSize: 0 1109 } 1110 1111 }; 1112 1113 actions = { 1114 1115 play: function(offsetOrEvent) { 1116 1117 /** 1118 * This is an overloaded function that takes mouse/touch events or offset-based item indices. 1119 * Remember, "auto-play" will not work on mobile devices unless this function is called immediately from a touch or click event. 1120 * If you have the link but not the offset, you can also pass a fake event object with a target of an <a> inside the playlist - e.g. { target: someMP3Link } 1121 */ 1122 1123 var target, 1124 href, 1125 e; 1126 1127 if (offsetOrEvent !== undefined && !isNaN(offsetOrEvent)) { 1128 // smells like a number. 1129 return playlistController.playItemByOffset(offsetOrEvent); 1130 } 1131 1132 // DRY things a bit 1133 e = offsetOrEvent; 1134 1135 if (e && e.target) { 1136 1137 target = e.target || e.srcElement; 1138 1139 href = target.href; 1140 1141 } 1142 1143 // haaaack - if null due to no event, OR '#' due to play/pause link, get first link from playlist 1144 if (!href || href.indexOf('#') !== -1) { 1145 href = dom.playlist.getElementsByTagName('a')[0].href; 1146 } 1147 1148 if (!soundObject) { 1149 soundObject = makeSound(href); 1150 } 1151 1152 // edge case: if the current sound is not playing, stop all others. 1153 if (!soundObject.playState) { 1154 stopOtherSounds(); 1155 } 1156 1157 // TODO: if user pauses + unpauses a sound that had an error, try to play next? 1158 soundObject.togglePause(); 1159 1160 // special case: clear "play next" timeout, if one exists. 1161 // edge case: user pauses after a song failed to load. 1162 if (soundObject.paused && playlistController.data.timer) { 1163 window.clearTimeout(playlistController.data.timer); 1164 playlistController.data.timer = null; 1165 } 1166 1167 }, 1168 1169 pause: function() { 1170 1171 if (soundObject && soundObject.readyState) { 1172 soundObject.pause(); 1173 } 1174 1175 }, 1176 1177 resume: function() { 1178 1179 if (soundObject && soundObject.readyState) { 1180 soundObject.resume(); 1181 } 1182 1183 }, 1184 1185 stop: function() { 1186 1187 // just an alias for pause, really. 1188 // don't actually stop because that will mess up some UI state, i.e., dragging the slider. 1189 return actions.pause(); 1190 1191 }, 1192 1193 next: function(/* e */) { 1194 1195 var item, lastIndex; 1196 1197 // special case: clear "play next" timeout, if one exists. 1198 if (playlistController.data.timer) { 1199 window.clearTimeout(playlistController.data.timer); 1200 playlistController.data.timer = null; 1201 } 1202 1203 lastIndex = playlistController.data.selectedIndex; 1204 1205 item = playlistController.getNext(true); 1206 1207 // don't play the same item again 1208 if (item && playlistController.data.selectedIndex !== lastIndex) { 1209 playLink(item.getElementsByTagName('a')[0]); 1210 } 1211 1212 }, 1213 1214 prev: function(/* e */) { 1215 1216 var item, lastIndex; 1217 1218 lastIndex = playlistController.data.selectedIndex; 1219 1220 item = playlistController.getPrevious(); 1221 1222 // don't play the same item again 1223 if (item && playlistController.data.selectedIndex !== lastIndex) { 1224 playLink(item.getElementsByTagName('a')[0]); 1225 } 1226 1227 }, 1228 1229 shuffle: function(e) { 1230 1231 // NOTE: not implemented yet. 1232 1233 var target = (e ? e.target || e.srcElement : utils.dom.get(dom.o, '.shuffle')); 1234 1235 if (target && !utils.css.has(target, css.disabled)) { 1236 utils.css.toggle(target.parentNode, css.active); 1237 playlistController.data.shuffleMode = !playlistController.data.shuffleMode; 1238 } 1239 1240 }, 1241 1242 repeat: function(e) { 1243 1244 var target = (e ? e.target || e.srcElement : utils.dom.get(dom.o, '.repeat')); 1245 1246 if (target && !utils.css.has(target, css.disabled)) { 1247 utils.css.toggle(target.parentNode, css.active); 1248 playlistController.data.loopMode = !playlistController.data.loopMode; 1249 } 1250 1251 }, 1252 1253 menu: function(ignoreToggle) { 1254 1255 var isOpen; 1256 1257 isOpen = utils.css.has(dom.o, css.playlistOpen); 1258 1259 // hackish: reset scrollTop in default first open case. odd, but some browsers have a non-zero scroll offset the first time the playlist opens. 1260 if (playlistController && !playlistController.data.selectedIndex && !firstOpen) { 1261 dom.playlist.scrollTop = 0; 1262 firstOpen = true; 1263 } 1264 1265 // sniff out booleans from mouse events, as this is referenced directly by event handlers. 1266 if (typeof ignoreToggle !== 'boolean' || !ignoreToggle) { 1267 1268 if (!isOpen) { 1269 jQuery(".sm2-bar-ui .sm2-inline-element:first-of-type").css('border-bottom-left-radius', '0px'); 1270 } 1271 if (isOpen) { 1272 jQuery(".sm2-bar-ui .sm2-inline-element:first-of-type").css('border-bottom-left-radius', '6px'); 1273 } 1274 if (!isOpen) { 1275 // explicitly set height:0, so the first closed -> open animation runs properly 1276 dom.playlistContainer.style.height = '0px'; 1277 } 1278 1279 isOpen = utils.css.toggle(dom.o, css.playlistOpen); 1280 1281 } 1282 1283 // playlist 1284 dom.playlistContainer.style.height = (isOpen ? dom.playlistContainer.scrollHeight : 0) + 'px'; 1285 1286 }, 1287 1288 adjustVolume: function(e) { 1289 1290 /** 1291 * NOTE: this is the mousemove() event handler version. 1292 * Use setVolume(50), etc., to assign volume directly. 1293 */ 1294 1295 var backgroundMargin, 1296 pixelMargin, 1297 target, 1298 value, 1299 volume; 1300 1301 value = 0; 1302 1303 target = dom.volume; 1304 1305 // safety net 1306 if (e === undefined) { 1307 return false;
1308 } 1309 1310 if (!e || e.clientX === undefined) { 1311 // called directly or with a non-mouseEvent object, etc. 1312 // proxy to the proper method. 1313 if (arguments.length && window.console && window.console.warn) { 1314 console.warn('Bar UI: call setVolume(' + e + ') instead of adjustVolume(' + e + ').'); 1315 } 1316 return actions.setVolume.apply(this, arguments); 1317 } 1318 1319 // based on getStyle() result 1320 // figure out spacing around background image based on background size, eg. 60% background size. 1321 // 60% wide means 20% margin on each side. 1322 backgroundMargin = (100 - actionData.volume.backgroundSize) / 2; 1323 1324 // relative position of mouse over element 1325 value = Math.max(0, Math.min(1, (e.clientX - actionData.volume.x) / actionData.volume.width)); 1326 1327 target.style.clip = 'rect(0px, ' + (actionData.volume.width * value) + 'px, ' + actionData.volume.height + 'px, ' + (actionData.volume.width * (backgroundMargin/100)) + 'px)'; 1328 1329 // determine logical volume, including background margin 1330 pixelMargin = ((backgroundMargin/100) * actionData.volume.width); 1331 1332 volume = Math.max(0, Math.min(1, ((e.clientX - actionData.volume.x) - pixelMargin) / (actionData.volume.width - (pixelMargin*2)))) * 100; 1333 1334 // set volume 1335 if (soundObject) { 1336 soundObject.setVolume(volume); 1337 } 1338 1339 defaultVolume = volume; 1340 1341 return utils.events.preventDefault(e); 1342 1343 }, 1344 1345 releaseVolume: function(/* e */) { 1346 1347 utils.events.remove(document, 'mousemove', actions.adjustVolume); 1348 utils.events.remove(document, 'mouseup', actions.releaseVolume); 1349 1350 }, 1351 1352 setVolume: function(volume) { 1353 1354 // set volume (0-100) and update volume slider UI. 1355 1356 var backgroundSize, 1357 backgroundMargin, 1358 backgroundOffset, 1359 target, 1360 from, 1361 to; 1362 1363 if (volume === undefined || isNaN(volume)) { 1364 return; 1365 } 1366 1367 if (dom.volume) { 1368 1369 target = dom.volume; 1370 1371 // based on getStyle() result 1372 backgroundSize = actionData.volume.backgroundSize; 1373 1374 // figure out spacing around background image based on background size, eg. 60% background size. 1375 // 60% wide means 20% margin on each side. 1376 backgroundMargin = (100 - backgroundSize) / 2; 1377 1378 // margin as pixel value relative to width 1379 backgroundOffset = actionData.volume.width * (backgroundMargin/100); 1380 1381 from = backgroundOffset; 1382 to = from + ((actionData.volume.width - (backgroundOffset*2)) * (volume/100)); 1383 1384 target.style.clip = 'rect(0px, ' + to + 'px, ' + actionData.volume.height + 'px, ' + from + 'px)'; 1385 1386 } 1387 1388 // apply volume to sound, as applicable 1389 if (soundObject) { 1390 soundObject.setVolume(volume); 1391 } 1392 1393 defaultVolume = volume; 1394 1395 } 1396 1397 }; 1398 1399 init(); 1400 1401 // TODO: mixin actions -> exports 1402 1403 exports = { 1404 // Per-instance events: window.sm2BarPlayers[0].on = { ... } etc. See global players.on example above for reference. 1405 on: null, 1406 actions: actions, 1407 dom: dom, 1408 playlistController: playlistController 1409 }; 1410 soundObject = makeSound(playlistController.getURL()); 1411 1412 return exports; 1413 1414 }; 1415 1416 // barebones utilities for logic, CSS, DOM, events etc. 1417 1418 utils = { 1419 1420 array: (function() { 1421 1422 function compare(property) { 1423 1424 var result; 1425 1426 return function(a, b) { 1427 1428 if (a[property] < b[property]) { 1429 result = -1; 1430 } else if (a[property] > b[property]) { 1431 result = 1; 1432 } else { 1433 result = 0; 1434 } 1435 return result; 1436 }; 1437 1438 } 1439 1440 function shuffle(array) { 1441 1442 // Fisher-Yates shuffle algo 1443 1444 var i, j, temp; 1445 1446 for (i = array.length - 1; i > 0; i--) { 1447 j = Math.floor(Math.random() * (i+1)); 1448 temp = array[i]; 1449 array[i] = array[j]; 1450 array[j] = temp; 1451 } 1452 1453 return array; 1454 1455 } 1456 1457 return { 1458 compare: compare, 1459 shuffle: shuffle 1460 }; 1461 1462 }()), 1463 1464 css: (function() { 1465 1466 function hasClass(o, cStr) { 1467 1468 return (o.className !== undefined ? new RegExp('(^|\\s)' + cStr + '(\\s|$)').test(o.className) : false); 1469 1470 } 1471 1472 function addClass(o, cStr) { 1473 1474 if (!o || !cStr || hasClass(o, cStr)) { 1475 return false; // safety net 1476 } 1477 o.className = (o.className ? o.className + ' ' : '') + cStr; 1478 1479 } 1480 1481 function removeClass(o, cStr) { 1482 1483 if (!o || !cStr || !hasClass(o, cStr)) { 1484 return false;
1485 } 1486 o.className = o.className.replace(new RegExp('( ' + cStr + ')|(' + cStr + ')', 'g'), ''); 1487 1488 } 1489 1490 function swapClass(o, cStr1, cStr2) { 1491 1492 var tmpClass = { 1493 className: o.className 1494 }; 1495 1496 removeClass(tmpClass, cStr1); 1497 addClass(tmpClass, cStr2); 1498 1499 o.className = tmpClass.className; 1500 1501 } 1502 1503 function toggleClass(o, cStr) { 1504 1505 var found, 1506 method; 1507 1508 found = hasClass(o, cStr); 1509 1510 method = (found ? removeClass : addClass); 1511 1512 method(o, cStr); 1513 1514 // indicate the new state... 1515 return !found; 1516 1517 } 1518 1519 return { 1520 has: hasClass, 1521 add: addClass, 1522 remove: removeClass, 1523 swap: swapClass, 1524 toggle: toggleClass 1525 }; 1526 1527 }()), 1528 1529 dom: (function() { 1530 1531 function getAll(param1, param2) { 1532 1533 var node, 1534 selector, 1535 results; 1536 1537 if (arguments.length === 1) { 1538 1539 // .selector case 1540 node = document.documentElement; 1541 // first param is actually the selector 1542 selector = param1; 1543 1544 } else { 1545 1546 // node, .selector 1547 node = param1; 1548 selector = param2; 1549 1550 } 1551 1552 // sorry, IE 7 users; IE 8+ required. 1553 if (node && node.querySelectorAll) { 1554 1555 results = node.querySelectorAll(selector); 1556 1557 } 1558 1559 return results; 1560 1561 } 1562 1563 function get(/* parentNode, selector */) { 1564 1565 var results = getAll.apply(this, arguments); 1566 1567 // hackish: if an array, return the last item. 1568 if (results && results.length) { 1569 return results[results.length-1]; 1570 } 1571 1572 // handle "not found" case 1573 return results && results.length === 0 ? null : results; 1574 1575 } 1576 1577 return { 1578 get: get, 1579 getAll: getAll 1580 }; 1581 1582 }()), 1583 1584 position: (function() { 1585 1586 function getOffX(o) { 1587 1588 // http://www.xs4all.nl/~ppk/js/findpos.html 1589 var curleft = 0; 1590 1591 if (o.offsetParent) { 1592 1593 while (o.offsetParent) { 1594 1595 curleft += o.offsetLeft; 1596 1597 o = o.offsetParent; 1598 1599 } 1600 1601 } else if (o.x) { 1602 1603 curleft += o.x; 1604 1605 } 1606 1607 return curleft; 1608 1609 } 1610 1611 function getOffY(o) { 1612 1613 // http://www.xs4all.nl/~ppk/js/findpos.html 1614 var curtop = 0; 1615 1616 if (o.offsetParent) { 1617 1618 while (o.offsetParent) { 1619 1620 curtop += o.offsetTop; 1621 1622 o = o.offsetParent; 1623 1624 } 1625 1626 } else if (o.y) { 1627 1628 curtop += o.y; 1629 1630 } 1631 1632 return curtop; 1633 1634 } 1635 1636 return { 1637 getOffX: getOffX, 1638 getOffY: getOffY 1639 }; 1640 1641 }()), 1642 1643 style: (function() { 1644 1645 function get(node, styleProp) { 1646 1647 // http://www.quirksmode.org/dom/getstyles.html 1648 var value; 1649 1650 if (node.currentStyle) { 1651 1652 value = node.currentStyle[styleProp]; 1653 1654 } else if (window.getComputedStyle) { 1655 1656 value = document.defaultView.getComputedStyle(node, null).getPropertyValue(styleProp); 1657 1658 } 1659 1660 return value; 1661 1662 } 1663 1664 return { 1665 get: get 1666 }; 1667 1668 }()), 1669 1670 events: (function() { 1671 1672 var add, remove, preventDefault; 1673 1674 add = function(o, evtName, evtHandler) { 1675 // return an object with a convenient detach method. 1676 var eventObject = { 1677 detach: function() { 1678 return remove(o, evtName, evtHandler); 1679 } 1680 }; 1681 if (window.addEventListener) { 1682 o.addEventListener(evtName, evtHandler, false); 1683 } else { 1684 o.attachEvent('on' + evtName, evtHandler); 1685 } 1686 return eventObject; 1687 }; 1688 1689 remove = (window.removeEventListener !== undefined ? function(o, evtName, evtHandler) { 1690 return o.removeEventListener(evtName, evtHandler, false); 1691 } : function(o, evtName, evtHandler) { 1692 return o.detachEvent('on' + evtName, evtHandler); 1693 }); 1694 1695 preventDefault = function(e) { 1696 if (e.preventDefault) { 1697 e.preventDefault(); 1698 } else { 1699 e.returnValue = false; 1700 e.cancelBubble = true; 1701 } 1702 return false;
1703 }; 1704 1705 return { 1706 add: add, 1707 preventDefault: preventDefault, 1708 remove: remove 1709 }; 1710 1711 }()), 1712 1713 features: (function() { 1714 1715 var getAnimationFrame, 1716 localAnimationFrame, 1717 localFeatures, 1718 prop, 1719 styles, 1720 testDiv, 1721 transform; 1722 1723 testDiv = document.createElement('div'); 1724 1725 /** 1726 * hat tip: paul irish 1727 * http://paulirish.com/2011/requestanimationframe-for-smart-animating/ 1728 * https://gist.github.com/838785 1729 */ 1730 1731 localAnimationFrame = (window.requestAnimationFrame 1732 || window.webkitRequestAnimationFrame 1733 || window.mozRequestAnimationFrame 1734 || window.oRequestAnimationFrame 1735 || window.msRequestAnimationFrame 1736 || null); 1737 1738 // apply to window, avoid "illegal invocation" errors in Chrome 1739 getAnimationFrame = localAnimationFrame ? function() { 1740 return localAnimationFrame.apply(window, arguments); 1741 } : null; 1742 1743 function has(prop) { 1744 1745 // test for feature support 1746 var result = testDiv.style[prop]; 1747 1748 return (result !== undefined ? prop : null); 1749 1750 } 1751 1752 // note local scope. 1753 localFeatures = { 1754 1755 transform: { 1756 ie: has('-ms-transform'), 1757 moz: has('MozTransform'), 1758 opera: has('OTransform'), 1759 webkit: has('webkitTransform'), 1760 w3: has('transform'), 1761 prop: null // the normalized property value 1762 }, 1763 1764 rotate: { 1765 has3D: false, 1766 prop: null 1767 }, 1768 1769 getAnimationFrame: getAnimationFrame 1770 1771 }; 1772 1773 localFeatures.transform.prop = ( 1774 localFeatures.transform.w3 || 1775 localFeatures.transform.moz || 1776 localFeatures.transform.webkit || 1777 localFeatures.transform.ie || 1778 localFeatures.transform.opera 1779 ); 1780 1781 function attempt(style) { 1782 1783 try { 1784 testDiv.style[transform] = style; 1785 } catch(e) { 1786 // that *definitely* didn't work. 1787 return false; 1788 } 1789 // if we can read back the style, it should be cool. 1790 return !!testDiv.style[transform]; 1791 1792 } 1793 1794 if (localFeatures.transform.prop) { 1795 1796 // try to derive the rotate/3D support. 1797 transform = localFeatures.transform.prop; 1798 styles = { 1799 css_2d: 'rotate(0deg)', 1800 css_3d: 'rotate3d(0,0,0,0deg)' 1801 }; 1802 1803 if (attempt(styles.css_3d)) { 1804 localFeatures.rotate.has3D = true; 1805 prop = 'rotate3d'; 1806 } else if (attempt(styles.css_2d)) { 1807 prop = 'rotate'; 1808 } 1809 1810 localFeatures.rotate.prop = prop; 1811 1812 } 1813 1814 testDiv = null; 1815 1816 return localFeatures; 1817 1818 }()) 1819 1820 }; 1821 1822 // --- 1823 1824 // expose to global 1825 window.sm2BarPlayers = players; 1826 window.sm2BarPlayerOptions = playerOptions; 1827 window.SM2BarPlayer = Player; 1828//if (playervars.autoplay == 1){ 1829// 1830//} 1831}(window));
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.