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

cpp 复制代码
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中配置所需要的文件访问权限和网络请求权限

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入口文件

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

相关推荐
leluckys8 小时前
flutter 专题 六十四 在原生项目中集成Flutter
flutter
leluckys8 小时前
flutter 专题 一百零四 Flutter环境搭建
flutter
唯鹿8 小时前
AI生成Flutter UI代码实践(一)
人工智能·flutter·ui
仙魁XAN10 小时前
Flutter 学习之旅 之 flutter 有时候部分手机【TextField】无法唤起【输入法软键盘】的一些简单整理
flutter·unity·华为手机·textfield·键盘唤不起
leluckys11 小时前
flutter 专题 五十八 关于Flutter提示Your Xcode project requires migration的错误
flutter
只可远观1 天前
Flutter Dart中的函数参数 默函数的定义 可选参数 箭头函数 匿名函认参数 命名参类数 闭包等
windows·flutter
JarvanMo2 天前
借助FlutterFire CLI实现Flutter与Firebase的多环境配置
前端·flutter
RichardLai882 天前
[Flutter 基础] - Flutter基础组件 - Icon
android·flutter
汤面不加鱼丸2 天前
flutter实践:比例对比线图实现
前端·flutter