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 '../../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'; import '../view/commend_view.dart'; import '../models/universal_bean.dart'; class MusicView extends StatefulWidget { final List songList; final int initialSongIndex; final Function(int index, bool isCollected, bool isLiked)? onSongStatusChanged; const MusicView({ super.key, required this.songList, required this.initialSongIndex, this.onSongStatusChanged, }); @override State createState() => _MusicViewState(); } class _MusicViewState extends State with SingleTickerProviderStateMixin { 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 = []; late AnimationController _rotationController; bool _isLoading = false; @override void initState() { super.initState(); currentSongIndex = widget.initialSongIndex; playerInit(); _initializeAsync(); _rotationController = AnimationController( duration: const Duration(seconds: 20), vsync: this, ); // 2. 监听播放器状态来控制动画 _audioPlayer.playerStateStream.listen((state) { if (state.playing) { // 当开始播放时,从当前角度继续旋转 _rotationController.repeat(); } else { // 当暂停时,保持当前角度停止 _rotationController.stop(canceled: false); } }); } @override void dispose() { _isDisposed = true; _audioPlayer.dispose(); _rotationController.dispose(); super.dispose(); } 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 { // 在后台停止当前播放 await _audioPlayer.stop(); _rotationController.reset(); // 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(); _rotationController.stop(canceled: false); } else { await _audioPlayer.play(); _rotationController.repeat(); } 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(); } void _changeCurrentSong(int index) { if (!_isDisposed) { setState(() { currentSongIndex = index; _updateCurrentSong(); }); } } Widget _buildRotatingAlbumCover() { return RotationTransition( turns: _rotationController, child: Stack( alignment: Alignment.center, children: [ Positioned( child: ClipRRect( child: Image.network( widget.songList[currentSongIndex].artistPic, width: 225, height: 225, fit: BoxFit.cover, ), ), ), ClipRRect( child: Image.asset( "assets/img/music_Ellipse.png", width: 350, height: 350, fit: BoxFit.cover, ), ), ], ), ); } @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: _buildRotatingAlbumCover(), ), 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( onPressed: () async{ setState(() { likesnot = !likesnot; likes[currentSongIndex] = !likes[currentSongIndex]; }); UniversalBean response = await LikesApiMusic().likesMusic(musicId: id[currentSongIndex], Authorization: AppData().currentToken); if (response.code != 200) { likesnot = !likesnot; likes[currentSongIndex] = !likes[currentSongIndex]; } widget.onSongStatusChanged?.call( currentSongIndex, collection[currentSongIndex], // 传递当前的收藏状态 likes[currentSongIndex] ); }, icon: Image.asset( likesnot ? "assets/img/music_good.png" : "assets/img/music_good_un.png", width: 29, height: 29, ), ), IconButton( onPressed: () async { setState(() { collectionsnot = !collectionsnot; collection[currentSongIndex] = !collection[currentSongIndex]; }); UniversalBean response = await CollectionApiMusic().addCollection(musicId: id[currentSongIndex], Authorization: AppData().currentToken); if (response.code != 200) { collectionsnot = !collectionsnot; collection[currentSongIndex] = !collection[currentSongIndex]; } widget.onSongStatusChanged?.call( currentSongIndex, collection[currentSongIndex], likes[currentSongIndex] // 传递当前的点赞状态 ); }, icon: Image.asset( collectionsnot ? "assets/img/music_star.png" : "assets/img/music_star_un.png", width: 29, height: 29, ), ), IconButton( onPressed: () { Navigator.push( context, MaterialPageRoute( builder: (context) => CommentView(initialSongIndex: widget.initialSongIndex), ), ); }, 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), ), ), ], ), ); }, ); } }