コード例 #1
0
ファイル: log.py プロジェクト: xuhui/clay
    def __init__(self):
        self.logs = []
        self.logfile = open('/tmp/clay.log', 'w')

        self._lock = Lock()

        self.on_log_event = EventHook()
コード例 #2
0
    def __init__(self):
        assert self.__class__.instance is None, 'Can be created only once!'
        self.is_debug = os.getenv('CLAY_DEBUG')
        self.mobile_client = Mobileclient()
        if self.is_debug:
            self.mobile_client._make_call = self._make_call_proxy(
                self.mobile_client._make_call)
            self.debug_file = open('/tmp/clay-api-log.json', 'w')
            self._last_call_index = 0
        self.cached_tracks = None
        self.cached_playlists = None

        self.invalidate_caches()

        self.auth_state_changed = EventHook()
コード例 #3
0
ファイル: gp.py プロジェクト: vale981/clay
    def __init__(self):
        # self.is_debug = os.getenv('CLAY_DEBUG')
        self.mobile_client = Mobileclient()
        self.mobile_client._make_call = self._make_call_proxy(
            self.mobile_client._make_call)
        # if self.is_debug:
        #     self.debug_file = open('/tmp/clay-api-log.json', 'w')
        #     self._last_call_index = 0
        self.cached_tracks = None
        self.cached_liked_songs = LikedSongs()
        self.cached_playlists = None
        self.cached_stations = None

        self.invalidate_caches()

        self.auth_state_changed = EventHook()
コード例 #4
0
ファイル: hotkeys.py プロジェクト: Sadin/clay
    def __init__(self):
        assert self.__class__.instance is None, 'Can be created only once!'
        self.hotkeys = {}
        self.config = None

        self.play_pause = EventHook()
        self.next = EventHook()
        self.prev = EventHook()

        if IS_INIT:
            Keybinder.init()
            self.initialize()

            threading.Thread(target=Gtk.main).start()
        else:
            NotificationArea.notify(
                'Could not import Keybinder and Gtk. Error was: "{}"\n'
                'Global shortcuts will not work.'.format(ERROR))
コード例 #5
0
ファイル: hotkeys.py プロジェクト: xuhui/clay
    def __init__(self):
        self._x_hotkeys = {}
        self._hotkeys = self._parse_hotkeys()
        self.config = None

        self.play_pause = EventHook()
        self.next = EventHook()
        self.prev = EventHook()

        if IS_INIT:
            Keybinder.init()
            self.initialize()
            threading.Thread(target=Gtk.main).start()
        else:
            logger.debug("Not loading the global shortcuts.")
            notification_area.notify(
                ERROR_MESSAGE +
                ", this means the global shortcuts will not work.\n" +
                "You can check the log for more details.")
