async def test_prepare_media(app_mock, song): player = Player(app_mock) mock_func = mock.MagicMock() player.prepare_media(song, mock_func) # this may fail, since we should probably wait a little bit longer await asyncio.sleep(0.1) assert mock_func.called is True
def __init__(self, args, config): self.mode = config.MODE # DEPRECATED: use app.config.MODE instead self.config = config self.args = args self.initialized = Signal() self.about_to_shutdown = Signal() self.plugin_mgr = PluginsManager(self) self.request = Request() # TODO: rename request to http self.version_mgr = VersionManager(self) self.task_mgr = TaskManager(self) # Library. self.library = Library(config.PROVIDERS_STANDBY) # TODO: initialization should be moved into initialize Resolver.library = self.library # Player. self.player = Player( audio_device=bytes(config.MPV_AUDIO_DEVICE, 'utf-8')) self.playlist = Playlist( self, audio_select_policy=config.AUDIO_SELECT_POLICY) self.live_lyric = LiveLyric(self) self.fm = FM(self) # TODO: initialization should be moved into initialize self.player.set_playlist(self.playlist) self.about_to_shutdown.connect(lambda _: self.dump_state(), weak=False)
def test_play_all(app_mock): player = Player() playlist = Playlist(app_mock) player.set_playlist(playlist) playlist.mode = PlaylistMode.fm playlist.set_models([], next_=True) assert playlist.mode == PlaylistMode.normal
def test_serialize_app(mocker): app = mocker.Mock(spec=App) app.live_lyric = mocker.Mock() app.live_lyric.current_sentence = '' player = Player(Playlist(app)) app.player = player app.playlist = player.playlist for format in ('plain', 'json'): serialize(format, app, brief=False) serialize(format, app, fetch=True) player.shutdown()
def test_change_song(app_mock, mocker, song, song1): mocker.patch.object(Player, 'play') pl = Playlist(app_mock) pl.add(song) pl._current_song = song player = Player(pl) with mock.patch.object(Playlist, 'current_song', new_callable=mock.PropertyMock) as mock_s: mock_s.return_value = song # return current song player.play_song(song1) pl.next() assert pl.current_song == song
async def test_prepare_media_in_non_mainthread(app_mock, song): player = Player(app_mock) loop = asyncio.get_event_loop() mock_func = mock.MagicMock() try: await loop.run_in_executor(None, player.prepare_media, song, mock_func) except RuntimeError: pytest.fail('Prepare media in non mainthread should work')
def mpvplayer(): player = MpvPlayer(Playlist(app_mock)) player.volume = 0 yield player player.stop() # HELP: player.shutdown() causes error on Windows # Windows fatal exception: code 0xe24c4a02 # Ref: https://github.com/feeluown/FeelUOwn/runs/4996179558 player.shutdown()
def __init__(self, parent=None): super().__init__(parent) ControllerApi.player = Player(self) ui = UiMainWidget() ViewOp.ui = ui ViewOp.controller = ControllerApi ui.setup_ui(self) engine = create_engine( 'sqlite:////%s' % DATABASE_SQLITE, connect_args={'check_same_thread': False}, echo=False) Base.metadata.create_all(engine) Session = sessionmaker() Session.configure(bind=engine) session = Session() LOG.info('db connected: %s' % DATABASE_SQLITE) ControllerApi.session = session ControllerApi.view = ViewOp ControllerApi.desktop_mini = DesktopMiniLayer() ControllerApi.lyric_widget = LyricWidget() ControllerApi.notify_widget = NotifyWidget() ControllerApi.network_manager = NetworkManager(self) ControllerApi.current_playlist_widget = CurrentMusicTable() self._search_shortcut = QShortcut(QKeySequence('Ctrl+F'), self) self._minimize_shortcut = QShortcut(QKeySequence('Ctrl+M'), self) self._pre_focus = QShortcut(QKeySequence('Ctrl+['), self) self._next_focus = QShortcut(QKeySequence('Ctrl+]'), self) self.setAttribute(Qt.WA_MacShowFocusRect, False) self.setWindowIcon(QIcon(WINDOW_ICON)) self.setWindowTitle('FeelUOwn') self.mode_manager = ModesManger() self._init_signal_binding() self.load_config() app_event_loop = asyncio.get_event_loop() app_event_loop.call_later(1, self._init_plugins) app_event_loop.call_later(6, TipsManager.show_start_tip) asyncio.Task(VersionManager.check_release())
class TestPlayerAndPlaylist(TestCase): def setUp(self): self.playlist = Playlist(app_mock) self.player = MpvPlayer() self.player.set_playlist(self.player) def tearDown(self): self.player.stop() self.player.shutdown() @skipIf(cannot_play_audio, '') @mock.patch.object(MpvPlayer, 'play') def test_remove_current_song_2(self, mock_play): """当播放列表只有一首歌时,移除它""" s1 = FakeValidSongModel() self.playlist.current_song = s1 time.sleep(MPV_SLEEP_SECOND) # 让 Mpv 真正的开始播放 self.playlist.remove(s1) self.assertEqual(len(self.playlist), 0) self.assertEqual(self.player.state, State.stopped)
def test_play_all(app_mock): player = Player(app_mock) playlist = player.playlist playlist.mode = PlaylistMode.fm player.play_songs([]) assert playlist.mode == PlaylistMode.normal
def setUp(self): self.player = MpvPlayer(Playlist(app_mock)) self.player.volume = 0
class TestPlayer(TestCase): def setUp(self): self.player = MpvPlayer(Playlist(app_mock)) self.player.volume = 0 def tearDown(self): self.player.stop() self.player.shutdown() @skipIf(cannot_play_audio, '') def test_play(self): self.player.play(MP3_URL) self.player.stop() @skipIf(cannot_play_audio, '') def test_duration(self): # This may failed? self.player.play(MP3_URL) time.sleep(MPV_SLEEP_SECOND) self.assertIsNotNone(self.player.duration) @skipIf(cannot_play_audio, '') def test_play_pause_toggle_resume_stop(self): self.player.play(MP3_URL) time.sleep(MPV_SLEEP_SECOND) self.player.toggle() self.assertEqual(self.player.state, State.paused) self.player.resume() self.assertEqual(self.player.state, State.playing) self.player.pause() self.assertEqual(self.player.state, State.paused) self.player.stop() self.assertEqual(self.player.state, State.stopped) @skipIf(cannot_play_audio, '') def test_set_volume(self): cb = mock.Mock() self.player.volume_changed.connect(cb) self.player.volume = 30 self.assertEqual(self.player.volume, 30) cb.assert_called_once_with(30) @mock.patch('feeluown.player.mpvplayer._mpv_set_option_string') def test_play_media_with_http_headers(self, mock_set_option_string): media = Media('http://xxx', http_headers={'referer': 'http://xxx'}) self.player.play(media) assert mock_set_option_string.called self.player.stop()
def setUp(self): self.playlist = Playlist(app_mock) self.player = MpvPlayer() self.player.set_playlist(self.player)
class App: """App 基类""" _instance = None # .. deprecated:: 3.8 # Use :class:`AppMode` instead. DaemonMode = AppMode.server.value GuiMode = AppMode.gui.value CliMode = AppMode.cli.value def __init__(self, args, config): self.mode = config.MODE # DEPRECATED: use app.config.MODE instead self.config = config self.args = args self.initialized = Signal() self.about_to_shutdown = Signal() self.plugin_mgr = PluginsManager(self) self.request = Request() # TODO: rename request to http self.version_mgr = VersionManager(self) self.task_mgr = TaskManager(self) # Library. self.library = Library(config.PROVIDERS_STANDBY) # TODO: initialization should be moved into initialize Resolver.library = self.library # Player. self.player = Player( audio_device=bytes(config.MPV_AUDIO_DEVICE, 'utf-8')) self.playlist = Playlist( self, audio_select_policy=config.AUDIO_SELECT_POLICY) self.live_lyric = LiveLyric(self) self.fm = FM(self) # TODO: initialization should be moved into initialize self.player.set_playlist(self.playlist) self.about_to_shutdown.connect(lambda _: self.dump_state(), weak=False) def initialize(self): self.player.position_changed.connect( self.live_lyric.on_position_changed) self.playlist.song_changed.connect(self.live_lyric.on_song_changed, aioqueue=True) self.plugin_mgr.scan() def run(self): pass @property def instance(self) -> Optional['App']: """App running instance. .. versionadded:: 3.8 """ return App._instance @property def has_server(self) -> bool: """ .. versionadded:: 3.8 """ return AppMode.server in AppMode(self.config.MODE) @property def has_gui(self) -> bool: """ .. versionadded:: 3.8 """ return AppMode.gui in AppMode(self.config.MODE) def show_msg(self, msg, *args, **kwargs): """在程序中显示消息,一般是用来显示程序当前状态""" # pylint: disable=no-self-use, unused-argument logger.info(msg) def get_listen_addr(self): return '0.0.0.0' if self.config.ALLOW_LAN_CONNECT else '127.0.0.1' def load_state(self): playlist = self.playlist player = self.player try: with open(STATE_FILE, 'r', encoding='utf-8') as f: state = json.load(f) except FileNotFoundError: pass except json.decoder.JSONDecodeError: logger.exception('invalid state file') else: player.volume = state['volume'] playlist.playback_mode = PlaybackMode(state['playback_mode']) songs = [] for song in state['playlist']: try: song = resolve(song) except ResolverNotFound: pass else: songs.append(song) playlist.set_models(songs) song = state['song'] def before_media_change(old_media, media): # When the song has no media or preparing media is failed, # the current_song is not the song we set. # # When user play a new media directly through player.play interface, # the old media is not None. if old_media is not None or playlist.current_song != song: player.media_about_to_changed.disconnect( before_media_change) player.set_play_range() if song is not None: try: song = resolve(state['song']) except ResolverNotFound: pass else: player.media_about_to_changed.connect(before_media_change, weak=False) player.pause() player.set_play_range(start=state['position']) playlist.set_current_song(song) def dump_state(self): logger.info("Dump app state") playlist = self.playlist player = self.player song = self.player.current_song if song is not None: song = reverse(song, as_line=True) # TODO: dump player.media state = { 'playback_mode': playlist.playback_mode.value, 'volume': player.volume, 'state': player.state.value, 'song': song, 'position': player.position, 'playlist': [reverse(song, as_line=True) for song in playlist.list()], } with open(STATE_FILE, 'w', encoding='utf-8') as f: json.dump(state, f) @contextmanager def create_action(self, s): # pylint: disable=no-self-use """根据操作描述生成 Action (alpha) 设计缘由:用户需要知道目前程序正在进行什么操作,进度怎么样, 结果是失败或者成功。这里将操作封装成 Action。 """ show_msg = self.show_msg class ActionError(Exception): pass class Action: def set_progress(self, value): value = int(value * 100) show_msg(s + f'...{value}%', timeout=-1) def failed(self, msg=''): raise ActionError(msg) show_msg(s + '...', timeout=-1) # doing try: yield Action() except ActionError as e: show_msg(s + f'...failed\t{str(e)}') except Exception as e: show_msg(s + f'...error\t{str(e)}') # error raise else: show_msg(s + '...done') # done def about_to_exit(self): logger.info('Do graceful shutdown') try: self.about_to_shutdown.emit(self) self.player.stop() self.exit_player() except: # noqa, pylint: disable=bare-except logger.exception("about-to-exit failed") logger.info('Ready for shutdown') def exit_player(self): self.player.shutdown() # this cause 'abort trap' on macOS def exit(self): self.about_to_exit()