APlayer/src/APlayer.js

890 lines
40 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

console.log("\n %c APlayer 1.6.1 %c http://aplayer.js.org \n\n","color: #fadfa3; background: #030307; padding:5px 0;","background: #fadfa3; padding:5px 0;");
require('./APlayer.scss');
let instances = [];
class APlayer {
/**
* APlayer constructor function
*
* @param {Object} option - See README
* @constructor
*/
constructor(option) {
const svg = {
'play': ['0 0 16 31', 'M15.552 15.168q0.448 0.32 0.448 0.832 0 0.448-0.448 0.768l-13.696 8.512q-0.768 0.512-1.312 0.192t-0.544-1.28v-16.448q0-0.96 0.544-1.28t1.312 0.192z'],
'pause': ['0 0 17 32', 'M14.080 4.8q2.88 0 2.88 2.048v18.24q0 2.112-2.88 2.112t-2.88-2.112v-18.24q0-2.048 2.88-2.048zM2.88 4.8q2.88 0 2.88 2.048v18.24q0 2.112-2.88 2.112t-2.88-2.112v-18.24q0-2.048 2.88-2.048z'],
'volume-up': ['0 0 28 32', 'M13.728 6.272v19.456q0 0.448-0.352 0.8t-0.8 0.32-0.8-0.32l-5.952-5.952h-4.672q-0.48 0-0.8-0.352t-0.352-0.8v-6.848q0-0.48 0.352-0.8t0.8-0.352h4.672l5.952-5.952q0.32-0.32 0.8-0.32t0.8 0.32 0.352 0.8zM20.576 16q0 1.344-0.768 2.528t-2.016 1.664q-0.16 0.096-0.448 0.096-0.448 0-0.8-0.32t-0.32-0.832q0-0.384 0.192-0.64t0.544-0.448 0.608-0.384 0.512-0.64 0.192-1.024-0.192-1.024-0.512-0.64-0.608-0.384-0.544-0.448-0.192-0.64q0-0.48 0.32-0.832t0.8-0.32q0.288 0 0.448 0.096 1.248 0.48 2.016 1.664t0.768 2.528zM25.152 16q0 2.72-1.536 5.056t-4 3.36q-0.256 0.096-0.448 0.096-0.48 0-0.832-0.352t-0.32-0.8q0-0.704 0.672-1.056 1.024-0.512 1.376-0.8 1.312-0.96 2.048-2.4t0.736-3.104-0.736-3.104-2.048-2.4q-0.352-0.288-1.376-0.8-0.672-0.352-0.672-1.056 0-0.448 0.32-0.8t0.8-0.352q0.224 0 0.48 0.096 2.496 1.056 4 3.36t1.536 5.056zM29.728 16q0 4.096-2.272 7.552t-6.048 5.056q-0.224 0.096-0.448 0.096-0.48 0-0.832-0.352t-0.32-0.8q0-0.64 0.704-1.056 0.128-0.064 0.384-0.192t0.416-0.192q0.8-0.448 1.44-0.896 2.208-1.632 3.456-4.064t1.216-5.152-1.216-5.152-3.456-4.064q-0.64-0.448-1.44-0.896-0.128-0.096-0.416-0.192t-0.384-0.192q-0.704-0.416-0.704-1.056 0-0.448 0.32-0.8t0.832-0.352q0.224 0 0.448 0.096 3.776 1.632 6.048 5.056t2.272 7.552z'],
'volume-down': ['0 0 28 32', 'M13.728 6.272v19.456q0 0.448-0.352 0.8t-0.8 0.32-0.8-0.32l-5.952-5.952h-4.672q-0.48 0-0.8-0.352t-0.352-0.8v-6.848q0-0.48 0.352-0.8t0.8-0.352h4.672l5.952-5.952q0.32-0.32 0.8-0.32t0.8 0.32 0.352 0.8zM20.576 16q0 1.344-0.768 2.528t-2.016 1.664q-0.16 0.096-0.448 0.096-0.448 0-0.8-0.32t-0.32-0.832q0-0.384 0.192-0.64t0.544-0.448 0.608-0.384 0.512-0.64 0.192-1.024-0.192-1.024-0.512-0.64-0.608-0.384-0.544-0.448-0.192-0.64q0-0.48 0.32-0.832t0.8-0.32q0.288 0 0.448 0.096 1.248 0.48 2.016 1.664t0.768 2.528z'],
'volume-off': ['0 0 28 32', 'M13.728 6.272v19.456q0 0.448-0.352 0.8t-0.8 0.32-0.8-0.32l-5.952-5.952h-4.672q-0.48 0-0.8-0.352t-0.352-0.8v-6.848q0-0.48 0.352-0.8t0.8-0.352h4.672l5.952-5.952q0.32-0.32 0.8-0.32t0.8 0.32 0.352 0.8z'],
'circulation': ['0 0 29 32', 'M25.6 9.92q1.344 0 2.272 0.928t0.928 2.272v9.28q0 1.28-0.928 2.24t-2.272 0.96h-22.4q-1.28 0-2.24-0.96t-0.96-2.24v-9.28q0-1.344 0.96-2.272t2.24-0.928h8v-3.52l6.4 5.76-6.4 5.76v-3.52h-6.72v6.72h19.84v-6.72h-4.8v-4.48h6.080z'],
'random': ['0 0 33 31', 'M29.867 9.356l-5.003 5.003c-0.094 0.094-0.235 0.141-0.36 0.141-0.266 0-0.5-0.219-0.5-0.5v-3.002h-4.002c-2.079 0-3.064 1.423-3.94 3.111-0.453 0.875-0.844 1.782-1.219 2.673-1.735 4.033-3.768 8.223-8.849 8.223h-3.502c-0.281 0-0.5-0.219-0.5-0.5v-3.002c0-0.281 0.219-0.5 0.5-0.5h3.502c2.079 0 3.064-1.423 3.94-3.111 0.453-0.875 0.844-1.782 1.219-2.673 1.735-4.033 3.768-8.223 8.849-8.223h4.002v-3.002c0-0.281 0.219-0.5 0.5-0.5 0.141 0 0.266 0.063 0.375 0.156l4.987 4.987c0.094 0.094 0.141 0.235 0.141 0.36s-0.047 0.266-0.141 0.36zM10.262 14.781c-0.907-1.892-1.907-3.783-4.268-3.783h-3.502c-0.281 0-0.5-0.219-0.5-0.5v-3.002c0-0.281 0.219-0.5 0.5-0.5h3.502c2.783 0 4.831 1.298 6.41 3.518-0.876 1.344-1.517 2.798-2.142 4.268zM29.867 23.363l-5.003 5.003c-0.094 0.094-0.235 0.141-0.36 0.141-0.266 0-0.5-0.235-0.5-0.5v-3.002c-4.643 0-7.504 0.547-10.396-3.518 0.86-1.344 1.501-2.798 2.126-4.268 0.907 1.892 1.907 3.783 4.268 3.783h4.002v-3.002c0-0.281 0.219-0.5 0.5-0.5 0.141 0 0.266 0.063 0.375 0.156l4.987 4.987c0.094 0.094 0.141 0.235 0.141 0.36s-0.047 0.266-0.141 0.36z'],
'order': ['0 0 32 32', 'M0.622 18.334h19.54v7.55l11.052-9.412-11.052-9.413v7.549h-19.54v3.725z'],
'single': ['0 0 38 32', 'M2.072 21.577c0.71-0.197 1.125-0.932 0.928-1.641-0.221-0.796-0.333-1.622-0.333-2.457 0-5.049 4.108-9.158 9.158-9.158h5.428c0.056-0.922 0.221-1.816 0.482-2.667h-5.911c-3.158 0-6.128 1.23-8.361 3.463s-3.463 5.203-3.463 8.361c0 1.076 0.145 2.143 0.431 3.171 0.164 0.59 0.7 0.976 1.284 0.976 0.117 0 0.238-0.016 0.357-0.049zM21.394 25.613h-12.409v-2.362c0-0.758-0.528-1.052-1.172-0.652l-5.685 3.522c-0.644 0.4-0.651 1.063-0.014 1.474l5.712 3.69c0.637 0.411 1.158 0.127 1.158-0.63v-2.374h12.409c3.158 0 6.128-1.23 8.361-3.463 1.424-1.424 2.44-3.148 2.99-5.029-0.985 0.368-2.033 0.606-3.125 0.691-1.492 3.038-4.619 5.135-8.226 5.135zM28.718 0c-4.985 0-9.026 4.041-9.026 9.026s4.041 9.026 9.026 9.026 9.026-4.041 9.026-9.026-4.041-9.026-9.026-9.026zM30.392 13.827h-1.728v-6.822c-0.635 0.576-1.433 1.004-2.407 1.285v-1.713c0.473-0.118 0.975-0.325 1.506-0.62 0.532-0.325 0.975-0.665 1.329-1.034h1.3v8.904z'],
'menu': ['0 0 22 32', 'M20.8 14.4q0.704 0 1.152 0.48t0.448 1.12-0.48 1.12-1.12 0.48h-19.2q-0.64 0-1.12-0.48t-0.48-1.12 0.448-1.12 1.152-0.48h19.2zM1.6 11.2q-0.64 0-1.12-0.48t-0.48-1.12 0.448-1.12 1.152-0.48h19.2q0.704 0 1.152 0.48t0.448 1.12-0.48 1.12-1.12 0.48h-19.2zM20.8 20.8q0.704 0 1.152 0.48t0.448 1.12-0.48 1.12-1.12 0.48h-19.2q-0.64 0-1.12-0.48t-0.48-1.12 0.448-1.12 1.152-0.48h19.2z']
};
this.getSVG = (type) => {
return `
<svg xmlns:xlink="http://www.w3.org/1999/xlink" height="100%" version="1.1" viewBox="${svg[type][0]}" width="100%">
<use xlink:href="#aplayer-${type}"></use>
<path class="aplayer-fill" d="${svg[type][1]}" id="aplayer-${type}"></path>
</svg>
`;
};
this.isMobile = /mobile/i.test(window.navigator.userAgent);
// compatibility: some mobile browsers don't suppose autoplay
if (this.isMobile) {
option.autoplay = false;
}
// default options
const defaultOption = {
element: document.getElementsByClassName('aplayer')[0],
narrow: false,
autoplay: false,
mutex: true,
showlrc: 0,
theme: '#b7daff',
mode: 'circulation'
};
for (let defaultKey in defaultOption) {
if (defaultOption.hasOwnProperty(defaultKey) && !option.hasOwnProperty(defaultKey)) {
option[defaultKey] = defaultOption[defaultKey];
}
}
this.option = option;
this.audios = [];
this.mode = option.mode;
/**
* Parse second to 00:00 format. 00:00:00 if audio is over an hour long.
*
* @param {Number} second
* @return {String} 00:00 format. 00:00:00 if over an hour long.
*/
this.secondToTime = (second) => {
if (isNaN(second)) {
return '00:00';
}
const add0 = (num) => {
return num < 10 ? '0' + num : '' + num;
};
const min = parseInt(second / 60);
const sec = parseInt(second - min * 60);
const hours = parseInt(min / 60);
const minAdjust = parseInt((second / 60) - (60 * parseInt((second / 60) / 60)));
return second >= 3600 ? add0(hours) + ':' + add0(minAdjust) + ':' + add0(sec) : add0(min) + ':' + add0(sec);
};
// save lrc
this.element = this.option.element;
if (this.option.showlrc === 2 || this.option.showlrc === true) {
this.savelrc = [];
for (let i = 0; i < this.element.getElementsByClassName('aplayer-lrc-content').length; i++) {
this.savelrc.push(this.element.getElementsByClassName('aplayer-lrc-content')[i].innerHTML);
}
}
this.lrcs = [];
/**
* 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
*/
this.updateBar = (type, percentage, direction) => {
percentage = percentage > 0 ? percentage : 0;
percentage = percentage < 1 ? percentage : 1;
bar[type + 'Bar'].style[direction] = percentage * 100 + '%';
};
/**
* Update lrc
*
* @param {Number} currentTime
*/
this.updateLrc = (currentTime = this.audio.currentTime) => {
if (this.lrcIndex > this.lrc.length - 1 || currentTime < this.lrc[this.lrcIndex][0] || (!this.lrc[this.lrcIndex + 1] || currentTime >= this.lrc[this.lrcIndex + 1][0])) {
for (let 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 * 16}px)`;
this.lrcContents.style.webkitTransform = `translateY(${-this.lrcIndex * 16}px)`;
this.lrcContents.getElementsByClassName('aplayer-lrc-current')[0].classList.remove('aplayer-lrc-current');
this.lrcContents.getElementsByTagName('p')[i].classList.add('aplayer-lrc-current');
}
}
}
};
// define APlayer events
const eventTypes = ['play', 'pause', 'canplay', 'playing', 'ended', 'error'];
this.event = {};
for (let i = 0; i < eventTypes.length; i++) {
this.event[eventTypes[i]] = [];
}
this.trigger = (type) => {
for (let i = 0; i < this.event[type].length; i++) {
this.event[type][i]();
}
};
// multiple music
this.playIndex = 0;
this.multiple = Object.prototype.toString.call(option.music) === '[object Array]';
if (!this.multiple) {
this.option.music = [this.option.music];
}
this.music = this.option.music[this.playIndex];
// add class aplayer-withlrc
if (this.option.showlrc) {
this.element.classList.add('aplayer-withlrc');
}
if (this.option.music.length > 1) {
this.element.classList.add('aplayer-withlist');
}
if (!this.multiple && this.mode !== 'circulation' && this.mode !== 'order') {
this.mode = 'circulation';
}
this.getRandomOrder();
// fill in HTML
let eleHTML = `
<div class="aplayer-pic" ${(this.music.pic ? (`style="background-image: url('${this.music.pic}');"`) : ``)}>
<div class="aplayer-button aplayer-play">
<button type="button" class="aplayer-icon aplayer-icon-play">
${this.getSVG('play')}
</button>
</div>
</div>
<div class="aplayer-info">
<div class="aplayer-music">
<span class="aplayer-title"></span>
<span class="aplayer-author"></span>
</div>
<div class="aplayer-lrc">
<div class="aplayer-lrc-contents" style="transform: translateY(0); -webkit-transform: translateY(0);"></div>
</div>
<div class="aplayer-controller">
<div class="aplayer-bar-wrap">
<div class="aplayer-bar">
<div class="aplayer-loaded" style="width: 0"></div>
<div class="aplayer-played" style="width: 0; background: ${this.option.theme};">
<span class="aplayer-thumb" style="border: 1px solid ${this.option.theme};"></span>
</div>
</div>
</div>
<div class="aplayer-time">
<span class="aplayer-time-inner">
- <span class="aplayer-ptime">00:00</span> / <span class="aplayer-dtime">00:00</span>
</span>
<div class="aplayer-volume-wrap">
<button type="button" class="aplayer-icon aplayer-icon-volume-down" ${this.isMobile ? 'style="display: none;"' : ''}>
${this.getSVG('volume-down')}
</button>
<div class="aplayer-volume-bar-wrap">
<div class="aplayer-volume-bar">
<div class="aplayer-volume" style="height: 80%; background: ${this.option.theme};"></div>
</div>
</div>
</div>
<button type="button" class="aplayer-icon aplayer-icon-mode">
${this.getSVG(this.mode)}
</button>
<button type="button" class="aplayer-icon aplayer-icon-menu">
${this.getSVG('menu')}
</button>
</div>
</div>
</div>
<div class="aplayer-list" ${this.option.listmaxheight ? `style="max-height: ${this.option.listmaxheight}"` : ``}>
<ol>`;
for (let i = 0; i < this.option.music.length; i++) {
eleHTML += `
<li>
<span class="aplayer-list-cur" style="background: ${this.option.theme};"></span>
<span class="aplayer-list-index">${(i + 1)}</span>
<span class="aplayer-list-title">${this.option.music[i].title}</span>
<span class="aplayer-list-author">${this.option.music[i].author}</span>
</li>`
}
eleHTML += `
</ol>
</div>`
this.element.innerHTML = eleHTML;
// hide mode button in arrow container
if (this.element.offsetWidth < 300) {
this.element.getElementsByClassName('aplayer-icon-mode')[0].style.display = 'none';
}
this.ptime = this.element.getElementsByClassName('aplayer-ptime')[0];
if (this.element.getElementsByClassName('aplayer-info')[0].offsetWidth < 200) {
this.element.getElementsByClassName('aplayer-time')[0].classList.add('aplayer-time-narrow');
}
// fix the width of aplayer bar
let bar = {};
bar.barWrap = this.element.getElementsByClassName('aplayer-bar-wrap')[0];
// switch to narrow style
if (this.option.narrow) {
this.element.classList.add('aplayer-narrow');
}
// play and pause button
this.button = this.element.getElementsByClassName('aplayer-button')[0];
this.button.addEventListener('click', (e) => {
this.toggle();
});
// click music list: change music
const list = this.element.getElementsByClassName('aplayer-list')[0];
list.addEventListener('click', (e) => {
let target;
if (e.target.tagName.toUpperCase() === 'LI') {
target = e.target;
}
else {
target = e.target.parentElement;
}
const musicIndex = parseInt(target.getElementsByClassName('aplayer-list-index')[0].innerHTML) - 1;
if (musicIndex !== this.playIndex) {
this.setMusic(musicIndex);
this.play();
}
else {
this.toggle();
}
});
// control play progress
bar.playedBar = this.element.getElementsByClassName('aplayer-played')[0];
bar.loadedBar = this.element.getElementsByClassName('aplayer-loaded')[0];
const thumb = this.element.getElementsByClassName('aplayer-thumb')[0];
let barWidth;
bar.barWrap.addEventListener('click', (event) => {
const e = event || window.event;
barWidth = bar.barWrap.clientWidth;
const percentage = (e.clientX - getElementViewLeft(bar.barWrap)) / barWidth;
if (isNaN(this.audio.duration)) {
this.updateBar('played', 0, 'width');
}
else {
this.updateBar('played', percentage, 'width');
this.element.getElementsByClassName('aplayer-ptime')[0].innerHTML = this.secondToTime(percentage * this.audio.duration);
this.audio.currentTime = parseFloat(bar.playedBar.style.width) / 100 * this.audio.duration;
}
});
thumb.addEventListener('mouseover', () => {
thumb.style.background = this.option.theme;
});
thumb.addEventListener('mouseout', () => {
thumb.style.background = '#fff';
});
const thumbMove = (event) => {
const e = event || window.event;
let percentage = (e.clientX - getElementViewLeft(bar.barWrap)) / barWidth;
percentage = percentage > 0 ? percentage : 0;
percentage = percentage < 1 ? percentage : 1;
this.updateBar('played', percentage, 'width');
if (this.option.showlrc) {
this.updateLrc(parseFloat(bar.playedBar.style.width) / 100 * this.audio.duration);
}
this.element.getElementsByClassName('aplayer-ptime')[0].innerHTML = this.secondToTime(percentage * this.audio.duration);
};
const thumbUp = () => {
document.removeEventListener('mouseup', thumbUp);
document.removeEventListener('mousemove', thumbMove);
if (isNaN(this.audio.duration)) {
this.updateBar('played', 0, 'width');
}
else {
this.audio.currentTime = parseFloat(bar.playedBar.style.width) / 100 * this.audio.duration;
this.playedTime = setInterval(() => {
this.updateBar('played', this.audio.currentTime / this.audio.duration, 'width');
if (this.option.showlrc) {
this.updateLrc();
}
this.element.getElementsByClassName('aplayer-ptime')[0].innerHTML = this.secondToTime(this.audio.currentTime);
this.trigger('playing');
}, 100);
}
};
thumb.addEventListener('mousedown', () => {
barWidth = bar.barWrap.clientWidth;
clearInterval(this.playedTime);
document.addEventListener('mousemove', thumbMove);
document.addEventListener('mouseup', thumbUp);
});
// control volume
bar.volumeBar = this.element.getElementsByClassName('aplayer-volume')[0];
const volumeBarWrap = this.element.getElementsByClassName('aplayer-volume-bar')[0];
this.volumeicon = this.element.getElementsByClassName('aplayer-time')[0].getElementsByTagName('button')[0];
const barHeight = 35;
this.element.getElementsByClassName('aplayer-volume-bar-wrap')[0].addEventListener('click', (event) => {
const e = event || window.event;
let percentage = (barHeight - e.clientY + getElementViewTop(volumeBarWrap)) / barHeight;
percentage = percentage > 0 ? percentage : 0;
percentage = percentage < 1 ? percentage : 1;
this.volume(percentage);
});
this.volumeicon.addEventListener('click', () => {
if (this.audio.muted) {
this.audio.muted = false;
this.volumeicon.className = this.audio.volume === 1 ? 'aplayer-icon aplayer-icon-volume-up' : 'aplayer-icon aplayer-icon-volume-down';
if (this.audio.volume === 1) {
this.volumeicon.className = 'aplayer-icon aplayer-icon-volume-up';
this.volumeicon.innerHTML = this.getSVG('volume-up');
}
else {
this.volumeicon.className = 'aplayer-icon aplayer-icon-volume-down';
this.volumeicon.innerHTML = this.getSVG('volume-down');
}
this.updateBar('volume', this.audio.volume, 'height');
}
else {
this.audio.muted = true;
this.volumeicon.className = 'aplayer-icon aplayer-icon-volume-off';
this.volumeicon.innerHTML = this.getSVG('volume-off');
this.updateBar('volume', 0, 'height');
}
});
// get element's view position
function getElementViewLeft(element) {
let actualLeft = element.offsetLeft;
let current = element.offsetParent;
let elementScrollLeft;
while (current !== null) {
actualLeft += current.offsetLeft;
current = current.offsetParent;
}
elementScrollLeft = document.body.scrollLeft + document.documentElement.scrollLeft;
return actualLeft - elementScrollLeft;
}
function getElementViewTop(element) {
let actualTop = element.offsetTop;
let current = element.offsetParent;
let elementScrollTop;
while (current !== null) {
actualTop += current.offsetTop;
current = current.offsetParent;
}
elementScrollTop = document.body.scrollTop + document.documentElement.scrollTop;
return actualTop - elementScrollTop;
}
// mode control
const modeEle = this.element.getElementsByClassName('aplayer-icon-mode')[0];
modeEle.addEventListener('click', () => {
if (this.multiple) {
if (this.mode === 'random') {
this.mode = 'single';
}
else if (this.mode === 'single') {
this.mode = 'order';
}
else if (this.mode === 'order') {
this.mode = 'circulation';
}
else if (this.mode === 'circulation') {
this.mode = 'random';
}
}
else {
if (this.mode === 'circulation') {
this.mode = 'order';
}
else {
this.mode = 'circulation';
}
}
modeEle.innerHTML = this.getSVG(this.mode);
this.audio.loop = !(this.multiple || this.mode === 'order');
});
// toggle menu control
list.style.height = list.offsetHeight + 'px';
this.element.getElementsByClassName('aplayer-icon-menu')[0].addEventListener('click', () => {
if (!list.classList.contains('aplayer-list-hide')) {
list.classList.add('aplayer-list-hide');
}
else {
list.classList.remove('aplayer-list-hide');
}
});
if (this.mode === 'random') {
this.setMusic(this.randomOrder[0]);
}
else {
this.setMusic(0);
}
instances.push(this);
}
/**
* Set music
*/
setMusic(index) {
// get this.music
if (typeof(index) !== 'undefined') {
this.playIndex = index;
}
const indexMusic = this.playIndex;
this.music = this.option.music[indexMusic];
// set html
if (this.music.pic) {
this.element.getElementsByClassName('aplayer-pic')[0].style.backgroundImage = `url('${this.music.pic}')`;
}
else {
this.element.getElementsByClassName('aplayer-pic')[0].style.backgroundImage = '';
}
this.element.getElementsByClassName('aplayer-title')[0].innerHTML = this.music.title;
this.element.getElementsByClassName('aplayer-author')[0].innerHTML = this.music.author ? ' - ' + this.music.author : '';
if (this.element.getElementsByClassName('aplayer-list-light')[0]) {
this.element.getElementsByClassName('aplayer-list-light')[0].classList.remove('aplayer-list-light');
}
this.element.getElementsByClassName('aplayer-list')[0].getElementsByTagName('li')[indexMusic].classList.add('aplayer-list-light');
// set the previous audio object
if (!this.isMobile && this.audio) {
this.pause();
this.audio.currentTime = 0;
}
this.element.getElementsByClassName('aplayer-list')[0].scrollTop = indexMusic * 33;
// get this audio object
if (this.isMobile && this.audio) {
this.audio.src = this.music.url;
this.play();
}
else if (!this.isMobile && this.audios[indexMusic]) {
this.audio = this.audios[indexMusic];
this.audio.volume = parseInt(this.element.getElementsByClassName('aplayer-volume')[0].style.height) / 100;
this.audio.currentTime = 0;
}
else {
this.audio = document.createElement("audio");
this.audio.src = this.music.url;
this.audio.preload = this.option.preload ? this.option.preload : 'auto';
this.audio.addEventListener('play', () => {
if (this.button.classList.contains('aplayer-play')) {
this.button.classList.remove('aplayer-play');
this.button.classList.add('aplayer-pause');
this.button.innerHTML = '';
setTimeout(() => {
this.button.innerHTML = `
<button type="button" class="aplayer-icon aplayer-icon-pause">`
+ this.getSVG('pause')
+ ` </button>`;
}, 100);
// pause other players (Thanks @Aprikyblue)
if (this.option.mutex) {
for (let i = 0; i < instances.length; i++) {
if (this != instances[i]) {
instances[i].pause();
}
}
}
if (this.playedTime) {
clearInterval(this.playedTime);
}
this.playedTime = setInterval(() => {
this.updateBar('played', this.audio.currentTime / this.audio.duration, 'width');
if (this.option.showlrc) {
this.updateLrc();
}
this.ptime.innerHTML = this.secondToTime(this.audio.currentTime);
this.trigger('playing');
}, 100);
this.trigger('play');
}
});
this.audio.addEventListener('pause', () => {
if (this.button && (this.button.classList.contains('aplayer-pause') || this.ended)) {
this.ended = false;
this.button.classList.remove('aplayer-pause');
this.button.classList.add('aplayer-play');
this.button.innerHTML = '';
setTimeout(() => {
this.button.innerHTML = `
<button type="button" class="aplayer-icon aplayer-icon-play">`
+ this.getSVG('play')
+ ` </button>`;
}, 100);
clearInterval(this.playedTime);
this.trigger('pause');
}
});
// show audio time: the metadata has loaded or changed
this.audio.addEventListener('durationchange', () => {
if (this.audio.duration !== 1) { // compatibility: Android browsers will output 1 at first
this.element.getElementsByClassName('aplayer-dtime')[0].innerHTML = this.secondToTime(this.audio.duration);
}
});
// show audio loaded bar: to inform interested parties of progress downloading the media
this.audio.addEventListener('progress', () => {
const percentage = this.audio.buffered.length ? this.audio.buffered.end(this.audio.buffered.length - 1) / this.audio.duration : 0;
this.updateBar('loaded', percentage, 'width');
});
// audio download error: an error occurs
this.audio.addEventListener('error', () => {
this.element.getElementsByClassName('aplayer-author')[0].innerHTML = ` - Error happens ╥﹏╥`;
this.trigger('pause');
});
// audio can play: enough data is available that the media can be played
this.audio.addEventListener('canplay', () => {
this.trigger('canplay');
});
// multiple music play
this.ended = false;
this.audio.addEventListener('ended', () => {
if (this.multiple) {
if (this.audio.currentTime !== 0) {
if (this.mode === 'random') {
this.setMusic(this.nextRandomNum());
}
else if (this.mode === 'single') {
this.setMusic(this.playIndex);
}
else if (this.mode === 'order') {
if (this.playIndex < this.option.music.length - 1) {
this.setMusic(++this.playIndex);
}
else {
this.ended = true;
this.pause();
this.trigger('ended');
}
}
else if (this.mode === 'circulation') {
if (this.playIndex < this.option.music.length - 1) {
this.setMusic(++this.playIndex);
}
else {
this.setMusic(0);
}
}
}
}
else {
if (this.mode === 'order') {
this.ended = true;
this.pause();
this.trigger('ended');
}
}
});
// control volume
this.audio.volume = parseInt(this.element.getElementsByClassName('aplayer-volume')[0].style.height) / 100;
// loop
this.audio.loop = !(this.multiple || this.mode === 'order');
this.audios[indexMusic] = this.audio;
}
/**
* Parse lrc, suppose multiple time tag
*
* @param {String} lrc_s - Format:
* [mm:ss.xx]lyric
* [mm:ss.xxx]lyric
* [mm:ss.xx][mm:ss.xx][mm:ss.xx]lyric
*
* @return {String} [[time, text], [time, text], [time, text], ...]
*/
const parseLrc = (lrc_s) => {
const lyric = lrc_s.split('\n');
let lrc = [];
const lyricLen = lyric.length;
for (let i = 0; i < lyricLen; i++) {
// match lrc time
const lrcTimes = lyric[i].match(/\[(\d{2}):(\d{2})\.(\d{2,3})]/g);
// match lrc text
const lrcText = lyric[i].replace(/\[(\d{2}):(\d{2})\.(\d{2,3})]/g, '').replace(/^\s+|\s+$/g, '');
if (lrcTimes != null) {
// handle multiple time tag
const timeLen = lrcTimes.length;
for (let j = 0; j < timeLen; j++) {
const oneTime = /\[(\d{2}):(\d{2})\.(\d{2,3})]/.exec(lrcTimes[j]);
const lrcTime = (oneTime[1]) * 60 + parseInt(oneTime[2]) + parseInt(oneTime[3]) / ((oneTime[3] + '').length === 2 ? 100 : 1000);
lrc.push([lrcTime, lrcText]);
}
}
}
// sort by time
lrc.sort((a, b) => a[0] - b[0]);
return lrc;
};
// fill in lrc
if (this.option.showlrc) {
const index = indexMusic;
if (!this.lrcs[index]) {
let lrcs = '';
if (this.option.showlrc === 1) {
lrcs = this.option.music[index].lrc;
}
else if (this.option.showlrc === 2 || this.option.showlrc === true) {
lrcs = this.savelrc[index];
}
else if (this.option.showlrc === 3) {
const xhr = new XMLHttpRequest();
xhr.onreadystatechange = () => {
if (xhr.readyState === 4) {
if (xhr.status >= 200 && xhr.status < 300 || xhr.status === 304) {
lrcs = xhr.responseText;
this.lrcs[index] = parseLrc(lrcs);
}
else {
console.log('Request was unsuccessful: ' + xhr.status);
this.lrcs[index] = [['00:00', 'Not available']];
}
this.lrc = this.lrcs[index];
let lrcHTML = '';
this.lrcContents = this.element.getElementsByClassName('aplayer-lrc-contents')[0];
for (let i = 0; i < this.lrc.length; i++) {
lrcHTML += `<p>${this.lrc[i][1]}</p>`;
}
this.lrcContents.innerHTML = lrcHTML;
if (!this.lrcIndex) {
this.lrcIndex = 0;
}
this.lrcContents.getElementsByTagName('p')[0].classList.add('aplayer-lrc-current');
this.lrcContents.style.transform = 'translateY(0px)';
this.lrcContents.style.webkitTransform = 'translateY(0px)';
}
};
let apiurl;
apiurl = this.option.music[index].lrc;
xhr.open('get', apiurl, true);
xhr.send(null);
}
if (lrcs) {
this.lrcs[index] = parseLrc(lrcs);
}
else {
if (this.option.showlrc === 3) {
this.lrcs[index] = [['00:00', 'Loading']];
}
else {
this.lrcs[index] = [['00:00', 'Not available']];
}
}
}
this.lrc = this.lrcs[index];
let lrcHTML = '';
this.lrcContents = this.element.getElementsByClassName('aplayer-lrc-contents')[0];
for (let i = 0; i < this.lrc.length; i++) {
lrcHTML += `<p>${this.lrc[i][1]}</p>`;
}
this.lrcContents.innerHTML = lrcHTML;
if (!this.lrcIndex) {
this.lrcIndex = 0;
}
this.lrcContents.getElementsByTagName('p')[0].classList.add('aplayer-lrc-current');
this.lrcContents.style.transform = 'translateY(0px)';
this.lrcContents.style.webkitTransform = 'translateY(0px)';
}
// set duration time
if (this.audio.duration !== 1) { // compatibility: Android browsers will output 1 at first
this.element.getElementsByClassName('aplayer-dtime')[0].innerHTML = this.audio.duration ? this.secondToTime(this.audio.duration) : '00:00';
}
// autoplay
if (this.option.autoplay && !this.isMobile) {
this.play();
}
this.option.autoplay = true; // autoplay next music
}
/**
* Play music
*/
play(time) {
if (Object.prototype.toString.call(time) === '[object Number]') {
this.audio.currentTime = time;
}
if (this.audio.paused) {
this.audio.play();
}
}
/**
* Pause music
*/
pause() {
if (!this.audio.paused) {
this.audio.pause();
}
}
/**
* Set volume
*/
volume(percentage) {
this.updateBar('volume', percentage, 'height');
this.audio.volume = percentage;
if (this.audio.muted) {
this.audio.muted = false;
}
if (percentage === 1) {
this.volumeicon.className = 'aplayer-icon aplayer-icon-volume-up';
this.volumeicon.innerHTML = this.getSVG('volume-up');
}
else {
this.volumeicon.className = 'aplayer-icon aplayer-icon-volume-down';
this.volumeicon.innerHTML = this.getSVG('volume-down');
}
}
/**
* attach event
*/
on(name, func) {
if (typeof func === 'function') {
this.event[name].push(func);
}
}
/**
* toggle between play and pause
*/
toggle() {
if (this.button.classList.contains('aplayer-play')) {
this.play();
}
else if (this.button.classList.contains('aplayer-pause')) {
this.pause();
}
}
/**
* get random order, using FisherYates shuffle
*/
getRandomOrder() {
function random(min, max) {
if (max == null) {
max = min;
min = 0;
}
return min + Math.floor(Math.random() * (max - min + 1));
}
function shuffle(arr) {
var length = arr.length,
shuffled = new Array(length);
for (var index = 0, rand; index < length; index++) {
rand = random(0, index);
if (rand !== index) shuffled[index] = shuffled[rand];
shuffled[rand] = arr[index];
}
return shuffled;
}
if (this.multiple) {
this.randomOrder = shuffle([...Array(this.option.music.length)].map(function(item, i) {
return i;
}));
}
}
/**
* get next random number
*/
nextRandomNum() {
if (this.multiple) {
let index = this.randomOrder.indexOf(this.playIndex);
if (index === this.randomOrder.length - 1) {
return this.randomOrder[0];
}
else {
return this.randomOrder[index + 1];
}
}
else {
return 0;
}
}
/**
* destroy this player
*/
destroy() {
instances.splice(instances.indexOf(this), 1);
this.pause();
this.element.innerHTML = '';
clearInterval(this.playedTime);
for (let key in this) {
if (this.hasOwnProperty(key)) {
delete this[key];
}
}
}
/**
* add music dynamically
*
* @param {Array} newMusic
*/
addMusic(newMusic) {
this.option.music = this.option.music.concat(newMusic);
const list = this.element.getElementsByClassName('aplayer-list')[0];
const listEle = list.getElementsByTagName('ol')[0];
let newItemHTML = ``;
for (let i = 0; i < newMusic.length; i++) {
newItemHTML += `
<li>
<span class="aplayer-list-cur" style="background: ${this.option.theme};"></span>
<span class="aplayer-list-index">${this.option.music.length - newMusic.length + i + 1}</span>
<span class="aplayer-list-title">${newMusic[i].title}</span>
<span class="aplayer-list-author">${newMusic[i].author}</span>
</li>`
}
listEle.innerHTML += newItemHTML;
if (!this.multiple) {
this.multiple = true;
this.element.classList.add('aplayer-withlist');
this.audio.loop = false;
}
list.style.height = 'auto';
list.style.height = list.offsetHeight + 'px';
this.getRandomOrder();
}
}
module.exports = APlayer;