【小程序】实现一个定制的音乐播放器

news2025/1/12 18:50:49

8a941a1295752c7ba2966d4a38f83839.png

应用地址:https://spacexcode.com/player

介绍

这是为自己制作的一个在线 Web 版的音乐播放器。众所周知,现在市面上的所有的音乐平台都是会员制。而免费的资源却分散在网络上的各个角落,为此,我收集了自己 喜欢的音乐,放到自己的服务器上,并制作了这样一个专属歌单的简单的音乐播放器。

实现讲解

该播放器虽然简单,但也是五脏俱全,该有的功能一个都没少。比如常见的音乐播放器,我们能想到的功能有:

  • 暂停/开始播放按键

  • 调节播放进度的进度条

  • 调节声音大小的按键

  • 切换下一首/上一首按键

  • 查看歌单的界面

界面

如果不考虑界面, Web 端的音频播放有现成的标签 audio 支持

<audio controls src="https://spacexcode.oss-cn-hangzhou.aliyuncs.com/mp3/那女孩对我说.mp3" />
8e382cf0d9905149be535d735f71b5da.png

这是浏览器渲染的默认样式,各个浏览器可能会有差异。我们要实现一个音乐播放器,当然要所有端的样式统一。这个时候,我们就移除 controls 属性,让 实际的播放组件在页面上什么都不显示。然后我们自己去实现上面的所有控件。

由于这个站点引入的是 Material UI,为了统一视觉,就使用里面现成的组件去实现。

function player () {
  const [paused, setPaused] = React.useState(true);

  return (
    <Box sx={{ width: '100%', overflow: 'hidden' }}>
      <div style={{
        padding: 16,
        borderRadius: 16,
        width: 343,
        maxWidth: '100%',
        margin: 'auto',
        position: 'relative',
        zIndex: 1,
        backgroundColor: 'rgba(255,255,255,0.4)',
        backdropFilter: 'blur(40px)'
      }}>
        <Box sx={{ display: 'flex', alignItems: 'center', position: 'relative' }}>
          <div style={{
            width: 100,
            height: 100,
            objectFit: 'cover',
            overflow: 'hidden',
            flexShrink: 0,
            borderRadius: 8,
            backgroundColor: 'rgba(0,0,0,0.08)',
            '& > img': {
              width: '100%',
            }
          }}>
            <img alt='那女孩对我说' src='https://spacexcode.oss-cn-hangzhou.aliyuncs.com/1697270523238-8b4b11a5-b5a3-4ac3-b6bd-1e264f526c76.png' />
          </div>
          <Box sx={{ position: 'absolute', top: 0, right: 0 }}>
            <IconButton aria-label="music queue">
              <QueueMusicIcon fontSize="small" htmlColor='rgba(0,0,0,0.4)' />
            </IconButton>
          </Box>
          <Box sx={{ ml: 1.5, minWidth: 0 }}>
            <Typography variant="caption" color="text.secondary" fontWeight={500}> 林俊杰 </Typography>
            <Typography noWrap> <b>那女孩对我说</b> </Typography>
            <Typography noWrap letterSpacing={-0.25}> 心很空 天很大 云很重 我很孤单 </Typography>
          </Box>
        </Box>
        <Slider aria-label="time-indicator" size="small" value={30} min={0} step={1} max={405}
          sx={{
            color: 'rgba(0,0,0,0.87)',
            height: 4,
            '& .MuiSlider-thumb': {
              width: 8,
              height: 8,
              transition: '0.3s cubic-bezier(.47,1.64,.41,.8)',
              '&:before': {
                boxShadow: '0 2px 12px 0 rgba(0,0,0,0.4)',
              },
              '&:hover, &.Mui-focusVisible': {
                boxShadow: `0px 0px 0px 8px ${
                  'rgb(0 0 0 / 16%)'
                }`,
              },
              '&.Mui-active': {
                width: 20,
                height: 20,
              },
            },
            '& .MuiSlider-rail': {
              opacity: 0.28,
            },
          }}
        />
        <Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', mt: -2 }}
        >
          <div style={{ fontSize: '0.75rem', opacity: 0.38, fontWeight: 500, letterSpacing: 0.2 }}>00:30</div>
          <div style={{ fontSize: '0.75rem', opacity: 0.38, fontWeight: 500, letterSpacing: 0.2 }}>-04:00</div>
        </Box>
        <Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'center', mt: -1 }}
        >
          <audio src='https://spacexcode.oss-cn-hangzhou.aliyuncs.com/mp3/那女孩对我说.mp3' />
          <IconButton aria-label="previous song">
            <FastRewindRounded fontSize="large" htmlColor='#000' />
          </IconButton>
          <IconButton aria-label='pause'>
            <PauseRounded sx={{ fontSize: '3rem' }} htmlColor='#000' />
          </IconButton>
          <IconButton aria-label="next song">
            <FastForwardRounded fontSize="large" htmlColor='#000' />
          </IconButton>
        </Box>
        <Stack spacing={2} direction="row" sx={{ mb: 1, px: 1 }} alignItems="center">
          <VolumeDownRounded htmlColor='rgba(0,0,0,0.4)' />
          <Slider aria-label="Volume" value={0.3} min={0} step={0.01} max={1}
            sx={{
              color: 'rgba(0,0,0,0.87)',
              '& .MuiSlider-track': {
                border: 'none',
              },
              '& .MuiSlider-thumb': {
                width: 16,
                height: 16,
                backgroundColor: '#fff',
                '&:before': {
                  boxShadow: '0 4px 8px rgba(0,0,0,0.4)',
                },
                '&:hover, &.Mui-focusVisible, &.Mui-active': {
                  boxShadow: 'none',
                },
              },
            }}
          />
          <VolumeUpRounded htmlColor='rgba(0,0,0,0.4)' />
        </Stack>
      </div>
    </Box>
  )
}
631352b68166a0eac2ce60cf0275accc.png

