Esempio n. 1
0
    def __init__(self, audio_device=b'auto', winid=None, *args, **kwargs):
        super(MpvPlayer, self).__init__(*args, **kwargs)
        # https://github.com/cosven/FeelUOwn/issues/246
        locale.setlocale(locale.LC_NUMERIC, 'C')
        mpvkwargs = {}
        if winid is not None:
            mpvkwargs['wid'] = winid
        mpvkwargs['vo'] = 'opengl-cb'
        # set log_handler if you want to debug
        # mpvkwargs['log_handler'] = self.__log_handler
        # mpvkwargs['msg_level'] = 'all=v'
        logger.info('libmpv version %s', _mpv_client_api_version())
        self._mpv = MPV(ytdl=False,
                        input_default_bindings=True,
                        input_vo_keyboard=True,
                        **mpvkwargs)
        _mpv_set_property_string(self._mpv.handle, b'audio-device',
                                 audio_device)
        # old version libmpv(for example: (1, 20)) should set option by using
        # _mpv_set_option_string, while newer version can use _mpv_set_property_string
        _mpv_set_option_string(self._mpv.handle, b'user-agent',
                               b'Mozilla/5.0 (Windows NT 10.0; Win64; x64)')

        #: if video_format changes to None, there is no video available
        self.video_format_changed = Signal()
Esempio n. 2
0
async def test_signals_slots_mgr(app_mock, signal):
    signals_slots_mgr = SignalsSlotsManager()
    Signal.setup_aio_support()
    func = mock.MagicMock()
    app_mock.test_signal = signal

    # test if add method works
    signals_slots_mgr.add('app.test_signal', func)
    signals_slots_mgr.initialize(app_mock)
    signal.emit()
    await asyncio.sleep(0.1)  # schedule signal callbacks with aioqueue enabled
    assert func.called is True

    # test if add method works after initialized
    func_lator = mock.MagicMock()
    signals_slots_mgr.add('app.test_signal', func_lator)
    signal.emit()
    await asyncio.sleep(0.1)
    assert func_lator.called is True

    # test if remove method works
    signals_slots_mgr.remove('app.test_signal', func_lator)
    signal.emit()
    await asyncio.sleep(0.1)
    assert func_lator.call_count == 1
    assert func.call_count == 3
    Signal.teardown_aio_support()
Esempio n. 3
0
    def __init__(self, config):
        self.mode = config.MODE  # DEPRECATED: use app.config.MODE instead
        self.config = config
        self.initialized = Signal()
        self.about_to_shutdown = Signal()

        self.initialized.connect(lambda _: self.load_state(), weak=False)
        self.about_to_shutdown.connect(lambda _: self.dump_state(), weak=False)
Esempio n. 4
0
def create_app(config):
    mode = config.MODE

    if mode & App.GuiMode:

        from PyQt5.QtCore import Qt
        from PyQt5.QtGui import QIcon, QPixmap
        from PyQt5.QtWidgets import QApplication, QWidget

        from feeluown.compat import QEventLoop

        q_app = QApplication(sys.argv)

        q_app.setQuitOnLastWindowClosed(not config.ENABLE_TRAY)
        q_app.setApplicationName('FeelUOwn')

        app_event_loop = QEventLoop(q_app)
        asyncio.set_event_loop(app_event_loop)

        class GuiApp(QWidget):
            mode = App.GuiMode

            def __init__(self):
                super().__init__()
                self.setObjectName('app')
                QApplication.setWindowIcon(QIcon(QPixmap(APP_ICON)))

            def closeEvent(self, _):
                if not self.config.ENABLE_TRAY:
                    self.exit()

            def exit(self):
                self.ui.mpv_widget.close()
                event_loop = asyncio.get_event_loop()
                event_loop.stop()

            def mouseReleaseEvent(self, e):
                if not self.rect().contains(e.pos()):
                    return
                if e.button() == Qt.BackButton:
                    self.browser.back()
                elif e.button() == Qt.ForwardButton:
                    self.browser.forward()

        class FApp(App, GuiApp):
            def __init__(self, config):
                App.__init__(self, config)
                GuiApp.__init__(self)

    else:
        FApp = App

    Signal.setup_aio_support()
    Resolver.setup_aio_support()
    app = FApp(config)
    attach_attrs(app)
    Resolver.library = app.library
    return app
