其他分享
首页 > 其他分享> > flutter 仿网易云音乐(2)

flutter 仿网易云音乐(2)

作者:互联网

gitee项目地址     github项目地址

如果喜欢请点点star

这里今天说一下项目的播放器选择

查看了一下pub.dev 上主流的播放器插件主要有audioplayersjust_audio

额外提一嘴android 和 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>仿网易云</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>

主要是http协议 至于ios的我也不懂 反正我配置了不得所以在网上瞎找了一下并且将网易云http改换成https才能播放,如果有哪位大哥懂的可以交流一下

两种播放器我都在本项目中使用过,一开始选择用audioplayers,操作也简便,但是考虑到搭配audio_service实现后台控制音乐,我琢磨了一段时间audioplayers发现我太会使用o(╥﹏╥)o,所以选择了与audio_service匹配的just_audio,通过audio_service的官方实例差不多可以使用,官方给的教程实例

根据官方文档的介绍需要安装的依赖有 just_audio、audio_service、get_it

创建目录 services、notifiers文件 page_manager.dart

先从services中先说service_locator.dart

// 初始化音频背景处理

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_it包,主要是可以全局去控制,具体我也不懂,我也是看官方是咧

AudioPlayerHandler 第一个是创建audio_service

PlaylistRepository 第二是创建初始化的播放列表 主要是给后续加载缓存中的上次的播放列表 

PageManager 第三个是主要用于控制audio_service 和 just_audio播放音频

// 播放列表

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;
  }
}

这个就是 playlist_repository.dart

fetchInitialPlaylist 是用来初始化播放列表,这里暂时没有用到

fetchAnotherSong 是用来返回一个song音频文件

这里创建了一个song来保存每个音频文件 

// 构造class 歌曲

class Song {
  int id; // 歌曲id
  String name; // 歌曲名字
  String artists; // 艺术家
  String picUrl; // 歌曲图片
  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

首先说一下我理解到的逻辑  MediaItem 是指当前正在播放的歌曲 / queue 是指播放列表

所以当更新时需要修改mediaitem的参数 播放列表改变时需要改变queue列表

再说一下后台播放几个按钮对应触发的函数 播放 play()  pause()  seek() skipToNext()  skipToPrevious()  stop() 这几个最好不要在里面做其他的处理

由于官方的just_audio自带的下一首逻辑没有带有网易云的心动模式,所以没有用自带的

不用这个的还有一个原因就是这是我需要获取音频url,没办法直接setAudioSource,如果你们有更好的办法,也可以教教我

然后自定义了几个我需要用到的方法

abstract class AudioPlayerHandler implements AudioHandler {
  // 添加公共方法
  Future<void> changeQueueLists(List<MediaItem> list, {int index = 0});
  // 改变播放列表
  Future<void> readySongUrl();
  // 获取歌曲url
  Future<void> playIndex(int index);
  // 从下标播放
  Future<void> addFmItems(List<MediaItem> mediaItems, bool isAddcurIndex);
  // 私人fm
}

