Flutter写个Nas音乐播放器(一)

2023-01-05 22:40≈ 229字≈ 2分钟

因为最近两个月比较无聊,在家里各种折腾我的老NAS,搭了一个音乐服务器,但是找不到特别满意的播放器,在桌面端还有个Sonixd颜值比较高,而且没有花里胡哨的功能,但是手机端的话就不甚满意,只有substreamer可以用,但是不太好看。

最关键的是这两个播放器都不支持歌词,而且还不能模糊查找以及简繁体查找。比如我想听周杰伦的退后,但是我挂搜出来的名字是退後,那我搜索的时候还需要切到繁体去才能搜出来,这就比较烦了。

我第一反应是去Github上找找有没有现成的多端轮子,但是看得上的都是本地播放的,或者有个仿网易云音乐播放器是通过网易云的API搞的,随时可能会被断掉不说,也不支持连Navidrome或者Subsonic的API。实在是找不到了,一咬牙一跺脚自己搞一个吧!

Subsonic API

Navidrome本身有原生的API,但是不多,而且作者正在计划重写,不过好在他针对Subsonic的API做了几乎全部支持,所以即使Navidrome本身没有API文档,看着Subsonic的文档也能放到Postman上试试看

验证服务器

1672924014867

猛看稍微比较懵逼,其实很简单,验证分两种:要么传入密码,要么传入加密后的(密码+六位盐值)再加上六位盐值

先Ping一下服务器进行测试(使用dio: ^4.0.6可以省却不少操作)

 1  try {
 2    var response = await Dio().get(
 3      _baseUrl +
 4          'rest/ping?v=0.0.1&c=xiumusic&f=json&u=' +   
 5          _username +  //v是客户端版本号,c是客户端的名称,u是用户名,p是密码
 6          '&p=' +
 7          _password,
 8    );
 9    print(response);
10    if (response.statusCode == 200) {
11      //这里需要多做一步,就是即便返回code200也别高兴,还要验证subsonic服务器的反馈
12      Map _value = response.data['subsonic-response'];
13      String _status = _value['status'];
14      //正常的状态是ok,我自己懒得抛错了,自己在debug里面查,产品化的时候肯定要抛出去,不过嘛,嘿嘿
15      if (_status == 'failed') {
16        return false;
17      } else {
18        return true;
19      }
20    } else {
21      return false;
22    }
23  } catch (e) {
24    print(e);
25    return false;
26  }

做完两步验证服务器和Subsonic的api都没有问题了就可以保存服务器信息了,后面的api都使用盐值来获取,其实无所谓啦,都是免费的

加密登陆

  1. 利用Rondom写一个随机生成6位字符

     1String generateRandomString() {
     2  final _random = Random();
     3  const _availableChars =
     4      'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz1234567890';
     5  final randomString = List.generate(6,
     6          (index) => _availableChars[_random.nextInt(_availableChars.length)])
     7      .join();
     8
     9  return randomString;
    10}
    
  2. 然后拼接之后用MD5加密

把这两个字符保存起来用于后面API的使用

获取目录

Subsonic里面的目录是这样自上而下分级的

  • 音乐文件夹
  • 音乐人文件夹
  • 专辑
  • 歌曲

不过Navidrome好像就只有一个音乐文件夹,所以我直接用了第二级的getIndexes来做我的目录主列表,然后根据音乐人ID再下到专辑列表

1672925500580

播放音乐

使用ValueNotifier来进行变量监听,不要轻易使用provider: ^6.0.5 ,虽然说真的方便但是全局变量总会出现各种各样的问题,尤其是当你依赖他之后,想要再把代码改回去,那工作量简直想让你重新写一遍!

其实ValueNotifier使用起来也挺方便的,他后面可以带<int>参数来指定值的类型,也可以自定义监控数据类型,我是根据这篇文章来操作的

  1. 定义数据监控器

    ValueNotifier<String> activeSongValue = ValueNotifier<String>("1");

    理论上这个放在哪个文件里面都可以,不过建议放到统一的全局变量定义文件里便于管理,我这里传入歌曲的id,给一个默认值做过滤,如果是"1"就不调用接口获取歌曲信息了(flutter2.12后的Null safety强制要求,刚开始不适应,现在觉得挺好的)

  2. 改变数据的值

    activeSongValue.value = _tem["id"];

    直接使用就可以,这样看来其实比provider还要简单

  3. 监听数据的变化

    使用ValueListenableBuilder构造来构造监听部分

    1return ValueListenableBuilder<String>(
    2        valueListenable: activeSongValue, //监控器
    3        builder: ((context, value, child) {
    4          if (value != "1") {         //改变值
    5            _setAudioSource(value);  //直接使用id进行查询等后续工作
    6          }
    7
    8          return Container()...
    
  4. 获取歌曲信息

    有了歌曲id之后,就可以使用getSong来获取音乐信息,使用getCoverArt来获取专辑图片,再使用stream来获取流文件

  5. 播放音乐流

    使用just_audio: ^0.9.31 可以大大加速开发进度,它是排名第二的dependencies,第一是audioplayers,不过正好这几天他更新了版本,官方文档的地址给搞没了,这就叫做缘分

    使用起来也极其简单,如果不管按钮滑块之类的控制展现,从点击音乐到开始播放就三行代码:

     1//定义组件
     2final _player = AudioPlayer();
     3//拼接流的url
     4String _url = await getSongStreamUrl(_id);
     5//获取流并播放
     6    try {
     7      await _player.setAudioSource(AudioSource.uri(Uri.parse(_url)));
     8      _player.play();
     9    } catch (e) {
    10      print("Error loading audio source: $e");
    11    }
    

以上就是目前的进度,包含了桌面端的服务器测试、然后目录进到歌曲级,然后播放音乐。

后面还有很多工作,不过Flutter是我从1.x的版本就在关注的,一度吐槽华为的工程师不用Flutter就是个废物,Google更是不遗余力的投入,我相信会很容易完成(写桌面端的时候就留意移动端的适配了,都写完估计简单调整一下移动端就可以了,目前只做功能)

这里是Github的地址,愿意下来玩也挺好,不过仅用于开发,离正常用还差很远。