Esempio n. 5
0
def setup_app(args, config):
    if config.DEBUG:
        verbose = 3
    else:
        verbose = args.verbose or 0
    logger_config(verbose=verbose, to_file=config.LOG_TO_FILE)
    Signal.setup_aio_support()
    app = create_app(config)
    return app
Esempio n. 6
0
    def __init__(self):
        self.sentence_changed = Signal(str)

        self._lyric = None
        self._pos_s_map = {}  # position sentence map
        self._pos_list = []  # position list
        self._pos = None

        self._current_sentence = ''
Esempio n. 7
0
    def __init__(self, providers_standby=None):
        """

        :type app: feeluown.app.App
        """
        self._providers_standby = providers_standby
        self._providers = set()

        self.provider_added = Signal()  # emit(AbstractProvider)
        self.provider_removed = Signal()  # emit(AbstractProvider)
Esempio n. 8
0
class LiveLyric(object):
    """live lyric

    LiveLyric listens to song changed signal and position changed signal
    and emit sentence changed signal. It also has a ``current_sentence`` property.

    Usage::

        live_lyric = LiveLyric()
        player.song_changed.connect(live_lyric.on_song_changed)
        player.position_change.connect(live_lyric.on_position_changed)
    """

    def __init__(self):
        self.sentence_changed = Signal(str)

        self._lyric = None
        self._pos_s_map = {}  # position sentence map
        self._pos_list = []  # position list
        self._pos = None

        self._current_sentence = ''

    @property
    def current_sentence(self):
        """get current lyric sentence"""
        return self._current_sentence

    @current_sentence.setter
    def current_sentence(self, value):
        self._current_sentence = value
        self.sentence_changed.emit(value)

    # TODO: performance optimization?
    def on_position_changed(self, position):
        """bind position changed signal with this"""
        if position is None or not self._lyric:
            return

        pos = find_previous(position * 1000 + 300, self._pos_list)
        if pos is not None and pos != self._pos:
            self.current_sentence = self._pos_s_map[pos]
            self._pos = pos

    def on_song_changed(self, song):
        """bind song changed signal with this"""
        if song is None or song.lyric is None:
            self._lyric = None
            self._pos_s_map = {}
        else:
            self._lyric = song.lyric.content
            self._pos_s_map = parse(self._lyric)
        self._pos_list = sorted(list(self._pos_s_map.keys()))
        self._pos = None
        self.current_sentence = ''
Esempio n. 9
0
    def __init__(self, playlist=None, **kwargs):
        self._position = 0  # seconds
        self._volume = 100  # (0, 100)
        self._playlist = Playlist() if playlist is None else playlist
        self._state = State.stopped
        self._duration = None

        self._current_media = None

        #: player position changed signal
        self.position_changed = Signal()

        #: player state changed signal
        self.state_changed = Signal()

        #: current song finished signal
        self.song_finished = Signal()

        #: duration changed signal
        self.duration_changed = Signal()

        #: media changed signal
        self.media_changed = Signal()

        #: volume changed signal: (int)
        self.volume_changed = Signal()
Esempio n. 10
0
def main():
    # 让程序能正确的找到图标等资源
    os.chdir(os.path.join(os.path.dirname(__file__), '..'))
    sys.excepthook = excepthook

    parser = setup_argparse()
    args = parser.parse_args()

    if args.version:
        print('feeluown {}, fuocore {}'.format(feeluown_version,
                                               fuocore_version))
        return

    check_ports()
    ensure_dirs()
    config = create_config()
    load_rcfile(config)
    map_args_to_config(args, config)
    logger_config(config.DEBUG, to_file=args.log_to_file)

    if config.MODE & App.GuiMode:
        try:
            import PyQt5  # noqa
        except ImportError:
            logger.warning('PyQt5 is not installed,can only use CLI mode.')
            config.MODE = App.CliMode

    if config.MODE & App.GuiMode:
        from PyQt5.QtWidgets import QApplication
        from quamash import QEventLoop

        q_app = QApplication(sys.argv)
        q_app.setQuitOnLastWindowClosed(True)
        q_app.setApplicationName('FeelUOwn')

        app_event_loop = QEventLoop(q_app)
        asyncio.set_event_loop(app_event_loop)

    event_loop = asyncio.get_event_loop()
    Signal.setup_aio_support(loop=event_loop)
    app = create_app(config)
    bind_signals(app)
    if sys.platform.lower() == 'darwin':
        enable_mac_hotkey(force=config.FORCE_MAC_HOTKEY)
    try:
        event_loop.run_forever()
    except KeyboardInterrupt:
        # NOTE: gracefully shutdown?
        pass
    finally:
        event_loop.stop()
        app.shutdown()
        event_loop.close()
