把 Mineradio 移植到 macOS:全屏修复与 DMG 打包记录

Mineradio 是一款基于 Electron 的沉浸式音乐播放器,视觉和交互体验都做得很棒,但官方只提供了 Windows 版。前段时间抽空把它适配到了 macOS,并打包成了 .dmg 安装包。这篇文章记录一下踩过的坑和最终方案。


背景

Mineradio 用 Electron 构建——也就是说底层是 Chromium + Node.js——按理说天生跨平台。项目里也确实有一段 macOS 的判断逻辑:

1
2
// 第 1288 行
if (process.platform !== 'darwin') app.quit();

这行代码的意思是”非 Windows 就退出”,给 macOS 留了个明确的入口开关。抱着试试看的心态关掉这个判断,跑了一下 npm start,窗口真的弹出来了。但全屏按钮点了没反应。

于是开始了适配之旅。


全屏修复:最折腾的一环

根因

Electron 窗口如果同时设置了 transparent: trueframe: false(透明 + 无边框),在 macOS 上调用 setFullScreen(true)静默失败。这是 Electron 在 macOS 上的一个已知限制,因为原生全屏动画依赖窗口管理器的某些特性,透明无边框窗口不满足条件。

Mineradio 恰好就是这种窗口:

1
2
3
4
5
mainWindow = new BrowserWindow({
transparent: true, // ← 透明背景
frame: false, // ← 无边框
// ...
});

解决方案

macOS 提供了一个替代方法叫 setSimpleFullScreen()。它不依赖原生全屏动画,透明无边框窗口也能正常使用。唯一的区别是它不会触发系统级的全屏转场动画(就是 macOS 那个带 Mission Control 飞入效果的切换),但实际效果完全一致——窗口占满整个屏幕。

改动一共涉及四处地方:

1. 进入全屏时判断平台

1
2
3
4
5
6
7
8
if (process.platform === 'darwin') {
win.setSimpleFullScreen(true);
// 手动铺满屏幕
const display = screen.getDisplayMatching(win.getBounds());
win.setBounds(display.bounds, false);
} else {
win.setFullScreen(true);
}

2. 退出全屏时同样走 macOS 分支

1
2
3
4
5
if (process.platform === 'darwin' && win.isSimpleFullScreen()) {
win.setSimpleFullScreen(false);
applyWindowedBounds(win);
return;
}

3. Escape 键退出的判断条件

原来的 isFullScreen() 在 simple fullscreen 模式下返回 false,所以要多加一个 isSimpleFullScreen() 的判断。

4. HTML 全屏退出时避免与简单全屏冲突

当视频等 HTML 元素退出全屏时,如果窗口本身也处于 simple fullscreen 状态,不要重置窗口尺寸。

改完之后全屏功能完全正常,包括窗口大小恢复和 Escape 键退出都跟原生体验一致。


打包成 .dmg

生成 macOS 图标

项目里只有 Windows 的 .ico 和一张 build/icon.png。macOS 需要 .icns 格式,可以用系统自带的 sipsiconutil 从 PNG 生成:

1
2
3
4
5
6
7
8
9
10
11
# 创建 iconset 目录
mkdir -p build/icon.iconset

# 用 sips 生成各种尺寸
for size in 16 32 64 128 256 512 1024; do
sips -z $size $size build/icon.png \
--out build/icon.iconset/icon_${size}x${size}.png
done

# 转换为 .icns
iconutil -c icns build/icon.iconset -o build/icon.icns

electron-builder 配置

package.jsonbuild 字段里添加 macOS 和 DMG 配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
"mac": {
"executableName": "Mineradio",
"icon": "build/icon.icns",
"category": "public.app-category.music",
"hardenedRuntime": true,
"gatekeeperAssess": false,
"target": [{ "target": "dmg", "arch": ["arm64", "x64"] }]
},
"dmg": {
"title": "${productName} ${version}",
"icon": "build/icon.icns",
"contents": [
{ "x": 130, "y": 220 },
{ "x": 410, "y": 220, "type": "link", "path": "/Applications" }
]
}

绕过 dmg-builder 的坑

在国内网络环境下,electron-builder 的 dmg-builder 工具经常下载失败(404 错误)。折腾了几次之后发现不如分两步走:

  1. 先用 electron-builder --mac dir 生成 .app 目录包
  2. 再用系统自带的 hdiutil 手动制作 DMG

手动生成 DMG 的命令很简单:

1
2
3
4
hdiutil create -volname "Mineradio 1.0.10" \
-srcfolder dist/mac-arm64/Mineradio.app \
-ov -format UDZO \
Mineradio-1.0.10-arm64.dmg

最终产物约 141MB,拖到 Applications 文件夹就能用。

首次启动

因为没有 Apple 开发者签名,macOS 会弹”无法验证开发者”的提示。解决办法跟其他未签名应用一样——右键点”打开”即可,只需要确认一次,后续正常双击运行。


macOS 上哪些功能不可用

以下是几个依赖 Windows 专属 API 的功能,在 macOS 上无法工作:

功能 原因 影响
桌面壁纸模式 使用了 Windows WorkerW API 将播放器嵌入桌面壁纸层 无法将播放器设为壁纸
桌面歌词鼠标穿透 使用 Windows GetAsyncKeyState 轮询中键 桌面歌词的锁定切换不可用
自动创建桌面快捷方式 使用 Windows .lnk 文件 API 无影响,拖到 Applications 即可

其他核心功能全部正常:

  • 音乐播放、搜索、歌单管理
  • 网易云音乐登录与播放
  • QQ 音乐音源补充
  • 歌词显示与桌面歌词(仅穿透不可用)
  • 粒子视觉、Emily 播放态
  • 3D 歌单架
  • 天气电台
  • 更新检测

总结

这次适配比预想的简单,大部分时间花在全屏问题的排查上。Electron 应用在 macOS 上跑起来的门槛其实很低,真正的坑往往藏在那些听起来人畜无害的 API 组合里——比如 transparent + frame: false 不能全屏这种事情,文档里不会特别提醒你。

最终适配的代码和构建配置我都放在了 Mineradio-macOS 这个 repo 里,有需要可以直接拿去用。