鸿蒙PC迁移:使用Electron`yesplaymusic-ohos` 鸿蒙迁移实战与适配全记录

一、写在前面

开源项目地址:https://atomgit.com/OpenHarmonyPCDeveloper/ohos_yesplaymusic

欢迎在PC社区平台申请新建项目:https://atomgit.com/OpenHarmonyPCDeveloper

环境搭建文章:https://blog.csdn.net/lbcyllqj/article/details/161286249?sharetype=blogdetail\&sharerId=161286249\&sharerefer=PC\&sharesource=lbcyllqj\&spm=1011.2480.3001.8118

这篇文章记录的是 YesPlayMusic 在 OpenHarmony / HarmonyOS Electron 运行环境中的一次迁移和适配过程。

YesPlayMusic 是一个第三方网易云音乐播放器,原始项目基于 Electron + Vue 2 构建。和普通 Web 项目不同,它不是只要页面能打开就算完成迁移。音乐播放器真正要验证的是一整条使用链路:

  • 首页、发现、音乐库、搜索这些页面要能正常切换
  • 本地网易云 API 服务要能启动
  • 歌曲详情、歌单、专辑、歌手信息要能请求
  • 播放、暂停、切歌、进度更新要稳定
  • 外部链接、Last.fm 授权、设置页要符合鸿蒙端使用习惯
  • 窗口按钮、应用图标这些原生壳能力要接到鸿蒙工程里

针对鸿蒙适配,这次没有把它当成一个"静态页面打包"问题处理,而是选择了一条更接近原桌面端运行方式的路线:

保留 YesPlayMusic 原有 Vue 业务界面,通过 OpenHarmony Electron runtime 启动本地 Node 服务和本地 Web 服务,再由鸿蒙窗口加载 http://127.0.0.1:27232

这样做的核心目标,是让 YesPlayMusic 在鸿蒙端不只是"页面展示出来",而是具备一个播放器应用的基本可用状态。

本文会按照实际适配步骤展开:先接入 HAP 工程,再处理构建脚本和运行入口,然后逐步解决本地 API、播放链路、窗口按钮、设置项、外部链接和图标这些问题。

先说一点个人判断:YesPlayMusic 这种项目不能只看"窗口里有没有内容"。首页能显示,只能说明 Vue 页面进来了;搜索能请求、歌曲能播放、设置项不误导用户、窗口按钮能正常显示,才说明鸿蒙端具备了可用基础。

所以这次的策略是把 YesPlayMusic 原有 Vue 界面、本地网易云 API、本地 HTTP 服务和鸿蒙 Electron runtime 串成一条完整链路。后面所有脚本和代码调整,也都围绕这个目标展开:确保资源能进 HAP,确保 API 能被前端访问,确保播放器失败时有恢复逻辑,确保桌面端能力在鸿蒙端有明确边界。


二、项目背景

YesPlayMusic 项目版本为:

text 复制代码
0.4.10

项目描述是:

text 复制代码
A third party music player for Netease Music

原项目主要技术栈包括:

  • Electron
  • Vue 2
  • Vue Router
  • Vuex
  • Howler
  • 本地网易云 API 服务
  • Last.fm 授权
  • Electron IPC
  • 桌面端托盘、快捷键、MPRIS、系统菜单等能力

从目录结构看,原项目的核心代码主要分布在:

text 复制代码
src/
├── api/
├── components/
├── electron/
├── store/
├── utils/
└── views/

其中 src/electronsrc/utils/Player.js 对桌面 Electron 环境依赖较明显。比如 renderer 中会使用 window.require('electron'),播放器状态会同步到托盘和 MPRIS,设置页中也保留了很多桌面平台专属选项。

这类项目迁移到鸿蒙时,不能简单按 Web 项目处理。因为它的运行过程至少包含三层:

  1. Electron 主进程负责窗口、本地服务、IPC 和外部链接。
  2. Vue renderer 负责页面、路由、播放器 UI 和业务状态。
  3. 本地网易云 API 服务负责音乐数据和播放链路。

所以这次适配的重点,是把这三层重新组织到鸿蒙 HAP 工程中。


三、总体变化概览

3.1 新增鸿蒙工程壳

适配后的项目新增:

