import 'dart:async'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:just_audio/just_audio.dart'; import 'package:music_player_miao/common_widget/app_data.dart'; import 'package:music_player_miao/models/universal_bean.dart'; import '../../view_model/home_view_model.dart'; import '../api/api_music_likes.dart'; import '../api/api_collection.dart'; import '../common/download_manager.dart'; import '../common_widget/Song_widegt.dart'; class MusicView extends StatefulWidget { final List songList; final int initialSongIndex; const MusicView({ super.key, required this.songList, required this.initialSongIndex }); @override State createState() => _MusicViewState(); } class _MusicViewState extends State { final homeVM = Get.put(HomeViewModel()); bool _isDisposed = false; AppData appData = AppData(); late int currentSongIndex; late AudioPlayer _audioPlayer; final downloadManager = Get.put(DownloadManager()); // Stream values Duration _duration = Duration.zero; Duration _position = Duration.zero; // Current song info late String artistName; late String musicName; late bool likesnot; late bool collectionsnot; // Song lists List id = []; List song2 = []; List artist = []; List music = []; List likes = []; List collection = []; bool _isLoading = false; Future _fetchSonglistData() async { setState(() { for (int i = 0; i < widget.songList.length; i++) { id.add(widget.songList[i].id); song2.add(widget.songList[i].musicurl); artist.add(widget.songList[i].artist); music.add(widget.songList[i].title); likes.add(widget.songList[i].likes); collection.add(widget.songList[i].collection); } }); } Future _updateCurrentSong() async { // 立即更新UI和歌曲信息 setState(() { _isLoading = true; _position = Duration.zero; _duration = Duration.zero; artistName = artist[currentSongIndex]; musicName = music[currentSongIndex]; likesnot = likes[currentSongIndex]; collectionsnot = collection[currentSongIndex]; }); try { // 在后台停止当前播放 unawaited(_audioPlayer.stop()); // Check for local file first final localSong = downloadManager.getLocalSong(currentSongIndex); final audioSource = localSong != null ? AudioSource.file(localSong.musicurl) : AudioSource.uri(Uri.parse(song2[currentSongIndex])); // Set the audio source and get duration final duration = await _audioPlayer.setAudioSource( audioSource, preload: true, ); if (!_isDisposed) { setState(() { _duration = duration ?? Duration.zero; _isLoading = false; }); } // 开始播放 await _audioPlayer.play(); } catch (e) { print('Error loading audio source: $e'); if (!_isDisposed) { setState(() { _isLoading = false; }); } } } void playerInit() { _audioPlayer = AudioPlayer(); // Initialize with first song artistName = widget.songList[widget.initialSongIndex].artist; musicName = widget.songList[widget.initialSongIndex].title; likesnot = widget.songList[widget.initialSongIndex].likes; collectionsnot = widget.songList[widget.initialSongIndex].collection; // Listen to player events _audioPlayer.positionStream.listen((position) { if (!_isDisposed) { setState(() => _position = position); } }); _audioPlayer.durationStream.listen((duration) { if (!_isDisposed) { setState(() => _duration = duration ?? Duration.zero); } }); _audioPlayer.processingStateStream.listen((state) { if (state == ProcessingState.completed) { playNextSong(); } }); } Widget _buildPlayButton() { return SizedBox( width: 52, height: 52, child: Center( child: _isLoading ? const CircularProgressIndicator( valueColor: AlwaysStoppedAnimation(Color(0xff429482)), strokeWidth: 3.0, ) : IconButton( padding: EdgeInsets.zero, // 移除内边距 constraints: const BoxConstraints( minWidth: 52, minHeight: 52, maxWidth: 52, maxHeight: 52, ), onPressed: playOrPause, icon: _audioPlayer.playing ? Image.asset( "assets/img/music_play.png", width: 52, height: 52, ) : Image.asset( "assets/img/music_pause.png", width: 52, height: 52, ), ), ), ); } void playOrPause() async { if (_audioPlayer.playing) { await _audioPlayer.pause(); } else { await _audioPlayer.play(); } setState(() {}); } void playNextSong() { if (currentSongIndex < widget.songList.length - 1) { currentSongIndex++; } else { currentSongIndex = 0; } _updateCurrentSong(); } void playPreviousSong() { if (currentSongIndex > 0) { currentSongIndex--; } else { currentSongIndex = widget.songList.length - 1; } _updateCurrentSong(); } String formatDuration(Duration duration) { String twoDigits(int n) => n.toString().padLeft(2, "0"); String twoDigitMinutes = twoDigits(duration.inMinutes.remainder(60)); String twoDigitSeconds = twoDigits(duration.inSeconds.remainder(60)); return "$twoDigitMinutes:$twoDigitSeconds"; } Future _initializeAsync() async { await _fetchSonglistData(); await _updateCurrentSong(); } @override void initState() { super.initState(); currentSongIndex = widget.initialSongIndex; playerInit(); _initializeAsync(); } @override void dispose() { _isDisposed = true; _audioPlayer.dispose(); super.dispose(); } void _changeCurrentSong(int index) { if (!_isDisposed) { setState(() { currentSongIndex = index; _updateCurrentSong(); }); } } @override Widget build(BuildContext context) { return Container( decoration: const BoxDecoration( image: DecorationImage( image: AssetImage("assets/img/app_bg.png"), fit: BoxFit.cover, ), ), child: Scaffold( resizeToAvoidBottomInset: false, backgroundColor: Colors.transparent, body: SingleChildScrollView( child: Padding( padding: const EdgeInsets.only(top: 45, left: 10, right: 10), child: Column( children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ IconButton( onPressed: () { Get.back(); }, icon: Image.asset( "assets/img/back.png", width: 25, height: 25, fit: BoxFit.contain, ), ), Row( children: [ IconButton( onPressed: () {}, icon: Image.asset( "assets/img/music_add.png", width: 30, height: 30, ), ), IconButton( onPressed: downloadManager.isDownloading(id[currentSongIndex]) || downloadManager.isCompleted(id[currentSongIndex]) ? null : () async { await downloadManager.startDownload( song: widget.songList[currentSongIndex], context: context, ); }, icon: Obx(() { if (downloadManager.isDownloading(id[currentSongIndex])) { return Stack( alignment: Alignment.center, children: [ SizedBox( width: 32, height: 32, child: CircularProgressIndicator( value: downloadManager.getProgress(id[currentSongIndex]), backgroundColor: Colors.grey[200], valueColor: const AlwaysStoppedAnimation(Color(0xff429482)), strokeWidth: 3.0, ), ), Text( '${(downloadManager.getProgress(id[currentSongIndex]) * 100).toInt()}', style: const TextStyle(fontSize: 12), ), ], ); } return Image.asset( downloadManager.isCompleted(id[currentSongIndex]) ? "assets/img/music_download_completed.png" : "assets/img/music_download.png", width: 30, height: 30, ); }), ), ], ) ], ), const SizedBox( height: 80, ), Center( child: Stack( alignment: Alignment.center, children: [ ClipRRect( borderRadius: BorderRadius.circular(10), child: Image.asset( "assets/img/music_Ellipse.png", width: 320, height: 320, fit: BoxFit.cover, ), ), Positioned( child: ClipRRect( borderRadius: BorderRadius.circular(80), child: Image.network( widget.songList[currentSongIndex].artistPic, width: 230, height: 230, fit: BoxFit.cover, ), ), ) ], ), ), const SizedBox( height: 60, ), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, crossAxisAlignment: CrossAxisAlignment.end, children: [ Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( musicName, style: const TextStyle( fontSize: 20, fontWeight: FontWeight.bold), ), Text( artistName, style: const TextStyle(fontSize: 20), ), ], ), Row( children: [ IconButton( // TODO 该处存在 BUG,影响功能:点赞 // 原因:同收藏功能 onPressed: () async{ setState(() { likesnot = !likesnot; likes[currentSongIndex] = !likes[currentSongIndex]; }); await LikesApiMusic().likesMusic(musicId: id[currentSongIndex], Authorization: AppData().currentToken); }, icon: Image.asset( likesnot ? "assets/img/music_good.png" : "assets/img/music_good_un.png", width: 29, height: 29, ), ), IconButton( // TODO 该处存在 BUG,影响功能:收藏 // 原因:这个视图接收的数据从父组件传递过来 // 在这更新数据不会影响父组件的数据,所以在父组件中的数据不会改变 // 所以从父组件进入该组件,显示的点赞和收藏数据均是父组件初始化的时候从服务器获取的,而不是最新的 // 同理,点赞逻辑也存在这个 BUG onPressed: () async{ setState(() { collectionsnot = !collectionsnot; collection[currentSongIndex] = !collection[currentSongIndex]; }); await CollectionApiMusic().addCollection(musicId: id[currentSongIndex], Authorization: AppData().currentToken); }, icon: Image.asset( collectionsnot ? "assets/img/music_star.png" : "assets/img/music_star_un.png", width: 29, height: 29, ), ), IconButton( onPressed: () { Image.asset("assets/img/music_good.png"); }, icon: Image.asset( "assets/img/music_commend_un.png", width: 29, height: 29, ), ), ], ) ], ), const SizedBox( height: 80, ), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( "${formatDuration(_position)}", style: const TextStyle( color: Colors.black, ), ), Expanded( child: SliderTheme( data: const SliderThemeData( trackHeight: 3.0, // 调整轨道的高度 thumbShape: RoundSliderThumbShape( enabledThumbRadius: 7.0), // 调整拇指的大小 overlayShape: RoundSliderOverlayShape(overlayRadius: 12.0), ), child: Slider( min: 0, max: _duration.inSeconds.toDouble(), value: _position.inSeconds.toDouble(), onChanged: (value) async { await _audioPlayer .seek(Duration(seconds: value.toInt())); setState(() {}); }, activeColor: const Color(0xff429482), inactiveColor: const Color(0xffE3F0ED), ), ), ), Text( "${formatDuration(_duration)}", style: const TextStyle( color: Colors.black, ), ), ], ), const SizedBox( height: 30, ), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ IconButton( onPressed: () {}, icon: Image.asset( "assets/img/music_random.png", width: 35, height: 35, ), ), Row( children: [ IconButton( onPressed: playPreviousSong, icon: Image.asset( "assets/img/music_back.png", width: 42, height: 42, ), ), const SizedBox( width: 10, ), _buildPlayButton(), // 这里使用新的方法替换原来的IconButton const SizedBox( width: 10, ), IconButton( onPressed: playNextSong, icon: Image.asset( "assets/img/music_next.png", width: 42, height: 42, ), ), ], ), IconButton( onPressed: _showPlaylist, icon: Image.asset( "assets/img/music_more.png", width: 35, height: 35, ), ), ], ) ], ), ), ), ), ); } void _showPlaylist() { showModalBottomSheet( context: context, shape: const RoundedRectangleBorder( borderRadius: BorderRadius.vertical(top: Radius.circular(30))), builder: (BuildContext context) { return Container( padding: const EdgeInsets.only(top: 15), constraints: BoxConstraints( maxHeight: MediaQuery.of(context).size.height * 0.45), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ const Center( child: Text( "播放列表", style: TextStyle( fontSize: 20, ), ), ), Expanded( child: ListView.builder( itemCount: music.length, itemBuilder: (BuildContext context, int index) { bool isCurrentlyPlaying = currentSongIndex == index; return ListTile( tileColor: isCurrentlyPlaying ? const Color(0xffE3F0ED) : null, title: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Padding( padding: const EdgeInsets.only(left: 20), // Add left padding child: Text( music[index], style: const TextStyle(fontSize: 18), ), ), Padding( padding: const EdgeInsets.only(right: 20), // Add right padding child: Image.asset( "assets/img/songs_run.png", width: 25, ), // Add your desired icon here ), ], ), onTap: () { _changeCurrentSong(index); Navigator.pop(context); }, ); }, ), ), ElevatedButton( onPressed: () => Navigator.pop(context), style: ElevatedButton.styleFrom( backgroundColor: const Color(0xff429482), padding: const EdgeInsets.symmetric(vertical: 14), tapTargetSize: MaterialTapTargetSize.shrinkWrap, shape: const RoundedRectangleBorder( borderRadius: BorderRadius.zero, ), ), child: const Text( "关闭", style: TextStyle(color: Colors.black, fontSize: 20), ), ), ], ), ); }, ); } }