flutter开发音乐APP(简单的音乐播放demo)
效果如下:
音乐播放界面
锁屏音乐播放展示
主要使用的插件如下
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.0environment: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: flutterjust_audio: ^0.9.34just_audio_background: ^0.0.1-beta.13audio_service: ^0.18.15dio: ^5.7.0flutter_screenutil: ^5.9.3permission_handler: ^11.3.1device_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.8dev_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.0get: ^4.6.6fluttertoast: ^8.2.4cached_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" /><applicationandroid:label="simple_music_app"android:name="${applicationName}"android:icon="@mipmap/ic_launcher"android:usesCleartextTraffic="true" android:enableOnBackInvokedCallback="true"><activityandroid: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-dataandroid: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"/><serviceandroid: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><receiverandroid: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 andhttps://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_packagesimport '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.@overrideWidget 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});@overrideState<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);}@overridevoid initState() {_fetchEveryRecommond();// TODO: implement initStatesuper.initState();}@overrideWidget 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