马黑黑 发表于 2022-8-26 20:24

歌词同步加进度可控的canvas画布音频播放器

本帖最后由 马黑黑 于 2022-8-26 20:27 编辑 <br /><br /><style>
        #papa { left: -214px; width: 1024px; height: 640px; background: #eee; box-shadow: 3px 3px 20px #000; position: relative; z-index: 1; }
        #player { position: absolute; left: 10px; bottom: 10px; border: 1px solid; }
</style>

<div id="papa">
        <canvas id="player"></canvas>
</div>

<script>
let ctx = player.getContext('2d'), w = player.width = 350, h = player.height = 100, btnFlag, lineFlag, aud = new Audio();
let mplayer = {
        startX: 30, startY: 70, radius: 16, lineLen: 200, c_lrc: 'teal', c_time: 'teal', c_track: 'lightblue', c_prg: 'red', c_circle: 'lightblue', c_btn: 'snow', c_btnhover: 'pink',
        drawLrc: function(text) {
                ctx.clearRect(0, 0, w, 50);
                ctx.fillStyle = this.c_lrc;
                ctx.textAlign = 'center';
                ctx.beginPath();
                ctx.font = '1.2em sans-serif';
                ctx.fillText(text, w/2, 30);
                ctx.fill();
        },
        drawProgress: function(prog, text) {
                ctx.clearRect(this.startX + this.radius, this.startY - 10, w - this.startX + this.radius, 40);
                ctx.beginPath();
                ctx.font = '14px sans-serif';
                ctx.textBaseline = 'middle';
                ctx.strokeStyle = this.c_track;
                ctx.moveTo(this.startX + this.radius + 4, this.startY);
                ctx.lineTo(this.startX + this.radius + 4 + this.lineLen, this.startY);
                ctx.stroke();
                ctx.beginPath();
                ctx.strokeStyle = this.c_prg;
                ctx.moveTo(this.startX + this.radius + 4, this.startY);
                ctx.lineTo(this.startX + this.radius + 4 + prog, this.startY);
                ctx.stroke();
                ctx.fillStyle = this.c_time;
                ctx.textAlign = 'left';
                ctx.fillText(text, this.startX + this.radius + 8 + this.lineLen, this.startY);
                ctx.fill();
        },
        drawBtn: function(id) {
                ctx.clearRect(this.startX - this.radius, this.startY - this.radius, this.radius * 2, this.radius * 2);
                ctx.fillStyle = this.c_circle;
                ctx.beginPath();
                ctx.arc(this.startX, this.startY, this.radius, 0, 2 * Math.PI);
                ctx.fill();
                ctx.fillStyle = btnFlag ? this.c_btnhover : this.c_btn;
                ctx.beginPath();
                if (id) {
                        ctx.moveTo(this.startX - this.radius / 2 + 1, this.startY - this.radius / 2);
                        ctx.lineTo(this.startX - this.radius / 2 + 1, this.startY + this.radius / 2);
                        ctx.lineTo(this.startX + this.radius / 2 + 1, this.startY);
                        ctx.fill();
                } else {
                        ctx.fillRect(this.startX - this.radius / 2 + 5, this.startY - this.radius / 2, 2, this.radius);
                        ctx.fillRect(this.startX - this.radius / 2 + 10, this.startY - this.radius / 2, 2, this.radius);
                }
        },
        setProgress: function() {
               
        },
        isBtnHover: function (x, y) {
                return Math.pow(x - this.startX, 2) + Math.pow(y - this.startY, 2) <= Math.pow(this.radius, 2);
        },
        isLineHover: function(x, y) {
                let xx = this.startX + this.radius + 4, yy = this.startY - 5;
                return x >= xx && x <= xx + this.lineLen && y >= yy && y <= yy + 10;
        },
};
let lrcAr = [
        ['0.01','童安格耶利亚女郎'],
        ['34.10','很远的地方有个女郎名字叫做耶利亚'],
        ['42.33','有人在传说她的眼睛看了使人更年轻'],
        ['50.62','如果你得到她的拥抱你就永远不会老'],
        ['59.15','为了这个神奇的传说我要努力去寻找'],
        ['66.42','耶利亚神秘耶利亚耶利耶利亚'],
        ['74.77','耶利亚神秘耶利亚我一定要找到她'],
        ['101.12','很远的地方有个女郎名字叫做耶利亚'],
        ['109.32','有人在传说她的眼睛看了使人更年轻'],
        ['117.90','如果你得到她的拥抱你就永远不会老'],
        ['126.22','为了这个神奇的传说我要努力去寻找'],
        ['133.57','耶利亚神秘耶利亚耶利耶利亚'],
        ['141.90','耶利亚神秘耶利亚我一定要找到她'],
        ['150.29','耶利亚神秘耶利亚耶利耶利亚'],
        ['158.76','耶利亚神秘耶利亚我一定要找到她'],
        ['198.70','耶利亚神秘耶利亚耶利耶利亚'],
        ['207.11','耶利亚神秘耶利亚我一定要找到她'],
        ['215.28','耶利亚神秘耶利亚耶利耶利亚'],
        ['223.72','耶利亚神秘耶利亚我一定要找到她']
];

