1# Copyright (c) 2014-2021 Cedric Bellegarde <cedric.bellegarde@adishatz.org>
2# This program is free software: you can redistribute it and/or modify
3# it under the terms of the GNU General Public License as published by
4# the Free Software Foundation, either version 3 of the License, or
5# (at your option) any later version.
6# This program is distributed in the hope that it will be useful,
7# but WITHOUT ANY WARRANTY; without even the implied warranty of
8# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9# GNU General Public License for more details.
10# You should have received a copy of the GNU General Public License
11# along with this program. If not, see <http://www.gnu.org/licenses/>.
12
13from gi.repository import GLib
14
15from random import choice
16from gettext import gettext as _
17
18from lollypop.logger import Logger
19from lollypop.objects_album import Album
20from lollypop.player_auto_similar import AutoSimilarPlayer
21from lollypop.player_auto_random import AutoRandomPlayer
22from lollypop.define import App, Repeat
23from lollypop.utils import emit_signal
24
25
26class AlbumsPlayer:
27    """
28        Handle player albums
29    """
30
31    def __init__(self):
32        """
33            Init player
34        """
35        # Albums in current playlist
36        self._albums = []
37
38    def add_album(self, album):
39        """
40            Add album to player
41            @param album as Album
42        """
43        self.add_albums([album])
44
45    def add_album_ids(self, album_ids):
46        """
47            Add album ids to player
48            @param album_ids as [int]
49        """
50        self.add_albums([Album(album_id) for album_id in album_ids])
51
52    def add_albums(self, albums):
53        """
54            Add albums to player
55            @param albums as [Album]
56        """
57        if not albums:
58            App().notify.send(_("No album available"))
59            return
60        try:
61            for album in albums:
62                # Merge album if previous is same
63                if self._albums and self._albums[-1].id == album.id:
64                    track_ids = self._albums[-1].track_ids
65                    for track in album.tracks:
66                        if track.id not in track_ids:
67                            self._albums[-1].append_track(track)
68                    emit_signal(self, "playback-updated", self._albums[-1])
69                else:
70                    self._albums.append(album)
71                    emit_signal(self, "playback-added", album)
72            self.update_next_prev()
73        except Exception as e:
74            Logger.error("Player::add_albums(): %s" % e)
75
76    def remove_album(self, album):
77        """
78            Remove album from albums
79            @param album as Album
80        """
81        try:
82            if album not in self._albums:
83                return
84            self._albums.remove(album)
85            emit_signal(self, "playback-removed", album)
86            self.update_next_prev()
87        except Exception as e:
88            Logger.error("Player::remove_album(): %s" % e)
89
90    def remove_album_by_id(self, album_id):
91        """
92            Remove all instances of album id
93            @param album_id as int
94        """
95        self.remove_album_by_ids([album_id])
96
97    def remove_album_by_ids(self, album_ids):
98        """
99            Remove all instances of album ids
100            @param album_ids as [int]
101        """
102        try:
103            for album_id in album_ids:
104                for album in self._albums:
105                    if album.id == album_id:
106                        self.remove_album(album)
107                        emit_signal(self, "playback-removed", album)
108            self.update_next_prev()
109        except Exception as e:
110            Logger.error("Player::remove_album_by_ids(): %s" % e)
111
112    def remove_track_from_album(self, track, album):
113        """
114            Remove track from album
115            @param track as Track
116            @param album as Album
117        """
118        is_current_track = track == self.current_track
119        if is_current_track:
120            self.next()
121        if album.remove_track(track):
122            self.remove_album(album)
123            emit_signal(self, "playback-removed", album)
124        else:
125            emit_signal(self, "playback-updated", album)
126            if not is_current_track:
127                self.update_next_prev()
128
129    def play_album(self, album):
130        """
131            Play album
132            @param album as Album
133        """
134        self.play_album_for_albums(album, [album])
135
136    def play_albums(self, albums):
137        """
138            Play albums
139            @param album as [Album]
140        """
141        if not albums:
142            App().notify.send(_("No album available"))
143            return
144        if App().settings.get_value("shuffle"):
145            album = choice(albums)
146        else:
147            album = albums[0]
148        self.play_album_for_albums(album, albums)
149
150    def play_track_for_albums(self, track, albums):
151        """
152            Play track and set albums as current playlist
153            @param albums as [Album]
154            @param track as Track
155        """
156        if self.is_party:
157            App().lookup_action("party").change_state(GLib.Variant("b", False))
158        self._albums = albums
159        self.load(track)
160        emit_signal(self, "playback-setted", list(albums))
161
162    def play_album_for_albums(self, album, albums):
163        """
164            Play album and set albums as current playlist
165            @param album as Album
166            @param albums as [Album]
167        """
168        if not albums:
169            App().notify.send(_("No album available"))
170            return
171        if self.is_party:
172            App().lookup_action("party").change_state(GLib.Variant("b", False))
173        if App().settings.get_value("shuffle"):
174            self.__play_shuffle_tracks(album, albums)
175        else:
176            self.__play_albums(album, albums)
177
178    def set_albums(self, albums, signal=True):
179        """
180            Set player albums
181            @param albums as [Album]
182            @param signal as bool
183        """
184        if not albums:
185            App().notify.send(_("No album available"))
186            return
187        self._albums = albums
188        if signal:
189            emit_signal(self, "playback-setted", list(albums))
190        self.update_next_prev()
191
192    def clear_albums(self):
193        """
194            Clear all albums
195        """
196        self._albums = []
197        emit_signal(self, "playback-setted", [])
198        self.update_next_prev()
199
200    def skip_album(self):
201        """
202            Skip current album
203        """
204        try:
205            # In party or shuffle, just update next track
206            if self.is_party or App().settings.get_value("shuffle"):
207                self.set_next()
208            elif self._current_track.id is not None:
209                index = self._albums.index(
210                    self._current_track.album)
211                if index + 1 >= len(self._albums):
212                    repeat = App().settings.get_enum("repeat")
213                    if repeat == Repeat.AUTO_SIMILAR:
214                        next_album = AutoSimilarPlayer.next_album(self)
215                        if next_album is not None:
216                            self.add_album(next_album)
217                    elif repeat == Repeat.AUTO_RANDOM:
218                        next_album = AutoRandomPlayer.next_album(self)
219                        if next_album is not None:
220                            self.add_album(next_album)
221                    elif repeat == Repeat.ALL:
222                        next_album = self._albums[0]
223                    else:
224                        next_album = None
225                else:
226                    next_album = self._albums[index + 1]
227                if next_album is None:
228                    self.stop()
229                else:
230                    self.load(next_album.tracks[0])
231        except Exception as e:
232            Logger.error("Player::skip_album(): %s" % e)
233
234    def track_in_playback(self, track):
235        """
236            True if track present in current playback
237            @param track as Track
238            @return Track/None
239        """
240        for album in self._albums:
241            if album.id == track.album.id:
242                for _track in album.tracks:
243                    if track.id == _track.id:
244                        return _track
245        return None
246
247    def get_albums_for_id(self, album_id):
248        """
249            Get albums for id
250            @param album_id as int
251            @return [Album]
252        """
253        return [album for album in self._albums if album.id == album_id]
254
255    @property
256    def albums(self):
257        """
258            Return albums
259            @return albums as [Album]
260        """
261        return list(self._albums)
262
263    @property
264    def album_ids(self):
265        """
266            Return albums ids
267            @return albums ids as [int]
268        """
269        return [album.id for album in self._albums]
270
271#######################
272# PRIVATE             #
273#######################
274    def __play_shuffle_tracks(self, album, albums):
275        """
276            Start shuffle tracks playback.
277            @param album as Album
278            @param albums as [albums]
279        """
280        if album is None:
281            album = choice(albums)
282        if album.tracks:
283            track = choice(album.tracks)
284        else:
285            track = None
286        self._albums = albums
287        emit_signal(self, "playback-setted", list(albums))
288        if track is not None:
289            self.load(track)
290        else:
291            self.update_next_prev()
292
293    def __play_albums(self, album, albums):
294        """
295            Start albums playback.
296            @param album as Album
297            @param albums as [albums]
298        """
299        if album is None:
300            album = albums[0]
301        if album.tracks:
302            track = album.tracks[0]
303        else:
304            track = None
305        self._albums = albums
306        emit_signal(self, "playback-setted", list(albums))
307        if track is not None:
308            self.load(track)
309        else:
310            self.update_next_prev()
311