Skip to content

Commit

Permalink
player: try to find standby when there is unknown expcetion (#743)
Browse files Browse the repository at this point in the history
  • Loading branch information
cosven authored Jan 6, 2024
1 parent 893d790 commit 73349c0
Show file tree
Hide file tree
Showing 3 changed files with 68 additions and 47 deletions.
3 changes: 2 additions & 1 deletion feeluown/nowplaying/macos.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ def set_artwork(info_center: MPNowPlayingInfoCenter, b):
nowplaying_info = NSMutableDictionary.dictionary()
else:
nowplaying_info = current_nowplaying_info.mutableCopy()
nowplaying_info[MPMediaItemPropertyArtwork] = artwork_from_bytes(b)
if b:
nowplaying_info[MPMediaItemPropertyArtwork] = artwork_from_bytes(b)
info_center.setNowPlayingInfo_(nowplaying_info)
return True
96 changes: 55 additions & 41 deletions feeluown/player/playlist.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@
logger = logging.getLogger(__name__)


def _get_song_str(song):
return f'{song.source}:{song.title_display} - {song.artists_name_display}'


class PlaybackMode(IntEnum):
"""
Playlist playback mode.
Expand Down Expand Up @@ -464,7 +468,7 @@ async def a_set_current_song(self, song):
If the song is bad, then this will try to use a standby in Playlist.normal mode.
"""
song_str = f'{song.source}:{song.title_display} - {song.artists_name_display}'
song_str = _get_song_str(song)

target_song = song # The song to be set.
media = None # The corresponding media to be set.
Expand All @@ -473,65 +477,75 @@ async def a_set_current_song(self, song):
media = await self._prepare_media(song)
except MediaNotFound as e:
if e.reason is MediaNotFound.Reason.check_children:
# TODO: maybe we can just add children to playlist?
self._app.show_msg(f'{song_str} 的播放资源在孩子节点上,将孩子节点添加到播放列表')
self.mark_as_bad(song)
logger.info(f'{song_str} has children, replace the current playlist')
song = await run_fn(self._app.library.song_upgrade, song)
if song.children:
self.batch_add(song.children)
await self.a_set_current_song(song.children[0])
else:
run_afn(self.a_next)
await self.a_set_current_song_children(song)
return

logger.info(f'{song_str} has no valid media, mark it as bad')
self.mark_as_bad(song)

# if mode is fm mode, do not find standby song,
# just skip the song
if self.mode is PlaylistMode.fm:
run_afn(self.a_next)
return

self._app.show_msg(f'{song_str} is invalid, try to find standby')
logger.info(f'try to find standby for {song_str}')
standby_candidates = await self._app.library.a_list_song_standby_v2(
song,
self.audio_select_policy
)
if standby_candidates:
standby, media = standby_candidates[0]
msg = f'Song standby was found in {standby.source} ✅'
logger.info(msg)
self._app.show_msg(msg)
# Insert the standby song after the song
if song in self._songs and standby not in self._songs:
index = self._songs.index(song)
self._songs.insert(index + 1, standby)
self.songs_added.emit(index + 1, 1)
target_song = standby
else:
msg = 'Song standby not found'
logger.info(msg)
self._app.show_msg(msg)
except ProviderIOError as e:
# FIXME: This may cause infinite loop when the prepare media always fails
logger.error(f'prepare media failed: {e}, try next song')
run_afn(self.a_next)
return
except Exception as e: # noqa
# When the exception is unknown, we mark the song as bad.
self._app.show_msg(f'prepare media failed due to unknown error: {e}')
logger.exception('prepare media failed due to unknown error, '
'so we mark the song as a bad one')
self.mark_as_bad(song)
run_afn(self.a_next)
return
else:
assert media, "media must not be empty"

# The song has no media, try to find and use standby unless it is in fm mode.
if media is None:
# if mode is fm mode, do not find standby song, just skip the song.
if self.mode is PlaylistMode.fm:
run_afn(self.a_next)
return
target_song, media = await self.find_and_use_standby(song)

metadata = await self._prepare_metadata_for_song(target_song)
self.pure_set_current_song(target_song, media, metadata)

async def a_set_current_song_children(self, song):
song_str = _get_song_str(song)
# TODO: maybe we can just add children to playlist?
self._app.show_msg(f'{song_str} 的播放资源在孩子节点上,将孩子节点添加到播放列表')
self.mark_as_bad(song)
logger.info(f'{song_str} has children, replace the current playlist')
song = await run_fn(self._app.library.song_upgrade, song)
if song.children:
self.batch_add(song.children)
await self.a_set_current_song(song.children[0])
else:
run_afn(self.a_next)
return

async def find_and_use_standby(self, song):
song_str = _get_song_str(song)
self._app.show_msg(f'{song_str} is invalid, try to find standby')
logger.info(f'try to find standby for {song_str}')
standby_candidates = await self._app.library.a_list_song_standby_v2(
song,
self.audio_select_policy
)
if standby_candidates:
standby, media = standby_candidates[0]
msg = f'Song standby was found in {standby.source} ✅'
logger.info(msg)
self._app.show_msg(msg)
# Insert the standby song after the song
if song in self._songs and standby not in self._songs:
index = self._songs.index(song)
self._songs.insert(index + 1, standby)
self.songs_added.emit(index + 1, 1)
return standby, media

msg = 'Song standby not found'
logger.info(msg)
self._app.show_msg(msg)
return song, None

def pure_set_current_song(self, song, media, metadata=None):
if song is None:
self._current_song = None
Expand Down
16 changes: 11 additions & 5 deletions tests/player/test_playlist.py
Original file line number Diff line number Diff line change
Expand Up @@ -275,19 +275,25 @@ async def test_playlist_remove_current_song(app_mock):
async def test_play_next_bad_song(app_mock, song, song1, mocker):
"""
Prepare media for song raises unknown error, the song should
be marked as bad.
be marked as bad. Besides, it should try to find standby.
"""
mock_pure_set_current_song = mocker.patch.object(Playlist, 'pure_set_current_song')
mocker.patch.object(Playlist, '_prepare_metadata_for_song', return_value=object())
mock_standby = mocker.patch.object(Playlist,
'find_and_use_standby',
return_value=(song1, None))
mocker.patch.object(Playlist, '_prepare_media', side_effect=Exception())
mock_mark_as_bad = mocker.patch.object(Playlist, 'mark_as_bad')

pl = Playlist(app_mock)
mocker.patch.object(pl, '_prepare_media', side_effect=Exception())
mock_mark_as_bad = mocker.patch.object(pl, 'mark_as_bad')
mock_next = mocker.patch.object(pl, 'next')
pl.add(song)
pl.add(song1)
pl._current_song = song
await pl.a_set_current_song(pl.next_song)
assert mock_mark_as_bad.called
await asyncio.sleep(0.1)
assert mock_next.called
assert mock_pure_set_current_song.called
assert mock_standby.called


def test_play_all(app_mock):
Expand Down

0 comments on commit 73349c0

Please sign in to comment.