aud.src = 'https://music.163.com/song/media/outer/url?id=150852.mp3';
aud.autoplay = true;
aud.loop = true;
mplayer.drawBtn(aud.paused);
mplayer.drawProgress(1, '00:00 | 00:00');
mplayer.drawLrc('等待播放 ...');

player.addEventListener('mousemove', (e) => {
        btnFlag = mplayer.isBtnHover(e.offsetX, e.offsetY), lineFlag = mplayer.isLineHover(e.offsetX, e.offsetY);
        player.style.cursor = btnFlag || lineFlag ? 'pointer' : 'default';
        mplayer.drawBtn(aud.paused);
});

player.addEventListener('click', (e) => {
        if(btnFlag) aud.paused ? aud.play() : aud.pause();
        if(lineFlag) aud.currentTime = aud.duration * (e.offsetX - mplayer.startX - mplayer.radius - 4) / mplayer.lineLen;
});

aud.addEventListener('playing', ()=> mplayer.drawBtn(false));
aud.addEventListener('pause', ()=> mplayer.drawBtn(true));

aud.addEventListener('timeupdate', () => {
        let prg = mplayer.lineLen * aud.currentTime / aud.duration, tMsg = toMin(aud.duration) + ' | ' + toMin(aud.currentTime);
        mplayer.drawProgress(prg, tMsg);
        for(j = 0; j < lrcAr.length;j ++) {
                if(aud.currentTime >= lrcAr) mplayer.drawLrc(lrcAr);
        }
});

let toMin = (val)=> {
        if (!val) return '00:00';
        val = Math.floor(val);
        let min = parseInt(val / 60), sec = parseFloat(val % 60);
        if(min < 10) min = '0' + min;
        if(sec < 10) sec = '0' + sec;
        return min + ':' + sec;
}
</script>

马黑黑 发表于 2022-8-26 20:26