text 复制代码
ohos_hap/
├── AppScope/
├── electron/
├── web_engine/
└── build-profile.json5

鸿蒙 Electron runtime 最终读取的应用资源目录是:

text 复制代码
ohos_hap/web_engine/src/main/resources/resfile/resources/app

最终同步进去的运行资源大致包括:

text 复制代码
resources/app/
├── main.js
├── package.json
├── ncmModDef.js
├── dist/
└── node_modules/

3.2 新增构建脚本

项目新增:

text 复制代码
scripts/build-ohos-package.js
scripts/build-ohos-hap.js

并在 package.json 中增加:

json 复制代码
{
  "ohos:sync": "OHOS_YESPLAYMUSIC_OUT=ohos_hap/web_engine/src/main/resources/resfile/resources/app node scripts/build-ohos-package.js",
  "ohos:build": "node scripts/build-ohos-hap.js"
}

这两个命令分别负责:

  • ohos:sync:构建 Vue 产物,生成鸿蒙 runtime 入口,同步运行资源和依赖。
  • ohos:build:同步资源、补齐 OpenHarmony 依赖、同步图标、调用 Hvigor 构建 HAP。

3.3 新增鸿蒙 Electron 启动引导

scripts/build-ohos-package.js 会写入:

text 复制代码
resources/app/main.js

这个入口负责:

  • 设置 YESPLAYMUSIC_OPENHARMONY=1
  • 创建 YesPlayMusic 主窗口
  • 启动本地网易云 API 服务
  • 启动本地 Express 静态服务
  • /api 代理到 127.0.0.1:10754
  • 拦截外部链接和 Last.fm 授权窗口
  • 注册窗口控制、设置同步、代理配置等 IPC

四、为什么 YesPlayMusic 不能只按静态页面迁移

很多前端应用迁到鸿蒙 Electron 时,可以选择直接加载 dist/index.html。但 YesPlayMusic 不太适合这么做。

原因很简单:它是播放器,不是展示页。

如果直接把 Vue 构建产物放进 HAP,可能会出现:

  • 页面可以打开,但 /api 请求没有后端承接
  • 首页能显示局部内容,但搜索、歌单、播放请求失败
  • Last.fm 授权无法回到应用内
  • window.open 打开外部链接行为不稳定
  • 设置页还显示桌面端功能,但鸿蒙端实际无法使用
  • 播放失败时没有良好的恢复路径

因此这次没有选择纯静态 file:// 方案,而是在鸿蒙 Electron 主进程中启动一个本地服务:

text 复制代码
http://127.0.0.1:27232

其中:

  • / 加载 Vue 构建后的 dist
  • /api 代理到本地网易云 API
  • /player 暴露当前播放状态

这样做以后,YesPlayMusic 前端仍然运行在熟悉的 HTTP 环境里,路由、资源、接口和播放器状态都更容易稳定下来。

这条路线可以概括成:

dist 前端产物 + 本地网易云 API + Express 同源代理 + 鸿蒙 Electron 窗口。

它比纯静态方案多了一层本地服务,但换来的好处是播放器链路更接近原 Electron 桌面端,也更方便逐步验证首页、搜索、歌单和播放这些核心路径。


五、鸿蒙适配核心路径

5.1 第一步:构建 Vue 产物并修正资源路径

scripts/build-ohos-package.js 首先检查:

text 复制代码
dist/index.html

如果产物不存在,或者发现里面仍然包含根路径资源,就会重新执行构建。

脚本中有一个资源路径检查:

js 复制代码
const hasRootRelativeAssets = file => {
  const html = fs.readFileSync(file, 'utf8');
  return /(?:src|href|content)="\/(?:css|js|img|fonts|favicon\.ico|manifest\.json)/.test(html);
};

构建时会设置:

text 复制代码
PUBLIC_PATH=./
YESPLAYMUSIC_OHOS_BUILD=1
NODE_OPTIONS=--openssl-legacy-provider

这里的几个变量都比较关键:

  • PUBLIC_PATH=./ 保证构建产物可以在鸿蒙 runtime 中正确加载资源。
  • YESPLAYMUSIC_OHOS_BUILD=1 用来告诉前端当前是鸿蒙构建。
  • NODE_OPTIONS=--openssl-legacy-provider 解决 Vue CLI / webpack 4 在较新 Node 环境下的 OpenSSL 兼容问题。