至此以上代码,初步实现了基本的播放器界面,然后我们需要对每个功能控件添加事件实现它应该有的功能。

播放按钮

控制播放器的播放,我们可以通过 audio 提供的 play()pause() 接口,首先我们使用 useRef 获取音频组件的实例,然后通过判断 当前的播放状态,调用播放和暂停接口。

function Player () {
  const audioPlayer = useRef();
  const [paused, setPaused] = useState(true);
  const onPlayOrPause = () => {
    paused ? audioPlayer.current.play() : audioPlayer.current.pause();
    setPaused(!paused)
  }
}

下面所有使用的变量 audioPlayer 代表获取的 <audio ref="audioPlayer" /> 播放器实例

调节进度

Slider 组件上绑定 onChange 事件,此时 Slider 组件中的最大值即为该音频资源的最大时长。回调中拿到设置的进度值后赋值给 currentTime 属性。

const [position, setPosition] = useState(0); // 表示当前的播放进度值

const onChangeProgress = (val) => {
  setPosition(val);
  audioPlayer.current.currentTime = val;
}

这个音频的资源时长,可以通过监听资源加载完成事件,通过 duration 属性获取。它单位为**毫秒(ms)**。

audioPlayer.current.addEventListener('loadeddata', () => {
  setDuration(audioPlayer.current?.duration)
});

调节音量

音量的调节和进度同样使用的是 Slider 组件,不过它的最大值为 1,步长为 0.01

改变音量时,将获取到的音量值赋给 audioPlayer 实例的 volumn 属性。

const onChangeVolume = (val) => {
  setVolume(val);
  audioPlayer.current.volume = val;
};

切换歌单

切换上一首/下一首,无非就是改变当前播放的音频资源。该音频资源对应到资源列表的索引值,我们通过控制索引值去列表中获取不同的音乐素材。

这里要考虑边界值的情况,当切换的时候碰到最大值和最小值,如果允许循环,那么在索引值等于数组长度时,将它的值重置为 0

