Exemple #1
0
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
Exemple #2
0
    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
Exemple #4
0
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()
Exemple #5
0
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
Exemple #6
0
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()
Exemple #8
0
    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)
Exemple #10
0
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)
Exemple #14
0
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()