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