// 上一首
const onPreview = () => {
  if (currentIndex > 0) {
    changeCurrentIndex(currentIndex - 1); 
  }
}

// 下一首
const onNext = () => {
  if (currentIndex < songList.length - 1) {
    changeCurrentIndex(currentIndex + 1);
  } else {
    changeCurrentIndex(0);
  }
}

查看歌单

除了所有的功能按钮,右上角还有一个查看歌单的按钮,点击从底部弹出一个 drawer 抽屉组件。里面列出了一个收藏的音乐列表。

function MusicQueue() {
  const songList = [
    {
      artists: '林俊杰',
      name: '那女孩对我说',
      avatar: 'https://spacexcode.oss-cn-hangzhou.aliyuncs.com/1697270523238-8b4b11a5-b5a3-4ac3-b6bd-1e264f526c76.png',
      link: 'https://spacexcode.oss-cn-hangzhou.aliyuncs.com/mp3/那女孩对我说.mp3',
      lyric: '心很空 天很大 云很重 我很孤单'
    },
    {
      artists: '张靓颖',
      name: '终于等到你',
      avatar: 'https://spacexcode.oss-cn-hangzhou.aliyuncs.com/1697503257045-7a262b7d-0df0-4005-a69c-2a645ac24c27.png',
      link: 'https://spacexcode.oss-cn-hangzhou.aliyuncs.com/mp3/终于等到你-张靓颖.mp3',
      lyric: '到了某个年纪你就会知道 一个人的日子真的难熬'
    },
    {
      artists: '张靓颖',
      name: '饿狼传说',
      avatar: 'https://spacexcode.oss-cn-hangzhou.aliyuncs.com/1697503257045-7a262b7d-0df0-4005-a69c-2a645ac24c27.png',
      link: 'https://spacexcode.oss-cn-hangzhou.aliyuncs.com/mp3/饿狼传说-张靓颖.mp3',
      lyric: '她熄掉晚灯 幽幽掩两肩 交织了火花 拘禁在沉淀'
    }
  ];
  return (
    <Box sx={{ width: 'auto' }} role="presentation">
      <List>
        {songList.map((item, index) => (
          <ListItem
            key={index} 
            disablePadding>
            <ListItemButton>
              <ListItemIcon>
                <Avatar alt={item.artists} src={item.avatar} />
              </ListItemIcon>
              <ListItemText secondary={
                <React.Fragment>
                  <Typography sx={{ display: 'inline' }} component="span" variant="body1" color="text.primary">
                    {item.name}
                  </Typography>
                  {' -- ' + item.artists}
                </React.Fragment>
              } />
            </ListItemButton>
          </ListItem>
        ))}
      </List>
    </Box>
  );
}
ab4da5ca5227a003db10c1bc4a386227.png

至此我们已经实现了界面上所有的控制按钮的功能。最后需要完善下一些边界和初始情况的处理。

当音频在播放的时候,进度条需要随着当前播放时间变化而变化。这里采用定时器,每隔一秒获取播放资源的当前播放时间点,同步给 Slider 组件的 value 属性来定位。

const [timer, setTimer] = useState(null);

audioPlayer.current.addEventListener('play', () => {
  if (timer) {
    clearInterval(timer);
  }
  setTimer(setInterval(() => {
    setPosition(audioPlayer.current?.currentTime)
  }, 1000));
});
audioPlayer.current.addEventListener('pause', () => {
  if (timer) {
    clearInterval(timer);
  }
});

还有歌词的显示,下一步计划加上~

总结

实现一个定制的音乐播放器不复杂,控制功能的实现都有现成的接口:

  • 播放 play()

  • 暂停 pause()

  • 播放进度通过 currentTime 获取

  • 音量控制通过 volumn 的值控制

  • 音频的自动播放通过 autoplay 属性

  • 控制音频播放的时候为静音 muted

  • 当前音频资源循环播放通过属性 loop

