2bb65980e1b5a74e1490953dda8cc7ac4116983b
[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 from listbox import ListDialog
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         b = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
138         b.set_label("Add to playlist")
139         b.connect("clicked", self.on_add_to_playlist)
140         self.menu.append(b)
141
142         self.menu.show_all()
143         self.set_app_menu(self.menu)
144
145     def on_state_changed(self, *args):
146         self.update_state()
147         self.update_play_button()
148
149         if self.player.playing():
150             self.start_position_timer()
151         else:
152             self.stop_position_timer()
153
154
155     def get_album_id(self):
156         if self.player.playlist and self.player.playlist.current():
157             c = self.player.playlist.current()
158             if not c.album_id:
159                 c.load()
160             if c.album_id:
161                 return c.album_id
162         return None
163
164     def on_destroy(self, wnd):
165         PlayerWindow.instance = None
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         if self.playlist.radio_mode:
203             ppstr = '<span size="small">Radio: %s</span>'%(cgi.escape(self.playlist.radio_name))
204         else:
205             ppstr = '<span size="small">Track %s of %s</span>'%(int(playlist_pos)+1, playlist_size)
206         self.playlist_pos.set_markup(ppstr)
207         self.track.set_markup('<span size="x-large">%s</span>'%(cgi.escape(track)))
208         self.artist.set_markup('<span size="large">%s</span>'%(cgi.escape(artist)))
209         self.album.set_markup('<span foreground="#aaaaaa">%s</span>'%(cgi.escape(album)))
210
211     def show_banner(self, message, timeout = 2000):
212         banner = hildon.hildon_banner_show_information(self, '', message)
213         banner.set_timeout(2000)
214
215     def on_add_to_playlist(self, button, user_data=None):
216         track = self.player.playlist.current()
217         from playlists import add_to_playlist
218         add_to_playlist(self, track)
219
220     def volume_changed_hildon(self, widget):
221         settings.volume = widget.get_level()/100.0
222
223     def mute_toggled(self, widget):
224         if widget.get_mute():
225             settings.volume = 0
226         else:
227             settings.volume = widget.get_level()/100.0
228
229     def on_position_timeout(self):
230         if the_player.playing():
231             self.set_song_position(*the_player.get_position_duration())
232         else:
233             log.debug("position timeout, but not playing")
234         return True
235
236     def start_position_timer(self):
237         if self._position_timer is not None:
238             self.stop_position_timer()
239         self._position_timer = gobject.timeout_add(1000, self.on_position_timeout)
240
241     def stop_position_timer(self):
242         if self._position_timer is not None:
243             gobject.source_remove(self._position_timer)
244             self._position_timer = None
245
246     def clear_position(self):
247         self.progress.set_position(0)
248
249     def set_song_position(self, time_elapsed, total_time):
250         value = (float(time_elapsed) / float(total_time)) if total_time else 0
251         self.progress.set_position(value)
252
253     def set_default_cover(self):
254         tmp = util.find_resource('album.png')
255         if tmp:
256             log.debug("Setting cover to %s", tmp)
257             self.cover.set_from_file(tmp)
258
259     def update_state(self):
260         item = self.playlist.current()
261         if item:
262             if not item.name:
263                 item.load()
264             self.set_labels(item.name, item.artist_name, item.album_name,
265                             self.playlist.current_index(), self.playlist.size())
266             self.set_default_cover()
267             postoffice.notify('request-album-cover', int(item.album_id), 300)
268         else:
269             self.set_labels('', '', '', 0, 0)
270             self.set_default_cover()
271
272     def set_album_cover(self, albumid, size, cover):
273         if size == 300:
274             playing = self.get_album_id()
275             if playing and albumid and (int(playing) == int(albumid)):
276                 log.debug("Setting cover to %s", cover)
277                 self.cover.set_from_file(cover)
278
279     def play_radio(self, radio_name, radio_id):
280         playlist = Playlist([])
281         playlist.radio_mode = True
282         playlist.radio_name = radio_name
283         playlist.radio_id = radio_id
284         playlist.add(jamaendo.get_radio_tracks(playlist.radio_id))
285         log.debug("Playing radio: %s", playlist)
286         self.__play_tracks(playlist)
287         log.debug("Playlist current: %s, playing? %s",
288                   playlist.current_index(),
289                   self.player.playing())
290
291     def play_tracks(self, tracks):
292         self.__play_tracks(tracks)
293
294     def __play_tracks(self, tracks):
295         self.clear_position()
296         if isinstance(tracks, Playlist):
297             self.playlist = tracks
298         else:
299             self.playlist = Playlist(tracks)
300         log.debug("Playing: %s", self.playlist)
301         self.player.stop()
302         self.player.play(self.playlist)
303
304     def on_play(self, button):
305         if not self.player.playing():
306             self.player.play(self.playlist)
307         else:
308             self.player.pause()
309     def on_prev(self, button):
310         self.player.prev()
311
312     def on_next(self, button):
313         self.player.next()
314
315     def on_stop(self, button):
316         self.clear_position()
317         self.player.stop()
318
319 def open_playerwindow():
320     if PlayerWindow.instance:
321         player = PlayerWindow.instance
322         stack = player.get_stack()
323         sz = stack.size()
324         windows = stack.pop(sz)
325         windows.remove(player)
326         windows.append(player)
327         stack.push_list(windows)
328     else:
329         player = PlayerWindow()
330         player.show_all()
331     return player