Esempio n. 11
0
    def __init__(self, songs=None, playback_mode=PlaybackMode.loop):
        """
        :param songs: list of :class:`fuocore.models.SongModel`
        :param playback_mode: :class:`fuocore.player.PlaybackMode`
        """
        self._current_song = None
        self._songs = songs or []
        self._playback_mode = playback_mode

        # signals
        self.playback_mode_changed = Signal()
        self.song_changed = Signal()
Esempio n. 12
0
    def __init__(self, app):
        """

        :type app: feeluown.app.App
        """
        self._tasks = []
        self._task_queue = asyncio.Queue()

        #: emit List[DownloadTask]
        self.tasks_changed = Signal()
        self.downloader: Downloader = AioRequestsDownloader()

        self._path = SONG_DIR
Esempio n. 13
0
    def test_receiver(self, mock_connect):
        s = Signal()

        @receiver(s)
        def f():
            pass
        self.assertTrue(mock_connect.called)
Esempio n. 14
0
    def __init__(self, songs=None, playback_mode=PlaybackMode.loop):
        """
        :param songs: list of :class:`fuocore.models.SongModel`
        :param playback_mode: :class:`fuocore.player.PlaybackMode`
        """
        self._current_song = None
        self._bad_songs = []  # songs whose url is invalid
        self._songs = songs or []

        self._playback_mode = playback_mode

        #: playback mode changed signal
        self.playback_mode_changed = Signal()

        #: current song changed signal
        self.song_changed = Signal()
Esempio n. 15
0
 def __init__(self, name, text, symbol, desc):
     # 如果需要,可以支持右键弹出菜单
     self._name = name
     self.text = text
     self.symbol = symbol
     self.desc = desc
     self.clicked = Signal()
Esempio n. 16
0
def create_app(config):
    mode = config.MODE

    if mode & App.GuiMode:

        from quamash import QEventLoop
        from PyQt5.QtCore import QSize
        from PyQt5.QtGui import QIcon, QPixmap
        from PyQt5.QtWidgets import QApplication, QWidget

        q_app = QApplication(sys.argv)
        q_app.setQuitOnLastWindowClosed(True)
        q_app.setApplicationName('FeelUOwn')

        app_event_loop = QEventLoop(q_app)
        asyncio.set_event_loop(app_event_loop)

        class GuiApp(QWidget):
            mode = App.GuiMode

            def __init__(self):
                super().__init__()
                self.setObjectName('app')
                QApplication.setWindowIcon(QIcon(QPixmap(APP_ICON)))

            def closeEvent(self, e):
                self.ui.mpv_widget.close()
                event_loop = asyncio.get_event_loop()
                event_loop.stop()

            def sizeHint(self):  # pylint: disable=no-self-use
                return QSize(1000, 618)

        class FApp(App, GuiApp):
            def __init__(self, config):
                App.__init__(self, config)
                GuiApp.__init__(self)

    else:
        FApp = App

    Signal.setup_aio_support()
    Resolver.setup_aio_support()
    app = FApp(config)
    attach_attrs(app)
    Resolver.library = app.library
    return app
