基于electron 和 react,我做了一个桌面音乐播放器🎵

背景

最近正在做一个开源的项目--pear-rec,pear-rec 是一个跨平台的截图、录屏、录音、录像软件。截图功能上篇文章已经讲过了,如果没有看过的可以去看看这篇文章------------手把手教你,用electron实现截图软件,并且项目的框架搭建也可以参考上一篇文章,开发到现在我想要一个音乐(录音)预览、播放功能,说干就干,下面我介绍一下我的开发过程。

工具

  • nodejs
  • pnpm
  • electron
  • vite
  • react
  • antd
  • aplayer

aplayer介绍

官网:aplayer.js.org/

npmjs: www.npmjs.com/package/apl...

github: github.com/DIYgod/APla...

aplayer是一款基于HTML5的音乐播放器插件,它使用简单方便,并且功能强大。以下是aplayer的一些特点和功能:

  1. 多格式支持:aplayer支持播放多种音频格式,包括MP3、WAV、FLAC等。
  2. 自适应布局:aplayer可以根据播放器容器的大小自动调整布局,适应不同的屏幕尺寸和设备类型。
  3. 音乐控制:aplayer提供了常见的音乐播放控制功能,包括播放/暂停、上一首/下一首、调节音量等。
  4. 播放列表:aplayer支持创建和管理播放列表,可以通过API动态添加或删除歌曲。
  5. 自定义样式:aplayer可以通过自定义CSS样式来美化播放器的外观,包括歌曲封面、进度条等。
  6. 丰富的事件和回调:aplayer提供了多个事件和回调函数,可以在播放器的不同状态下进行操作和处理。
  7. 响应式设计:aplayer支持响应式设计,可以在移动设备上提供更好的用户体验。
  8. 支持插件扩展:aplayer支持通过插件扩展功能,可以添加自定义的控制按钮、特效等。

总的来说,aplayer是一个功能全面、易于使用且高度可定制的音乐播放器插件,适用于各种网页音乐播放的需求。它提供了丰富的API和事件,使开发者可以灵活地实现自己的音乐播放器界面和交互效果。

实现

其实实现音乐播放很简单,只需要两个页面就可以了,一个是主页面,一个是音乐播放页面。主页面主要功能是获取音乐位置或是要播放音频的,音乐播放页面就是展示音乐和播放音乐的页面。接下来,我们分两步实现音乐播放器的功能。

web 端实现

一、 路由配置

js 复制代码
import Home from "@/pages/home";
import ViewAudio from "@/pages/viewAudio";

<Routes>
    <Route path="/" element={<Home />}></Route>
    <Route path="/viewAudio" element={<ViewAudio />}></Route>
</Routes>

二、 主页面

可以看到主页面有一个选择卡片,点击卡片,可以选择音乐的位置或要播放的音频。

js 复制代码
import React, { useState, useImperativeHandle, forwardRef } from "react";
import { BsMusicNoteBeamed } from "react-icons/bs";
import { Button, Card, Upload } from "antd";
import { useNavigate } from "react-router-dom";
import type { UploadProps } from "antd/es/upload/interface";

const ViewAudioCard = forwardRef((props: any, ref: any) => {
	const navigate = useNavigate();
	useImperativeHandle(ref, () => ({
		handleViewAudio,
	}));

	const uploadProps: UploadProps = {
		accept: "audio/*",
		name: "file",
		multiple: false,
		showUploadList: false,
		customRequest: () => {},
		beforeUpload: (file) => {
			if (window.electronAPI) {
				window.electronAPI.sendVaOpenWin({ url: file.path });
			} else {
				const url = window.URL.createObjectURL(file);
				navigate(`/viewAudio?url=${encodeURIComponent(url)}`);
			}
			return false;
		},
	};

	function handleViewAudio(e) {
		window.electronAPI
			? window.electronAPI.sendVaOpenWin()
			: navigate("/viewAudio");
		e.stopPropagation();
	}

	return (
		<Upload {...uploadProps}>
			<Card
				title="查看音频"
				hoverable
				bordered={false}
				extra={
					<Button type="link" onClick={handleViewAudio}>
						打开
					</Button>
				}
				style={{ maxWidth: 300 }}
			>
				<div className="cardContent">
					<BsMusicNoteBeamed rev={undefined} />
				</div>
			</Card>
		</Upload>
	);
});

export default ViewAudioCard;

三、 音乐页面

可以看到这是一个播放列表页面。要基于aplayer制作一个页面播放器,你可以按照以下步骤进行:

  1. 安装 aplayer
css 复制代码
npm install aplayer --save
  1. 引入aplayer模块
javascript 复制代码
import 'APlayer/dist/APlayer.min.css';
import APlayer from 'APlayer';
  1. 创建一个容器标签,例如div,给它一个id,以便在js中使用。例如:
bash 复制代码
<div id="player"></div>
  1. 在你的js文件中,实例化aplayer对象,并将其附加到容器上。例如:
css 复制代码
var ap = new APlayer({
    container: document.getElementById('player'),
    autoplay: true,
    audio: [{
        name: 'Song Name',
        artist: 'Artist Name',
        url: 'http://www.example.com/song.mp3',
        cover: 'http://www.example.com/cover.jpg'
    }]
});

在这个例子中,我们使用了一个audio对象来定义一个歌曲,你可以根据自己的需求添加更多的歌曲。

  1. 通过aplayer的API可以控制播放器的行为,例如:
