flutter imitates Netease cloud music

gitee project address     github project address

If you like, please click star

Let's talk about the player selection of the project today

Check out pub The main player plug-ins on dev are audioplayers and just_audio

Mention the configuration of android and ios

android

AndroidManifest.xml

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.netease_app">
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
    <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.CAMERA" />
    <uses-permission android:name="android.permission.CALL_PHONE" />
    <uses-permission android:name="android.permission.WAKE_LOCK" />
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
   <application
        android:label="netease_app"
        android:icon="@mipmap/ic_launcher"
        android:usesCleartextTraffic="true"
        android:networkSecurityConfig="@xml/network_security_config"
        >
        <activity
            android:name="com.ryanheise.audioservice.AudioServiceActivity"
            android:launchMode="singleTop"
            android:theme="@style/LaunchTheme"
            android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
            android:hardwareAccelerated="true"
            android:supportsPictureInPicture="true"
            android:resizeableActivity="true"
            android:windowSoftInputMode="adjustResize">
            <!-- Specifies an Android theme to apply to this Activity as soon as
                 the Android process has started. This theme is visible to the user
                 while the Flutter UI initializes. After that, this theme continues
                 to determine the Window background behind the Flutter UI. -->
            <meta-data
              android:name="io.flutter.embedding.android.NormalTheme"
              android:resource="@style/NormalTheme"
              />
            <!-- Displays an Android View that continues showing the launch screen
                 Drawable until Flutter paints its first frame, then this splash
                 screen fades out. A splash screen is useful to avoid any visual
                 gap between the end of Android's launch screen and the painting of
                 Flutter's first frame. -->
            <meta-data
              android:name="io.flutter.embedding.android.SplashScreenDrawable"
              android:resource="@drawable/launch_background"
              />
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>
                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity>
        <service android:name="com.ryanheise.audioservice.AudioService">
          <intent-filter>
            <action android:name="android.media.browse.MediaBrowserService" />
          </intent-filter>
        </service>

        <receiver android:name="com.ryanheise.audioservice.MediaButtonReceiver" >
          <intent-filter>
            <action android:name="android.intent.action.MEDIA_BUTTON" />
          </intent-filter>
        </receiver> 
        <!-- Don't delete the meta-data below.
             This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
        <meta-data
            android:name="flutterEmbedding"
            android:value="2" />
    </application>
</manifest>

ios

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
	<dict>
		<key>CFBundleDevelopmentRegion</key>
		<string>$(DEVELOPMENT_LANGUAGE)</string>
		<key>CFBundleExecutable</key>
		<string>$(EXECUTABLE_NAME)</string>
		<key>CFBundleIdentifier</key>
		<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
		<key>CFBundleInfoDictionaryVersion</key>
		<string>6.0</string>
		<key>CFBundleName</key>
		<string>Imitation Netease cloud</string>
		<key>CFBundlePackageType</key>
		<string>APPL</string>
		<key>CFBundleShortVersionString</key>
		<string>$(FLUTTER_BUILD_NAME)</string>
		<key>CFBundleSignature</key>
		<string>????</string>
		<key>CFBundleVersion</key>
		<string>$(FLUTTER_BUILD_NUMBER)</string>
		<key>LSRequiresIPhoneOS</key>
		<true />
		<key>UILaunchStoryboardName</key>
		<string>LaunchScreen</string>
		<key>UIMainStoryboardFile</key>
		<string>Main</string>
		<key>UISupportedInterfaceOrientations</key>
		<array>
			<string>UIInterfaceOrientationPortrait</string>
			<string>UIInterfaceOrientationLandscapeLeft</string>
			<string>UIInterfaceOrientationLandscapeRight</string>
		</array>
		<key>UISupportedInterfaceOrientations~ipad</key>
		<array>
			<string>UIInterfaceOrientationPortrait</string>
			<string>UIInterfaceOrientationPortraitUpsideDown</string>
			<string>UIInterfaceOrientationLandscapeLeft</string>
			<string>UIInterfaceOrientationLandscapeRight</string>
		</array>
		<key>UIViewControllerBasedStatusBarAppearance</key>
		<false />
		<key>UIBackgroundModes</key>
		<array>
			<string>audio</string>
		</array>
		<key>NSAppTransportSecurity</key>
		<dict>
			<key>NSExceptionMinimumTLSVersion</key>
  			<string>TLSv1.0</string>
			<key>NSAllowsArbitraryLoads</key>
    		<true/>
			<key>NSAllowsArbitraryLoadsInWebContent</key>
			<true/>
    		<key>NSAllowsArbitraryLoadsForMedia</key>
    		<true/>
		   	<key>NSExceptionRequiresForwardSecrecy</key>
            <true/>
            <key>NSIncludesSubdomains</key>
            <true/>
            <key>NSTemporaryExceptionAllowsInsecureHTTPLoads</key>
            <true/>
		</dict>
	</dict>
