自己动手,打造一款适合 Python 程序员的 Html5 音乐播放器
2020-03-04 22:45
901 查看
近期断断续续读了一些 Web Audio API 和 WebRTC API 的文档,发现了很多好玩的东西,有一种“忽入桃花源”的感觉。这一款 Html5 音乐播放器,就是基于 Web Audio 技术实现的,原型来自于 Web Audio API 的例子。为什么说适合 Python 程序员呢?因为使用者需要理解下面这一行命令:
python -m http.server
这个命令,将(命令行窗口)当前路径作为 WebRoot,启动了一个 Web 服务器,我们可以从浏览器访问 WebRoot 之下的所有文件和文件夹。这意味着,放在 WebRoot 之下的 html 文件,将以远程文件而不是本地文件的形式被浏览器打开。我们知道,JavaScript 支持的很多高级功能,比如 Ajax、 Web Audio 等,都必须依赖于服务端才能运行,有了这个快速构建 Web 服务器的手段,将会给启蒙学习、验证测试带来极大的便利。
播放器外观模拟了一台录音机,带音量调节和双声道均衡,纯 JavaScript (不是jQuery)实现,完全兼容Firefox/Chrome/Edge 等浏览器。除了样式表文件,全部代码只有130行,我已上传至github,如果有兴趣,可自行下载。
使用方法
- 在项目所在路径下打开一个m命令行窗口,运行:python -m http.server
- 在浏览器地址栏输入:http://127.0.0.1:8000
- 仅能播放项目所在路径下music文件夹内的音乐文件
index.html
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta http-equiv="x-ua-compatible" content="ie=edge"> <title>Html5音乐播放器</title> <meta name="description" content="Audio basics demo for Web Audio API"> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> <link rel="stylesheet" type="text/css" href="style.css"> </head> <body> <div id="boombox"> <div class="boombox-handle"></div> <div class="boombox-body"> <section class="master-controls"> <input type="range" id="volume" class="control-volume" min="0" max="2" value="1" list="gain-vals" step="0.01" data-action="volume" /> <datalist id="gain-vals"> <option value="0" label="min"> <option value="2" label="max"> </datalist> <label for="volume">VOL</label> <input type="range" id="panner" class="control-panner" list="pan-vals" min="-1" max="1" value="0" step="0.01" data-action="panner" /> <datalist id="pan-vals"> <option value="-1" label="left"> <option value="1" label="right"> </datalist> <label for="panner">PAN</label> <button class="control-power" role="switch" aria-checked="false" data-power="on"> <span>On/Off</span> </button> </section> <section class="tape"> <audio src="" crossorigin="anonymous" ></audio> <button data-playing="false" class="tape-controls-play" role="switch" aria-checked="false"> <span>Play/Pause</span> </button> </section> </div> <div class="boombox-file"> <input type="file" name="mp3fn" id="mp3fn" accept="audio/mp3, audio/wav" /> </div> </div> <script type="text/javascript"> console.clear(); var burl = document.URL.split("/"); burl.pop(); burl = burl.join("/"); const AudioContext = window.AudioContext || window.webkitAudioContext; let audioCtx; const audioElement = document.querySelector('audio'); let track; const playButton = document.querySelector('.tape-controls-play'); playButton.disabled = true; const fileButton = document.querySelector('#mp3fn'); fileButton.addEventListener('change', function() { let fn = fileButton.value.split("\\"); fn = fn.pop(); if(playButton.dataset.playing == 'true'){ playButton.click(); } audioElement.src = burl + "/" + fn; playButton.disabled = false; playButton.click(); }); // play pause audio playButton.addEventListener('click', function() { let fn = fileButton.value.split("\\"); fn = fn.pop(); audioElement.src = burl + "/music/" + fn; if(!audioCtx) { init(); } if (audioCtx.state === 'suspended') { audioCtx.resume(); } if (this.dataset.playing === 'false') { audioElement.play(); this.dataset.playing = 'true'; } else if (this.dataset.playing === 'true') { audioElement.pause(); this.dataset.playing = 'false'; } let state = this.getAttribute('aria-checked') === "true" ? true : false; this.setAttribute( 'aria-checked', state ? "false" : "true" ); }, false); audioElement.addEventListener('ended', () => { playButton.dataset.playing = 'false'; playButton.setAttribute( "aria-checked", "false" ); }, false); function init() { audioCtx = new AudioContext(); track = audioCtx.createMediaElementSource(audioElement); const gainNode = audioCtx.createGain(); const volumeControl = document.querySelector('[data-action="volume"]'); volumeControl.addEventListener('input', function() { gainNode.gain.value = this.value; }, false); const pannerOptions = { pan: 0 }; const panner = new StereoPannerNode(audioCtx, pannerOptions); const pannerControl = document.querySelector('[data-action="panner"]'); pannerControl.addEventListener('input', function() { panner.pan.value = this.value; }, false); track.connect(gainNode).connect(panner).connect(audioCtx.destination); } </script> </body> </html>
style.css
:root { --orange: hsla(32, 100%, 50%, 1); --yellow: hsla(49, 99%, 50%, 1); --lime: hsla(82, 90%, 45%, 1); --green: hsla(127, 81%, 41%, 1); --red: hsla(342, 93%, 53%, 1); --pink: hsla(314, 85%, 45%, 1); --blue: hsla(211, 92%, 52%, 1); --purple: hsla(283, 92%, 44%, 1); --cyan: hsla(195, 98%, 55%, 1); --white: hsla(0, 0%, 95%, 1); --black: hsla(0, 0%, 10%, 1); /* abstract our colours */ --boxMain: var(--blue); --boxSecond: var(--cyan); --boxHigh: var(--orange); --border: 1vmin solid var(--black); --borderRad: 2px; } * {box-sizing: border-box;} body { background-color: var(--white); padding: 4vmax; font-family: system-ui; color: var(--black); } #boombox { width: 82vw; max-width: 800px; margin: 0px auto; } @media screen and (max-width: 570px) { #boombox {max-width: 600px;} } .boombox-handle { margin: 0px 5vw; height: 12vh; background: var(--white) linear-gradient(180deg, var(--boxHigh) 4vmin, var(--black) 4vmin, var(--black) 5vmin, var(--white) 5vmin) no-repeat; border: var(--border); border-bottom-width: 0px; border-radius: var(--borderRad); } .boombox-body { /*gradient with two circles for speakers*/ /* padding-top: 3vh; */ background: var(--boxMain) repeat-x bottom left; background-image: radial-gradient(circle, var(--boxMain) 2vmin, var(--black) 2vmin, var(--black) 3vmin, var(--boxSecond) 3vmin, var(--boxSecond) 9vmin, var(--black) 9vmin, var(--black) 9.5vmin, var(--boxSecond) 9.5vmin, var(--boxSecond) 12vmin, var(--black) 12vmin, var(--black) 13vmin, var(--boxMain) 13vmin); background-size: 33.3% 70%; border: 6px solid var(--black); border-radius: var(--borderRad); } /*small screen with one circle*/ @media screen and (max-width: 570px) { .boombox-body { background-repeat: no-repeat; background-position: center top; background-size: 70% 70%; background-image: radial-gradient(circle, var(--boxMain) 4vmin, var(--black) 4vmin, var(--black) 5vmin, var(--boxSecond) 5vmin, var(--boxSecond) 21vmin, var(--black) 21vmin, var(--black) 21.5vmin, var(--boxSecond) 21.5vmin, var(--boxSecond) 24vmin, var(--black) 24vmin, var(--black) 25vmin, var(--boxMain) 25vmin); } } .boombox-file { padding: 10px; } .master-controls { display: grid; grid-template-rows: repeat(2, auto); grid-template-columns: repeat(2, 1fr) 12%; /*name our grid areas so they are more manipulative later*/ grid-template-areas: "volin panin power" "vollab panlab power"; justify-items: center; align-items: center; padding: 2vmax; background-color: var(--boxSecond); border-bottom: var(--border); } /* change control grid areas for smaller boom box */ @media screen and (max-width: 570px) { .master-controls { grid-gap: 10px; grid-template-columns: 16% 1fr 12%; grid-template-areas: "vollab volin power" "panlab panin power"; } } .master-controls input, .master-controls label {display: block;} .master-controls input::before, .master-controls input::after { line-height: 5vh; color: var(--black); } .master-controls input::before {padding-right: 1vw;} .master-controls input::after {padding-left: 1vw;} .control-volume {grid-area: volin;} .control-volume::before {content: 'min';} .control-volume::after {content: 'max';} label[for="volume"] {grid-area: vollab; margin-top: 15px;} .control-panner {grid-area: panin;} .control-panner::before {content: 'left';} .control-panner::after {content: 'right';} label[for="panner"] {grid-area: panlab; margin-top: 15px;} @media screen and (max-width: 570px) { label[for="volume"], label[for="panner"] {margin-top: 0px;} .control-volume {margin-bottom: 20px;} } .control-power { grid-area: power; align-self: center; width: 40px; height: 40px; border: 3px solid var(--black); border-radius: 20px; cursor: pointer; } .control-power span {display: none;} /* tape area ~~~~~~~~~~~~~~~~~~~~~~ */ .tape { display: grid; grid-template-rows: 24vh 6vh; grid-template-columns: repeat(5, 1fr); grid-template-areas: "tape tape tape tape tape" "back rewind play ff next"; width: 26vw; margin: 0px auto; background: var(--boxMain) no-repeat center center; background-image: radial-gradient(circle at 30%, var(--boxSecond) 2vh, var(--black) 2vh, var(--black) 2.5vh, transparent 2.5vh), radial-gradient(circle at 70%, var(--boxSecond) 2vh, var(--black) 2vh, var(--black) 2.5vh, transparent 2.5vh), linear-gradient(180deg, transparent 9.1vh, var(--black) 9.1vh, var(--black) 10.1vh, var(--boxHigh) 10.1vh, var(--boxHigh) 20vh, var(--black) 20vh, var(--black) 21vh, transparent 21vh), radial-gradient(circle at 30%, var(--boxHigh) 5vh, var(--black) 5vh, var(--black) 6vh, transparent 6vh), radial-gradient(circle at 70%, var(--boxHigh) 5vh, var(--black) 5vh, var(--black) 6vh, transparent 6vh); background-size: 100% 100%, 100% 100%, 40% 100%, 100% 100%, 100% 100%; border: var(--border); border-bottom-width: 0px; border-radius: var(--borderRad); max-width: 300px; } @media screen and (max-width: 570px) { .tape {width: 80%; margin-top: 30vh;} } audio { grid-area: tape; } /*TODO GIVE BUTTONS CLASSES*/ [class^="tape-controls"] { border: none; } [class^="tape-controls"] span {display: none;} .tape-controls-play {grid-area: play;} /* ~~~~~~~~~~~~~~~~ All the crazy range styling */ input[type=range] { -webkit-appearance: none; width: 30vw; background: transparent; } /* set min & max for different screen sizes */ @media screen and (min-width: 1100px) { input[type=range] {width: 270px;} } @media(max-width: 680px) { input[type=range] { width: 180px; } } @media(max-width: 380px) { input[type=range] { width: 130px; } } input[type=range]::-ms-track { width: 100%; cursor: pointer; background: transparent; border-color: transparent; color: transparent; } input[type=range]::-webkit-slider-thumb { -webkit-appearance: none; margin-top: -1vh; height: 4vh; width: 2vw; border: 0.5vmin solid var(--black); border-radius: var(--borderRad); background-color: var(--boxMain); cursor: pointer; } input[type=range]::-moz-range-thumb { height: 4vh; width: 2vw; border: 0.5vmin solid var(--black); border-radius: var(--borderRad); background-color: var(--boxMain); cursor: pointer; } input[type=range]::-ms-thumb { height: 4vh; width: 2vw; border: 0.5vmin solid var(--black); border-radius: var(--borderRad); background-color: var(--boxMain); cursor: pointer; } input[type=range]::-webkit-slider-runnable-track { height: 2vh; cursor: pointer; background-color: var(--black); border-radius: var(--borderRad); } input[type=range]::-moz-range-track { height: 2vh; cursor: pointer; background-color: var(--black); border-radius: var(--borderRad); } input[type=range]::-ms-track { height: 2vh; cursor: pointer; background-color: var(--black); border-radius: var(--borderRad); } input[type=range]:focus { outline: none; } input[type=range]:focus::-webkit-slider-runnable-track { background: var(--boxMain); } /*~~~~~~~~~~~~~~~~ the play pause & power icons*/ [data-playing] { background: transparent url('data:image/svg+xml;charset=utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M424.4 214.7L72.4 6.6C43.8-10.3 0 6.1 0 47.9V464c0 37.5 40.7 60.1 72.4 41.3l352-208c31.4-18.5 31.5-64.1 0-82.6z" fill="black" /></svg>') no-repeat center center; background-size: 60% 60%; cursor: pointer; } [data-playing]:hover { background: transparent url('data:image/svg+xml;charset=utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M424.4 214.7L72.4 6.6C43.8-10.3 0 6.1 0 47.9V464c0 37.5 40.7 60.1 72.4 41.3l352-208c31.4-18.5 31.5-64.1 0-82.6z" fill="hsla(32, 100%, 50%, 1)" /></svg>') no-repeat center center; background-size: 60% 60%; } [data-playing="true"] { background: transparent url('data:image/svg+xml;charset=utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M144 479H48c-26.5 0-48-21.5-48-48V79c0-26.5 21.5-48 48-48h96c26.5 0 48 21.5 48 48v352c0 26.5-21.5 48-48 48zm304-48V79c0-26.5-21.5-48-48-48h-96c-26.5 0-48 21.5-48 48v352c0 26.5 21.5 48 48 48h96c26.5 0 48-21.5 48-48z" fill="black" /></svg>') no-repeat center center; background-size: 60% 60%; } [data-playing="true"]:hover { background: transparent url('data:image/svg+xml;charset=utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M144 479H48c-26.5 0-48-21.5-48-48V79c0-26.5 21.5-48 48-48h96c26.5 0 48 21.5 48 48v352c0 26.5-21.5 48-48 48zm304-48V79c0-26.5-21.5-48-48-48h-96c-26.5 0-48 21.5-48 48v352c0 26.5 21.5 48 48 48h96c26.5 0 48-21.5 48-48z" fill="hsla(32, 100%, 50%, 1)" /></svg>') no-repeat center center; background-size: 60% 60%; } [data-power] { background: var(--boxSecond) url('data:image/svg+xml; charset=utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M400 54.1c63 45 104 118.6 104 201.9 0 136.8-110.8 247.7-247.5 248C120 504.3 8.2 393 8 256.4 7.9 173.1 48.9 99.3 111.8 54.2c11.7-8.3 28-4.8 35 7.7L162.6 90c5.9 10.5 3.1 23.8-6.6 31-41.5 30.8-68 79.6-68 134.9-.1 92.3 74.5 168.1 168 168.1 91.6 0 168.6-74.2 168-169.1-.3-51.8-24.7-101.8-68.1-134-9.7-7.2-12.4-20.5-6.5-30.9l15.8-28.1c7-12.4 23.2-16.1 34.8-7.8zM296 264V24c0-13.3-10.7-24-24-24h-32c-13.3 0-24 10.7-24 24v240c0 13.3 10.7 24 24 24h32c13.3 0 24-10.7 24-24z" fill="black" /></svg>') no-repeat center center; background-size: 60% 60%; } [data-power]:hover, [data-power="on"] { background: var(--boxHigh) url('data:image/svg+xml; charset=utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M400 54.1c63 45 104 118.6 104 201.9 0 136.8-110.8 247.7-247.5 248C120 504.3 8.2 393 8 256.4 7.9 173.1 48.9 99.3 111.8 54.2c11.7-8.3 28-4.8 35 7.7L162.6 90c5.9 10.5 3.1 23.8-6.6 31-41.5 30.8-68 79.6-68 134.9-.1 92.3 74.5 168.1 168 168.1 91.6 0 168.6-74.2 168-169.1-.3-51.8-24.7-101.8-68.1-134-9.7-7.2-12.4-20.5-6.5-30.9l15.8-28.1c7-12.4 23.2-16.1 34.8-7.8zM296 264V24c0-13.3-10.7-24-24-24h-32c-13.3 0-24 10.7-24 24v240c0 13.3 10.7 24 24 24h32c13.3 0 24-10.7 24-24z" fill="black" /></svg>') no-repeat center center; background-size: 60% 60%; }
- 点赞 1
- 收藏
- 分享
- 文章举报
相关文章推荐
- Python打造出适合自己的定制化Eclipse IDE
- linux 下好用的音乐播放器介绍(总有一款适合你,有些依赖问题,自己解决)
- Python打造出适合自己的定制化Eclipse IDE
- 自己动手打造html5星际迷航!
- 自己动手python打造渗透工具集
- 深入浅出PE文件格式---自己动手打造PE Show
- 自己动手开发音乐播放器《五》播放功能的实现
- HTML5:使用aduio标签打造音乐播放器
- Windows8的到来,让所有程序员必须彻底的刷新自己,否则将被淘汰!学习HTML5
- 《自己动手打造“超高精度浮点数类”》源代码简要导读
- 自己动手打造WEB服务器 Windows + Apache + PHP + MySQL
- HTML5打造的炫酷本地音乐播放器-喵喵Player
- 选购一款适合自己的显卡系列之4:The Chips
- 自己动手打造企业网络访问控制器
- Java程序员从笨鸟到菜鸟之(六十八)细谈Spring(二)自己动手模拟spring
- 调查报告:Python成最热门工具!近半程序员认为上级技术水平不如自己!
- 自己动手打造企业垃圾邮件过滤系统
- 自己动手打造企业网络访问控制器NAC
- 利用python打造自己的ftp暴力破解工具
- 自己动手开发智能聊天机器人完全指南(附python完整源码)