コード例 #6
0
ファイル: gp.py プロジェクト: vale981/clay
class _GP(object):
    """
    Interface to :class:`gmusicapi.Mobileclient`. Implements
    asynchronous API calls, caching and some other perks.

    Singleton.
    """
    # TODO: Switch to urwid signals for more explicitness?
    caches_invalidated = EventHook()

    def __init__(self):
        # self.is_debug = os.getenv('CLAY_DEBUG')
        self.mobile_client = Mobileclient()
        self.mobile_client._make_call = self._make_call_proxy(
            self.mobile_client._make_call)
        # if self.is_debug:
        #     self.debug_file = open('/tmp/clay-api-log.json', 'w')
        #     self._last_call_index = 0
        self.cached_tracks = None
        self.cached_liked_songs = LikedSongs()
        self.cached_playlists = None
        self.cached_stations = None

        self.invalidate_caches()

        self.auth_state_changed = EventHook()

    def _make_call_proxy(self, func):
        """
        Return a function that wraps *fn* and logs args & return values.
        """
        def _make_call(protocol, *args, **kwargs):
            """
            Wrapper function.
            """
            logger.debug('GP::{}(*{}, **{})'.format(protocol.__name__, args,
                                                    kwargs))
            result = func(protocol, *args, **kwargs)
            # self._last_call_index += 1
            # call_index = self._last_call_index
            # self.debug_file.write(json.dumps([
            #     call_index,
            #     protocol.__name__, args, kwargs,
            #     result
            # ]) + '\n')
            # self.debug_file.flush()
            return result

        return _make_call

    def invalidate_caches(self):
        """
        Clear cached tracks & playlists & stations.
        """
        self.cached_tracks = None
        self.cached_playlists = None
        self.cached_stations = None
        self.caches_invalidated.fire()

    @synchronized
    def login(self, email, password, device_id, **_):
        """
        Log in into Google Play Music.
        """
        self.mobile_client.logout()
        self.invalidate_caches()
        # prev_auth_state = self.is_authenticated
        result = self.mobile_client.login(email, password, device_id)
        # if prev_auth_state != self.is_authenticated:
        self.auth_state_changed.fire(self.is_authenticated)
        return result

    login_async = asynchronous(login)

    @synchronized
    def use_authtoken(self, authtoken, device_id):
        """
        Try to use cached token to log into Google Play Music.
        """
        # pylint: disable=protected-access
        self.mobile_client.session._authtoken = authtoken
        self.mobile_client.session.is_authenticated = True
        self.mobile_client.android_id = device_id
        del self.mobile_client.is_subscribed
        if self.mobile_client.is_subscribed:
            self.auth_state_changed.fire(True)
            return True
        del self.mobile_client.is_subscribed
        self.mobile_client.android_id = None
        self.mobile_client.session.is_authenticated = False
        self.auth_state_changed.fire(False)
        return False

    use_authtoken_async = asynchronous(use_authtoken)

    def get_authtoken(self):
        """
        Return currently active auth token.
        """
        # pylint: disable=protected-access
        return self.mobile_client.session._authtoken

    @synchronized
    def get_all_tracks(self):
        """
        Cache and return all tracks from "My library".

        Each track will have "id" and "storeId" keys.
        """
        if self.cached_tracks:
            return self.cached_tracks
        data = self.mobile_client.get_all_songs()
        self.cached_tracks = Track.from_data(data, Track.SOURCE_LIBRARY, True)

        return self.cached_tracks

    get_all_tracks_async = asynchronous(get_all_tracks)

    def get_stream_url(self, stream_id):
        """
        Returns playable stream URL of track by id.
        """
        return self.mobile_client.get_stream_url(stream_id)

    get_stream_url_async = asynchronous(get_stream_url)

    def increment_song_playcount(self, track_id):
        """
        Increments the playcount of the song given by track_id
        by one.
        """
        return gp.mobile_client.increment_song_playcount(track_id)

    increment_song_playcount_async = asynchronous(increment_song_playcount)

    @synchronized
    def get_all_user_station_contents(self, **_):
        """
              Return list of :class:`.Station` instances.
              """
        if self.cached_stations:
            return self.cached_stations
        self.get_all_tracks()

        self.cached_stations = Station.from_data(
            self.mobile_client.get_all_stations(), True)
        return self.cached_stations

    get_all_user_station_contents_async = (  # pylint: disable=invalid-name
        asynchronous(get_all_user_station_contents))

    @synchronized
    def get_all_user_playlist_contents(self, **_):
        """
        Return list of :class:`.Playlist` instances.
        """
        if self.cached_playlists:
            return [self.cached_liked_songs] + self.cached_playlists

        self.get_all_tracks()

        self.cached_playlists = Playlist.from_data(
            self.mobile_client.get_all_user_playlist_contents(), True)
        return [self.cached_liked_songs] + self.cached_playlists

    get_all_user_playlist_contents_async = (  # pylint: disable=invalid-name
        asynchronous(get_all_user_playlist_contents))

    def get_cached_tracks_map(self):
        """
        Return a dictionary of tracks where keys are strings with track IDs
        and values are :class:`.Track` instances.
        """
        return {track.id: track for track in self.cached_tracks}

    def get_track_by_id(self, any_id):
        """
        Return track by id or store_id.
        """
        for track in self.cached_tracks:
            if any_id in (track.library_id, track.store_id,
                          track.playlist_item_id):
                return track
        return None

    def search(self, query):
        """
        Find tracks and return an instance of :class:`.SearchResults`.
        """
        results = self.mobile_client.search(query)
        return SearchResults.from_data(results)

    search_async = asynchronous(search)

    def add_to_my_library(self, track):
        """
        Add a track to my library.
        """
        result = self.mobile_client.add_store_tracks(track.id)
        if result:
            self.invalidate_caches()
        return result

    def remove_from_my_library(self, track):
        """
        Remove a track from my library.
        """
        result = self.mobile_client.delete_songs(track.id)
        if result:
            self.invalidate_caches()
        return result

    @property
    def is_authenticated(self):
        """
        Return True if user is authenticated on Google Play Music, false otherwise.
        """
        return self.mobile_client.is_authenticated()

    @property
    def is_subscribed(self):
        """
        Return True if user is subscribed on Google Play Music, false otherwise.
        """
        return self.mobile_client.is_subscribed
