comparison src/browser/sat_browser/radiocol.py @ 467:97c72fe4a5f2

browser_side: import fixes: - moved browser modules in a sat_browser packages, to avoid import conflicts with std lib (e.g. logging), and let pyjsbuild work normaly - refactored bad import practices: classes are most of time not imported directly, module is imported instead.
author Goffi <goffi@goffi.org>
date Mon, 09 Jun 2014 22:15:26 +0200
parents src/browser/radiocol.py@1a0cec9b0f1e
children bbdc5357dc00
comparison
equal deleted inserted replaced
466:01880aa8ea2d 467:97c72fe4a5f2
1 #!/usr/bin/python
2 # -*- coding: utf-8 -*-
3
4 # Libervia: a Salut à Toi frontend
5 # Copyright (C) 2011, 2012, 2013, 2014 Jérôme Poisson <goffi@goffi.org>
6
7 # This program is free software: you can redistribute it and/or modify
8 # it under the terms of the GNU Affero General Public License as published by
9 # the Free Software Foundation, either version 3 of the License, or
10 # (at your option) any later version.
11
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU Affero General Public License for more details.
16
17 # You should have received a copy of the GNU Affero General Public License
18 # along with this program. If not, see <http://www.gnu.org/licenses/>.
19
20 import pyjd # this is dummy in pyjs
21 from sat.core.log import getLogger
22 log = getLogger(__name__)
23 from sat_frontends.tools.misc import DEFAULT_MUC
24 from sat.core.i18n import _
25
26 from pyjamas.ui.VerticalPanel import VerticalPanel
27 from pyjamas.ui.HorizontalPanel import HorizontalPanel
28 from pyjamas.ui.FlexTable import FlexTable
29 from pyjamas.ui.FormPanel import FormPanel
30 from pyjamas.ui.Label import Label
31 from pyjamas.ui.Button import Button
32 from pyjamas.ui.ClickListener import ClickHandler
33 from pyjamas.ui.Hidden import Hidden
34 from pyjamas.ui.CaptionPanel import CaptionPanel
35 from pyjamas.media.Audio import Audio
36 from pyjamas import Window
37 from pyjamas.Timer import Timer
38
39 import html_tools
40 import file_tools
41
42
43 class MetadataPanel(FlexTable):
44
45 def __init__(self):
46 FlexTable.__init__(self)
47 title_lbl = Label("title:")
48 title_lbl.setStyleName('radiocol_metadata_lbl')
49 artist_lbl = Label("artist:")
50 artist_lbl.setStyleName('radiocol_metadata_lbl')
51 album_lbl = Label("album:")
52 album_lbl.setStyleName('radiocol_metadata_lbl')
53 self.title = Label("")
54 self.title.setStyleName('radiocol_metadata')
55 self.artist = Label("")
56 self.artist.setStyleName('radiocol_metadata')
57 self.album = Label("")
58 self.album.setStyleName('radiocol_metadata')
59 self.setWidget(0, 0, title_lbl)
60 self.setWidget(1, 0, artist_lbl)
61 self.setWidget(2, 0, album_lbl)
62 self.setWidget(0, 1, self.title)
63 self.setWidget(1, 1, self.artist)
64 self.setWidget(2, 1, self.album)
65 self.setStyleName("radiocol_metadata_pnl")
66
67 def setTitle(self, title):
68 self.title.setText(title)
69
70 def setArtist(self, artist):
71 self.artist.setText(artist)
72
73 def setAlbum(self, album):
74 self.album.setText(album)
75
76
77 class ControlPanel(FormPanel):
78 """Panel used to show controls to add a song, or vote for the current one"""
79
80 def __init__(self, parent):
81 FormPanel.__init__(self)
82 self.setEncoding(FormPanel.ENCODING_MULTIPART)
83 self.setMethod(FormPanel.METHOD_POST)
84 self.setAction("upload_radiocol")
85 self.timer_on = False
86 self._parent = parent
87 vPanel = VerticalPanel()
88
89 types = [('audio/ogg', '*.ogg', 'Ogg Vorbis Audio'),
90 ('video/ogg', '*.ogv', 'Ogg Vorbis Video'),
91 ('application/ogg', '*.ogx', 'Ogg Vorbis Multiplex'),
92 ('audio/mpeg', '*.mp3', 'MPEG-Layer 3'),
93 ('audio/mp3', '*.mp3', 'MPEG-Layer 3'),
94 ]
95 self.file_upload = file_tools.FilterFileUpload("song", 10, types)
96 vPanel.add(self.file_upload)
97
98 hPanel = HorizontalPanel()
99 self.upload_btn = Button("Upload song", getattr(self, "onBtnClick"))
100 hPanel.add(self.upload_btn)
101 self.status = Label()
102 self.updateStatus()
103 hPanel.add(self.status)
104 #We need to know the filename and the referee
105 self.filename_field = Hidden('filename', '')
106 hPanel.add(self.filename_field)
107 referee_field = Hidden('referee', self._parent.referee)
108 hPanel.add(self.filename_field)
109 hPanel.add(referee_field)
110 vPanel.add(hPanel)
111
112 self.add(vPanel)
113 self.addFormHandler(self)
114
115 def updateStatus(self):
116 if self.timer_on:
117 return
118 # TODO: the status should be different if a song is being played or not
119 queue = self._parent.getQueueSize()
120 queue_data = self._parent.queue_data
121 if queue < queue_data[0]:
122 left = queue_data[0] - queue
123 self.status.setText("[we need %d more song%s]" % (left, "s" if left > 1 else ""))
124 elif queue < queue_data[1]:
125 left = queue_data[1] - queue
126 self.status.setText("[%d available spot%s]" % (left, "s" if left > 1 else ""))
127 elif queue >= queue_data[1]:
128 self.status.setText("[The queue is currently full]")
129 self.status.setStyleName('radiocol_status')
130
131 def onBtnClick(self):
132 if self.file_upload.check():
133 self.status.setText('[Submitting, please wait...]')
134 self.filename_field.setValue(self.file_upload.getFilename())
135 if self.file_upload.getFilename().lower().endswith('.mp3'):
136 self._parent._parent.host.showWarning('STATUS', 'For a better support, it is recommended to submit Ogg Vorbis file instead of MP3. You can convert your files easily, ask for help if needed!', 5000)
137 self.submit()
138 self.file_upload.setFilename("")
139
140 def onSubmit(self, event):
141 pass
142
143 def blockUpload(self):
144 self.file_upload.setVisible(False)
145 self.upload_btn.setEnabled(False)
146
147 def unblockUpload(self):
148 self.file_upload.setVisible(True)
149 self.upload_btn.setEnabled(True)
150
151 def setTemporaryStatus(self, text, style):
152 self.status.setText(text)
153 self.status.setStyleName('radiocol_upload_status_%s' % style)
154 self.timer_on = True
155
156 def cb(timer):
157 self.timer_on = False
158 self.updateStatus()
159
160 Timer(5000, cb)
161
162 def onSubmitComplete(self, event):
163 result = event.getResults()
164 if result == "OK":
165 # the song can still be rejected (not readable, full queue...)
166 self.setTemporaryStatus('[Your song has been submitted to the radio]', "ok")
167 elif result == "KO":
168 self.setTemporaryStatus('[Something went wrong during your song upload]', "ko")
169 self._parent.radiocolSongRejected(_("The uploaded file has been rejected, only Ogg Vorbis and MP3 songs are accepted."))
170 # TODO: would be great to re-use the original Exception class and message
171 # but it is lost in the middle of the traceback and encapsulated within
172 # a DBusException instance --> extract the data from the traceback?
173 else:
174 Window.alert('Submit error: %s' % result)
175 self.status.setText('')
176
177
178 class Player(Audio):
179
180 def __init__(self, player_id, metadata_panel):
181 Audio.__init__(self)
182 self._id = player_id
183 self.metadata = metadata_panel
184 self.timestamp = ""
185 self.title = ""
186 self.artist = ""
187 self.album = ""
188 self.filename = None
189 self.played = False # True when the song is playing/has played, becomes False on preload
190 self.setAutobuffer(True)
191 self.setAutoplay(False)
192 self.setVisible(False)
193
194 def preload(self, timestamp, filename, title, artist, album):
195 """preload the song but doesn't play it"""
196 self.timestamp = timestamp
197 self.filename = filename
198 self.title = title
199 self.artist = artist
200 self.album = album
201 self.played = False
202 self.setSrc("radiocol/%s" % html_tools.html_sanitize(filename))
203 log.debug("preloading %s in %s" % (title, self._id))
204
205 def play(self, play=True):
206 """Play or pause the song
207 @param play: set to True to play or to False to pause
208 """
209 if play:
210 self.played = True
211 self.metadata.setTitle(self.title)
212 self.metadata.setArtist(self.artist)
213 self.metadata.setAlbum(self.album)
214 Audio.play(self)
215 else:
216 self.pause()
217
218
219 class RadioColPanel(HorizontalPanel, ClickHandler):
220
221 def __init__(self, parent, referee, player_nick, players, queue_data):
222 """
223 @param parent
224 @param referee
225 @param player_nick
226 @param players
227 @param queue_data: list of integers (queue to start, queue limit)
228 """
229 # We need to set it here and not in the CSS :(
230 HorizontalPanel.__init__(self, Height="90px")
231 ClickHandler.__init__(self)
232 self._parent = parent
233 self.referee = referee
234 self.queue_data = queue_data
235 self.setStyleName("radiocolPanel")
236
237 # Now we set up the layout
238 self.metadata_panel = MetadataPanel()
239 self.add(CaptionPanel("Now playing", self.metadata_panel))
240 self.playlist_panel = VerticalPanel()
241 self.add(CaptionPanel("Songs queue", self.playlist_panel))
242 self.control_panel = ControlPanel(self)
243 self.add(CaptionPanel("Controls", self.control_panel))
244
245 self.next_songs = []
246 self.players = [Player("player_%d" % i, self.metadata_panel) for i in xrange(queue_data[1] + 1)]
247 self.current_player = None
248 for player in self.players:
249 self.add(player)
250 self.addClickListener(self)
251
252 help_msg = """Accepted file formats: Ogg Vorbis (recommended), MP3.<br />
253 Please do not submit files that are protected by copyright.<br />
254 Click <a style="color: red;">here</a> if you need some support :)"""
255 link_cb = lambda: self._parent.host.bridge.call('joinMUC', None, DEFAULT_MUC, self._parent.nick)
256 self._parent.printInfo(help_msg, type_='link', link_cb=link_cb)
257
258 def pushNextSong(self, title):
259 """Add a song to the left panel's next songs queue"""
260 next_song = Label(title)
261 next_song.setStyleName("radiocol_next_song")
262 self.next_songs.append(next_song)
263 self.playlist_panel.append(next_song)
264 self.control_panel.updateStatus()
265
266 def popNextSong(self):
267 """Remove the first song of next songs list
268 should be called when the song is played"""
269 #FIXME: should check that the song we remove is the one we play
270 next_song = self.next_songs.pop(0)
271 self.playlist_panel.remove(next_song)
272 self.control_panel.updateStatus()
273
274 def getQueueSize(self):
275 return len(self.playlist_panel.getChildren())
276
277 def radiocolCheckPreload(self, timestamp):
278 for player in self.players:
279 if player.timestamp == timestamp:
280 return False
281 return True
282
283 def radiocolPreload(self, timestamp, filename, title, artist, album, sender):
284 if not self.radiocolCheckPreload(timestamp):
285 return # song already preloaded
286 preloaded = False
287 for player in self.players:
288 if not player.filename or \
289 (player.played and player != self.current_player):
290 #if player has no file loaded, or it has already played its song
291 #we use it to preload the next one
292 player.preload(timestamp, filename, title, artist, album)
293 preloaded = True
294 break
295 if not preloaded:
296 log.warning("Can't preload song, we are getting too many songs to preload, we shouldn't have more than %d at once" % self.queue_data[1])
297 else:
298 self.pushNextSong(title)
299 self._parent.printInfo(_('%(user)s uploaded %(artist)s - %(title)s') % {'user': sender, 'artist': artist, 'title': title})
300
301 def radiocolPlay(self, filename):
302 found = False
303 for player in self.players:
304 if not found and player.filename == filename:
305 player.play()
306 self.popNextSong()
307 self.current_player = player
308 found = True
309 else:
310 player.play(False) # in case the previous player was not sync
311 if not found:
312 log.error("Song not found in queue, can't play it. This should not happen")
313
314 def radiocolNoUpload(self):
315 self.control_panel.blockUpload()
316
317 def radiocolUploadOk(self):
318 self.control_panel.unblockUpload()
319
320 def radiocolSongRejected(self, reason):
321 Window.alert("Song rejected: %s" % reason)