scss 复制代码
ap.play(); // 播放音乐
ap.pause(); // 暂停音乐
ap.volume(0.5); // 设置音量
// 更多的控制选项请参考aplayer的文档。

这样,你就可以在页面上实现基于aplayer的播放器了。你可以根据自己的需求,进行样式和交互的定制。

electron 端实现

现在我们已经在web端实现了音乐播放等功能了,接下来就是在electron 端实现了,实现逻辑一样,也是两个页面,一个主页面,一个播放页面。

  1. 创建一个播放页面
js 复制代码
function createViewAudioWin(search?: any): BrowserWindow {
	viewAudioWin = new BrowserWindow({
		title: "pear-rec 视频预览",
		icon: ICON,
		autoHideMenuBar: true, // 自动隐藏菜单栏
		webPreferences: {
			preload,
		},
	});

	if (url) {
		// electron-vite-vue#298
		viewAudioWin.loadURL(url + `#/viewAudio?url=${search?.url || ""}`);
		// Open devTool if the app is not packaged
		// viewAudioWin.webContents.openDevTools();
	} else {
		viewAudioWin.loadFile(indexHtml, {
			hash: `/viewAudio?url=${search?.url || ""}`,
		});
	}

	viewAudioWin.once("ready-to-show", async () => {
		viewAudioWin?.show();
	});

	return viewAudioWin;
}

function openViewAudioWin(search?: any) {
	if (!viewAudioWin || viewAudioWin?.isDestroyed()) {
		viewAudioWin = createViewAudioWin(search);
	}
	viewAudioWin.show();
}

function closeViewAudioWin() {
	viewAudioWin?.close();
	viewAudioWin = null;
}
  1. 获取当前音频目录下的所有音频
js 复制代码
// 获取所有音频
async function getAudios(audioUrl: any) {
	let audios = await getAudiosByAudioUrl(audioUrl);
	return audios;
}
js 复制代码
// 遍历当前文件夹
function getAudiosByAudioUrl(audioUrl: string) {
  const directoryPath = path.dirname(audioUrl);
  const files = fs.readdirSync(directoryPath); // 读取目录内容
  let audios: any[] = [];
  let index = 0;
  files.forEach(file => {
    const filePath = path.join(directoryPath, file);
    if (isAudioFile(filePath)) {
      const fileName = path.basename(filePath);
      if(filePath == audioUrl) {
        audios.unshift({ url: `pearrec:///${filePath}`, name: fileName, cover: "./imgs/music.png" });
      } else {
        audios.push({ url: `pearrec:///${filePath}`, name: fileName, cover: "./imgs/music.png" });
      }
      index++;
    }
  });
  return audios;
}
scss 复制代码
// 判断是否是音频
function isAudioFile(filePath: string): boolean {
  const ext = path.extname(filePath).toLowerCase();
  return ['.mp3', '.wav', '.aac', '.ogg', '.flac', '.aiff', 'aif', '.m4a', '.alac', '.ac3'].includes(ext);
}
  1. 与web交互

ipcMain 文件

js 复制代码
ipcMain.on("va:open-win", (e, search) => {
        viewAudioWin.closeViewAudioWin();
        viewAudioWin.openViewAudioWin(search);
});

ipcMain.handle("va:get-audios", async (e, audioUrl) => {
        const audios = await viewAudioWin.getAudios(audioUrl);
        return audios;
});

ipcMain.handle("va:set-historyAudio", async (e, audioUrl) => {
        store.setHistoryAudio(audioUrl);
});

preload/electronAPI 文件

js 复制代码
sendVaOpenWin: (search?: any) => ipcRenderer.send("va:open-win", search),
invokeVaGetAudios: (audioUrl: any) =>
        ipcRenderer.invoke("va:get-audios", audioUrl),
sendVaSetHistoryAudio: (audioUrl: any) =>
        ipcRenderer.send("va:set-historyAudio", audioUrl),

其实逻辑不复杂,大家可以自己动手做起来。

总结

文章写到这里基本结束了,简单回顾下文章的内容。

  • Q: 有源码吗?

当然有,地址如下:github.com/027xiguapi/...,有兴趣的话可以大家一起探讨,同时也欢迎大家forkstar

相关推荐
m0_748236118 分钟前
Calcite Web 项目常见问题解决方案
开发语言·前端·rust
Watermelo61720 分钟前
详解js柯里化原理及用法,探究柯里化在Redux Selector 的场景模拟、构建复杂的数据流管道、优化深度嵌套函数中的精妙应用
开发语言·前端·javascript·算法·数据挖掘·数据分析·ecmascript
m0_7482489422 分钟前
HTML5系列(11)-- Web 无障碍开发指南
前端·html·html5
m0_7482356133 分钟前
从零开始学前端之HTML(三)
前端·html
一个处女座的程序猿O(∩_∩)O2 小时前
小型 Vue 项目,该不该用 Pinia 、Vuex呢?
前端·javascript·vue.js
hackeroink6 小时前
【2024版】最新推荐好用的XSS漏洞扫描利用工具_xss扫描工具
前端·xss
迷雾漫步者7 小时前
Flutter组件————FloatingActionButton
前端·flutter·dart
向前看-8 小时前
验证码机制
前端·后端
燃先生._.9 小时前
Day-03 Vue(生命周期、生命周期钩子八个函数、工程化开发和脚手架、组件化开发、根组件、局部注册和全局注册的步骤)
前端·javascript·vue.js