Fixed playlist bug
[jamaendo] / jamaui / playerwindow.py
1 #!/usr/bin/env python
2 #
3 # This file is part of Jamaendo.
4 # Copyright (c) 2010 Kristoffer Gronlund
5 #
6 # Jamaendo is free software: you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation, either version 3 of the License, or
9 # (at your option) any later version.
10 #
11 # Jamaendo is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 # GNU General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License
17 # along with Jamaendo.  If not, see <http://www.gnu.org/licenses/>.
18 #
19 # Player code heavily based on http://thpinfo.com/2008/panucci/:
20 #  A resuming media player for Podcasts and Audiobooks
21 #  Copyright (c) 2008-05-26 Thomas Perl <thpinfo.com>
22 #  (based on http://pygstdocs.berlios.de/pygst-tutorial/seeking.html)
23 #
24 import gtk
25 import gobject
26 import hildon
27 import util
28 import pango
29 import jamaendo
30 from settings import settings
31 from postoffice import postoffice
32 from player import Playlist, the_player
33 import logging
34 import cgi
35
36 from songposition import SongPosition
37
38 log = logging.getLogger(__name__)
39
40 class PlayerWindow(hildon.StackableWindow):
41     def __init__(self):
42         hildon.StackableWindow.__init__(self)
43         self.set_title("jamaendo")
44
45         self.connect('destroy', self.on_destroy)
46
47         self.player = the_player
48         self.playlist = the_player.playlist
49
50         vbox = gtk.VBox()
51
52         hbox = gtk.HBox(False, 8)
53
54         self.cover = gtk.Image()
55         self.set_default_cover()
56
57         vbox2 = gtk.VBox()
58
59         self.playlist_pos = gtk.Label()
60         self.playlist_pos.set_alignment(1.0,0)
61         self.track = gtk.Label()
62         self.track.set_alignment(0,1)
63         self.track.set_ellipsize(pango.ELLIPSIZE_END)
64         self.artist = gtk.Label()
65         self.artist.set_alignment(0,0.5)
66         self.artist.set_ellipsize(pango.ELLIPSIZE_END)
67         self.album = gtk.Label()
68         self.album.set_alignment(0,0)
69         self.album.set_ellipsize(pango.ELLIPSIZE_END)
70         self.progress = SongPosition()
71
72         self.set_labels('', '', '', 0, 0)
73
74         self._position_timer = None
75
76         vbox2.pack_start(self.playlist_pos, False)
77         vbox2.pack_start(self.track, True)
78         vbox2.pack_start(self.artist, True)
79         vbox2.pack_start(self.album, True)
80
81         hbox.pack_start(self.cover, False, True, 0)
82         hbox.pack_start(vbox2, True, True, 0)
83
84         vbox.pack_start(hbox, True, True, 0)
85
86         btns = gtk.HButtonBox()
87         btns.set_property('layout-style', gtk.BUTTONBOX_SPREAD)
88
89         vbox.pack_start(btns, False, True, 0)
90
91         vbox.pack_start(self.progress, False)
92
93         self.add_stock_button(btns, gtk.STOCK_MEDIA_PREVIOUS, self.on_prev)
94         self.add_play_button(btns)
95         self.add_stock_button(btns, gtk.STOCK_MEDIA_STOP, self.on_stop)
96         self.add_stock_button(btns, gtk.STOCK_MEDIA_NEXT, self.on_next)
97
98         self.add(vbox)
99
100         postoffice.connect('album-cover', self, self.set_album_cover)
101         postoffice.connect(['next', 'prev', 'play', 'pause', 'stop'], self, self.on_state_changed)
102
103         self.on_state_changed()
104
105         self.create_menu()
106
107     def create_menu(self):
108         self.menu = hildon.AppMenu()
109
110         def to_artist(*args):
111             from showartist import ShowArtist
112             track = self.playlist.current()
113             artist = jamaendo.get_artist(int(track.artist_id))
114             wnd = ShowArtist(artist)
115             wnd.show_all()
116         def to_album(*args):
117             from showalbum import ShowAlbum
118             track = self.playlist.current()
119             album = jamaendo.get_album(int(track.album_id))
120             wnd = ShowAlbum(album)
121             wnd.show_all()
122
123         b = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
124         b.set_label("Artist")
125         b.connect("clicked", to_artist)
126         self.menu.append(b)
127
128         b = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
129         b.set_label("Album")
130         b.connect("clicked", to_album)
131         self.menu.append(b)
132
133         self.menu.show_all()
134         self.set_app_menu(self.menu)
135
136     def on_state_changed(self, *args):
137         self.update_state()
138         self.update_play_button()
139
140         if self.player.playing():
141             self.start_position_timer()
142         else:
143             self.stop_position_timer()
144
145
146     def get_album_id(self):
147         if self.player.playlist and self.player.playlist.current():
148             c = self.player.playlist.current()
149             if not c.album_id:
150                 c.load()
151             if c.album_id:
152                 return c.album_id
153         return None
154
155     def on_destroy(self, wnd):
156         self.stop_position_timer()
157         postoffice.disconnect(['album-cover', 'next', 'prev', 'play', 'stop'], self)
158
159     def add_stock_button(self, btns, stock, cb):
160         btn = hildon.GtkButton(gtk.HILDON_SIZE_FINGER_HEIGHT)
161         btn.set_relief(gtk.RELIEF_NONE)
162         sz = gtk.ICON_SIZE_BUTTON
163         btn.set_image(gtk.image_new_from_stock(stock, sz))
164         btn.connect('clicked', cb)
165         btns.add(btn)
166
167     def add_play_button(self, btns):
168         sz = gtk.ICON_SIZE_BUTTON
169         self.playimg = gtk.image_new_from_stock(gtk.STOCK_MEDIA_PLAY, sz)
170         self.pauseimg = gtk.image_new_from_stock(gtk.STOCK_MEDIA_PAUSE, sz)
171         btn = hildon.GtkButton(gtk.HILDON_SIZE_FINGER_HEIGHT)
172         btn.set_relief(gtk.RELIEF_NONE)
173         if self.player.playing():
174             btn.set_image(self.pauseimg)
175             btn.set_data('state', 'pause')
176         else:
177             btn.set_image(self.playimg)
178             btn.set_data('state', 'play')
179         btn.connect('clicked', self.on_play)
180         btns.add(btn)
181         self.playbtn = btn
182
183     def update_play_button(self):
184         if self.player.playing():
185             self.playbtn.set_image(self.pauseimg)
186             self.playbtn.set_data('state', 'pause')
187         else:
188             self.playbtn.set_image(self.playimg)
189             self.playbtn.set_data('state', 'play')
190
191     def set_labels(self, track, artist, album, playlist_pos, playlist_size):
192         if self.playlist.radio_mode:
193             ppstr = '<span size="small">Radio: %s</span>'%(cgi.escape(self.playlist.radio_name))
194         else:
195             ppstr = '<span size="small">Track %s of %s</span>'%(int(playlist_pos)+1, playlist_size)
196         self.playlist_pos.set_markup(ppstr)
197         self.track.set_markup('<span size="x-large">%s</span>'%(cgi.escape(track)))
198         self.artist.set_markup('<span size="large">%s</span>'%(cgi.escape(artist)))
199         self.album.set_markup('<span foreground="#aaaaaa">%s</span>'%(cgi.escape(album)))
200
201
202     def volume_changed_hildon(self, widget):
203         settings.volume = widget.get_level()/100.0
204
205     def mute_toggled(self, widget):
206         if widget.get_mute():
207             settings.volume = 0
208         else:
209             settings.volume = widget.get_level()/100.0
210
211     def on_position_timeout(self):
212         if the_player.playing():
213             self.set_song_position(*the_player.get_position_duration())
214         else:
215             log.debug("position timeout, but not playing")
216         return True
217
218     def start_position_timer(self):
219         if self._position_timer is not None:
220             self.stop_position_timer()
221         self._position_timer = gobject.timeout_add(1000, self.on_position_timeout)
222
223     def stop_position_timer(self):
224         if self._position_timer is not None:
225             gobject.source_remove(self._position_timer)
226             self._position_timer = None
227
228     def clear_position(self):
229         self.progress.set_position(0)
230
231     def set_song_position(self, time_elapsed, total_time):
232         value = (float(time_elapsed) / float(total_time)) if total_time else 0
233         self.progress.set_position(value)
234
235     def set_default_cover(self):
236         tmp = util.find_resource('album.png')
237         if tmp:
238             self.cover.set_from_file(tmp)
239
240     def update_state(self):
241         item = self.playlist.current()
242         if item:
243             if not item.name:
244                 item.load()
245             self.set_labels(item.name, item.artist_name, item.album_name,
246                             self.playlist.current_index(), self.playlist.size())
247             self.set_default_cover()
248             postoffice.notify('request-album-cover', int(item.album_id), 300)
249         else:
250             self.set_labels('', '', '', 0, 0)
251             self.set_default_cover()
252
253     def set_album_cover(self, albumid, size, cover):
254         if size == 300:
255             playing = self.get_album_id()
256             if playing and albumid and (int(playing) == int(albumid)):
257                 self.cover.set_from_file(cover)
258
259     def play_radio(self, radio_name, radio_id):
260         playlist = Playlist([])
261         playlist.radio_mode = True
262         playlist.radio_name = radio_name
263         playlist.radio_id = radio_id
264         playlist.add(jamaendo.get_radio_tracks(playlist.radio_id))
265         log.debug("Playing radio: %s", playlist)
266         self.__play_tracks(playlist)
267         log.debug("Playlist current: %s, playing? %s",
268                   playlist.current_index(),
269                   self.player.playing())
270
271     def play_tracks(self, tracks):
272         self.__play_tracks(tracks)
273
274     def __play_tracks(self, tracks):
275         self.clear_position()
276         if isinstance(tracks, Playlist):
277             self.playlist = tracks
278         else:
279             self.playlist = Playlist(tracks)
280         log.debug("Playing: %s", self.playlist)
281         self.player.stop()
282         self.player.play(self.playlist)
283
284     def on_play(self, button):
285         if not self.player.playing():
286             self.player.play(self.playlist)
287         else:
288             self.player.pause()
289     def on_prev(self, button):
290         self.player.prev()
291
292     def on_next(self, button):
293         self.player.next()
294
295     def on_stop(self, button):
296         self.clear_position()
297         self.player.stop()
298
299 def open_playerwindow():
300     player = PlayerWindow()
301     player.show_all()
302     return player