source: gs3-extensions/audioDB/trunk/src/interface/script/page-player.js@ 26291

Last change on this file since 26291 was 26291, checked in by davidb, 12 years ago

files that need to added into the 'web/ext/XX' area to support the new interface elements

File size: 33.4 KB
Line 
1/*
2
3 SoundManager 2 Demo: "Page as playlist" UI
4 ----------------------------------------------
5 http://schillmania.com/projects/soundmanager2/
6
7 An example of a Muxtape.com-style UI, where an
8 unordered list of MP3 links becomes a playlist
9
10 Flash 9 "MovieStar" edition supports MPEG4
11 audio as well.
12
13 Requires SoundManager 2 Javascript API.
14
15*/
16
17/*jslint white: false, onevar: true, undef: true, nomen: false, eqeqeq: true, plusplus: false, bitwise: true, newcap: true, immed: true */
18/*global soundManager, window, document, navigator, setTimeout, attachEvent, Metadata, PP_CONFIG */
19
20var pagePlayer = null;
21
22function PagePlayer() {
23
24 var self = this,
25 pl = this,
26 sm = soundManager, // soundManager instance
27 _event,
28 vuDataCanvas = null,
29 controlTemplate = null,
30 _head = document.getElementsByTagName('head')[0],
31 spectrumContainer = null,
32 // sniffing for favicon stuff, IE workarounds and touchy-feely devices
33 ua = navigator.userAgent,
34 supportsFavicon = (ua.match(/(opera|firefox)/i)),
35 isTouchDevice = (ua.match(/ipad|ipod|iphone/i)),
36 cleanup;
37
38 // configuration options
39 // note that if Flash 9 is required, you must set soundManager.flashVersion = 9 in your script before this point.
40
41 this.config = {
42 usePeakData: false, // [Flash 9 only]: show peak data
43 useWaveformData: false, // [Flash 9 only]: enable sound spectrum (raw waveform data) - WARNING: CPU-INTENSIVE: may set CPUs on fire.
44 useEQData: false, // [Flash 9 only]: enable sound EQ (frequency spectrum data) - WARNING: Also CPU-intensive.
45 fillGraph: false, // [Flash 9 only]: draw full lines instead of only top (peak) spectrum points
46 useMovieStar: true, // [Flash 9 only]: Support for MPEG4 audio formats
47 allowRightClick: true, // let users right-click MP3 links ("save as...", etc.) or discourage (can't prevent.)
48 useThrottling: true, // try to rate-limit potentially-expensive calls (eg. dragging position around)
49 autoStart: false, // begin playing first sound when page loads
50 playNext: true, // stop after one sound, or play through list until end
51 updatePageTitle: true, // change the page title while playing sounds
52 emptyTime: '-:--', // null/undefined timer values (before data is available)
53 useFavIcon: false // try to show peakData in address bar (Firefox + Opera) - may be too CPU heavy
54 };
55
56 this.css = { // CSS class names appended to link during various states
57 sDefault: 'sm2_link', // default state
58 sLoading: 'sm2_loading',
59 sPlaying: 'sm2_playing',
60 sPaused: 'sm2_paused'
61 };
62
63 this.sounds = [];
64 this.soundsByObject = [];
65 this.lastSound = null;
66 this.soundCount = 0;
67 this.strings = [];
68 this.dragActive = false;
69 this.dragExec = new Date();
70 this.dragTimer = null;
71 this.pageTitle = document.title;
72 this.lastWPExec = new Date();
73 this.lastWLExec = new Date();
74 this.vuMeterData = [];
75 this.oControls = null;
76
77 this._mergeObjects = function(oMain,oAdd) {
78 // non-destructive merge
79 var o1 = {}, o2, i, o; // clone o1
80 for (i in oMain) {
81 if (oMain.hasOwnProperty(i)) {
82 o1[i] = oMain[i];
83 }
84 }
85 o2 = (typeof oAdd === 'undefined'?{}:oAdd);
86 for (o in o2) {
87 if (typeof o1[o] === 'undefined') {
88 o1[o] = o2[o];
89 }
90 }
91 return o1;
92 };
93
94 _event = (function() {
95
96 var old = (window.attachEvent && !window.addEventListener),
97 _slice = Array.prototype.slice,
98 evt = {
99 add: (old?'attachEvent':'addEventListener'),
100 remove: (old?'detachEvent':'removeEventListener')
101 };
102
103 function getArgs(oArgs) {
104 var args = _slice.call(oArgs), len = args.length;
105 if (old) {
106 args[1] = 'on' + args[1]; // prefix
107 if (len > 3) {
108 args.pop(); // no capture
109 }
110 } else if (len === 3) {
111 args.push(false);
112 }
113 return args;
114 }
115
116 function apply(args, sType) {
117 var element = args.shift(),
118 method = [evt[sType]];
119 if (old) {
120 element[method](args[0], args[1]);
121 } else {
122 element[method].apply(element, args);
123 }
124 }
125
126 function add() {
127 apply(getArgs(arguments), 'add');
128 }
129
130 function remove() {
131 apply(getArgs(arguments), 'remove');
132 }
133
134 return {
135 'add': add,
136 'remove': remove
137 };
138
139 }());
140
141 this.hasClass = function(o, cStr) {
142 return (typeof(o.className)!=='undefined'?new RegExp('(^|\\s)'+cStr+'(\\s|$)').test(o.className):false);
143 };
144
145 this.addClass = function(o, cStr) {
146 if (!o || !cStr || self.hasClass(o,cStr)) {
147 return false; // safety net
148 }
149 o.className = (o.className?o.className+' ':'')+cStr;
150 };
151
152 this.removeClass = function(o, cStr) {
153 if (!o || !cStr || !self.hasClass(o,cStr)) {
154 return false;
155 }
156 o.className = o.className.replace(new RegExp('( '+cStr+')|('+cStr+')','g'),'');
157 };
158
159 this.select = function(className, oParent) {
160 var result = self.getByClassName(className, 'div', oParent||null);
161 return (result ? result[0] : null);
162 };
163
164 this.getByClassName = (document.querySelectorAll ? function(className, tagNames, oParent) { // tagNames: string or ['div', 'p'] etc.
165
166 var pattern = ('.'+className), qs;
167 if (tagNames) {
168 tagNames = tagNames.split(' ');
169 }
170 qs = (tagNames.length > 1 ? tagNames.join(pattern+', ') : tagNames[0]+pattern);
171 return (oParent?oParent:document).querySelectorAll(qs);
172
173 } : function(className, tagNames, oParent) {
174
175 var node = (oParent?oParent:document), matches = [], i, j, nodes = [];
176 if (tagNames) {
177 tagNames = tagNames.split(' ');
178 }
179 if (tagNames instanceof Array) {
180 for (i=tagNames.length; i--;) {
181 if (!nodes || !nodes[tagNames[i]]) {
182 nodes[tagNames[i]] = node.getElementsByTagName(tagNames[i]);
183 }
184 }
185 for (i=tagNames.length; i--;) {
186 for (j=nodes[tagNames[i]].length; j--;) {
187 if (self.hasClass(nodes[tagNames[i]][j], className)) {
188 matches.push(nodes[tagNames[i]][j]);
189 }
190 }
191 }
192 } else {
193 nodes = node.all||node.getElementsByTagName('*');
194 for (i=0, j=nodes.length; i<j; i++) {
195 if (self.hasClass(nodes[i],className)) {
196 matches.push(nodes[i]);
197 }
198 }
199 }
200 return matches;
201
202 });
203
204 this.isChildOfClass = function(oChild, oClass) {
205 if (!oChild || !oClass) {
206 return false;
207 }
208 while (oChild.parentNode && !self.hasClass(oChild,oClass)) {
209 oChild = oChild.parentNode;
210 }
211 return (self.hasClass(oChild,oClass));
212 };
213
214 this.getParentByNodeName = function(oChild, sParentNodeName) {
215 if (!oChild || !sParentNodeName) {
216 return false;
217 }
218 sParentNodeName = sParentNodeName.toLowerCase();
219 while (oChild.parentNode && sParentNodeName !== oChild.parentNode.nodeName.toLowerCase()) {
220 oChild = oChild.parentNode;
221 }
222 return (oChild.parentNode && sParentNodeName === oChild.parentNode.nodeName.toLowerCase()?oChild.parentNode:null);
223 };
224
225 this.getOffX = function(o) {
226 // http://www.xs4all.nl/~ppk/js/findpos.html
227 var curleft = 0;
228 if (o.offsetParent) {
229 while (o.offsetParent) {
230 curleft += o.offsetLeft;
231 o = o.offsetParent;
232 }
233 }
234 else if (o.x) {
235 curleft += o.x;
236 }
237 return curleft;
238 };
239
240 this.getTime = function(nMSec, bAsString) {
241 // convert milliseconds to mm:ss, return as object literal or string
242 var nSec = Math.floor(nMSec/1000),
243 min = Math.floor(nSec/60),
244 sec = nSec-(min*60);
245 // if (min === 0 && sec === 0) return null; // return 0:00 as null
246 return (bAsString?(min+':'+(sec<10?'0'+sec:sec)):{'min':min,'sec':sec});
247 };
248
249 this.getSoundByObject = function(o) {
250 return (typeof self.soundsByObject[o.id] !== 'undefined'?self.soundsByObject[o.id]:null);
251 };
252
253 this.getPreviousItem = function(o) {
254 // given <li> playlist item, find previous <li> and then <a>
255 if (o.previousElementSibling) {
256 o = o.previousElementSibling;
257 } else {
258 o = o.previousSibling; // move from original node..
259 while (o && o.previousSibling && o.previousSibling.nodeType !== 1) {
260 o = o.previousSibling;
261 }
262 }
263 if (o.nodeName.toLowerCase() !== 'li') {
264 return null;
265 } else {
266 return o.getElementsByTagName('a')[0];
267 }
268 };
269
270 this.playPrevious = function(oSound) {
271 if (!oSound) {
272 oSound = self.lastSound;
273 }
274 if (!oSound) {
275 return false;
276 }
277 var previousItem = self.getPreviousItem(oSound._data.oLI);
278 if (previousItem) {
279 pl.handleClick({target:previousItem}); // fake a click event - aren't we sneaky. ;)
280 }
281 return previousItem;
282 };
283
284 this.getNextItem = function(o) {
285 // given <li> playlist item, find next <li> and then <a>
286 if (o.nextElementSibling) {
287 o = o.nextElementSibling;
288 } else {
289 o = o.nextSibling; // move from original node..
290 while (o && o.nextSibling && o.nextSibling.nodeType !== 1) {
291 o = o.nextSibling;
292 }
293 }
294 if (o.nodeName.toLowerCase() !== 'li') {
295 return null;
296 } else {
297 return o.getElementsByTagName('a')[0];
298 }
299 };
300
301 this.playNext = function(oSound) {
302 if (!oSound) {
303 oSound = self.lastSound;
304 }
305 if (!oSound) {
306 return false;
307 }
308 var nextItem = self.getNextItem(oSound._data.oLI);
309 if (nextItem) {
310 pl.handleClick({target:nextItem}); // fake a click event - aren't we sneaky. ;)
311 }
312 return nextItem;
313 };
314
315 this.setPageTitle = function(sTitle) {
316 if (!self.config.updatePageTitle) {
317 return false;
318 }
319 try {
320 document.title = (sTitle?sTitle+' - ':'')+self.pageTitle;
321 } catch(e) {
322 // oh well
323 self.setPageTitle = function() {
324 return false;
325 };
326 }
327 };
328
329 this.events = {
330
331 // handlers for sound events as they're started/stopped/played
332
333 play: function() {
334 pl.removeClass(this._data.oLI,this._data.className);
335 this._data.className = pl.css.sPlaying;
336 pl.addClass(this._data.oLI,this._data.className);
337 self.setPageTitle(this._data.originalTitle);
338 },
339
340 stop: function() {
341 pl.removeClass(this._data.oLI,this._data.className);
342 this._data.className = '';
343 this._data.oPosition.style.width = '0px';
344 self.setPageTitle();
345 self.resetPageIcon();
346 },
347
348 pause: function() {
349 if (pl.dragActive) {
350 return false;
351 }
352 pl.removeClass(this._data.oLI,this._data.className);
353 this._data.className = pl.css.sPaused;
354 pl.addClass(this._data.oLI,this._data.className);
355 self.setPageTitle();
356 self.resetPageIcon();
357 },
358
359 resume: function() {
360 if (pl.dragActive) {
361 return false;
362 }
363 pl.removeClass(this._data.oLI,this._data.className);
364 this._data.className = pl.css.sPlaying;
365 pl.addClass(this._data.oLI,this._data.className);
366 },
367
368 finish: function() {
369 pl.removeClass(this._data.oLI,this._data.className);
370 this._data.className = '';
371 this._data.oPosition.style.width = '0px';
372 // play next if applicable
373 if (self.config.playNext) {
374 pl.playNext(this);
375 } else {
376 self.setPageTitle();
377 self.resetPageIcon();
378
379 // Greenstone tweak // ****
380 var mainPlayButton = document.getElementById("mainPlayButton");
381 togglePlayVisual(mainPlayButton);
382 var currentPosElem = document.getElementById("mysongCurrentPos");
383 var offsetInMSecs = currentPosElem.setAttribute("offsetInMSecs",0);
384 }
385 },
386
387 whileloading: function() {
388 function doWork() {
389 this._data.oLoading.style.width = (((this.bytesLoaded/this.bytesTotal)*100)+'%'); // theoretically, this should work.
390 if (!this._data.didRefresh && this._data.metadata) {
391 this._data.didRefresh = true;
392 this._data.metadata.refresh();
393 }
394 }
395 if (!pl.config.useThrottling) {
396 doWork.apply(this);
397 } else {
398 var d = new Date();
399 if (d && d-self.lastWLExec>30 || this.bytesLoaded === this.bytesTotal) {
400 doWork.apply(this);
401 self.lastWLExec = d;
402 }
403 }
404
405 },
406
407 onload: function() {
408 if (!this.loaded) {
409 var oTemp = this._data.oLI.getElementsByTagName('a')[0],
410 oString = oTemp.innerHTML,
411 oThis = this;
412 oTemp.innerHTML = oString+' <span style="font-size:0.5em"> | Load failed, d\'oh! '+(sm.sandbox.noRemote?' Possible cause: Flash sandbox is denying remote URL access.':(sm.sandbox.noLocal?'Flash denying local filesystem access':'404?'))+'</span>';
413 setTimeout(function(){
414 oTemp.innerHTML = oString;
415 // pl.events.finish.apply(oThis); // load next
416 },5000);
417 } else {
418 if (this._data.metadata) {
419 this._data.metadata.refresh();
420 }
421 }
422 },
423
424 whileplaying: function() {
425 var d = null;
426 if (pl.dragActive || !pl.config.useThrottling) {
427 self.updateTime.apply(this);
428 if (sm.flashVersion >= 9) {
429 if (pl.config.usePeakData && this.instanceOptions.usePeakData) {
430 self.updatePeaks.apply(this);
431 }
432 if (pl.config.useWaveformData && this.instanceOptions.useWaveformData || pl.config.useEQData && this.instanceOptions.useEQData) {
433 self.updateGraph.apply(this);
434 }
435 }
436 if (this._data.metadata) {
437 d = new Date();
438 if (d && d-self.lastWPExec>500) {
439 this._data.metadata.refreshMetadata(this);
440 self.lastWPExec = d;
441 }
442 }
443 this._data.oPosition.style.width = (((this.position/self.getDurationEstimate(this))*100)+'%');
444 } else {
445 d = new Date();
446 if (d-self.lastWPExec>30) {
447 self.updateTime.apply(this);
448 if (sm.flashVersion >= 9) {
449 if (pl.config.usePeakData && this.instanceOptions.usePeakData) {
450 self.updatePeaks.apply(this);
451 }
452 if (pl.config.useWaveformData && this.instanceOptions.useWaveformData || pl.config.useEQData && this.instanceOptions.useEQData) {
453 self.updateGraph.apply(this);
454 }
455 }
456 if (this._data.metadata) {
457 this._data.metadata.refreshMetadata(this);
458 }
459 this._data.oPosition.style.width = (((this.position/self.getDurationEstimate(this))*100)+'%');
460 self.lastWPExec = d;
461 }
462 }
463 }
464
465 }; // events{}
466
467 this.setPageIcon = function(sDataURL) {
468 if (!self.config.useFavIcon || !self.config.usePeakData || !sDataURL) {
469 return false;
470 }
471 var link = document.getElementById('sm2-favicon');
472 if (link) {
473 _head.removeChild(link);
474 link = null;
475 }
476 if (!link) {
477 link = document.createElement('link');
478 link.id = 'sm2-favicon';
479 link.rel = 'shortcut icon';
480 link.type = 'image/png';
481 link.href = sDataURL;
482 document.getElementsByTagName('head')[0].appendChild(link);
483 }
484 };
485
486 this.resetPageIcon = function() {
487 if (!self.config.useFavIcon) {
488 return false;
489 }
490 var link = document.getElementById('favicon');
491 if (link) {
492 link.href = '/favicon.ico';
493 }
494 };
495
496 this.updatePeaks = function() {
497 var o = this._data.oPeak,
498 oSpan = o.getElementsByTagName('span');
499 oSpan[0].style.marginTop = (13-(Math.floor(15*this.peakData.left))+'px');
500 oSpan[1].style.marginTop = (13-(Math.floor(15*this.peakData.right))+'px');
501 if (sm.flashVersion > 8 && self.config.useFavIcon && self.config.usePeakData) {
502 self.setPageIcon(self.vuMeterData[parseInt(16*this.peakData.left,10)][parseInt(16*this.peakData.right,10)]);
503 }
504 };
505
506 this.updateGraph = function() {
507 if (pl.config.flashVersion < 9 || (!pl.config.useWaveformData && !pl.config.useEQData)) {
508 return false;
509 }
510 var sbC = this._data.oGraph.getElementsByTagName('div'),
511 scale, i, offset;
512 if (pl.config.useWaveformData) {
513 // raw waveform
514 scale = 8; // Y axis (+/- this distance from 0)
515 for (i=255; i--;) {
516 sbC[255-i].style.marginTop = (1+scale+Math.ceil(this.waveformData.left[i]*-scale))+'px';
517 }
518 } else {
519 // eq spectrum
520 offset = 9;
521 for (i=255; i--;) {
522 sbC[255-i].style.marginTop = ((offset*2)-1+Math.ceil(this.eqData[i]*-offset))+'px';
523 }
524 }
525 };
526
527 this.resetGraph = function() {
528 if (!pl.config.useEQData || pl.config.flashVersion<9) {
529 return false;
530 }
531 var sbC = this._data.oGraph.getElementsByTagName('div'),
532 scale = (!pl.config.useEQData?'9px':'17px'),
533 nHeight = (!pl.config.fillGraph?'1px':'32px'),
534 i;
535 for (i=255; i--;) {
536 sbC[255-i].style.marginTop = scale; // EQ scale
537 sbC[255-i].style.height = nHeight;
538 }
539 };
540
541 this.updateTime = function() {
542 var str = self.strings.timing.replace('%s1',self.getTime(this.position,true));
543 str = str.replace('%s2',self.getTime(self.getDurationEstimate(this),true));
544 this._data.oTiming.innerHTML = str;
545
546 var startMatchOffset = document.getElementById("mysongCurrentPos");
547 startMatchOffset.innerHTML = self.getTime(this.position,true) + " secs";
548 startMatchOffset.setAttribute("offsetInMSecs",this.position);
549 };
550
551 this.getTheDamnTarget = function(e) {
552 return (e.target||(window.event?window.event.srcElement:null));
553 };
554
555 this.withinStatusBar = function(o) {
556 return (self.isChildOfClass(o,'controls'));
557 };
558
559 this.handleClick = function(e) {
560
561 // a sound (or something) was clicked - determine what and handle appropriately
562
563 if (e.button === 2) {
564 if (!pl.config.allowRightClick) {
565 pl.stopEvent(e);
566 }
567 return pl.config.allowRightClick; // ignore right-clicks
568 }
569 var o = self.getTheDamnTarget(e),
570 sURL, soundURL, thisSound, oControls, oLI, str;
571 if (!o) {
572 return true;
573 }
574 if (self.dragActive) {
575 self.stopDrag(); // to be safe
576 }
577 if (self.withinStatusBar(o)) {
578 // self.handleStatusClick(e);
579 return false;
580 }
581 if (o.nodeName.toLowerCase() !== 'a') {
582 o = self.getParentByNodeName(o,'a');
583 }
584 if (!o) {
585 // not a link
586 return true;
587 }
588
589 // OK, we're dealing with a link
590
591 sURL = o.getAttribute('href');
592
593 if (!o.href || (!sm.canPlayLink(o) && !self.hasClass(o,'playable')) || self.hasClass(o,'exclude')) {
594
595 // do nothing, don't return anything.
596 return true;
597
598 } else {
599
600 // we have something we're interested in.
601
602 // find and init parent UL, if need be
603 self.initUL(self.getParentByNodeName(o, 'ul'));
604
605 // and decorate the link too, if needed
606 self.initItem(o);
607
608 soundURL = o.href;
609 thisSound = self.getSoundByObject(o);
610
611 if (thisSound) {
612
613 // sound already exists
614 self.setPageTitle(thisSound._data.originalTitle);
615 if (thisSound === self.lastSound) {
616 // ..and was playing (or paused) and isn't in an error state
617 if (thisSound.readyState !== 2) {
618 if (thisSound.playState !== 1) {
619 // not yet playing
620 thisSound.play();
621 } else {
622 thisSound.togglePause();
623 }
624 } else {
625 sm._writeDebug('Warning: sound failed to load (security restrictions, 404 or bad format)',2);
626 }
627 } else {
628 // ..different sound
629 if (self.lastSound) {
630 self.stopSound(self.lastSound);
631 }
632 if (spectrumContainer) {
633 thisSound._data.oTimingBox.appendChild(spectrumContainer);
634 }
635 thisSound.togglePause(); // start playing current
636 }
637
638 } else {
639
640 // create sound
641 thisSound = sm.createSound({
642 id:o.id,
643 url:decodeURI(soundURL),
644 onplay:self.events.play,
645 onstop:self.events.stop,
646 onpause:self.events.pause,
647 onresume:self.events.resume,
648 onfinish:self.events.finish,
649 whileloading:self.events.whileloading,
650 whileplaying:self.events.whileplaying,
651 onmetadata:self.events.metadata,
652 onload:self.events.onload
653 });
654
655 // append control template
656 oControls = self.oControls.cloneNode(true);
657 oLI = o.parentNode;
658 oLI.appendChild(oControls);
659 if (spectrumContainer) {
660 oLI.appendChild(spectrumContainer);
661 }
662 self.soundsByObject[o.id] = thisSound;
663
664 // tack on some custom data
665 thisSound._data = {
666 oLink: o, // DOM reference within SM2 object event handlers
667 oLI: oLI,
668 oControls: self.select('controls',oLI),
669 oStatus: self.select('statusbar',oLI),
670 oLoading: self.select('loading',oLI),
671 oPosition: self.select('position',oLI),
672 oTimingBox: self.select('timing',oLI),
673 oTiming: self.select('timing',oLI).getElementsByTagName('div')[0],
674 oPeak: self.select('peak',oLI),
675 oGraph: self.select('spectrum-box',oLI),
676 className: self.css.sPlaying,
677 originalTitle: o.innerHTML,
678 metadata: null
679 };
680
681 if (spectrumContainer) {
682 thisSound._data.oTimingBox.appendChild(spectrumContainer);
683 }
684
685 // "Metadata"
686 if (thisSound._data.oLI.getElementsByTagName('ul').length) {
687 thisSound._data.metadata = new Metadata(thisSound);
688 }
689
690 // set initial timer stuff (before loading)
691 str = self.strings.timing.replace('%s1',self.config.emptyTime);
692 str = str.replace('%s2',self.config.emptyTime);
693 thisSound._data.oTiming.innerHTML = str;
694 self.sounds.push(thisSound);
695 if (self.lastSound) {
696 self.stopSound(self.lastSound);
697 }
698 self.resetGraph.apply(thisSound);
699 thisSound.play();
700
701 }
702
703 self.lastSound = thisSound; // reference for next call
704 return self.stopEvent(e);
705
706 }
707
708 };
709
710 this.handleMouseDown = function(e) {
711 // a sound link was clicked
712 if (isTouchDevice && e.touches) {
713 e = e.touches[0];
714 }
715 if (e.button === 2) {
716 if (!pl.config.allowRightClick) {
717 pl.stopEvent(e);
718 }
719 return pl.config.allowRightClick; // ignore right-clicks
720 }
721 var o = self.getTheDamnTarget(e);
722 if (!o) {
723 return true;
724 }
725 if (!self.withinStatusBar(o)) {
726 return true;
727 }
728 self.dragActive = true;
729 self.lastSound.pause();
730 self.setPosition(e);
731 if (!isTouchDevice) {
732 _event.add(document,'mousemove',self.handleMouseMove);
733 } else {
734 _event.add(document,'touchmove',self.handleMouseMove);
735 }
736 self.addClass(self.lastSound._data.oControls,'dragging');
737 return self.stopEvent(e);
738 };
739
740 this.handleMouseMove = function(e) {
741 if (isTouchDevice && e.touches) {
742 e = e.touches[0];
743 }
744 // set position accordingly
745 if (self.dragActive) {
746 if (self.config.useThrottling) {
747 // be nice to CPU/externalInterface
748 var d = new Date();
749 if (d-self.dragExec>20) {
750 self.setPosition(e);
751 } else {
752 window.clearTimeout(self.dragTimer);
753 self.dragTimer = window.setTimeout(function(){self.setPosition(e);},20);
754 }
755 self.dragExec = d;
756 } else {
757 // oh the hell with it
758 self.setPosition(e);
759 }
760 } else {
761 self.stopDrag();
762 }
763 e.stopPropagation = true;
764 return false;
765 };
766
767 this.stopDrag = function(e) {
768 if (self.dragActive) {
769 self.removeClass(self.lastSound._data.oControls,'dragging');
770 if (!isTouchDevice) {
771 _event.remove(document,'mousemove',self.handleMouseMove);
772 } else {
773 _event.remove(document,'touchmove',self.handleMouseMove);
774 }
775 if (!pl.hasClass(self.lastSound._data.oLI,self.css.sPaused)) {
776 self.lastSound.resume();
777 }
778 self.dragActive = false;
779 return self.stopEvent(e);
780 }
781 };
782
783 this.handleStatusClick = function(e) {
784 self.setPosition(e);
785 if (!pl.hasClass(self.lastSound._data.oLI,self.css.sPaused)) {
786 self.resume();
787 }
788 return self.stopEvent(e);
789 };
790
791 this.stopEvent = function(e) {
792 if (typeof e !== 'undefined') {
793 if (typeof e.preventDefault !== 'undefined') {
794 e.preventDefault();
795 } else {
796 e.stopPropagation = true;
797 e.returnValue = false;
798 }
799 }
800 return false;
801 };
802
803 this.setPosition = function(e) {
804 // called from slider control
805 var oThis = self.getTheDamnTarget(e),
806 x, oControl, oSound, nMsecOffset;
807 if (!oThis) {
808 return true;
809 }
810 oControl = oThis;
811 while (!self.hasClass(oControl,'controls') && oControl.parentNode) {
812 oControl = oControl.parentNode;
813 }
814 oSound = self.lastSound;
815 x = parseInt(e.clientX,10);
816 // play sound at this position
817 nMsecOffset = Math.floor((x-self.getOffX(oControl)-4)/(oControl.offsetWidth)*self.getDurationEstimate(oSound));
818 if (!isNaN(nMsecOffset)) {
819 nMsecOffset = Math.min(nMsecOffset,oSound.duration);
820 }
821 if (!isNaN(nMsecOffset)) {
822 oSound.setPosition(nMsecOffset);
823 }
824 };
825
826 this.stopSound = function(oSound) {
827 sm._writeDebug('stopping sound: '+oSound.sID);
828 sm.stop(oSound.sID);
829 if (!isTouchDevice) { // iOS 4.2+ security blocks onfinish() -> playNext() if we set a .src in-between(?)
830 sm.unload(oSound.sID);
831 }
832 };
833
834 this.getDurationEstimate = function(oSound) {
835 if (oSound.instanceOptions.isMovieStar) {
836 return (oSound.duration);
837 } else {
838 return (!oSound._data.metadata || !oSound._data.metadata.data.givenDuration ? (oSound.durationEstimate||0) : oSound._data.metadata.data.givenDuration);
839 }
840 };
841
842 this.createVUData = function() {
843
844 var i=0, j=0,
845 canvas = vuDataCanvas.getContext('2d'),
846 vuGrad = canvas.createLinearGradient(0, 16, 0, 0),
847 bgGrad, outline;
848
849 vuGrad.addColorStop(0,'rgb(0,192,0)');
850 vuGrad.addColorStop(0.30,'rgb(0,255,0)');
851 vuGrad.addColorStop(0.625,'rgb(255,255,0)');
852 vuGrad.addColorStop(0.85,'rgb(255,0,0)');
853 bgGrad = canvas.createLinearGradient(0, 16, 0, 0);
854 outline = 'rgba(0,0,0,0.2)';
855 bgGrad.addColorStop(0,outline);
856 bgGrad.addColorStop(1,'rgba(0,0,0,0.5)');
857 for (i=0; i<16; i++) {
858 self.vuMeterData[i] = [];
859 }
860 for (i=0; i<16; i++) {
861 for (j=0; j<16; j++) {
862 // reset/erase canvas
863 vuDataCanvas.setAttribute('width',16);
864 vuDataCanvas.setAttribute('height',16);
865 // draw new stuffs
866 canvas.fillStyle = bgGrad;
867 canvas.fillRect(0,0,7,15);
868 canvas.fillRect(8,0,7,15);
869 /*
870 // shadow
871 canvas.fillStyle = 'rgba(0,0,0,0.1)';
872 canvas.fillRect(1,15-i,7,17-(17-i));
873 canvas.fillRect(9,15-j,7,17-(17-j));
874 */
875 canvas.fillStyle = vuGrad;
876 canvas.fillRect(0,15-i,7,16-(16-i));
877 canvas.fillRect(8,15-j,7,16-(16-j));
878 // and now, clear out some bits.
879 canvas.clearRect(0,3,16,1);
880 canvas.clearRect(0,7,16,1);
881 canvas.clearRect(0,11,16,1);
882 self.vuMeterData[i][j] = vuDataCanvas.toDataURL('image/png');
883 // for debugging VU images
884 /*
885 var o = document.createElement('img');
886 o.style.marginRight = '5px';
887 o.src = self.vuMeterData[i][j];
888 document.documentElement.appendChild(o);
889 */
890 }
891 }
892
893 };
894
895 this.testCanvas = function() {
896 // canvas + toDataURL();
897 var c = document.createElement('canvas'),
898 ctx = null, ok;
899 if (!c || typeof c.getContext === 'undefined') {
900 return null;
901 }
902 ctx = c.getContext('2d');
903 if (!ctx || typeof c.toDataURL !== 'function') {
904 return null;
905 }
906 // just in case..
907 try {
908 ok = c.toDataURL('image/png');
909 } catch(e) {
910 // no canvas or no toDataURL()
911 return null;
912 }
913 // assume we're all good.
914 return c;
915 };
916
917 this.initItem = function(oNode) {
918 if (!oNode.id) {
919 oNode.id = 'pagePlayerMP3Sound'+(self.soundCount++);
920 }
921 self.addClass(oNode,self.css.sDefault); // add default CSS decoration
922 };
923
924 this.initUL = function(oULNode) {
925 // set up graph box stuffs
926 if (sm.flashVersion >= 9) {
927 self.addClass(oULNode,self.cssBase);
928 }
929 };
930
931 this.init = function(oConfig) {
932
933 if (oConfig) {
934 // allow overriding via arguments object
935 sm._writeDebug('pagePlayer.init(): Using custom configuration');
936 this.config = this._mergeObjects(oConfig,this.config);
937 } else {
938 sm._writeDebug('pagePlayer.init(): Using default configuration');
939 }
940
941 var i, spectrumBox, sbC, oF, oClone, oTiming;
942
943 // apply externally-defined override, if applicable
944 this.cssBase = []; // optional features added to ul.playlist
945
946 // apply some items to SM2
947 sm.useFlashBlock = true;
948
949 if (sm.flashVersion >= 9) {
950
951 sm.useMovieStar = this.config.useMovieStar; // enable playing FLV, MP4 etc.
952 sm.defaultOptions.usePeakData = this.config.usePeakData;
953 sm.defaultOptions.useWaveformData = this.config.useWaveformData;
954 sm.defaultOptions.useEQData = this.config.useEQData;
955
956 if (this.config.usePeakData) {
957 this.cssBase.push('use-peak');
958 }
959
960 if (this.config.useWaveformData || this.config.useEQData) {
961 this.cssBase.push('use-spectrum');
962 }
963
964 this.cssBase = this.cssBase.join(' ');
965
966 if (this.config.useFavIcon) {
967 vuDataCanvas = self.testCanvas();
968 if (vuDataCanvas && supportsFavicon) {
969 // these browsers support dynamically-updating the favicon
970 self.createVUData();
971 } else {
972 // browser doesn't support doing this
973 this.config.useFavIcon = false;
974 }
975 }
976
977 } else if (this.config.usePeakData || this.config.useWaveformData || this.config.useEQData) {
978
979 sm._writeDebug('Page player: Note: soundManager.flashVersion = 9 is required for peak/waveform/EQ features.');
980
981 }
982
983 controlTemplate = document.createElement('div');
984
985 controlTemplate.innerHTML = [
986
987 // control markup inserted dynamically after each page player link
988 // if you want to change the UI layout, this is the place to do it.
989
990 ' <div class="controls">',
991 ' <div class="statusbar">',
992 ' <div class="loading"></div>',
993 ' <div class="position"></div>',
994 ' </div>',
995 ' </div>',
996
997 ' <div class="timing">',
998 ' <div id="sm2_timing" class="timing-data">',
999 ' <span class="sm2_position">%s1</span> / <span class="sm2_total">%s2</span>',
1000 ' </div>',
1001 ' </div>',
1002
1003 ' <div class="peak">',
1004 ' <div class="peak-box"><span class="l"></span><span class="r"></span></div>',
1005 ' </div>',
1006
1007 ' <div class="spectrum-container">',
1008 ' <div class="spectrum-box">',
1009 ' <div class="spectrum"></div>',
1010 ' </div>',
1011 ' </div>'
1012
1013 ].join('\n');
1014
1015 if (sm.flashVersion >= 9) {
1016
1017 // create the spectrum box ish
1018 spectrumContainer = self.select('spectrum-container',controlTemplate);
1019
1020 // take out of template, too
1021 spectrumContainer = controlTemplate.removeChild(spectrumContainer);
1022
1023 spectrumBox = self.select('spectrum-box',spectrumContainer);
1024
1025 sbC = spectrumBox.getElementsByTagName('div')[0];
1026 oF = document.createDocumentFragment();
1027 oClone = null;
1028 for (i=256; i--;) {
1029 oClone = sbC.cloneNode(false);
1030 oClone.style.left = (i)+'px';
1031 oF.appendChild(oClone);
1032 }
1033 spectrumBox.removeChild(sbC);
1034 spectrumBox.appendChild(oF);
1035
1036 } else {
1037
1038 // flash 8-only, take out the spectrum container and peak elements
1039 controlTemplate.removeChild(self.select('spectrum-container',controlTemplate));
1040 controlTemplate.removeChild(self.select('peak',controlTemplate));
1041
1042 }
1043
1044 self.oControls = controlTemplate.cloneNode(true);
1045
1046 oTiming = self.select('timing-data',controlTemplate);
1047 self.strings.timing = oTiming.innerHTML;
1048 oTiming.innerHTML = '';
1049 oTiming.id = '';
1050
1051 function doEvents(action) { // action: add / remove
1052
1053 _event[action](document,'click',self.handleClick);
1054
1055 if (!isTouchDevice) {
1056 _event[action](document,'mousedown',self.handleMouseDown);
1057 _event[action](document,'mouseup',self.stopDrag);
1058 } else {
1059 _event[action](document,'touchstart',self.handleMouseDown);
1060 _event[action](document,'touchend',self.stopDrag);
1061 }
1062
1063 _event[action](window, 'unload', cleanup);
1064
1065 }
1066
1067 cleanup = function() {
1068 doEvents('remove');
1069 };
1070
1071 doEvents('add');
1072
1073 sm._writeDebug('pagePlayer.init(): Ready',1);
1074
1075 if (self.config.autoStart) {
1076 // grab the first ul.playlist link
1077 pl.handleClick({target:pl.getByClassName('playlist', 'ul')[0].getElementsByTagName('a')[0]});
1078 }
1079
1080 };
1081
1082}
1083
1084soundManager.useFlashBlock = true;
1085
1086soundManager.onready(function() {
1087 pagePlayer = new PagePlayer();
1088 pagePlayer.init(typeof PP_CONFIG !== 'undefined' ? PP_CONFIG : null);
1089});
Note: See TracBrowser for help on using the repository browser.