5.2 第二步:给前端注入鸿蒙运行标识

vue.config.js 中增加了鸿蒙构建判断:

js 复制代码
const isOhosBuild = process.env.YESPLAYMUSIC_OHOS_BUILD === '1';

鸿蒙构建时会注入:

js 复制代码
process.env.IS_ELECTRON = true;
process.env.IS_OHOS = true;

这样前端可以明确区分三种环境:

  1. 普通 Web
  2. 桌面 Electron
  3. 鸿蒙 Electron

后续设置页裁剪、播放器 IPC、安全兜底、标题栏行为,都是基于这个判断来做的。

5.3 第三步:生成鸿蒙 runtime 入口

鸿蒙端使用脚本生成的 main.js,而不是直接照搬桌面端主进程。

它会启动本地网易云 API:

js 复制代码
await server.serveNcmApi({
  port: 10754,
  moduleDefs: require('./ncmModDef')
});

再启动本地应用服务:

js 复制代码
expressApp.use('/', express.static(path.join(__dirname, 'dist')));
expressApp.use('/api', expressProxy('http://127.0.0.1:10754'));
appServer = expressApp.listen(27232, '127.0.0.1');

最后创建窗口:

js 复制代码
win.loadURL('http://127.0.0.1:27232/');

这一步完成了从 Vue 页面到本地音乐 API 的闭环。

5.4 第四步:复制运行时依赖

YesPlayMusic 鸿蒙运行时需要一部分 Node 依赖进入 HAP:

js 复制代码
const runtimeDependencyEntrypoints = [
  '@neteaseapireborn',
  'axios',
  'express',
  'express-fileupload',
  'express-http-proxy'
];

脚本会递归解析这些依赖,并复制到:

text 复制代码
resources/app/node_modules

这一步很重要。只复制 dist 没有意义,因为本地 API 服务和 Express 代理都需要运行时依赖。

5.5 第五步:收口 Electron IPC

原项目中不少文件会直接使用:

js 复制代码
window.require('electron')

在桌面端这通常没有问题,但鸿蒙适配时更容易遇到运行时 API 不一致、模块加载顺序不同、某些能力不可用等情况。

因此新增:

text 复制代码
src/utils/electron.js

统一提供:

js 复制代码
getElectron()
getIpcRenderer()
sendIpc()
invokeIpc()
onIpc()

前端发送 IPC 时不再直接访问 window.require,而是先走安全封装。这样即使某些鸿蒙端不支持的 Electron 能力不可用,也不会在页面初始化阶段直接崩掉。

5.6 第六步:调整播放器播放失败恢复逻辑

播放器核心在:

text 复制代码
src/utils/Player.js

鸿蒙端需要处理两个问题:

第一,桌面端的托盘、MPRIS、UnblockNeteaseMusic 等能力不能默认启用。

第二,音频加载失败时不能反复使用同一个缓存音源,否则用户看到的就是"点了播放但没反应"。

适配后做了几件事:

  • 鸿蒙端跳过 MPRIS 和托盘更新。
  • 鸿蒙端不启用 UnblockNeteaseMusic 桌面链路。
  • Howler loaderror 后跳过缓存重新取音源。
  • 同一首歌同一音源失败后进入下一首,避免播放器卡住。
  • 播放失败时给出 toast 提示。

5.7 第七步:裁剪鸿蒙端不适合的设置项

设置页在:

text 复制代码
src/views/settings.vue

适配中增加了:

js 复制代码
isOhos() {
  return process.env.IS_OHOS === true;
},
isDesktopElectron() {
  return this.isElectron && !this.isOhos;
}

鸿蒙端隐藏或固定这些桌面专属能力:

  • 托盘图标主题
  • 音频输出设备选择
  • UnblockNeteaseMusic 配置
  • Discord Rich Presence
  • Linux 自定义标题栏
  • 全局快捷键
  • OS 桌面歌词等能力

同时通过 normalizeOhosSettings() 把不适合鸿蒙端的设置写回安全值:

