def next(self): """ Return next radio name, uri @return Track """ track = Track() if self.current_track.id != Type.RADIOS: return track radios_manager = RadiosManager() radios = radios_manager.get() i = 0 for (radio_id, name) in radios: i += 1 if self.current_track.album_artist == name: break # Get next radio if i >= len(radios): i = 0 name = radios[i][1] uris = radios_manager.get_tracks(name) if len(uris) > 0: track.set_radio(name, uris[0]) return track
def prev(self): """ Return prev radio name, uri @return Track """ track = Track() if self.current_track.id != Type.RADIOS: return track radios_manager = RadiosManager() radios = radios_manager.get() i = len(radios) - 1 for (radio_id, name) in reversed(radios): i -= 1 if self.current_track.album_artist == name: break # Get prev radio if i < 0: i = len(radios) - 1 name = radios[i][1] uris = radios_manager.get_tracks(name) if len(uris) > 0: track.set_radio(name, uris[0]) return track
def __init__(self, name, radios_manager): """ Init Popover @param name as string @param radios_manager as RadiosManager """ Gtk.Popover.__init__(self) self.connect('map', self.__on_map) self.connect('unmap', self.__on_unmap) self.__name = name self.__radios_manager = radios_manager self.__start = 0 self.__orig_pixbufs = {} self.__stack = Gtk.Stack() self.__stack.set_transition_duration(1000) self.__stack.set_transition_type(Gtk.StackTransitionType.CROSSFADE) self.__stack.show() builder = Gtk.Builder() builder.add_from_resource('/org/gnome/Lollypop/RadioPopover.ui') builder.connect_signals(self) self.__view = Gtk.FlowBox() self.__view.set_selection_mode(Gtk.SelectionMode.NONE) self.__view.connect('child-activated', self.__on_activate) self.__view.set_max_children_per_line(100) self.__view.set_property('row-spacing', 10) self.__view.show() builder.get_object('viewport').add(self.__view) self.__name_entry = builder.get_object('name') self.__uri_entry = builder.get_object('uri') self.__btn_add_modify = builder.get_object('btn_add_modify') self.__spinner = builder.get_object('spinner') self.__stack.add_named(builder.get_object('spinner-grid'), 'spinner') self.__stack.add_named(builder.get_object('notfound'), 'notfound') self.__stack.add_named(builder.get_object('logo'), 'logo') self.__stack.add_named(builder.get_object('widget'), 'widget') self.__stack.set_visible_child_name('widget') self.add(self.__stack) track = Track() track.set_radio(name, '') rating = RatingWidget(track) rating.show() builder.get_object('widget').attach(rating, 0, 2, 2, 1) if self.__name == '': # Translators: radio context builder.get_object('btn_add_modify').set_label(_("Add")) else: # Translators: radio context builder.get_object('btn_add_modify').set_label(_("Modify")) builder.get_object('btn_delete').show() self.__name_entry.set_text(self.__name) url = self.__radios_manager.get_url(self.__name) if url: self.__uri_entry.set_text(url)
def next(self): """ Return next radio name, uri @return Track """ track = Track() if self.current_track.id != Type.RADIOS: return track radios_manager = Radios() radios = radios_manager.get() i = 0 for (name, url) in radios: i += 1 if self.current_track.album_artist == name: break # Get next radio if i >= len(radios): i = 0 name = radios[i][0] url = radios[i][1] if url: track.set_radio(name, url) return track
def set_albums(self, track_id, artist_ids, genre_ids): """ Set album list (for next/prev) @param track id as int @param artist id as int @param genre id as int """ # Invalid track if track_id is None: return album = Track(track_id).album self._albums = [] ShufflePlayer.reset_history(self) # We are not playing a user playlist anymore self._user_playlist = [] self._user_playlist_id = None # We are in all artists if (genre_ids and genre_ids[0] == Type.ALL) or\ (artist_ids and artist_ids[0] == Type.ALL): self._albums = Lp().albums.get_compilations() self._albums += Lp().albums.get_ids() # We are in populars view, add popular albums elif genre_ids and genre_ids[0] == Type.POPULARS: if self._shuffle in [Shuffle.TRACKS_ARTIST, Shuffle.ALBUMS_ARTIST]: self._albums = [] self.next_track = Track() for album_id in Lp().albums.get_populars(): if Lp().albums.get_artist_id(album_id) == \ album.artist_id: self._albums.append(album_id) else: self._albums = Lp().albums.get_populars() # We are in recents view, add recent albums elif genre_ids and genre_ids[0] == Type.RECENTS: self._albums = Lp().albums.get_recents() # We are in randoms view, add random albums elif genre_ids and genre_ids[0] == Type.RANDOMS: self._albums = Lp().albums.get_cached_randoms() # We are in compilation view without genre elif genre_ids and genre_ids[0] == Type.COMPILATIONS: self._albums = Lp().albums.get_compilations() # Random tracks/albums for artist elif self._shuffle in [Shuffle.TRACKS_ARTIST, Shuffle.ALBUMS_ARTIST]: self._albums = Lp().albums.get_ids([album.artist_id], genre_ids) # Add all albums for genre else: if not artist_ids: self._albums = Lp().albums.get_compilations(genre_ids) self._albums += Lp().albums.get_ids(artist_ids, genre_ids) album.set_genre(genre_ids) if track_id in album.tracks_ids: self.context.artist_ids = artist_ids self.context.genre_ids = genre_ids # Shuffle album list if needed self._shuffle_albums() else: # Error self.stop()
def __init__(self, name, radios_manager): """ Init Popover @param name as string @param radios_manager as RadiosManager """ Gtk.Popover.__init__(self) self.connect("map", self._on_map) self.connect("unmap", self._on_unmap) self._name = name self._radios_manager = radios_manager self._start = 0 self._orig_pixbufs = {} self._stack = Gtk.Stack() self._stack.set_transition_duration(1000) self._stack.set_transition_type(Gtk.StackTransitionType.CROSSFADE) self._stack.show() builder = Gtk.Builder() builder.add_from_resource("/org/gnome/Lollypop/RadioPopover.ui") builder.connect_signals(self) self._view = Gtk.FlowBox() self._view.set_selection_mode(Gtk.SelectionMode.NONE) self._view.connect("child-activated", self._on_activate) self._view.set_max_children_per_line(100) self._view.set_property("row-spacing", 10) self._view.show() builder.get_object("viewport").add(self._view) self._name_entry = builder.get_object("name") self._uri_entry = builder.get_object("uri") self._btn_add_modify = builder.get_object("btn_add_modify") self._spinner = builder.get_object("spinner") self._stack.add_named(builder.get_object("spinner-grid"), "spinner") self._stack.add_named(builder.get_object("notfound"), "notfound") self._stack.add_named(builder.get_object("logo"), "logo") self._stack.add_named(builder.get_object("widget"), "widget") self._stack.set_visible_child_name("widget") self.add(self._stack) track = Track() track.set_radio(name, "") rating = RatingWidget(track) rating.show() builder.get_object("widget").attach(rating, 0, 2, 2, 1) if self._name == "": builder.get_object("btn_add_modify").set_label(_("Add")) else: builder.get_object("btn_add_modify").set_label(_("Modify")) builder.get_object("btn_delete").show() self._name_entry.set_text(self._name) url = self._radios_manager.get_url(self._name) if url: self._uri_entry.set_text(url)
def restore_state(self): """ Restore player state """ if Lp().settings.get_value('save-state'): track_id = Lp().settings.get_value('track-id').get_int32() playlist_ids = Lp().settings.get_value('playlist-ids') if playlist_ids and playlist_ids[0] == Type.RADIOS: radios = Radios() track = Track() name = radios.get_name(track_id) url = radios.get_url(name) track.set_radio(name, url) self.load(track) elif Lp().tracks.get_path(track_id) != "": track = Track(track_id) self._load_track(track) if playlist_ids: try: pids = [] for playlist_id in playlist_ids: pids.append(int(playlist_id)) track_ids = [] for playlist_id in playlist_ids: if playlist_id == Type.POPULARS: tracks = Lp().tracks.get_populars() elif playlist_id == Type.RECENTS: tracks = Lp().tracks.get_recently_listened_to() elif playlist_id == Type.NEVER: tracks = Lp().tracks.get_never_listened_to() elif playlist_id == Type.RANDOMS: tracks = Lp().tracks.get_randoms() else: tracks = Lp().playlists.get_tracks_ids( playlist_id) for track_id in tracks: if track_id not in track_ids: track_ids.append(track_id) self.populate_user_playlist_by_tracks(track_ids, pids) except: pass # User set non int in gsettings else: self._albums = Lp().artists.get_albums( track.album_artist_ids) for album_id in self._albums: self.context.genre_ids[album_id] = [] self.context.artist_ids[album_id] = [] self.set_next() self.set_prev() if Lp().settings.get_value('repeat'): self.context.next = NextContext.NONE else: self.context.next = NextContext.STOP_ALL self.emit('current-changed') else: print("Player::restore_state(): track missing")
def _on_play_press_event(self, widget, event): """ Play radio @param: widget as Gtk.EventBox @param: event as Gdk.Event """ url = self._radios_manager.get_url(self._name) if url: track = Track() track.set_radio(self._name, url) Lp().player.load(track)
def _on_album_activated(self, flowbox, child): """ Play album @param flowbox as Gtk.Flowbox @child as Gtk.FlowboxChild """ name = child.get_child().get_name() url = self._radios_manager.get_url(name) if url: track = Track() track.set_radio(name, url) Lp().player.load(track)
def _on_album_activated(self, flowbox, child): """ Play album @param flowbox as Gtk.Flowbox @child as Gtk.FlowboxChild """ name = child.get_child().get_name() uris = self._radios_manager.get_tracks(name) if len(uris) > 0: track = Track() track.set_radio(name, uris[0]) Lp.player.load(track)
def set_albums(self, track_id, artist_id, genre_id): """ Set album list (for next/prev) @param track id as int @param artist id as int @param genre id as int """ # Invalid track if track_id is None: return album = Track(track_id).album self._albums = None ShufflePlayer.reset_history(self) # We are not playing a user playlist anymore self._user_playlist = None self._user_playlist_id = None # We are in all artists if genre_id == Type.ALL or artist_id == Type.ALL: self._albums = Lp.albums.get_compilations(Type.ALL) self._albums += Lp.albums.get_ids() # We are in populars view, add popular albums elif genre_id == Type.POPULARS: self._albums = Lp.albums.get_populars() # We are in recents view, add recent albums elif genre_id == Type.RECENTS: self._albums = Lp.albums.get_recents() # We are in randoms view, add random albums elif genre_id == Type.RANDOMS: self._albums = Lp.albums.get_cached_randoms() # We are in compilation view without genre elif genre_id == Type.COMPILATIONS: self._albums = Lp.albums.get_compilations(None) # Random tracks/albums for artist elif self._shuffle in [Shuffle.TRACKS_ARTIST, Shuffle.ALBUMS_ARTIST]: self._albums = Lp.albums.get_ids(artist_id, genre_id) # Add all albums for genre else: self._albums = Lp.albums.get_compilations(genre_id) self._albums += Lp.albums.get_ids(None, genre_id) album.set_genre(genre_id) if track_id in album.tracks_ids: self.context.position = album.tracks_ids.index(track_id) self.context.genre_id = genre_id # Shuffle album list if needed self._shuffle_albums() else: # Error self.stop()
def _load_web(self, track, play=True): """ Load track url and play it @param track as Track @param play as bool @return True if loading """ if not get_network_available(): # Force widgets to update (spinners) self.emit('current-changed') return False try: from lollypop.web import Web if play: self.emit('loading-changed', True) t = Thread(target=Web.play_track, args=(track, play, self.__set_gv_uri)) t.daemon = True t.start() return True except Exception as e: self._current_track = Track() self.stop() self.emit('current-changed') if Lp().notify is not None: Lp().notify.send(str(e), track.uri) print("PlayerBin::_load_web()", e)
def __init__(self, rowid, num): """ Init row widgets @param rowid as int @param num as int @param show loved as bool """ # We do not use Gtk.Builder for speed reasons Gtk.ListBoxRow.__init__(self) self.set_sensitive(False) self._track = Track(rowid) self._number = num self._indicator = IndicatorWidget(self._track.id) self.set_indicator(Lp().player.current_track.id == self._track.id, utils.is_loved(self._track.id)) self.set_sensitive(True) self._row_widget = Gtk.EventBox() self._row_widget.connect('button-press-event', self._on_button_press) self._row_widget.connect('enter-notify-event', self._on_enter_notify) self._grid = Gtk.Grid() self._grid.set_column_spacing(5) self._row_widget.add(self._grid) self._title_label = Gtk.Label.new(self._track.formated_name()) self._title_label.set_use_markup(True) self._title_label.set_property('has-tooltip', True) self._title_label.connect('query-tooltip', self._on_title_query_tooltip) self._title_label.set_property('hexpand', True) self._title_label.set_property('halign', Gtk.Align.START) self._title_label.set_ellipsize(Pango.EllipsizeMode.END) self._duration_label = Gtk.Label.new( seconds_to_string(self._track.duration)) self._duration_label.get_style_context().add_class('dim-label') self._num_label = Gtk.Label() self._num_label.set_ellipsize(Pango.EllipsizeMode.END) self._num_label.set_property('valign', Gtk.Align.CENTER) self._num_label.set_width_chars(4) self._num_label.get_style_context().add_class('dim-label') self.update_num_label() self._menu_image = Gtk.Image.new() self._menu_image.set_opacity(0.2) self._menu_button = Gtk.Button.new() self._menu_button.set_relief(Gtk.ReliefStyle.NONE) self._menu_button.get_style_context().add_class('menu-button') self._menu_button.get_style_context().add_class('track-menu-button') self._menu_button.set_image(self._menu_image) self._menu_button.connect('clicked', self._on_button_clicked) self._grid.add(self._num_label) self._grid.add(self._title_label) self._grid.add(self._duration_label) # TODO Remove this later if Gtk.get_minor_version() > 16: self._grid.add(self._menu_button) else: self.connect('map', self._on_map) self.add(self._row_widget) self.get_style_context().add_class('trackrow')
def load_external(self, uri, name=''): """ Load external tracks @param uri as str @param name as string """ track = Track() track.set_album_artists([name]) track.set_uri(uri) if track.uri.startswith('file://'): track.id = Type.EXTERNALS track.name = GLib.path_get_basename(GLib.filename_from_uri(uri)[0]) else: track.name = uri track.id = Type.RADIOS self._external_tracks.append(track)
def _sticker(self, cmd_args): """ Send stickers @syntax sticker [get|set] song rating @param args as str @return msg as str """ args = self._get_args(cmd_args) msg = "" if args[0].find("get song ") != -1 and\ args[2].find("rating") != -1: track_id = Lp().tracks.get_id_by_path(args[1]) track = Track(track_id) msg = "sticker: rating=%s\n" % int(track.get_popularity()*2) elif args[0].find("set song") != -1 and\ args[2].find("rating") != -1: track_id = Lp().tracks.get_id_by_path(args[1]) track = Track(track_id) track.set_popularity(int(args[3])/2) return msg
def prev(self): """ Prev track based on history @return Track """ track_id = None if self._shuffle == Shuffle.TRACKS or self.__is_party: if self.shuffle_has_prev: track_id = self.__history.get_prev().get_value() else: track_id = self._current_track.id return Track(track_id)
def _on_activated(self, widget, track_id): """ On track activation, play track @param widget as TracksWidget @param track id as int """ # Play track with no album, force repeat on track if self._button_state & Gdk.ModifierType.SHIFT_MASK: Lp().player.clear_albums() Lp().player.load(Track(track_id)) else: Lp().player.context.next = NextContext.NONE if not Lp().player.is_party(): if len(self._artist_ids) > 1: Lp().player.set_albums(track_id, self._artist_ids, self._album.genre_ids) else: Lp().player.set_albums(track_id, [], self._album.genre_ids) Lp().player.load(Track(track_id)) if self._button_state & Gdk.ModifierType.CONTROL_MASK: Lp().player.context.next = NextContext.STOP_TRACK
def _sticker(self, cmd_args): """ Send stickers @syntax sticker [get|set] song rating @param args as str @return msg as str """ args = self._get_args(cmd_args) msg = "" print(args) if args[0] == "get" and args[1] == "song" and\ args[3] == "rating": track_id = Lp().tracks.get_id_by_path(args[2]) track = Track(track_id) msg = "sticker: rating=%s\n" % int((track.get_popularity()-0.5)*2) elif args[0] == "set" and args[1] == "song" and\ args[3] == "rating": track_id = Lp().tracks.get_id_by_path(args[2]) track = Track(track_id) track.set_popularity(int(args[4])/2) return msg
def next(self, force): """ Next Track @param force as bool @return Track """ track = Track() if force: current_track = self._next_track else: current_track = self._current_track if self._user_playlist and\ current_track.id in self._user_playlist: idx = self._user_playlist.index(current_track.id) if idx + 1 >= len(self._user_playlist): self._next_context = NextContext.STOP idx = 0 else: idx += 1 track = Track(self._user_playlist[idx]) return track
def update_headers(self, prev_album_id=None): """ Update headers @param previous album id as int """ for child in self.get_children(): track = Track(child.id) if track.album.id == prev_album_id: child.show_headers(False) else: child.show_headers(True) prev_album_id = track.album.id
def set_id(self, rowid, is_track): """ Set row id @param rowid as int @param is track as bool """ self._id = rowid self._is_track = is_track if self._is_track: self._title.set_text("♫ " + Track(self._id).name) else: self._title.set_text(Album(self._id).name)
def _remove_from_device(self, playlists): """ Delete files not available in playlist """ track_uris = [] tracks_ids = [] # Get tracks ids for playlist in playlists: tracks_ids += Lp().playlists.get_tracks_ids(playlist) # Get tracks uris for track_id in tracks_ids: if not self._syncing: self._fraction = 1.0 self._in_thread = False return track = Track(track_id) album_name = GLib.uri_escape_string(track.album_name.lower(), "", False) artist_name = GLib.uri_escape_string(track.artist.lower(), "", False) album_uri = "%s/tracks/%s_%s" % (self._uri, artist_name, album_name) track_name = GLib.uri_escape_string(GLib.basename(track.path), "", False) # Check extension, if not mp3, convert ext = os.path.splitext(track.path)[1] if ext != ".mp3" and self._convert: track_name = track_name.replace(ext, ".mp3") on_disk = Gio.File.new_for_path(track.path) info = on_disk.query_info('time::modified', Gio.FileQueryInfoFlags.NONE, None) # Prefix track with mtime to make sure updating it later mtime = info.get_attribute_as_string('time::modified') dst_uri = "%s/%s_%s" % (album_uri, mtime, track_name) track_uris.append(dst_uri) on_mtp_files = self._get_tracks_files() # Delete file on device and not in playlists for uri in on_mtp_files: if not self._syncing: self._fraction = 1.0 self._in_thread = False return if uri not in track_uris and uri not in self._copied_art_uris: to_delete = Gio.File.new_for_uri(uri) self._retry(to_delete.delete, (None, )) self._done += 1 self._fraction = self._done / self._total
def next(self): """ Next shuffle track @return Track """ track = Track() if self._shuffle == Shuffle.TRACKS or self.__is_party: if self.shuffle_has_next: track = self.__history.next.value elif self._albums or (self._playlist_tracks and self._shuffle == Shuffle.TRACKS): track = self.__get_next() return track
def update_playlist(): # Save playlist in db only if one playlist visible if len(self.__playlist_ids) == 1 and self.__playlist_ids[0] >= 0: Lp().playlists.clear(self.__playlist_ids[0], False) tracks = [] for track_id in self.__tracks_left + self.__tracks_right: tracks.append(Track(track_id)) Lp().playlists.add_tracks(self.__playlist_ids[0], tracks, False) if not (set(self.__playlist_ids) - set(Lp().player.get_user_playlist_ids())): Lp().player.update_user_playlist(self.__tracks_left + self.__tracks_right)
def next(self): """ Next Track @return Track """ repeat = App().settings.get_enum("repeat") if repeat == Repeat.TRACK: return self._current_playback_track track = Track() current_track_id = self._current_playback_track.id track_ids = self.playlist_track_ids if track_ids and current_track_id in track_ids: idx = track_ids.index(current_track_id) if idx + 1 >= len(track_ids): if repeat == Repeat.ALL: idx = 0 else: return Track() else: idx += 1 track = self._playlist_tracks[idx] return track
def prev(self): """ Prev track id @return Track """ repeat = App().settings.get_enum("repeat") if repeat == Repeat.TRACK: return self._current_playback_track track = Track() current_track_id = self._current_playback_track.id track_ids = self.playlist_track_ids if track_ids and current_track_id in track_ids: idx = track_ids.index(current_track_id) if idx - 1 < 0: if repeat == Repeat.ALL: idx = len(track_ids) - 1 else: return Track() else: idx -= 1 track = self._playlist_tracks[idx] return track
def restore_state(self): """ Restore player state """ try: if Lp().settings.get_value('save-state'): track_id = load(open(DataPath + "/track_id.bin", "rb")) playlist_ids = load(open(DataPath + "/playlist_ids.bin", "rb")) if playlist_ids and playlist_ids[0] == Type.RADIOS: radios = Radios() track = Track() name = radios.get_name(track_id) url = radios.get_url(name) track.set_radio(name, url) self.load(track) elif Lp().tracks.get_uri(track_id) != "": track = Track(track_id) if Lp().notify is not None: Lp().notify.inhibit() self._load_track(track) # We set this initial state # because seek while failed otherwise self.pause() if playlist_ids: pids = [] for playlist_id in playlist_ids: pids.append(int(playlist_id)) track_ids = [] for playlist_id in playlist_ids: if playlist_id == Type.POPULARS: tracks = Lp().tracks.get_populars() elif playlist_id == Type.RECENTS: tracks = Lp().tracks.get_recently_listened_to() elif playlist_id == Type.NEVER: tracks = Lp().tracks.get_never_listened_to() elif playlist_id == Type.RANDOMS: tracks = Lp().tracks.get_randoms() else: tracks = Lp().playlists.get_track_ids( playlist_id) for track_id in tracks: if track_id not in track_ids: track_ids.append(track_id) self.populate_user_playlist_by_tracks( track_ids, pids) else: self._albums = load( open(DataPath + "/albums.bin", "rb")) self.shuffle_albums(True) self._context.genre_ids = load( open(DataPath + "/genre_ids.bin", "rb")) self._context.artist_ids = load( open(DataPath + "/artist_ids.bin", "rb")) self.set_next() self.set_prev() else: print("Player::restore_state(): track missing") except Exception as e: print("Player::restore_state()", e)
def __init__(self, object): """ Init edit menu @param object as Album/Track """ # Ignore genre_ids/artist_ids if isinstance(object, Album): obj = Album(object.id) else: obj = Track(object.id) BaseMenu.__init__(self, obj) if App().art.tag_editor: self.__set_edit_actions()
def _findadd(self, cmd_args): """ Find tracks and add them to playlist @syntax findadd filter value @param args as str @return msg as str """ tracks = [] for track_id in self._find_tracks(cmd_args): tracks.append(Track(track_id)) if tracks: Lp().playlists.add_tracks(Type.MPD, tracks, False) return ""
def _clear(self, cmd_args): """ Clear mpd playlist @syntax clear @param args as str @return msg as str """ Lp().playlists.clear(Type.MPD, False) Lp().player.set_user_playlist_by_id(Type.NONE) GLib.idle_add(Lp().player.stop) Lp().player.current_track = Track() GLib.idle_add(Lp().player.emit, 'current-changed') return ""
def load_external(self, filename, name=''): """ Load external tracks @param filename as str @param name as string """ try: uri = GLib.filename_to_uri(filename) except: uri = filename track = Track() track.name = GLib.path_get_basename(GLib.filename_from_uri(uri)[0]) track.set_album_artists([name]) track.set_uri(uri) if track.uri.startswith('file://'): track.id = Type.EXTERNALS else: track.id = Type.RADIOS self._external_tracks.append(track)
def _play_search(self, object_id=None, is_track=True): """ Play tracks based on search @param started object id as int @param is track as bool """ tracks = [] track_id = None for child in self._view.get_children(): if child.is_track: tracks.append(Track(child.id)) else: album_tracks = Lp().albums.get_tracks(child.id, None) if not is_track and child.id == object_id and album_tracks: track_id = album_tracks[0] for tid in album_tracks: tracks.append(Track(tid)) if tracks: if object_id is not None and is_track: track_id = object_id elif track_id is None: track_id = tracks[0].id GLib.idle_add(self._set_user_playlist, tracks, track_id)
def __get_playlists_random(self): """ Return a track from current playlist @return Track """ for track in sorted(self._playlist_tracks, key=lambda *args: random.random()): # Ignore current track, not an issue if playing one track # in shuffle because LinearPlayer will handle next() if track != App().player.current_track and ( track.album not in self.__already_played_tracks.keys() or track not in self.__already_played_tracks[track.album]): return track return Track()
def __on_loved_playlist_changed(self, widget, playlist_id, uri, *ignore): """ Updates the loved icon @param playlist as Playlist @param playlist id as int @param track id as int """ if playlist_id != Type.LOVED: return track_id = App().tracks.get_id_by_uri(uri) for row in self.get_children(): if track_id == row.track.id: row.set_indicator(track_id == App().player.current_track.id, Track(track_id).loved)
def next(self): """ Next shuffle track @return Track """ track_id = None if self._shuffle in [Shuffle.TRACKS, Shuffle.TRACKS_ARTIST] or\ self._is_party: if self._history is not None and \ self._history.has_next(): track_id = self._history.get_next().get_value() elif self._albums is not None: track_id = self._shuffle_next() return Track(track_id)
def prev(self): """ Prev track based on history @return Track """ track_id = None if self._shuffle in [Shuffle.TRACKS, Shuffle.TRACKS_ARTIST] or\ self._is_party: if self._history is not None and \ self._history.has_prev(): track_id = self._history.get_prev().get_value() else: track_id = self.current_track.id return Track(track_id)
def set_row(self, row, track_id, pos, show_cover=False): """ Set row content @param row as Row @param track id as int @param pos as position @param show cover as bool """ track = Track(track_id) row.show_indicator(Lp().player.current_track.id == track_id, utils.is_loved(track_id)) row.set_number(pos) self._update_pos_label(row, track_id) row.set_title_label(track.formated_name()) row.set_duration_label(seconds_to_string(track.duration)) row.set_id(track_id) if show_cover: surface = Lp().art.get_album_artwork( track.album, ArtSize.MEDIUM*row.get_scale_factor()) row.set_cover(surface, track.album.name) del surface row.show_header(True)
def _on_play_all_press_event(self, widget, event): """ Play album with context @param: widget as Gtk.EventBox @param: event as Gdk.Event """ self._show_append(False) if Lp().player.is_party(): Lp().player.set_party(False) track = Track(self._album.tracks_ids[0]) Lp().player.load(track) Lp().player.set_albums(track.id, self._artist_ids, self._album.genre_ids) return True
def load_smart(): tracks = [] request = App().playlists.get_smart_sql(playlist_ids[0]) ids = App().db.execute(request) for id in ids: track = Track(id) # Smart playlist may report invalid tracks # An album always have an artist so check # object is valid. Others Lollypop widgets assume # objects are valid if not track.album.artist_ids: continue tracks.append(track) return tracks
def __init__(self): """ Init base player variables """ # In case of multiple subclassing, # do not init variables for every subclass if not hasattr(self, '_albums'): GObject.GObject.__init__(self) self._base_init = True # A user playlist used as current playlist self._user_playlist = None # Used by shuffle tracks to restore user playlist before shuffle self._user_playlist_backup = None self.current_track = Track() self.next_track = Track() self.prev_track = Track() self.context = PlayContext() # Albums in current playlist self._albums = None # Current shuffle mode self._shuffle = Lp.settings.get_enum('shuffle') # For tracks from the cmd line self._external_tracks = []
def next(self): """ Next shuffle track @return Track """ track_id = None if self._shuffle == Shuffle.TRACKS or self.__is_party: if self.shuffle_has_next: track_id = self.__history.get_next().get_value() elif self._albums: track_id = self.__shuffle_next() else: track_id = self._current_track.id return Track(track_id)
def _on_play_clicked(self, widget): """ Play artist albums """ try: if Lp().player.is_party: Lp().player.set_party(False) album_id = Lp().albums.get_ids(self._artist_ids, self._genre_ids)[0] track = Track(Album(album_id).track_ids[0]) Lp().player.load(track) Lp().player.set_albums(track.id, self._artist_ids, self._genre_ids) self.__set_add_icon() except: pass # Artist not available anymore for this context
def prev(self): """ Return prev radio name, uri @return Track """ track = Track() if self._current_track.id != Type.RADIOS or not self.__radios: return track i = len(self.__radios) - 1 for (name, url) in reversed(self.__radios): i -= 1 if self._current_track.album_artists[0] == name: break # Get prev radio if i < 0: i = len(self.__radios) - 1 name = self.__radios[i][0] url = self.__radios[i][1] if url: track.set_radio(name, url) return track
def next(self): """ Return next radio name, uri @return Track """ track = Track() if self._current_track.id != Type.RADIOS or not self.__radios: return track i = 0 for (name, url) in self.__radios: i += 1 if self._current_track.album_artists[0] == name: break # Get next radio if i >= len(self.__radios): i = 0 name = self.__radios[i][0] url = self.__radios[i][1] if url: track.set_radio(name, url) return track
def load(): tracks = [] all_ids = [] for playlist_id in playlist_ids: if playlist_id == Type.LOVED: ids = App().tracks.get_loved_track_ids() else: ids = App().playlists.get_track_ids(playlist_id) for id in ids: if id in all_ids: continue all_ids.append(id) track = Track(id) tracks.append(track) return tracks
def restore_state(self): """ Restore player state """ track_id = Lp().settings.get_value('track-id').get_int32() if Lp().settings.get_value('save-state') and track_id > 0: path = Lp().tracks.get_path(track_id) if path != "": self._load_track(Track(track_id)) self.set_albums(track_id, [Type.ALL], [Type.ALL]) self.set_next() self.set_prev() self.emit('current-changed') else: print("Player::restore_state(): track missing")
def load_external(self, uri, name=''): """ Load external tracks @param uri as str @param name as string """ try: uri = GLib.filename_to_uri(uri) except: pass track = Track() track.set_album_artists([name]) track.set_uri(uri) if track.uri.startswith('file://'): track.id = Type.EXTERNALS else: track.id = Type.RADIOS self._external_tracks.append(track)
def set_albums(self, track_id, artist_ids, genre_ids): """ Set album list (for next/prev) @param track id as int @param artist id as int @param genre id as int """ # Invalid track if track_id is None: return album = Track(track_id).album album.set_genre(genre_ids) self._albums = [] self.context.genre_ids = {} ShufflePlayer.reset_history(self) # We are not playing a user playlist anymore self._user_playlist = [] self._user_playlist_ids = [] # We are in all artists if (genre_ids and genre_ids[0] == Type.ALL) or\ (artist_ids and artist_ids[0] == Type.ALL): if artist_ids and artist_ids[0] != Type.ALL: self._albums += Lp().albums.get_ids(artist_ids) else: if Lp().settings.get_value('show-compilations'): self._albums = Lp().albums.get_compilations() self._albums += Lp().albums.get_ids() # We are in populars view, add popular albums elif genre_ids and genre_ids[0] == Type.POPULARS: self._albums = Lp().albums.get_populars() # We are in recents view, add recent albums elif genre_ids and genre_ids[0] == Type.RECENTS: self._albums = Lp().albums.get_recents() # We are in randoms view, add random albums elif genre_ids and genre_ids[0] == Type.RANDOMS: self._albums = Lp().albums.get_cached_randoms() # We are in compilation view without genre elif genre_ids and genre_ids[0] == Type.COMPILATIONS: self._albums = Lp().albums.get_compilations() # Add albums for artists/genres else: # If we are not in compilation view and show compilation is on, # add compilations if (not artist_ids or artist_ids[0] != Type.COMPILATIONS) and\ Lp().settings.get_value('show-compilations'): self._albums += Lp().albums.get_compilations(genre_ids) self._albums += Lp().albums.get_ids(artist_ids, genre_ids) if Lp().settings.get_value('repeat'): self.context.next = NextContext.NONE else: self.context.next = NextContext.STOP_ALL # We do not store genre_ids for ALL/POPULARS/... if genre_ids and genre_ids[0] < 0: genre_ids = [] # Set context for each album for album_id in self._albums: self.context.genre_ids[album_id] = genre_ids # Shuffle album list if needed self.shuffle_albums(True)
class BinPlayer(BasePlayer): """ Gstreamer bin player """ def __init__(self): """ Init playbin """ Gst.init(None) BasePlayer.__init__(self) self.__codecs = Codecs() self._playbin = self.__playbin1 = Gst.ElementFactory.make( 'playbin', 'player') self.__playbin2 = Gst.ElementFactory.make('playbin', 'player') self.__preview = None self._plugins = self._plugins1 = PluginsPlayer(self.__playbin1) self._plugins2 = PluginsPlayer(self.__playbin2) self._playbin.connect('notify::volume', self.__on_volume_changed) for playbin in [self.__playbin1, self.__playbin2]: flags = playbin.get_property("flags") flags &= ~GstPlayFlags.GST_PLAY_FLAG_VIDEO playbin.set_property('flags', flags) playbin.set_property('buffer-size', 5 << 20) playbin.set_property('buffer-duration', 10 * Gst.SECOND) playbin.connect('about-to-finish', self.__on_stream_about_to_finish) bus = playbin.get_bus() bus.add_signal_watch() bus.connect('message::error', self.__on_bus_error) bus.connect('message::eos', self.__on_bus_eos) bus.connect('message::element', self.__on_bus_element) bus.connect('message::stream-start', self._on_stream_start) bus.connect("message::tag", self.__on_bus_message_tag) self._start_time = 0 @property def preview(self): """ Get a preview bin @return Gst.Element """ if self.__preview is None: self.__preview = Gst.ElementFactory.make('playbin', 'player') PluginsPlayer(self.__preview) self.set_preview_output() return self.__preview def set_preview_output(self): """ Set preview output """ if self.__preview is not None: output = Lp().settings.get_value('preview-output').get_string() pulse = Gst.ElementFactory.make('pulsesink', 'output') if pulse is None: pulse = Gst.ElementFactory.make('alsasink', 'output') if pulse is not None: pulse.set_property('device', output) self.__preview.set_property('audio-sink', pulse) def get_status(self): """ Playback status @return Gstreamer state """ ok, state, pending = self._playbin.get_state(Gst.CLOCK_TIME_NONE) if ok == Gst.StateChangeReturn.ASYNC: state = pending elif (ok != Gst.StateChangeReturn.SUCCESS): state = Gst.State.NULL return state def load(self, track): """ Stop current track, load track id and play it @param track as Track """ if self._crossfading and\ self._current_track.id is not None and\ self.is_playing and\ self._current_track.id != Type.RADIOS: duration = Lp().settings.get_value('mix-duration').get_int32() self.__do_crossfade(duration, track, False) else: self.__load(track) def play(self): """ Change player state to PLAYING """ # No current playback, song in queue if self._current_track.id is None: if self._next_track.id is not None: self.load(self._next_track) else: self._playbin.set_state(Gst.State.PLAYING) self.emit("status-changed") def pause(self): """ Change player state to PAUSED """ if self._current_track.id == Type.RADIOS: self._playbin.set_state(Gst.State.NULL) else: self._playbin.set_state(Gst.State.PAUSED) self.emit("status-changed") def stop(self): """ Change player state to STOPPED """ self._playbin.set_state(Gst.State.NULL) self.emit("status-changed") def stop_all(self): """ Stop all bins, lollypop should quit now """ # Stop self.__playbin1.set_state(Gst.State.NULL) self.__playbin2.set_state(Gst.State.NULL) def play_pause(self): """ Set playing if paused Set paused if playing """ if self.is_playing: self.pause() else: self.play() def seek(self, position): """ Seek current track to position @param position as seconds """ if self.locked or self._current_track.id is None: return # Seems gstreamer doesn't like seeking to end, sometimes # doesn't go to next track if position >= self._current_track.duration: self.next() else: self._playbin.seek_simple(Gst.Format.TIME, Gst.SeekFlags.FLUSH | Gst.SeekFlags.KEY_UNIT, position * Gst.SECOND) self.emit("seeked", position) @property def is_playing(self): """ True if player is playing @return bool """ ok, state, pending = self._playbin.get_state(Gst.CLOCK_TIME_NONE) if ok == Gst.StateChangeReturn.ASYNC: return pending == Gst.State.PLAYING elif ok == Gst.StateChangeReturn.SUCCESS: return state == Gst.State.PLAYING else: return False @property def position(self): """ Return bin playback position @HACK handle crossefade here, as we know we're going to be called every seconds @return position as int """ position = self._playbin.query_position(Gst.Format.TIME)[1] / 1000 if self._crossfading and self._current_track.duration > 0: duration = self._current_track.duration - position / 1000000 if duration < Lp().settings.get_value('mix-duration').get_int32(): self.__do_crossfade(duration) return position * 60 @property def current_track(self): """ Current track """ return self._current_track @property def volume(self): """ Return player volume rate @return rate as double """ return self._playbin.get_volume(GstAudio.StreamVolumeFormat.CUBIC) def set_volume(self, rate): """ Set player volume rate @param rate as double """ self.__playbin1.set_volume(GstAudio.StreamVolumeFormat.CUBIC, rate) self.__playbin2.set_volume(GstAudio.StreamVolumeFormat.CUBIC, rate) self.emit('volume-changed') def next(self): """ Go next track """ pass ####################### # PROTECTED # ####################### def _load_track(self, track, init_volume=True): """ Load track @param track as Track @param init volume as bool @return False if track not loaded """ if self.__need_to_stop(): return False if init_volume: self._plugins.volume.props.volume = 1.0 debug("BinPlayer::_load_track(): %s" % track.uri) try: self._current_track = track if track.is_web: loaded = self._load_web(track) # If track not loaded, go next if not loaded: self.set_next() GLib.timeout_add(500, self.__load, self.next_track, init_volume) return False # Return not loaded as handled by load_web() else: self._playbin.set_property('uri', track.uri) except Exception as e: # Gstreamer error print("BinPlayer::_load_track(): ", e) return False return True def _load_web(self, track, play=True): """ Load track url and play it @param track as Track @param play as bool @return True if loading """ if not get_network_available(): # Force widgets to update (spinners) self.emit('current-changed') return False try: from lollypop.web import Web if play: self.emit('loading-changed', True) t = Thread(target=Web.play_track, args=(track, play, self.__set_gv_uri)) t.daemon = True t.start() return True except Exception as e: self._current_track = Track() self.stop() self.emit('current-changed') if Lp().notify is not None: Lp().notify.send(str(e), track.uri) print("PlayerBin::_load_web()", e) def _scrobble(self, finished, finished_start_time): """ Scrobble on lastfm @param finished as Track @param finished_start_time as int """ # Last.fm policy if finished.duration < 30: return # Scrobble on lastfm if Lp().lastfm is not None: artists = ", ".join(finished.artists) played = time() - finished_start_time # We can scrobble if the track has been played # for at least half its duration, or for 4 minutes if played >= finished.duration / 2 or played >= 240: Lp().lastfm.do_scrobble(artists, finished.album_name, finished.title, int(finished_start_time)) def _on_stream_start(self, bus, message): """ On stream start Emit "current-changed" to notify others components @param bus as Gst.Bus @param message as Gst.Message """ self._start_time = time() debug("Player::_on_stream_start(): %s" % self._current_track.uri) self.emit('current-changed') # Update now playing on lastfm if Lp().lastfm is not None and self._current_track.id >= 0: artists = ", ".join(self._current_track.artists) Lp().lastfm.now_playing(artists, self._current_track.album_name, self._current_track.title, int(self._current_track.duration)) if not Lp().scanner.is_locked(): Lp().tracks.set_listened_at(self._current_track.id, int(time())) ####################### # PRIVATE # ####################### def __update_current_duration(self, reader, track): """ Update current track duration @param reader as TagReader @param track id as int """ try: duration = reader.get_info(track.uri).get_duration() / 1000000000 if duration != track.duration and duration > 0: Lp().tracks.set_duration(track.id, duration) self._current_track.set_duration(duration) GLib.idle_add(self.emit, 'duration-changed', track.id) except: pass def __load(self, track, init_volume=True): """ Stop current track, load track id and play it If was playing, do not use play as status doesn't changed @param track as Track @param init volume as bool """ was_playing = self.is_playing self._playbin.set_state(Gst.State.NULL) if self._load_track(track, init_volume): if was_playing: self._playbin.set_state(Gst.State.PLAYING) else: self.play() def __volume_up(self, playbin, plugins, duration): """ Make volume going up smoothly @param playbin as Gst.Bin @param plugins as PluginsPlayer @param duration as int """ # We are not the active playbin, stop all if self._playbin != playbin: return if duration > 0: vol = plugins.volume.props.volume steps = duration / 0.25 vol_up = (1.0 - vol) / steps rate = vol + vol_up if rate < 1.0: plugins.volume.props.volume = rate GLib.timeout_add(250, self.__volume_up, playbin, plugins, duration - 0.25) else: plugins.volume.props.volume = 1.0 else: plugins.volume.props.volume = 1.0 def __volume_down(self, playbin, plugins, duration): """ Make volume going down smoothly @param playbin as Gst.Bin @param plugins as PluginsPlayer @param duration as int """ # We are again the active playbin, stop all if self._playbin == playbin: return if duration > 0: vol = plugins.volume.props.volume steps = duration / 0.25 vol_down = vol / steps rate = vol - vol_down if rate > 0: plugins.volume.props.volume = rate GLib.timeout_add(250, self.__volume_down, playbin, plugins, duration - 0.25) else: plugins.volume.props.volume = 0.0 playbin.set_state(Gst.State.NULL) else: plugins.volume.props.volume = 0.0 playbin.set_state(Gst.State.NULL) def __do_crossfade(self, duration, track=None, next=True): """ Crossfade tracks @param duration as int @param track as Track @param next as bool """ # No cossfading if we need to stop if self.__need_to_stop() and next: return if track is None: self._scrobble(self._current_track, self._start_time) # Increment popularity if not Lp().scanner.is_locked(): Lp().tracks.set_more_popular(self._current_track.id) # In party mode, linear popularity if self.is_party: pop_to_add = 1 # In normal mode, based on tracks count else: pop_to_add = int(Lp().albums.max_count / Lp().albums.get_tracks_count( self._current_track.album_id)) Lp().albums.set_more_popular(self._current_track.album_id, pop_to_add) GLib.idle_add(self.__volume_down, self._playbin, self._plugins, duration) if self._playbin == self.__playbin2: self._playbin = self.__playbin1 self._plugins = self._plugins1 else: self._playbin = self.__playbin2 self._plugins = self._plugins2 if track is not None: self.__load(track, False) self._plugins.volume.props.volume = 0 GLib.idle_add(self.__volume_up, self._playbin, self._plugins, duration) elif next and self._next_track.id is not None: self.__load(self._next_track, False) self._plugins.volume.props.volume = 0 GLib.idle_add(self.__volume_up, self._playbin, self._plugins, duration) elif self._prev_track.id is not None: self.__load(self._prev_track, False) self._plugins.volume.props.volume = 0 GLib.idle_add(self.__volume_up, self._playbin, self._plugins, duration) def __need_to_stop(self): """ Return True if playback needs to stop @return bool """ stop = False playback = Lp().settings.get_enum('playback') if playback == NextContext.STOP: if not self._albums or playback == self._next_context: stop = True return stop and self.is_playing def __on_volume_changed(self, playbin, sink): """ Update volume @param playbin as Gst.Bin @param sink as Gst.Sink """ if playbin == self.__playbin1: vol = self.__playbin1.get_volume(GstAudio.StreamVolumeFormat.CUBIC) self.__playbin2.set_volume(GstAudio.StreamVolumeFormat.CUBIC, vol) else: vol = self.__playbin2.get_volume(GstAudio.StreamVolumeFormat.CUBIC) self.__playbin1.set_volume(GstAudio.StreamVolumeFormat.CUBIC, vol) self.emit('volume-changed') def __on_bus_message_tag(self, bus, message): """ Read tags from stream @param bus as Gst.Bus @param message as Gst.Message """ # Some radio streams send message tag every seconds! changed = False if self._current_track.persistent == DbPersistent.INTERNAL and\ (self._current_track.id >= 0 or self._current_track.duration > 0.0): return debug("Player::__on_bus_message_tag(): %s" % self._current_track.uri) reader = TagReader() # Update duration of non internals if self._current_track.persistent != DbPersistent.INTERNAL: t = Thread(target=self.__update_current_duration, args=(reader, self._current_track)) t.daemon = True t.start() return tags = message.parse_tag() title = reader.get_title(tags, '') if title != '' and self._current_track.name != title: self._current_track.name = title changed = True if self._current_track.name == '': self._current_track.name = self._current_track.uri changed = True artists = reader.get_artists(tags) if artists != '' and self._current_track.artists != artists: self._current_track.artists = artists.split(',') changed = True if not self._current_track.artists: self._current_track.artists = self._current_track.album_artists changed = True if self._current_track.id == Type.EXTERNALS: (b, duration) = self._playbin.query_duration(Gst.Format.TIME) if b: self._current_track.duration = duration/1000000000 # We do not use tagreader as we need to check if value is None self._current_track.album_name = tags.get_string_index('album', 0)[1] if self._current_track.album_name is None: self._current_track.album_name = '' self._current_track.genres = reader.get_genres(tags).split(',') changed = True if changed: self.emit('current-changed') def __on_bus_element(self, bus, message): """ Set elements for missings plugins @param bus as Gst.Bus @param message as Gst.Message """ if GstPbutils.is_missing_plugin_message(message): self.__codecs.append(message) def __on_bus_error(self, bus, message): """ Handle first bus error, ignore others @param bus as Gst.Bus @param message as Gst.Message """ debug("Error playing: %s" % self._current_track.uri) Lp().window.pulse(False) if self.__codecs.is_missing_codec(message): self.__codecs.install() Lp().scanner.stop() elif Lp().notify is not None: Lp().notify.send(message.parse_error()[0].message) self.emit('current-changed') return True def __on_bus_eos(self, bus, message): """ On end of stream, stop playback go next otherwise """ debug("Player::__on_bus_eos(): %s" % self._current_track.uri) if self._playbin.get_bus() == bus: self.stop() self._next_context = NextContext.NONE if self._next_track.id is not None: self._load_track(self._next_track) self.emit('current-changed') def __on_stream_about_to_finish(self, playbin): """ When stream is about to finish, switch to next track without gap @param playbin as Gst bin """ debug("Player::__on_stream_about_to_finish(): %s" % playbin) # Don't do anything if crossfade on, track already changed if self._crossfading: return if self._current_track.id == Type.RADIOS: return self._scrobble(self._current_track, self._start_time) # Increment popularity if not Lp().scanner.is_locked() and self._current_track.id >= 0: Lp().tracks.set_more_popular(self._current_track.id) # In party mode, linear popularity if self.is_party: pop_to_add = 1 # In normal mode, based on tracks count else: # Some users report an issue where get_tracks_count() return 0 # See issue #886 # Don't understand how this can happen! count = Lp().albums.get_tracks_count( self._current_track.album_id) if count: pop_to_add = int(Lp().albums.max_count / count) else: pop_to_add = 1 Lp().albums.set_more_popular(self._current_track.album_id, pop_to_add) if self._next_track.id is not None: self._load_track(self._next_track) def __set_gv_uri(self, uri, track, play): """ Play uri for io @param uri as str @param track as Track @param play as bool """ track.set_uri(uri) if play: self.load(track)
class Row(Gtk.ListBoxRow): """ A row """ def __init__(self, rowid, num): """ Init row widgets @param rowid as int @param num as int @param show loved as bool """ # We do not use Gtk.Builder for speed reasons Gtk.ListBoxRow.__init__(self) self._track = Track(rowid) self._number = num self._indicator = IndicatorWidget(self._track.id) self.set_indicator(Lp().player.current_track.id == self._track.id, utils.is_loved(self._track.id)) self._row_widget = Gtk.EventBox() self._row_widget.connect('button-press-event', self._on_button_press) self._row_widget.connect('enter-notify-event', self._on_enter_notify) self._grid = Gtk.Grid() self._grid.set_column_spacing(5) self._row_widget.add(self._grid) self._title_label = Gtk.Label.new(self._track.formated_name()) self._title_label.set_use_markup(True) self._title_label.set_property('has-tooltip', True) self._title_label.connect('query-tooltip', self._on_title_query_tooltip) self._title_label.set_property('hexpand', True) self._title_label.set_property('halign', Gtk.Align.START) self._title_label.set_ellipsize(Pango.EllipsizeMode.END) self._duration_label = Gtk.Label.new( seconds_to_string(self._track.duration)) self._duration_label.get_style_context().add_class('dim-label') self._num_label = Gtk.Label() self._num_label.set_ellipsize(Pango.EllipsizeMode.END) self._num_label.set_property('valign', Gtk.Align.CENTER) self._num_label.set_width_chars(4) self._num_label.get_style_context().add_class('dim-label') self.update_num_label() self._menu_button = Gtk.Button.new() # Here a hack to make old Gtk version support min-height css attribute # min-height = 24px, borders = 2px, we set directly on stack # min-width = 24px, borders = 2px, padding = 8px self._menu_button.set_size_request(34, 26) self._menu_button.set_relief(Gtk.ReliefStyle.NONE) self._menu_button.get_style_context().add_class('menu-button') self._menu_button.get_style_context().add_class('track-menu-button') self._grid.add(self._num_label) self._grid.add(self._title_label) self._grid.add(self._duration_label) # TODO Remove this later if Gtk.get_minor_version() > 16: self._grid.add(self._menu_button) else: self.connect('map', self._on_map) self.add(self._row_widget) self.get_style_context().add_class('trackrow') def set_indicator(self, playing, loved): """ Show indicator @param widget name as str @param playing as bool @param loved as bool """ self._indicator.clear() if playing: self.get_style_context().remove_class('trackrow') self.get_style_context().add_class('trackrowplaying') if loved: self._indicator.play_loved() else: self._indicator.play() else: self.get_style_context().remove_class('trackrowplaying') self.get_style_context().add_class('trackrow') if loved: self._indicator.loved() else: self._indicator.empty() def set_number(self, num): """ Set number @param number as int """ self._number = num def update_num_label(self): """ Update position label for row """ if Lp().player.is_in_queue(self._track.id): self._num_label.get_style_context().add_class('queued') pos = Lp().player.get_track_position(self._track.id) self._num_label.set_text(str(pos)) elif self._number > 0: self._num_label.get_style_context().remove_class('queued') self._num_label.set_text(str(self._number)) else: self._num_label.get_style_context().remove_class('queued') self._num_label.set_text('') def get_id(self): """ Get object id @return Current id as int """ return self._track.id ####################### # PRIVATE # ####################### def _on_map(self, widget): """ Fix for Gtk < 3.18, if we are in a popover, do not show menu button """ widget = self.get_parent() while widget is not None: if isinstance(widget, Gtk.Popover): break widget = widget.get_parent() if widget is None: self._grid.add(self._menu_button) def _on_enter_notify(self, widget, event): """ Set image on buttons now, speed reason @param widget as Gtk.Widget @param event as Gdk.Event """ if self._menu_button.get_image() is None: image = Gtk.Image.new_from_icon_name('open-menu-symbolic', Gtk.IconSize.MENU) image.set_opacity(0.2) self._menu_button.set_image(image) self._menu_button.connect('clicked', self._on_button_clicked) self._indicator.update_button() def _on_button_press(self, widget, event): """ Popup menu for track relative to track row @param widget as Gtk.Widget @param event as Gdk.Event """ if event.button == 3 and Gtk.get_minor_version() > 16: window = widget.get_window() if window == event.window: self._popup_menu(widget, event.x, event.y) # Happens when pressing button over menu btn else: self._popup_menu(self._menu_button) return True elif event.button == 2: if self._track.id in Lp().player.get_queue(): Lp().player.del_from_queue(self._track.id) else: Lp().player.append_to_queue(self._track.id) def _on_button_clicked(self, widget): """ Popup menu for track relative to button @param widget as Gtk.Button """ self._popup_menu(widget) def _popup_menu(self, widget, xcoordinate=None, ycoordinate=None): """ Popup menu for track @param widget as Gtk.Button @param xcoordinate as int (or None) @param ycoordinate as int (or None) """ popover = TrackMenuPopover(self._track.id, TrackMenu(self._track.id)) popover.set_relative_to(widget) if xcoordinate is not None and ycoordinate is not None: rect = widget.get_allocation() rect.x = xcoordinate rect.y = ycoordinate rect.width = rect.height = 1 popover.set_pointing_to(rect) popover.connect('closed', self._on_closed) self.get_style_context().add_class('track-menu-selected') popover.show() def _on_closed(self, widget): """ Remove selected style @param widget as Gtk.Popover """ self.get_style_context().remove_class('track-menu-selected') def _on_title_query_tooltip(self, widget, x, y, keyboard, tooltip): """ Show tooltip if needed @param widget as Gtk.Widget @param x as int @param y as int @param keyboard as bool @param tooltip as Gtk.Tooltip """ layout = self._title_label.get_layout() if layout.is_ellipsized(): self._title_label.set_tooltip_markup(self._track.formated_name()) elif len(self._track.artists) > 1: self._title_label.set_tooltip_text( ", ".join(self._track.artists[1:])) else: self._title_label.set_tooltip_text('')
def restore_state(self): """ Restore player state """ try: if Lp().settings.get_value('save-state'): track_id = load(open(DataPath + "/track_id.bin", "rb")) playlist_ids = load(open(DataPath + "/playlist_ids.bin", "rb")) if playlist_ids and playlist_ids[0] == Type.RADIOS: radios = Radios() track = Track() name = radios.get_name(track_id) url = radios.get_url(name) track.set_radio(name, url) self.load(track) elif Lp().tracks.get_path(track_id) != "": track = Track(track_id) if Lp().notify is not None: Lp().notify.inhibit() self._load_track(track) # We set this initial state # because seek while failed otherwise self.pause() if playlist_ids: pids = [] for playlist_id in playlist_ids: pids.append(int(playlist_id)) track_ids = [] for playlist_id in playlist_ids: if playlist_id == Type.POPULARS: tracks = Lp().tracks.get_populars() elif playlist_id == Type.RECENTS: tracks = Lp().tracks.get_recently_listened_to() elif playlist_id == Type.NEVER: tracks = Lp().tracks.get_never_listened_to() elif playlist_id == Type.RANDOMS: tracks = Lp().tracks.get_randoms() else: tracks = Lp().playlists.get_track_ids( playlist_id) for track_id in tracks: if track_id not in track_ids: track_ids.append(track_id) self.populate_user_playlist_by_tracks(track_ids, pids) else: self._albums = load(open( DataPath + "/albums.bin", "rb")) self.shuffle_albums(True) self._context.genre_ids = load(open( DataPath + "/genre_ids.bin", "rb")) self._context.artist_ids = load(open( DataPath + "/artist_ids.bin", "rb")) self.set_next() self.set_prev() if Lp().settings.get_value('repeat'): self._context.next = NextContext.NONE else: self._context.next = NextContext.STOP_ALL else: print("Player::restore_state(): track missing") except Exception as e: print("Player::restore_state()", e)