 这里初始化audio_service

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

然后创建自定义的audio_handler

class MyAudioHandler extends BaseAudioHandler
    with SeekHandler
    implements AudioPlayerHandler {
  final _player = AudioPlayer(); // 播放器
  final _playlist = ConcatenatingAudioSource(children: []); // 播放列表
  final _songlist = <Song>[]; // 这个是播放列表
  int _curIndex = 0; // 播放列表索引

  MyAudioHandler() {
    // 初始化
    // _loadEmptyPlaylist(); // 加载播放列表
    _notifyAudioHandlerAboutPlaybackEvents(); // 背景状态更改
    _listenForDurationChanges(); // 当时间更改时更新背景
    _listenPlayEnd();
    // _listenForCurrentSongIndexChanges(); // 这个也是改背景
  }

  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("错误:$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() {
    // 当时间发送变化时(也就是意味着歌曲发送变化) 这个是更新时间
    _player.durationStream.listen((duration) {
      // final index = _player.currentIndex; // 当前播放下标
      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) {
      // 判断当前歌曲的位置是否是处于最后一位

      _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);
    }
  }

  // 私人Fm的添加
  @override
  Future<void> addFmItems(List<MediaItem> mediaItems, bool isadd) async {
    final audioSource = mediaItems.map(_futterSongItem);
    if (_songlist.length > 0) {
      // 判断当前歌曲的位置是否是处于最后一位

      _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 {
    // 这里是替换播放列表
    final audioSource = mediaitems.map(_futterSongItem);
    // _playlist.clear();
    // _playlist.addAll(audioSource.toList()); // 添加到播放列表

    _songlist.clear();
    _songlist.addAll(audioSource.toList());
    _curIndex = index; // 更换了播放列表,将索引归0

    // notify system
    queue.value.clear();
    final newQueue = queue.value..addAll(mediaitems);
    queue.add(newQueue); // 添加到背景播放列表
  }

  @override
  Future<void> playIndex(int index) async {
    // 接收到下标
    _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) {
      // 判断当前歌曲的位置是否是处于最后一位
      _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 {
    // 这里是获取歌曲url
    var song = this._songlist[_curIndex];
    String url = await getSongUrl({"id": '${song.id}'});
    if (url.isNotEmpty) {
      // 加载音乐
      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');
      }
      // 这里需要重新更新一次
      final playlist = queue.value;
      if (_curIndex == null || playlist.isEmpty) return;
      mediaItem.add(playlist[_curIndex]);

      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 {
    // 当触发播放下一首
    if (_curIndex >= _songlist.length - 1) {
      _curIndex = 0;
    } else {
      _curIndex++;
    }
    // 然后触发获取url
    readySongUrl();
    final model = getIt<PageManager>();
    print('触发播放下一首');
    if (model.isPersonFm.value) {
      // 如果是私人fm
      print('私人fm========$_curIndex');
      if (_curIndex == _songlist.length - 1) {
        // 判断如果是最后一首
        print('触发');
        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 舒适化播放器 _playlist 这个已经废弃没有用到 _songlist 播放列表  _curIndex 播放索引

  MyAudioHandler() {
    // 初始化
    // _loadEmptyPlaylist(); // 加载播放列表
    _notifyAudioHandlerAboutPlaybackEvents(); // 背景状态更改
    _listenForDurationChanges(); // 当时间更改时更新背景
    _listenPlayEnd();
    // _listenForCurrentSongIndexChanges(); // 这个也是改背景
  }

我就简单说说上面的代码有少许注释

readySongUrl    是我自定义的在play之前触发的加载音频url 

  @override
  Future<void> readySongUrl() async {
    // 这里是获取歌曲url
    var song = this._songlist[_curIndex];
    String url = await getSongUrl({"id": '${song.id}'});
    if (url.isNotEmpty) {
      // 加载音乐
      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');
      }
      // 这里需要重新更新一次
      final playlist = queue.value;
      if (_curIndex == null || playlist.isEmpty) return;
      mediaItem.add(playlist[_curIndex]);

      play(); // 播放
    }
  }

如何在页面上控制播放

// 全局处理

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>([]); // 播放列表
  final progressNotifier = ProgressNotifier(); // 时间进度条
  // final repeatButtonNotifier = RepeatButtonNotifier();
  final isFirstSongNotifier = ValueNotifier<bool>(true); // 是不是第一首歌
  final playButtonNotifier = PlayButtonNotifier();
  final isLastSongNotifier = ValueNotifier<bool>(true); // 是不是最后一首歌
  final isShuffleModeEnabledNotifier = ValueNotifier<bool>(false);
  final _audioHandler = getIt<AudioPlayerHandler>();
  final isPersonFm = ValueNotifier<bool>(false); // 当前是否是私人fm
  final waitPersonFm = ValueNotifier<bool>(false);

  // 初始化
  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() {
    // 监听播放状态
    _audioHandler.playbackState.listen((playbackState) {
      final isPlaying = playbackState.playing;
      final processingState = playbackState.processingState;
      print(processingState);
      if (processingState == AudioProcessingState.loading ||
          processingState == AudioProcessingState.buffering) {
        // 加载中
        playButtonNotifier.value = ButtonState.loading;
      } else if (!isPlaying) {
        // 没有播放
        playButtonNotifier.value = ButtonState.paused;
      } else if (processingState != AudioProcessingState.completed) {
        // 播放中
        playButtonNotifier.value = ButtonState.playing;
      } else if (isPlaying) {
        // playButtonNotifier.value = ButtonState.playing;
      } else {
        // 重置
        // print('重置触发');
        // _audioHandler.seek(Duration.zero);
        // _audioHandler.pause();
      }
    });
  }

  // 更新播放时间
  void _listenToCurrentPosition() {
    AudioService.position.listen((position) {
      // print('播放时间$position');
      final oldState = progressNotifier.value;
      progressNotifier.value = ProgressBarState(
        current: position,
        buffered: oldState.buffered,
        total: oldState.total,
      );
    });
  }

  // 更新缓冲位置
  void _listenToBufferedPosition() {
    _audioHandler.playbackState.listen((playbackState) {
      final oldState = progressNotifier.value;
      progressNotifier.value = ProgressBarState(
        current: oldState.current,
        buffered: playbackState.bufferedPosition,
        total: oldState.total,
      );
    });
  }

  // 更新总时长
  void _listenToTotalDuration() {
    _audioHandler.mediaItem.listen((mediaItem) {
      final oldState = progressNotifier.value;
      progressNotifier.value = ProgressBarState(
        current: oldState.current,
        buffered: oldState.buffered,
        total: mediaItem?.duration ?? Duration.zero,
      );
    });
  }

  // 更新歌曲显示最新的歌曲
  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) {
    //   // 如果这个是私人Fm的话 当播放的这首歌是最后一首歌的时候,遇到获取新的歌曲添加到歌单
    //   print(
    //       'object============${isLastSongNotifier.value}===========${waitPersonFm.value}');
    //   if (isLastSongNotifier.value && !waitPersonFm.value) {
    //     waitPersonFm.value = true;
    //     print('触发===============');
    //     this.getPersonFmList();
    //   }
    // }
  }

  // 改变是否是私人Fm
  void changeIsPersonFm(bool personFm) {
    isPersonFm.value = personFm;
  }

  // 获取私人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) {
      // 执行播放
      resumePlay();
    } else {
      // 执行暂停
      pasue();
    }
  }

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

  void seek(Duration position) => _audioHandler.seek(position); // 时间跳转
  void previous() => _audioHandler.skipToPrevious(); // 上一首
  void next() => _audioHandler.skipToNext(); // 下一首
  void repeat() {}
  void shuffle() {}
  // 添加一首歌
  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);
  }

// 添加一个列表到播放列表
  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();
  }
}

标签:网易,音乐,value,final,mediaItem,Future,id,curIndex,flutter
来源: https://blog.csdn.net/weixin_45660666/article/details/122535011