/** * APlayer constructor function * * @param {Object} option - See README * @constructor */ function APlayer(option) { // handle options error if (!('music' in option && 'title' in option.music && 'author' in option.music && 'url' in option.music && 'pic' in option.music)) { throw 'APlayer Error: Music, music.title, music.author, music.url, music.pic are required in options'; } if (option.element === null) { throw 'APlayer Error: element option null'; } this.isMobile = navigator.userAgent.match(/(iPad)|(iPhone)|(iPod)|(android)|(webOS)/i); // compatibility: some mobile browsers don't suppose autoplay if (this.isMobile) { option.autoplay = false; } // default options var defaultOption = { element: document.getElementsByClassName('aplayer')[0], narrow: false, autoplay: false, showlrc: false }; for (var defaultKey in defaultOption) { if (defaultOption.hasOwnProperty(defaultKey) && !option.hasOwnProperty(defaultKey)) { option[defaultKey] = defaultOption[defaultKey]; } } this.option = option; } /** * AutoLink initialization function */ APlayer.prototype.init = function () { this.element = this.option.element; this.music = this.option.music; // parser lrc if (this.option.showlrc) { var lrcs = this.element.getElementsByClassName('aplayer-lrc-content')[0].innerHTML; this.lrc = this.parseLrc(lrcs); } // fill in HTML this.element.innerHTML = '' + '
' + this.lrc[i][1] + '
'; } this.lrcContents.innerHTML = lrcHTML; this.lrcIndex = 0; this.lrcContents.getElementsByTagName('p')[0].classList.add('aplayer-lrc-current'); } // switch to narrow style if (this.option.narrow) { this.element.classList.add('aplayer-narrow'); } // create audio element this.audio = document.createElement("audio"); this.audio.src = this.music.url; this.audio.loop = true; this.audio.preload = 'metadata'; // show audio time var _self = this; this.audio.addEventListener('durationchange', function() { if (_self.audio.duration !== 1) { // compatibility: Android browsers will output 1 at first _self.element.getElementsByClassName('aplayer-dtime')[0].innerHTML = _self.secondToTime(_self.audio.duration); } }); // can play, remove loading style, show loading progress bar // compatibility: different mobile browsers have different triggering time, use loadedmetadata event to take the place of canplay event this.audio.addEventListener('loadedmetadata', function () { _self.element.getElementsByClassName('aplayer-author')[0].innerHTML = ' - ' + _self.music.author; _self.loadedTime = setInterval(function () { var percentage = _self.audio.buffered.end(_self.audio.buffered.length - 1) / _self.audio.duration; _self.updateBar.call(_self, 'loaded', percentage, 'width'); if (percentage === 1) { clearInterval(_self.loadedTime); } }, 500); }); // audio download error this.audio.addEventListener('error', function () { _self.element.getElementsByClassName('aplayer-author')[0].innerHTML = ' - ' + '加载失败 ╥﹏╥'; }); // play and pause button this.playButton = this.element.getElementsByClassName('aplayer-play')[0]; this.pauseButton = this.element.getElementsByClassName('aplayer-pause')[0]; this.playButton.addEventListener('click', function () { _self.play.call(_self); }); this.pauseButton.addEventListener('click', function () { _self.pause.call(_self); }); // control play progress this.playedBar = this.element.getElementsByClassName('aplayer-played')[0]; this.loadedBar = this.element.getElementsByClassName('aplayer-loaded')[0]; this.thumb = this.element.getElementsByClassName('aplayer-thumb')[0]; this.bar = this.element.getElementsByClassName('aplayer-bar')[0]; var barWidth; this.bar.addEventListener('click', function (event) { var e = event || window.event; barWidth = _self.bar.clientWidth; var percentage = (e.clientX - getElementViewLeft(_self.bar)) / barWidth; _self.updateBar.call(_self, 'played', percentage, 'width'); _self.element.getElementsByClassName('aplayer-ptime')[0].innerHTML = _self.secondToTime(percentage * _self.audio.duration); _self.audio.currentTime = parseFloat(_self.playedBar.style.width) / 100 * _self.audio.duration; }); this.thumb.addEventListener('mousedown', function () { barWidth = _self.bar.clientWidth; clearInterval(_self.playedTime); document.addEventListener('mousemove', thumbMove); document.addEventListener('mouseup', thumbUp); }); function thumbMove (event) { var e = event || window.event; var percentage = (e.clientX - getElementViewLeft(_self.bar)) / barWidth; percentage = percentage > 0 ? percentage : 0; percentage = percentage < 1 ? percentage : 1; _self.updateBar.call(_self, 'played', percentage, 'width'); if (_self.option.showlrc) { _self.updateLrc.call(_self, parseFloat(_self.playedBar.style.width) / 100 * _self.audio.duration); } _self.element.getElementsByClassName('aplayer-ptime')[0].innerHTML = _self.secondToTime(percentage * _self.audio.duration); } function thumbUp () { document.removeEventListener('mouseup', thumbUp); document.removeEventListener('mousemove', thumbMove); _self.audio.currentTime = parseFloat(_self.playedBar.style.width) / 100 * _self.audio.duration; _self.play(); } // control volume this.audio.volume = 0.8; this.volumeBar = this.element.getElementsByClassName('aplayer-volume')[0]; var volumeBarWrap = this.element.getElementsByClassName('aplayer-volume-bar')[0]; var volumeicon = _self.element.getElementsByClassName('aplayer-time')[0].getElementsByTagName('i')[0]; var barHeight = 35; this.element.getElementsByClassName('aplayer-volume-bar-wrap')[0].addEventListener('click', function (event) { var e = event || window.event; var percentage = (barHeight - e.clientY + getElementViewTop(volumeBarWrap)) / barHeight; percentage = percentage > 0 ? percentage : 0; percentage = percentage < 1 ? percentage : 1; _self.updateBar.call(_self, 'volume', percentage, 'height'); _self.audio.volume = percentage; if (_self.audio.muted) { _self.audio.muted = false; } if (percentage === 1) { volumeicon.className = 'demo-icon aplayer-icon-volume-up'; } else { volumeicon.className = 'demo-icon aplayer-icon-volume-down'; } }); volumeicon.addEventListener('click', function () { if (_self.audio.muted) { _self.audio.muted = false; volumeicon.className = _self.audio.volume === 1 ? 'demo-icon aplayer-icon-volume-up' : 'demo-icon aplayer-icon-volume-down'; _self.updateBar.call(_self, 'volume', _self.audio.volume, 'height'); } else { _self.audio.muted = true; volumeicon.className = 'demo-icon aplayer-icon-volume-off'; _self.updateBar.call(_self, 'volume', 0, 'height'); } }); // get element's view position function getElementViewLeft (element) { var actualLeft = element.offsetLeft; var current = element.offsetParent; var elementScrollLeft; while (current !== null){ actualLeft += current.offsetLeft; current = current.offsetParent; } elementScrollLeft = document.body.scrollLeft + document.documentElement.scrollLeft; return actualLeft - elementScrollLeft; } function getElementViewTop (element) { var actualTop = element.offsetTop; var current = element.offsetParent; var elementScrollTop; while (current !== null){ actualTop += current. offsetTop; current = current.offsetParent; } elementScrollTop = document.body.scrollTop + document.documentElement.scrollTop; return actualTop - elementScrollTop; } // autoplay if (this.option.autoplay) { this.play(); } }; /** * Play music */ APlayer.prototype.play = function () { this.playButton.classList.add('aplayer-hide'); this.pauseButton.classList.remove('aplayer-hide'); this.audio.play(); var _self = this; this.playedTime = setInterval(function () { _self.updateBar.call(_self, 'played', _self.audio.currentTime / _self.audio.duration, 'width'); if (_self.option.showlrc) { _self.updateLrc.call(_self); } _self.element.getElementsByClassName('aplayer-ptime')[0].innerHTML = _self.secondToTime(_self.audio.currentTime); }, 100); }; /** * Pause music */ APlayer.prototype.pause = function () { this.pauseButton.classList.add('aplayer-hide'); this.playButton.classList.remove('aplayer-hide'); this.audio.pause(); clearInterval(this.playedTime); }; /** * Update progress bar, including loading progress bar and play progress bar * * @param {String} type - Point out which bar it is, should be played loaded or volume * @param {Number} percentage * @param {String} direction - Point out the direction of this bar, Should be height or width */ APlayer.prototype.updateBar = function (type, percentage, direction) { percentage = percentage > 0 ? percentage : 0; percentage = percentage < 1 ? percentage : 1; this[type + 'Bar'].style[direction] = percentage * 100 + '%'; }; /** * Update lrc * * @param {Number} currentTime */ APlayer.prototype.updateLrc = function (currentTime) { if (!currentTime) { currentTime = this.audio.currentTime; } if (currentTime < this.lrc[this.lrcIndex][0] || (!this.lrc[this.lrcIndex + 1] || currentTime >= this.lrc[this.lrcIndex + 1][0])) { for (var i = 0; i < this.lrc.length; i++) { if (currentTime >= this.lrc[i][0] && (!this.lrc[i + 1] || currentTime < this.lrc[i + 1][0])) { this.lrcIndex = i; this.lrcContents.style.transform = 'translateY(' + -this.lrcIndex * 20 + 'px)'; this.lrcContents.getElementsByClassName('aplayer-lrc-current')[0].classList.remove('aplayer-lrc-current'); this.lrcContents.getElementsByTagName('p')[i].classList.add('aplayer-lrc-current'); } } } }; /** * Parse second to 00:00 format * * @param {Number} second * @return {String} 00:00 format */ APlayer.prototype.secondToTime = function (second) { var add0 = function (num) { return num < 10 ? '0' + num : '' + num; }; var min = parseInt(second / 60); var sec = parseInt(second - min * 60); return add0(min) + ':' + add0(sec); }; /** * Parse lrc, suppose multiple time tag * * @param {String} text - Format: [mm:ss.xx][mm:ss.xx]lyric * @return {Array} [[time, text], [time, text], [time, text], ...] */ APlayer.prototype.parseLrc = function (text) { var lyric = text.split('\n'); var lrc = []; var lyricLen = lyric.length; for (var i = 0; i < lyricLen; i++) { // match lrc time var lrcTimes = lyric[i].match(/\[(\d{2}):(\d{2})\.(\d{2})]/g); // match lrc text var lrcText = lyric[i].replace(/\[(\d{2}):(\d{2})\.(\d{2})]/g, '').replace(/^\s+|\s+$/g, ''); if (lrcTimes != null) { // handle multiple time tag var timeLen = lrcTimes.length; for (var j = 0; j < timeLen; j++) { var oneTime = /\[(\d{2}):(\d{2})\.(\d{2})]/.exec(lrcTimes[j]); var lrcTime = (oneTime[1]) * 60 + parseInt(oneTime[2]) + parseInt(oneTime[3]) / 100; lrc.push([lrcTime, lrcText]); } } } // sort by time lrc.sort(function (a, b) { return a[0] - b[0]; }); return lrc; };