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