Esempio n. 17
0
    def __init__(self, audio_device=b'auto', winid=None, *args, **kwargs):
        super(MpvPlayer, self).__init__(*args, **kwargs)
        # https://github.com/cosven/FeelUOwn/issues/246
        locale.setlocale(locale.LC_NUMERIC, 'C')
        mpvkwargs = {}
        if winid is not None:
            mpvkwargs['wid'] = winid
        mpvkwargs['vo'] = 'opengl-cb'
        self._mpv = MPV(ytdl=True,
                        input_default_bindings=True,
                        input_vo_keyboard=True,
                        **mpvkwargs)
        _mpv_set_property_string(self._mpv.handle, b'audio-device',
                                 audio_device)

        # TODO: 之后可以考虑将这个属性加入到 AbstractPlayer 中
        self.video_format_changed = Signal()
Esempio n. 18
0
    def __init__(self, parent=None):
        super().__init__(parent)
        QTableView.__init__(self, parent)

        # override ItemViewNoScrollMixin variables
        self._least_row_count = 6
        self._row_height = 40

        # slot functions
        self.remove_song_func = None

        self.delegate = SongsTableDelegate(self)
        self.setItemDelegate(self.delegate)
        self.activated.connect(self._on_activated)
        self.about_to_show_menu = Signal()

        self._setup_ui()
Esempio n. 19
0
async def test_signals_slots_mgr_add_slot_symbol(app_mock, signal, mocker):
    """
    test add slot symbol
    """
    signals_slots_mgr = SignalsSlotsManager()
    signals_slots_mgr.initialize(app_mock)
    Signal.setup_aio_support()
    app_mock.test_signal = signal

    func = mock.MagicMock()
    func.__name__ = 'fake_func'
    mock_F = mocker.patch('feeluown.fuoexec.fuoexec_F', return_value=func)
    signals_slots_mgr.add('app.test_signal', 'fake_func')

    signal.emit()
    await asyncio.sleep(0.1)
    mock_F.assert_called_once_with('fake_func')
    assert func.called is True
    Signal.teardown_aio_support()