</plist>

It's mainly the http protocol. I don't understand ios. Anyway, I can't configure it, so I looked for it on the Internet and changed Netease cloud http to https to play it. If anyone understands it, you can communicate

I have used both players in this project. At the beginning, I chose to use audiolayers, which is easy to operate, but considering the collocation audio_service To realize the background control of music, I pondered for some time. audioplayers found that I was too good at using o(╥﹏╥) o, so I chose to work with audio_ Just matching service_ Audio, via audio_ The official instance of service can be used almost, Official tutorial examples

According to the official documentation, the dependencies that need to be installed are {just_audio,audio_service,get_it

Create directory services, notifiers and page files_ manager. dart

First say service from services_ locator. dart

// Initialize audio background processing

import 'package:get_it/get_it.dart';
import '../page_manager.dart';
import 'audio_handler.dart';
import 'playlist_repository.dart';

GetIt getIt = GetIt.instance;
Future<void> setupServiceLocator() async {
  getIt.registerSingleton<AudioPlayerHandler>(await initAudioService());
  getIt.registerLazySingleton<PlaylistRepository>(() => DemoPlaylist());
  getIt.registerLazySingleton<PageManager>(() => PageManager());
}

Git is used here_ It packages can be controlled globally. I don't understand the details. I also see the official

AudioPlayerHandler first creates audio_service

The second step of PlaylistRepository is to create an initialized playlist, which is mainly to load the last playlist in the cache later

The third one of PageManager is mainly used to control audio_service and just_audio play audio

// playlist

import 'package:netease_app/model/songs.dart';

abstract class PlaylistRepository {
  Future<List<Map<String, String>>> fetchInitialPlaylist();
  Future<Song> fetchAnotherSong(Song song);
}

class DemoPlaylist extends PlaylistRepository {
  @override
  Future<List<Map<String, String>>> fetchInitialPlaylist(
      {int length = 3}) async {
    return [];
  }

  @override
  Future<Song> fetchAnotherSong(Song song) async {
    return song;
  }
}

This is the playlist_repository.dart

fetchInitialPlaylist is used to initialize the playlist, which is not used here for the time being

fetchAnotherSong is used to return a song audio file

Here, a song is created to save each audio file

// Construct class songs

class Song {
  int id; // Song id
  String name; // Song name
  String artists; // artist
  String picUrl; // Song pictures
  Duration timer;
  Song(this.id, this.timer,
      {this.name = '', this.artists = '', this.picUrl = ''});
  Song.fromJson(Map<String, dynamic> json)
      : id = int.parse(json['id']),
        name = json['name'],
        artists = json['artists'],
        timer = Duration(milliseconds: int.parse(json["timer"])),
        picUrl = json['picUrl'];

  Map<String, dynamic> toJson() => {
        'id': id,
        'name': name,
        'artists': artists,
        'picUrl': picUrl,
        'timer': timer,
      };

  @override
  String toString() {
    return '{"id": "$id", "name": "$name", "artists": "$artists","picUrl": "$picUrl","timer": "${timer.inMilliseconds}"}';
  }
}

audio_handler.dart

First, let's talk about the logic I understand. MediaItem refers to the currently playing song / queue refers to the playlist

Therefore, the parameters of mediaitem need to be modified when updating, and the queue list needs to be changed when the playlist is changed

Let's talk about the functions triggered by the buttons playing in the background. play() pause() seek() skipToNext() skipToPrevious() stop() better not do other processing in it

Due to the official just_ The next logic of audio doesn't have the heart mode of Netease cloud, so it doesn't use its own logic

Another reason why I don't need this is that I need to get the audio url. I can't directly setAudioSource. If you have a better way, you can teach me

Then I customized several methods I need to use

abstract class AudioPlayerHandler implements AudioHandler {
  // Add public method
  Future<void> changeQueueLists(List<MediaItem> list, {int index = 0});
  // Change playlist
  Future<void> readySongUrl();
  // Get song url
  Future<void> playIndex(int index);
  // Play from subscript
  Future<void> addFmItems(List<MediaItem> mediaItems, bool isAddcurIndex);
  // Private fm
}

Audio is initialized here_ service

Future<AudioPlayerHandler> initAudioService() async {
  return await AudioService.init(
    builder: () => MyAudioHandler(),
    config: AudioServiceConfig(
      androidNotificationChannelId: 'com.mycompany.myapp.audio',
      androidNotificationChannelName: 'NetEase cloud music',
      androidNotificationOngoing: true,
      androidStopForegroundOnPause: true,
    ),
  );
}

Then create a custom audio_handler

class MyAudioHandler extends BaseAudioHandler
    with SeekHandler
    implements AudioPlayerHandler {
  final _player = AudioPlayer(); // player
  final _playlist = ConcatenatingAudioSource(children: []); // playlist
  final _songlist = <Song>[]; // This is a playlist
  int _curIndex = 0; // Playlist index

  MyAudioHandler() {
    // initialization
    // _ loadEmptyPlaylist(); //  Load playlist
    _notifyAudioHandlerAboutPlaybackEvents(); // Background status change
    _listenForDurationChanges(); // Update background when time changes
    _listenPlayEnd();
    // _ listenForCurrentSongIndexChanges(); //  This is also the background
  }

  UriAudioSource _createAudioSource(MediaItem mediaItem) {
    return AudioSource.uri(
      Uri.parse(mediaItem.id),
      tag: mediaItem,
    );
  }

  Future<void> _loadEmptyPlaylist() async {
    final session = await AudioSession.instance;
    await session.configure(AudioSessionConfiguration.speech());
    try {
      await _player.setAudioSource(_playlist);
    } catch (e) {
      print("error:$e");
    }
  }

  void _listenPlayEnd() {
    _player.playerStateStream.listen((state) {
      if (state.playing) {
      } else {}
      switch (state.processingState) {
        case ProcessingState.idle:
          break;
        case ProcessingState.loading:
          break;
        case ProcessingState.buffering:
          break;
        case ProcessingState.ready:
          break;
        case ProcessingState.completed:
          skipToNext();
          break;
      }
    });
  }

  void _listenForDurationChanges() {
    // When the time transmission changes (that is, the song transmission changes), this is the update time
    _player.durationStream.listen((duration) {
      // final index = _player.currentIndex; //  Current play subscript
      final newQueue = queue.value;
      if (_curIndex == null || newQueue.isEmpty) return;
      final oldMediaItem = newQueue[_curIndex];
      final newMediaItem = oldMediaItem.copyWith(duration: duration);
      newQueue[_curIndex] = newMediaItem;
      queue.add(newQueue);
      mediaItem.add(newMediaItem);
    });
  }

  void _notifyAudioHandlerAboutPlaybackEvents() {
    _player.playbackEventStream.listen((PlaybackEvent event) {
      final playing = _player.playing;
      playbackState.add(playbackState.value.copyWith(
        controls: [
          MediaControl.skipToPrevious,
          if (playing) MediaControl.pause else MediaControl.play,
          MediaControl.stop,
          MediaControl.skipToNext,
        ],
        systemActions: const {
          MediaAction.seek,
        },
        androidCompactActionIndices: const [0, 1, 3],
        processingState: const {
          ProcessingState.idle: AudioProcessingState.idle,
          ProcessingState.loading: AudioProcessingState.loading,
          ProcessingState.buffering: AudioProcessingState.buffering,
          ProcessingState.ready: AudioProcessingState.ready,
          ProcessingState.completed: AudioProcessingState.completed,
        }[_player.processingState]!,
        shuffleMode: (_player.shuffleModeEnabled)
            ? AudioServiceShuffleMode.all
            : AudioServiceShuffleMode.none,
        playing: playing,
        updatePosition: _player.position,
        bufferedPosition: _player.bufferedPosition,
        speed: _player.speed,
        queueIndex: _curIndex,
      ));
    });
  }

  // void _listenForCurrentSongIndexChanges() {
  //   _player.currentIndexStream.listen((index) {
  // final playlist = queue.value;
  // if (index == null || playlist.isEmpty) return;
  // mediaItem.add(playlist[index]);
  //   });
  // }

  @override
  Future<void> addQueueItems(List<MediaItem> mediaItems) async {
    final audioSource = mediaItems.map(_futterSongItem);
    if (_songlist.length > 0) {
      // Judge whether the current song is in the last position

      _songlist.insertAll(_curIndex + 1, audioSource.toList());

      final newQueue = queue.value..insertAll(_curIndex + 1, mediaItems);
      // _curIndex++;
      queue.add(newQueue);
    } else {
      _songlist.insertAll(_curIndex, audioSource.toList());
      final newQueue = queue.value..insertAll(_curIndex, mediaItems);
      queue.add(newQueue);
    }
  }

  // Addition of private Fm
  @override
  Future<void> addFmItems(List<MediaItem> mediaItems, bool isadd) async {
    final audioSource = mediaItems.map(_futterSongItem);
    if (_songlist.length > 0) {
      // Judge whether the current song is in the last position

      _songlist.insertAll(_curIndex + 1, audioSource.toList());

      final newQueue = queue.value..insertAll(_curIndex + 1, mediaItems);
      if (isadd) {
        _curIndex++;
      }
      queue.add(newQueue);
    } else {
      _songlist.insertAll(_curIndex, audioSource.toList());
      final newQueue = queue.value..insertAll(_curIndex, mediaItems);
      queue.add(newQueue);
    }
  }

  Song _futterSongItem(MediaItem mediaItem) {
    return Song(
      int.parse(mediaItem.id),
      mediaItem.duration!,
      artists: mediaItem.artist ?? '',
      picUrl: mediaItem.extras?["picUrl"] ?? '',
      name: mediaItem.title,
    );
  }

  @override
  Future<void> changeQueueLists(List<MediaItem> mediaitems,
      {int index = 0}) async {
    // Here is the replacement playlist
    final audioSource = mediaitems.map(_futterSongItem);
    // _playlist.clear();
    // _ playlist.addAll(audioSource.toList()); //  Add to Playlist

    _songlist.clear();
    _songlist.addAll(audioSource.toList());
    _curIndex = index; // Changed the playlist and set the index to 0

    // notify system
    queue.value.clear();
    final newQueue = queue.value..addAll(mediaitems);
    queue.add(newQueue); // Add to background playlist
  }

  @override
  Future<void> playIndex(int index) async {
    // Subscript received
    _curIndex = index;
    readySongUrl();
  }

  @override
  Future<void> removeQueueItemAt(int index) async {
    // manage Just Audio
    _playlist.removeAt(index);

    // notify system
    final newQueue = queue.value..removeAt(index);
    queue.add(newQueue);
  }

  @override
  Future<void> addQueueItem(MediaItem mediaItem) async {
    // manage Just Audio
    if (_songlist.length > 0) {
      // Judge whether the current song is in the last position
      _songlist.insert(_curIndex + 1, _futterSongItem(mediaItem));
      final newQueue = queue.value..insert(_curIndex + 1, mediaItem);
      _curIndex++;
      queue.add(newQueue);
    } else {
      _songlist.insert(_curIndex, _futterSongItem(mediaItem));
      final newQueue = queue.value..insert(_curIndex, mediaItem);
      queue.add(newQueue);
    }

    // notify system
  }

  @override
  Future<void> readySongUrl() async {
    // Here is the url to get the song
    var song = this._songlist[_curIndex];
    String url = await getSongUrl({"id": '${song.id}'});
    if (url.isNotEmpty) {
      // Load music
      url = url.replaceFirst('http', 'https');
      try {
        await _player.setAudioSource(AudioSource.uri(
          Uri.parse(url),
          tag: MediaItem(
            id: '${song.id}',
            title: song.name,
            artist: song.artists,
            duration: song.timer,
            artUri: Uri.parse(song.picUrl),
          ),
        ));
      } catch (e) {
        print('error======$e');
      }
      // It needs to be updated again
      final playlist = queue.value;
      if (_curIndex == null || playlist.isEmpty) return;
      mediaItem.add(playlist[_curIndex]);

      play(); // play
    }
  }

  @override
  Future<void> play() async {
    _player.play();
  }

  @override
  Future<void> pause() => _player.pause();

  @override
  Future<void> seek(Duration position) => _player.seek(position);

  @override
  Future<void> skipToNext() async {
    // When triggered, play the next song
    if (_curIndex >= _songlist.length - 1) {
      _curIndex = 0;
    } else {
      _curIndex++;
    }
    // Then trigger to get the url
    readySongUrl();
    final model = getIt<PageManager>();
    print('Trigger to play the next song');
    if (model.isPersonFm.value) {
      // If it's private fm
      print('private fm========$_curIndex');
      if (_curIndex == _songlist.length - 1) {
        // Judge if it's the last song
        print('trigger');
        model.getPersonFmList();
      }
    }
  }

  @override
  Future<void> skipToPrevious() async {
    if (_curIndex <= 0) {
      _curIndex = _songlist.length - 1;
    } else {
      _curIndex--;
    }
    readySongUrl();
  }

  @override
  Future<void> stop() async {
    await _player.stop();
    return super.stop();
  }
}

_ Player comfort player_ The playlist has been abandoned and has not been used_ songlist playlist_ curIndex playback index

  MyAudioHandler() {
    // initialization
    // _ loadEmptyPlaylist(); //  Load playlist
    _notifyAudioHandlerAboutPlaybackEvents(); // Background status change
    _listenForDurationChanges(); // Update background when time changes
    _listenPlayEnd();
    // _ listenForCurrentSongIndexChanges(); //  This is also the background
  }

I'll just talk about the above code with a few comments

readySongUrl # is my customized url for loading audio triggered before play

  @override
  Future<void> readySongUrl() async {
    // Here is the url to get the song
    var song = this._songlist[_curIndex];
    String url = await getSongUrl({"id": '${song.id}'});
    if (url.isNotEmpty) {
      // Load music
      url = url.replaceFirst('http', 'https');
      try {
        await _player.setAudioSource(AudioSource.uri(
          Uri.parse(url),
          tag: MediaItem(
            id: '${song.id}',
            title: song.name,
            artist: song.artists,
            duration: song.timer,
            artUri: Uri.parse(song.picUrl),
          ),
        ));
      } catch (e) {
        print('error======$e');
      }
      // It needs to be updated again
      final playlist = queue.value;
      if (_curIndex == null || playlist.isEmpty) return;
      mediaItem.add(playlist[_curIndex]);

      play(); // play
    }
  }

How to control playback on a page

// Global processing

import 'dart:async';

import 'package:audio_service/audio_service.dart';
import 'package:flutter/foundation.dart';
import 'package:netease_app/http/request.dart';
import 'package:netease_app/model/songs.dart';
import 'package:netease_app/notifiers/play_button_notifier.dart';
import 'package:netease_app/notifiers/play_item_notifier.dart';
import 'package:netease_app/notifiers/progress_notifier.dart';
import 'package:netease_app/services/audio_handler.dart';
import 'package:netease_app/services/playlist_repository.dart';
import 'package:netease_app/services/service_locator.dart';

class PageManager {
  final currentSongTitleNotifier = PlayItemNotifier();
  final playlistNotifier = ValueNotifier<List>([]); // playlist
  final progressNotifier = ProgressNotifier(); // Time progress bar
  // final repeatButtonNotifier = RepeatButtonNotifier();
  final isFirstSongNotifier = ValueNotifier<bool>(true); // Is it the first song
  final playButtonNotifier = PlayButtonNotifier();
  final isLastSongNotifier = ValueNotifier<bool>(true); // Is it the last song
  final isShuffleModeEnabledNotifier = ValueNotifier<bool>(false);
  final _audioHandler = getIt<AudioPlayerHandler>();
  final isPersonFm = ValueNotifier<bool>(false); // Is it currently private fm
  final waitPersonFm = ValueNotifier<bool>(false);

  // initialization
  void init() async {
    await _loadPlaylist();
    _listenToChangesInPlaylist();
    _listenToPlaybackState();
    _listenToCurrentPosition();
    _listenToBufferedPosition();
    _listenToTotalDuration();
    _listenToChangesInSong();
  }

  Future<void> _loadPlaylist() async {
    final songRepository = getIt<PlaylistRepository>();
    final playlist = await songRepository.fetchInitialPlaylist();
    final mediaItems = playlist
        .map((song) => MediaItem(
              id: song['id'] ?? '',
              album: song['album'] ?? '',
              title: song['title'] ?? '',
              extras: {'url': song['url']},
            ))
        .toList();
    _audioHandler.addQueueItems(mediaItems);
  }

  void _listenToChangesInPlaylist() {
    _audioHandler.queue.listen((playlist) {
      if (playlist.isEmpty) {
        playlistNotifier.value = [];
        currentSongTitleNotifier.value = MediaItem(id: '', title: '');
      } else {
        final newList = playlist;
        playlistNotifier.value = newList;
      }
      _updateSkipButtons();
    });
  }

  void _listenToPlaybackState() {
    // Monitor playback status
    _audioHandler.playbackState.listen((playbackState) {
      final isPlaying = playbackState.playing;
      final processingState = playbackState.processingState;
      print(processingState);
      if (processingState == AudioProcessingState.loading ||
          processingState == AudioProcessingState.buffering) {
        // Loading
        playButtonNotifier.value = ButtonState.loading;
      } else if (!isPlaying) {
        // No play
        playButtonNotifier.value = ButtonState.paused;
      } else if (processingState != AudioProcessingState.completed) {
        // Playing
        playButtonNotifier.value = ButtonState.playing;
      } else if (isPlaying) {
        // playButtonNotifier.value = ButtonState.playing;
      } else {
        // Reset
        // print('reset trigger ');
        // _audioHandler.seek(Duration.zero);
        // _audioHandler.pause();
      }
    });
  }

  // Update playback time
  void _listenToCurrentPosition() {
    AudioService.position.listen((position) {
      // print('playback time $position ');
      final oldState = progressNotifier.value;
      progressNotifier.value = ProgressBarState(
        current: position,
        buffered: oldState.buffered,
        total: oldState.total,
      );
    });
  }

  // Update buffer location
  void _listenToBufferedPosition() {
    _audioHandler.playbackState.listen((playbackState) {
      final oldState = progressNotifier.value;
      progressNotifier.value = ProgressBarState(
        current: oldState.current,
        buffered: playbackState.bufferedPosition,
        total: oldState.total,
      );
    });
  }

  // Total update time
  void _listenToTotalDuration() {
    _audioHandler.mediaItem.listen((mediaItem) {
      final oldState = progressNotifier.value;
      progressNotifier.value = ProgressBarState(
        current: oldState.current,
        buffered: oldState.buffered,
        total: mediaItem?.duration ?? Duration.zero,
      );
    });
  }

  // Update songs displays the latest songs
  void _listenToChangesInSong() {
    _audioHandler.mediaItem.listen((mediaItem) {
      currentSongTitleNotifier.value = mediaItem!;
      print('Item$mediaItem');
      _updateSkipButtons();
    });
  }

  void _updateSkipButtons() {
    final mediaItem = _audioHandler.mediaItem.value;
    final playlist = _audioHandler.queue.value;
    if (playlist.length < 2 || mediaItem == null) {
      isFirstSongNotifier.value = true;
      isLastSongNotifier.value = true;
    } else {
      isFirstSongNotifier.value = playlist.first == mediaItem;
      isLastSongNotifier.value = playlist.last == mediaItem;
    }
    // if (isPersonFm.value) {
    //   //If this is a private Fm, when the song played is the last song, it is necessary to obtain a new song and add it to the song list
    //   print(
    //       'object============${isLastSongNotifier.value}===========${waitPersonFm.value}');
    //   if (isLastSongNotifier.value && !waitPersonFm.value) {
    //     waitPersonFm.value = true;
    //     print('trigger ================ ';
    //     this.getPersonFmList();
    //   }
    // }
  }

  // Is the change private Fm
  void changeIsPersonFm(bool personFm) {
    isPersonFm.value = personFm;
  }

  // Get private Fm
  Future<void> getPersonFmList() async {
    Map<String, dynamic> params = {
      "timestamp": '${DateTime.now().microsecondsSinceEpoch}',
    };
    getPersonaFm(params).then((value) {
      if (value["code"] == 200) {
        List<Song> playsongslist = <Song>[];
        value["data"].forEach((element) {
          playsongslist.add(Song(
            element["id"],
            Duration(milliseconds: element["duration"]),
            name: element["name"],
            picUrl: element["album"]["picUrl"],
            artists: createArtistString(element["artists"]),
          ));
        });
        addSongs(playsongslist);
      }
    });
  }

  createArtistString(List list) {
    List artistString = [];
    list.forEach((element) {
      artistString.add(element["name"]);
    });
    return artistString.join('/');
  }

  Future<void> play() async {
    await _audioHandler.readySongUrl();
  }

  Future<void> resumePlay() async {
    await _audioHandler.play();
  }

  sinkProgress(int timer) async {
    final oldState = progressNotifier.value;
    progressNotifier.value = ProgressBarState(
      current: Duration(milliseconds: timer),
      buffered: oldState.buffered,
      total: oldState.total,
    );
  }

  void pasue() {
    _audioHandler.pause();
  }

  void togglePlay() {
    if (playButtonNotifier.value == ButtonState.paused) {
      // Perform playback
      resumePlay();
    } else {
      // Execution pause
      pasue();
    }
  }

  void playInex(int index) async {
    await _audioHandler.playIndex(index);
  }

  void seek(Duration position) => _audioHandler.seek(position); // Time jump
  void previous() => _audioHandler.skipToPrevious(); // Last song
  void next() => _audioHandler.skipToNext(); // Next song
  void repeat() {}
  void shuffle() {}
  // Add a song
  Future<void> add(Song song) async {
    final songRepository = getIt<PlaylistRepository>();

    final songitem = await songRepository.fetchAnotherSong(song);
    final mediaItem = MediaItem(
      id: songitem.id.toString(),
      album: '',
      artist: songitem.artists,
      duration: songitem.timer,
      title: songitem.name,
      artUri: Uri.parse(songitem.picUrl),
      extras: {
        'picUrl': songitem.picUrl,
      },
    );
    _audioHandler.addQueueItem(mediaItem);
  }

// Add a list to the playlist
  Future<void> addSongs(List<Song> songlist, {int index = 0}) async {
    final List<MediaItem> mediaItems = <MediaItem>[];
    songlist.forEach((element) {
      mediaItems.add(MediaItem(
        id: element.id.toString(),
        album: '',
        artist: element.artists,
        duration: element.timer,
        title: element.name,
        artUri: Uri.parse(element.picUrl),
        extras: {
          'picUrl': element.picUrl,
        },
      ));
    });
    await _audioHandler.addQueueItems(mediaItems);
  }

  Future<void> changesonglistplay(List<Song> list, {int index = 0}) async {
    final List<MediaItem> mediaItems = <MediaItem>[];
    list.forEach((element) {
      mediaItems.add(MediaItem(
        id: element.id.toString(),
        album: '',
        artist: element.artists,
        duration: element.timer,
        title: element.name,
        artUri: Uri.parse(element.picUrl),
        extras: {
          'picUrl': element.picUrl,
        },
      ));
    });
    await _audioHandler.changeQueueLists(mediaItems, index: index);
  }

  void remove() {
    final lastIndex = _audioHandler.queue.value.length - 1;
    if (lastIndex < 0) return;
    _audioHandler.removeQueueItemAt(lastIndex);
  }

  Future<void> changelist(list) async {}

  void dispose() {
    _audioHandler.customAction('dispose');
  }

  void stop() {
    _audioHandler.stop();
  }
}

Keywords: Flutter

Added by guidance on Wed, 19 Jan 2022 08:06:34 +0200