一个基于Webview2的Apple Music客户端

Apple Music On Windows

Apple Music的学生优惠只要5元每月,而且在Apple设备上可以提供无损音质和空间音频等体验,看上去似乎很香。但是当我尝试在Windows平台上使用AM时,却发现我似乎只有两种选择:WebiTunes,后一种以它上古的外观和性能坚决劝退了我,前一种虽然有较为现代化的UI,但是以网页的形式存在很容易让我不小心关掉它,而不知道是网络问题还是其他问题,Web端在某些情况下非常卡顿。此外,无论是Web还是iTunes,都没有我很需要的桌面歌词功能。

直到我在V2ex上发现了Lito,一个基于Apple Music Kit开发的,针对Windows的第三方Apple Music客户端。可惜从这个项目当时的进展来看,似乎和我的需求相差有些远:没有搜索和歌单功能,只能按照专辑列表顺序播放主页推荐歌曲,展示歌手和专辑信息,更别提桌面歌词了。给作者提出了几个Feature Request,但是似乎作者本人不太需要这些功能,看了下技术栈是React/Rust,虽然没有一个是我熟悉的,但是用几天时间改出我需要的功能应该也不难,所以我Fork了一份决定自己动手。

Electron & Webview

好像几乎是一夜之间,随着Electron和Node.js的成熟,所有的前端程序员突然变成了全栈程序员。通过直接在Chromium的Runtime上构建逻辑,Electron绕过了Qt等其他跨端框架在多端实现差异上的血海深坑(然后掉进了捆绑Chrome运行时带来的庞大安装包和巨大内存占用等性能优化深坑),连著名的密码管理软件1Password都抛弃Mac端的原生界面转向Electron(然后被骂惨了XD).

而针对捆绑Chrome运行时导致安装包过大的问题,已经出现了很多替代方案,包括惰性加载Chromium,调用操作系统原生Webview等。

自从Edge弃用了EdgeHTML转投Chromium阵营后,Windows上的原生Webview2已经全面跟进Chromium(版本号甚至比Chrome还要新),已经可以在Windows上替代Electron了。Webview2已经在Win11以上版本全部预装,而且提供用时下载的方案,因此安装包可以精简至MB级别(Lito的安装包仅1.8MB)。同时Webview2允许JS与原生层互相调用函数,看上去是比Electron的IPC不知道高到哪里去了。
当然 由于Webview2是微软维护的项目,因此可以直接自带Widevine DRM的专利代码包,不需要从Chrome里扒了,对于Apple Music这种流媒体平台更为适合。
同时,我一直比较纠结Electron的内存占用问题,目前的体验来看,Webview2解决了安装包臃肿的问题,但是对内存占用似乎无能为力。Lito的内存占用稳定在100-150MB左右。但是我比较网易云音乐,发现网易云稳定在250MB+...相比之下性能似乎也不太重要了不是吗XD

image-20211019221636814

image-20211019224440747

桌面歌词实现?

如果涉及到桌面歌词,在Electron上的一种方式是新创建一个透明窗口来实现歌词显示,缺点当然是性能问题。所以在Lito中我采用了原生方法来完成这个功能,使用Direct2D来直接渲染歌词内容,当然也是轮子。

此外,两个窗口之间的通信也有点纠结,有些项目采用了IPC或者起一个Http的服务器的方式完成,纠结了半天,还是采用了比较传统的Win32 API窗口传递信息的方式。因为两个窗口同属一个父进程,因此这样子是可以实现的,否则若归属不同的进程,可能只能采用Websocket方式了(比如RainMeter的有些项目和iOS越狱项目就不得不采用Web Server的方式....)

最后具体逻辑为React更新歌词=>PostMessage to Webview2=>Webview2 Call Rust Callback fn=>Call Win32 API SendMessageW=>Windows Post Message to Lyrics Window=>D2D Render(这样的性能哪里有好了....)

总之用Rust写C++绑定过来的Win32 API肯定不是啥很好的体验,特别是牵扯到FFI,只能通过CString\CStr的方式将OwnerShip转移到C语言,然后将地址使用SendMessageW发送出去,在另一个窗口的回调中通过地址获取CStr,并获得所有权。(当然用D2D写渲染的函数更加痛苦,好在大部分函数都可以仿照https://github.com/lujjjh/iLyrics这个项目中完成,不过将Windows绑定库从0.19.0升级到0.20.0也花费了很大功夫,比如HResult::fron_win32这种函数莫名其妙就消失了,文档中没有替代函数,也没有废弃标志....

一通折腾总算是能跑了,如下:

image-20211023224212804

Github: https://github.com/lx200916/lito

tag(s): none
show comments · back · home
Edit with markdown

已有 2 条评论

  1. 1

    能看看代码吗 学习一下

    1 November 17th, 2021 at 05:18 pm回复
    1. SaltedFish

      啊抱歉 忘了写在文章里了 我Fork的Repo在这里 https://github.com/lx200916/lito. 但是说句老实话,学习就不必了,React和Rust都不是我熟悉的技术栈,特别是桌面歌词的实现部分非常Buggy.只能属于凑合用的程度.如果你发现Bug也欢迎提出PR或Issue.

      SaltedFish November 17th, 2021 at 11:24 pm回复