flutter 仿网易云音乐(2)
作者:互联网
如果喜欢请点点star
这里今天说一下项目的播放器选择
查看了一下pub.dev 上主流的播放器插件主要有audioplayers和just_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