diff --git a/feeluown/gui/components/line_song.py b/feeluown/gui/components/line_song.py index b42466dbbb..0200dcc8b8 100644 --- a/feeluown/gui/components/line_song.py +++ b/feeluown/gui/components/line_song.py @@ -4,6 +4,7 @@ from PyQt5.QtGui import QPainter, QPalette, QColor from PyQt5.QtWidgets import QLabel, QSizePolicy, QMenu, QVBoxLayout, QWidget +from feeluown.player import PlaylistPlayModelStage from feeluown.library import fmt_artists_names from feeluown.gui.components import SongMenuInitializer from feeluown.gui.helpers import elided_text @@ -35,8 +36,8 @@ def __init__(self, app: 'GuiApp', parent=None): self._app.player.metadata_changed.connect( self.on_metadata_changed, aioqueue=True ) - self._app.playlist.play_model_handling.connect( - self.on_play_model_handling, aioqueue=True + self._app.playlist.play_model_stage_changed.connect( + self.on_play_model_stage_changed, aioqueue=True ) def on_metadata_changed(self, metadata): @@ -53,8 +54,17 @@ def on_metadata_changed(self, metadata): text += f" • {','.join(artists)}" self.setText(text) - def on_play_model_handling(self): - self.setText('正在加载歌曲...') + def on_play_model_stage_changed(self, stage): + if stage == PlaylistPlayModelStage.prepare_media: + self.setText('正在获取歌曲播放链接...') + elif stage == PlaylistPlayModelStage.find_standby_by_mv: + self.setText('正在获取音乐的视频播放链接...') + elif stage == PlaylistPlayModelStage.find_standby: + self.setText('尝试寻找备用播放链接...') + elif stage == PlaylistPlayModelStage.prepare_metadata: + self.setText('尝试获取完整的歌曲元信息...') + elif stage == PlaylistPlayModelStage.load_media: + self.setText('正在加载歌曲资源...') def change_text_position(self): if not self.parent().isVisible(): # type: ignore diff --git a/feeluown/library/library.py b/feeluown/library/library.py index 9ffa03bab7..e63e59d563 100644 --- a/feeluown/library/library.py +++ b/feeluown/library/library.py @@ -290,7 +290,6 @@ def song_prepare_media(self, song: BriefSongModel, policy) -> Media: if not media: if self.ytdl is not None and isinstance(provider, SupportsSongWebUrl): song_web_url = provider.song_get_web_url(song) - logger.info(f'use ytdl to get media for {song_web_url}') media = self.ytdl.select_audio(song_web_url, policy, source=song.source) found = media is not None logger.debug(f'ytdl select audio for {song_web_url} finished, ' @@ -438,7 +437,6 @@ def video_prepare_media(self, video: BriefVideoModel, policy) -> Media: except MediaNotFound: if self.ytdl is not None and isinstance(provider, SupportsVideoWebUrl): video_web_url = provider.video_get_web_url(video) - logger.info(f'use ytdl to get media for {video_web_url}') media = self.ytdl.select_video(video_web_url, policy, source=video.source) diff --git a/feeluown/library/ytdl.py b/feeluown/library/ytdl.py index 4466fb9b28..4fb59308bb 100644 --- a/feeluown/library/ytdl.py +++ b/feeluown/library/ytdl.py @@ -29,7 +29,7 @@ def __init__(self, rules: Optional[List[Any]] = None): # TODO: valid rules self._rules = rules or [] - def match_rule(self, _, source=''): + def match_rule(self, url, source=''): """ Currently, only 'match_source' is supported. Maybe support 'match_url' in the future. @@ -39,6 +39,10 @@ def match_rule(self, _, source=''): for rule in self._rules: if rule['name'] == 'match_source' and source == rule['pattern']: matched_rule = rule + if matched_rule: + logger.info(f"ytdl rule matched for {url}") + else: + logger.info(f"no ytdl rule matched for {url}") return matched_rule def select_audio(self, url, _: Optional[str] = None, source='') -> Optional[Media]: diff --git a/feeluown/player/__init__.py b/feeluown/player/__init__.py index 78d14665e4..b3e51d3b1c 100644 --- a/feeluown/player/__init__.py +++ b/feeluown/player/__init__.py @@ -1,5 +1,6 @@ from .metadata import MetadataFields, Metadata -from .playlist import PlaybackMode, PlaylistRepeatMode, PlaylistShuffleMode +from .playlist import PlaybackMode, PlaylistRepeatMode, PlaylistShuffleMode, \ + PlaylistPlayModelStage from .base_player import State from .mpvplayer import MpvPlayer as Player from .playlist import PlaylistMode, Playlist @@ -15,6 +16,7 @@ 'PlaybackMode', 'PlaylistRepeatMode', 'PlaylistShuffleMode', + 'PlaylistPlayModelStage', 'State', 'FM', diff --git a/feeluown/player/playlist.py b/feeluown/player/playlist.py index 9da5edc3fc..cfb7f341fb 100644 --- a/feeluown/player/playlist.py +++ b/feeluown/player/playlist.py @@ -72,6 +72,14 @@ class PlaylistMode(IntEnum): fm = 1 #: FM mode +class PlaylistPlayModelStage(IntEnum): + prepare_media = 10 + find_standby_by_mv = 20 + find_standby = 30 + prepare_metadata = 40 + load_media = 50 + + class Playlist: def __init__(self, app: 'App', songs=None, playback_mode=PlaybackMode.loop, audio_select_policy='hq<>'): @@ -134,6 +142,9 @@ def __init__(self, app: 'App', songs=None, playback_mode=PlaybackMode.loop, # .. versionadded:: 3.9.0 # The *play_model_handling* signal. self.play_model_handling = Signal() + # .. versionadded:: 4.1.7 + # The *play_model_handling* signal. + self.play_model_stage_changed = Signal() self._app.player.media_finished.connect(self._on_media_finished) self.song_changed.connect(self._on_song_changed) @@ -493,6 +504,7 @@ async def a_set_current_song(self, song): target_song = song # The song to be set. media = None # The corresponding media to be set. try: + self.play_model_stage_changed.emit(PlaylistPlayModelStage.prepare_media) media = await self._prepare_media(song) except MediaNotFound as e: if e.reason is MediaNotFound.Reason.check_children: @@ -513,12 +525,14 @@ async def a_set_current_song(self, song): # The song has no media, try to find and use standby unless it is in fm mode. if media is None: if self._app.config.ENABLE_MV_AS_STANDBY: - self._app.show_msg('尝试获取音乐视频的播放资源...') + self.play_model_stage_changed.emit( + PlaylistPlayModelStage.find_standby_by_mv) media = await self._prepare_mv_media(song) if media: self._app.show_msg('使用音乐视频作为其播放资源 ✅') else: + self._app.show_msg('未找到可用的音乐视频资源 🙁') # if mode is fm mode, do not find standby song, just skip the song. if self.mode is PlaylistMode.fm: self.mark_as_bad(song) @@ -527,9 +541,14 @@ async def a_set_current_song(self, song): logger.info(f"no media found for {song}, mark it as bad") self.mark_as_bad(song) + self.play_model_stage_changed.emit(PlaylistPlayModelStage.find_standby) target_song, media = await self.find_and_use_standby(song) - metadata = await self._metadata_mgr.prepare_for_song(target_song) + metadata = None + if media is not None: + self.play_model_stage_changed.emit(PlaylistPlayModelStage.prepare_metadata) + metadata = await self._metadata_mgr.prepare_for_song(target_song) + self.play_model_stage_changed.emit(PlaylistPlayModelStage.load_media) self.pure_set_current_song(target_song, media, metadata) async def a_set_current_song_children(self, song): @@ -582,6 +601,7 @@ def pure_set_current_song(self, song, media, metadata=None): if song is not None: if media is None: + self._app.show_msg("没找到可用的播放链接,播放下一首...") run_afn(self.a_next) else: # Note that the value of model v1 {}_display may be None. diff --git a/tests/player/test_playlist.py b/tests/player/test_playlist.py index c80e06348a..58d069b3ae 100644 --- a/tests/player/test_playlist.py +++ b/tests/player/test_playlist.py @@ -114,13 +114,11 @@ async def test_set_current_song_with_bad_song_1( pl_list_standby_return_empty): mock_pure_set_current_song = mocker.patch.object(Playlist, 'pure_set_current_song') mock_mark_as_bad = mocker.patch.object(Playlist, 'mark_as_bad') - sentinal = object() - mocker.patch.object(MetadataAssembler, 'prepare_for_song', return_value=sentinal) await pl.a_set_current_song(song2) # A song that has no valid media should be marked as bad assert mock_mark_as_bad.called # Since there is no standby song, the media should be None - mock_pure_set_current_song.assert_called_once_with(song2, None, sentinal) + mock_pure_set_current_song.assert_called_once_with(song2, None, None) @pytest.mark.asyncio