New progress bar
[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 try:
27     import hildon
28 except:
29     import helldon as hildon
30 import util
31 import pango
32 import jamaendo
33 from settings import settings
34 from postoffice import postoffice
35 from player import Playlist, the_player
36 import logging
37 import cgi
38
39 from songposition import SongPosition
40 from listbox import ListDialog
41 import colors
42 log = logging.getLogger(__name__)
43
44 class PlayerWindow(hildon.StackableWindow):
45     instance = None
46
47     def __init__(self):
48         hildon.StackableWindow.__init__(self)
49         self.set_title("jamaendo")
50
51         PlayerWindow.instance = self
52
53         self.connect('destroy', self.on_destroy)
54
55         self.player = the_player
56         self.playlist = the_player.playlist
57
58         vbox = gtk.VBox(False, 8)
59
60         hbox = gtk.HBox(False)
61
62         self.cover = gtk.Image()
63         self.set_default_cover()
64
65         vbox2 = gtk.VBox()
66
67         self.playlist_pos = gtk.Label()
68         self.playlist_pos.set_alignment(1.0,0)
69         self.track = gtk.Label()
70         self.track.set_alignment(0,1)
71         self.track.set_ellipsize(pango.ELLIPSIZE_END)
72         self.artist = gtk.Label()
73         self.artist.set_alignment(0,0.5)
74         self.artist.set_ellipsize(pango.ELLIPSIZE_END)
75         self.album = gtk.Label()
76         self.album.set_alignment(0,0)
77         self.album.set_ellipsize(pango.ELLIPSIZE_END)
78         self.progress = SongPosition()
79
80         self.set_labels('', '', '', 0, 0)
81
82         self._position_timer = None
83
84         vbox2.pack_start(self.playlist_pos, False)
85         vbox2.pack_start(self.track, True)
86         vbox2.pack_start(self.artist, True)
87         vbox2.pack_start(self.album, True)
88         vbox2.pack_start(self.progress, False, True)
89
90         hbox.set_border_width(8)
91         hbox.pack_start(self.cover, False, True, 8)
92         hbox.pack_start(vbox2, True, True, 0)
93
94         vbox.pack_start(hbox, True, True, 0)
95
96         btns = gtk.HButtonBox()
97         btns.set_property('layout-style', gtk.BUTTONBOX_SPREAD)
98
99         vbox.pack_start(btns, False, True, 0)
100
101         self.add_stock_button(btns, gtk.STOCK_MEDIA_PREVIOUS, self.on_prev)
102         self.add_play_button(btns)
103         self.add_stock_button(btns, gtk.STOCK_MEDIA_STOP, self.on_stop)
104         self.add_stock_button(btns, gtk.STOCK_MEDIA_NEXT, self.on_next)
105
106         self.add(vbox)
107
108         postoffice.connect('album-cover', self, self.set_album_cover)
109         postoffice.connect(['next', 'prev', 'play', 'pause', 'stop'], self, self.on_state_changed)
110
111         self.on_state_changed()
112
113         self.create_menu()
114
115     def create_menu(self):
116         self.menu = hildon.AppMenu()
117
118         def to_artist(*args):
119             from showartist import ShowArtist
120             track = self.playlist.current()
121             artist = jamaendo.get_artist(int(track.artist_id))
122             wnd = ShowArtist(artist)
123             wnd.show_all()
124         def to_album(*args):
125             from showalbum import ShowAlbum
126             track = self.playlist.current()
127             album = jamaendo.get_album(int(track.album_id))
128             wnd = ShowAlbum(album)
129             wnd.show_all()
130
131         b = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
132         b.set_label("Artist")
133         b.connect("clicked", to_artist)
134         self.menu.append(b)
135
136         b = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
137         b.set_label("Album")
138         b.connect("clicked", to_album)
139         self.menu.append(b)
140
141         b = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
142         b.set_label("Add to playlist")
143         b.connect("clicked", self.on_add_to_playlist)
144         self.menu.append(b)
145
146         self.menu.show_all()
147         self.set_app_menu(self.menu)
148
149     def on_state_changed(self, *args):
150         self.update_state()
151         self.update_play_button()
152
153         if self.player.playing():
154             self.start_position_timer()
155         else:
156             self.stop_position_timer()
157
158
159     def get_album_id(self):
160         if self.player.playlist and self.player.playlist.current():
161             c = self.player.playlist.current()
162             if not c.album_id:
163                 c.load()
164             if c.album_id:
165                 return c.album_id
166         return None
167
168     def on_destroy(self, wnd):
169         PlayerWindow.instance = None
170         self.stop_position_timer()
171         postoffice.disconnect(['album-cover', 'next', 'prev', 'play', 'stop'], self)
172
173     def add_stock_button(self, btns, stock, cb):
174         btn = hildon.GtkButton(gtk.HILDON_SIZE_FINGER_HEIGHT)
175         btn.set_relief(gtk.RELIEF_NONE)
176         btn.set_focus_on_click(False)
177         sz = gtk.ICON_SIZE_BUTTON
178         btn.set_image(gtk.image_new_from_stock(stock, sz))
179         btn.connect('clicked', cb)
180         btns.add(btn)
181
182     def add_play_button(self, btns):
183         sz = gtk.ICON_SIZE_BUTTON
184         self.playimg = gtk.image_new_from_stock(gtk.STOCK_MEDIA_PLAY, sz)
185         self.pauseimg = gtk.image_new_from_stock(gtk.STOCK_MEDIA_PAUSE, sz)
186         btn = hildon.GtkButton(gtk.HILDON_SIZE_FINGER_HEIGHT)
187         btn.set_relief(gtk.RELIEF_NONE)
188         if self.player.playing():
189             btn.set_image(self.pauseimg)
190             btn.set_data('state', 'pause')
191         else:
192             btn.set_image(self.playimg)
193             btn.set_data('state', 'play')
194         btn.connect('clicked', self.on_play)
195         btns.add(btn)
196         self.playbtn = btn
197
198     def update_play_button(self):
199         if self.player.playing():
200             self.playbtn.set_image(self.pauseimg)
201             self.playbtn.set_data('state', 'pause')
202         else:
203             self.playbtn.set_image(self.playimg)
204             self.playbtn.set_data('state', 'play')
205
206     def set_labels(self, track, artist, album, playlist_pos, playlist_size):
207
208         if self.playlist.radio_mode:
209             ppstr = '<span size="small">Radio: %s</span>'%(cgi.escape(self.playlist.radio_name))
210         else:
211             ppstr = '<span font_desc="%s" foreground="%s">Track %s of %s</span>'%(colors.SmallSystemFont(), colors.SecondaryTextColor(), int(playlist_pos)+1, playlist_size)
212         self.playlist_pos.set_markup(ppstr)
213         self.track.set_markup('<span font_desc="%s">%s</span>'%(colors.LargeSystemFont(), cgi.escape(track)))
214         self.artist.set_markup('%s'%(cgi.escape(artist)))
215         self.album.set_markup('<span foreground="%s">%s</span>'%(colors.SecondaryTextColor(), cgi.escape(album)))
216
217     def show_banner(self, message, timeout = 2000):
218         banner = hildon.hildon_banner_show_information(self, '', message)
219         banner.set_timeout(2000)
220
221     def on_add_to_playlist(self, button, user_data=None):
222         track = self.player.playlist.current()
223         from playlists import add_to_playlist
224         add_to_playlist(self, track)
225
226     def volume_changed_hildon(self, widget):
227         settings.volume = widget.get_level()/100.0
228
229     def mute_toggled(self, widget):
230         if widget.get_mute():
231             settings.volume = 0
232         else:
233             settings.volume = widget.get_level()/100.0
234
235     def on_position_timeout(self):
236         if the_player.playing():
237             self.set_song_position(*the_player.get_position_duration())
238         else:
239             log.debug("position timeout, but not playing")
240         return True
241
242     def start_position_timer(self):
243         if self._position_timer is not None:
244             self.stop_position_timer()
245         self._position_timer = gobject.timeout_add(1000, self.on_position_timeout)
246
247     def stop_position_timer(self):
248         if self._position_timer is not None:
249             gobject.source_remove(self._position_timer)
250             self._position_timer = None
251
252     def clear_position(self):
253         self.progress.set_position(0)
254
255     def set_song_position(self, time_elapsed, total_time):
256         value = (float(time_elapsed) / float(total_time)) if total_time else 0
257         self.progress.set_position(value)
258
259     def set_default_cover(self):
260         tmp = util.find_resource('album.png')
261         if tmp:
262             log.debug("Setting cover to %s", tmp)
263             self.cover.set_from_file(tmp)
264
265     def update_state(self):
266         item = self.playlist.current()
267         if item:
268             if not item.name:
269                 item.load()
270             self.set_labels(item.name, item.artist_name, item.album_name,
271                             self.playlist.current_index(), self.playlist.size())
272             self.set_default_cover()
273             postoffice.notify('request-album-cover', int(item.album_id), 300)
274         else:
275             self.set_labels('', '', '', 0, 0)
276             self.set_default_cover()
277
278     def set_album_cover(self, albumid, size, cover):
279         if size == 300:
280             playing = self.get_album_id()
281             if playing and albumid and (int(playing) == int(albumid)):
282                 log.debug("Setting cover to %s", cover)
283                 self.cover.set_from_file(cover)
284
285     def play_radio(self, radio_name, radio_id):
286         playlist = Playlist([])
287         playlist.radio_mode = True
288         playlist.radio_name = radio_name
289         playlist.radio_id = radio_id
290         playlist.add(jamaendo.get_radio_tracks(playlist.radio_id))
291         log.debug("Playing radio: %s", playlist)
292         self.__play_tracks(playlist)
293         log.debug("Playlist current: %s, playing? %s",
294                   playlist.current_index(),
295                   self.player.playing())
296
297     def play_tracks(self, tracks):
298         self.__play_tracks(tracks)
299
300     def __play_tracks(self, tracks):
301         self.clear_position()
302         if isinstance(tracks, Playlist):
303             self.playlist = tracks
304         else:
305             self.playlist = Playlist(tracks)
306         log.debug("Playing: %s", self.playlist)
307         self.player.stop()
308         self.player.play(self.playlist)
309
310     def on_play(self, button):
311         if not self.player.playing():
312             self.player.play(self.playlist)
313         else:
314             self.player.pause()
315     def on_prev(self, button):
316         self.player.prev()
317
318     def on_next(self, button):
319         self.player.next()
320
321     def on_stop(self, button):
322         self.clear_position()
323         self.player.stop()
324
325 def open_playerwindow():
326     if PlayerWindow.instance:
327         player = PlayerWindow.instance
328         stack = player.get_stack()
329         sz = stack.size()
330         windows = stack.pop(sz)
331         windows.remove(player)
332         windows.append(player)
333         stack.push_list(windows)
334     else:
335         player = PlayerWindow()
336         player.show_all()
337     return player