还在为听歌繁琐而烦恼?耗时两个半月,打造一款属于自己的在线音乐软件

342 阅读5分钟

前期提要

经常👋🐟的小伙伴应该都喜欢在上班忙碌之际,打开自己电脑上的音乐软件,想要听听自己喜欢的音乐来抚慰自己受伤的灵魂,然而正当我们怀着热情的期待去点击播放时,去出现了以下提示...

image.png

由于QQ音乐会员种类太多(比如开完绿钻还有超级会员、音乐包等等),于是好不容易狠下心去了隔壁网易云音乐开了尊贵的黑胶VIP会员,然而又提示我😭 image.png 说多了都是泪,我们总是在多个事物之间权衡利弊,却又无法得到最想要的结果,于是我不再受制于人,决心打造一款属于自己的在线音乐软件,废话少说,撸起袖子就是开干!

软件介绍

首页

先来给咱软件(以下简称YMS或YMSV2)露个面,当前已经迭代至V2V1可以访问以往项目主页查看YourMusicstation-V1 , 对V2感兴趣的小伙伴可以前往官网下载试用(公益项目,内容完全免费,只为寻找一个灵魂共鸣的你)
321上链接:ymsv2.top image.png 这里解释一下YMS为什么是绑定网易云音乐,是因为本人曾是云音乐的重度用户,热爱云音乐的氛围以及每日推荐,适合我这种兴趣驱动的刚需开发者。

软件使用

首先打开后,就是我们的软件首页,本人钟爱卡片式设计,于是统一采用这种方式来写组件,整个软件未使用任何第三方UI框架(因为太过臃肿,不符合本人极简美的设计原则,内置业务组件只需要做到它需要做的事情就是perfect👍,精妙~)

image.png 你可以点击任何卡片任何按钮,随时随地播放音乐,包括但不限于文章开头提到的那些音乐

小而美的播放页演示

image.png

Windows任务栏控制

image.png

绑定你的网易云音乐(播放你歌单里的灰色)

image.png

开发历程

技术栈介绍

YMS采用了elctron + vue + vite的技术架构来提升开发效率,本人也是与时俱进,为了技术尝鲜,截至目前统一使用了依赖的latest stable版本

Electron30 + Vue3 + vite5

整体开发下来的感受就两个字:丝滑!比德芙还要丝滑!
得益于vite的HMR优化、按需加载,以及vue3.4之后的AST语法解析升级,项目启动耗时仅需毫秒级

image.png
其中核心技术部分(如何收听你听不到的音乐),这里基于 @unblockneteasemusic 二开,由于原项目已经不再维护,于是自己魔改了一个内部版本并在本地打好依赖补丁,覆盖原本的依赖与项目一同打包发布。

出于版权保护原因,V2项目仓库闭源自用,代码实现细节暂不公开

项目结构

image.png
V2基于electron-vite搭建,结构上更加利于后期维护升级。

难点及实现(部分)

资源缓存策略

  • 缓存占用空间
  • 已缓存数量
  • 设置到期自动删除(3d, 30d, 90d, 180d, 360d, never)

目的:优化客户端读取音频资源的速度以及增强用户的使用体验

前提: 客户端每次请求资源都需要从服务器拉取最新的信息,这段信息大部分是相同无差异的,其中二进制文件(主要是音频,图片URL未设防盗链可暂不存储二进制)是几乎保持不变的,因此对这部分主要内容是必须进行资源缓存的,因此需要一个方案来满足这部分资源缓存的条件,从而实现客户端快速响应音频等大容量资源(尽管开启了预加载,但无法保证每次资源都能够从服务器正确获取,且在某些情况下所获取到的资源过大,如果客户端网络情况不佳以及用户点击进度条跳转,则可能造成明显的卡顿),综上必须实现资源缓存。

  • [❌] - electron-store(无法适应图像音频等二进制文件的存储)

  • [❌] - localStore(无法适应大容量缓存需求)

  • [✔] - IndexedDB(满足存储二进制文件、大容量等特点)

封装IndexedDB实现工具类
  • 对于原生indexedDB,api过于复杂和繁琐,因此选用轻量级第三方框架Dexie.js来进行底层逻辑搭建。
  • Dexie.js支持Typescript语法,因此采用ts进行二次封装工具类YDBUtils.ts
// TrackDB.ts
import Dexie, { Table } from 'dexie';
import { Track } from '@/interfaces/Track';
/**
 * YourMusicStation database
 * Do not quote this class directly, use YDBUtils
 */
class TrackObject extends Dexie {
  Track!: Table<Track>
  constructor() {
    super('YourMusicStationDB');
    // If the table structure changes, the version number needs to be changed
    this.version(1).stores({
      // id and updateTime as indexes, do not store files, large strings in the index
      Track: '&id, updateTime',
    });
  }
}

const TrackDB = new TrackObject();
export default TrackDB;
搭配主工具类实现缓存读取

image.png 创建逻辑:首先读取缓存后进行判空,执行对应分支,此处请求缓存和服务器得到的资源数据结构具有差异,需要进行手动调整,最终结果是得到此次创建的音频url(临时)

此处通过Blob生成的缓存资源链接需要额外注意,搭配howler需要手动显示指定format,否则将无法匹配和正确解码音频文件。

注:解码函数需要每次执行释放资源操作,否则会造成客户端内存泄露。

解码函数
解码函数

创建新的Howler实例

创建新的Howler实例时,第一步需要先将之前所有占用的内存资源尽可能都释放掉,然后才是重新加载(V1的缺陷很明显,在于没有做好内存管理,内存会随着实例数量增加而不断驻留在后台导致卡顿),释放调用Howler全局方法unload

总结

本文章仅作个人技术开发记录分享,更多软件细节可以自行下载体验,本软件仅供有缘者自用,请勿在公共平台传播,软件无任何收费,开箱即用,由于代码特殊性,因此不开源,感兴趣或意见反馈可以前往社区留言txc.qq.com/products/63…