我们需要考虑的是如何优化界面,毕竟一款颜值高的播放器和动听的音乐才能给我们带来内心的愉悦。

- END -

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1127873.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

代码签名证书到期了怎么续费?

我们都知道代码签名证书最长期限可以申请3年&#xff0c;但有的首次申请也会申请1年&#xff0c;这种情况下证书到期了就意味着要重新办理&#xff0c;同样的实名验证步骤还需要再走一遍&#xff0c;尤其目前无论是哪种类型的代码签名证书都会有物理硬件&#xff0c;即使交钱实…

将本地代码上传至码云具体步骤

前言&#xff1a;假如我们在本地创建了一个新项目&#xff0c;现在想将这个项目上传至码云 第一步&#xff1a;码云上创建仓库 第二步&#xff1a;点击创建完成仓库 到这就已经完成了码云仓库的创建&#xff01;&#xff01;&#xff01; 第三步&#xff1a;打开cmd命令输入这…

微信小程序5

一、什么是后台交互&#xff1f; 在小程序中&#xff0c;与后台交互指的是小程序前端与后台服务器之间的数据通信和请求处理过程。通过与后台交互&#xff0c;小程序能够获取服务器端的数据、上传用户数据、发送请求等。 与后台交互可以通过以下方式实现&#xff1a; 发起网络请…

java对象深拷贝(Mapstruct)代码实现

这几天写的需求正在提测中&#xff0c;所以比较有空闲时间&#xff0c;正好来总结一下开发中遇到的问题并记录一下。 在开发过程中遇到这样一个问题&#xff1a;多个对象实体间要进行对象拷贝&#xff0c;并且对象里面还包含别的对象集合&#xff0c;对象名字也不同&#xff0…

那些只要一两行代码就能搞定的Python操作

Python是一种简洁、易读且功能强大的编程语言&#xff0c;有很多操作只需要一行代码就能完成。本文将介绍一些常用的单行代码操作&#xff0c;并分析其技术原理&#xff0c;让读者更深入地理解Python的简洁与高效。 1、列表推导式 列表推导式是Python中一种简洁的构造列表的方法…

STM32:外部中断

中断&#xff0c;顾名思义就是停止现在正在干的活&#xff0c;去干其他更紧急的事情。在通常的信息系统中&#xff0c;中断发生时&#xff0c;会先保留现场&#xff0c;即当前的运行情况和状态。在去做其他紧急事情。事情做完还要恢复原先中断前的状态继续干原来的活。在STM32中…

python 字典dict和列表list的读取速度问题, range合并

嗨喽&#xff0c;大家好呀~这里是爱看美女的茜茜呐 python 字典和列表的读取速度问题 最近在进行基因组数据处理的时候&#xff0c;需要读取较大数据&#xff08;2.7G&#xff09;存入字典中&#xff0c; 然后对被处理数据进行字典key值的匹配&#xff0c;在被处理文件中每次…

模拟开关与多路复用器

模拟开关 模拟开关现在有两种工艺&#xff0c;模拟开关与cmos工艺 CMOS模拟开关收到温度还有供电电压影响&#xff0c;尽量供电高一点 jfet断电导通&#xff0c;cmos断电断开 因为寄生电容&#xff0c;交流信号会漏过模拟开关 没有负电源脚不能传交流电&#xff0c…

windows安装docker,解决require wsl 2问题

想在windows上安装桌面版docker&#xff0c;上官网下载了安装包&#xff0c;安装完后&#xff0c;启动报错&#xff0c;忘了截图了。 大概意思就是require wsl 2。 于是就是docker FAQ中找相关问题解决方案&#xff0c;点&#xff0c;点&#xff0c;点然后就点到微软了。 ws…

重入漏洞EtherStore

重入漏洞 // SPDX-License-Identifier: MIT pragma solidity ^0.8.13;contract EtherStore {mapping(address > uint) public balances;function deposit() public payable {balances[msg.sender] msg.value;}function withdraw() public {uint bal balances[msg.sender]…

