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