16bfffe5eeada909810aec9560761af491e48e44
[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             log.debug("Setting cover to %s", tmp)
244             self.cover.set_from_file(tmp)
245
246     def update_state(self):
247         item = self.playlist.current()
248         if item:
249             if not item.name:
250                 item.load()
251             self.set_labels(item.name, item.artist_name, item.album_name,
252                             self.playlist.current_index(), self.playlist.size())
253             self.set_default_cover()
254             postoffice.notify('request-album-cover', int(item.album_id), 300)
255         else:
256             self.set_labels('', '', '', 0, 0)
257             self.set_default_cover()
258
259     def set_album_cover(self, albumid, size, cover):
260         if size == 300:
261             playing = self.get_album_id()
262             if playing and albumid and (int(playing) == int(albumid)):
263                 log.debug("Setting cover to %s", cover)
264                 self.cover.set_from_file(cover)
265
266     def play_radio(self, radio_name, radio_id):
267         playlist = Playlist([])
268         playlist.radio_mode = True
269         playlist.radio_name = radio_name
270         playlist.radio_id = radio_id
271         playlist.add(jamaendo.get_radio_tracks(playlist.radio_id))
272         log.debug("Playing radio: %s", playlist)
273         self.__play_tracks(playlist)
274         log.debug("Playlist current: %s, playing? %s",
275                   playlist.current_index(),
276                   self.player.playing())
277
278     def play_tracks(self, tracks):
279         self.__play_tracks(tracks)
280
281     def __play_tracks(self, tracks):
282         self.clear_position()
283         if isinstance(tracks, Playlist):
284             self.playlist = tracks
285         else:
286             self.playlist = Playlist(tracks)
287         log.debug("Playing: %s", self.playlist)
288         self.player.stop()
289         self.player.play(self.playlist)
290
291     def on_play(self, button):
292         if not self.player.playing():
293             self.player.play(self.playlist)
294         else:
295             self.player.pause()
296     def on_prev(self, button):
297         self.player.prev()
298
299     def on_next(self, button):
300         self.player.next()
301
302     def on_stop(self, button):
303         self.clear_position()
304         self.player.stop()
305
306 def open_playerwindow():
307     if PlayerWindow.instance:
308         player = PlayerWindow.instance
309         stack = player.get_stack()
310         sz = stack.size()
311         windows = stack.pop(sz)
312         windows.remove(player)
313         windows.append(player)
314         stack.push_list(windows)
315     else:
316         player = PlayerWindow()
317         player.show_all()
318     return player