Flutter writes a Nas music player (1)
2023-01-05 22:40 ≈ 1.3kWords ≈ 7Minutes

Note

This article was automatically translated using Google

Because the last two months have been boring, I have been tossing my old NAS at home, [set up a music server](https://www.igerm.ee/experience/%E7%AE%A1%E7%90%86 %E7%BC%96%E8%BE%91%E5%88%86%E8%BD%A8%E6%AC%A3%E8%B5%8F%E6%9C%AC%E5%9C%B0%E9 %9F%B3%E4%B9%90%E4%B8%80%E6%9D%A1%E9%BE%99/), but I can’t find a particularly satisfactory player. There is also [Sonixd] on the desktop (https://github.com/jeffvli/sonixd) has a good appearance and no fancy functions, but I am not satisfied with the mobile phone. Only substreamer can be used. But not very pretty.

The most important thing is that these two players do not support lyrics, and they cannot search in fuzzy or simplified or traditional characters. For example, I want to listen to Jay Chou’s “Back Back”, but the name I searched is Back Back, so when I search, I need to switch to traditional Chinese to find it out, which is quite annoying.

My first reaction was to go to Github to find out if there are any ready-made multi-terminal wheels, but the ones I like are all played locally, or there is a music player imitating NetEase Cloud through the API of NetEase Cloud, which may be blocked at any time. Not to mention breaking it, and it doesn’t even support Navidrome or Subsonic’s API. I really can’t find it, so I gritted my teeth and stomped my feet to make one myself!

Subsonic API

Navidrome itself has a native API, but not many, and the author is planning to rewrite, but fortunately he has almost all support for Subsonic’s API, so even if Navidrome itself has no API documentation, look at [Subsonic’s documentation](http: //www.subsonic.org/pages/api.jsp) can also be put on Postman to try

Authentication Server

1672924014867

It’s a bit confusing at first glance, but it’s actually very simple. There are two types of verification: either pass in password, or pass in encrypted (password + six-digit salt value) plus six-digit salt value

First Ping the server for testing (using dio: ^4.0.6 can save a lot of operations)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
try {
var response = await Dio(). get(
_baseUrl +
'rest/ping?v=0.0.1&c=xiumusic&f=json&u=' +
_username + //v is the client version number, c is the name of the client, u is the username, p is the password
'&p=' +
_password,
);
print(response);
if (response. statusCode == 200) {
//There needs to be one more step here, that is, don’t be happy even if code200 is returned, and verify the feedback from the subsonic server
Map _value = response.data['subsonic-response'];
String _status = _value['status'];
//The normal state is ok, I am too lazy to throw it wrong, I check it in the debug, and I will definitely throw it out when it is commercialized, but hey, hehe
if (_status == 'failed') {
return false;
} else {
return true;
}
} else {
return false;
}
} catch (e) {
print(e);
return false;
}

After completing the two-step verification server and Subsonic’s api, you can save the server information. The subsequent apis are obtained using salt values. In fact, it doesn’t matter, they are all free.

Encrypted Login

  1. Use Rondom to write a randomly generated 6-bit character

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    String generateRandomString() {
    final_random = Random();
    const _availableChars =
    'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz1234567890';
    final randomString = List. generate(6,
    (index) => _availableChars[_random.nextInt(_availableChars.length)])
    .join();

    return randomString;
    }
  2. Then encrypt with MD5 after splicing

Save these two characters for later API use

Get directory

The directories in Subsonic are hierarchical from top to bottom like this

  • music folder
  • Artist folder
  • Album
  • songs

However, Navidrome seems to have only one music folder, so I directly use the second-level getIndexes to make my directory master list, and then go down to the album list according to the musician ID

1672925500580

play music

Use ValueNotifier for variable monitoring, do not use provider: ^6.0.5 lightly, although it is really convenient, there will always be various global variables The problem, especially when you rely on him and want to change the code back, the workload simply makes you rewrite it!

In fact, ValueNotifier is also very convenient to use. It can be followed by <int> parameters to specify the type of value, and you can also customize the monitoring data type. I am based on [this article](https://blog. csdn.net/Mr_Tony/article/details/112170758) to operate

  1. Define the data monitor

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

    In theory, it can be placed in any file, but it is recommended to put it in a unified global variable definition file for easy management. Here I pass in the id of the song and filter it with a default value. If it is "1", it will be Do not call the interface to get song information (Null safety is mandatory after flutter2.12, I didn’t adapt to it at first, but now I think it’s good)

  2. Change the value of the data

    activeSongValue. value = _tem["id"];

    You can use it directly, so it seems that it is actually simpler than provider

  3. Monitor data changes

    Use the ValueListenableBuilder construct to construct the listening part

    1
    2
    3
    4
    5
    6
    7
    8
    return ValueListenableBuilder<String>(
    valueListenable: activeSongValue, //monitor
    builder: ((context, value, child) {
    if (value != "1") { // change the value
    _setAudioSource(value); //Directly use id to query and other follow-up work
    }

    return Container()...
  4. Get song information

    After you have the song id, you can use getSong to get the music information, use getCoverArt to get the album image, and then use stream to get the streaming file

  5. Play the music stream

    Using just_audio: ^0.9.31 can greatly speed up the development progress, it is the second-ranked dependencies, the first is audioplayers, but just these days he updated the version , the address of the official document is lost, this is called fate

    It is also extremely simple to use. If you don’t care about the display of controls such as button sliders, it only takes three lines of code from clicking the music to starting playback:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    //Define the component
    final_player = AudioPlayer();
    //The url of splicing stream
    String _url = await getSongStreamUrl(_id);
    // Get the stream and play it
    try {
    await _player.setAudioSource(AudioSource.uri(Uri.parse(_url)));
    _player.play();
    } catch (e) {
    print("Error loading audio source: $e");
    }

The above is the current progress, including the server test on the desktop, and then the directory enters the song level, and then plays the music.

There is still a lot of work in the future, but I have been paying attention to Flutter since the 1.x version. I once complained that Huawei’s engineers are useless if they don’t use Flutter. Just pay attention to the adaptation of the mobile terminal. After finishing writing, it is estimated that a simple adjustment of the mobile terminal will be enough. Currently, it is only for functions)

Here is the Github address, it’s good to be willing to come down and play, but it’s only for development, it’s far from normal use.