flutter开发音乐APP(简单的音乐播放demo)

发布于:2025-05-02 ⋅ 阅读:(17) ⋅ 点赞:(0)

效果如下:

音乐播放界面

锁屏音乐播放展示

主要使用的插件如下

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


网站公告

今日签到

点亮在社区的每一天
去签到