js 复制代码
enableUnblockNeteaseMusic: false
enableDiscordRichPresence: false
enableGlobalShortcut: false
enableOsdlyricsSupport: false
linuxEnableCustomTitlebar: false
outputDevice: 'default'

这样用户在鸿蒙端看到的是可用配置,而不是一堆点了没有效果的桌面选项。

5.8 第八步:处理窗口按钮和顶部点击区域

窗口按钮不能放在 Vue 页面里模拟。

YesPlayMusic 原本有 Win32 / Linux 自定义标题栏组件,但鸿蒙端应该使用原生窗口装饰。主进程创建窗口时设置:

js 复制代码
frame: true,
resizable: true,
minimizable: true,
maximizable: true,
closable: true,
titleBarStyle: 'default'

并调用:

js 复制代码
setUseNativeFrame(true)
setWindowButtonVisibility(true)
setMinimizable(true)
setMaximizable(true)
setClosable(true)
setResizable(true)

同时,Vue 顶部导航栏在鸿蒙端要禁用桌面 Electron 的拖动区域:

css 复制代码
nav.is-ohos {
  -webkit-app-region: no-drag;
}

这个修正很关键。否则顶部导航可能被当成窗口拖动区域,导致首页、发现、音乐库、搜索、头像菜单这些按钮点击无效。

5.9 第九步:处理外部链接和 Last.fm 授权

新增:

text 复制代码
src/utils/openExternal.js

用于统一处理外部链接:

  • GitHub
  • 网易云专辑 / 歌手 / MV 页面
  • Last.fm 授权页面

鸿蒙端通过 IPC 发给主进程:

js 复制代码
sendIpc('open-url', normalizedUrl)

这里踩过一个比较隐蔽的问题。Vue Router 在 Electron 模式下使用 hash 路由,站内链接会解析成:

text 复制代码
http://127.0.0.1:27232/#/explore
http://127.0.0.1:27232/#/library

如果外链拦截器只判断 http,就会把站内路由也拦截掉,导致顶部"发现""音乐库"点击没反应。最终修成只拦截跨域链接:

js 复制代码
new URL(url, window.location.href).origin !== window.location.origin

主进程也增加保护:如果收到的是 127.0.0.1:27232/#/...,只在主窗口导航,不再新开窗口。

5.10 第十步:同步原项目图标

YesPlayMusic 原项目图标位于:

text 复制代码
build/icons/icon.png
build/icons/512x512.png
build/icons/32x32.png

鸿蒙端应用图标来自:

text 复制代码
ohos_hap/AppScope/resources/base/media/app_icon.png

启动图和小图标还包括:

text 复制代码
startIcon.png
product_logo_32.png

scripts/build-ohos-hap.js 中增加 syncOhosIcons(),每次构建 HAP 前自动同步:

text 复制代码
app_icon.png         <- build/icons/512x512.png
product_logo_32.png <- build/icons/32x32.png
startIcon.png       <- build/icons/icon.png 生成 192x192

这样可以保证桌面图标、启动图标和项目原 Electron 图标保持一致。


六、问题困难解决

这次 YesPlayMusic 适配中最核心的问题是:

如何把一个依赖 Electron 主进程、本地 API 和播放器状态的桌面音乐应用,整理成鸿蒙端可用的 HAP 应用。

具体看,主要有下面几类问题。

6.1 困难一:页面能打开不代表接口能用

YesPlayMusic 不适合只加载静态 dist

因为播放器数据来自本地网易云 API,如果没有本地 API 服务,首页、搜索、歌单和播放都会受影响。

解决方式是在 runtime main.js 中启动:

  • @neteaseapireborn/api/server
  • Express 静态服务
  • /api 本地代理

最终前端只需要访问同源 /api,不用关心鸿蒙端本地服务细节。

6.2 困难二:桌面 Electron API 不能散落在 renderer 里

原项目中有大量 Electron 调用散落在组件和工具文件中。鸿蒙端如果直接照搬,容易出现某个 API 不存在就导致页面初始化失败。

解决方式是新增 src/utils/electron.js,把 IPC 和 Electron 模块访问统一收口。

这样播放器、设置页、标题栏组件、store 插件都可以通过 sendIpc()invokeIpc()onIpc() 访问主进程,失败时也有统一兜底。

