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