干货分享 | TSMaster几种过滤器的对比及使用

TSMaster的4种过滤器&#xff1a; //硬件过滤器&#xff1a;可以在硬件端针对数据位进行筛选过滤&#xff0c;硬件过滤。在硬件端阻止接收一部分不需要的报文&#xff0c;留更多带宽对其他报文进行接收。 // 数据流过滤器&#xff1a;过滤总线数据流&#xff0c;软件过滤。操…

A股风格因子看板 (2023.10 第11期)

该因子看板跟踪A股风格因子&#xff0c;该因子主要解释沪深两市的市场收益、刻画市场风格趋势的系列风格因子&#xff0c;用以分析市场风格切换、组合风格暴露等。 今日为该因子跟踪第11期&#xff0c;指数组合数据截止日2023-09-30&#xff0c;要点如下 近1年A股风格因子检验统…

gin框架初识

先引入gin的包 终端执行 go get -u github.com/gin-gonic/gin 代码 package mainimport ("github.com/gin-gonic/gin""net/http" )func main() {r : gin.Default() //默认的路由引擎r.GET("/book", func(c *gin.Context) {c.JSON(http.Statu…

muduo源码学习base——Exception(带 stack trace 的异常基类)

Exception(带 stack trace 的异常基类&#xff09; 前置ExceptionCurrentThread::stackTrace() 前置 ABI: Application Binary Interface&#xff0c;应用程序二进制接口&#xff0c;可以参考&#xff1a;细谈ABI RTTI type_info: RTTI&#xff1a;Run Time Type Identificatio…

js给一段话,遇到的第一个括号处加上换行符

list.forEach((item,index0)>{const productName item.name;const index productName.indexOf(&#xff08;);if (index -1) {return productName;}const before productName.slice(0, index);const after productName.slice(index);item.namebefore \n after;});

算法学习(七)判断一个二叉树是否为完全二叉树

描述 给定一个二叉树&#xff0c;确定他是否是一个完全二叉树。 完全二叉树的定义&#xff1a;若二叉树的深度为 h&#xff0c;除第 h 层外&#xff0c;其它各层的结点数都达到最大个数&#xff0c;第 h 层所有的叶子结点都连续集中在最左边&#xff0c;这就是完全二叉树。&a…

值改变事件(SMART PLC梯形图FC)

值改变事件在通信速度优化上的应用,请查看下面文章链接: C#winform事件驱动 值改变事件 PLC寄存器值改变_plc数据变化触发条件_RXXW_Dor的博客-CSDN博客Modbus通讯时,设置值发生改变时,我们希望启动一次请求帧,发送写数据帧,这个功能,在C#winform里很容易实现,因为有对…

“唯品会VIP商品搜索API:尊享购物体验,一键获取心仪商品!“

唯品会按关键字搜索VIP商品API是一项面向唯品会VIP用户的API服务&#xff0c;它主要用于在唯品会网站上根据用户指定的关键字快速搜索到VIP商品&#xff0c;并提供商品详情、价格、库存量、评价等信息。这个API的核心功能是为用户提供便捷且准确的搜索服务&#xff0c;让用户能…

探索二次开发途径

一、什么是二次开发&#xff1f; 软件二次开发&#xff0c;也被称为定制开发或应用开发&#xff0c;是指在已有的软件基础上&#xff0c;通过编写自定义代码或应用程序来满足特定需求&#xff0c;扩展现有软件的功能。这种方式可在满足定制需求的同时&#xff0c;减少了开发新…

【会议征稿通知】第二届语言与文化传播国际学术会议(ICLCC 2024)

第二届语言与文化传播国际学术会议&#xff08;ICLCC 2024&#xff09; The 2nd International Conference on Language and Cultural Communication 第二届语言与文化传播国际学术会议&#xff08;ICLCC 2024&#xff09;的目标是将语言与文化传播领域的创新学者和行业专家聚…