6.3 困难三:播放器失败恢复要更清楚

音乐播放器的失败状态比普通工具应用更常见。歌曲可能因为版权、格式、缓存音源失效等原因无法播放。

适配中调整了 Player.js

  • loaderror 后跳过缓存重取音源
  • 同一音源失败后不无限重试
  • 失败时提示用户
  • 必要时自动进入下一首

这样用户不会卡在一首不可播放的歌上。

6.4 困难四:鸿蒙端需要主动做功能减法

托盘、MPRIS、Linux 自定义标题栏、Discord Rich Presence、全局快捷键等能力,在传统桌面端合理,但在当前鸿蒙 Electron 运行环境中并不适合作为默认能力展示。

适配方式不是强行模拟,而是:

  1. 桌面端继续保留。
  2. 鸿蒙端隐藏对应设置项。
  3. store 中把不适合的设置规范化为安全值。
  4. 播放器逻辑中跳过这些桌面集成。

这能减少用户点击无效功能的情况。

6.5 困难五:窗口按钮必须回到原生壳

最初容易误判的一点是,把最大化、最小化、关闭按钮做在 Vue 页面里。

但鸿蒙 Electron 的窗口按钮属于原生窗口装饰,应该在壳层处理。

最终做法是在主进程和鸿蒙 WebAbility 相关逻辑中启用原生窗口按钮,并在 Vue 侧关闭鸿蒙端自定义标题栏。

同时处理 -webkit-app-region: drag 对顶部导航点击的影响,让顶部导航回到普通可点击区域。

6.6 困难六:外链拦截不能误伤站内 hash 路由

外部链接适配时,最容易出现的问题是把站内路由也拦截掉。

YesPlayMusic 使用 hash 路由后,#/explore 这类站内地址在浏览器里也是完整 HTTP URL。如果只判断 http,就会导致路由点击失效。

最终判断条件改为"跨域 http/https 才拦截",同时主进程对本地同源 URL 做保护,不再打开新窗口。

6.7 运行链路确认顺序

YesPlayMusic 在鸿蒙端的运行链路可以按这个顺序确认:

  1. dist/index.html 是否生成。
  2. resources/app/dist 是否同步成功。
  3. resources/app/main.js 是否生成。
  4. resources/app/node_modules 是否包含运行时依赖。
  5. 本地网易云 API 是否启动。
  6. Express 本地服务是否监听 127.0.0.1:27232
  7. BrowserWindow 是否加载首页。
  8. 顶部导航、搜索、头像菜单是否可点击。
  9. 歌曲是否能播放、失败是否能恢复。
  10. HAP 图标和窗口按钮是否符合鸿蒙端表现。

这个顺序能把问题从 HAP、主进程、前端资源、本地服务、播放器行为逐层拆开。


七、构建与运行方式

同步资源:

bash 复制代码
cd ~/XM/yesplaymusic-ohos
npm run ohos:sync

构建 HAP:

bash 复制代码
cd ~/XM/yesplaymusic-ohos
env NODE_OPTIONS=--openssl-legacy-provider OHOS_YESPLAYMUSIC_FORCE_REBUILD=1 npm run ohos:build

HAP 输出目录:

text 复制代码
ohos_hap/electron/build/default/outputs/default/

当前生成的 HAP:

text 复制代码
electron-default-unsigned.hap

安装和启动可以使用:

bash 复制代码
hdc shell aa force-stop io.yesplaymusic.ohos
hdc uninstall io.yesplaymusic.ohos
hdc shell mkdir -p data/local/tmp/yesplaymusic-ohos
hdc file send ohos_hap/electron/build/default/outputs/default/electron-default-unsigned.hap data/local/tmp/yesplaymusic-ohos
hdc shell bm install -p data/local/tmp/yesplaymusic-ohos
hdc shell aa start -a EntryAbility -b io.yesplaymusic.ohos

安装成功后可以看到:

text 复制代码
install bundle successfully.
start ability successfully.

八、适配成果

