From 4a876106e990342d01ba26777c12eb4db2d4167b Mon Sep 17 00:00:00 2001 From: canxin Date: Wed, 1 May 2024 21:23:33 +0800 Subject: [PATCH 01/28] [Refactor] refactor audio_controller --- lib/comp/card/music_card.dart | 3 +- lib/comp/music_bar/bar.dart | 8 +- lib/comp/play_page_comp/control_button.dart | 8 +- lib/comp/play_page_comp/lyric.dart | 20 +- lib/comp/play_page_comp/music_list.dart | 6 +- lib/comp/play_page_comp/progress_slider.dart | 2 +- lib/comp/play_page_comp/quality_time.dart | 14 +- lib/main.dart | 5 +- lib/page/in_music_list.dart | 15 +- lib/page/search_page.dart | 2 +- lib/types/music.dart | 41 ++- lib/util/audio_controller.dart | 357 ++++++++++--------- lib/util/helper.dart | 29 +- 13 files changed, 287 insertions(+), 223 deletions(-) diff --git a/lib/comp/card/music_card.dart b/lib/comp/card/music_card.dart index 5b74d6f..b6dce00 100644 --- a/lib/comp/card/music_card.dart +++ b/lib/comp/card/music_card.dart @@ -1,7 +1,6 @@ import 'dart:io'; import 'package:app_rhyme/src/rust/api/mirror.dart'; -import 'package:app_rhyme/types/music.dart'; import 'package:app_rhyme/util/colors.dart'; import 'package:app_rhyme/util/default.dart'; import 'package:app_rhyme/util/time_parse.dart'; @@ -10,7 +9,7 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; class MusicCard extends StatefulWidget { - final DisplayMusic music; + final dynamic music; final VoidCallback? onClick; final VoidCallback? onPress; final Future? hasCache; diff --git a/lib/comp/music_bar/bar.dart b/lib/comp/music_bar/bar.dart index 36bca66..dbbecc1 100644 --- a/lib/comp/music_bar/bar.dart +++ b/lib/comp/music_bar/bar.dart @@ -93,7 +93,7 @@ class MusicPlayBar extends StatelessWidget { child: CupertinoButton( padding: EdgeInsets.zero, onPressed: () { - globalAudioServiceHandler.skipToNext(); + globalAudioHandler.seekToPrevious(); }, child: const Icon( CupertinoIcons.backward_fill, @@ -112,7 +112,7 @@ class MusicPlayBar extends StatelessWidget { color: CupertinoColors.black, ), onPressed: () { - globalAudioServiceHandler.pause(); + globalAudioHandler.pause(); }, )); } else { @@ -125,7 +125,7 @@ class MusicPlayBar extends StatelessWidget { color: CupertinoColors.black, ), onPressed: () { - globalAudioServiceHandler.play(); + globalAudioHandler.play(); }, )); } @@ -135,7 +135,7 @@ class MusicPlayBar extends StatelessWidget { child: CupertinoButton( padding: EdgeInsets.zero, onPressed: () { - globalAudioServiceHandler.skipToNext(); + globalAudioHandler.seekToNext(); }, child: const Icon( CupertinoIcons.forward_fill, diff --git a/lib/comp/play_page_comp/control_button.dart b/lib/comp/play_page_comp/control_button.dart index 910492f..81ef940 100644 --- a/lib/comp/play_page_comp/control_button.dart +++ b/lib/comp/play_page_comp/control_button.dart @@ -31,7 +31,7 @@ class ControlButtonState extends State { child: Icon(CupertinoIcons.backward_fill, color: CupertinoColors.white, size: widget.buttonSize), onPressed: () { - globalAudioServiceHandler.skipToPrevious(); + globalAudioHandler.seekToPrevious(); }, )), Obx(() { @@ -43,7 +43,7 @@ class ControlButtonState extends State { child: Icon(CupertinoIcons.pause_solid, color: CupertinoColors.white, size: widget.buttonSize), onPressed: () { - globalAudioServiceHandler.pause(); + globalAudioHandler.pause(); }, )); } else { @@ -54,7 +54,7 @@ class ControlButtonState extends State { child: Icon(CupertinoIcons.play_arrow_solid, color: CupertinoColors.white, size: widget.buttonSize), onPressed: () { - globalAudioServiceHandler.play(); + globalAudioHandler.play(); }, )); } @@ -66,7 +66,7 @@ class ControlButtonState extends State { child: Icon(CupertinoIcons.forward_fill, color: CupertinoColors.white, size: widget.buttonSize), onPressed: () { - globalAudioServiceHandler.skipToNext(); + globalAudioHandler.seekToNext(); }, )), ], diff --git a/lib/comp/play_page_comp/lyric.dart b/lib/comp/play_page_comp/lyric.dart index afe40d4..9c0dc07 100644 --- a/lib/comp/play_page_comp/lyric.dart +++ b/lib/comp/play_page_comp/lyric.dart @@ -19,7 +19,7 @@ class LyricDisplayState extends State { bool playing = false; int position = 0; late StreamSubscription stream1; - late StreamSubscription stream2; + late StreamSubscription stream2; var lyricModel = LyricsModelBuilder.create().bindLyricToMain("[00:00.00]无歌词").getModel(); var lyricUI = UINetease(lyricAlign: LyricAlign.CENTER, highlight: true); @@ -27,14 +27,12 @@ class LyricDisplayState extends State { void initState() { super.initState(); lyricModel = LyricsModelBuilder.create() - .bindLyricToMain(globalAudioServiceHandler - .musicQueue.currentlyPlaying.value?.info.lyric ?? + .bindLyricToMain(globalAudioHandler.playingMusic.value?.info.lyric ?? "[00:00.00]无歌词") .getModel(); - playing = - globalAudioServiceHandler.musicQueue.currentlyPlaying.value != null; - stream1 = globalAudioServiceHandler.player + playing = globalAudioHandler.playingMusic.value != null; + stream1 = globalAudioHandler .createPositionStream( maxPeriod: const Duration(milliseconds: 5), minPeriod: const Duration(milliseconds: 5)) @@ -44,12 +42,12 @@ class LyricDisplayState extends State { }); }); - stream2 = - globalAudioServiceHandler.musicQueue.currentlyPlaying.listen((p0) { + stream2 = globalAudioUiController.duration.listen((p0) { setState(() { - playing = p0 != null; lyricModel = LyricsModelBuilder.create() - .bindLyricToMain(p0?.info.lyric ?? "[00:00.00]无歌词") + .bindLyricToMain( + globalAudioHandler.playingMusic.value?.info.lyric ?? + "[00:00.00]无歌词") .getModel(); }); }); @@ -81,7 +79,7 @@ class LyricDisplayState extends State { children: [ IconButton( onPressed: () { - globalAudioServiceHandler + globalAudioHandler .seek(Duration(milliseconds: progress)) .then((value) { confirm.call(); diff --git a/lib/comp/play_page_comp/music_list.dart b/lib/comp/play_page_comp/music_list.dart index 9cd3005..521e473 100644 --- a/lib/comp/play_page_comp/music_list.dart +++ b/lib/comp/play_page_comp/music_list.dart @@ -13,7 +13,7 @@ class PlayMusicList extends StatelessWidget { @override Widget build(BuildContext context) { return Obx(() { - var musics = globalAudioServiceHandler.musicQueue.downCast(); + var musics = globalAudioHandler.playMusicList; return Expanded( child: CustomScrollView( @@ -33,7 +33,7 @@ class PlayMusicList extends StatelessWidget { .hashCode), music: musics[index], onClick: () { - globalAudioServiceHandler.skipToMusic(index); + globalAudioHandler.seek(Duration.zero, index); }, onPress: () { showCupertinoPopupWithActions(context: context, options: [ @@ -41,7 +41,7 @@ class PlayMusicList extends StatelessWidget { "添加" ], actionCallbacks: [ () async { - globalAudioServiceHandler.delMusic(index); + globalAudioHandler.removeAt(index); }, () async { var tables = await globalSqlMusicFactory.readMusicLists(); diff --git a/lib/comp/play_page_comp/progress_slider.dart b/lib/comp/play_page_comp/progress_slider.dart index 31a0f57..86ac780 100644 --- a/lib/comp/play_page_comp/progress_slider.dart +++ b/lib/comp/play_page_comp/progress_slider.dart @@ -46,7 +46,7 @@ class ProgressSliderState extends State { onProgressUpdated: (value) { isPressing = false; var toSeek = globalAudioUiController.getToSeek(value); - globalAudioServiceHandler.seek(toSeek); + globalAudioHandler.seek(toSeek); }, ), onTap: () { diff --git a/lib/comp/play_page_comp/quality_time.dart b/lib/comp/play_page_comp/quality_time.dart index ee2bcf3..faf483a 100644 --- a/lib/comp/play_page_comp/quality_time.dart +++ b/lib/comp/play_page_comp/quality_time.dart @@ -1,4 +1,5 @@ import 'package:app_rhyme/src/rust/api/mirror.dart'; +import 'package:app_rhyme/types/music.dart'; import 'package:app_rhyme/util/helper.dart'; import 'package:app_rhyme/util/audio_controller.dart'; import 'package:app_rhyme/util/selection.dart'; @@ -57,8 +58,8 @@ class QualityTimeState extends State { ); }), onPressed: () { - List? qualityOptions = globalAudioServiceHandler - .musicQueue.currentlyPlaying.value?.info.qualities; + List? qualityOptions = + globalAudioHandler.playingMusic.value?.info.qualities; List? qualityStrs = qualityOptions?.map((e) => e.short).toList(); if (qualityOptions != null && qualityOptions.isNotEmpty) { @@ -66,8 +67,13 @@ class QualityTimeState extends State { context: context, options: qualityStrs!, actionCallbacks: (index) async { - globalAudioServiceHandler - .tryChangePlayingMusicQuality(qualityOptions[index]); + var playingMusic = globalAudioHandler.playingMusic.value; + if (playingMusic != null) { + var newPlayMusic = await display2PlayMusic( + DisplayMusic.fromPlayMusic(playingMusic), + qualityOptions[index]); + await globalAudioHandler.replaceMusic(newPlayMusic); + } }); } }, diff --git a/lib/main.dart b/lib/main.dart index d4ed3f0..b8650bb 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -10,6 +10,9 @@ import 'package:chinese_font_library/chinese_font_library.dart'; import 'package:flutter/cupertino.dart'; import 'package:app_rhyme/src/rust/frb_generated.dart'; import 'package:path_provider/path_provider.dart'; +import 'package:talker/talker.dart'; + +Talker talker = Talker(); late SqlMusicFactoryW globalSqlMusicFactory; late Config globalConfig; @@ -33,7 +36,7 @@ Future main() async { } } - await initGlobalAudioServiceHandler(); + await initGlobalAudioHandler(); await initGlobalAudioUiController(); runApp(const MyApp()); } diff --git a/lib/page/in_music_list.dart b/lib/page/in_music_list.dart index 4db3a26..fe53b66 100644 --- a/lib/page/in_music_list.dart +++ b/lib/page/in_music_list.dart @@ -89,7 +89,8 @@ class MusicPageState extends State { icon: CupertinoIcons.play_fill, label: '播放全部', onPressed: () { - globalAudioServiceHandler.replaceAllMusic(_musics); + globalAudioHandler + .clearReplaceMusicAll(_musics.reversed.toList()); }, ), _buildButton( @@ -97,8 +98,10 @@ class MusicPageState extends State { icon: Icons.shuffle, label: '随机播放', onPressed: () { - globalAudioServiceHandler - .replaceAllMusic(shuffleList(_musics)); + var musics = _musics.toList(); + musics.shuffle(); + globalAudioHandler + .clearReplaceMusicAll(shuffleList(musics)); }, ), ], @@ -124,7 +127,7 @@ class MusicPageState extends State { key: ValueKey(music.info.id), music: music, onClick: () { - globalAudioServiceHandler.addMusicPlay( + globalAudioHandler.addMusicPlay( music, ); }, @@ -147,7 +150,7 @@ class MusicPageState extends State { // 下载完成之后设置本地路径为新的播放文件 playMusic.playInfo.file = file; // 如果这首歌正在播放列表中,替换他,防止继续在线播放 - globalAudioServiceHandler.replaceMusic(playMusic); + globalAudioHandler.replaceMusic(playMusic); // 在这里需要重新判断是否 hasCache,所以直接setState解决 setState(() {}); }); @@ -197,7 +200,7 @@ class MusicPageState extends State { print("成功删除缓存:${music.info.name}"); } display2PlayMusic(music).then((value) { - globalAudioServiceHandler.replaceMusic(value); + globalAudioHandler.replaceMusic(value); }); }); } diff --git a/lib/page/search_page.dart b/lib/page/search_page.dart index 61467e2..d0776d0 100644 --- a/lib/page/search_page.dart +++ b/lib/page/search_page.dart @@ -139,7 +139,7 @@ class SearchPage extends StatelessWidget { itemBuilder: (context, item, index) => MusicCard( music: item, onClick: () { - globalAudioServiceHandler.addMusicPlay( + globalAudioHandler.addMusicPlay( item, ); }, diff --git a/lib/types/music.dart b/lib/types/music.dart index 7cadb98..2de7e2d 100644 --- a/lib/types/music.dart +++ b/lib/types/music.dart @@ -7,18 +7,23 @@ import 'package:audio_service/audio_service.dart'; // 是一个原本只具有展示功能的DisplayMusicTuple通过请求第三方api变成可以播放的音乐 // 这个过程已经决定了一个音乐是否可以播放,因此本函数应该可能throw Exception -Future display2PlayMusic(DisplayMusic music) async { - late Quality defaultQuality; - if (music.info.defaultQuality != null) { - defaultQuality = music.info.defaultQuality!; - } else if (music.info.qualities.isNotEmpty) { - defaultQuality = music.info.qualities[0]; - log("音乐无默认音质,选择音质中第一个进行播放:$defaultQuality"); +Future display2PlayMusic(DisplayMusic music, + [Quality? quality]) async { + late Quality finalQuality; + if (quality != null) { + finalQuality = quality; } else { - throw Exception("音乐无可播放音质"); + if (music.info.defaultQuality != null) { + finalQuality = music.info.defaultQuality!; + } else if (music.info.qualities.isNotEmpty) { + finalQuality = music.info.qualities[0]; + log("音乐无默认音质,选择音质中第一个进行播放:$finalQuality"); + } else { + throw Exception("音乐无可播放音质"); + } } // 音乐缓存获取的逻辑 - var extra = music.ref.getExtraInto(quality: defaultQuality); + var extra = music.ref.getExtraInto(quality: finalQuality); if (globalExternApi == null) { log("无第三方音乐源,无法获取播放信息"); throw Exception("无第三方音乐源,无法获取播放信息"); @@ -50,6 +55,9 @@ class DisplayMusic { ref = musicRef_; info = ref.getMusicInfo(); } + DisplayMusic.fromPlayMusic(PlayMusic music) { + DisplayMusic(music.ref); + } String toCacheFileName() { if (info.defaultQuality == null) { return ""; @@ -117,4 +125,19 @@ class PlayMusic { String toCacheFileName() { return "${info.name}_${info.artist.join(',')}_${info.source}_${extra.hashCode}.${playInfo.quality.format ?? "unknown"}"; } + + MediaItem toMediaItem() { + Uri? artUri; + if (info.artPic != null) { + artUri = Uri.parse(info.artPic!); + } else { + artUri = null; + } + return MediaItem( + id: playInfo.file, + title: info.name, + album: info.album, + artUri: artUri, + artist: info.artist.join(",")); + } } diff --git a/lib/util/audio_controller.dart b/lib/util/audio_controller.dart index 600ec36..41dbcf4 100644 --- a/lib/util/audio_controller.dart +++ b/lib/util/audio_controller.dart @@ -1,180 +1,230 @@ -import 'dart:async'; -import 'package:app_rhyme/src/rust/api/mirror.dart'; +import 'package:app_rhyme/main.dart'; import 'package:app_rhyme/types/music.dart'; -import 'package:app_rhyme/types/play_music_queue.dart'; -import 'package:audio_service/audio_service.dart'; +import 'package:audio_session/audio_session.dart'; import 'package:get/get.dart'; import 'package:just_audio/just_audio.dart'; +import 'package:just_audio_background/just_audio_background.dart'; DateTime lastComplete = DateTime(1999); -late AudioServiceHandler globalAudioServiceHandler; +late AudioHandler globalAudioHandler; late AudioUiController globalAudioUiController; -Future initGlobalAudioServiceHandler() async { - globalAudioServiceHandler = await AudioService.init( - builder: () => AudioServiceHandler(), - config: const AudioServiceConfig( - androidNotificationChannelId: 'com.ryanheise.myapp.channel.audio', - androidNotificationChannelName: 'Audio playback', - androidNotificationOngoing: false, - ), + +// 初始化所有和Audio相关的内容 +Future initGlobalAudioHandler() async { + await JustAudioBackground.init( + androidNotificationChannelId: 'com.ryanheise.bg_demo.channel.audio', + androidNotificationChannelName: 'Audio playback', + androidNotificationOngoing: true, ); + final session = await AudioSession.instance; + await session.configure(const AudioSessionConfiguration.music()); + globalAudioHandler = AudioHandler(); } -// 应该实现所有的播放和控制,但是完全不关心ui如何 -class AudioServiceHandler extends BaseAudioHandler - with SeekHandler, QueueHandler { - final AudioPlayer player = AudioPlayer(); - final PlayMusicQueue musicQueue = PlayMusicQueue(); - int playingMusicIndex = 0; +class AudioHandler extends GetxController { + final AudioPlayer _player = AudioPlayer(); + // 这两个本质都是list,但是我们需要确保其同步变化 + final RxList playMusicList = RxList([]); + final Rx playingMusic = Rx(null); + final ConcatenatingAudioSource playSourceList = + ConcatenatingAudioSource(children: []); - AudioServiceHandler() { - player.playbackEventStream.map(_transformEvent).pipe(playbackState); + Future _init() async { + // 先默认开启所有的循环 + _player.setLoopMode(LoopMode.all); + // 监听错误事件并用talker来log + _player.playbackEventStream.listen((event) {}, + onError: (Object e, StackTrace stackTrace) { + talker.error('[PlaybackEventStream Error] $e'); + }); + // 将playSourceList交给player作为列表,不过目前是空的 + _player.setAudioSource(playSourceList); } - // 这里受制于需要两个变量做判断,所以流移动到 UiController中去了 - Future actionWhenComplete() async { - await skipToNext(); + AudioHandler() { + _init(); } - // 处理播放逻辑 - Future _trySetSourcePlay(PlayMusic? music) async { - // 先确保播放状态重置 - if (player.playing) { - await pause(); - } - // 开始设置新歌曲 - if (music != null) { - String toPlaySource = music.playInfo.file; - var tag = music.item; - // 设置播放资源 - await player - .setAudioSource(AudioSource.uri(Uri.parse(toPlaySource), tag: tag)); - // 设置系统显示 - // 这里的mediaItem是AudioService内部的 - - mediaItem.add(tag); - // 开始播放 - await play(); - return true; - } else { - return false; + Future addMusicPlay(DisplayMusic music) async { + try { + PlayMusic playMusic; + try { + playMusic = await display2PlayMusic(music); + } catch (e) { + talker.error( + "[Error Music Handler] In addMusicPlay, Failed to diaplayMusic2PlayMusic: $e"); + return; + } + + var index = playMusicList + .indexWhere((element) => element.extra == playMusic.extra); + if (index != -1) { + playMusicList.removeAt(index); + playSourceList.removeAt(index); + } + + // 添加新的音乐 + playMusicList.add(playMusic); + await playSourceList.add(AudioSource.uri( + Uri.parse(playMusic.playInfo.file), + tag: playMusic.toMediaItem())); + + playingMusic.value = playMusic; + update(); + + // 播放新的音乐 + await _player.seek(Duration.zero, index: playSourceList.length - 1); + + await _player.play(); + } catch (e) { + talker.error("[Error Music Handler] In addMusicPlay, Error occur: $e"); } + // 先检查是否已有这个音乐,如果已有,删除掉原有的 } - Future replaceAllMusic(List musics) async { - if (player.playing) { - await pause(); + Future replaceMusic(PlayMusic playMusic) async { + try { + int index = playMusicList + .indexWhere((element) => element.extra == playMusic.extra); + bool shouldPlay = index == _player.currentIndex; + if (index != -1) { + // 删除对应位置的音乐 + await removeAt(index); + // 插入新音乐到对应位置 + await _insert(index, playMusic); + if (shouldPlay) { + // 重新播放这个位置的音乐 + await _player.seek(Duration.zero, index: index); + await _player.play(); + playingMusic.value = playMusic; + update(); + } + } + } catch (e) { + talker.error( + "[Error Music Handler] In replaceMusic, Failed to display2PlayMusic: $e"); } - var music = await musicQueue.replaceAllMusics(musics); - return _trySetSourcePlay(music); } - Future replaceMusic(PlayMusic music) async { - var music_ = await musicQueue.replaceMusic(music); - if (music_ != null) { - return _trySetSourcePlay(music_); - } else { - return true; + Future clearReplaceMusicAll(List musics) async { + talker.log( + "[Log Music Handler] Request to add all musics of length: ${musics.length}"); + List newPlayMusics = []; + List newAudioSources = []; + for (var music in musics) { + try { + var playMusic = await display2PlayMusic(music); + newPlayMusics.add(playMusic); + newAudioSources.add(AudioSource.uri(Uri.parse(playMusic.playInfo.file), + tag: playMusic.toMediaItem())); + } catch (e) { + talker.error( + "[Error Music Handler] In clearReplaceMusicAll, Failed to diaplayMusic2PlayMusic: $e"); + } } - } - Future delMusic(int index) async { - PlayMusic? music = musicQueue.delIndex(index); - return await _trySetSourcePlay(music); + await clear(); + + playMusicList.addAll(newPlayMusics); + try { + // await player + // .setAudioSource(ConcatenatingAudioSource(children: newAudioSources)); + await playSourceList.addAll(newAudioSources); + } catch (e) { + talker.error( + "[Error Music Handler] In clearReplaceMusicAll, Failed to diaplayMusic2PlayMusic: $e"); + } + talker.log( + "[Log Music Handler] After add all: crt playMusicList length:${playMusicList.length},crt playSourceList length:${playSourceList.length}"); + await _player.seek(Duration.zero, index: playSourceList.length - 1); + + await _player.play(); + + playingMusic.value = playMusicList[playMusicList.length - 1]; + update(); } - Future skipToMusic(int index) async { - PlayMusic? music = musicQueue.skipToMusic(index); - return await _trySetSourcePlay(music); + Future _insert(int index, PlayMusic music) async { + playMusicList.insert(index, music); + await playSourceList.insert( + index, + AudioSource.uri(Uri.parse(music.playInfo.file), + tag: music.toMediaItem())); } - Future addMusicPlay( - DisplayMusic music, - ) async { - PlayMusic? music_ = await musicQueue.addMusic( - music, - ); - return await _trySetSourcePlay(music_); + Future clear() async { + talker.log("[Log Music Handler] Request to clear all musics"); + if (playMusicList.isNotEmpty) { + playMusicList.clear(); + } + if (playSourceList.length > 0) { + await playSourceList.clear(); + update(); + } + playingMusic.value = null; } - Future tryChangePlayingMusicQuality(Quality quality) async { - PlayMusic? music = await musicQueue.changeCurrentPlayingQuality(quality); - return await _trySetSourcePlay(music); + Future removeAt(int index) async { + talker.log("[Log Music Handler] Request to remove music of index:$index"); + // 如果正在播放,先暂停 + if (_player.currentIndex != null && _player.currentIndex! == index) { + await _player.pause(); + playingMusic.value = null; + update(); + } + playMusicList.removeAt(index); + await playSourceList.removeAt(index); } - // 以下实际上是为 系统控制提供调用 - @override - Future skipToNext() async { - PlayMusic? music = musicQueue.skipToNext(); - return await _trySetSourcePlay(music); + // PlayMusic? playingMusic.value { + // if (player.currentIndex != null && + // player.currentIndex != -1 && + // playMusicList.isNotEmpty) { + // try { + // return playMusicList[player.currentIndex!]; + // } catch (e) { + // talker.error( + // "[Error Music Handler] Failed to get playingMusic when index is not null: $e"); + // } + // } + // return null; + // } + Future seekToNext() async { + await _player.seekToNext(); + if (_player.currentIndex != null) { + playingMusic.value = playMusicList[_player.currentIndex!]; + update(); + } } - @override - Future skipToPrevious() async { - PlayMusic? music = musicQueue.skipToPrevious(); - return await _trySetSourcePlay(music); + Future seekToPrevious() async { + await _player.seekToPrevious(); + if (_player.currentIndex != null) { + playingMusic.value = playMusicList[_player.currentIndex!]; + update(); + } } - @override - Future skipToQueueItem(int index) async { - PlayMusic? music = musicQueue.skipToMusic(index); - return await _trySetSourcePlay(music); + Future pause() async { + await _player.pause(); } - @override Future play() async { - await player.play(); - // Future.delayed(const Duration(milliseconds: 500)).then((value) { - // if (!player.playing) { - // play(); - // } - // }); + await _player.play(); } - @override - Future pause() async { - await player.pause(); - } - - @override - Future stop() async { - await player.stop(); - } - - @override - Future seek(Duration position) async { - await player.seek(position); - } - - PlaybackState _transformEvent(PlaybackEvent event) { - return PlaybackState( - controls: [ - MediaControl.skipToPrevious, - if (player.playing) MediaControl.pause else MediaControl.play, - MediaControl.skipToNext, - ], - systemActions: const { - MediaAction.play, - MediaAction.pause, - MediaAction.skipToNext, - MediaAction.skipToPrevious, - MediaAction.seek - }, - androidCompactActionIndices: const [0, 1, 2], - processingState: const { - ProcessingState.idle: AudioProcessingState.idle, - ProcessingState.loading: AudioProcessingState.loading, - ProcessingState.buffering: AudioProcessingState.buffering, - ProcessingState.ready: AudioProcessingState.ready, - ProcessingState.completed: AudioProcessingState.completed, - }[player.processingState]!, - playing: player.playing, - updatePosition: player.position, - bufferedPosition: player.bufferedPosition, - speed: player.speed, - queueIndex: event.currentIndex, - ); + Future seek(Duration position, [int? index]) async { + await _player.seek(position, index: index); + } + + Stream createPositionStream({ + int steps = 800, + Duration minPeriod = const Duration(milliseconds: 200), + Duration maxPeriod = const Duration(milliseconds: 200), + }) { + return _player.createPositionStream( + steps: steps, minPeriod: minPeriod, maxPeriod: maxPeriod); } } @@ -191,9 +241,15 @@ class AudioUiController extends GetxController { Rx playProgress = 0.0.obs; AudioUiController() { // 初始化late - playerState = globalAudioServiceHandler.player.playerState.obs; + playerState = globalAudioHandler._player.playerState.obs; + + globalAudioHandler._player.playerStateStream.listen((event) { + playerState.value = event; + update(); + }); + // 在这里触发playbackevent状态变化 - globalAudioServiceHandler.player.playbackEventStream.listen((event) { + globalAudioHandler._player.playbackEventStream.listen((event) { position.value = event.updatePosition; if (event.duration != null) { duration.value = event.duration!; @@ -201,27 +257,8 @@ class AudioUiController extends GetxController { update(); }); - // 在这里触发播放状态变化 - globalAudioServiceHandler.player.playerStateStream.listen((state) { - playerState.value = state; - // 检测播放结束的逻辑,这里实际上的执行者还是AudioServiceHandler - if (state.processingState == ProcessingState.completed) { - if (DateTime.now().difference(lastComplete).inSeconds > 3) { - lastComplete = DateTime.now(); - globalAudioServiceHandler.actionWhenComplete(); - } - } else if (position.value.inSeconds > 1 && - (position.value > duration.value || - (duration.value - position.value).abs().inMicroseconds < 1000)) { - if (DateTime.now().difference(lastComplete).inSeconds > 3) { - lastComplete = DateTime.now(); - globalAudioServiceHandler.actionWhenComplete(); - } - } - }); - // 在这里触发音乐总时长变化 - globalAudioServiceHandler.player.durationStream.listen((newDuration) { + globalAudioHandler._player.durationStream.listen((newDuration) { if (newDuration != null) { duration.value = newDuration; playProgress.value = @@ -229,7 +266,7 @@ class AudioUiController extends GetxController { update(); } }); - globalAudioServiceHandler.player + globalAudioHandler._player .createPositionStream( maxPeriod: const Duration(milliseconds: 100), minPeriod: const Duration(milliseconds: 1)) diff --git a/lib/util/helper.dart b/lib/util/helper.dart index a64118f..7db586e 100644 --- a/lib/util/helper.dart +++ b/lib/util/helper.dart @@ -28,12 +28,9 @@ Future fileCacheHelper(String file, String cachePath) async { Future playingMusicImage() async { late Image image; - if (globalAudioServiceHandler.musicQueue.currentlyPlaying.value != null && - globalAudioServiceHandler - .musicQueue.currentlyPlaying.value!.info.artPic != - null) { - String url = globalAudioServiceHandler - .musicQueue.currentlyPlaying.value!.info.artPic!; + var playingMusic = globalAudioHandler.playingMusic.value; + if (playingMusic != null && playingMusic.info.artPic != null) { + String url = playingMusic.info.artPic!; String toUseSource = await fileCacheHelper(url, picCachePath); if (toUseSource.contains("http")) { image = Image.network(toUseSource); @@ -51,10 +48,9 @@ Future playingMusicImage() async { String get playingMusicQualityShort { late Quality quality; - if (globalAudioServiceHandler.musicQueue.currentlyPlayingPlayinfo.value != - null) { - quality = globalAudioServiceHandler - .musicQueue.currentlyPlayingPlayinfo.value!.quality; + var playingMusic = globalAudioHandler.playingMusic.value; + if (playingMusic != null) { + quality = playingMusic.playInfo.quality; } else { quality = const Quality(short: "Quality"); } @@ -63,9 +59,9 @@ String get playingMusicQualityShort { String get playingMusicName { late String name; - if (globalAudioServiceHandler.musicQueue.currentlyPlaying.value != null) { - name = - globalAudioServiceHandler.musicQueue.currentlyPlaying.value!.info.name; + var playingMusic = globalAudioHandler.playingMusic.value; + if (playingMusic != null) { + name = playingMusic.info.name; } else { name = "Music"; } @@ -74,10 +70,9 @@ String get playingMusicName { String get playingMusicArtist { late String artist; - if (globalAudioServiceHandler.musicQueue.currentlyPlaying.value != null) { - artist = globalAudioServiceHandler - .musicQueue.currentlyPlaying.value!.info.artist - .join(","); + var playingMusic = globalAudioHandler.playingMusic.value; + if (playingMusic != null) { + artist = playingMusic.info.artist.join(","); } else { artist = "Artist"; } From e21e50bdbea1df3c882dc0c55016d8e7408d894a Mon Sep 17 00:00:00 2001 From: canxin Date: Thu, 2 May 2024 02:23:56 +0800 Subject: [PATCH 02/28] [fix] uncaught exception in extern_api; [fix] global audio_controller; [Test] Test ios stop play by try to avoid Overflow --- lib/comp/card/music_card.dart | 16 +- lib/comp/play_page_comp/music_artpic.dart | 31 +- lib/comp/play_page_comp/music_info.dart | 16 +- lib/comp/play_page_comp/quality_time.dart | 1 + lib/main.dart | 3 +- lib/page/home.dart | 102 +++--- lib/page/in_music_list.dart | 10 +- lib/page/playing_music_page copy.dart | 182 ++++++++++ lib/page/playing_music_page.dart | 88 +++-- lib/page/search_page.dart | 1 - lib/page/setting.dart | 3 +- lib/types/extern_api.dart | 11 +- lib/types/http_wrap.dart | 32 +- lib/types/music.dart | 59 ++-- lib/types/play_music_queue.dart | 409 +++++++++++----------- lib/util/audio_controller.dart | 94 +++-- lib/util/helper.dart | 13 + lib/util/window.dart | 2 +- pubspec.lock | 48 +++ pubspec.yaml | 1 + 20 files changed, 699 insertions(+), 423 deletions(-) create mode 100644 lib/page/playing_music_page copy.dart diff --git a/lib/comp/card/music_card.dart b/lib/comp/card/music_card.dart index b6dce00..9c4de90 100644 --- a/lib/comp/card/music_card.dart +++ b/lib/comp/card/music_card.dart @@ -3,6 +3,7 @@ import 'dart:io'; import 'package:app_rhyme/src/rust/api/mirror.dart'; import 'package:app_rhyme/util/colors.dart'; import 'package:app_rhyme/util/default.dart'; +import 'package:app_rhyme/util/helper.dart'; import 'package:app_rhyme/util/time_parse.dart'; import 'package:chinese_font_library/chinese_font_library.dart'; import 'package:flutter/cupertino.dart'; @@ -60,9 +61,18 @@ class MusicCardState extends State { ClipRRect( borderRadius: BorderRadius.circular(4.0), child: info.artPic != null - ? info.artPic!.contains("http") - ? Image.network(info.artPic!, width: 40.0) - : Image.file(File(info.artPic!), width: 40.0) + ? FutureBuilder( + future: useCacheImage(info.artPic!), + builder: (context, snapshot) { + if (snapshot.hasError || + snapshot.connectionState == + ConnectionState.waiting) { + return defaultArtPic; + } else { + return snapshot.data ?? defaultArtPic; + } + }, + ) : defaultArtPic, ), Expanded( diff --git a/lib/comp/play_page_comp/music_artpic.dart b/lib/comp/play_page_comp/music_artpic.dart index bd1dc92..a3c81b6 100644 --- a/lib/comp/play_page_comp/music_artpic.dart +++ b/lib/comp/play_page_comp/music_artpic.dart @@ -4,10 +4,8 @@ import 'package:flutter/cupertino.dart'; import 'package:get/get_state_manager/src/rx_flutter/rx_obx_widget.dart'; class MusicArtPic extends StatefulWidget { - final double imageSize; const MusicArtPic({ super.key, - required this.imageSize, }); // 修改构造函数 @override @@ -22,26 +20,23 @@ class MusicArtPicState extends State { future: playingMusicImage(), // 这里调用异步函数 builder: (BuildContext context, AsyncSnapshot snapshot) { if (snapshot.connectionState == ConnectionState.waiting) { - return ClipRRect( - borderRadius: BorderRadius.circular(18.0), - child: SizedBox( - width: widget.imageSize, - height: widget.imageSize, - child: defaultArtPic, // 异步函数提供的图片或默认图片 - ), - ); + return Container( + padding: const EdgeInsets.only(left: 20, right: 20, top: 20), + child: ClipRRect( + borderRadius: BorderRadius.circular(18.0), + child: defaultArtPic, + ) // 异步函数提供的图片或默认图片 + ); } else if (snapshot.hasError) { return Text('Error: ${snapshot.error}'); } else { // 使用Cupertino组件包裹图片,并限制大小和加圆角边框 - return ClipRRect( - borderRadius: BorderRadius.circular(18.0), - child: SizedBox( - width: widget.imageSize, - height: widget.imageSize, - child: snapshot.data ?? defaultArtPic, // 异步函数提供的图片或默认图片 - ), - ); + return Container( + padding: const EdgeInsets.only(left: 20, right: 20, top: 20), + child: ClipRRect( + borderRadius: BorderRadius.circular(18.0), + child: snapshot.data ?? defaultArtPic, + )); } }, ), diff --git a/lib/comp/play_page_comp/music_info.dart b/lib/comp/play_page_comp/music_info.dart index b1c9eeb..babf9c2 100644 --- a/lib/comp/play_page_comp/music_info.dart +++ b/lib/comp/play_page_comp/music_info.dart @@ -21,20 +21,10 @@ class MusicInfoState extends State { return Container( alignment: Alignment.topLeft, child: Text( - playingMusicName, - style: const TextStyle(fontSize: 24.0), - textAlign: TextAlign.center, - ), - ); - }), - // 演唱者 - Obx(() { - return Container( - alignment: Alignment.bottomLeft, - child: Text( - playingMusicArtist, - style: const TextStyle(fontSize: 18.0), + "$playingMusicName\n$playingMusicArtist", + style: const TextStyle(fontSize: 20.0), textAlign: TextAlign.center, + overflow: TextOverflow.clip, ), ); }), diff --git a/lib/comp/play_page_comp/quality_time.dart b/lib/comp/play_page_comp/quality_time.dart index faf483a..62c83c4 100644 --- a/lib/comp/play_page_comp/quality_time.dart +++ b/lib/comp/play_page_comp/quality_time.dart @@ -72,6 +72,7 @@ class QualityTimeState extends State { var newPlayMusic = await display2PlayMusic( DisplayMusic.fromPlayMusic(playingMusic), qualityOptions[index]); + if (newPlayMusic == null) return; await globalAudioHandler.replaceMusic(newPlayMusic); } }); diff --git a/lib/main.dart b/lib/main.dart index b8650bb..2fcaa0c 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -3,7 +3,6 @@ import 'package:app_rhyme/src/rust/api/config.dart'; import 'package:app_rhyme/src/rust/api/init.dart'; import 'package:app_rhyme/src/rust/api/music_sdk.dart'; import 'package:app_rhyme/types/extern_api.dart'; -import 'package:app_rhyme/types/music.dart'; import 'package:app_rhyme/util/audio_controller.dart'; import 'package:app_rhyme/util/window.dart'; import 'package:chinese_font_library/chinese_font_library.dart'; @@ -32,7 +31,7 @@ Future main() async { try { globalExternApi = ExternApi(globalConfig.externApiPath!); } catch (e) { - log("加载第三方音乐源失败: $e"); + talker.error("[Main] 加载第三方音乐源失败: $e"); } } diff --git a/lib/page/home.dart b/lib/page/home.dart index 10e1a4d..6e9f294 100644 --- a/lib/page/home.dart +++ b/lib/page/home.dart @@ -7,6 +7,7 @@ import 'package:app_rhyme/page/user_agreement.dart'; import 'package:app_rhyme/util/colors.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_keyboard_visibility/flutter_keyboard_visibility.dart'; import 'package:get/get.dart'; final TopUiController globalTopUiController = Get.put(TopUiController()); @@ -51,6 +52,7 @@ class HomePage extends StatefulWidget { } class HomePageState extends State { + bool isKeyboardVisible = false; @override void initState() { super.initState(); @@ -58,6 +60,12 @@ class HomePageState extends State { if (!globalConfig.userAgreement) { showUserAgreement(context); } + // 监听键盘状态的变化 + KeyboardVisibilityController().onChange.listen((bool visible) { + setState(() { + isKeyboardVisible = visible; + }); + }); }); } @@ -67,57 +75,59 @@ class HomePageState extends State { child: Stack( children: [ Obx(() => globalTopUiController.currentWidget.value), - Align( - alignment: Alignment.bottomCenter, - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - // 音乐播放控制栏 - const MusicPlayBar(), - // 一个浅色的分隔 - const Divider( - color: CupertinoColors.systemGrey6, - height: 1, - thickness: 1, - indent: 20, - endIndent: 20, - ), - // 底部的导航图标按钮 - Obx(() => CupertinoTabBar( - activeColor: activeIconColor, - backgroundColor: barBackgoundColor, - currentIndex: globalTopUiController.currentIndex.value, - onTap: globalTopUiController.changeTabIndex, - items: const [ - BottomNavigationBarItem( - icon: Padding( - padding: EdgeInsets.only(top: 10), - child: Icon( - CupertinoIcons.music_albums_fill, + // 使用MediaQuery检测键盘是否可见 + if (!isKeyboardVisible) + Align( + alignment: Alignment.bottomCenter, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + // 音乐播放控制栏 + const MusicPlayBar(), + // 一个浅色的分隔 + const Divider( + color: CupertinoColors.systemGrey6, + height: 1, + thickness: 1, + indent: 20, + endIndent: 20, + ), + // 底部的导航图标按钮 + Obx(() => CupertinoTabBar( + activeColor: activeIconColor, + backgroundColor: barBackgoundColor, + currentIndex: globalTopUiController.currentIndex.value, + onTap: globalTopUiController.changeTabIndex, + items: const [ + BottomNavigationBarItem( + icon: Padding( + padding: EdgeInsets.only(top: 10), + child: Icon( + CupertinoIcons.music_albums_fill, + ), ), ), - ), - BottomNavigationBarItem( - icon: Padding( - padding: EdgeInsets.only(top: 10), - child: Icon(CupertinoIcons.search), + BottomNavigationBarItem( + icon: Padding( + padding: EdgeInsets.only(top: 10), + child: Icon(CupertinoIcons.search), + ), ), - ), - BottomNavigationBarItem( - icon: Padding( - padding: EdgeInsets.only(top: 10), - child: Icon(CupertinoIcons.settings), + BottomNavigationBarItem( + icon: Padding( + padding: EdgeInsets.only(top: 10), + child: Icon(CupertinoIcons.settings), + ), ), - ), - ], - )), - // 底部导航栏图标按钮和底部的一个空白边界 - Container( - padding: const EdgeInsets.only(bottom: 10), - color: barBackgoundColor) - ], + ], + )), + // 底部导航栏图标按钮和底部的一个空白边界 + Container( + padding: const EdgeInsets.only(bottom: 10), + color: barBackgoundColor) + ], + ), ), - ), ], ), ); diff --git a/lib/page/in_music_list.dart b/lib/page/in_music_list.dart index fe53b66..cb618cd 100644 --- a/lib/page/in_music_list.dart +++ b/lib/page/in_music_list.dart @@ -89,8 +89,7 @@ class MusicPageState extends State { icon: CupertinoIcons.play_fill, label: '播放全部', onPressed: () { - globalAudioHandler - .clearReplaceMusicAll(_musics.reversed.toList()); + globalAudioHandler.clearReplaceMusicAll(_musics); }, ), _buildButton( @@ -142,6 +141,7 @@ class MusicPageState extends State { () async { // 缓存 var playMusic = await display2PlayMusic(music); + if (playMusic == null) return; cacheFile( file: playMusic.playInfo.file, cachePath: musicCachePath, @@ -189,10 +189,13 @@ class MusicPageState extends State { }, () async { // 删除缓存 + var result = music.toCacheFileNameAndExtra(); + if (result == null) return; + var (cacheFileName, _) = result; deleteCacheFile( file: "", cachePath: musicCachePath, - filename: music.toCacheFileName()) + filename: cacheFileName) .then((value) { // 删除缓存后刷新是否有缓存 setState(() {}); @@ -200,6 +203,7 @@ class MusicPageState extends State { print("成功删除缓存:${music.info.name}"); } display2PlayMusic(music).then((value) { + if (value == null) return; globalAudioHandler.replaceMusic(value); }); }); diff --git a/lib/page/playing_music_page copy.dart b/lib/page/playing_music_page copy.dart new file mode 100644 index 0000000..bd56f48 --- /dev/null +++ b/lib/page/playing_music_page copy.dart @@ -0,0 +1,182 @@ +import 'package:app_rhyme/comp/card/playing_music_card.dart'; +import 'package:app_rhyme/comp/play_page_comp/botton_button.dart'; +import 'package:app_rhyme/comp/play_page_comp/control_button.dart'; +import 'package:app_rhyme/comp/play_page_comp/lyric.dart'; +import 'package:app_rhyme/comp/play_page_comp/music_artpic.dart'; +import 'package:app_rhyme/comp/play_page_comp/music_info.dart'; +import 'package:app_rhyme/comp/play_page_comp/music_list.dart'; +import 'package:app_rhyme/comp/play_page_comp/progress_slider.dart'; +import 'package:app_rhyme/comp/play_page_comp/quality_time.dart'; +import 'package:app_rhyme/comp/play_page_comp/volume_slider.dart'; +import 'package:flutter/cupertino.dart'; + +import 'package:dismissible_page/dismissible_page.dart'; +import 'package:pro_animated_blur/pro_animated_blur.dart'; + +enum PageState { main, list, lyric } + +class SongDisplayPage extends StatefulWidget { + const SongDisplayPage({super.key}); + + @override + SongDisplayPageState createState() => SongDisplayPageState(); +} + +class SongDisplayPageState extends State { + PageState pageState = PageState.main; + int topFlex = 5; + int bottomFlex = 7; + void onListBotton() { + setState(() { + if (pageState == PageState.list) { + pageState = PageState.main; + bottomFlex = 7; + } else { + pageState = PageState.list; + bottomFlex = 2; + } + }); + } + + void onLyricBotton() { + setState(() { + if (pageState == PageState.lyric) { + pageState = PageState.main; + bottomFlex = 7; + } else { + pageState = PageState.lyric; + bottomFlex = 2; + } + }); + } + + @override + Widget build(BuildContext context) { + double screenWidth = MediaQuery.of(context).size.width; + double screenHeight = MediaQuery.of(context).size.height; + + List topWidgets; + switch (pageState) { + case PageState.main: + topWidgets = [ + const Flexible( + flex: 1, + child: MusicArtPic(), + ) + ]; + break; + case PageState.list: + topWidgets = [ + Padding(padding: EdgeInsets.only(top: screenHeight * 0.03)), + const PlayingMusicCard(), + const Padding(padding: EdgeInsets.only(left: 30)), + Container( + padding: const EdgeInsets.only(left: 20), + alignment: Alignment.centerLeft, + child: const Text( + '待播清单', + style: TextStyle( + fontSize: 20.0, + fontWeight: FontWeight.bold, + ), + ), + ), + const PlayMusicList(), + ]; + break; + case PageState.lyric: + topWidgets = [ + Padding(padding: EdgeInsets.only(top: screenHeight * 0.03)), + const PlayingMusicCard(), + const Padding(padding: EdgeInsets.only(top: 10, bottom: 10)), + LyricDisplay(maxHeight: screenHeight * 0.55), + ]; + break; + } + // 底部组件,包括 ProgressSlider, QualityTime, ControlButton, VolumeSlider 和 BottomButton + List bottomWidgets = [ + if (pageState == PageState.main) + const Flexible(flex: 2, child: MusicInfo()), + const Flexible( + flex: 1, + child: ProgressSlider(), + ), + const Flexible( + flex: 1, + child: QualityTime(), + ), + Flexible( + flex: 3, + child: ControlButton( + buttonSize: screenWidth * 0.1, + buttonSpacing: screenWidth * 0.2, + ), + ), + const Flexible( + flex: 1, + child: VolumeSlider(), + ), + Flexible( + flex: 1, + child: Container( + padding: const EdgeInsets.only(top: 10, bottom: 10), + child: BottomButton( + onList: onListBotton, + onLyric: onLyricBotton, + ), + ), + ) + ]; + + return DismissiblePage( + isFullScreen: true, + direction: DismissiblePageDismissDirection.down, + backgroundColor: CupertinoColors.white, + onDismissed: () => Navigator.of(context).pop(), + child: Container( + clipBehavior: Clip.antiAliasWithSaveLayer, + decoration: const BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topRight, + end: Alignment.bottomLeft, + colors: [ + CupertinoColors.systemGrey2, + CupertinoColors.systemGrey, + ], + ), + ), + child: ProAnimatedBlur( + blur: 500, + duration: const Duration(milliseconds: 0), + child: Column( + children: [ + Flexible( + flex: topFlex, + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + children: topWidgets, + )), + // 使用 Expanded 包裹一个新的 Column,以便底部组件从下往上排布 + Flexible( + flex: bottomFlex, + child: Column( + mainAxisAlignment: MainAxisAlignment.end, + children: bottomWidgets, + ), + ), + ], + ), + ), + ), + ); + } +} + +void navigateToSongDisplayPage(BuildContext context) { + Navigator.of(context).push( + CupertinoPageRoute( + builder: (context) => const SongDisplayPage(), + fullscreenDialog: true, + ), + ); +} diff --git a/lib/page/playing_music_page.dart b/lib/page/playing_music_page.dart index 88d2be7..8f4ccfb 100644 --- a/lib/page/playing_music_page.dart +++ b/lib/page/playing_music_page.dart @@ -25,12 +25,12 @@ class SongDisplayPage extends StatefulWidget { class SongDisplayPageState extends State { PageState pageState = PageState.main; int topFlex = 5; - int bottomFlex = 3; + int bottomFlex = 7; void onListBotton() { setState(() { if (pageState == PageState.list) { pageState = PageState.main; - bottomFlex = 3; + bottomFlex = 7; } else { pageState = PageState.list; bottomFlex = 2; @@ -42,7 +42,7 @@ class SongDisplayPageState extends State { setState(() { if (pageState == PageState.lyric) { pageState = PageState.main; - bottomFlex = 3; + bottomFlex = 7; } else { pageState = PageState.lyric; bottomFlex = 2; @@ -59,57 +59,73 @@ class SongDisplayPageState extends State { switch (pageState) { case PageState.main: topWidgets = [ - Padding(padding: EdgeInsets.only(top: screenHeight * 0.06)), - MusicArtPic( - imageSize: screenWidth * 0.9, - ), + // const Flexible( + // flex: 1, + // child: MusicArtPic(), + // ) ]; break; case PageState.list: topWidgets = [ Padding(padding: EdgeInsets.only(top: screenHeight * 0.03)), - const PlayingMusicCard(), - const Padding(padding: EdgeInsets.only(left: 30)), - Container( - padding: const EdgeInsets.only(left: 20), - alignment: Alignment.centerLeft, - child: const Text( - '待播清单', - style: TextStyle( - fontSize: 20.0, - fontWeight: FontWeight.bold, - ), - ), - ), + // const PlayingMusicCard(), + // const Padding(padding: EdgeInsets.only(left: 30)), + // Container( + // padding: const EdgeInsets.only(left: 20), + // alignment: Alignment.centerLeft, + // child: const Text( + // '待播清单', + // style: TextStyle( + // fontSize: 20.0, + // fontWeight: FontWeight.bold, + // ), + // ), + // ), const PlayMusicList(), ]; break; case PageState.lyric: topWidgets = [ Padding(padding: EdgeInsets.only(top: screenHeight * 0.03)), - const PlayingMusicCard(), - const Padding(padding: EdgeInsets.only(top: 10, bottom: 10)), - LyricDisplay(maxHeight: screenHeight * 0.55), + // const PlayingMusicCard(), + // const Padding(padding: EdgeInsets.only(top: 10, bottom: 10)), + // LyricDisplay(maxHeight: screenHeight * 0.55), ]; break; } // 底部组件,包括 ProgressSlider, QualityTime, ControlButton, VolumeSlider 和 BottomButton List bottomWidgets = [ - if (pageState == PageState.main) const MusicInfo(), - const ProgressSlider(), - const QualityTime(), - const Padding(padding: EdgeInsets.only(top: 40)), - ControlButton( - buttonSize: screenWidth * 0.1, - buttonSpacing: screenWidth * 0.2, + if (pageState == PageState.main) + const Flexible(flex: 2, child: MusicInfo()), + const Flexible( + flex: 1, + child: ProgressSlider(), + ), + const Flexible( + flex: 1, + child: QualityTime(), ), - const Padding(padding: EdgeInsets.only(top: 40)), - const VolumeSlider(), - BottomButton( - onList: onListBotton, - onLyric: onLyricBotton, + Flexible( + flex: 3, + child: ControlButton( + buttonSize: screenWidth * 0.1, + buttonSpacing: screenWidth * 0.2, + ), + ), + const Flexible( + flex: 1, + child: VolumeSlider(), ), - const Padding(padding: EdgeInsets.only(top: 5, bottom: 5)), + Flexible( + flex: 1, + child: Container( + padding: const EdgeInsets.only(top: 10, bottom: 10), + child: BottomButton( + onList: onListBotton, + onLyric: onLyricBotton, + ), + ), + ) ]; return DismissiblePage( diff --git a/lib/page/search_page.dart b/lib/page/search_page.dart index d0776d0..f706ff8 100644 --- a/lib/page/search_page.dart +++ b/lib/page/search_page.dart @@ -54,7 +54,6 @@ class SearchController extends GetxController { newItems.add(DisplayMusic(result)); } List uniqueItems = []; - log("new: ${newItems.length}"); if (pagingController.value.itemList != null) { for (DisplayMusic newItem in newItems) { diff --git a/lib/page/setting.dart b/lib/page/setting.dart index 12ced94..573012f 100644 --- a/lib/page/setting.dart +++ b/lib/page/setting.dart @@ -2,7 +2,6 @@ import 'package:app_rhyme/main.dart'; import 'package:app_rhyme/src/rust/api/cache.dart'; import 'package:app_rhyme/src/rust/api/config.dart'; import 'package:app_rhyme/types/extern_api.dart'; -import 'package:app_rhyme/types/music.dart'; import 'package:app_rhyme/util/default.dart'; import 'package:flutter/cupertino.dart'; import 'package:file_picker/file_picker.dart'; @@ -28,7 +27,7 @@ class SettingsPageState extends State { externApiPath: path, userAgreement: globalConfig.userAgreement); globalConfig.save(); } catch (e) { - log("设置第三方音乐源失败:$e"); + talker.error("[Setting Page] 设置第三方音乐源失败:$e"); } }); } diff --git a/lib/types/extern_api.dart b/lib/types/extern_api.dart index 8139394..77b7468 100644 --- a/lib/types/extern_api.dart +++ b/lib/types/extern_api.dart @@ -3,7 +3,6 @@ import 'dart:io'; import 'package:app_rhyme/types/http_wrap.dart'; import 'package:app_rhyme/types/music.dart'; import 'package:dart_eval/dart_eval.dart'; -import 'package:dart_eval/dart_eval_bridge.dart'; import 'package:dart_eval/stdlib/core.dart'; class ExternApi { @@ -20,7 +19,7 @@ class ExternApi { isBridge: true); } - Future getMusicPlayInfo( + Future getMusicPlayInfo( String source, String extra, ) async { @@ -29,7 +28,11 @@ class ExternApi { "getMusicPlayInfo", [$String("kuwo".toString()), $String(extra)]) as Future; - $Map<$Value, $Value> result = await resultFuture; - return PlayInfo.fromObject(result.$reified); + dynamic result = await resultFuture; + if (result.runtimeType != $null) { + return PlayInfo.fromObject(result.$reified); + } else { + return null; + } } } diff --git a/lib/types/http_wrap.dart b/lib/types/http_wrap.dart index ed9b95c..82038e3 100644 --- a/lib/types/http_wrap.dart +++ b/lib/types/http_wrap.dart @@ -6,20 +6,24 @@ import 'package:http/http.dart' as http; class HttpHelper { Future sendRequest(String method, Map headers, String url, String payload) async { - var uri = Uri.parse(url); - switch (method.toUpperCase()) { - case 'GET': - return (await http.get(uri, headers: headers)).body; - case 'POST': - return (await http.post(uri, headers: headers, body: payload)).body; - case 'PUT': - return (await http.put(uri, headers: headers, body: payload)).body; - case 'DELETE': - return (await http.delete(uri, headers: headers)).body; - case 'PATCH': - return (await http.patch(uri, headers: headers, body: payload)).body; - default: - throw Exception('Unsupported HTTP method: $method'); + try { + var uri = Uri.parse(url); + switch (method.toUpperCase()) { + case 'GET': + return (await http.get(uri, headers: headers)).body; + case 'POST': + return (await http.post(uri, headers: headers, body: payload)).body; + case 'PUT': + return (await http.put(uri, headers: headers, body: payload)).body; + case 'DELETE': + return (await http.delete(uri, headers: headers)).body; + case 'PATCH': + return (await http.patch(uri, headers: headers, body: payload)).body; + default: + return ""; + } + } catch (e) { + return ""; } } } diff --git a/lib/types/music.dart b/lib/types/music.dart index 2de7e2d..da9736f 100644 --- a/lib/types/music.dart +++ b/lib/types/music.dart @@ -7,7 +7,7 @@ import 'package:audio_service/audio_service.dart'; // 是一个原本只具有展示功能的DisplayMusicTuple通过请求第三方api变成可以播放的音乐 // 这个过程已经决定了一个音乐是否可以播放,因此本函数应该可能throw Exception -Future display2PlayMusic(DisplayMusic music, +Future display2PlayMusic(DisplayMusic music, [Quality? quality]) async { late Quality finalQuality; if (quality != null) { @@ -17,37 +17,45 @@ Future display2PlayMusic(DisplayMusic music, finalQuality = music.info.defaultQuality!; } else if (music.info.qualities.isNotEmpty) { finalQuality = music.info.qualities[0]; - log("音乐无默认音质,选择音质中第一个进行播放:$finalQuality"); + talker.log("[Display2PlayMusic] 音乐无默认音质,选择音质中第一个进行播放:$finalQuality"); } else { - throw Exception("音乐无可播放音质"); + talker.error("[Display2PlayMusic] 音乐没有可供播放的音质"); + return null; } } + // 音乐缓存获取的逻辑 - var extra = music.ref.getExtraInto(quality: finalQuality); + var result = music.toCacheFileNameAndExtra(); + if (result == null) { + return null; + } + var (cacheFileName, extra) = result; + // 尝试获取本地缓存 + var cache = await useCacheFile( + file: "", cachePath: musicCachePath, filename: cacheFileName); + // 有本地缓存直接返回 + if (cache != null) { + return PlayMusic(music.ref, music.info, PlayInfo(cache, finalQuality), + music.ref.getExtraInto(quality: finalQuality)); + } + // 没有本地缓存,也没有第三方api,直接返回null if (globalExternApi == null) { - log("无第三方音乐源,无法获取播放信息"); - throw Exception("无第三方音乐源,无法获取播放信息"); + talker.error("[Display2PlayMusic] 无第三方音乐源,无法获取播放信息"); } + var playinfo = await globalExternApi!.getMusicPlayInfo(music.info.source, extra); - var playMusic = PlayMusic(music.ref, music.info, playinfo, extra); - - String? file = await useCacheFile( - file: "", - cachePath: musicCachePath, - filename: playMusic.toCacheFileName()); - if (file != null) { - playMusic.playInfo.file = file; - playMusic.hasCache = true; - log("使用缓存后音乐进行播放: $file"); + // 如果第三方api夜叉找不到,直接返回null + if (playinfo == null) { + talker.error("[Display2PlayMusic] 第三方音乐源无法获取到playinfo: ${music.info.name}"); + return null; } + var playMusic = PlayMusic(music.ref, music.info, playinfo, extra); return playMusic; } -void log(String s) {} - class DisplayMusic { late MusicW ref; late MusicInfo info; @@ -58,16 +66,23 @@ class DisplayMusic { DisplayMusic.fromPlayMusic(PlayMusic music) { DisplayMusic(music.ref); } - String toCacheFileName() { + (String, String)? toCacheFileNameAndExtra() { if (info.defaultQuality == null) { - return ""; + return null; } - return "${info.name}_${info.artist.join(',')}_${info.source}_${ref.getExtraInto(quality: info.defaultQuality!).hashCode}.${info.defaultQuality!.format ?? "unknown"}"; + var extra = ref.getExtraInto(quality: info.defaultQuality!); + var cacheFileName = + "${info.name}_${info.artist.join(',')}_${info.source}_${extra.hashCode}.${info.defaultQuality!.format ?? "unknown"}"; + return (cacheFileName, extra); } Future hasCache() async { + var result = toCacheFileNameAndExtra(); + if (result == null) { + return false; + } var cache = await useCacheFile( - file: "", cachePath: musicCachePath, filename: toCacheFileName()); + file: "", cachePath: musicCachePath, filename: result.$1); if (cache != null) { return true; } else { diff --git a/lib/types/play_music_queue.dart b/lib/types/play_music_queue.dart index 293af9a..d41add3 100644 --- a/lib/types/play_music_queue.dart +++ b/lib/types/play_music_queue.dart @@ -1,218 +1,221 @@ -import 'dart:async'; -import 'package:app_rhyme/main.dart'; -import 'package:app_rhyme/src/rust/api/mirror.dart'; -import 'package:app_rhyme/types/music.dart'; -import 'package:get/get.dart'; +// import 'dart:async'; +// import 'package:app_rhyme/main.dart'; +// import 'package:app_rhyme/src/rust/api/mirror.dart'; +// import 'package:app_rhyme/types/music.dart'; +// import 'package:get/get.dart'; -class PlayMusicQueue extends GetxController { - final RxList musicList = RxList([]); - final Rx currentlyPlaying = Rx(null); - final Rx currentlyPlayingPlayinfo = Rx(null); - PlayMusicQueue(); +// class PlayMusicQueue extends GetxController { +// final RxList musicList = RxList([]); +// final Rx currentlyPlaying = Rx(null); +// final Rx currentlyPlayingPlayinfo = Rx(null); +// PlayMusicQueue(); - // 添加播放新音乐(已存在则调整至最后) - Future addMusic( - DisplayMusic music, - ) async { - // 需要将DisplayMusicTuple扩充成MusicTuple,才具备播放能力 - PlayMusic newMusic = await display2PlayMusic(music); - var existingIndex = musicList.indexWhere((m) => m.extra == newMusic.extra); - if (existingIndex != -1) { - // 如果音乐已经存在,移动到列表末尾 - var existingMusic = musicList.removeAt(existingIndex); - musicList.add(existingMusic); - currentlyPlaying.value = existingMusic; - currentlyPlayingPlayinfo.value = existingMusic.playInfo; - update(); - } else { - // 如果音乐不存在,添加到列表并设置为当前播放 - try { - var newMusic = await display2PlayMusic(music); - musicList.add(newMusic); - currentlyPlaying.value = newMusic; - currentlyPlayingPlayinfo.value = newMusic.playInfo; - update(); - } catch (e) { - log("Faild to build music tuple: $e"); - return null; - } - } - return currentlyPlaying.value; - } +// // 添加播放新音乐(已存在则调整至最后) +// Future addMusic( +// DisplayMusic music, +// ) async { +// // 需要将DisplayMusicTuple扩充成MusicTuple,才具备播放能力 +// PlayMusic? newMusic = await display2PlayMusic(music); +// if (newMusic == null) { +// return null; +// } +// var existingIndex = musicList.indexWhere((m) => m.extra == newMusic.extra); +// if (existingIndex != -1) { +// // 如果音乐已经存在,移动到列表末尾 +// var existingMusic = musicList.removeAt(existingIndex); +// musicList.add(existingMusic); +// currentlyPlaying.value = existingMusic; +// currentlyPlayingPlayinfo.value = existingMusic.playInfo; +// update(); +// } else { +// // 如果音乐不存在,添加到列表并设置为当前播放 +// try { +// var newMusic = await display2PlayMusic(music); +// musicList.add(newMusic); +// currentlyPlaying.value = newMusic; +// currentlyPlayingPlayinfo.value = newMusic.playInfo; +// update(); +// } catch (e) { +// log("Faild to build music tuple: $e"); +// return null; +// } +// } +// return currentlyPlaying.value; +// } - Future replaceMusic(PlayMusic newPlayMusic) async { - try { - // 查找具有相同extra的音乐索引 - int index = musicList.indexWhere((m) => m.extra == newPlayMusic.extra); - if (index != -1) { - // 如果找到,替换旧的PlayMusic - musicList[index] = newPlayMusic; - // 检查是否正在播放这首音乐 - if (currentlyPlaying.value?.extra == newPlayMusic.extra) { - currentlyPlaying.value = newPlayMusic; - currentlyPlayingPlayinfo.value = newPlayMusic.playInfo; - } - update(); - return currentlyPlaying.value; - } else { - log("未找到具有相同extra的音乐"); - return null; - } - } catch (e) { - log("替换音乐时出错: $e"); - return null; - } - } +// Future replaceMusic(PlayMusic newPlayMusic) async { +// try { +// // 查找具有相同extra的音乐索引 +// int index = musicList.indexWhere((m) => m.extra == newPlayMusic.extra); +// if (index != -1) { +// // 如果找到,替换旧的PlayMusic +// musicList[index] = newPlayMusic; +// // 检查是否正在播放这首音乐 +// if (currentlyPlaying.value?.extra == newPlayMusic.extra) { +// currentlyPlaying.value = newPlayMusic; +// currentlyPlayingPlayinfo.value = newPlayMusic.playInfo; +// } +// update(); +// return currentlyPlaying.value; +// } else { +// log("未找到具有相同extra的音乐"); +// return null; +// } +// } catch (e) { +// log("替换音乐时出错: $e"); +// return null; +// } +// } - Future replaceAllMusics( - List musics, - ) async { - musicList.clear(); - update(); - for (var music in musics) { - musicList.add(await display2PlayMusic(music)); - } - var firstMusic = _getIndex(0); - currentlyPlaying.value = firstMusic; - currentlyPlayingPlayinfo.value = firstMusic?.playInfo; - update(); - return firstMusic; - } +// Future replaceAllMusics( +// List musics, +// ) async { +// musicList.clear(); +// update(); +// for (var music in musics) { +// musicList.add(await display2PlayMusic(music)); +// } +// var firstMusic = _getIndex(0); +// currentlyPlaying.value = firstMusic; +// currentlyPlayingPlayinfo.value = firstMusic?.playInfo; +// update(); +// return firstMusic; +// } - // 这里我们认为跳到同一首歌也是改变了(改变进度从头开始) - PlayMusic? skipToMusic(int index) { - var music = _getIndex(index); - if (music != null) { - currentlyPlaying.value = music; - currentlyPlayingPlayinfo.value = music.playInfo; - update(); - return currentlyPlaying.value; - } else { - log("try to play invaild index:$index music"); - return null; - } - } +// // 这里我们认为跳到同一首歌也是改变了(改变进度从头开始) +// PlayMusic? skipToMusic(int index) { +// var music = _getIndex(index); +// if (music != null) { +// currentlyPlaying.value = music; +// currentlyPlayingPlayinfo.value = music.playInfo; +// update(); +// return currentlyPlaying.value; +// } else { +// log("try to play invaild index:$index music"); +// return null; +// } +// } - // 播放下一首音乐 - PlayMusic? skipToNext() { - if (musicList.isNotEmpty) { - int currentIndex = currentlyPlaying.value != null - ? musicList.indexOf(currentlyPlaying.value!) - : -1; - int nextIndex = (currentIndex + 1) % musicList.length; // 循环播放 - currentlyPlaying.value = musicList[nextIndex]; - currentlyPlayingPlayinfo.value = musicList[nextIndex].playInfo; - update(); - return currentlyPlaying.value; - } else { - log("try to play next music, but no music found"); - return null; - } - } +// // 播放下一首音乐 +// PlayMusic? skipToNext() { +// if (musicList.isNotEmpty) { +// int currentIndex = currentlyPlaying.value != null +// ? musicList.indexOf(currentlyPlaying.value!) +// : -1; +// int nextIndex = (currentIndex + 1) % musicList.length; // 循环播放 +// currentlyPlaying.value = musicList[nextIndex]; +// currentlyPlayingPlayinfo.value = musicList[nextIndex].playInfo; +// update(); +// return currentlyPlaying.value; +// } else { +// log("try to play next music, but no music found"); +// return null; +// } +// } - // 播放上一首音乐 - PlayMusic? skipToPrevious() { - if (musicList.isNotEmpty) { - int currentIndex = currentlyPlaying.value != null - ? musicList.indexOf(currentlyPlaying.value!) - : musicList.length; - int previousIndex = - (currentIndex - 1 + musicList.length) % musicList.length; // 循环播放 - currentlyPlaying.value = musicList[previousIndex]; - currentlyPlayingPlayinfo.value = musicList[previousIndex].playInfo; - update(); - return currentlyPlaying.value; - } else { - log("try to play previous music, but no music found"); - return null; - } - } +// // 播放上一首音乐 +// PlayMusic? skipToPrevious() { +// if (musicList.isNotEmpty) { +// int currentIndex = currentlyPlaying.value != null +// ? musicList.indexOf(currentlyPlaying.value!) +// : musicList.length; +// int previousIndex = +// (currentIndex - 1 + musicList.length) % musicList.length; // 循环播放 +// currentlyPlaying.value = musicList[previousIndex]; +// currentlyPlayingPlayinfo.value = musicList[previousIndex].playInfo; +// update(); +// return currentlyPlaying.value; +// } else { +// log("try to play previous music, but no music found"); +// return null; +// } +// } - // 删除指定索引的音乐 - PlayMusic? delIndex(int index) { - try { - if (currentlyPlaying.value == musicList[index]) { - currentlyPlaying.value = null; - currentlyPlayingPlayinfo.value = null; - update(); - log("del playingMusic, stop playiing"); - } - musicList.removeAt(index); - update(); - return currentlyPlaying.value; - } catch (_) { - log("try to del music of index:$index, but not found."); - } - return null; - } +// // 删除指定索引的音乐 +// PlayMusic? delIndex(int index) { +// try { +// if (currentlyPlaying.value == musicList[index]) { +// currentlyPlaying.value = null; +// currentlyPlayingPlayinfo.value = null; +// update(); +// log("del playingMusic, stop playiing"); +// } +// musicList.removeAt(index); +// update(); +// return currentlyPlaying.value; +// } catch (_) { +// log("try to del music of index:$index, but not found."); +// } +// return null; +// } - Future _changePlayingMusicQuality( - int index, - Quality quality, - ) async { - var music = _getIndex(index); - if (music != null) { - try { - if (globalExternApi == null) { - log("无第三方音乐源,无法获取播放信息"); - throw Exception("无第三方音乐源,无法获取播放信息"); - } - var playinfo = await globalExternApi!.getMusicPlayInfo( - music.info.source, music.ref.getExtraInto(quality: quality)); - music.playInfo = playinfo; +// Future _changePlayingMusicQuality( +// int index, +// Quality quality, +// ) async { +// var music = _getIndex(index); +// if (music != null) { +// try { +// if (globalExternApi == null) { +// log("无第三方音乐源,无法获取播放信息"); +// throw Exception("无第三方音乐源,无法获取播放信息"); +// } +// var playinfo = await globalExternApi!.getMusicPlayInfo( +// music.info.source, music.ref.getExtraInto(quality: quality)); +// music.playInfo = playinfo; - if (currentlyPlaying.value == music) { - currentlyPlaying.value = music; - currentlyPlayingPlayinfo.value = music.playInfo; - update(); - return currentlyPlaying.value; - } - } catch (e) { - log("Failed to change quality for $index: $e"); - return null; - } - } - return null; - } +// if (currentlyPlaying.value == music) { +// currentlyPlaying.value = music; +// currentlyPlayingPlayinfo.value = music.playInfo; +// update(); +// return currentlyPlaying.value; +// } +// } catch (e) { +// log("Failed to change quality for $index: $e"); +// return null; +// } +// } +// return null; +// } - // 重新排序音乐,我们不认为重新排序要播放别的,故可以不管 - void reorderMusic(int oldIndex, int newIndex) { - if (oldIndex < newIndex) { - newIndex -= 1; - } - final PlayMusic music = musicList.removeAt(oldIndex); - musicList.insert(newIndex, music); - update(); - } +// // 重新排序音乐,我们不认为重新排序要播放别的,故可以不管 +// void reorderMusic(int oldIndex, int newIndex) { +// if (oldIndex < newIndex) { +// newIndex -= 1; +// } +// final PlayMusic music = musicList.removeAt(oldIndex); +// musicList.insert(newIndex, music); +// update(); +// } - // 改变当前正在播放的音乐的音质 - Future changeCurrentPlayingQuality(Quality quality) async { - if (currentlyPlaying.value != null) { - return await _changePlayingMusicQuality( - musicList.indexOf(currentlyPlaying.value!), quality); - } - log("try to change playingMusic quality, but no music is playing"); - return null; - } +// // 改变当前正在播放的音乐的音质 +// Future changeCurrentPlayingQuality(Quality quality) async { +// if (currentlyPlaying.value != null) { +// return await _changePlayingMusicQuality( +// musicList.indexOf(currentlyPlaying.value!), quality); +// } +// log("try to change playingMusic quality, but no music is playing"); +// return null; +// } - // 私有方法,用于获取指定索引的音乐 - PlayMusic? _getIndex(int index) { - try { - return musicList[index]; - } catch (_) { - return null; - } - } +// // 私有方法,用于获取指定索引的音乐 +// PlayMusic? _getIndex(int index) { +// try { +// return musicList[index]; +// } catch (_) { +// return null; +// } +// } - List downCast() { - List rst = []; - for (var m in musicList) { - rst.add(DisplayMusic(m.ref)); - } - return rst; - } +// List downCast() { +// List rst = []; +// for (var m in musicList) { +// rst.add(DisplayMusic(m.ref)); +// } +// return rst; +// } - // 公共属性和方法 - int get length => musicList.length; - RxList get allMusic => musicList; -} +// // 公共属性和方法 +// int get length => musicList.length; +// RxList get allMusic => musicList; +// } diff --git a/lib/util/audio_controller.dart b/lib/util/audio_controller.dart index 41dbcf4..3b1e7d3 100644 --- a/lib/util/audio_controller.dart +++ b/lib/util/audio_controller.dart @@ -48,14 +48,8 @@ class AudioHandler extends GetxController { Future addMusicPlay(DisplayMusic music) async { try { - PlayMusic playMusic; - try { - playMusic = await display2PlayMusic(music); - } catch (e) { - talker.error( - "[Error Music Handler] In addMusicPlay, Failed to diaplayMusic2PlayMusic: $e"); - return; - } + PlayMusic? playMusic = await display2PlayMusic(music); + if (playMusic == null) return; var index = playMusicList .indexWhere((element) => element.extra == playMusic.extra); @@ -66,16 +60,19 @@ class AudioHandler extends GetxController { // 添加新的音乐 playMusicList.add(playMusic); - await playSourceList.add(AudioSource.uri( - Uri.parse(playMusic.playInfo.file), - tag: playMusic.toMediaItem())); - - playingMusic.value = playMusic; - update(); + if (playMusic.playInfo.file.contains("http")) { + await playSourceList.add(AudioSource.uri( + Uri.parse(playMusic.playInfo.file), + tag: playMusic.toMediaItem())); + } else { + await playSourceList.add(AudioSource.file(playMusic.playInfo.file, + tag: playMusic.toMediaItem())); + } // 播放新的音乐 await _player.seek(Duration.zero, index: playSourceList.length - 1); + updateRx(); await _player.play(); } catch (e) { talker.error("[Error Music Handler] In addMusicPlay, Error occur: $e"); @@ -96,9 +93,8 @@ class AudioHandler extends GetxController { if (shouldPlay) { // 重新播放这个位置的音乐 await _player.seek(Duration.zero, index: index); + updateRx(); await _player.play(); - playingMusic.value = playMusic; - update(); } } } catch (e) { @@ -113,23 +109,17 @@ class AudioHandler extends GetxController { List newPlayMusics = []; List newAudioSources = []; for (var music in musics) { - try { - var playMusic = await display2PlayMusic(music); - newPlayMusics.add(playMusic); - newAudioSources.add(AudioSource.uri(Uri.parse(playMusic.playInfo.file), - tag: playMusic.toMediaItem())); - } catch (e) { - talker.error( - "[Error Music Handler] In clearReplaceMusicAll, Failed to diaplayMusic2PlayMusic: $e"); - } + var playMusic = await display2PlayMusic(music); + if (playMusic == null) continue; + newPlayMusics.add(playMusic); + newAudioSources.add(AudioSource.uri(Uri.parse(playMusic.playInfo.file), + tag: playMusic.toMediaItem())); } await clear(); playMusicList.addAll(newPlayMusics); try { - // await player - // .setAudioSource(ConcatenatingAudioSource(children: newAudioSources)); await playSourceList.addAll(newAudioSources); } catch (e) { talker.error( @@ -137,12 +127,10 @@ class AudioHandler extends GetxController { } talker.log( "[Log Music Handler] After add all: crt playMusicList length:${playMusicList.length},crt playSourceList length:${playSourceList.length}"); - await _player.seek(Duration.zero, index: playSourceList.length - 1); + await _player.seek(Duration.zero, index: 0); + updateRx(); await _player.play(); - - playingMusic.value = playMusicList[playMusicList.length - 1]; - update(); } Future _insert(int index, PlayMusic music) async { @@ -162,7 +150,7 @@ class AudioHandler extends GetxController { await playSourceList.clear(); update(); } - playingMusic.value = null; + updateRx(); } Future removeAt(int index) async { @@ -170,40 +158,20 @@ class AudioHandler extends GetxController { // 如果正在播放,先暂停 if (_player.currentIndex != null && _player.currentIndex! == index) { await _player.pause(); - playingMusic.value = null; - update(); } playMusicList.removeAt(index); await playSourceList.removeAt(index); + updateRx(); } - // PlayMusic? playingMusic.value { - // if (player.currentIndex != null && - // player.currentIndex != -1 && - // playMusicList.isNotEmpty) { - // try { - // return playMusicList[player.currentIndex!]; - // } catch (e) { - // talker.error( - // "[Error Music Handler] Failed to get playingMusic when index is not null: $e"); - // } - // } - // return null; - // } Future seekToNext() async { await _player.seekToNext(); - if (_player.currentIndex != null) { - playingMusic.value = playMusicList[_player.currentIndex!]; - update(); - } + updateRx(); } Future seekToPrevious() async { await _player.seekToPrevious(); - if (_player.currentIndex != null) { - playingMusic.value = playMusicList[_player.currentIndex!]; - update(); - } + updateRx(); } Future pause() async { @@ -226,6 +194,22 @@ class AudioHandler extends GetxController { return _player.createPositionStream( steps: steps, minPeriod: minPeriod, maxPeriod: maxPeriod); } + + void updateRx() { + if (playMusicList.isNotEmpty && _player.currentIndex != null) { + try { + playingMusic.value = playMusicList[_player.currentIndex!]; + } catch (e) { + talker.error("[Music Handler] Failed to updateRx,set null"); + playingMusic.value = null; + } + } else { + playingMusic.value = null; + } + talker.log( + "[Music Handler] Called updateRx: playingMusic: ${playingMusic.value?.info.name ?? "No music"}"); + update(); + } } Future initGlobalAudioUiController() async { diff --git a/lib/util/helper.dart b/lib/util/helper.dart index 7db586e..6b3a752 100644 --- a/lib/util/helper.dart +++ b/lib/util/helper.dart @@ -128,3 +128,16 @@ Future getMusicImageProvider( } return image; } + +Future useCacheImage(String file) async { + var cache = await useCacheFile(file: file, cachePath: picCachePath); + if (cache != null) { + return Image.file(File(cache)); + } else { + if (file.contains("http")) { + return Image.network(file); + } else { + return Image.file(File(file)); + } + } +} diff --git a/lib/util/window.dart b/lib/util/window.dart index 79332e2..d413f39 100644 --- a/lib/util/window.dart +++ b/lib/util/window.dart @@ -14,7 +14,7 @@ Future initWindow() async { skipTaskbar: false, titleBarStyle: TitleBarStyle.normal, ); - windowManager.setResizable(false); + // windowManager.setResizable(false); windowManager.waitUntilReadyToShow(windowOptions, () async { await windowManager.show(); await windowManager.focus(); diff --git a/pubspec.lock b/pubspec.lock index cab62bb..d57b671 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -331,6 +331,54 @@ packages: url: "https://pub.dev" source: hosted version: "15.0.0" + flutter_keyboard_visibility: + dependency: "direct main" + description: + name: flutter_keyboard_visibility + sha256: "98664be7be0e3ffca00de50f7f6a287ab62c763fc8c762e0a21584584a3ff4f8" + url: "https://pub.dev" + source: hosted + version: "6.0.0" + flutter_keyboard_visibility_linux: + dependency: transitive + description: + name: flutter_keyboard_visibility_linux + sha256: "6fba7cd9bb033b6ddd8c2beb4c99ad02d728f1e6e6d9b9446667398b2ac39f08" + url: "https://pub.dev" + source: hosted + version: "1.0.0" + flutter_keyboard_visibility_macos: + dependency: transitive + description: + name: flutter_keyboard_visibility_macos + sha256: c5c49b16fff453dfdafdc16f26bdd8fb8d55812a1d50b0ce25fc8d9f2e53d086 + url: "https://pub.dev" + source: hosted + version: "1.0.0" + flutter_keyboard_visibility_platform_interface: + dependency: transitive + description: + name: flutter_keyboard_visibility_platform_interface + sha256: e43a89845873f7be10cb3884345ceb9aebf00a659f479d1c8f4293fcb37022a4 + url: "https://pub.dev" + source: hosted + version: "2.0.0" + flutter_keyboard_visibility_web: + dependency: transitive + description: + name: flutter_keyboard_visibility_web + sha256: d3771a2e752880c79203f8d80658401d0c998e4183edca05a149f5098ce6e3d1 + url: "https://pub.dev" + source: hosted + version: "2.0.0" + flutter_keyboard_visibility_windows: + dependency: transitive + description: + name: flutter_keyboard_visibility_windows + sha256: fc4b0f0b6be9b93ae527f3d527fb56ee2d918cd88bbca438c478af7bcfd0ef73 + url: "https://pub.dev" + source: hosted + version: "1.0.0" flutter_launcher_icons: dependency: "direct dev" description: diff --git a/pubspec.yaml b/pubspec.yaml index 1bf6df8..0ae882c 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -42,6 +42,7 @@ dependencies: talker: ^4.1.5 just_audio_background: ^0.0.1-beta.11 audio_session: ^0.1.19 + flutter_keyboard_visibility: ^6.0.0 # audioplayers: ^6.0.0 dev_dependencies: From 1e323528650b925ab3359a13b69c51a84040e0de Mon Sep 17 00:00:00 2001 From: canxin Date: Fri, 3 May 2024 02:34:18 +0800 Subject: [PATCH 03/28] [Feat] Add runtime log page. [Refactor] use async wait in clearReplaceAllMusic. --- lib/main.dart | 6 ++ lib/page/setting.dart | 14 ++++ lib/types/music.dart | 2 +- lib/util/audio_controller.dart | 20 ++++-- lib/util/helper.dart | 37 +++-------- linux/flutter/generated_plugin_registrant.cc | 4 ++ linux/flutter/generated_plugins.cmake | 1 + macos/Flutter/GeneratedPluginRegistrant.swift | 2 + pubspec.lock | 64 +++++++++++++++++++ pubspec.yaml | 1 + .../flutter/generated_plugin_registrant.cc | 6 ++ windows/flutter/generated_plugins.cmake | 2 + 12 files changed, 123 insertions(+), 36 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index 2fcaa0c..b0643e8 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -8,6 +8,7 @@ import 'package:app_rhyme/util/window.dart'; import 'package:chinese_font_library/chinese_font_library.dart'; import 'package:flutter/cupertino.dart'; import 'package:app_rhyme/src/rust/frb_generated.dart'; +import 'package:flutter/material.dart'; import 'package:path_provider/path_provider.dart'; import 'package:talker/talker.dart'; @@ -46,6 +47,11 @@ class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return CupertinoApp( + localizationsDelegates: const [ + DefaultMaterialLocalizations.delegate, + DefaultCupertinoLocalizations.delegate, + DefaultWidgetsLocalizations.delegate, + ], title: 'AppRhyme', theme: CupertinoThemeData( primaryColor: CupertinoColors.black, diff --git a/lib/page/setting.dart b/lib/page/setting.dart index 573012f..554b58b 100644 --- a/lib/page/setting.dart +++ b/lib/page/setting.dart @@ -5,6 +5,8 @@ import 'package:app_rhyme/types/extern_api.dart'; import 'package:app_rhyme/util/default.dart'; import 'package:flutter/cupertino.dart'; import 'package:file_picker/file_picker.dart'; +import 'package:flutter/material.dart'; +import 'package:talker_flutter/talker_flutter.dart'; class SettingsPage extends StatefulWidget { const SettingsPage({super.key}); @@ -110,6 +112,18 @@ class SettingsPageState extends State { ), ), ), + CupertinoFormRow( + prefix: const Text("运行日志"), + child: CupertinoButton( + child: const Icon( + CupertinoIcons.book, + color: CupertinoColors.activeGreen, + ), + onPressed: () { + Navigator.of(context).push(CupertinoPageRoute( + builder: (context) => TalkerScreen(talker: talker), + )); + })) ], ), ], diff --git a/lib/types/music.dart b/lib/types/music.dart index da9736f..c385c1a 100644 --- a/lib/types/music.dart +++ b/lib/types/music.dart @@ -17,7 +17,7 @@ Future display2PlayMusic(DisplayMusic music, finalQuality = music.info.defaultQuality!; } else if (music.info.qualities.isNotEmpty) { finalQuality = music.info.qualities[0]; - talker.log("[Display2PlayMusic] 音乐无默认音质,选择音质中第一个进行播放:$finalQuality"); + talker.info("[Display2PlayMusic] 音乐无默认音质,选择音质中第一个进行播放:$finalQuality"); } else { talker.error("[Display2PlayMusic] 音乐没有可供播放的音质"); return null; diff --git a/lib/util/audio_controller.dart b/lib/util/audio_controller.dart index 3b1e7d3..f9fd33a 100644 --- a/lib/util/audio_controller.dart +++ b/lib/util/audio_controller.dart @@ -104,12 +104,20 @@ class AudioHandler extends GetxController { } Future clearReplaceMusicAll(List musics) async { - talker.log( + talker.info( "[Log Music Handler] Request to add all musics of length: ${musics.length}"); List newPlayMusics = []; List newAudioSources = []; + List> futures = []; + +// 首先创建所有的future,但不等待它们 for (var music in musics) { - var playMusic = await display2PlayMusic(music); + futures.add(display2PlayMusic(music)); + } + +// 然后等待所有future完成,并按顺序处理结果 + List playMusicsResults = await Future.wait(futures); + for (var playMusic in playMusicsResults) { if (playMusic == null) continue; newPlayMusics.add(playMusic); newAudioSources.add(AudioSource.uri(Uri.parse(playMusic.playInfo.file), @@ -125,7 +133,7 @@ class AudioHandler extends GetxController { talker.error( "[Error Music Handler] In clearReplaceMusicAll, Failed to diaplayMusic2PlayMusic: $e"); } - talker.log( + talker.info( "[Log Music Handler] After add all: crt playMusicList length:${playMusicList.length},crt playSourceList length:${playSourceList.length}"); await _player.seek(Duration.zero, index: 0); @@ -142,7 +150,7 @@ class AudioHandler extends GetxController { } Future clear() async { - talker.log("[Log Music Handler] Request to clear all musics"); + talker.info("[Log Music Handler] Request to clear all musics"); if (playMusicList.isNotEmpty) { playMusicList.clear(); } @@ -154,7 +162,7 @@ class AudioHandler extends GetxController { } Future removeAt(int index) async { - talker.log("[Log Music Handler] Request to remove music of index:$index"); + talker.info("[Log Music Handler] Request to remove music of index:$index"); // 如果正在播放,先暂停 if (_player.currentIndex != null && _player.currentIndex! == index) { await _player.pause(); @@ -206,7 +214,7 @@ class AudioHandler extends GetxController { } else { playingMusic.value = null; } - talker.log( + talker.info( "[Music Handler] Called updateRx: playingMusic: ${playingMusic.value?.info.name ?? "No music"}"); update(); } diff --git a/lib/util/helper.dart b/lib/util/helper.dart index 6b3a752..d3810bb 100644 --- a/lib/util/helper.dart +++ b/lib/util/helper.dart @@ -1,6 +1,7 @@ import 'dart:developer'; import 'dart:io'; +import 'package:app_rhyme/main.dart'; import 'package:app_rhyme/src/rust/api/cache.dart'; import 'package:app_rhyme/src/rust/api/mirror.dart'; import 'package:app_rhyme/util/audio_controller.dart'; @@ -15,11 +16,10 @@ Future fileCacheHelper(String file, String cachePath) async { cachePath: cachePath, ); if (localSource != null) { - log("fileCacheHelper: 使用已缓存source: ($file)->($localSource)"); + talker.debug("fileCacheHelper: 使用已缓存source: ($file)->($localSource)"); toUseSource = localSource; } else { - log("fileCacheHelper: 不缓存,直接使用 $file"); - + talker.debug("fileCacheHelper: 不缓存,直接使用 $file"); toUseSource = file; } @@ -112,32 +112,11 @@ Future getMusicListImage(MusicList musicList, bool useCache) async { return image; } -Future getMusicImageProvider( - MusicList musicList, bool useCache) async { - late ImageProvider image; - if (musicList.artPic.isNotEmpty) { - String url = musicList.artPic; - String source = await fileCacheHelper(url, picCachePath); - if (source.contains("http")) { - image = NetworkImage(source); - } else { - image = FileImage(File(source)); - } +Future useCacheImage(String file_) async { + var file = await fileCacheHelper(file_, picCachePath); + if (file.contains("http")) { + return Image.network(file); } else { - image = defaultArtPicProvider; - } - return image; -} - -Future useCacheImage(String file) async { - var cache = await useCacheFile(file: file, cachePath: picCachePath); - if (cache != null) { - return Image.file(File(cache)); - } else { - if (file.contains("http")) { - return Image.network(file); - } else { - return Image.file(File(file)); - } + return Image.file(File(file)); } } diff --git a/linux/flutter/generated_plugin_registrant.cc b/linux/flutter/generated_plugin_registrant.cc index fdef6b6..19d24f2 100644 --- a/linux/flutter/generated_plugin_registrant.cc +++ b/linux/flutter/generated_plugin_registrant.cc @@ -9,6 +9,7 @@ #include #include #include +#include #include void fl_register_plugins(FlPluginRegistry* registry) { @@ -21,6 +22,9 @@ void fl_register_plugins(FlPluginRegistry* registry) { g_autoptr(FlPluginRegistrar) screen_retriever_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "ScreenRetrieverPlugin"); screen_retriever_plugin_register_with_registrar(screen_retriever_registrar); + g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin"); + url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar); g_autoptr(FlPluginRegistrar) window_manager_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "WindowManagerPlugin"); window_manager_plugin_register_with_registrar(window_manager_registrar); diff --git a/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake index cf7a49e..f5ebbfc 100644 --- a/linux/flutter/generated_plugins.cmake +++ b/linux/flutter/generated_plugins.cmake @@ -6,6 +6,7 @@ list(APPEND FLUTTER_PLUGIN_LIST file_selector_linux flutter_volume_controller screen_retriever + url_launcher_linux window_manager ) diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index c928a5e..f2662e0 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -12,6 +12,7 @@ import flutter_volume_controller import just_audio import path_provider_foundation import screen_retriever +import share_plus import sqflite import window_manager @@ -23,6 +24,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { JustAudioPlugin.register(with: registry.registrar(forPlugin: "JustAudioPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) ScreenRetrieverPlugin.register(with: registry.registrar(forPlugin: "ScreenRetrieverPlugin")) + SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin")) SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin")) WindowManagerPlugin.register(with: registry.registrar(forPlugin: "WindowManagerPlugin")) } diff --git a/pubspec.lock b/pubspec.lock index d57b671..223b9a0 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -475,6 +475,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.2" + group_button: + dependency: transitive + description: + name: group_button + sha256: "0610fcf28ed122bfb4b410fce161a390f7f2531d55d1d65c5375982001415940" + url: "https://pub.dev" + source: hosted + version: "5.3.4" html: dependency: transitive description: @@ -896,6 +904,22 @@ packages: url: "https://pub.dev" source: hosted version: "0.1.9" + share_plus: + dependency: transitive + description: + name: share_plus + sha256: ef3489a969683c4f3d0239010cc8b7a2a46543a8d139e111c06c558875083544 + url: "https://pub.dev" + source: hosted + version: "9.0.0" + share_plus_platform_interface: + dependency: transitive + description: + name: share_plus_platform_interface + sha256: "0f9e4418835d1b2c3ae78fdb918251959106cefdbc4dd43526e182f80e82f6d4" + url: "https://pub.dev" + source: hosted + version: "4.0.0" sky_engine: dependency: transitive description: flutter @@ -989,6 +1013,14 @@ packages: url: "https://pub.dev" source: hosted version: "4.1.5" + talker_flutter: + dependency: "direct main" + description: + name: talker_flutter + sha256: "981da0bb1501a9937f914574b5987f839e363a8402f613addbbf9ebe7e8393c1" + url: "https://pub.dev" + source: hosted + version: "4.1.5" talker_logger: dependency: transitive description: @@ -1021,6 +1053,38 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.2" + url_launcher_linux: + dependency: transitive + description: + name: url_launcher_linux + sha256: ab360eb661f8879369acac07b6bb3ff09d9471155357da8443fd5d3cf7363811 + url: "https://pub.dev" + source: hosted + version: "3.1.1" + url_launcher_platform_interface: + dependency: transitive + description: + name: url_launcher_platform_interface + sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029" + url: "https://pub.dev" + source: hosted + version: "2.3.2" + url_launcher_web: + dependency: transitive + description: + name: url_launcher_web + sha256: "8d9e750d8c9338601e709cd0885f95825086bd8b642547f26bda435aade95d8a" + url: "https://pub.dev" + source: hosted + version: "2.3.1" + url_launcher_windows: + dependency: transitive + description: + name: url_launcher_windows + sha256: ecf9725510600aa2bb6d7ddabe16357691b6d2805f66216a97d1b881e21beff7 + url: "https://pub.dev" + source: hosted + version: "3.1.1" uuid: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 0ae882c..815525c 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -43,6 +43,7 @@ dependencies: just_audio_background: ^0.0.1-beta.11 audio_session: ^0.1.19 flutter_keyboard_visibility: ^6.0.0 + talker_flutter: ^4.1.5 # audioplayers: ^6.0.0 dev_dependencies: diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index 3e352b2..ded73b0 100644 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -10,6 +10,8 @@ #include #include #include +#include +#include #include void RegisterPlugins(flutter::PluginRegistry* registry) { @@ -21,6 +23,10 @@ void RegisterPlugins(flutter::PluginRegistry* registry) { registry->GetRegistrarForPlugin("JustAudioWindowsPlugin")); ScreenRetrieverPluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("ScreenRetrieverPlugin")); + SharePlusWindowsPluginCApiRegisterWithRegistrar( + registry->GetRegistrarForPlugin("SharePlusWindowsPluginCApi")); + UrlLauncherWindowsRegisterWithRegistrar( + registry->GetRegistrarForPlugin("UrlLauncherWindows")); WindowManagerPluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("WindowManagerPlugin")); } diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index f39806d..334e472 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -7,6 +7,8 @@ list(APPEND FLUTTER_PLUGIN_LIST flutter_volume_controller just_audio_windows screen_retriever + share_plus + url_launcher_windows window_manager ) From 81951409ae9f072ecd96c6719fa1928b05caa4e9 Mon Sep 17 00:00:00 2001 From: canxin Date: Fri, 3 May 2024 13:01:35 +0800 Subject: [PATCH 04/28] [Add] Add more log. [Fix] Add music play, use remove music. --- lib/comp/play_page_comp/music_list.dart | 2 +- lib/main.dart | 12 ++- lib/page/playing_music_page.dart | 66 +++++++------- lib/types/music.dart | 4 +- lib/util/audio_controller.dart | 109 +++++++++++++++++------- lib/util/helper.dart | 5 +- 6 files changed, 128 insertions(+), 70 deletions(-) diff --git a/lib/comp/play_page_comp/music_list.dart b/lib/comp/play_page_comp/music_list.dart index 521e473..3068f23 100644 --- a/lib/comp/play_page_comp/music_list.dart +++ b/lib/comp/play_page_comp/music_list.dart @@ -33,7 +33,7 @@ class PlayMusicList extends StatelessWidget { .hashCode), music: musics[index], onClick: () { - globalAudioHandler.seek(Duration.zero, index); + globalAudioHandler.seek(Duration.zero, index: index); }, onPress: () { showCupertinoPopupWithActions(context: context, options: [ diff --git a/lib/main.dart b/lib/main.dart index b0643e8..3d5e42b 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,3 +1,5 @@ +import 'dart:ui'; + import 'package:app_rhyme/page/home.dart'; import 'package:app_rhyme/src/rust/api/config.dart'; import 'package:app_rhyme/src/rust/api/init.dart'; @@ -17,13 +19,19 @@ Talker talker = Talker(); late SqlMusicFactoryW globalSqlMusicFactory; late Config globalConfig; ExternApi? globalExternApi; - Future main() async { WidgetsFlutterBinding.ensureInitialized(); await initWindow(); await RustLib.init(); - + FlutterError.onError = (details) { + FlutterError.presentError(details); + talker.error("[Flutter Error] $details"); + }; + PlatformDispatcher.instance.onError = (error, stack) { + talker.error("[PlatForm Error] Error: $error\nStackTrace: $stack"); + return true; + }; String rootPath = (await getApplicationDocumentsDirectory()).path; var stores = await initStore(storeRoot: rootPath); globalSqlMusicFactory = stores.$1; diff --git a/lib/page/playing_music_page.dart b/lib/page/playing_music_page.dart index 8f4ccfb..2135eb0 100644 --- a/lib/page/playing_music_page.dart +++ b/lib/page/playing_music_page.dart @@ -134,40 +134,40 @@ class SongDisplayPageState extends State { backgroundColor: CupertinoColors.white, onDismissed: () => Navigator.of(context).pop(), child: Container( - clipBehavior: Clip.antiAliasWithSaveLayer, - decoration: const BoxDecoration( - gradient: LinearGradient( - begin: Alignment.topRight, - end: Alignment.bottomLeft, - colors: [ - CupertinoColors.systemGrey2, - CupertinoColors.systemGrey, - ], - ), - ), - child: ProAnimatedBlur( - blur: 500, - duration: const Duration(milliseconds: 0), - child: Column( - children: [ - Flexible( - flex: topFlex, - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - children: topWidgets, - )), - // 使用 Expanded 包裹一个新的 Column,以便底部组件从下往上排布 - Flexible( - flex: bottomFlex, - child: Column( - mainAxisAlignment: MainAxisAlignment.end, - children: bottomWidgets, - ), - ), - ], + // clipBehavior: Clip.antiAliasWithSaveLayer, + // decoration: const BoxDecoration( + // gradient: LinearGradient( + // begin: Alignment.topRight, + // end: Alignment.bottomLeft, + // colors: [ + // CupertinoColors.systemGrey2, + // CupertinoColors.systemGrey, + // ], + // ), + // ), + // child: ProAnimatedBlur( + // blur: 500, + // duration: const Duration(milliseconds: 0), + // child: Column( + // children: [ + // Flexible( + // flex: topFlex, + // child: Column( + // mainAxisAlignment: MainAxisAlignment.start, + // children: topWidgets, + // )), + // // 使用 Expanded 包裹一个新的 Column,以便底部组件从下往上排布 + // Flexible( + // flex: bottomFlex, + // child: Column( + // mainAxisAlignment: MainAxisAlignment.end, + // children: bottomWidgets, + // ), + // ), + // ], + // ), + // ), ), - ), - ), ); } } diff --git a/lib/types/music.dart b/lib/types/music.dart index c385c1a..3d67757 100644 --- a/lib/types/music.dart +++ b/lib/types/music.dart @@ -35,6 +35,7 @@ Future display2PlayMusic(DisplayMusic music, file: "", cachePath: musicCachePath, filename: cacheFileName); // 有本地缓存直接返回 if (cache != null) { + talker.info("[Display2PlayMusic] 使用本地歌曲缓存转化歌曲: ${music.info.name}"); return PlayMusic(music.ref, music.info, PlayInfo(cache, finalQuality), music.ref.getExtraInto(quality: finalQuality)); } @@ -51,8 +52,9 @@ Future display2PlayMusic(DisplayMusic music, talker.error("[Display2PlayMusic] 第三方音乐源无法获取到playinfo: ${music.info.name}"); return null; } - var playMusic = PlayMusic(music.ref, music.info, playinfo, extra); + talker.info("[Display2PlayMusic] 使用第三方Api请求转化歌曲: ${music.info.name}"); + var playMusic = PlayMusic(music.ref, music.info, playinfo, extra); return playMusic; } diff --git a/lib/util/audio_controller.dart b/lib/util/audio_controller.dart index f9fd33a..d3fe9d8 100644 --- a/lib/util/audio_controller.dart +++ b/lib/util/audio_controller.dart @@ -33,6 +33,8 @@ class AudioHandler extends GetxController { Future _init() async { // 先默认开启所有的循环 _player.setLoopMode(LoopMode.all); + // 关闭随机播放 + _player.setShuffleModeEnabled(false); // 监听错误事件并用talker来log _player.playbackEventStream.listen((event) {}, onError: (Object e, StackTrace stackTrace) { @@ -48,16 +50,22 @@ class AudioHandler extends GetxController { Future addMusicPlay(DisplayMusic music) async { try { - PlayMusic? playMusic = await display2PlayMusic(music); - if (playMusic == null) return; - - var index = playMusicList - .indexWhere((element) => element.extra == playMusic.extra); + PlayMusic? playMusic; + var index = -1; + if (music.info.defaultQuality != null) { + index = playMusicList.indexWhere((element) => + element.extra == + music.ref.getExtraInto(quality: music.info.defaultQuality!)); + } if (index != -1) { - playMusicList.removeAt(index); + playMusic = playMusicList.removeAt(index); playSourceList.removeAt(index); + } else { + playMusic = await display2PlayMusic(music); } + if (playMusic == null) return; + // 添加新的音乐 playMusicList.add(playMusic); if (playMusic.playInfo.file.contains("http")) { @@ -70,14 +78,13 @@ class AudioHandler extends GetxController { } // 播放新的音乐 - await _player.seek(Duration.zero, index: playSourceList.length - 1); + await seek(Duration.zero, index: playSourceList.length - 1); updateRx(); - await _player.play(); + await play(); } catch (e) { - talker.error("[Error Music Handler] In addMusicPlay, Error occur: $e"); + talker.error("[Music Handler] In addMusicPlay, Error occur: $e"); } - // 先检查是否已有这个音乐,如果已有,删除掉原有的 } Future replaceMusic(PlayMusic playMusic) async { @@ -92,30 +99,30 @@ class AudioHandler extends GetxController { await _insert(index, playMusic); if (shouldPlay) { // 重新播放这个位置的音乐 - await _player.seek(Duration.zero, index: index); + await seek(Duration.zero, index: index); updateRx(); - await _player.play(); + await play(); } } } catch (e) { talker.error( - "[Error Music Handler] In replaceMusic, Failed to display2PlayMusic: $e"); + "[Music Handler] In replaceMusic, Failed to display2PlayMusic: $e"); } } Future clearReplaceMusicAll(List musics) async { talker.info( - "[Log Music Handler] Request to add all musics of length: ${musics.length}"); + "[Music Handler] Request to add all musics of length: ${musics.length}"); List newPlayMusics = []; List newAudioSources = []; List> futures = []; -// 首先创建所有的future,但不等待它们 + // 首先创建所有的future,但不等待它们 for (var music in musics) { futures.add(display2PlayMusic(music)); } -// 然后等待所有future完成,并按顺序处理结果 + // 然后等待所有future完成,并按顺序处理结果 List playMusicsResults = await Future.wait(futures); for (var playMusic in playMusicsResults) { if (playMusic == null) continue; @@ -131,14 +138,14 @@ class AudioHandler extends GetxController { await playSourceList.addAll(newAudioSources); } catch (e) { talker.error( - "[Error Music Handler] In clearReplaceMusicAll, Failed to diaplayMusic2PlayMusic: $e"); + "[Music Handler] In clearReplaceMusicAll, Failed to diaplayMusic2PlayMusic: $e"); } - talker.info( - "[Log Music Handler] After add all: crt playMusicList length:${playMusicList.length},crt playSourceList length:${playSourceList.length}"); - await _player.seek(Duration.zero, index: 0); + + await seek(Duration.zero, index: 0); updateRx(); - await _player.play(); + log2List("After add all"); + await play(); } Future _insert(int index, PlayMusic music) async { @@ -150,7 +157,7 @@ class AudioHandler extends GetxController { } Future clear() async { - talker.info("[Log Music Handler] Request to clear all musics"); + talker.info("[Music Handler] Request to clear all musics"); if (playMusicList.isNotEmpty) { playMusicList.clear(); } @@ -159,11 +166,11 @@ class AudioHandler extends GetxController { update(); } updateRx(); + log2List("Afer Clear all musics"); } Future removeAt(int index) async { - talker.info("[Log Music Handler] Request to remove music of index:$index"); - // 如果正在播放,先暂停 + talker.info("[Music Handler] Request to remove music of index:$index"); if (_player.currentIndex != null && _player.currentIndex! == index) { await _player.pause(); } @@ -173,25 +180,52 @@ class AudioHandler extends GetxController { } Future seekToNext() async { - await _player.seekToNext(); + try { + await _player.seekToNext(); + talker.info("[Music Handler] In seekToNext, Succeed"); + } catch (e) { + talker.error("[Music Handler] In seekToNext, error occur: $e"); + } updateRx(); + await play(); } Future seekToPrevious() async { - await _player.seekToPrevious(); + try { + await _player.seekToPrevious(); + talker.info("[Music Handler] In seekToPrevious, Succeed"); + } catch (e) { + talker.error("[Music Handler] In seekToPrevious, error occur: $e"); + } updateRx(); + await play(); } Future pause() async { - await _player.pause(); + try { + await _player.pause(); + talker.info("[Music Handler] In pause, succeed"); + } catch (e) { + talker.error("[Music Handler] In pause, error occur: $e"); + } } Future play() async { - await _player.play(); + try { + await _player.play(); + talker.info("[Music Handler] In play, succeed"); + } catch (e) { + talker.error("[Music Handler] In play. error occur: $e"); + } } - Future seek(Duration position, [int? index]) async { - await _player.seek(position, index: index); + Future seek(Duration position, {int? index}) async { + try { + await _player.seek(position, index: index); + talker.info("[Music Handler] In seek, Succeed"); + } catch (e) { + talker.error("[Music Handler] In seek, error occur: $e"); + } } Stream createPositionStream({ @@ -218,6 +252,21 @@ class AudioHandler extends GetxController { "[Music Handler] Called updateRx: playingMusic: ${playingMusic.value?.info.name ?? "No music"}"); update(); } + + void log2List(String prefix) { + String playListStr = + playMusicList.map((element) => element.info.name).join(","); + String sourceListStr = + playSourceList.sequence.map((e) => e.tag.title).join(","); + String msg; + if (playListStr == sourceListStr) { + talker.log( + "[Music Handler] $prefix: PlayList = PlaySourceList = [$playListStr]"); + } else { + talker.error( + "[Music Handler] $prefix: PlayList != PlaySourceList\nPlayList:[$playListStr]\nPlaySourceList:[$playSourceList]"); + } + } } Future initGlobalAudioUiController() async { diff --git a/lib/util/helper.dart b/lib/util/helper.dart index d3810bb..705d995 100644 --- a/lib/util/helper.dart +++ b/lib/util/helper.dart @@ -1,4 +1,3 @@ -import 'dart:developer'; import 'dart:io'; import 'package:app_rhyme/main.dart'; @@ -16,10 +15,10 @@ Future fileCacheHelper(String file, String cachePath) async { cachePath: cachePath, ); if (localSource != null) { - talker.debug("fileCacheHelper: 使用已缓存source: ($file)->($localSource)"); + talker.debug("[fileCacheHelper] 使用已缓存source: ($file)->($localSource)"); toUseSource = localSource; } else { - talker.debug("fileCacheHelper: 不缓存,直接使用 $file"); + talker.debug("[fileCacheHelper] 不缓存,直接使用 $file"); toUseSource = file; } From 05e94efd12452fc96508e5404224a1ce2d025766 Mon Sep 17 00:00:00 2001 From: canxin Date: Fri, 3 May 2024 13:02:54 +0800 Subject: [PATCH 05/28] [Add] add more log --- lib/util/audio_controller.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/util/audio_controller.dart b/lib/util/audio_controller.dart index d3fe9d8..2288d0b 100644 --- a/lib/util/audio_controller.dart +++ b/lib/util/audio_controller.dart @@ -261,10 +261,10 @@ class AudioHandler extends GetxController { String msg; if (playListStr == sourceListStr) { talker.log( - "[Music Handler] $prefix: PlayList = PlaySourceList = [$playListStr]"); + "[Music Handler] $prefix: PlayList = PlaySourceList, length = ${playMusicList.length}, content = [$playListStr]"); } else { talker.error( - "[Music Handler] $prefix: PlayList != PlaySourceList\nPlayList:[$playListStr]\nPlaySourceList:[$playSourceList]"); + "[Music Handler] $prefix: PlayList != PlaySourceList\nPlayList = length: ${playMusicList.length}, content = [$playListStr]\nPlaySourceList: length = ${playSourceList.length},content = [$playSourceList]"); } } } From 50897e006396e2976241a699927a2f18c31751ae Mon Sep 17 00:00:00 2001 From: canxin Date: Fri, 3 May 2024 13:35:22 +0800 Subject: [PATCH 06/28] [Fix] clearReplaceAllMusic: FileMusic --- lib/util/audio_controller.dart | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/lib/util/audio_controller.dart b/lib/util/audio_controller.dart index 2288d0b..21d0bd2 100644 --- a/lib/util/audio_controller.dart +++ b/lib/util/audio_controller.dart @@ -127,8 +127,14 @@ class AudioHandler extends GetxController { for (var playMusic in playMusicsResults) { if (playMusic == null) continue; newPlayMusics.add(playMusic); - newAudioSources.add(AudioSource.uri(Uri.parse(playMusic.playInfo.file), - tag: playMusic.toMediaItem())); + + if (playMusic.playInfo.file.contains("http")) { + newAudioSources.add(AudioSource.uri(Uri.parse(playMusic.playInfo.file), + tag: playMusic.toMediaItem())); + } else { + newAudioSources.add(AudioSource.file(playMusic.playInfo.file, + tag: playMusic.toMediaItem())); + } } await clear(); From dc701fd3bb864c345ce69ce5eba9c7868b7a30e4 Mon Sep 17 00:00:00 2001 From: canxin Date: Fri, 3 May 2024 14:16:28 +0800 Subject: [PATCH 07/28] [Fix] background update --- lib/main.dart | 2 +- lib/page/home.dart | 11 ++--------- lib/util/audio_controller.dart | 8 ++++++++ 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index 3d5e42b..c6de79e 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -24,7 +24,7 @@ Future main() async { await initWindow(); await RustLib.init(); - FlutterError.onError = (details) { + FlutterError.onError = (details) { FlutterError.presentError(details); talker.error("[Flutter Error] $details"); }; diff --git a/lib/page/home.dart b/lib/page/home.dart index 6e9f294..9d07783 100644 --- a/lib/page/home.dart +++ b/lib/page/home.dart @@ -75,6 +75,8 @@ class HomePageState extends State { child: Stack( children: [ Obx(() => globalTopUiController.currentWidget.value), + + // 以下内容改成固定在页面底部的 // 使用MediaQuery检测键盘是否可见 if (!isKeyboardVisible) Align( @@ -84,15 +86,6 @@ class HomePageState extends State { children: [ // 音乐播放控制栏 const MusicPlayBar(), - // 一个浅色的分隔 - const Divider( - color: CupertinoColors.systemGrey6, - height: 1, - thickness: 1, - indent: 20, - endIndent: 20, - ), - // 底部的导航图标按钮 Obx(() => CupertinoTabBar( activeColor: activeIconColor, backgroundColor: barBackgoundColor, diff --git a/lib/util/audio_controller.dart b/lib/util/audio_controller.dart index 21d0bd2..a4dbee8 100644 --- a/lib/util/audio_controller.dart +++ b/lib/util/audio_controller.dart @@ -1,5 +1,6 @@ import 'package:app_rhyme/main.dart'; import 'package:app_rhyme/types/music.dart'; +import 'package:audio_service/audio_service.dart'; import 'package:audio_session/audio_session.dart'; import 'package:get/get.dart'; import 'package:just_audio/just_audio.dart'; @@ -17,8 +18,10 @@ Future initGlobalAudioHandler() async { androidNotificationChannelName: 'Audio playback', androidNotificationOngoing: true, ); + final session = await AudioSession.instance; await session.configure(const AudioSessionConfiguration.music()); + globalAudioHandler = AudioHandler(); } @@ -42,6 +45,11 @@ class AudioHandler extends GetxController { }); // 将playSourceList交给player作为列表,不过目前是空的 _player.setAudioSource(playSourceList); + + _player.currentIndexStream.listen((event) { + talker.info("[Music Handler] currentIndexStream updated"); + updateRx(); + }); } AudioHandler() { From 51a2b5aad9308b531b2388bfbdb1a3a3c5d72438 Mon Sep 17 00:00:00 2001 From: canxin Date: Fri, 3 May 2024 14:19:59 +0800 Subject: [PATCH 08/28] [Test] DisplayPage only top widgets --- lib/page/playing_music_page.dart | 62 +++++++++++++++----------------- 1 file changed, 29 insertions(+), 33 deletions(-) diff --git a/lib/page/playing_music_page.dart b/lib/page/playing_music_page.dart index 2135eb0..e25b253 100644 --- a/lib/page/playing_music_page.dart +++ b/lib/page/playing_music_page.dart @@ -134,40 +134,36 @@ class SongDisplayPageState extends State { backgroundColor: CupertinoColors.white, onDismissed: () => Navigator.of(context).pop(), child: Container( - // clipBehavior: Clip.antiAliasWithSaveLayer, - // decoration: const BoxDecoration( - // gradient: LinearGradient( - // begin: Alignment.topRight, - // end: Alignment.bottomLeft, - // colors: [ - // CupertinoColors.systemGrey2, - // CupertinoColors.systemGrey, - // ], - // ), - // ), - // child: ProAnimatedBlur( - // blur: 500, - // duration: const Duration(milliseconds: 0), - // child: Column( - // children: [ - // Flexible( - // flex: topFlex, - // child: Column( - // mainAxisAlignment: MainAxisAlignment.start, - // children: topWidgets, - // )), - // // 使用 Expanded 包裹一个新的 Column,以便底部组件从下往上排布 - // Flexible( - // flex: bottomFlex, - // child: Column( - // mainAxisAlignment: MainAxisAlignment.end, - // children: bottomWidgets, - // ), - // ), - // ], - // ), - // ), + clipBehavior: Clip.antiAliasWithSaveLayer, + decoration: const BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topRight, + end: Alignment.bottomLeft, + colors: [ + CupertinoColors.systemGrey2, + CupertinoColors.systemGrey, + ], ), + ), + child: Column( + children: [ + Flexible( + flex: topFlex, + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + children: topWidgets, + )), + // // 使用 Expanded 包裹一个新的 Column,以便底部组件从下往上排布 + // Flexible( + // flex: bottomFlex, + // child: Column( + // mainAxisAlignment: MainAxisAlignment.end, + // children: bottomWidgets, + // ), + // ), + ], + ), + ), ); } } From 10204ed91bf6fe81a485822fe337d05749fe605e Mon Sep 17 00:00:00 2001 From: canxin Date: Fri, 3 May 2024 14:22:18 +0800 Subject: [PATCH 09/28] [Test] DisplayPage only top widgets --- lib/page/playing_music_page.dart | 40 ++++++++++++++++---------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/lib/page/playing_music_page.dart b/lib/page/playing_music_page.dart index e25b253..8f7f5e8 100644 --- a/lib/page/playing_music_page.dart +++ b/lib/page/playing_music_page.dart @@ -59,37 +59,37 @@ class SongDisplayPageState extends State { switch (pageState) { case PageState.main: topWidgets = [ - // const Flexible( - // flex: 1, - // child: MusicArtPic(), - // ) + const Flexible( + flex: 1, + child: MusicArtPic(), + ) ]; break; case PageState.list: topWidgets = [ Padding(padding: EdgeInsets.only(top: screenHeight * 0.03)), - // const PlayingMusicCard(), - // const Padding(padding: EdgeInsets.only(left: 30)), - // Container( - // padding: const EdgeInsets.only(left: 20), - // alignment: Alignment.centerLeft, - // child: const Text( - // '待播清单', - // style: TextStyle( - // fontSize: 20.0, - // fontWeight: FontWeight.bold, - // ), - // ), - // ), + const PlayingMusicCard(), + const Padding(padding: EdgeInsets.only(left: 30)), + Container( + padding: const EdgeInsets.only(left: 20), + alignment: Alignment.centerLeft, + child: const Text( + '待播清单', + style: TextStyle( + fontSize: 20.0, + fontWeight: FontWeight.bold, + ), + ), + ), const PlayMusicList(), ]; break; case PageState.lyric: topWidgets = [ Padding(padding: EdgeInsets.only(top: screenHeight * 0.03)), - // const PlayingMusicCard(), - // const Padding(padding: EdgeInsets.only(top: 10, bottom: 10)), - // LyricDisplay(maxHeight: screenHeight * 0.55), + const PlayingMusicCard(), + const Padding(padding: EdgeInsets.only(top: 10, bottom: 10)), + LyricDisplay(maxHeight: screenHeight * 0.55), ]; break; } From 0f21559f32ca15612496166a71c3f3834803822b Mon Sep 17 00:00:00 2001 From: canxin Date: Fri, 3 May 2024 14:56:07 +0800 Subject: [PATCH 10/28] [Test] delete blur and safearea. --- lib/comp/music_bar/bar.dart | 256 ++++++++++----------- lib/comp/play_page_comp/botton_button.dart | 4 +- lib/page/home.dart | 11 +- lib/page/playing_music_page.dart | 74 +++--- 4 files changed, 160 insertions(+), 185 deletions(-) diff --git a/lib/comp/music_bar/bar.dart b/lib/comp/music_bar/bar.dart index dbbecc1..224e633 100644 --- a/lib/comp/music_bar/bar.dart +++ b/lib/comp/music_bar/bar.dart @@ -5,148 +5,134 @@ import 'package:app_rhyme/util/helper.dart'; import 'package:app_rhyme/util/audio_controller.dart'; import 'package:flutter/cupertino.dart'; import 'package:get/get_state_manager/src/rx_flutter/rx_obx_widget.dart'; -import 'package:pro_animated_blur/pro_animated_blur.dart'; class MusicPlayBar extends StatelessWidget { const MusicPlayBar({super.key}); @override Widget build(BuildContext context) { - // 获取屏幕总高度 - double screenHeight = MediaQuery.of(context).size.height; - // 计算组件的最大高度 - double barHeight = screenHeight * 0.08; - - return SafeArea( - child: ConstrainedBox( - constraints: BoxConstraints( - maxHeight: barHeight, - ), - child: GestureDetector( - onVerticalDragUpdate: (details) { - // 当用户上滑时,details.delta.dy 会是一个负值 - if (details.delta.dy < 0) { - navigateToSongDisplayPage(context); - } - }, - onTap: () { - navigateToSongDisplayPage(context); - }, - child: Container( - clipBehavior: Clip.antiAliasWithSaveLayer, - decoration: BoxDecoration(color: barBackgoundColor), - child: ProAnimatedBlur( - blur: 500, - duration: const Duration(milliseconds: 0), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Padding( - padding: const EdgeInsets.all(8.0), - // 使用AspectRatio来保持图片的宽高比 - child: Obx(() => FutureBuilder( - future: playingMusicImage(), // 这是异步函数 - builder: (BuildContext context, - AsyncSnapshot snapshot) { - if (snapshot.connectionState == - ConnectionState.waiting) { - return AspectRatio( - aspectRatio: 1, - child: Hero( - tag: "PlayingMusicPic", - child: defaultArtPic, - ), - ); - } else if (snapshot.hasError) { - return Text('Error: ${snapshot.error}'); - } else { - return AspectRatio( - aspectRatio: 1, - child: snapshot.data ?? defaultArtPic, - ); - } - }, - )), - ), - Expanded( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Obx( - () => Text( - playingMusicName, - style: const TextStyle(fontSize: 15.0), - textAlign: TextAlign.center, - overflow: TextOverflow.ellipsis, - maxLines: 1, - ), - ), - ], - ), + return ConstrainedBox( + constraints: BoxConstraints( + maxHeight: MediaQuery.of(context).size.height * 0.1, + ), + child: GestureDetector( + onVerticalDragUpdate: (details) { + // 当用户上滑时,details.delta.dy 会是一个负值 + if (details.delta.dy < 0) { + navigateToSongDisplayPage(context); + } + }, + onTap: () { + navigateToSongDisplayPage(context); + }, + child: Container( + clipBehavior: Clip.antiAliasWithSaveLayer, + decoration: BoxDecoration(color: barBackgoundColor), + child: Row( + children: [ + // 音乐图标 + Padding( + padding: const EdgeInsets.all(8.0), + // 使用AspectRatio来保持图片的宽高比 + child: Obx(() => FutureBuilder( + future: playingMusicImage(), // 这是异步函数 + builder: + (BuildContext context, AsyncSnapshot snapshot) { + if (snapshot.connectionState == + ConnectionState.waiting) { + return AspectRatio( + aspectRatio: 1, + child: Hero( + tag: "PlayingMusicPic", + child: defaultArtPic, + ), + ); + } else if (snapshot.hasError) { + return Text('Error: ${snapshot.error}'); + } else { + return AspectRatio( + aspectRatio: 1, + child: snapshot.data ?? defaultArtPic, + ); + } + }, + )), + ), + // 音乐名称 + Expanded( + child: Obx( + () => Text( + playingMusicName, + style: const TextStyle(fontSize: 15.0), + textAlign: TextAlign.left, + overflow: TextOverflow.ellipsis, + maxLines: 1, + ), + ), + ), + // 音乐控制按钮 + Row( + mainAxisSize: MainAxisSize.min, + children: [ + Hero( + tag: "SkipToPreviousButton", + child: CupertinoButton( + padding: EdgeInsets.zero, + onPressed: () { + globalAudioHandler.seekToPrevious(); + }, + child: const Icon( + CupertinoIcons.backward_fill, + color: CupertinoColors.black, ), - Row( - mainAxisSize: MainAxisSize.min, - children: [ - Hero( - tag: "SkipToPreviousButton", - child: CupertinoButton( - padding: EdgeInsets.zero, - onPressed: () { - globalAudioHandler.seekToPrevious(); - }, - child: const Icon( - CupertinoIcons.backward_fill, - color: CupertinoColors.black, - ), - )), - Obx(() { - if (globalAudioUiController - .playerState.value.playing) { - return Hero( - tag: "PlayOrPauseButton", - child: CupertinoButton( - padding: EdgeInsets.zero, - child: const Icon( - CupertinoIcons.pause_solid, - color: CupertinoColors.black, - ), - onPressed: () { - globalAudioHandler.pause(); - }, - )); - } else { - return Hero( - tag: "PlayOrPauseButton", - child: CupertinoButton( - padding: EdgeInsets.zero, - child: const Icon( - CupertinoIcons.play_arrow_solid, - color: CupertinoColors.black, - ), - onPressed: () { - globalAudioHandler.play(); - }, - )); - } - }), - Hero( - tag: "SkipToNextButton", - child: CupertinoButton( - padding: EdgeInsets.zero, - onPressed: () { - globalAudioHandler.seekToNext(); - }, - child: const Icon( - CupertinoIcons.forward_fill, - color: CupertinoColors.black, - ), - )), - ], + )), + Obx(() { + if (globalAudioUiController.playerState.value.playing) { + return Hero( + tag: "PlayOrPauseButton", + child: CupertinoButton( + padding: EdgeInsets.zero, + child: const Icon( + CupertinoIcons.pause_solid, + color: CupertinoColors.black, + ), + onPressed: () { + globalAudioHandler.pause(); + }, + )); + } else { + return Hero( + tag: "PlayOrPauseButton", + child: CupertinoButton( + padding: EdgeInsets.zero, + child: const Icon( + CupertinoIcons.play_arrow_solid, + color: CupertinoColors.black, + ), + onPressed: () { + globalAudioHandler.play(); + }, + )); + } + }), + Hero( + tag: "SkipToNextButton", + child: CupertinoButton( + padding: EdgeInsets.zero, + onPressed: () { + globalAudioHandler.seekToNext(); + }, + child: const Icon( + CupertinoIcons.forward_fill, + color: CupertinoColors.black, ), - ], - ), - ), - )))); + )), + ], + ), + ], + ), + ), + ), + ); } } diff --git a/lib/comp/play_page_comp/botton_button.dart b/lib/comp/play_page_comp/botton_button.dart index 0c738da..00ee269 100644 --- a/lib/comp/play_page_comp/botton_button.dart +++ b/lib/comp/play_page_comp/botton_button.dart @@ -23,7 +23,7 @@ class BottomButtonState extends State { mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ CupertinoButton( - padding: const EdgeInsets.all(0), + padding: const EdgeInsets.only(top: 10, bottom: 10), child: Icon( // 根据当前页面状态来决定图标 currentPage == PageState.lyric @@ -43,7 +43,7 @@ class BottomButtonState extends State { }, ), CupertinoButton( - padding: const EdgeInsets.all(0), + padding: const EdgeInsets.only(top: 10, bottom: 10), child: Icon( // 根据当前页面状态来决定图标 currentPage == PageState.list diff --git a/lib/page/home.dart b/lib/page/home.dart index 9d07783..d1720c6 100644 --- a/lib/page/home.dart +++ b/lib/page/home.dart @@ -6,7 +6,6 @@ import 'package:app_rhyme/page/setting.dart'; import 'package:app_rhyme/page/user_agreement.dart'; import 'package:app_rhyme/util/colors.dart'; import 'package:flutter/cupertino.dart'; -import 'package:flutter/material.dart'; import 'package:flutter_keyboard_visibility/flutter_keyboard_visibility.dart'; import 'package:get/get.dart'; @@ -94,7 +93,7 @@ class HomePageState extends State { items: const [ BottomNavigationBarItem( icon: Padding( - padding: EdgeInsets.only(top: 10), + padding: EdgeInsets.only(top: 10, bottom: 10), child: Icon( CupertinoIcons.music_albums_fill, ), @@ -102,22 +101,18 @@ class HomePageState extends State { ), BottomNavigationBarItem( icon: Padding( - padding: EdgeInsets.only(top: 10), + padding: EdgeInsets.only(top: 10, bottom: 10), child: Icon(CupertinoIcons.search), ), ), BottomNavigationBarItem( icon: Padding( - padding: EdgeInsets.only(top: 10), + padding: EdgeInsets.only(top: 10, bottom: 10), child: Icon(CupertinoIcons.settings), ), ), ], )), - // 底部导航栏图标按钮和底部的一个空白边界 - Container( - padding: const EdgeInsets.only(bottom: 10), - color: barBackgoundColor) ], ), ), diff --git a/lib/page/playing_music_page.dart b/lib/page/playing_music_page.dart index 8f7f5e8..d9f6474 100644 --- a/lib/page/playing_music_page.dart +++ b/lib/page/playing_music_page.dart @@ -11,7 +11,6 @@ import 'package:app_rhyme/comp/play_page_comp/volume_slider.dart'; import 'package:flutter/cupertino.dart'; import 'package:dismissible_page/dismissible_page.dart'; -import 'package:pro_animated_blur/pro_animated_blur.dart'; enum PageState { main, list, lyric } @@ -116,16 +115,6 @@ class SongDisplayPageState extends State { flex: 1, child: VolumeSlider(), ), - Flexible( - flex: 1, - child: Container( - padding: const EdgeInsets.only(top: 10, bottom: 10), - child: BottomButton( - onList: onListBotton, - onLyric: onLyricBotton, - ), - ), - ) ]; return DismissiblePage( @@ -134,36 +123,41 @@ class SongDisplayPageState extends State { backgroundColor: CupertinoColors.white, onDismissed: () => Navigator.of(context).pop(), child: Container( - clipBehavior: Clip.antiAliasWithSaveLayer, - decoration: const BoxDecoration( - gradient: LinearGradient( - begin: Alignment.topRight, - end: Alignment.bottomLeft, - colors: [ - CupertinoColors.systemGrey2, - CupertinoColors.systemGrey, - ], + clipBehavior: Clip.antiAliasWithSaveLayer, + decoration: const BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topRight, + end: Alignment.bottomLeft, + colors: [ + CupertinoColors.systemGrey2, + CupertinoColors.systemGrey, + ], + ), ), - ), - child: Column( - children: [ - Flexible( - flex: topFlex, - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - children: topWidgets, - )), - // // 使用 Expanded 包裹一个新的 Column,以便底部组件从下往上排布 - // Flexible( - // flex: bottomFlex, - // child: Column( - // mainAxisAlignment: MainAxisAlignment.end, - // children: bottomWidgets, - // ), - // ), - ], - ), - ), + child: Stack(children: [ + Column( + children: [ + Flexible( + flex: topFlex, + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + children: topWidgets, + )), + // // 使用 Expanded 包裹一个新的 Column,以便底部组件从下往上排布 + // Flexible( + // flex: bottomFlex, + // child: Column( + // mainAxisAlignment: MainAxisAlignment.end, + // children: bottomWidgets, + // ), + // ), + ], + ), + Align( + alignment: Alignment.bottomCenter, + child: BottomButton(onList: onListBotton, onLyric: onLyricBotton), + ) + ])), ); } } From 563655ebb631df5c8451d7e2fe395b7ccc5cf1d9 Mon Sep 17 00:00:00 2001 From: canxin Date: Fri, 3 May 2024 19:20:40 +0800 Subject: [PATCH 11/28] [Fix] Adjust ui --- lib/comp/play_page_comp/botton_button.dart | 15 +- lib/page/home.dart | 79 +++-- lib/page/in_music_list.dart | 307 +++++++++--------- lib/page/playing_music_page copy.dart | 344 ++++++++++----------- lib/page/playing_music_page.dart | 7 +- pubspec.yaml | 2 - 6 files changed, 376 insertions(+), 378 deletions(-) diff --git a/lib/comp/play_page_comp/botton_button.dart b/lib/comp/play_page_comp/botton_button.dart index 00ee269..60f726e 100644 --- a/lib/comp/play_page_comp/botton_button.dart +++ b/lib/comp/play_page_comp/botton_button.dart @@ -6,8 +6,12 @@ enum PageState { home, lyric, list } class BottomButton extends StatefulWidget { final VoidCallback onList; final VoidCallback onLyric; - - const BottomButton({super.key, required this.onList, required this.onLyric}); + final EdgeInsetsGeometry padding; + const BottomButton( + {super.key, + required this.onList, + required this.onLyric, + required this.padding}); @override State createState() => BottomButtonState(); @@ -23,9 +27,8 @@ class BottomButtonState extends State { mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ CupertinoButton( - padding: const EdgeInsets.only(top: 10, bottom: 10), + padding: widget.padding, child: Icon( - // 根据当前页面状态来决定图标 currentPage == PageState.lyric ? CupertinoIcons.quote_bubble_fill : CupertinoIcons.quote_bubble, @@ -33,8 +36,6 @@ class BottomButtonState extends State { ), onPressed: () { setState(() { - // 如果当前在歌词页面,点击则回到主页 - // 否则,进入歌词页面 currentPage = currentPage == PageState.lyric ? PageState.home : PageState.lyric; @@ -43,7 +44,7 @@ class BottomButtonState extends State { }, ), CupertinoButton( - padding: const EdgeInsets.only(top: 10, bottom: 10), + padding: widget.padding, child: Icon( // 根据当前页面状态来决定图标 currentPage == PageState.list diff --git a/lib/page/home.dart b/lib/page/home.dart index d1720c6..2f8c7f5 100644 --- a/lib/page/home.dart +++ b/lib/page/home.dart @@ -6,7 +6,6 @@ import 'package:app_rhyme/page/setting.dart'; import 'package:app_rhyme/page/user_agreement.dart'; import 'package:app_rhyme/util/colors.dart'; import 'package:flutter/cupertino.dart'; -import 'package:flutter_keyboard_visibility/flutter_keyboard_visibility.dart'; import 'package:get/get.dart'; final TopUiController globalTopUiController = Get.put(TopUiController()); @@ -59,63 +58,59 @@ class HomePageState extends State { if (!globalConfig.userAgreement) { showUserAgreement(context); } - // 监听键盘状态的变化 - KeyboardVisibilityController().onChange.listen((bool visible) { - setState(() { - isKeyboardVisible = visible; - }); - }); }); } @override Widget build(BuildContext context) { return CupertinoPageScaffold( + resizeToAvoidBottomInset: false, child: Stack( children: [ - Obx(() => globalTopUiController.currentWidget.value), + SafeArea( + child: Obx(() => globalTopUiController.currentWidget.value), + ), // 以下内容改成固定在页面底部的 // 使用MediaQuery检测键盘是否可见 - if (!isKeyboardVisible) - Align( - alignment: Alignment.bottomCenter, - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - // 音乐播放控制栏 - const MusicPlayBar(), - Obx(() => CupertinoTabBar( - activeColor: activeIconColor, - backgroundColor: barBackgoundColor, - currentIndex: globalTopUiController.currentIndex.value, - onTap: globalTopUiController.changeTabIndex, - items: const [ - BottomNavigationBarItem( - icon: Padding( - padding: EdgeInsets.only(top: 10, bottom: 10), - child: Icon( - CupertinoIcons.music_albums_fill, - ), + Align( + alignment: Alignment.bottomCenter, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + // 音乐播放控制栏 + const MusicPlayBar(), + Obx(() => CupertinoTabBar( + activeColor: activeIconColor, + backgroundColor: barBackgoundColor, + currentIndex: globalTopUiController.currentIndex.value, + onTap: globalTopUiController.changeTabIndex, + items: const [ + BottomNavigationBarItem( + icon: Padding( + padding: EdgeInsets.only(top: 10, bottom: 10), + child: Icon( + CupertinoIcons.music_albums_fill, ), ), - BottomNavigationBarItem( - icon: Padding( - padding: EdgeInsets.only(top: 10, bottom: 10), - child: Icon(CupertinoIcons.search), - ), + ), + BottomNavigationBarItem( + icon: Padding( + padding: EdgeInsets.only(top: 10, bottom: 10), + child: Icon(CupertinoIcons.search), ), - BottomNavigationBarItem( - icon: Padding( - padding: EdgeInsets.only(top: 10, bottom: 10), - child: Icon(CupertinoIcons.settings), - ), + ), + BottomNavigationBarItem( + icon: Padding( + padding: EdgeInsets.only(top: 10, bottom: 10), + child: Icon(CupertinoIcons.settings), ), - ], - )), - ], - ), + ), + ], + )), + ], ), + ), ], ), ); diff --git a/lib/page/in_music_list.dart b/lib/page/in_music_list.dart index cb618cd..229c247 100644 --- a/lib/page/in_music_list.dart +++ b/lib/page/in_music_list.dart @@ -47,8 +47,6 @@ class MusicPageState extends State { @override Widget build(BuildContext context) { - double screenWidth = MediaQuery.of(context).size.width; - return CupertinoPageScaffold( navigationBar: CupertinoNavigationBar( padding: const EdgeInsetsDirectional.only(end: 16), @@ -61,167 +59,166 @@ class MusicPageState extends State { ), trailing: editTextButton(context), ), - child: Scaffold( - body: CustomScrollView( - slivers: [ - // 封面图片 - SliverToBoxAdapter( - child: Padding( - padding: EdgeInsets.only( - top: 60, left: screenWidth * 0.1, right: screenWidth * 0.1), - child: Container( - constraints: BoxConstraints( - maxWidth: screenWidth * 0.7, - ), - child: MusicListCard(musicList: _musicList), + child: SafeArea( + child: CustomScrollView( + slivers: [ + // 封面图片 + SliverToBoxAdapter( + child: Padding( + padding: EdgeInsets.only( + top: Screen.width * 0.1, + left: Screen.width * 0.1, + right: Screen.width * 0.1), + child: Container( + constraints: BoxConstraints( + maxWidth: Screen.width * 0.7, ), + child: MusicListCard(musicList: _musicList), ), ), - // 两个按钮 - SliverToBoxAdapter( - child: Padding( - padding: const EdgeInsets.symmetric(vertical: 20.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - _buildButton( - context, - icon: CupertinoIcons.play_fill, - label: '播放全部', - onPressed: () { - globalAudioHandler.clearReplaceMusicAll(_musics); - }, - ), - _buildButton( - context, - icon: Icons.shuffle, - label: '随机播放', - onPressed: () { - var musics = _musics.toList(); - musics.shuffle(); - globalAudioHandler - .clearReplaceMusicAll(shuffleList(musics)); - }, - ), - ], - ), + ), + // 两个按钮 + SliverToBoxAdapter( + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 20.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + _buildButton( + context, + icon: CupertinoIcons.play_fill, + label: '播放全部', + onPressed: () { + globalAudioHandler.clearReplaceMusicAll(_musics); + }, + ), + _buildButton( + context, + icon: Icons.shuffle, + label: '随机播放', + onPressed: () { + var musics = _musics.toList(); + musics.shuffle(); + globalAudioHandler + .clearReplaceMusicAll(shuffleList(musics)); + }, + ), + ], ), ), - // 一个分界线 - const SliverToBoxAdapter( - child: Divider( - color: CupertinoColors.systemGrey5, - height: 1, - ), + ), + // 一个分界线 + const SliverToBoxAdapter( + child: Divider( + color: CupertinoColors.systemGrey5, + height: 1, ), - SliverList.separated( - separatorBuilder: (context, index) => const Divider( - color: CupertinoColors.systemGrey4, - indent: 30, - endIndent: 30, - ), - itemBuilder: (context, index) { - final music = _musics[index]; - return MusicCard( - key: ValueKey(music.info.id), - music: music, - onClick: () { - globalAudioHandler.addMusicPlay( - music, - ); - }, - hasCache: music.hasCache(), - onPress: () { - showCupertinoPopupWithActions(context: context, options: [ - "缓存", - "删除", - "用作封面", - "删除缓存" - ], actionCallbacks: [ - () async { - // 缓存 - var playMusic = await display2PlayMusic(music); - if (playMusic == null) return; - cacheFile( - file: playMusic.playInfo.file, - cachePath: musicCachePath, - filename: playMusic.toCacheFileName()) - .then((file) { - // 下载完成之后设置本地路径为新的播放文件 - playMusic.playInfo.file = file; - // 如果这首歌正在播放列表中,替换他,防止继续在线播放 - globalAudioHandler.replaceMusic(playMusic); - // 在这里需要重新判断是否 hasCache,所以直接setState解决 - setState(() {}); - }); - }, - () async { - // 删除音乐 - await globalSqlMusicFactory.delMusic( - musicList: _musicList, - ids: Int64List.fromList([music.info.id])); - setState(() { - _musics.removeAt(index); - }); - }, - () async { - // 将音乐图片应用成歌单图片 - var pic = music.info.artPic; - if (pic != null) { - await globalSqlMusicFactory.changeMusicListMetadata( - oldList: [ - _musicList - ], - newList: [ - MusicList( - name: "", - artPic: pic, - desc: _musicList.desc) - ]).then((_) { - setState(() { - _musicList = MusicList( - name: _musicList.name, - artPic: pic, - desc: _musicList.desc); - }); - }); - } - }, - () async { - // 删除缓存 - var result = music.toCacheFileNameAndExtra(); - if (result == null) return; - var (cacheFileName, _) = result; - deleteCacheFile( - file: "", - cachePath: musicCachePath, - filename: cacheFileName) - .then((value) { - // 删除缓存后刷新是否有缓存 - setState(() {}); - if (kDebugMode) { - print("成功删除缓存:${music.info.name}"); - } - display2PlayMusic(music).then((value) { - if (value == null) return; - globalAudioHandler.replaceMusic(value); + ), + SliverList.separated( + separatorBuilder: (context, index) => const Divider( + color: CupertinoColors.systemGrey4, + indent: 30, + endIndent: 30, + ), + itemBuilder: (context, index) { + final music = _musics[index]; + return MusicCard( + key: ValueKey(music.info.id), + music: music, + onClick: () { + globalAudioHandler.addMusicPlay( + music, + ); + }, + hasCache: music.hasCache(), + onPress: () { + showCupertinoPopupWithActions(context: context, options: [ + "缓存", + "删除", + "用作封面", + "删除缓存" + ], actionCallbacks: [ + () async { + // 缓存 + var playMusic = await display2PlayMusic(music); + if (playMusic == null) return; + cacheFile( + file: playMusic.playInfo.file, + cachePath: musicCachePath, + filename: playMusic.toCacheFileName()) + .then((file) { + // 下载完成之后设置本地路径为新的播放文件 + playMusic.playInfo.file = file; + // 如果这首歌正在播放列表中,替换他,防止继续在线播放 + globalAudioHandler.replaceMusic(playMusic); + // 在这里需要重新判断是否 hasCache,所以直接setState解决 + setState(() {}); + }); + }, + () async { + // 删除音乐 + await globalSqlMusicFactory.delMusic( + musicList: _musicList, + ids: Int64List.fromList([music.info.id])); + setState(() { + _musics.removeAt(index); + }); + }, + () async { + // 将音乐图片应用成歌单图片 + var pic = music.info.artPic; + if (pic != null) { + await globalSqlMusicFactory.changeMusicListMetadata( + oldList: [ + _musicList + ], + newList: [ + MusicList( + name: "", artPic: pic, desc: _musicList.desc) + ]).then((_) { + setState(() { + _musicList = MusicList( + name: _musicList.name, + artPic: pic, + desc: _musicList.desc); }); }); } - ]); - }, - ); - }, - itemCount: _musics.length, - ), - const SliverToBoxAdapter( - child: Padding( - padding: EdgeInsets.only(top: 200), - ), + }, + () async { + // 删除缓存 + var result = music.toCacheFileNameAndExtra(); + if (result == null) return; + var (cacheFileName, _) = result; + deleteCacheFile( + file: "", + cachePath: musicCachePath, + filename: cacheFileName) + .then((value) { + // 删除缓存后刷新是否有缓存 + setState(() {}); + if (kDebugMode) { + print("成功删除缓存:${music.info.name}"); + } + display2PlayMusic(music).then((value) { + if (value == null) return; + globalAudioHandler.replaceMusic(value); + }); + }); + } + ]); + }, + ); + }, + itemCount: _musics.length, + ), + const SliverToBoxAdapter( + child: Padding( + padding: EdgeInsets.only(top: 200), ), - ], - ), - ), + ), + ], + )), ); } } @@ -265,7 +262,9 @@ Widget _buildButton(BuildContext context, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8.0), ), - padding: const EdgeInsets.symmetric(horizontal: 45.0, vertical: 15.0), + padding: EdgeInsets.symmetric( + horizontal: MediaQuery.of(context).size.width * 0.1, + vertical: MediaQuery.of(context).size.height * 0.02), backgroundColor: CupertinoColors.systemGrey6), ); } diff --git a/lib/page/playing_music_page copy.dart b/lib/page/playing_music_page copy.dart index bd56f48..7dd3c00 100644 --- a/lib/page/playing_music_page copy.dart +++ b/lib/page/playing_music_page copy.dart @@ -1,182 +1,182 @@ -import 'package:app_rhyme/comp/card/playing_music_card.dart'; -import 'package:app_rhyme/comp/play_page_comp/botton_button.dart'; -import 'package:app_rhyme/comp/play_page_comp/control_button.dart'; -import 'package:app_rhyme/comp/play_page_comp/lyric.dart'; -import 'package:app_rhyme/comp/play_page_comp/music_artpic.dart'; -import 'package:app_rhyme/comp/play_page_comp/music_info.dart'; -import 'package:app_rhyme/comp/play_page_comp/music_list.dart'; -import 'package:app_rhyme/comp/play_page_comp/progress_slider.dart'; -import 'package:app_rhyme/comp/play_page_comp/quality_time.dart'; -import 'package:app_rhyme/comp/play_page_comp/volume_slider.dart'; -import 'package:flutter/cupertino.dart'; +// import 'package:app_rhyme/comp/card/playing_music_card.dart'; +// import 'package:app_rhyme/comp/play_page_comp/botton_button.dart'; +// import 'package:app_rhyme/comp/play_page_comp/control_button.dart'; +// import 'package:app_rhyme/comp/play_page_comp/lyric.dart'; +// import 'package:app_rhyme/comp/play_page_comp/music_artpic.dart'; +// import 'package:app_rhyme/comp/play_page_comp/music_info.dart'; +// import 'package:app_rhyme/comp/play_page_comp/music_list.dart'; +// import 'package:app_rhyme/comp/play_page_comp/progress_slider.dart'; +// import 'package:app_rhyme/comp/play_page_comp/quality_time.dart'; +// import 'package:app_rhyme/comp/play_page_comp/volume_slider.dart'; +// import 'package:flutter/cupertino.dart'; -import 'package:dismissible_page/dismissible_page.dart'; -import 'package:pro_animated_blur/pro_animated_blur.dart'; +// import 'package:dismissible_page/dismissible_page.dart'; +// import 'package:pro_animated_blur/pro_animated_blur.dart'; -enum PageState { main, list, lyric } +// enum PageState { main, list, lyric } -class SongDisplayPage extends StatefulWidget { - const SongDisplayPage({super.key}); +// class SongDisplayPage extends StatefulWidget { +// const SongDisplayPage({super.key}); - @override - SongDisplayPageState createState() => SongDisplayPageState(); -} +// @override +// SongDisplayPageState createState() => SongDisplayPageState(); +// } -class SongDisplayPageState extends State { - PageState pageState = PageState.main; - int topFlex = 5; - int bottomFlex = 7; - void onListBotton() { - setState(() { - if (pageState == PageState.list) { - pageState = PageState.main; - bottomFlex = 7; - } else { - pageState = PageState.list; - bottomFlex = 2; - } - }); - } +// class SongDisplayPageState extends State { +// PageState pageState = PageState.main; +// int topFlex = 5; +// int bottomFlex = 7; +// void onListBotton() { +// setState(() { +// if (pageState == PageState.list) { +// pageState = PageState.main; +// bottomFlex = 7; +// } else { +// pageState = PageState.list; +// bottomFlex = 2; +// } +// }); +// } - void onLyricBotton() { - setState(() { - if (pageState == PageState.lyric) { - pageState = PageState.main; - bottomFlex = 7; - } else { - pageState = PageState.lyric; - bottomFlex = 2; - } - }); - } +// void onLyricBotton() { +// setState(() { +// if (pageState == PageState.lyric) { +// pageState = PageState.main; +// bottomFlex = 7; +// } else { +// pageState = PageState.lyric; +// bottomFlex = 2; +// } +// }); +// } - @override - Widget build(BuildContext context) { - double screenWidth = MediaQuery.of(context).size.width; - double screenHeight = MediaQuery.of(context).size.height; +// @override +// Widget build(BuildContext context) { +// double screenWidth = MediaQuery.of(context).size.width; +// double screenHeight = MediaQuery.of(context).size.height; - List topWidgets; - switch (pageState) { - case PageState.main: - topWidgets = [ - const Flexible( - flex: 1, - child: MusicArtPic(), - ) - ]; - break; - case PageState.list: - topWidgets = [ - Padding(padding: EdgeInsets.only(top: screenHeight * 0.03)), - const PlayingMusicCard(), - const Padding(padding: EdgeInsets.only(left: 30)), - Container( - padding: const EdgeInsets.only(left: 20), - alignment: Alignment.centerLeft, - child: const Text( - '待播清单', - style: TextStyle( - fontSize: 20.0, - fontWeight: FontWeight.bold, - ), - ), - ), - const PlayMusicList(), - ]; - break; - case PageState.lyric: - topWidgets = [ - Padding(padding: EdgeInsets.only(top: screenHeight * 0.03)), - const PlayingMusicCard(), - const Padding(padding: EdgeInsets.only(top: 10, bottom: 10)), - LyricDisplay(maxHeight: screenHeight * 0.55), - ]; - break; - } - // 底部组件,包括 ProgressSlider, QualityTime, ControlButton, VolumeSlider 和 BottomButton - List bottomWidgets = [ - if (pageState == PageState.main) - const Flexible(flex: 2, child: MusicInfo()), - const Flexible( - flex: 1, - child: ProgressSlider(), - ), - const Flexible( - flex: 1, - child: QualityTime(), - ), - Flexible( - flex: 3, - child: ControlButton( - buttonSize: screenWidth * 0.1, - buttonSpacing: screenWidth * 0.2, - ), - ), - const Flexible( - flex: 1, - child: VolumeSlider(), - ), - Flexible( - flex: 1, - child: Container( - padding: const EdgeInsets.only(top: 10, bottom: 10), - child: BottomButton( - onList: onListBotton, - onLyric: onLyricBotton, - ), - ), - ) - ]; +// List topWidgets; +// switch (pageState) { +// case PageState.main: +// topWidgets = [ +// const Flexible( +// flex: 1, +// child: MusicArtPic(), +// ) +// ]; +// break; +// case PageState.list: +// topWidgets = [ +// Padding(padding: EdgeInsets.only(top: screenHeight * 0.03)), +// const PlayingMusicCard(), +// const Padding(padding: EdgeInsets.only(left: 30)), +// Container( +// padding: const EdgeInsets.only(left: 20), +// alignment: Alignment.centerLeft, +// child: const Text( +// '待播清单', +// style: TextStyle( +// fontSize: 20.0, +// fontWeight: FontWeight.bold, +// ), +// ), +// ), +// const PlayMusicList(), +// ]; +// break; +// case PageState.lyric: +// topWidgets = [ +// Padding(padding: EdgeInsets.only(top: screenHeight * 0.03)), +// const PlayingMusicCard(), +// const Padding(padding: EdgeInsets.only(top: 10, bottom: 10)), +// LyricDisplay(maxHeight: screenHeight * 0.55), +// ]; +// break; +// } +// // 底部组件,包括 ProgressSlider, QualityTime, ControlButton, VolumeSlider 和 BottomButton +// List bottomWidgets = [ +// if (pageState == PageState.main) +// const Flexible(flex: 2, child: MusicInfo()), +// const Flexible( +// flex: 1, +// child: ProgressSlider(), +// ), +// const Flexible( +// flex: 1, +// child: QualityTime(), +// ), +// Flexible( +// flex: 3, +// child: ControlButton( +// buttonSize: screenWidth * 0.1, +// buttonSpacing: screenWidth * 0.2, +// ), +// ), +// const Flexible( +// flex: 1, +// child: VolumeSlider(), +// ), +// Flexible( +// flex: 1, +// child: Container( +// padding: const EdgeInsets.only(top: 10, bottom: 10), +// child: BottomButton( +// onList: onListBotton, +// onLyric: onLyricBotton, +// ), +// ), +// ) +// ]; - return DismissiblePage( - isFullScreen: true, - direction: DismissiblePageDismissDirection.down, - backgroundColor: CupertinoColors.white, - onDismissed: () => Navigator.of(context).pop(), - child: Container( - clipBehavior: Clip.antiAliasWithSaveLayer, - decoration: const BoxDecoration( - gradient: LinearGradient( - begin: Alignment.topRight, - end: Alignment.bottomLeft, - colors: [ - CupertinoColors.systemGrey2, - CupertinoColors.systemGrey, - ], - ), - ), - child: ProAnimatedBlur( - blur: 500, - duration: const Duration(milliseconds: 0), - child: Column( - children: [ - Flexible( - flex: topFlex, - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - children: topWidgets, - )), - // 使用 Expanded 包裹一个新的 Column,以便底部组件从下往上排布 - Flexible( - flex: bottomFlex, - child: Column( - mainAxisAlignment: MainAxisAlignment.end, - children: bottomWidgets, - ), - ), - ], - ), - ), - ), - ); - } -} +// return DismissiblePage( +// isFullScreen: true, +// direction: DismissiblePageDismissDirection.down, +// backgroundColor: CupertinoColors.white, +// onDismissed: () => Navigator.of(context).pop(), +// child: Container( +// clipBehavior: Clip.antiAliasWithSaveLayer, +// decoration: const BoxDecoration( +// gradient: LinearGradient( +// begin: Alignment.topRight, +// end: Alignment.bottomLeft, +// colors: [ +// CupertinoColors.systemGrey2, +// CupertinoColors.systemGrey, +// ], +// ), +// ), +// child: ProAnimatedBlur( +// blur: 500, +// duration: const Duration(milliseconds: 0), +// child: Column( +// children: [ +// Flexible( +// flex: topFlex, +// child: Column( +// mainAxisAlignment: MainAxisAlignment.start, +// children: topWidgets, +// )), +// // 使用 Expanded 包裹一个新的 Column,以便底部组件从下往上排布 +// Flexible( +// flex: bottomFlex, +// child: Column( +// mainAxisAlignment: MainAxisAlignment.end, +// children: bottomWidgets, +// ), +// ), +// ], +// ), +// ), +// ), +// ); +// } +// } -void navigateToSongDisplayPage(BuildContext context) { - Navigator.of(context).push( - CupertinoPageRoute( - builder: (context) => const SongDisplayPage(), - fullscreenDialog: true, - ), - ); -} +// void navigateToSongDisplayPage(BuildContext context) { +// Navigator.of(context).push( +// CupertinoPageRoute( +// builder: (context) => const SongDisplayPage(), +// fullscreenDialog: true, +// ), +// ); +// } diff --git a/lib/page/playing_music_page.dart b/lib/page/playing_music_page.dart index d9f6474..f3cb9b7 100644 --- a/lib/page/playing_music_page.dart +++ b/lib/page/playing_music_page.dart @@ -153,9 +153,14 @@ class SongDisplayPageState extends State { // ), ], ), + // 固定在页面底部的导航按钮 Align( alignment: Alignment.bottomCenter, - child: BottomButton(onList: onListBotton, onLyric: onLyricBotton), + child: BottomButton( + onList: onListBotton, + onLyric: onLyricBotton, + padding: const EdgeInsets.only(top: 10, bottom: 20), + ), ) ])), ); diff --git a/pubspec.yaml b/pubspec.yaml index 815525c..766af07 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -42,9 +42,7 @@ dependencies: talker: ^4.1.5 just_audio_background: ^0.0.1-beta.11 audio_session: ^0.1.19 - flutter_keyboard_visibility: ^6.0.0 talker_flutter: ^4.1.5 - # audioplayers: ^6.0.0 dev_dependencies: flutter_test: From 96145e6a0b000a3a288a92dba30aaf6442fe1ac8 Mon Sep 17 00:00:00 2001 From: canxin Date: Fri, 3 May 2024 19:44:15 +0800 Subject: [PATCH 12/28] [Change] Use getx directly in lyric comp --- lib/comp/play_page_comp/lyric.dart | 115 ++++++++++++----------------- lib/util/audio_controller.dart | 4 + 2 files changed, 50 insertions(+), 69 deletions(-) diff --git a/lib/comp/play_page_comp/lyric.dart b/lib/comp/play_page_comp/lyric.dart index 9c0dc07..e487b2e 100644 --- a/lib/comp/play_page_comp/lyric.dart +++ b/lib/comp/play_page_comp/lyric.dart @@ -1,11 +1,9 @@ -import 'dart:async'; - -import 'package:app_rhyme/types/music.dart'; import 'package:app_rhyme/util/audio_controller.dart'; import 'package:app_rhyme/util/time_parse.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_lyric/lyrics_reader.dart'; +import 'package:get/get.dart'; class LyricDisplay extends StatefulWidget { final double maxHeight; @@ -16,10 +14,6 @@ class LyricDisplay extends StatefulWidget { } class LyricDisplayState extends State { - bool playing = false; - int position = 0; - late StreamSubscription stream1; - late StreamSubscription stream2; var lyricModel = LyricsModelBuilder.create().bindLyricToMain("[00:00.00]无歌词").getModel(); var lyricUI = UINetease(lyricAlign: LyricAlign.CENTER, highlight: true); @@ -30,77 +24,60 @@ class LyricDisplayState extends State { .bindLyricToMain(globalAudioHandler.playingMusic.value?.info.lyric ?? "[00:00.00]无歌词") .getModel(); - - playing = globalAudioHandler.playingMusic.value != null; - stream1 = globalAudioHandler - .createPositionStream( - maxPeriod: const Duration(milliseconds: 5), - minPeriod: const Duration(milliseconds: 5)) - .listen((event) { - setState(() { - position = event.inMilliseconds; - }); - }); - - stream2 = globalAudioUiController.duration.listen((p0) { + globalAudioHandler.playingMusic.listen((p0) { setState(() { lyricModel = LyricsModelBuilder.create() - .bindLyricToMain( - globalAudioHandler.playingMusic.value?.info.lyric ?? - "[00:00.00]无歌词") + .bindLyricToMain(p0?.info.lyric ?? "[00:00.00]无歌词") .getModel(); }); }); } - @override - void dispose() { - stream1.cancel(); - stream2.cancel(); - super.dispose(); - } - @override Widget build(BuildContext context) { - return LyricsReader( - playing: playing, - emptyBuilder: () => Center( - child: Text( - "No lyrics", - style: lyricUI.getOtherMainTextStyle(), - ), - ), - model: lyricModel, - position: position, - lyricUi: lyricUI, - size: Size(double.infinity, widget.maxHeight), - selectLineBuilder: (progress, confirm) { - return Row( - children: [ - IconButton( - onPressed: () { - globalAudioHandler - .seek(Duration(milliseconds: progress)) - .then((value) { - confirm.call(); - }); - }, - icon: - const Icon(Icons.play_arrow, color: CupertinoColors.white)), - Expanded( - child: Container( - decoration: const BoxDecoration(color: CupertinoColors.white), - height: 1, - width: double.infinity, - ), + return Obx(() => LyricsReader( + playing: globalAudioHandler.playingMusic.value != null, + emptyBuilder: () => Center( + child: Text( + "No lyrics", + style: lyricUI.getOtherMainTextStyle(), ), - Text( - formatDuration(Duration(milliseconds: progress).inSeconds), - style: const TextStyle(color: CupertinoColors.white), - ) - ], - ); - }, - ); + ), + model: lyricModel, + position: globalAudioUiController.position.value.inMilliseconds, + lyricUi: lyricUI, + size: Size(double.infinity, widget.maxHeight), + selectLineBuilder: (progress, confirm) { + return Row( + children: [ + IconButton( + onPressed: () { + globalAudioHandler + .seek(Duration(milliseconds: progress)) + .then((value) { + confirm.call(); + if (!globalAudioHandler.isPlaying()) { + globalAudioHandler.play(); + } + }); + }, + icon: const Icon(Icons.play_arrow, + color: CupertinoColors.white)), + Expanded( + child: Container( + decoration: + const BoxDecoration(color: CupertinoColors.white), + height: 1, + width: double.infinity, + ), + ), + Text( + formatDuration(Duration(milliseconds: progress).inSeconds), + style: const TextStyle(color: CupertinoColors.white), + ) + ], + ); + }, + )); } } diff --git a/lib/util/audio_controller.dart b/lib/util/audio_controller.dart index a4dbee8..82891aa 100644 --- a/lib/util/audio_controller.dart +++ b/lib/util/audio_controller.dart @@ -281,6 +281,10 @@ class AudioHandler extends GetxController { "[Music Handler] $prefix: PlayList != PlaySourceList\nPlayList = length: ${playMusicList.length}, content = [$playListStr]\nPlaySourceList: length = ${playSourceList.length},content = [$playSourceList]"); } } + + bool isPlaying() { + return _player.playing; + } } Future initGlobalAudioUiController() async { From cc7fc4b37a2753eb16f84a6b00e443b1338c76e3 Mon Sep 17 00:00:00 2001 From: canxin Date: Fri, 3 May 2024 19:47:43 +0800 Subject: [PATCH 13/28] [Fix] dispose stream in lyric comp --- lib/comp/play_page_comp/lyric.dart | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/lib/comp/play_page_comp/lyric.dart b/lib/comp/play_page_comp/lyric.dart index e487b2e..4f6df1c 100644 --- a/lib/comp/play_page_comp/lyric.dart +++ b/lib/comp/play_page_comp/lyric.dart @@ -1,3 +1,6 @@ +import 'dart:async'; + +import 'package:app_rhyme/types/music.dart'; import 'package:app_rhyme/util/audio_controller.dart'; import 'package:app_rhyme/util/time_parse.dart'; import 'package:flutter/cupertino.dart'; @@ -17,6 +20,7 @@ class LyricDisplayState extends State { var lyricModel = LyricsModelBuilder.create().bindLyricToMain("[00:00.00]无歌词").getModel(); var lyricUI = UINetease(lyricAlign: LyricAlign.CENTER, highlight: true); + late StreamSubscription stream; @override void initState() { super.initState(); @@ -24,7 +28,7 @@ class LyricDisplayState extends State { .bindLyricToMain(globalAudioHandler.playingMusic.value?.info.lyric ?? "[00:00.00]无歌词") .getModel(); - globalAudioHandler.playingMusic.listen((p0) { + stream = globalAudioHandler.playingMusic.listen((p0) { setState(() { lyricModel = LyricsModelBuilder.create() .bindLyricToMain(p0?.info.lyric ?? "[00:00.00]无歌词") @@ -33,6 +37,12 @@ class LyricDisplayState extends State { }); } + @override + void dispose() { + stream.cancel(); + super.dispose(); + } + @override Widget build(BuildContext context) { return Obx(() => LyricsReader( From c3758392193f1ceca0531925c4dd97714e134aa4 Mon Sep 17 00:00:00 2001 From: canxin Date: Fri, 3 May 2024 20:53:44 +0800 Subject: [PATCH 14/28] [Test] Use stack to see if sound display in ios. --- lib/comp/card/playing_music_card.dart | 7 +- lib/comp/play_page_comp/music_artpic.dart | 15 +++- lib/comp/play_page_comp/music_list.dart | 5 +- lib/page/home.dart | 1 + lib/page/playing_music_page.dart | 94 ++++++++--------------- pubspec.lock | 48 ------------ 6 files changed, 48 insertions(+), 122 deletions(-) diff --git a/lib/comp/card/playing_music_card.dart b/lib/comp/card/playing_music_card.dart index 68877e4..ea49af7 100644 --- a/lib/comp/card/playing_music_card.dart +++ b/lib/comp/card/playing_music_card.dart @@ -4,7 +4,7 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; -// 这个组件唯一使用就是在 待播放列表的上方显示,将被合并 +// 这个组件在 待播放列表和歌词的上方显示 class PlayingMusicCard extends StatefulWidget { final VoidCallback? onClick; final VoidCallback? onPress; @@ -29,7 +29,8 @@ class PlayingMusicCardState extends State { // 根据isTaller参数决定卡片的高度 double cardHeight = 80.0; - return GestureDetector( + return SafeArea( + child: GestureDetector( onTap: widget.onClick, onLongPress: widget.onPress, child: SizedBox( @@ -96,6 +97,6 @@ class PlayingMusicCardState extends State { ), ), ), - ); + )); } } diff --git a/lib/comp/play_page_comp/music_artpic.dart b/lib/comp/play_page_comp/music_artpic.dart index a3c81b6..bac5c5d 100644 --- a/lib/comp/play_page_comp/music_artpic.dart +++ b/lib/comp/play_page_comp/music_artpic.dart @@ -15,13 +15,17 @@ class MusicArtPic extends StatefulWidget { class MusicArtPicState extends State { @override Widget build(BuildContext context) { - return Obx( + return SafeArea( + child: Obx( () => FutureBuilder( future: playingMusicImage(), // 这里调用异步函数 builder: (BuildContext context, AsyncSnapshot snapshot) { if (snapshot.connectionState == ConnectionState.waiting) { return Container( - padding: const EdgeInsets.only(left: 20, right: 20, top: 20), + padding: const EdgeInsets.only( + left: 20, + right: 20, + ), child: ClipRRect( borderRadius: BorderRadius.circular(18.0), child: defaultArtPic, @@ -32,7 +36,10 @@ class MusicArtPicState extends State { } else { // 使用Cupertino组件包裹图片,并限制大小和加圆角边框 return Container( - padding: const EdgeInsets.only(left: 20, right: 20, top: 20), + padding: const EdgeInsets.only( + left: 20, + right: 20, + ), child: ClipRRect( borderRadius: BorderRadius.circular(18.0), child: snapshot.data ?? defaultArtPic, @@ -40,6 +47,6 @@ class MusicArtPicState extends State { } }, ), - ); + )); } } diff --git a/lib/comp/play_page_comp/music_list.dart b/lib/comp/play_page_comp/music_list.dart index 3068f23..90550da 100644 --- a/lib/comp/play_page_comp/music_list.dart +++ b/lib/comp/play_page_comp/music_list.dart @@ -15,8 +15,7 @@ class PlayMusicList extends StatelessWidget { return Obx(() { var musics = globalAudioHandler.playMusicList; - return Expanded( - child: CustomScrollView( + return CustomScrollView( slivers: [ SliverList.separated( separatorBuilder: (context, index) => const Divider( @@ -63,7 +62,7 @@ class PlayMusicList extends StatelessWidget { itemCount: musics.length, ), ], - )); + ); }); } } diff --git a/lib/page/home.dart b/lib/page/home.dart index 2f8c7f5..4e69e25 100644 --- a/lib/page/home.dart +++ b/lib/page/home.dart @@ -80,6 +80,7 @@ class HomePageState extends State { children: [ // 音乐播放控制栏 const MusicPlayBar(), + // 底部导航按钮 Obx(() => CupertinoTabBar( activeColor: activeIconColor, backgroundColor: barBackgoundColor, diff --git a/lib/page/playing_music_page.dart b/lib/page/playing_music_page.dart index f3cb9b7..26f48ca 100644 --- a/lib/page/playing_music_page.dart +++ b/lib/page/playing_music_page.dart @@ -23,16 +23,12 @@ class SongDisplayPage extends StatefulWidget { class SongDisplayPageState extends State { PageState pageState = PageState.main; - int topFlex = 5; - int bottomFlex = 7; void onListBotton() { setState(() { if (pageState == PageState.list) { pageState = PageState.main; - bottomFlex = 7; } else { pageState = PageState.list; - bottomFlex = 2; } }); } @@ -41,10 +37,8 @@ class SongDisplayPageState extends State { setState(() { if (pageState == PageState.lyric) { pageState = PageState.main; - bottomFlex = 7; } else { pageState = PageState.lyric; - bottomFlex = 2; } }); } @@ -58,17 +52,12 @@ class SongDisplayPageState extends State { switch (pageState) { case PageState.main: topWidgets = [ - const Flexible( - flex: 1, - child: MusicArtPic(), - ) + const MusicArtPic(), ]; break; case PageState.list: topWidgets = [ - Padding(padding: EdgeInsets.only(top: screenHeight * 0.03)), const PlayingMusicCard(), - const Padding(padding: EdgeInsets.only(left: 30)), Container( padding: const EdgeInsets.only(left: 20), alignment: Alignment.centerLeft, @@ -80,42 +69,18 @@ class SongDisplayPageState extends State { ), ), ), - const PlayMusicList(), + const Expanded( + child: PlayMusicList(), + ), ]; break; case PageState.lyric: topWidgets = [ - Padding(padding: EdgeInsets.only(top: screenHeight * 0.03)), const PlayingMusicCard(), - const Padding(padding: EdgeInsets.only(top: 10, bottom: 10)), - LyricDisplay(maxHeight: screenHeight * 0.55), + LyricDisplay(maxHeight: screenHeight * 0.7), ]; break; } - // 底部组件,包括 ProgressSlider, QualityTime, ControlButton, VolumeSlider 和 BottomButton - List bottomWidgets = [ - if (pageState == PageState.main) - const Flexible(flex: 2, child: MusicInfo()), - const Flexible( - flex: 1, - child: ProgressSlider(), - ), - const Flexible( - flex: 1, - child: QualityTime(), - ), - Flexible( - flex: 3, - child: ControlButton( - buttonSize: screenWidth * 0.1, - buttonSpacing: screenWidth * 0.2, - ), - ), - const Flexible( - flex: 1, - child: VolumeSlider(), - ), - ]; return DismissiblePage( isFullScreen: true, @@ -135,33 +100,34 @@ class SongDisplayPageState extends State { ), ), child: Stack(children: [ + // 上方组件 Column( - children: [ - Flexible( - flex: topFlex, - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - children: topWidgets, - )), - // // 使用 Expanded 包裹一个新的 Column,以便底部组件从下往上排布 - // Flexible( - // flex: bottomFlex, - // child: Column( - // mainAxisAlignment: MainAxisAlignment.end, - // children: bottomWidgets, - // ), - // ), - ], + children: topWidgets, ), - // 固定在页面底部的导航按钮 - Align( - alignment: Alignment.bottomCenter, - child: BottomButton( - onList: onListBotton, - onLyric: onLyricBotton, - padding: const EdgeInsets.only(top: 10, bottom: 20), + // 固定在页面底部的内容 + Positioned( + bottom: 0, // 确保它固定在底部 + left: 0, + right: 0, + child: Column( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + if (pageState == PageState.main) const MusicInfo(), + const ProgressSlider(), + const QualityTime(), + ControlButton( + buttonSize: screenWidth * 0.1, + buttonSpacing: screenWidth * 0.2, + ), + const VolumeSlider(), + BottomButton( + onList: onListBotton, + onLyric: onLyricBotton, + padding: const EdgeInsets.only(top: 10, bottom: 20), + ), + ], ), - ) + ), ])), ); } diff --git a/pubspec.lock b/pubspec.lock index 223b9a0..b4714dd 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -331,54 +331,6 @@ packages: url: "https://pub.dev" source: hosted version: "15.0.0" - flutter_keyboard_visibility: - dependency: "direct main" - description: - name: flutter_keyboard_visibility - sha256: "98664be7be0e3ffca00de50f7f6a287ab62c763fc8c762e0a21584584a3ff4f8" - url: "https://pub.dev" - source: hosted - version: "6.0.0" - flutter_keyboard_visibility_linux: - dependency: transitive - description: - name: flutter_keyboard_visibility_linux - sha256: "6fba7cd9bb033b6ddd8c2beb4c99ad02d728f1e6e6d9b9446667398b2ac39f08" - url: "https://pub.dev" - source: hosted - version: "1.0.0" - flutter_keyboard_visibility_macos: - dependency: transitive - description: - name: flutter_keyboard_visibility_macos - sha256: c5c49b16fff453dfdafdc16f26bdd8fb8d55812a1d50b0ce25fc8d9f2e53d086 - url: "https://pub.dev" - source: hosted - version: "1.0.0" - flutter_keyboard_visibility_platform_interface: - dependency: transitive - description: - name: flutter_keyboard_visibility_platform_interface - sha256: e43a89845873f7be10cb3884345ceb9aebf00a659f479d1c8f4293fcb37022a4 - url: "https://pub.dev" - source: hosted - version: "2.0.0" - flutter_keyboard_visibility_web: - dependency: transitive - description: - name: flutter_keyboard_visibility_web - sha256: d3771a2e752880c79203f8d80658401d0c998e4183edca05a149f5098ce6e3d1 - url: "https://pub.dev" - source: hosted - version: "2.0.0" - flutter_keyboard_visibility_windows: - dependency: transitive - description: - name: flutter_keyboard_visibility_windows - sha256: fc4b0f0b6be9b93ae527f3d527fb56ee2d918cd88bbca438c478af7bcfd0ef73 - url: "https://pub.dev" - source: hosted - version: "1.0.0" flutter_launcher_icons: dependency: "direct dev" description: From 75396d148549d6e05ff6423bcda45bced8497d6e Mon Sep 17 00:00:00 2001 From: canxin Date: Fri, 3 May 2024 21:23:06 +0800 Subject: [PATCH 15/28] [Test] no bottom in display page --- lib/page/playing_music_page.dart | 46 ++++++++++++++++---------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/lib/page/playing_music_page.dart b/lib/page/playing_music_page.dart index 26f48ca..5e006c0 100644 --- a/lib/page/playing_music_page.dart +++ b/lib/page/playing_music_page.dart @@ -105,29 +105,29 @@ class SongDisplayPageState extends State { children: topWidgets, ), // 固定在页面底部的内容 - Positioned( - bottom: 0, // 确保它固定在底部 - left: 0, - right: 0, - child: Column( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - if (pageState == PageState.main) const MusicInfo(), - const ProgressSlider(), - const QualityTime(), - ControlButton( - buttonSize: screenWidth * 0.1, - buttonSpacing: screenWidth * 0.2, - ), - const VolumeSlider(), - BottomButton( - onList: onListBotton, - onLyric: onLyricBotton, - padding: const EdgeInsets.only(top: 10, bottom: 20), - ), - ], - ), - ), + // Positioned( + // bottom: 0, // 确保它固定在底部 + // left: 0, + // right: 0, + // child: Column( + // mainAxisAlignment: MainAxisAlignment.end, + // children: [ + // if (pageState == PageState.main) const MusicInfo(), + // const ProgressSlider(), + // const QualityTime(), + // ControlButton( + // buttonSize: screenWidth * 0.1, + // buttonSpacing: screenWidth * 0.2, + // ), + // const VolumeSlider(), + // BottomButton( + // onList: onListBotton, + // onLyric: onLyricBotton, + // padding: const EdgeInsets.only(top: 10, bottom: 20), + // ), + // ], + // ), + // ), ])), ); } From f2f9a8b231fff846454f33f82c6158f38012002c Mon Sep 17 00:00:00 2001 From: canxin Date: Fri, 3 May 2024 21:24:48 +0800 Subject: [PATCH 16/28] [Test] only button and volume --- lib/page/playing_music_page.dart | 46 ++++++++++++++++---------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/lib/page/playing_music_page.dart b/lib/page/playing_music_page.dart index 5e006c0..8a095c7 100644 --- a/lib/page/playing_music_page.dart +++ b/lib/page/playing_music_page.dart @@ -105,29 +105,29 @@ class SongDisplayPageState extends State { children: topWidgets, ), // 固定在页面底部的内容 - // Positioned( - // bottom: 0, // 确保它固定在底部 - // left: 0, - // right: 0, - // child: Column( - // mainAxisAlignment: MainAxisAlignment.end, - // children: [ - // if (pageState == PageState.main) const MusicInfo(), - // const ProgressSlider(), - // const QualityTime(), - // ControlButton( - // buttonSize: screenWidth * 0.1, - // buttonSpacing: screenWidth * 0.2, - // ), - // const VolumeSlider(), - // BottomButton( - // onList: onListBotton, - // onLyric: onLyricBotton, - // padding: const EdgeInsets.only(top: 10, bottom: 20), - // ), - // ], - // ), - // ), + Positioned( + bottom: 0, // 确保它固定在底部 + left: 0, + right: 0, + child: Column( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + // if (pageState == PageState.main) const MusicInfo(), + // const ProgressSlider(), + // const QualityTime(), + ControlButton( + buttonSize: screenWidth * 0.1, + buttonSpacing: screenWidth * 0.2, + ), + const VolumeSlider(), + BottomButton( + onList: onListBotton, + onLyric: onLyricBotton, + padding: const EdgeInsets.only(top: 10, bottom: 20), + ), + ], + ), + ), ])), ); } From c7f680a0ed208ae48062fb83eb97de198e483f25 Mon Sep 17 00:00:00 2001 From: canxin Date: Fri, 3 May 2024 21:27:13 +0800 Subject: [PATCH 17/28] [Test] only button and slider --- lib/page/playing_music_page.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/page/playing_music_page.dart b/lib/page/playing_music_page.dart index 8a095c7..60419dd 100644 --- a/lib/page/playing_music_page.dart +++ b/lib/page/playing_music_page.dart @@ -113,13 +113,13 @@ class SongDisplayPageState extends State { mainAxisAlignment: MainAxisAlignment.end, children: [ // if (pageState == PageState.main) const MusicInfo(), - // const ProgressSlider(), + const ProgressSlider(), // const QualityTime(), ControlButton( buttonSize: screenWidth * 0.1, buttonSpacing: screenWidth * 0.2, ), - const VolumeSlider(), + // const VolumeSlider(), BottomButton( onList: onListBotton, onLyric: onLyricBotton, From 5d8ed93fc19cf67eb987d45300fe94ed0bfa6a04 Mon Sep 17 00:00:00 2001 From: canxin Date: Fri, 3 May 2024 21:40:55 +0800 Subject: [PATCH 18/28] [Test] disable volume control in ios --- lib/page/playing_music_page.dart | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/page/playing_music_page.dart b/lib/page/playing_music_page.dart index 60419dd..d082875 100644 --- a/lib/page/playing_music_page.dart +++ b/lib/page/playing_music_page.dart @@ -1,3 +1,5 @@ +import 'dart:io'; + import 'package:app_rhyme/comp/card/playing_music_card.dart'; import 'package:app_rhyme/comp/play_page_comp/botton_button.dart'; import 'package:app_rhyme/comp/play_page_comp/control_button.dart'; @@ -112,14 +114,14 @@ class SongDisplayPageState extends State { child: Column( mainAxisAlignment: MainAxisAlignment.end, children: [ - // if (pageState == PageState.main) const MusicInfo(), + if (pageState == PageState.main) const MusicInfo(), const ProgressSlider(), - // const QualityTime(), + const QualityTime(), ControlButton( buttonSize: screenWidth * 0.1, buttonSpacing: screenWidth * 0.2, ), - // const VolumeSlider(), + if (!Platform.isIOS) const VolumeSlider(), BottomButton( onList: onListBotton, onLyric: onLyricBotton, From b6cce309917039300f482bbdc60e44d854d20801 Mon Sep 17 00:00:00 2001 From: canxin Date: Fri, 3 May 2024 22:21:03 +0800 Subject: [PATCH 19/28] [Test] disable volume control all; [Fix] fix change playing music quality; --- lib/comp/music_bar/bar.dart | 5 +++-- lib/comp/play_page_comp/quality_time.dart | 8 +++++--- lib/page/home.dart | 6 +++++- lib/page/in_music_list.dart | 4 +++- lib/page/playing_music_page.dart | 21 +++++++++++++-------- lib/types/music.dart | 21 ++++++++++++--------- lib/util/audio_controller.dart | 21 +++++++++++++++++++++ 7 files changed, 62 insertions(+), 24 deletions(-) diff --git a/lib/comp/music_bar/bar.dart b/lib/comp/music_bar/bar.dart index 224e633..bac8444 100644 --- a/lib/comp/music_bar/bar.dart +++ b/lib/comp/music_bar/bar.dart @@ -7,13 +7,14 @@ import 'package:flutter/cupertino.dart'; import 'package:get/get_state_manager/src/rx_flutter/rx_obx_widget.dart'; class MusicPlayBar extends StatelessWidget { - const MusicPlayBar({super.key}); + final double maxHeight; + const MusicPlayBar({super.key, required this.maxHeight}); @override Widget build(BuildContext context) { return ConstrainedBox( constraints: BoxConstraints( - maxHeight: MediaQuery.of(context).size.height * 0.1, + maxHeight: maxHeight, ), child: GestureDetector( onVerticalDragUpdate: (details) { diff --git a/lib/comp/play_page_comp/quality_time.dart b/lib/comp/play_page_comp/quality_time.dart index 62c83c4..9906728 100644 --- a/lib/comp/play_page_comp/quality_time.dart +++ b/lib/comp/play_page_comp/quality_time.dart @@ -69,11 +69,13 @@ class QualityTimeState extends State { actionCallbacks: (index) async { var playingMusic = globalAudioHandler.playingMusic.value; if (playingMusic != null) { + var displayData = DisplayMusic(playingMusic.ref, + info_: playingMusic.info); var newPlayMusic = await display2PlayMusic( - DisplayMusic.fromPlayMusic(playingMusic), - qualityOptions[index]); + displayData, qualityOptions[index]); if (newPlayMusic == null) return; - await globalAudioHandler.replaceMusic(newPlayMusic); + await globalAudioHandler + .replacePlayingMusic(newPlayMusic); } }); } diff --git a/lib/page/home.dart b/lib/page/home.dart index 4e69e25..8c14c16 100644 --- a/lib/page/home.dart +++ b/lib/page/home.dart @@ -1,3 +1,5 @@ +import 'dart:math'; + import 'package:app_rhyme/comp/music_bar/bar.dart'; import 'package:app_rhyme/main.dart'; import 'package:app_rhyme/page/out_music_list_grid.dart'; @@ -79,7 +81,9 @@ class HomePageState extends State { mainAxisSize: MainAxisSize.min, children: [ // 音乐播放控制栏 - const MusicPlayBar(), + MusicPlayBar( + maxHeight: min(60, MediaQuery.of(context).size.height * 0.1), + ), // 底部导航按钮 Obx(() => CupertinoTabBar( activeColor: activeIconColor, diff --git a/lib/page/in_music_list.dart b/lib/page/in_music_list.dart index 229c247..0baff3f 100644 --- a/lib/page/in_music_list.dart +++ b/lib/page/in_music_list.dart @@ -187,7 +187,9 @@ class MusicPageState extends State { }, () async { // 删除缓存 - var result = music.toCacheFileNameAndExtra(); + if (music.info.defaultQuality == null) return; + var result = music + .toCacheFileNameAndExtra(music.info.defaultQuality!); if (result == null) return; var (cacheFileName, _) = result; deleteCacheFile( diff --git a/lib/page/playing_music_page.dart b/lib/page/playing_music_page.dart index d082875..bb6d668 100644 --- a/lib/page/playing_music_page.dart +++ b/lib/page/playing_music_page.dart @@ -79,7 +79,7 @@ class SongDisplayPageState extends State { case PageState.lyric: topWidgets = [ const PlayingMusicCard(), - LyricDisplay(maxHeight: screenHeight * 0.7), + LyricDisplay(maxHeight: screenHeight * 0.50), ]; break; } @@ -103,8 +103,11 @@ class SongDisplayPageState extends State { ), child: Stack(children: [ // 上方组件 - Column( - children: topWidgets, + Container( + constraints: BoxConstraints(maxHeight: screenHeight * 0.7), + child: Column( + children: topWidgets, + ), ), // 固定在页面底部的内容 Positioned( @@ -117,11 +120,13 @@ class SongDisplayPageState extends State { if (pageState == PageState.main) const MusicInfo(), const ProgressSlider(), const QualityTime(), - ControlButton( - buttonSize: screenWidth * 0.1, - buttonSpacing: screenWidth * 0.2, - ), - if (!Platform.isIOS) const VolumeSlider(), + Container( + padding: const EdgeInsets.only(top: 10, bottom: 10), + child: ControlButton( + buttonSize: screenWidth * 0.1, + buttonSpacing: screenWidth * 0.2, + )), + // if (!Platform.isIOS) const VolumeSlider(), BottomButton( onList: onListBotton, onLyric: onLyricBotton, diff --git a/lib/types/music.dart b/lib/types/music.dart index 3d67757..ec60cec 100644 --- a/lib/types/music.dart +++ b/lib/types/music.dart @@ -25,7 +25,7 @@ Future display2PlayMusic(DisplayMusic music, } // 音乐缓存获取的逻辑 - var result = music.toCacheFileNameAndExtra(); + var result = music.toCacheFileNameAndExtra(finalQuality); if (result == null) { return null; } @@ -61,25 +61,28 @@ Future display2PlayMusic(DisplayMusic music, class DisplayMusic { late MusicW ref; late MusicInfo info; - DisplayMusic(MusicW musicRef_) { + DisplayMusic(MusicW musicRef_, {MusicInfo? info_}) { ref = musicRef_; - info = ref.getMusicInfo(); - } - DisplayMusic.fromPlayMusic(PlayMusic music) { - DisplayMusic(music.ref); + if (info_ != null) { + info = info_; + } else { + info = ref.getMusicInfo(); + } } - (String, String)? toCacheFileNameAndExtra() { + + (String, String)? toCacheFileNameAndExtra(Quality quality) { if (info.defaultQuality == null) { return null; } - var extra = ref.getExtraInto(quality: info.defaultQuality!); + var extra = ref.getExtraInto(quality: quality); var cacheFileName = "${info.name}_${info.artist.join(',')}_${info.source}_${extra.hashCode}.${info.defaultQuality!.format ?? "unknown"}"; return (cacheFileName, extra); } Future hasCache() async { - var result = toCacheFileNameAndExtra(); + if (info.defaultQuality == null) return false; + var result = toCacheFileNameAndExtra(info.defaultQuality!); if (result == null) { return false; } diff --git a/lib/util/audio_controller.dart b/lib/util/audio_controller.dart index 82891aa..d50f6d2 100644 --- a/lib/util/audio_controller.dart +++ b/lib/util/audio_controller.dart @@ -95,6 +95,27 @@ class AudioHandler extends GetxController { } } + Future replacePlayingMusic(PlayMusic playMusic) async { + try { + if (playingMusic.value == null) return; + int index = playMusicList + .indexWhere((element) => element.extra == playingMusic.value!.extra); + if (index != -1) { + // 删除对应位置的音乐 + await removeAt(index); + // 插入新音乐到对应位置 + await _insert(index, playMusic); + // 重新播放这个位置的音乐 + await seek(Duration.zero, index: index); + updateRx(); + await play(); + } + } catch (e) { + talker.error( + "[Music Handler] In replaceMusic, Failed to display2PlayMusic: $e"); + } + } + Future replaceMusic(PlayMusic playMusic) async { try { int index = playMusicList From db0cced32543168271a419b8e16cc72bd0fcb19c Mon Sep 17 00:00:00 2001 From: canxin Date: Fri, 3 May 2024 22:52:36 +0800 Subject: [PATCH 20/28] [Change] remove volume slider. --- lib/comp/play_page_comp/control_button.dart | 2 - lib/comp/play_page_comp/volume_slider.dart | 84 +++++++++---------- linux/flutter/generated_plugin_registrant.cc | 4 - linux/flutter/generated_plugins.cmake | 1 - macos/Flutter/GeneratedPluginRegistrant.swift | 2 - pubspec.lock | 16 ---- pubspec.yaml | 2 - .../flutter/generated_plugin_registrant.cc | 3 - windows/flutter/generated_plugins.cmake | 1 - 9 files changed, 42 insertions(+), 73 deletions(-) diff --git a/lib/comp/play_page_comp/control_button.dart b/lib/comp/play_page_comp/control_button.dart index 81ef940..fa41dbc 100644 --- a/lib/comp/play_page_comp/control_button.dart +++ b/lib/comp/play_page_comp/control_button.dart @@ -1,5 +1,3 @@ -import 'dart:developer'; - import 'package:app_rhyme/util/audio_controller.dart'; import 'package:flutter/cupertino.dart'; import 'package:get/get_state_manager/src/rx_flutter/rx_obx_widget.dart'; diff --git a/lib/comp/play_page_comp/volume_slider.dart b/lib/comp/play_page_comp/volume_slider.dart index 5b5f13e..aaee117 100644 --- a/lib/comp/play_page_comp/volume_slider.dart +++ b/lib/comp/play_page_comp/volume_slider.dart @@ -1,47 +1,47 @@ -import 'dart:async'; +// import 'dart:async'; -import 'package:flutter/cupertino.dart'; -import 'package:flutter_volume_controller/flutter_volume_controller.dart'; -import 'package:interactive_slider/interactive_slider.dart'; +// import 'package:flutter/cupertino.dart'; +// import 'package:flutter_volume_controller/flutter_volume_controller.dart'; +// import 'package:interactive_slider/interactive_slider.dart'; -class VolumeSlider extends StatefulWidget { - final double padding; - const VolumeSlider({super.key, this.padding = 5.0}); +// class VolumeSlider extends StatefulWidget { +// final double padding; +// const VolumeSlider({super.key, this.padding = 5.0}); - @override - State createState() => VolumeSliderState(); -} +// @override +// State createState() => VolumeSliderState(); +// } -class VolumeSliderState extends State { - InteractiveSliderController volumeController = InteractiveSliderController(0); - late StreamSubscription listen2; - @override - void initState() { - super.initState(); - listen2 = FlutterVolumeController.addListener((value) { - try { - volumeController.value = value; - } catch (e) { - if (e.toString().contains("disposed")) { - listen2.cancel(); - } - } - }); - } +// class VolumeSliderState extends State { +// InteractiveSliderController volumeController = InteractiveSliderController(0); +// late StreamSubscription listen2; +// @override +// void initState() { +// super.initState(); +// listen2 = FlutterVolumeController.addListener((value) { +// try { +// volumeController.value = value; +// } catch (e) { +// if (e.toString().contains("disposed")) { +// listen2.cancel(); +// } +// } +// }); +// } - @override - Widget build(BuildContext context) { - return Container( - padding: EdgeInsets.only(left: widget.padding, right: widget.padding), - child: InteractiveSlider( - controller: volumeController, - padding: const EdgeInsets.all(0), - onProgressUpdated: (value) { - FlutterVolumeController.setVolume(value); - }, - startIcon: const Icon(CupertinoIcons.volume_down), - endIcon: const Icon(CupertinoIcons.volume_up), - ), - ); - } -} +// @override +// Widget build(BuildContext context) { +// return Container( +// padding: EdgeInsets.only(left: widget.padding, right: widget.padding), +// child: InteractiveSlider( +// controller: volumeController, +// padding: const EdgeInsets.all(0), +// onProgressUpdated: (value) { +// FlutterVolumeController.setVolume(value); +// }, +// startIcon: const Icon(CupertinoIcons.volume_down), +// endIcon: const Icon(CupertinoIcons.volume_up), +// ), +// ); +// } +// } diff --git a/linux/flutter/generated_plugin_registrant.cc b/linux/flutter/generated_plugin_registrant.cc index 19d24f2..d76db18 100644 --- a/linux/flutter/generated_plugin_registrant.cc +++ b/linux/flutter/generated_plugin_registrant.cc @@ -7,7 +7,6 @@ #include "generated_plugin_registrant.h" #include -#include #include #include #include @@ -16,9 +15,6 @@ void fl_register_plugins(FlPluginRegistry* registry) { g_autoptr(FlPluginRegistrar) file_selector_linux_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "FileSelectorPlugin"); file_selector_plugin_register_with_registrar(file_selector_linux_registrar); - g_autoptr(FlPluginRegistrar) flutter_volume_controller_registrar = - fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterVolumeControllerPlugin"); - flutter_volume_controller_plugin_register_with_registrar(flutter_volume_controller_registrar); g_autoptr(FlPluginRegistrar) screen_retriever_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "ScreenRetrieverPlugin"); screen_retriever_plugin_register_with_registrar(screen_retriever_registrar); diff --git a/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake index f5ebbfc..b32a2ec 100644 --- a/linux/flutter/generated_plugins.cmake +++ b/linux/flutter/generated_plugins.cmake @@ -4,7 +4,6 @@ list(APPEND FLUTTER_PLUGIN_LIST file_selector_linux - flutter_volume_controller screen_retriever url_launcher_linux window_manager diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index f2662e0..a1d8d31 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -8,7 +8,6 @@ import Foundation import audio_service import audio_session import file_selector_macos -import flutter_volume_controller import just_audio import path_provider_foundation import screen_retriever @@ -20,7 +19,6 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { AudioServicePlugin.register(with: registry.registrar(forPlugin: "AudioServicePlugin")) AudioSessionPlugin.register(with: registry.registrar(forPlugin: "AudioSessionPlugin")) FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin")) - FlutterVolumeControllerPlugin.register(with: registry.registrar(forPlugin: "FlutterVolumeControllerPlugin")) JustAudioPlugin.register(with: registry.registrar(forPlugin: "JustAudioPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) ScreenRetrieverPlugin.register(with: registry.registrar(forPlugin: "ScreenRetrieverPlugin")) diff --git a/pubspec.lock b/pubspec.lock index b4714dd..40f9b80 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -393,14 +393,6 @@ packages: description: flutter source: sdk version: "0.0.0" - flutter_volume_controller: - dependency: "direct main" - description: - name: flutter_volume_controller - sha256: fa4c36dfe7ef7f423704f34ab8e64e00b4a30a90aa6e56f251e9dba649efcd7f - url: "https://pub.dev" - source: hosted - version: "1.3.2" flutter_web_plugins: dependency: transitive description: flutter @@ -793,14 +785,6 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.8" - pro_animated_blur: - dependency: "direct main" - description: - name: pro_animated_blur - sha256: "2a4506266b743939758425a6ac28d249365ccdf3d7fc159675832c3c156d8300" - url: "https://pub.dev" - source: hosted - version: "0.0.2+1" process: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 766af07..7f71861 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -24,9 +24,7 @@ dependencies: git: url: https://github.com/bdlukaa/just_audio_windows path: just_audio_windows - flutter_volume_controller: ^1.3.2 blur: ^3.1.0 - pro_animated_blur: ^0.0.2+1 infinite_scroll_pagination: ^4.0.0 path_provider: ^2.1.3 flutter_popup: ^3.3.0 diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index ded73b0..b4d78a4 100644 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -7,7 +7,6 @@ #include "generated_plugin_registrant.h" #include -#include #include #include #include @@ -17,8 +16,6 @@ void RegisterPlugins(flutter::PluginRegistry* registry) { FileSelectorWindowsRegisterWithRegistrar( registry->GetRegistrarForPlugin("FileSelectorWindows")); - FlutterVolumeControllerPluginCApiRegisterWithRegistrar( - registry->GetRegistrarForPlugin("FlutterVolumeControllerPluginCApi")); JustAudioWindowsPluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("JustAudioWindowsPlugin")); ScreenRetrieverPluginRegisterWithRegistrar( diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index 334e472..f007bea 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -4,7 +4,6 @@ list(APPEND FLUTTER_PLUGIN_LIST file_selector_windows - flutter_volume_controller just_audio_windows screen_retriever share_plus From 9ae363df4762cdea72f8dff90457de39551c684d Mon Sep 17 00:00:00 2001 From: canxin Date: Sat, 4 May 2024 14:29:25 +0800 Subject: [PATCH 21/28] [Test] Try to fix position error after seek in ios --- lib/comp/card/music_card.dart | 2 - lib/comp/play_page_comp/lyric.dart | 9 +- lib/comp/play_page_comp/progress_slider.dart | 31 ++-- lib/page/playing_music_page copy.dart | 182 ------------------- lib/page/playing_music_page.dart | 3 - lib/page/setting.dart | 1 - lib/util/audio_controller.dart | 36 +++- 7 files changed, 47 insertions(+), 217 deletions(-) delete mode 100644 lib/page/playing_music_page copy.dart diff --git a/lib/comp/card/music_card.dart b/lib/comp/card/music_card.dart index 9c4de90..5994681 100644 --- a/lib/comp/card/music_card.dart +++ b/lib/comp/card/music_card.dart @@ -1,5 +1,3 @@ -import 'dart:io'; - import 'package:app_rhyme/src/rust/api/mirror.dart'; import 'package:app_rhyme/util/colors.dart'; import 'package:app_rhyme/util/default.dart'; diff --git a/lib/comp/play_page_comp/lyric.dart b/lib/comp/play_page_comp/lyric.dart index 4f6df1c..1d3d42c 100644 --- a/lib/comp/play_page_comp/lyric.dart +++ b/lib/comp/play_page_comp/lyric.dart @@ -1,5 +1,6 @@ import 'dart:async'; +import 'package:app_rhyme/main.dart'; import 'package:app_rhyme/types/music.dart'; import 'package:app_rhyme/util/audio_controller.dart'; import 'package:app_rhyme/util/time_parse.dart'; @@ -62,10 +63,12 @@ class LyricDisplayState extends State { children: [ IconButton( onPressed: () { - globalAudioHandler - .seek(Duration(milliseconds: progress)) - .then((value) { + var toSeek = Duration(milliseconds: progress); + talker.info( + "[Lyric] Call seek to ${formatDuration(toSeek.inSeconds)}"); + globalAudioHandler.seek(toSeek).then((value) { confirm.call(); + // 这里是考虑到在暂停状态下。需要开启播放 if (!globalAudioHandler.isPlaying()) { globalAudioHandler.play(); } diff --git a/lib/comp/play_page_comp/progress_slider.dart b/lib/comp/play_page_comp/progress_slider.dart index 86ac780..7da5171 100644 --- a/lib/comp/play_page_comp/progress_slider.dart +++ b/lib/comp/play_page_comp/progress_slider.dart @@ -1,6 +1,8 @@ import 'dart:async'; +import 'package:app_rhyme/main.dart'; import 'package:app_rhyme/util/audio_controller.dart'; +import 'package:app_rhyme/util/time_parse.dart'; import 'package:flutter/cupertino.dart'; import 'package:interactive_slider/interactive_slider.dart'; @@ -15,7 +17,7 @@ class ProgressSliderState extends State { InteractiveSliderController progressController = InteractiveSliderController(0); late StreamSubscription listen1; - bool isPressing = false; + bool isPressing = true; @override void initState() { @@ -44,26 +46,21 @@ class ProgressSliderState extends State { padding: const EdgeInsets.all(0), controller: progressController, onProgressUpdated: (value) { - isPressing = false; var toSeek = globalAudioUiController.getToSeek(value); + talker.info( + "[Slider] Call seek to ${formatDuration(toSeek.inSeconds)}"); globalAudioHandler.seek(toSeek); + isPressing = false; }, ), - onTap: () { - isPressing = true; - }, - onLongPress: () { - isPressing = true; - }, - onLongPressMoveUpdate: (_) { - isPressing = true; - }, - onPanDown: (_) { - isPressing = true; - }, - onPanStart: (_) { - isPressing = true; - }, + onTapDown: (_) => setState(() => isPressing = true), + onTapUp: (_) => setState(() => isPressing = true), + onTapCancel: () => setState(() => isPressing = true), + onLongPressStart: (_) => setState(() => isPressing = true), + onLongPressEnd: (_) => setState(() => isPressing = true), + onPanDown: (_) => setState(() => isPressing = true), + onPanCancel: () => setState(() => isPressing = true), + onPanEnd: (_) => setState(() => isPressing = true), ), ); } diff --git a/lib/page/playing_music_page copy.dart b/lib/page/playing_music_page copy.dart deleted file mode 100644 index 7dd3c00..0000000 --- a/lib/page/playing_music_page copy.dart +++ /dev/null @@ -1,182 +0,0 @@ -// import 'package:app_rhyme/comp/card/playing_music_card.dart'; -// import 'package:app_rhyme/comp/play_page_comp/botton_button.dart'; -// import 'package:app_rhyme/comp/play_page_comp/control_button.dart'; -// import 'package:app_rhyme/comp/play_page_comp/lyric.dart'; -// import 'package:app_rhyme/comp/play_page_comp/music_artpic.dart'; -// import 'package:app_rhyme/comp/play_page_comp/music_info.dart'; -// import 'package:app_rhyme/comp/play_page_comp/music_list.dart'; -// import 'package:app_rhyme/comp/play_page_comp/progress_slider.dart'; -// import 'package:app_rhyme/comp/play_page_comp/quality_time.dart'; -// import 'package:app_rhyme/comp/play_page_comp/volume_slider.dart'; -// import 'package:flutter/cupertino.dart'; - -// import 'package:dismissible_page/dismissible_page.dart'; -// import 'package:pro_animated_blur/pro_animated_blur.dart'; - -// enum PageState { main, list, lyric } - -// class SongDisplayPage extends StatefulWidget { -// const SongDisplayPage({super.key}); - -// @override -// SongDisplayPageState createState() => SongDisplayPageState(); -// } - -// class SongDisplayPageState extends State { -// PageState pageState = PageState.main; -// int topFlex = 5; -// int bottomFlex = 7; -// void onListBotton() { -// setState(() { -// if (pageState == PageState.list) { -// pageState = PageState.main; -// bottomFlex = 7; -// } else { -// pageState = PageState.list; -// bottomFlex = 2; -// } -// }); -// } - -// void onLyricBotton() { -// setState(() { -// if (pageState == PageState.lyric) { -// pageState = PageState.main; -// bottomFlex = 7; -// } else { -// pageState = PageState.lyric; -// bottomFlex = 2; -// } -// }); -// } - -// @override -// Widget build(BuildContext context) { -// double screenWidth = MediaQuery.of(context).size.width; -// double screenHeight = MediaQuery.of(context).size.height; - -// List topWidgets; -// switch (pageState) { -// case PageState.main: -// topWidgets = [ -// const Flexible( -// flex: 1, -// child: MusicArtPic(), -// ) -// ]; -// break; -// case PageState.list: -// topWidgets = [ -// Padding(padding: EdgeInsets.only(top: screenHeight * 0.03)), -// const PlayingMusicCard(), -// const Padding(padding: EdgeInsets.only(left: 30)), -// Container( -// padding: const EdgeInsets.only(left: 20), -// alignment: Alignment.centerLeft, -// child: const Text( -// '待播清单', -// style: TextStyle( -// fontSize: 20.0, -// fontWeight: FontWeight.bold, -// ), -// ), -// ), -// const PlayMusicList(), -// ]; -// break; -// case PageState.lyric: -// topWidgets = [ -// Padding(padding: EdgeInsets.only(top: screenHeight * 0.03)), -// const PlayingMusicCard(), -// const Padding(padding: EdgeInsets.only(top: 10, bottom: 10)), -// LyricDisplay(maxHeight: screenHeight * 0.55), -// ]; -// break; -// } -// // 底部组件,包括 ProgressSlider, QualityTime, ControlButton, VolumeSlider 和 BottomButton -// List bottomWidgets = [ -// if (pageState == PageState.main) -// const Flexible(flex: 2, child: MusicInfo()), -// const Flexible( -// flex: 1, -// child: ProgressSlider(), -// ), -// const Flexible( -// flex: 1, -// child: QualityTime(), -// ), -// Flexible( -// flex: 3, -// child: ControlButton( -// buttonSize: screenWidth * 0.1, -// buttonSpacing: screenWidth * 0.2, -// ), -// ), -// const Flexible( -// flex: 1, -// child: VolumeSlider(), -// ), -// Flexible( -// flex: 1, -// child: Container( -// padding: const EdgeInsets.only(top: 10, bottom: 10), -// child: BottomButton( -// onList: onListBotton, -// onLyric: onLyricBotton, -// ), -// ), -// ) -// ]; - -// return DismissiblePage( -// isFullScreen: true, -// direction: DismissiblePageDismissDirection.down, -// backgroundColor: CupertinoColors.white, -// onDismissed: () => Navigator.of(context).pop(), -// child: Container( -// clipBehavior: Clip.antiAliasWithSaveLayer, -// decoration: const BoxDecoration( -// gradient: LinearGradient( -// begin: Alignment.topRight, -// end: Alignment.bottomLeft, -// colors: [ -// CupertinoColors.systemGrey2, -// CupertinoColors.systemGrey, -// ], -// ), -// ), -// child: ProAnimatedBlur( -// blur: 500, -// duration: const Duration(milliseconds: 0), -// child: Column( -// children: [ -// Flexible( -// flex: topFlex, -// child: Column( -// mainAxisAlignment: MainAxisAlignment.start, -// children: topWidgets, -// )), -// // 使用 Expanded 包裹一个新的 Column,以便底部组件从下往上排布 -// Flexible( -// flex: bottomFlex, -// child: Column( -// mainAxisAlignment: MainAxisAlignment.end, -// children: bottomWidgets, -// ), -// ), -// ], -// ), -// ), -// ), -// ); -// } -// } - -// void navigateToSongDisplayPage(BuildContext context) { -// Navigator.of(context).push( -// CupertinoPageRoute( -// builder: (context) => const SongDisplayPage(), -// fullscreenDialog: true, -// ), -// ); -// } diff --git a/lib/page/playing_music_page.dart b/lib/page/playing_music_page.dart index bb6d668..9c1e2d5 100644 --- a/lib/page/playing_music_page.dart +++ b/lib/page/playing_music_page.dart @@ -1,5 +1,3 @@ -import 'dart:io'; - import 'package:app_rhyme/comp/card/playing_music_card.dart'; import 'package:app_rhyme/comp/play_page_comp/botton_button.dart'; import 'package:app_rhyme/comp/play_page_comp/control_button.dart'; @@ -9,7 +7,6 @@ import 'package:app_rhyme/comp/play_page_comp/music_info.dart'; import 'package:app_rhyme/comp/play_page_comp/music_list.dart'; import 'package:app_rhyme/comp/play_page_comp/progress_slider.dart'; import 'package:app_rhyme/comp/play_page_comp/quality_time.dart'; -import 'package:app_rhyme/comp/play_page_comp/volume_slider.dart'; import 'package:flutter/cupertino.dart'; import 'package:dismissible_page/dismissible_page.dart'; diff --git a/lib/page/setting.dart b/lib/page/setting.dart index 554b58b..949bdd8 100644 --- a/lib/page/setting.dart +++ b/lib/page/setting.dart @@ -5,7 +5,6 @@ import 'package:app_rhyme/types/extern_api.dart'; import 'package:app_rhyme/util/default.dart'; import 'package:flutter/cupertino.dart'; import 'package:file_picker/file_picker.dart'; -import 'package:flutter/material.dart'; import 'package:talker_flutter/talker_flutter.dart'; class SettingsPage extends StatefulWidget { diff --git a/lib/util/audio_controller.dart b/lib/util/audio_controller.dart index d50f6d2..9fc4d7f 100644 --- a/lib/util/audio_controller.dart +++ b/lib/util/audio_controller.dart @@ -1,6 +1,6 @@ import 'package:app_rhyme/main.dart'; import 'package:app_rhyme/types/music.dart'; -import 'package:audio_service/audio_service.dart'; +import 'package:app_rhyme/util/time_parse.dart'; import 'package:audio_session/audio_session.dart'; import 'package:get/get.dart'; import 'package:just_audio/just_audio.dart'; @@ -39,6 +39,7 @@ class AudioHandler extends GetxController { // 关闭随机播放 _player.setShuffleModeEnabled(false); // 监听错误事件并用talker来log + _player.playbackEventStream.listen((event) {}, onError: (Object e, StackTrace stackTrace) { talker.error('[PlaybackEventStream Error] $e'); @@ -257,7 +258,12 @@ class AudioHandler extends GetxController { Future seek(Duration position, {int? index}) async { try { await _player.seek(position, index: index); - talker.info("[Music Handler] In seek, Succeed"); + talker.info( + "[Music Handler] In seek, Succeed; Seek to ${formatDuration(position.inSeconds)} of ${index ?? "current"}"); + Future.delayed(Duration(seconds: 1)).then((value) { + talker.info( + "[Music Handler] After Seek, the position is ${formatDuration(globalAudioUiController.position.value.inSeconds - 1)}"); + }); } catch (e) { talker.error("[Music Handler] In seek, error occur: $e"); } @@ -293,7 +299,6 @@ class AudioHandler extends GetxController { playMusicList.map((element) => element.info.name).join(","); String sourceListStr = playSourceList.sequence.map((e) => e.tag.title).join(","); - String msg; if (playListStr == sourceListStr) { talker.log( "[Music Handler] $prefix: PlayList = PlaySourceList, length = ${playMusicList.length}, content = [$playListStr]"); @@ -346,16 +351,29 @@ class AudioUiController extends GetxController { update(); } }); - globalAudioHandler._player - .createPositionStream( - maxPeriod: const Duration(milliseconds: 100), - minPeriod: const Duration(milliseconds: 1)) - .listen((newPosition) { - position.value = newPosition; + globalAudioHandler._player.positionDiscontinuityStream.listen((event) { + position.value = event.event.updatePosition; + playProgress.value = + position.value.inMicroseconds / duration.value.inMicroseconds; + update(); + }); + globalAudioHandler._player.positionStream.listen((event) { + position.value = event; playProgress.value = position.value.inMicroseconds / duration.value.inMicroseconds; update(); }); + + // globalAudioHandler._player + // .createPositionStream( + // maxPeriod: const Duration(milliseconds: 100), + // minPeriod: const Duration(milliseconds: 1)) + // .listen((newPosition) { + // position.value = newPosition; + // playProgress.value = + // position.value.inMicroseconds / duration.value.inMicroseconds; + // update(); + // }); } Duration getToSeek(double toSeek) { From 0a890725b297f273680ae011f2888b206cb4d43a Mon Sep 17 00:00:00 2001 From: canxin Date: Sat, 4 May 2024 15:06:51 +0800 Subject: [PATCH 22/28] [Fix] slider default pressing false. --- lib/comp/play_page_comp/progress_slider.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/comp/play_page_comp/progress_slider.dart b/lib/comp/play_page_comp/progress_slider.dart index 7da5171..1edeb98 100644 --- a/lib/comp/play_page_comp/progress_slider.dart +++ b/lib/comp/play_page_comp/progress_slider.dart @@ -17,7 +17,7 @@ class ProgressSliderState extends State { InteractiveSliderController progressController = InteractiveSliderController(0); late StreamSubscription listen1; - bool isPressing = true; + bool isPressing = false; @override void initState() { From 438404c83b9f61c714df40654194e8e85d799463 Mon Sep 17 00:00:00 2001 From: canxin Date: Sat, 4 May 2024 21:25:52 +0800 Subject: [PATCH 23/28] [Test] try to fix seek error of ios. --- lib/types/music.dart | 27 +++++++++++++++++++++++++++ lib/util/audio_controller.dart | 25 ++++--------------------- 2 files changed, 31 insertions(+), 21 deletions(-) diff --git a/lib/types/music.dart b/lib/types/music.dart index ec60cec..61b4f29 100644 --- a/lib/types/music.dart +++ b/lib/types/music.dart @@ -1,9 +1,12 @@ +import 'dart:io'; + import 'package:app_rhyme/main.dart'; import 'package:app_rhyme/src/rust/api/cache.dart'; import 'package:app_rhyme/src/rust/api/mirror.dart'; import 'package:app_rhyme/src/rust/api/music_sdk.dart'; import 'package:app_rhyme/util/default.dart'; import 'package:audio_service/audio_service.dart'; +import 'package:just_audio/just_audio.dart'; // 是一个原本只具有展示功能的DisplayMusicTuple通过请求第三方api变成可以播放的音乐 // 这个过程已经决定了一个音乐是否可以播放,因此本函数应该可能throw Exception @@ -160,4 +163,28 @@ class PlayMusic { artUri: artUri, artist: info.artist.join(",")); } + + AudioSource toAudioSource() { + if (playInfo.file.contains("http")) { + if (Platform.isIOS || Platform.isMacOS) { + return ProgressiveAudioSource(Uri.parse(playInfo.file), + tag: toMediaItem(), + options: const ProgressiveAudioSourceOptions( + darwinAssetOptions: + DarwinAssetOptions(preferPreciseDurationAndTiming: true))); + } else { + return AudioSource.uri(Uri.parse(playInfo.file), tag: toMediaItem()); + } + } else { + if (Platform.isIOS || Platform.isMacOS) { + return ProgressiveAudioSource(Uri.file(playInfo.file), + tag: toMediaItem(), + options: const ProgressiveAudioSourceOptions( + darwinAssetOptions: + DarwinAssetOptions(preferPreciseDurationAndTiming: true))); + } else { + return AudioSource.file(playInfo.file, tag: toMediaItem()); + } + } + } } diff --git a/lib/util/audio_controller.dart b/lib/util/audio_controller.dart index 9fc4d7f..7622662 100644 --- a/lib/util/audio_controller.dart +++ b/lib/util/audio_controller.dart @@ -77,14 +77,7 @@ class AudioHandler extends GetxController { // 添加新的音乐 playMusicList.add(playMusic); - if (playMusic.playInfo.file.contains("http")) { - await playSourceList.add(AudioSource.uri( - Uri.parse(playMusic.playInfo.file), - tag: playMusic.toMediaItem())); - } else { - await playSourceList.add(AudioSource.file(playMusic.playInfo.file, - tag: playMusic.toMediaItem())); - } + await playSourceList.add(playMusic.toAudioSource()); // 播放新的音乐 await seek(Duration.zero, index: playSourceList.length - 1); @@ -157,14 +150,7 @@ class AudioHandler extends GetxController { for (var playMusic in playMusicsResults) { if (playMusic == null) continue; newPlayMusics.add(playMusic); - - if (playMusic.playInfo.file.contains("http")) { - newAudioSources.add(AudioSource.uri(Uri.parse(playMusic.playInfo.file), - tag: playMusic.toMediaItem())); - } else { - newAudioSources.add(AudioSource.file(playMusic.playInfo.file, - tag: playMusic.toMediaItem())); - } + newAudioSources.add(playMusic.toAudioSource()); } await clear(); @@ -186,10 +172,7 @@ class AudioHandler extends GetxController { Future _insert(int index, PlayMusic music) async { playMusicList.insert(index, music); - await playSourceList.insert( - index, - AudioSource.uri(Uri.parse(music.playInfo.file), - tag: music.toMediaItem())); + await playSourceList.insert(index, music.toAudioSource()); } Future clear() async { @@ -260,7 +243,7 @@ class AudioHandler extends GetxController { await _player.seek(position, index: index); talker.info( "[Music Handler] In seek, Succeed; Seek to ${formatDuration(position.inSeconds)} of ${index ?? "current"}"); - Future.delayed(Duration(seconds: 1)).then((value) { + Future.delayed(const Duration(seconds: 1)).then((value) { talker.info( "[Music Handler] After Seek, the position is ${formatDuration(globalAudioUiController.position.value.inSeconds - 1)}"); }); From 4fa6f2e60d414bf790ea84c865946e45c69888a9 Mon Sep 17 00:00:00 2001 From: canxin Date: Sat, 4 May 2024 21:43:21 +0800 Subject: [PATCH 24/28] [Test] Add log in toAudioSource; --- lib/types/music.dart | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/types/music.dart b/lib/types/music.dart index 61b4f29..3b77909 100644 --- a/lib/types/music.dart +++ b/lib/types/music.dart @@ -167,6 +167,7 @@ class PlayMusic { AudioSource toAudioSource() { if (playInfo.file.contains("http")) { if (Platform.isIOS || Platform.isMacOS) { + talker.info("[PlayMusic ToAudioSource] Use ProgressiveAudioSource"); return ProgressiveAudioSource(Uri.parse(playInfo.file), tag: toMediaItem(), options: const ProgressiveAudioSourceOptions( @@ -177,6 +178,7 @@ class PlayMusic { } } else { if (Platform.isIOS || Platform.isMacOS) { + talker.info("[PlayMusic ToAudioSource] Use ProgressiveAudioSource"); return ProgressiveAudioSource(Uri.file(playInfo.file), tag: toMediaItem(), options: const ProgressiveAudioSourceOptions( From 9c7210bdacf2f5862cf1f5a97f881679efcdca43 Mon Sep 17 00:00:00 2001 From: canxin Date: Sun, 5 May 2024 02:48:42 +0800 Subject: [PATCH 25/28] [Test] Try fix file seek in ios by use my fork of just audio. --- pubspec.lock | 9 +++++---- pubspec.yaml | 5 ++++- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/pubspec.lock b/pubspec.lock index 40f9b80..cb5bad9 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -571,10 +571,11 @@ packages: just_audio: dependency: "direct main" description: - name: just_audio - sha256: b7cb6bbf3750caa924d03f432ba401ec300fd90936b3f73a9b33d58b1e96286b - url: "https://pub.dev" - source: hosted + path: just_audio + ref: HEAD + resolved-ref: "295207826a37ce44d3c1d20cc0d3857a5d04ceec" + url: "https://github.com/canxin121/just_audio" + source: git version: "0.9.37" just_audio_background: dependency: "direct main" diff --git a/pubspec.yaml b/pubspec.yaml index 7f71861..704b9c4 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -19,7 +19,10 @@ dependencies: dismissible_page: ^1.0.2 get: ^4.6.6 audio_service: ^0.18.13 - just_audio: ^0.9.37 + just_audio: + git: + url: https://github.com/canxin121/just_audio + path: just_audio just_audio_windows: git: url: https://github.com/bdlukaa/just_audio_windows From 3add1d1a1c5a6844fd84a0a9a49546bbaf9fe0ed Mon Sep 17 00:00:00 2001 From: canxin Date: Sun, 5 May 2024 17:48:35 +0800 Subject: [PATCH 26/28] [Fix] windows can't add music. --- assets/nature.mp3 | Bin 0 -> 40491 bytes lib/types/music.dart | 4 ++-- lib/util/audio_controller.dart | 35 ++++++++++++++++++++++++++------- pubspec.lock | 24 ++++++++++++++++++++++ pubspec.yaml | 3 ++- 5 files changed, 56 insertions(+), 10 deletions(-) create mode 100644 assets/nature.mp3 diff --git a/assets/nature.mp3 b/assets/nature.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..ea56cbfb71857fdd50ebccaa17917fe53427ead9 GIT binary patch literal 40491 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
)MbV+IXV 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 O7ytnDfB)Too%ufsJfe&M literal 0 HcmV?d00001 diff --git a/lib/types/music.dart b/lib/types/music.dart index 3b77909..8dff644 100644 --- a/lib/types/music.dart +++ b/lib/types/music.dart @@ -166,7 +166,7 @@ class PlayMusic { AudioSource toAudioSource() { if (playInfo.file.contains("http")) { - if (Platform.isIOS || Platform.isMacOS) { + if (Platform.isIOS || Platform.isMacOS || Platform.isWindows) { talker.info("[PlayMusic ToAudioSource] Use ProgressiveAudioSource"); return ProgressiveAudioSource(Uri.parse(playInfo.file), tag: toMediaItem(), @@ -177,7 +177,7 @@ class PlayMusic { return AudioSource.uri(Uri.parse(playInfo.file), tag: toMediaItem()); } } else { - if (Platform.isIOS || Platform.isMacOS) { + if (Platform.isIOS || Platform.isMacOS || Platform.isWindows) { talker.info("[PlayMusic ToAudioSource] Use ProgressiveAudioSource"); return ProgressiveAudioSource(Uri.file(playInfo.file), tag: toMediaItem(), diff --git a/lib/util/audio_controller.dart b/lib/util/audio_controller.dart index 7622662..b48b94d 100644 --- a/lib/util/audio_controller.dart +++ b/lib/util/audio_controller.dart @@ -1,3 +1,5 @@ +import 'dart:io'; + import 'package:app_rhyme/main.dart'; import 'package:app_rhyme/types/music.dart'; import 'package:app_rhyme/util/time_parse.dart'; @@ -6,6 +8,8 @@ import 'package:get/get.dart'; import 'package:just_audio/just_audio.dart'; import 'package:just_audio_background/just_audio_background.dart'; +bool isWindowsFirstPlay = true; + DateTime lastComplete = DateTime(1999); late AudioHandler globalAudioHandler; @@ -13,11 +17,13 @@ late AudioUiController globalAudioUiController; // 初始化所有和Audio相关的内容 Future initGlobalAudioHandler() async { - await JustAudioBackground.init( - androidNotificationChannelId: 'com.ryanheise.bg_demo.channel.audio', - androidNotificationChannelName: 'Audio playback', - androidNotificationOngoing: true, - ); + if (!Platform.isWindows) { + await JustAudioBackground.init( + androidNotificationChannelId: 'com.ryanheise.bg_demo.channel.audio', + androidNotificationChannelName: 'Audio playback', + androidNotificationOngoing: true, + ); + } final session = await AudioSession.instance; await session.configure(const AudioSessionConfiguration.music()); @@ -27,11 +33,19 @@ Future initGlobalAudioHandler() async { class AudioHandler extends GetxController { final AudioPlayer _player = AudioPlayer(); - // 这两个本质都是list,但是我们需要确保其同步变化 final RxList playMusicList = RxList([]); final Rx playingMusic = Rx(null); final ConcatenatingAudioSource playSourceList = - ConcatenatingAudioSource(children: []); + ConcatenatingAudioSource(children: [ + if (Platform.isWindows) + AudioSource.asset( + "assets/nature.mp3", + tag: const MediaItem( + title: "Empty", + id: 'default', + ), + ), + ]); Future _init() async { // 先默认开启所有的循环 @@ -59,6 +73,10 @@ class AudioHandler extends GetxController { Future addMusicPlay(DisplayMusic music) async { try { + if (Platform.isWindows && isWindowsFirstPlay) { + isWindowsFirstPlay = false; + await clear(); + } PlayMusic? playMusic; var index = -1; if (music.info.defaultQuality != null) { @@ -268,6 +286,9 @@ class AudioHandler extends GetxController { } catch (e) { talker.error("[Music Handler] Failed to updateRx,set null"); playingMusic.value = null; + Future.delayed(const Duration(seconds: 1)).then((value) { + updateRx(); + }); } } else { playingMusic.value = null; diff --git a/pubspec.lock b/pubspec.lock index cb5bad9..f4aa4d4 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -225,6 +225,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.2" + eventify: + dependency: transitive + description: + name: eventify + sha256: b829429f08586cc2001c628e7499e3e3c2493a1d895fd73b00ecb23351aa5a66 + url: "https://pub.dev" + source: hosted + version: "1.0.1" exception_templates: dependency: transitive description: @@ -585,6 +593,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.0.1-beta.11" + just_audio_mpv: + dependency: "direct main" + description: + name: just_audio_mpv + sha256: d6e4e9fd20bfb9d2fd5e3dcd7906c90ed07f83d1d2f44f31204160821ab62fed + url: "https://pub.dev" + source: hosted + version: "0.1.7" just_audio_platform_interface: dependency: transitive description: @@ -690,6 +706,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.5" + mpv_dart: + dependency: transitive + description: + name: mpv_dart + sha256: a33bd9a68439b496b7a5f36fecd3aa3cf6cbf0176ae15b9b60b12ae96e58f5a4 + url: "https://pub.dev" + source: hosted + version: "0.0.1" package_config: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 704b9c4..418e65e 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -44,7 +44,8 @@ dependencies: just_audio_background: ^0.0.1-beta.11 audio_session: ^0.1.19 talker_flutter: ^4.1.5 - + just_audio_mpv: ^0.1.6 + dev_dependencies: flutter_test: sdk: flutter From 5d72fb66ed94559fde5e0138499df7c66a21f3a1 Mon Sep 17 00:00:00 2001 From: canxin Date: Sun, 5 May 2024 17:51:53 +0800 Subject: [PATCH 27/28] [Chore] Remove unused dependency --- pubspec.lock | 48 ------------------------------------------------ pubspec.yaml | 3 --- 2 files changed, 51 deletions(-) diff --git a/pubspec.lock b/pubspec.lock index f4aa4d4..7c98fd4 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -81,14 +81,6 @@ packages: url: "https://pub.dev" source: hosted version: "0.1.19" - blur: - dependency: "direct main" - description: - name: blur - sha256: fd23f1247faee4a7d1a3efb6b7c3cea134f3b939d72e5f8d45233deb0776259f - url: "https://pub.dev" - source: hosted - version: "3.1.0" boolean_selector: dependency: transitive description: @@ -225,14 +217,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.2" - eventify: - dependency: transitive - description: - name: eventify - sha256: b829429f08586cc2001c628e7499e3e3c2493a1d895fd73b00ecb23351aa5a66 - url: "https://pub.dev" - source: hosted - version: "1.0.1" exception_templates: dependency: transitive description: @@ -331,14 +315,6 @@ packages: description: flutter source: sdk version: "0.0.0" - flutter_fast_forms: - dependency: "direct main" - description: - name: flutter_fast_forms - sha256: "55e708fb6905bcb7cc6c4d763e47e9d9a5b7731702d862d5060ffd227e74c2fc" - url: "https://pub.dev" - source: hosted - version: "15.0.0" flutter_launcher_icons: dependency: "direct dev" description: @@ -552,14 +528,6 @@ packages: url: "https://pub.dev" source: hosted version: "0.2.2" - intl: - dependency: transitive - description: - name: intl - sha256: d6f56758b7d3014a48af9701c085700aac781a92a87a62b1333b46d8879661cf - url: "https://pub.dev" - source: hosted - version: "0.19.0" js: dependency: transitive description: @@ -593,14 +561,6 @@ packages: url: "https://pub.dev" source: hosted version: "0.0.1-beta.11" - just_audio_mpv: - dependency: "direct main" - description: - name: just_audio_mpv - sha256: d6e4e9fd20bfb9d2fd5e3dcd7906c90ed07f83d1d2f44f31204160821ab62fed - url: "https://pub.dev" - source: hosted - version: "0.1.7" just_audio_platform_interface: dependency: transitive description: @@ -706,14 +666,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.5" - mpv_dart: - dependency: transitive - description: - name: mpv_dart - sha256: a33bd9a68439b496b7a5f36fecd3aa3cf6cbf0176ae15b9b60b12ae96e58f5a4 - url: "https://pub.dev" - source: hosted - version: "0.0.1" package_config: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 418e65e..47d38b0 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -27,12 +27,10 @@ dependencies: git: url: https://github.com/bdlukaa/just_audio_windows path: just_audio_windows - blur: ^3.1.0 infinite_scroll_pagination: ^4.0.0 path_provider: ^2.1.3 flutter_popup: ^3.3.0 image_picker: ^1.1.0 - flutter_fast_forms: ^15.0.0 chinese_font_library: ^1.1.0 http: ^1.2.1 dart_eval: ^0.7.9 @@ -44,7 +42,6 @@ dependencies: just_audio_background: ^0.0.1-beta.11 audio_session: ^0.1.19 talker_flutter: ^4.1.5 - just_audio_mpv: ^0.1.6 dev_dependencies: flutter_test: From 9e30628650ef7861af03d66e05ddd3f1cef5e147 Mon Sep 17 00:00:00 2001 From: canxin Date: Sun, 5 May 2024 17:52:24 +0800 Subject: [PATCH 28/28] [Chore] remove unused dependency. --- lib/comp/play_page_comp/quality_time.dart | 7 ------- 1 file changed, 7 deletions(-) diff --git a/lib/comp/play_page_comp/quality_time.dart b/lib/comp/play_page_comp/quality_time.dart index 9906728..6913e19 100644 --- a/lib/comp/play_page_comp/quality_time.dart +++ b/lib/comp/play_page_comp/quality_time.dart @@ -4,7 +4,6 @@ import 'package:app_rhyme/util/helper.dart'; import 'package:app_rhyme/util/audio_controller.dart'; import 'package:app_rhyme/util/selection.dart'; import 'package:app_rhyme/util/time_parse.dart'; -import 'package:blur/blur.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:get/get_state_manager/src/rx_flutter/rx_obx_widget.dart'; @@ -49,12 +48,6 @@ class QualityTimeState extends State { fontSize: 10.0, fontWeight: FontWeight.normal, ), - ).frosted( - blur: 10, - frostColor: Colors.transparent, - borderRadius: BorderRadius.circular(15), - padding: const EdgeInsets.only( - left: 10, right: 10, top: 5, bottom: 5), ); }), onPressed: () {