效果如下:
音乐播放界面
锁屏音乐播放展示
主要使用的插件如下
just_audio : 是一个功能丰富的音频播放器,适用于Android、iOS、macOS、Web、Linux和Windows平台。它提供了多种功能,包括从URL、文件、资产或字节流读取音频,支持DASH、HLS等流媒体协议,处理ICy元数据,以及更多高级特性如播放列表管理、无缝播放、循环播放、随机播放等。
just_audio_background : 使用该插件可以让应用在后台播放音频并且响应来自锁屏界面、媒体通知、头戴耳机、AndroidAuto/CarPlay 或 智能手表的控制。
audio_service :负责音乐的后台、通知栏展示功能
dio:用于网络请求
permission_handler:系统权限处理
device_info_plus:用于获取当前设备的信息
flutter_screenutil:适配屏幕尺寸和屏幕密度的 Flutter 插件
pubspec.yaml
name: simple_music_app
description: "A new Flutter project."
# The following line prevents the package from being accidentally published to
# pub.dev using `flutter pub publish`. This is preferred for private packages.
publish_to: 'none' # Remove this line if you wish to publish to pub.dev
# The following defines the version and build number for your application.
# A version number is three numbers separated by dots, like 1.2.43
# followed by an optional build number separated by a +.
# Both the version and the builder number may be overridden in flutter
# build by specifying --build-name and --build-number, respectively.
# In Android, build-name is used as versionName while build-number used as versionCode.
# Read more about Android versioning at https://developer.android.com/studio/publish/versioning
# In iOS, build-name is used as CFBundleShortVersionString while build-number is used as CFBundleVersion.
# Read more about iOS versioning at
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
# In Windows, build-name is used as the major, minor, and patch parts
# of the product and file versions while build-number is used as the build suffix.
version: 1.0.0
environment:
sdk: '>=3.3.0 <4.0.0'
# Dependencies specify other packages that your package needs in order to work.
# To automatically upgrade your package dependencies to the latest versions
# consider running `flutter pub upgrade --major-versions`. Alternatively,
# dependencies can be manually updated by changing the version numbers below to
# the latest version available on pub.dev. To see which dependencies have newer
# versions available, run `flutter pub outdated`.
dependencies:
flutter:
sdk: flutter
just_audio: ^0.9.34
just_audio_background: ^0.0.1-beta.13
audio_service: ^0.18.15
dio: ^5.7.0
flutter_screenutil: ^5.9.3
permission_handler: ^11.3.1
device_info_plus: ^11.2.0
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^1.0.8
dev_dependencies:
flutter_test:
sdk: flutter
# The "flutter_lints" package below contains a set of recommended lints to
# encourage good coding practices. The lint set provided by the package is
# activated in the `analysis_options.yaml` file located at the root of your
# package. See that file for information about deactivating specific lint
# rules and activating additional ones.
flutter_lints: ^4.0.0
get: ^4.6.6
fluttertoast: ^8.2.4
cached_network_image: ^3.3.1
# For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec
# The following section is specific to Flutter packages.
flutter:
# The following line ensures that the Material Icons font is
# included with your application, so that you can use the icons in
# the material Icons class.
uses-material-design: true
# To add assets to your application, add an assets section, like this:
# assets:
# - images/a_dot_burr.jpeg
# - images/a_dot_ham.jpeg
# An image asset can refer to one or more resolution-specific "variants", see
# https://flutter.dev/to/resolution-aware-images
# For details regarding adding assets from package dependencies, see
# https://flutter.dev/to/asset-from-package
# To add custom fonts to your application, add a fonts section here,
# in this "flutter" section. Each entry in this list should have a
# "family" key with the font family name, and a "fonts" key with a
# list giving the asset and other descriptors for the font. For
# example:
# fonts:
# - family: Schyler
# fonts:
# - asset: fonts/Schyler-Regular.ttf
# - asset: fonts/Schyler-Italic.ttf
# style: italic
# - family: Trajan Pro
# fonts:
# - asset: fonts/TrajanPro.ttf
# - asset: fonts/TrajanPro_Bold.ttf
# weight: 700
#
# For details regarding fonts from package dependencies,
# see https://flutter.dev/to/font-from-package
下载所需要的插件后,首先要在AndroidManifest.xml中配置所需要的文件访问权限和网络请求权限
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.ryanheise.just_audio_example"
tools:ignore="Instantiatable">
<!-- 配置网络权限 -->
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<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.WAKE_LOCK"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK"/>
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/>
<!-- for below android 13-->
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" tools:ignore="ScopedStorage" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<!-- for above android 13-->
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />
<application
android:label="simple_music_app"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher"
android:usesCleartextTraffic="true"
android:enableOnBackInvokedCallback="true">
<activity
android:name="com.ryanheise.audioservice.AudioServiceActivity"
android:exported="true"
android:launchMode="singleTop"
android:taskAffinity=""
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
<!-- <meta-data
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme"
/> -->
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<!-- 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"/>
<service
android:name="com.ryanheise.audioservice.AudioService"
android:foregroundServiceType="mediaPlayback"
android:exported="true" tools:ignore="Instantiatable">
<intent-filter>
<action android:name="android.media.browse.MediaBrowserService" />
</intent-filter>
</service>
<receiver
android:name="com.ryanheise.audioservice.MediaButtonReceiver"
android:exported="true" tools:ignore="Instantiatable">
<intent-filter>
<action android:name="android.intent.action.MEDIA_BUTTON" />
</intent-filter>
</receiver>
</application>
<!-- Required to query activities that can process text, see:
https://developer.android.com/training/package-visibility and
https://developer.android.com/reference/android/content/Intent#ACTION_PROCESS_TEXT.
In particular, this is used by the Flutter engine in io.flutter.plugin.text.ProcessTextPlugin. -->
<queries>
<intent>
<action android:name="android.intent.action.PROCESS_TEXT"/>
<data android:mimeType="text/plain"/>
</intent>
</queries>
</manifest>
main.dart入口文件
// ignore_for_file: depend_on_referenced_packages
import 'package:flutter/material.dart';
import 'package:just_audio/just_audio.dart';
import 'package:just_audio_background/just_audio_background.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:simple_music_app/common/music_service.dart';
import 'package:simple_music_app/common/utils.dart';
import 'package:simple_music_app/components/float_music_player.dart';
import 'package:simple_music_app/components/song_everyday_recommond.dart';
import 'package:simple_music_app/control/music_control.dart';
import 'package:simple_music_app/model/common_model.dart';
import 'package:simple_music_app/model/everyday_recommond_res.dart';
import 'package:get/get.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
Get.put(MusicController());
final MusicController audioController = Get.put(MusicController());
await storagePermission();
await JustAudioBackground.init(
androidNotificationChannelId: 'com.ryanheise.bg_demo.channel.audio',
androidNotificationChannelName: 'Audio playback',
androidNotificationOngoing: true,
);
audioController.playerStateStream();
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return ScreenUtilInit(
designSize: const Size(375, 812), // 设计稿尺寸(单位:逻辑像素)
minTextAdapt: true, // 允许字体根据屏幕缩放
splitScreenMode: true, // 支持分屏模式
builder: (context, child) {
return const MaterialApp(
debugShowCheckedModeBanner: false,
home: MyHomePage(),
);
},
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key});
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
final MusicController audioController = Get.put(MusicController());
List<EverydayRecommondSongList> everyRecommondList = [];
Future<void> _fetchEveryRecommond() async {
fetchEveryRecommond().then((everyRecommondRes) {
if (everyRecommondRes.status == 1) {
setState(() {
everyRecommondList = everyRecommondRes.data.songList;
});
}
});
}
void handlePlayMusic(index) async{
if (audioController.storePlaylist.isNotEmpty) {
var currentHash = audioController.storePlaylist[index].audioHash;
audioController.updateCurrentHash(currentHash);
}
// print(everyRecommondList[index].singerinfo[0].id);
var listTemp = List<AudioSource>.filled(
everyRecommondList.length,
AudioSource.uri(Uri.parse(''),
tag: MediaItem(
id: '0',
title: '歌曲加载中',
artUri: Uri.parse(
'https://pic.downk.cc/item/5f9e1f771cd1bbb86bf49c90.jpg'),
album: 'music')),
);
List<PlayList> storePlaylist = everyRecommondList
.map((song) => PlayList(
filesize128: song.filesize128,
filesize320: song.filesize320,
filesizeFlac: song.filesizeFlac,
audioHash320: song.hash320,
audioHashFlac: song.hashFlac,
songName: song.songname,
songDuration: song.timeLength,
hasQuality: 1,
singerName: song.authorName,
coverUrl: song.sizableCover
.replaceAll('{size}', '720')
.replaceAll('http', 'https'),
audioId: song.songid,
audioHash: song.hash,
mixsongid: song.albumAudioId))
.toList();
audioController.updatePlayList(storePlaylist);
audioController.updateCurrentPlaylist(listTemp);
String audioHash = everyRecommondList[index].hash;
await audioController.play(index, audioHash);
}
@override
void initState() {
_fetchEveryRecommond();
// TODO: implement initState
super.initState();
}
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(
colorScheme: const ColorScheme.dark(),
scaffoldBackgroundColor: Colors.black,
appBarTheme: const AppBarTheme(
backgroundColor: Colors.transparent,
elevation: 0,
scrolledUnderElevation: 0)),
home: Scaffold(
appBar: AppBar(
backgroundColor: Colors.black,
title: const Text('Music'),
),
body: Stack(
children: [
Column(
children: [
Expanded(
child: ListView.builder(
padding: const EdgeInsets.all(10).r,
itemCount: everyRecommondList.length,
itemBuilder: (context, index) {
return SongEverydayRecommond(
posterUrl: everyRecommondList[index]
.sizableCover
.replaceAll('/{size}', '')
.replaceAll('http', 'https'),
sognName: everyRecommondList[index].songname,
singerName: everyRecommondList[index].authorName,
songTag: everyRecommondList[index]
.recSubCopyWrite
.toString(),
handelClickFn: () {
handlePlayMusic(index);
},
);
},
),
),
Obx(
() {
if (audioController.currentIndex.value != 100000) {
return SizedBox(
height: 60.h,
);
} else {
return Container();
}
},
),
],
),
Obx(
() {
if (audioController.currentIndex.value != 100000) {
return const FloatMusicPlayer();
} else {
return Container();
}
},
),
],
)),
);
}
}
项目lib