/** * * SoundManager 2 Demo: Play MP3 links "in-place" * ---------------------------------------------- * * http://schillmania.com/projects/soundmanager2/ * * A simple demo making MP3s playable "inline" * and easily styled/customizable via CSS. * * Requires SoundManager 2 Javascript API. * */ (function() { /* global soundManager, document, window, navigator, event */ 'use strict'; function InlinePlayer() { var self = this; var pl = this; var sm = soundManager; // soundManager instance var isIE = (navigator.userAgent.match(/msie/i)); this.playableClass = 'inline-playable'; // CSS class for forcing a link to be playable (eg. doesn't have .MP3 in it) this.excludeClass = 'inline-exclude'; // CSS class for ignoring MP3 links this.links = []; this.sounds = []; this.soundsByURL = []; this.indexByURL = []; this.lastSound = null; this.soundCount = 0; this.config = { playNext: false, // stop after one sound, or play through list until end autoPlay: false // start playing the first sound right away }; this.css = { // CSS class names appended to link during various states sDefault: 'sm2_link', // default state sLoading: 'sm2_loading', sPlaying: 'sm2_playing', sPaused: 'sm2_paused' }; this.addEventHandler = (typeof window.addEventListener !== 'undefined' ? function(o, evtName, evtHandler) { return o.addEventListener(evtName, evtHandler, false); } : function(o, evtName, evtHandler) { o.attachEvent('on' + evtName, evtHandler); }); this.removeEventHandler = (typeof window.removeEventListener !== 'undefined' ? function(o, evtName, evtHandler) { return o.removeEventListener(evtName, evtHandler, false); } : function(o, evtName, evtHandler) { return o.detachEvent('on' + evtName, evtHandler); }); this.classContains = function(o, cStr) { return (typeof o.className !== 'undefined' ? o.className.match(new RegExp('(\\s|^)' + cStr + '(\\s|$)')) : false); }; this.addClass = function(o, cStr) { if (!o || !cStr || self.classContains(o, cStr)) return; o.className = (o.className ? o.className + ' ' : '') + cStr; }; this.removeClass = function(o, cStr) { if (!o || !cStr || !self.classContains(o, cStr)) return; o.className = o.className.replace(new RegExp('( ' + cStr + ')|(' + cStr + ')', 'g'), ''); }; this.getSoundByURL = function(sURL) { return (typeof self.soundsByURL[sURL] !== 'undefined' ? self.soundsByURL[sURL] : null); }; this.isChildOfNode = function(o, sNodeName) { if (!o || !o.parentNode) { return false; } sNodeName = sNodeName.toLowerCase(); do { o = o.parentNode; } while (o && o.parentNode && o.nodeName.toLowerCase() !== sNodeName); return (o.nodeName.toLowerCase() === sNodeName ? o : null); }; this.events = { // handlers for sound events as they're started/stopped/played play: function() { pl.removeClass(this._data.oLink, this._data.className); this._data.className = pl.css.sPlaying; pl.addClass(this._data.oLink, this._data.className); }, stop: function() { pl.removeClass(this._data.oLink, this._data.className); this._data.className = ''; }, pause: function() { pl.removeClass(this._data.oLink, this._data.className); this._data.className = pl.css.sPaused; pl.addClass(this._data.oLink, this._data.className); }, resume: function() { pl.removeClass(this._data.oLink, this._data.className); this._data.className = pl.css.sPlaying; pl.addClass(this._data.oLink, this._data.className); }, finish: function() { pl.removeClass(this._data.oLink, this._data.className); this._data.className = ''; if (pl.config.playNext) { var nextLink = (pl.indexByURL[this._data.oLink.href] + 1); if (nextLink < pl.links.length) { pl.handleClick({ target: pl.links[nextLink] }); } } } }; this.stopEvent = function(e) { if (typeof e !== 'undefined' && typeof e.preventDefault !== 'undefined') { e.preventDefault(); } else if (typeof event !== 'undefined' && typeof event.returnValue !== 'undefined') { event.returnValue = false; } return false; }; this.getTheDamnLink = (isIE) ? function(e) { // I really didn't want to have to do this. return (e && e.target ? e.target : window.event.srcElement); } : function(e) { return e.target; }; this.handleClick = function(e) { // a sound link was clicked if (typeof e.button !== 'undefined' && e.button > 1) { // ignore right-click return true; } var o = self.getTheDamnLink(e); if (o.nodeName.toLowerCase() !== 'a') { o = self.isChildOfNode(o, 'a'); if (!o) return true; } if (!o.href || (!sm.canPlayLink(o) && !self.classContains(o, self.playableClass)) || self.classContains(o, self.excludeClass)) { return true; // pass-thru for non-MP3/non-links } var soundURL = (o.href); var thisSound = self.getSoundByURL(soundURL); if (thisSound) { // already exists if (thisSound === self.lastSound) { // and was playing (or paused) thisSound.togglePause(); } else { // different sound sm._writeDebug('sound different than last sound: ' + self.lastSound.id); if (self.lastSound) { self.stopSound(self.lastSound); } thisSound.togglePause(); // start playing current } } else { // stop last sound if (self.lastSound) { self.stopSound(self.lastSound); } // create sound thisSound = sm.createSound({ id: 'inlineMP3Sound' + (self.soundCount++), url: soundURL, onplay: self.events.play, onstop: self.events.stop, onpause: self.events.pause, onresume: self.events.resume, onfinish: self.events.finish, type: (o.type || null) }); // tack on some custom data thisSound._data = { oLink: o, // DOM node for reference within SM2 object event handlers className: self.css.sPlaying }; self.soundsByURL[soundURL] = thisSound; self.sounds.push(thisSound); thisSound.play(); } self.lastSound = thisSound; // reference for next call if (typeof e !== 'undefined' && typeof e.preventDefault !== 'undefined') { e.preventDefault(); } else { event.returnValue = false; } return false; }; this.stopSound = function(oSound) { soundManager.stop(oSound.id); soundManager.unload(oSound.id); }; this.init = function() { sm._writeDebug('inlinePlayer.init()'); var oLinks = document.getElementsByTagName('a'); // grab all links, look for .mp3 var foundItems = 0; for (var i = 0, j = oLinks.length; i < j; i++) { if ((sm.canPlayLink(oLinks[i]) || self.classContains(oLinks[i], self.playableClass)) && !self.classContains(oLinks[i], self.excludeClass)) { self.addClass(oLinks[i], self.css.sDefault); // add default CSS decoration self.links[foundItems] = (oLinks[i]); self.indexByURL[oLinks[i].href] = foundItems; // hack for indexing foundItems++; } } if (foundItems > 0) { self.addEventHandler(document, 'click', self.handleClick); if (self.config.autoPlay) { self.handleClick({ target: self.links[0], preventDefault: function() {} }); } } sm._writeDebug('inlinePlayer.init(): Found ' + foundItems + ' relevant items.'); }; this.init(); } var inlinePlayer = null; // in the event any external script references this... window.inlinePlayer = inlinePlayer; soundManager.setup({ // disable or enable debug output debugMode: true, // use HTML5 audio for MP3/MP4, if available preferFlash: false, useFlashBlock: true, // path to directory containing SM2 SWF url: '../../swf/', // optional: enable MPEG-4/AAC support (requires flash 9) flashVersion: 9 }); soundManager.onready(function() { // soundManager.createSound() etc. may now be called inlinePlayer = new InlinePlayer(); }); }());