代码(不需要播放器边框时可去掉CSS里 #player 的 border 属性):<style>
        #papa { left: -214px; width: 1024px; height: 640px; background: #ccc; box-shadow: 3px 3px 20px #000; position: relative; z-index: 1; }
        #player { position: absolute; left: 10px; bottom: 10px; border: 1px solid; }
</style>

<div id="papa">
        <canvas id="player"></canvas>
</div>

<script>
let ctx = player.getContext('2d'), w = player.width = 350, h = player.height = 100, btnFlag, lineFlag, aud = new Audio();
let mplayer = {
        startX: 30, startY: 70, radius: 16, lineLen: 200, c_lrc: 'teal', c_time: 'teal', c_track: 'lightblue', c_prg: 'red', c_circle: 'lightblue', c_btn: 'snow', c_btnhover: 'pink',
        drawLrc: function(text) {
                ctx.clearRect(0, 0, w, 50);
                ctx.fillStyle = this.c_lrc;
                ctx.textAlign = 'center';
                ctx.beginPath();
                ctx.font = '1.2em sans-serif';
                ctx.fillText(text, w/2, 30);
                ctx.fill();
        },
        drawProgress: function(prog, text) {
                ctx.clearRect(this.startX + this.radius, this.startY - 10, w - this.startX + this.radius, 40);
                ctx.beginPath();
                ctx.font = '14px sans-serif';
                ctx.textBaseline = 'middle';
                ctx.strokeStyle = this.c_track;
                ctx.moveTo(this.startX + this.radius + 4, this.startY);
                ctx.lineTo(this.startX + this.radius + 4 + this.lineLen, this.startY);
                ctx.stroke();
                ctx.beginPath();
                ctx.strokeStyle = this.c_prg;
                ctx.moveTo(this.startX + this.radius + 4, this.startY);
                ctx.lineTo(this.startX + this.radius + 4 + prog, this.startY);
                ctx.stroke();
                ctx.fillStyle = this.c_time;
                ctx.textAlign = 'left';
                ctx.fillText(text, this.startX + this.radius + 8 + this.lineLen, this.startY);
                ctx.fill();
        },
        drawBtn: function(id) {
                ctx.clearRect(this.startX - this.radius, this.startY - this.radius, this.radius * 2, this.radius * 2);
                ctx.fillStyle = this.c_circle;
                ctx.beginPath();
                ctx.arc(this.startX, this.startY, this.radius, 0, 2 * Math.PI);
                ctx.fill();
                ctx.fillStyle = btnFlag ? this.c_btnhover : this.c_btn;
                ctx.beginPath();
                if (id) {
                        ctx.moveTo(this.startX - this.radius / 2 + 1, this.startY - this.radius / 2);
                        ctx.lineTo(this.startX - this.radius / 2 + 1, this.startY + this.radius / 2);
                        ctx.lineTo(this.startX + this.radius / 2 + 1, this.startY);
                        ctx.fill();
                } else {
                        ctx.fillRect(this.startX - this.radius / 2 + 5, this.startY - this.radius / 2, 2, this.radius);
                        ctx.fillRect(this.startX - this.radius / 2 + 10, this.startY - this.radius / 2, 2, this.radius);
                }
        },
        setProgress: function() {
               
        },
        isBtnHover: function (x, y) {
                return Math.pow(x - this.startX, 2) + Math.pow(y - this.startY, 2) <= Math.pow(this.radius, 2);
        },
        isLineHover: function(x, y) {
                let xx = this.startX + this.radius + 4, yy = this.startY - 5;
                return x >= xx && x <= xx + this.lineLen && y >= yy && y <= yy + 10;
        },
};
let lrcAr = [
        ['0.01','童安格耶利亚女郎'],
        ['34.10','很远的地方有个女郎名字叫做耶利亚'],
        ['42.33','有人在传说她的眼睛看了使人更年轻'],
        ['50.62','如果你得到她的拥抱你就永远不会老'],
        ['59.15','为了这个神奇的传说我要努力去寻找'],
        ['66.42','耶利亚神秘耶利亚耶利耶利亚'],
        ['74.77','耶利亚神秘耶利亚我一定要找到她'],
        ['101.12','很远的地方有个女郎名字叫做耶利亚'],
        ['109.32','有人在传说她的眼睛看了使人更年轻'],
        ['117.90','如果你得到她的拥抱你就永远不会老'],
        ['126.22','为了这个神奇的传说我要努力去寻找'],
        ['133.57','耶利亚神秘耶利亚耶利耶利亚'],
        ['141.90','耶利亚神秘耶利亚我一定要找到她'],
        ['150.29','耶利亚神秘耶利亚耶利耶利亚'],
        ['158.76','耶利亚神秘耶利亚我一定要找到她'],
        ['198.70','耶利亚神秘耶利亚耶利耶利亚'],
        ['207.11','耶利亚神秘耶利亚我一定要找到她'],
        ['215.28','耶利亚神秘耶利亚耶利耶利亚'],
        ['223.72','耶利亚神秘耶利亚我一定要找到她']
];

aud.src = 'https://music.163.com/song/media/outer/url?id=150852.mp3';
aud.autoplay = true;
aud.loop = true;
mplayer.drawBtn(aud.paused);
mplayer.drawProgress(1, '00:00 | 00:00');
mplayer.drawLrc('等待播放 ...');

player.addEventListener('mousemove', (e) => {
        btnFlag = mplayer.isBtnHover(e.offsetX, e.offsetY), lineFlag = mplayer.isLineHover(e.offsetX, e.offsetY);
        player.style.cursor = btnFlag || lineFlag ? 'pointer' : 'default';
        mplayer.drawBtn(aud.paused);
});

player.addEventListener('click', (e) => {
        if(btnFlag) aud.paused ? aud.play() : aud.pause();
        if(lineFlag) aud.currentTime = aud.duration * (e.offsetX - mplayer.startX - mplayer.radius - 4) / mplayer.lineLen;
});

aud.addEventListener('playing', ()=> mplayer.drawBtn(false));
aud.addEventListener('pause', ()=> mplayer.drawBtn(true));

aud.addEventListener('timeupdate', () => {
        let prg = mplayer.lineLen * aud.currentTime / aud.duration, tMsg = toMin(aud.duration) + ' | ' + toMin(aud.currentTime);
        mplayer.drawProgress(prg, tMsg);
        for(j = 0; j < lrcAr.length;j ++) {
                if(aud.currentTime >= lrcAr) mplayer.drawLrc(lrcAr);
        }
});

let toMin = (val)=> {
        if (!val) return '00:00';
        val = Math.floor(val);
        let min = parseInt(val / 60), sec = parseFloat(val % 60);
        if(min < 10) min = '0' + min;
        if(sec < 10) sec = '0' + sec;
        return min + ':' + sec;
}
</script>


马黑黑 发表于 2022-8-26 20:31

画布对代码量的需求大一些。

小辣椒 发表于 2022-8-26 20:50

黑黑辛苦,速度也是杠杆的,刚还在说有一个,回头就看见了

小辣椒 发表于 2022-8-26 20:57

黑黑我已经做进刚才的帖里面,这个播放器进度条可控,非常的棒{:4_199:}

马黑黑 发表于 2022-8-26 21:11

小辣椒 发表于 2022-8-26 20:50
黑黑辛苦,速度也是杠杆的,刚还在说有一个,回头就看见了

{:5_106:}

马黑黑 发表于 2022-8-26 21:11

小辣椒 发表于 2022-8-26 20:57
黑黑我已经做进刚才的帖里面,这个播放器进度条可控,非常的棒

对,可以调节播放进度,有用的功能

红影 发表于 2022-8-27 10:15

这个可以调整进度了,太棒了{:4_199:}

红影 发表于 2022-8-27 10:16

前面那个就想问了,如何才能进度条可控,没好意思问,结果黑黑自己就做出来了。很赞{:4_187:}

马黑黑 发表于 2022-8-27 10:36

红影 发表于 2022-8-27 10:16
前面那个就想问了,如何才能进度条可控,没好意思问,结果黑黑自己就做出来了。很赞

之前做过进度可控的,这是举手之劳

红影 发表于 2022-8-27 19:50

马黑黑 发表于 2022-8-27 10:36
之前做过进度可控的,这是举手之劳

黑黑厉害,简直佩服得无可名状{:4_199:}

马黑黑 发表于 2022-8-27 20:19

红影 发表于 2022-8-27 19:50
黑黑厉害,简直佩服得无可名状

{:4_172:}
页: [1]
查看完整版本: 歌词同步加进度可控的canvas画布音频播放器