Esempio n. 20
0
    def __init__(self, app, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self._app = app

        #: mainthread asyncio loop ref
        # We know that feeluown is a asyncio-app, and we can assume
        # that the playlist is inited in main thread.
        self._loop = asyncio.get_event_loop()

        #: find-song-standby task
        self._task = None

        #: init playlist mode normal
        self._mode = PlaylistMode.normal

        #: playlist eof signal
        # playlist have no enough songs
        self.eof_reached = Signal()

        #: playlist mode changed signal
        self.mode_changed = Signal()
Esempio n. 21
0
    def __init__(self, songs=None, playback_mode=PlaybackMode.loop):
        """
        :param songs: list of :class:`fuocore.models.SongModel`
        :param playback_mode: :class:`fuocore.player.PlaybackMode`
        """
        #: store value for ``current_song`` property
        self._current_song = None

        #: songs whose url is invalid
        self._bad_songs = []

        #: store value for ``songs`` property
        self._songs = songs or []

        #: store value for ``playback_mode`` property
        self._playback_mode = playback_mode

        #: playback mode changed signal
        self.playback_mode_changed = Signal()
        self.song_changed = Signal()
        """current song changed signal
Esempio n. 22
0
    def __init__(self,
                 songs=None,
                 playback_mode=PlaybackMode.loop,
                 audio_select_policy='hq<>'):
        """
        :param songs: list of :class:`fuocore.models.SongModel`
        :param playback_mode: :class:`fuocore.player.PlaybackMode`
        """
        #: store value for ``current_song`` property
        self._current_song = None

        #: songs whose url is invalid
        self._bad_songs = DedupList()

        #: store value for ``songs`` property
        self._songs = DedupList(songs or [])

        self.audio_select_policy = audio_select_policy

        #: store value for ``playback_mode`` property
        self._playback_mode = playback_mode

        #: playback mode changed signal
        self.playback_mode_changed = Signal()
        self.song_changed = Signal()
        """current song changed signal

        The player will play the song after it receive the signal,
        when song is None, the player will stop playback.
        """
        self.song_changed_v2 = Signal()
        """current song chagned signal, v2
Esempio n. 23
0
    def __init__(self, audio_device=b'auto', winid=None, *args, **kwargs):
        super(MpvPlayer, self).__init__(*args, **kwargs)
        # https://github.com/cosven/FeelUOwn/issues/246
        locale.setlocale(locale.LC_NUMERIC, 'C')
        mpvkwargs = {}
        if winid is not None:
            mpvkwargs['wid'] = winid
        mpvkwargs['vo'] = 'opengl-cb'
        # set log_handler if you want to debug
        # mpvkwargs['log_handler'] = self.__log_handler
        # mpvkwargs['msg_level'] = 'all=v'
        # the default version of libmpv on Ubuntu 18.04 is (1, 25)
        self._version = _mpv_client_api_version()
        self._mpv = MPV(ytdl=False,
                        input_default_bindings=True,
                        input_vo_keyboard=True,
                        **mpvkwargs)
        _mpv_set_property_string(self._mpv.handle, b'audio-device',
                                 audio_device)
        # old version libmpv(for example: (1, 20)) should set option by using
        # _mpv_set_option_string, while newer version can use _mpv_set_property_string
        _mpv_set_option_string(self._mpv.handle, b'user-agent',
                               b'Mozilla/5.0 (Windows NT 10.0; Win64; x64)')

        #: if video_format changes to None, there is no video available
        self.video_format_changed = Signal()

        self._mpv.observe_property(
            'time-pos',
            lambda name, position: self._on_position_changed(position))
        self._mpv.observe_property(
            'duration',
            lambda name, duration: self._on_duration_changed(duration))
        self._mpv.observe_property(
            'video-format',
            lambda name, vformat: self._on_video_format_changed(vformat))
        # self._mpv.register_event_callback(lambda event: self._on_event(event))
        self._mpv._event_callbacks.append(self._on_event)
        logger.debug('Player initialize finished.')
Esempio n. 24
0
 def test_connect1(self):
     with mock.patch.object(A, 'f', return_value=None) as mock_method_f:
         s = Signal()
         # pay attention
         self.assertTrue(self.a1.f == self.a2.f == mock_method_f)
         s.connect(self.a1.f)
         s.emit(1, 'hello')
         mock_method_f.assert_called_once_with(1, 'hello')
Esempio n. 25
0
    def __init__(self, playlist=Playlist(), **kwargs):
        self._position = 0  # seconds
        self._volume = 100  # (0, 100)
        self._playlist = playlist
        self._state = State.stopped
        self._duration = None

        self.position_changed = Signal()
        self.state_changed = Signal()
        self.song_finished = Signal()
        self.duration_changed = Signal()
        self.media_changed = Signal()
Esempio n. 26
0
class PluginsManager:
    """在 App 初始化完成之后,加载插件

    TODO: 以后可能需要支持在 App 初始化完成之前加载插件
    """

    scan_finished = Signal()

    def __init__(self, app):
        super().__init__()
        self._app = app

        self._plugins = {}

    def load(self, plugin):
        plugin.enable(self._app)

    def unload(self, plugin):
        plugin.disable(self._app)

    def scan(self):
        logger.debug('Scaning plugins...')
        modules_name = [
            p for p in os.listdir(PLUGINS_DIR)
            if os.path.isdir(os.path.join(PLUGINS_DIR, p))
        ]
        modules_name.extend([
            p for p in os.listdir(USER_PLUGINS_DIR)
            if os.path.isdir(os.path.join(USER_PLUGINS_DIR))
        ])
        for module_name in modules_name:
            try:
                module = importlib.import_module(module_name)
                plugin_alias = module.__alias__
                plugin = Plugin(module, alias=plugin_alias)
                plugin.enable(self._app)
                logger.info('detect plugin: %s.' % plugin_alias)
            except:  # noqa
                logger.exception('detect a bad plugin %s' % module_name)
            else:
                self._plugins[plugin.name] = plugin
        logger.debug('Scaning plugins...done')

        self.scan_finished.emit(list(self._plugins.values()))
Esempio n. 27
0
class PluginsManager:
    """在 App 初始化完成之后,加载插件

    TODO: 以后可能需要支持在 App 初始化完成之前加载插件
    """

    scan_finished = Signal()

    def __init__(self, app):
        super().__init__()
        self._app = app

        self._plugins = {}

    def enable(self, plugin):
        plugin.enable(self._app)

    def disable(self, plugin):
        plugin.disable(self._app)

    def scan(self):
        """扫描并加载插件"""
        with action_log('Scaning plugins'):
            self._scan_dirs()
            self._scan_entry_points()
        self.scan_finished.emit(list(self._plugins.values()))

    def load_module(self, module):
        """加载插件模块并启用插件"""
        plugin = None
        with action_log('Creating plugin from module:%s' % module.__name__):
            try:
                plugin = Plugin.create(module)
            except InvalidPluginError as e:
                raise ActionError(str(e))
        if plugin is None:
            return
        with action_log('Enabling plugin:%s' % plugin.name):
            self._plugins[plugin.name] = plugin
            try:
                self.enable(plugin)
            except Exception as e:
                raise ActionError(str(e))

    def _scan_dirs(self):
        """扫描插件目录中的插件"""
        modules_name = ([
            p for p in os.listdir(USER_PLUGINS_DIR)
            if os.path.isdir(os.path.join(USER_PLUGINS_DIR))
        ])

        for module_name in modules_name:
            try:
                module = importlib.import_module(module_name)
            except Exception as e:
                logger.exception('Failed to import module %s', module_name)
            else:
                self.load_module(module)

    def _scan_entry_points(self):
        """扫描通过 setuptools 机制注册的插件

        https://packaging.python.org/guides/creating-and-discovering-plugins/
        """
        for entry_point in pkg_resources.iter_entry_points('fuo.plugins_v1'):
            try:
                module = entry_point.load()
            except Exception as e:  # noqa
                logger.exception('Failed to load module %s', entry_point.name)
            else:
                self.load_module(module)
Esempio n. 28
0
class Playlist:
    """player playlist provide a list of song model to play
    """
    def __init__(self, songs=None, playback_mode=PlaybackMode.loop):
        """
        :param songs: list of :class:`fuocore.models.SongModel`
        :param playback_mode: :class:`fuocore.player.PlaybackMode`
        """
        #: store value for ``current_song`` property
        self._current_song = None

        #: songs whose url is invalid
        self._bad_songs = []

        #: store value for ``songs`` property
        self._songs = songs or []

        #: store value for ``playback_mode`` property
        self._playback_mode = playback_mode

        #: playback mode changed signal
        self.playback_mode_changed = Signal()
        self.song_changed = Signal()
        """current song changed signal

        The player will play the song after it receive the signal,
        when song is None, the player will stop playback.
        """

    def __len__(self):
        return len(self._songs)

    def __getitem__(self, index):
        """overload [] operator"""
        return self._songs[index]

    def mark_as_bad(self, song):
        if song in self._songs and song not in self._bad_songs:
            self._bad_songs.append(song)

    def add(self, song):
        """往播放列表末尾添加一首歌曲"""
        if song in self._songs:
            return
        self._songs.append(song)
        logger.debug('Add %s to player playlist', song)

    def insert(self, song):
        """在当前歌曲后插入一首歌曲"""
        if song in self._songs:
            return
        if self._current_song is None:
            self._songs.append(song)
        else:
            index = self._songs.index(self._current_song)
            self._songs.insert(index + 1, song)

    def remove(self, song):
        """Remove song from playlist. O(n)

        If song is current song, remove the song and play next. Otherwise,
        just remove it.
        """
        if song in self._songs:
            if self._current_song is None:
                self._songs.remove(song)
            elif song == self._current_song:
                next_song = self.next_song
                # 随机模式下或者歌单只剩一首歌曲,下一首可能和当前歌曲相同
                if next_song == self.current_song:
                    self.current_song = None
                    self._songs.remove(song)
                    self.current_song = self.next_song
                else:
                    self.current_song = self.next_song
                    self._songs.remove(song)
            else:
                self._songs.remove(song)
            logger.debug('Remove {} from player playlist'.format(song))
        else:
            logger.debug('Remove failed: {} not in playlist'.format(song))

        if song in self._bad_songs:
            self._bad_songs.remove(song)

    def init_from(self, songs):
        """(alpha) temporarily, should only called by player.play_songs"""
        self.clear()
        # since we will call songs.clear method during playlist clearing,
        # we need to deepcopy songs object here.
        self._songs = copy.deepcopy(songs)

    def clear(self):
        """remove all songs from playlists"""
        if self.current_song is not None:
            self.current_song = None
        self._songs.clear()
        self._bad_songs.clear()

    def list(self):
        """get all songs in playlists"""
        return self._songs

    @property
    def current_song(self):
        """
        current playing song, return None if there is no current song
        """
        return self._current_song

    @current_song.setter
    def current_song(self, song):
        """设置当前歌曲,将歌曲加入到播放列表,并发出 song_changed 信号

        .. note::

            该方法理论上只应该被 Player 对象调用。
        """
        self._last_song = self.current_song
        if song is None:
            self._current_song = None
        # add it to playlist if song not in playlist
        elif song in self._songs:
            self._current_song = song
        else:
            self.insert(song)
            self._current_song = song
        self.song_changed.emit(song)

    @property
    def playback_mode(self):
        return self._playback_mode

    @playback_mode.setter
    def playback_mode(self, playback_mode):
        self._playback_mode = playback_mode
        self.playback_mode_changed.emit(playback_mode)

    def _get_good_song(self, base=0, random_=False, direction=1):
        """从播放列表中获取一首可以播放的歌曲

        :param base: base index
        :param random: random strategy or not
        :param direction: forward if > 0 else backword

        >>> pl = Playlist([1, 2, 3])
        >>> pl._get_good_song()
        1
        >>> pl._get_good_song(base=1)
        2
        >>> pl._bad_songs = [2]
        >>> pl._get_good_song(base=1, direction=-1)
        1
        >>> pl._get_good_song(base=1)
        3
        >>> pl._bad_songs = [1, 2, 3]
        >>> pl._get_good_song()
        """
        if not self._songs or len(self._songs) <= len(self._bad_songs):
            logger.debug('No good song in playlist.')
            return None

        good_songs = []
        if direction > 0:
            song_list = self._songs[base:] + self._songs[0:base]
        else:
            song_list = self._songs[base::-1] + self._songs[:base:-1]
        for song in song_list:
            if song not in self._bad_songs:
                good_songs.append(song)
        if random_:
            return random.choice(good_songs)
        else:
            return good_songs[0]

    @property
    def next_song(self):
        """next song for player, calculated based on playback_mode"""
        # 如果没有正在播放的歌曲,找列表里面第一首能播放的
        if self.current_song is None:
            return self._get_good_song()

        if self.playback_mode == PlaybackMode.random:
            next_song = self._get_good_song(random_=True)
        else:
            current_index = self._songs.index(self.current_song)
            if current_index == len(self._songs) - 1:
                if self.playback_mode in (PlaybackMode.loop,
                                          PlaybackMode.one_loop):
                    next_song = self._get_good_song()
                elif self.playback_mode == PlaybackMode.sequential:
                    next_song = None
            else:
                next_song = self._get_good_song(base=current_index + 1)
        return next_song

    @property
    def previous_song(self):
        """previous song for player to play

        NOTE: not the last played song
        """
        if self.current_song is None:
            return self._get_good_song(base=-1, direction=-1)

        if self.playback_mode == PlaybackMode.random:
            previous_song = self._get_good_song(direction=-1)
        else:
            current_index = self._songs.index(self.current_song)
            previous_song = self._get_good_song(base=current_index - 1,
                                                direction=-1)
        return previous_song

    def next(self):
        """advance to the next song in playlist"""
        self.current_song = self.next_song

    def previous(self):
        """return to the previous song in playlist"""
        self.current_song = self.previous_song
Esempio n. 29
0
 def test_disconnect(self):
     s = Signal()
     s.connect(f)
     s.disconnect(f)
     s.emit(1, 'hello')
Esempio n. 30
0
 def test_connect2(self):
     s = Signal()
     s.connect(f)
     s.emit(1, 'hello')
     s.emit(1, 'hello')