コード例 #7
0
class _Player(object):
    """
    Interface to libVLC. Uses Queue as a playback plan.
    Emits various events if playback state, tracks or play flags change.

    Singleton.
    """
    media_position_changed = EventHook()
    media_state_changed = EventHook()
    track_changed = EventHook()
    playback_flags_changed = EventHook()
    queue_changed = EventHook()
    track_appended = EventHook()
    track_removed = EventHook()

    def __init__(self):
        self.instance = vlc.Instance()
        print_func = CFUNCTYPE(
            c_void_p,
            c_void_p,  # data
            c_int,  # level
            c_void_p,  # context
            c_char_p,  # fmt
            c_void_p)  # args

        self.instance.log_set(print_func(_dummy_log), None)

        self.instance.set_user_agent(meta.APP_NAME, meta.USER_AGENT)

        self.media_player = self.instance.media_player_new()

        self.media_player.event_manager().event_attach(
            vlc.EventType.MediaPlayerPlaying, self._media_state_changed)
        self.media_player.event_manager().event_attach(
            vlc.EventType.MediaPlayerPaused, self._media_state_changed)
        self.media_player.event_manager().event_attach(
            vlc.EventType.MediaPlayerEndReached, self._media_end_reached)
        self.media_player.event_manager().event_attach(
            vlc.EventType.MediaPlayerPositionChanged,
            self._media_position_changed)

        self.equalizer = vlc.libvlc_audio_equalizer_new()
        self.media_player.set_equalizer(self.equalizer)
        self._create_station_notification = None
        self._is_loading = False
        self.queue = _Queue()

    def enable_xorg_bindings(self):
        """Enable the global X bindings using keybinder"""
        if os.environ.get("DISPLAY") is None:
            logger.debug(
                "X11 isn't running so we can't load the global keybinds")
            return

        from clay.hotkeys import hotkey_manager
        hotkey_manager.play_pause += self.play_pause
        hotkey_manager.next += self.next
        hotkey_manager.prev += lambda: self.seek_absolute(0)

    def broadcast_state(self):
        """
        Write current playback state into a ``/tmp/clay.json`` file.
        """
        track = self.queue.get_current_track()
        if track is None:
            data = dict(playing=False,
                        artist=None,
                        title=None,
                        progress=None,
                        length=None)
        else:
            data = dict(loading=self.is_loading,
                        playing=self.is_playing,
                        artist=track.artist,
                        title=track.title,
                        progress=self.get_play_progress_seconds(),
                        length=self.get_length_seconds(),
                        album_name=track.album_name,
                        album_url=track.album_url)
        with open('/tmp/clay.json', 'w') as statefile:
            statefile.write(json.dumps(data, indent=4))

    def _media_state_changed(self, event):
        """
        Called when a libVLC playback state changes.
        Broadcasts playback state & fires :attr:`media_state_changed` event.
        """
        assert event
        self.broadcast_state()
        self.media_state_changed.fire(self.is_loading, self.is_playing)

    def _media_end_reached(self, event):
        """
        Called when end of currently played track is reached.
        Increments the play count and advances to the next track.
        """
        assert event
        self.queue.get_current_track().increment_playcount()
        self.next()

    def _media_position_changed(self, event):
        """
        Called when playback position changes (this happens few times each second.)
        Fires :attr:`.media_position_changed` event.
        """
        assert event
        self.broadcast_state()
        self.media_position_changed.fire(self.get_play_progress())

    def load_queue(self, data, current_index=None):
        """
        Load queue & start playback.
        Fires :attr:`.queue_changed` event.

        See :meth:`._Queue.load`.
        """
        self.queue.load(data, current_index)
        self.queue_changed.fire()
        self._play()

    def append_to_queue(self, track):
        """
        Append track to queue.
        Fires :attr:`.track_appended` event.

        See :meth:`._Queue.append`
        """
        self.queue.append(track)
        self.track_appended.fire(track)
        # self.queue_changed.fire()

    def remove_from_queue(self, track):
        """
        Remove track from queue.
        Fires :attr:`.track_removed` event.

        See :meth:`._Queue.remove`
        """
        self.queue.remove(track)
        self.track_removed.fire(track)

    def create_station_from_track(self, track):
        """
        Request creation of new station from some track.
        Runs in background.
        """
        self._create_station_notification = notification_area.notify(
            'Creating station...')
        track.create_station_async(
            callback=self._create_station_from_track_ready)

    def _create_station_from_track_ready(self, station, error):
        """
        Called when a station is created.
        If *error* is ``None``, load new station's tracks into queue.
        """
        if error:
            self._create_station_notification.update(
                'Failed to create station: {}'.format(str(error)))
            return

        if not station.get_tracks():
            self._create_station_notification.update(
                'Newly created station is empty :(')
            return

        self.load_queue(station.get_tracks())
        self._create_station_notification.update('Station ready!')

    def get_is_random(self):
        """
        Return ``True`` if track selection from queue is randomed, ``False`` otherwise.
        """
        return self.queue.random

    def get_is_repeat_one(self):
        """
        Return ``True`` if track repetition in queue is enabled, ``False`` otherwise.
        """
        return self.queue.repeat_one

    def set_random(self, value):
        """
        Enable/disable random track selection.
        """
        self.queue.random = value
        self.playback_flags_changed.fire()

    def set_repeat_one(self, value):
        """
        Enable/disable track repetition.
        """
        self.queue.repeat_one = value
        self.playback_flags_changed.fire()

    def get_queue_tracks(self):
        """
        Return :attr:`._Queue.get_tracks`
        """
        return self.queue.get_tracks()

    def _play(self):
        """
        Pick current track from a queue and requests media stream URL.
        Completes in background.
        """
        track = self.queue.get_current_track()
        if track is None:
            return
        self._is_loading = True
        self.broadcast_state()
        self.track_changed.fire(track)

        if settings.get('download_tracks', 'play_settings') or \
           settings.get_is_file_cached(track.filename):
            path = settings.get_cached_file_path(track.filename)

            if path is None:
                logger.debug('Track %s not in cache, downloading...',
                             track.store_id)
                track.get_url(callback=self._download_track)
            else:
                logger.debug('Track %s in cache, playing', track.store_id)
                self._play_ready(path, None, track)
        else:
            logger.debug('Starting to stream %s', track.store_id)
            track.get_url(callback=self._play_ready)

    def _download_track(self, url, error, track):
        if error:
            notification_area.notify('Failed to request media URL: {}'.format(
                str(error)))
            logger.error('Failed to request media URL for track %s: %s',
                         track.original_data, str(error))
            return
        response = urlopen(url)
        path = settings.save_file_to_cache(track.filename, response.read())
        self._play_ready(path, None, track)

    def _play_ready(self, url, error, track):
        """
        Called once track's media stream URL request completes.
        If *error* is ``None``, tell libVLC to play media by *url*.
        """
        self._is_loading = False
        if error:
            notification_area.notify('Failed to request media URL: {}'.format(
                str(error)))
            logger.error('Failed to request media URL for track %s: %s',
                         track.original_data, str(error))
            return
        assert track
        media = vlc.Media(url)
        self.media_player.set_media(media)

        self.media_player.play()

        osd_manager.notify(track)

    @property
    def is_loading(self):
        """
        True if current libVLC state is :attr:`vlc.State.Playing`
        """
        return self._is_loading

    @property
    def is_playing(self):
        """
        True if current libVLC state is :attr:`vlc.State.Playing`
        """
        return self.media_player.get_state() == vlc.State.Playing

    def play_pause(self):
        """
        Toggle playback, i.e. play if paused or pause if playing.
        """
        if self.is_playing:
            self.media_player.pause()
        else:
            self.media_player.play()

    def get_play_progress(self):
        """
        Return current playback position in range ``[0;1]`` (``float``).
        """
        return self.media_player.get_position()

    def get_play_progress_seconds(self):
        """
        Return current playback position in seconds (``int``).
        """
        return int(self.media_player.get_position() *
                   self.media_player.get_length() / 1000)

    def get_length_seconds(self):
        """
        Return currently played track's length in seconds (``int``).
        """
        return int(self.media_player.get_length() // 1000)

    def next(self, force=False):
        """
        Advance to next track in queue.
        See :meth:`._Queue.next`.
        """
        self.queue.next(force)
        self._play()

    def prev(self, force=False):
        """
        Advance to their previous track in their queue
        seek :meth:`._Queue.prev`
        """
        self.queue.prev(force)
        self._play()

    def get_current_track(self):
        """
        Return currently played track.
        See :meth:`._Queue.get_current_track`.
        """
        return self.queue.get_current_track()

    def seek(self, delta):
        """
        Seek to relative position.
        *delta* must be a ``float`` in range ``[-1;1]``.
        """
        self.media_player.set_position(self.get_play_progress() + delta)

    def seek_absolute(self, position):
        """
        Seek to absolute position.
        *position* must be a ``float`` in range ``[0;1]``.
        """
        self.media_player.set_position(position)

    @staticmethod
    def get_equalizer_freqs():
        """
        Return a list of equalizer frequencies for each band.
        """
        return [
            vlc.libvlc_audio_equalizer_get_band_frequency(index)
            for index in range(vlc.libvlc_audio_equalizer_get_band_count())
        ]

    def get_equalizer_amps(self):
        """
        Return a list of equalizer amplifications for each band.
        """
        return [
            vlc.libvlc_audio_equalizer_get_amp_at_index(self.equalizer, index)
            for index in range(vlc.libvlc_audio_equalizer_get_band_count())
        ]

    def set_equalizer_value(self, index, amp):
        """
        Set equalizer amplification for specific band.
        """
        assert vlc.libvlc_audio_equalizer_set_amp_at_index(
            self.equalizer, amp, index) == 0
        self.media_player.set_equalizer(self.equalizer)

    def set_equalizer_values(self, amps):
        """
        Set a list of equalizer amplifications for each band.
        """
        assert len(amps) == vlc.libvlc_audio_equalizer_get_band_count()
        for index, amp in enumerate(amps):
            assert vlc.libvlc_audio_equalizer_set_amp_at_index(
                self.equalizer, amp, index) == 0
        self.media_player.set_equalizer(self.equalizer)
コード例 #8
0
ファイル: log.py プロジェクト: xuhui/clay
class _Logger(object):
    """
    Global logger.

    Allows subscribing to log events.
    """
    def __init__(self):
        self.logs = []
        self.logfile = open('/tmp/clay.log', 'w')

        self._lock = Lock()

        self.on_log_event = EventHook()

    def log(self, level, message, *args):
        """
        Add log item.
        """
        self._lock.acquire()
        try:
            logger_record = _LoggerRecord(level, message, args)
            self.logs.append(logger_record)
            self.logfile.write('{} {:8} {}\n'.format(
                logger_record.formatted_timestamp, logger_record.verbosity,
                logger_record.formatted_message))
            self.logfile.flush()
            self.on_log_event.fire(logger_record)
        finally:
            self._lock.release()

    def debug(self, message, *args):
        """
        Add debug log item.
        """
        self.log('DEBUG', message, *args)

    def info(self, message, *args):
        """
        Add info log item.
        """
        self.log('INFO', message, *args)

    def warn(self, message, *args):
        """
        Add warning log item.
        """
        self.log('WARNING', message, *args)

    warning = warn

    def error(self, message, *args):
        """
        Add error log item.
        """
        self.log('ERROR', message, *args)

    def get_logs(self):
        """
        Return all logs.
        """
        return self.logs
コード例 #9
0
ファイル: player.py プロジェクト: Sadin/clay
class Player(object):
    """
    Interface to libVLC. Uses Queue as a playback plan.
    Emits various events if playback state, tracks or play flags change.

    Singleton.
    """
    instance = None

    media_position_changed = EventHook()
    media_state_changed = EventHook()
    track_changed = EventHook()
    playback_flags_changed = EventHook()
    queue_changed = EventHook()
    track_appended = EventHook()
    track_removed = EventHook()

    def __init__(self):
        assert self.__class__.instance is None, 'Can be created only once!'
        self.instance = vlc.Instance()
        self.instance.set_user_agent(
            meta.APP_NAME,
            meta.USER_AGENT
        )

        self.media_player = self.instance.media_player_new()

        self.media_player.event_manager().event_attach(
            vlc.EventType.MediaPlayerPlaying,
            self._media_state_changed
        )
        self.media_player.event_manager().event_attach(
            vlc.EventType.MediaPlayerPaused,
            self._media_state_changed
        )
        self.media_player.event_manager().event_attach(
            vlc.EventType.MediaPlayerEndReached,
            self._media_end_reached
        )
        self.media_player.event_manager().event_attach(
            vlc.EventType.MediaPlayerPositionChanged,
            self._media_position_changed
        )

        self.equalizer = vlc.libvlc_audio_equalizer_new()

        self.media_player.set_equalizer(self.equalizer)

        hotkey_manager = HotkeyManager.get()
        hotkey_manager.play_pause += self.play_pause
        hotkey_manager.next += self.next
        hotkey_manager.prev += lambda: self.seek_absolute(0)

        self._create_station_notification = None
        self._is_loading = False
        self.queue = Queue()

    @classmethod
    def get(cls):
        """
        Create new :class:`.Player` instance or return existing one.
        """
        if cls.instance is None:
            cls.instance = Player()

        return cls.instance

    def broadcast_state(self):
        """
        Write current playback state into a ``/tmp/clay.json`` file.
        """
        track = self.queue.get_current_track()
        if track is None:
            data = dict(
                playing=False,
                artist=None,
                title=None,
                progress=None,
                length=None
            )
        else:
            data = dict(
                loading=self.is_loading,
                playing=self.is_playing,
                artist=track.artist,
                title=track.title,
                progress=self.get_play_progress_seconds(),
                length=self.get_length_seconds(),
                album_name=track.album_name,
                album_url=track.album_url
            )
        with open('/tmp/clay.json', 'w') as statefile:
            statefile.write(json.dumps(data, indent=4))

    def _media_state_changed(self, event):
        """
        Called when a libVLC playback state changes.
        Broadcasts playback state & fires :attr:`media_state_changed` event.
        """
        assert event
        self.broadcast_state()
        self.media_state_changed.fire(self.is_loading, self.is_playing)

    def _media_end_reached(self, event):
        """
        Called when end of currently played track is reached.
        Advances to the next track.
        """
        assert event
        self.next()

    def _media_position_changed(self, event):
        """
        Called when playback position changes (this happens few times each second.)
        Fires :attr:`.media_position_changed` event.
        """
        assert event
        self.broadcast_state()
        self.media_position_changed.fire(
            self.get_play_progress()
        )

    def load_queue(self, data, current_index=None):
        """
        Load queue & start playback.
        Fires :attr:`.queue_changed` event.

        See :meth:`.Queue.load`.
        """
        self.queue.load(data, current_index)
        self.queue_changed.fire()
        self._play()

    def append_to_queue(self, track):
        """
        Append track to queue.
        Fires :attr:`.track_appended` event.

        See :meth:`.Queue.append`
        """
        self.queue.append(track)
        self.track_appended.fire(track)
        # self.queue_changed.fire()

    def remove_from_queue(self, track):
        """
        Remove track from queue.
        Fires :attr:`.track_removed` event.

        See :meth:`.Queue.remove`
        """
        self.queue.remove(track)
        self.track_removed.fire(track)

    def create_station_from_track(self, track):
        """
        Request creation of new station from some track.
        Runs in background.
        """
        self._create_station_notification = NotificationArea.notify('Creating station...')
        track.create_station_async(callback=self._create_station_from_track_ready)

    def _create_station_from_track_ready(self, station, error):
        """
        Called when a station is created.
        If *error* is ``None``, load new station's tracks into queue.
        """
        if error:
            self._create_station_notification.update(
                'Failed to create station: {}'.format(str(error))
            )
            return

        if not station.get_tracks():
            self._create_station_notification.update(
                'Newly created station is empty :('
            )
            return

        self.load_queue(station.get_tracks())
        self._create_station_notification.update('Station ready!')

    def get_is_random(self):
        """
        Return ``True`` if track selection from queue is randomed, ``False`` otherwise.
        """
        return self.queue.random

    def get_is_repeat_one(self):
        """
        Return ``True`` if track repetition in queue is enabled, ``False`` otherwise.
        """
        return self.queue.repeat_one

    def set_random(self, value):
        """
        Enable/disable random track selection.
        """
        self.queue.random = value
        self.playback_flags_changed.fire()

    def set_repeat_one(self, value):
        """
        Enable/disable track repetition.
        """
        self.queue.repeat_one = value
        self.playback_flags_changed.fire()

    def get_queue_tracks(self):
        """
        Return :attr:`.Queue.get_tracks`
        """
        return self.queue.get_tracks()

    def _play(self):
        """
        Pick current track from a queue and requests media stream URL.
        Completes in background.
        """
        track = self.queue.get_current_track()
        if track is None:
            return
        self._is_loading = True
        track.get_url(callback=self._play_ready)
        self.broadcast_state()
        self.track_changed.fire(track)

    def _play_ready(self, url, error, track):
        """
        Called once track's media stream URL request completes.
        If *error* is ``None``, tell libVLC to play media by *url*.
        """
        self._is_loading = False
        if error:
            NotificationArea.notify('Failed to request media URL: {}'.format(str(error)))
            return
        assert track
        media = vlc.Media(url)
        self.media_player.set_media(media)

        self.media_player.play()

    @property
    def is_loading(self):
        """
        True if current libVLC state is :attr:`vlc.State.Playing`
        """
        return self._is_loading

    @property
    def is_playing(self):
        """
        True if current libVLC state is :attr:`vlc.State.Playing`
        """
        return self.media_player.get_state() == vlc.State.Playing

    def play_pause(self):
        """
        Toggle playback, i.e. play if paused or pause if playing.
        """
        if self.is_playing:
            self.media_player.pause()
        else:
            self.media_player.play()

    def get_play_progress(self):
        """
        Return current playback position in range ``[0;1]`` (``float``).
        """
        return self.media_player.get_position()

    def get_play_progress_seconds(self):
        """
        Return current playback position in seconds (``int``).
        """
        return int(self.media_player.get_position() * self.media_player.get_length() / 1000)

    def get_length_seconds(self):
        """
        Return currently played track's length in seconds (``int``).
        """
        return int(self.media_player.get_length() // 1000)

    def next(self, force=False):
        """
        Advance to next track in queue.
        See :meth:`.Queue.next`.
        """
        self.queue.next(force)
        self._play()

    def get_current_track(self):
        """
        Return currently played track.
        See :meth:`.Queue.get_current_track`.
        """
        return self.queue.get_current_track()

    def seek(self, delta):
        """
        Seek to relative position.
        *delta* must be a ``float`` in range ``[-1;1]``.
        """
        self.media_player.set_position(self.get_play_progress() + delta)

    def seek_absolute(self, position):
        """
        Seek to absolute position.
        *position* must be a ``float`` in range ``[0;1]``.
        """
        self.media_player.set_position(position)

    @staticmethod
    def get_equalizer_freqs():
        """
        Return a list of equalizer frequencies for each band.
        """
        return [
            vlc.libvlc_audio_equalizer_get_band_frequency(index)
            for index
            in range(vlc.libvlc_audio_equalizer_get_band_count())
        ]

    def get_equalizer_amps(self):
        """
        Return a list of equalizer amplifications for each band.
        """
        return [
            vlc.libvlc_audio_equalizer_get_amp_at_index(
                self.equalizer,
                index
            )
            for index
            in range(vlc.libvlc_audio_equalizer_get_band_count())
        ]

    def set_equalizer_value(self, index, amp):
        """
        Set equalizer amplification for specific band.
        """
        assert vlc.libvlc_audio_equalizer_set_amp_at_index(
            self.equalizer,
            amp,
            index
        ) == 0
        self.media_player.set_equalizer(self.equalizer)

    def set_equalizer_values(self, amps):
        """
        Set a list of equalizer amplifications for each band.
        """
        assert len(amps) == vlc.libvlc_audio_equalizer_get_band_count()
        for index, amp in enumerate(amps):
            assert vlc.libvlc_audio_equalizer_set_amp_at_index(
                self.equalizer,
                amp,
                index
            ) == 0
        self.media_player.set_equalizer(self.equalizer)