import 'dart:async'; import 'dart:io'; import 'package:path/path.dart' as path; 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_download.dart'; import '../api/api_music_likes.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; double _downloadProgress = 0.0; bool _isDownloading = false; bool _isDownloaded = false; // 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; double _bufferProgress = 0.0; 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); } }); } String _getFileExtension(String url) { // Remove query parameters final urlWithoutQuery = url.split('?').first; // Get the extension including the dot final extension = path.extension(urlWithoutQuery); return extension.isNotEmpty ? extension : '.mp3'; // Default to .mp3 if no extension found } Future _getLocalAudioPath() async { if (!_isDownloaded) return null; final fileName = '${id[currentSongIndex]}_${music[currentSongIndex]}_${artist[currentSongIndex]}'; final extension = _getFileExtension(song2[currentSongIndex]); final fullFileName = '$fileName$extension'; return path.join('/storage/emulated/0/MTMusic', fullFileName); } Future _checkIfDownloaded() async { try { final fileName = '${id[currentSongIndex]}_${music[currentSongIndex]}_${artist[currentSongIndex]}'; final directory = Directory('/storage/emulated/0/MTMusic'); if (await directory.exists()) { final files = directory.listSync(); final isExists = files.any((file) { final name = path.basename(file.path); return name.startsWith(fileName); }); if (mounted) { setState(() { _isDownloaded = isExists; _isDownloading = false; _downloadProgress = 0.0; }); } } else { if (mounted) { setState(() { _isDownloaded = false; _isDownloading = false; _downloadProgress = 0.0; }); } } } catch (e) { print('Error checking downloaded file: $e'); if (mounted) { setState(() { _isDownloaded = false; _isDownloading = false; _downloadProgress = 0.0; }); } } } 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]; }); // 异步检查下载状态 await _checkIfDownloaded(); try { // 在后台停止当前播放 unawaited(_audioPlayer.stop()); // Check for local file first final localPath = await _getLocalAudioPath(); final audioSource = localPath != null ? AudioSource.file(localPath) : AudioSource.uri(Uri.parse(song2[currentSongIndex])); print("-------------" + audioSource.uri.toString()); // 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(); currentSongIndex = widget.initialSongIndex; // 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 _initBufferingListener() { _audioPlayer.bufferedPositionStream.listen((bufferedPosition) { if (!_isDisposed) { final bufferProgress = _duration.inMilliseconds > 0 ? bufferedPosition.inMilliseconds / _duration.inMilliseconds : 0.0; setState(() { _bufferProgress = bufferProgress; }); } }); } 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(); playerInit(); _initBufferingListener(); _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: _isDownloading || _isDownloaded ? null: () async { setState(() { _isDownloading = true; _downloadProgress = 0; }); final downloadApi = DownloadApi(); final fileName = '${id[currentSongIndex]}_${music[currentSongIndex]}_${artist[currentSongIndex]}'; final filePath = await downloadApi.downloadMusic( musicUrl: song2[currentSongIndex], name: fileName, context: context, onProgress: (progress) { // 添加进度回调 setState(() { _downloadProgress = progress; }); }, ); setState(() { _isDownloading = false; }); if (filePath != null) { setState(() { _isDownloaded = true; }); } }, icon: _isDownloading ? Stack( alignment: Alignment.center, children: [ SizedBox( width: 32, height: 32, child: CircularProgressIndicator( value: _downloadProgress, backgroundColor: Colors.grey[200], valueColor: AlwaysStoppedAnimation(Color(0xff429482)), strokeWidth: 3.0, ), ), Text( '${(_downloadProgress * 100).toInt()}', style: TextStyle(fontSize: 12), ), ], ) : Image.asset( _isDownloaded ? "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( onPressed: () async{ UniversalBean bean1 = await LikesApiMusic().likesMusic(musicId: currentSongIndex+1, Authorization: AppData().currentToken); setState(() { likesnot = !likesnot; }); }, icon: Image.asset( likesnot ? "assets/img/music_good.png" : "assets/img/music_good_un.png", width: 29, height: 29, ), ), IconButton( onPressed: () { setState(() { collectionsnot = !collectionsnot; }); }, 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: 3, 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), ), ), ], ), ); }, ); } }