yesplaymusic-ohos 已经完成:

  • OpenHarmony Electron HAP 工程接入
  • Vue 前端产物构建与同步
  • 本地 Express 服务承载前端页面
  • 本地网易云 API 服务启动
  • /api 同源代理
  • Electron IPC 安全封装
  • 播放器音源获取和失败恢复优化
  • 首页、发现、音乐库、搜索主路径适配
  • 设置页鸿蒙端能力裁剪
  • Last.fm 授权窗口处理
  • 网易云外部链接处理
  • 鸿蒙原生窗口按钮适配
  • YesPlayMusic 原图标同步到 AppScope
  • 命令行构建 HAP

暂时不强行迁移的能力包括:

  • 托盘图标
  • MPRIS
  • Discord Rich Presence
  • 全局快捷键
  • Linux 自定义标题栏
  • UnblockNeteaseMusic 桌面端完整能力

这部分能力并不是完全不能做,而是当前更适合先从鸿蒙端隐藏或禁用,避免用户看到无效选项。


九、功能验证结果

这次适配按播放器实际使用路径做了验证。

第一组是应用启动和页面:

  • 应用可以安装并启动
  • 首页能正常显示
  • 发现页面能切换
  • 音乐库入口正常
  • 搜索框可以输入并跳转搜索页
  • 头像菜单可以打开设置、登录或 GitHub 入口

第二组是音乐数据和播放:

  • 首页推荐数据可加载
  • 搜索结果可显示
  • 歌曲点击后底部播放器更新
  • 播放、暂停、切歌、进度显示正常
  • 不可播放歌曲会提示并跳过

第三组是鸿蒙端交互:

  • 原生最大化、最小化、关闭按钮可见
  • 顶部导航不再被拖拽区域吞掉点击
  • 外部链接不会误伤站内路由
  • Last.fm 授权能通过新窗口打开
  • 设置页不再展示明显不适合鸿蒙端的桌面选项

第四组是构建和图标:

  • npm run ohos:build 可以生成 HAP
  • HAP 可通过 hdc 安装
  • 应用图标替换为 YesPlayMusic 原图标
  • 启动图标和小图标同步到鸿蒙资源目录

十、结语

yesplaymusic-ohos 的适配重点,是把一个依赖 Electron 主进程和本地音乐 API 的桌面播放器,迁移到鸿蒙 Electron runtime 中稳定运行。

这次实践中最重要的不是简单把页面塞进 HAP,而是把几条链路打通:

  • HAP 工程承载应用资源
  • Electron 主进程启动本地服务
  • Vue 前端通过同源 /api 获取音乐数据
  • 播放器能处理成功和失败状态
  • 桌面端能力在鸿蒙端有明确边界
  • 原生窗口按钮和应用图标接入鸿蒙工程

从结果看,YesPlayMusic 已经完成鸿蒙端的基础可用适配。它保留了播放器的核心体验,也没有强行展示不适合鸿蒙端的桌面能力。

这类迁移项目的经验是:先把运行链路做稳,再逐步补平台能力。对音乐播放器来说,真正重要的是用户能打开、能搜索、能播放、能切换、能退出,而不是一开始就把桌面端所有边角能力全部搬过去。

相关推荐
鸽芷咕1 小时前
鸿蒙PC迁移:Minitube Qt YouTube 客户端鸿蒙PC适配全记录
qt·华为·harmonyos
小鹏linux2 小时前
鸿蒙PC使用 Electron 迁移:Beekeeper Studio 适配全记录
华为·electron·harmonyos
前端不太难2 小时前
Agent First:鸿蒙 App 的下一代 AI Runtime 架构
人工智能·架构·harmonyos
轻口味2 小时前
轻规划鸿蒙开发实战11:自研 Haptic Canvas 粒子系统,纯 ArkUI 高性能烟花渲染与性能避
华为·harmonyos·鸿蒙
狼哥16862 小时前
学习卡片案例新特性接入
ui·华为·harmonyos
Davina_yu2 小时前
JSON数据处理:字符串序列化与反序列化实战(20)
harmonyos·鸿蒙·鸿蒙系统
小鹏linux2 小时前
鸿蒙PC迁移:Tesseract OCR C++ 三方库鸿蒙适配全记录
c++·ocr·harmonyos
JOJO数据科学2 小时前
DbGate Electron 鸿蒙 PC 适配全记录:从桌面数据库工具到 OpenHarmony HAP
数据库·electron·harmonyos