1
# Licensed under the MIT license
2
# http://opensource.org/licenses/mit-license.php
4
# Copyright 2008, Frank Scholz <coherence@beebits.net>
10
from coherence.upnp.core.soap_service import errorCode
11
from coherence.upnp.core import DIDLLite
15
from coherence.extern.simple_plugin import Plugin
17
from coherence import log
21
class RhythmboxPlayer(log.Loggable):
23
""" a backend to the Rhythmbox
26
logCategory = 'rb_media_renderer'
28
implements = ['MediaRenderer']
29
vendor_value_defaults = {'RenderingControl': {'A_ARG_TYPE_Channel':'Master'},
30
'AVTransport': {'A_ARG_TYPE_SeekMode':('ABS_TIME','REL_TIME')}}
31
vendor_range_defaults = {'RenderingControl': {'Volume': {'maximum':100}}}
33
def __init__(self, device, **kwargs):
34
self.warning("__init__ RhythmboxPlayer %r", kwargs)
35
self.shell = kwargs['shell']
40
self.name = "Rhythmbox on %s" % self.server.coherence.hostname
42
self.player = self.shell.get_player()
43
self.player.connect ('playing-song-changed',
44
self.playing_song_changed),
45
self.player.connect ('playing-changed',
47
self.player.connect ('elapsed-changed',
49
self.player.connect("notify::volume", self.volume_changed)
50
louie.send('Coherence.UPnP.Backend.init_completed', None, backend=self)
56
self.muted_volume = None
61
return str(self.__class__).split('.')[-1]
63
def volume_changed(self, player, parameter):
64
self.volume = self.player.props.volume
65
self.warning('volume_changed to %r', self.volume)
67
rcs_id = self.server.connection_manager_server.lookup_rcs_id(self.current_connection_id)
68
self.server.rendering_control_server.set_variable(rcs_id, 'Volume', self.volume*100)
70
def playing_song_changed(self, player, entry):
71
self.warning("playing_song_changed %r", entry)
72
if self.server != None:
73
connection_id = self.server.connection_manager_server.lookup_avt_id(self.current_connection_id)
75
self.update('STOPPED')
81
self.id = self.shell.props.db.entry_get (entry, rhythmdb.PROP_ENTRY_ID)
82
bitrate = self.shell.props.db.entry_get(entry, rhythmdb.PROP_BITRATE) * 1024 / 8
83
# Duration is in HH:MM:SS format
84
seconds = self.shell.props.db.entry_get(entry, rhythmdb.PROP_DURATION)
85
hours = seconds / 3600
86
seconds = seconds - hours * 3600
87
minutes = seconds / 60
88
seconds = seconds - minutes * 60
89
self.duration = "%02d:%02d:%02d" % (hours, minutes, seconds)
91
mimetype = self.shell.props.db.entry_get(entry, rhythmdb.PROP_MIMETYPE)
92
# This isn't a real mime-type
93
if mimetype == "application/x-id3":
94
mimetype = "audio/mpeg"
95
size = self.shell.props.db.entry_get(entry, rhythmdb.PROP_FILE_SIZE)
98
item = DIDLLite.MusicTrack(self.id + TRACK_COUNT)
99
item.album = self.shell.props.db.entry_get(entry, rhythmdb.PROP_ALBUM)
100
item.artist = self.shell.props.db.entry_get(entry, rhythmdb.PROP_ARTIST)
101
item.genre = self.shell.props.db.entry_get(entry, rhythmdb.PROP_GENRE)
102
item.originalTrackNumber = str(self.shell.props.db.entry_get (entry, rhythmdb.PROP_TRACK_NUMBER))
103
item.title = self.shell.props.db.entry_get(entry, rhythmdb.PROP_TITLE) # much nicer if it was entry.title
107
uri = self.shell.props.db.entry_get(entry, rhythmdb.PROP_LOCATION)
108
if uri.startswith("file://"):
109
location = unicode(urllib.unquote(uri[len("file://"):]))
111
# add a fake resource for the moment
112
res = DIDLLite.Resource(location, 'http-get:*:%s:*' % mimetype)
115
if self.duration > 0:
116
res.duration = self.duration
118
res.bitrate = str(bitrate)
121
elt = DIDLLite.DIDLElement()
123
self.metadata = elt.toString()
125
if self.server != None:
126
self.server.av_transport_server.set_variable(connection_id, 'AVTransportURIMetaData',self.metadata)
127
self.server.av_transport_server.set_variable(connection_id, 'CurrentTrackMetaData',self.metadata)
128
self.warning("playing_song_changed %r", self.metadata)
129
if self.server != None:
130
self.server.av_transport_server.set_variable(connection_id, 'RelativeTimePosition', '00:00:00')
131
self.server.av_transport_server.set_variable(connection_id, 'AbsoluteTimePosition', '00:00:00')
133
def playing_changed(self, player, state):
134
self.warning("playing_changed", state)
136
transport_state = 'PLAYING'
138
if self.playing is False:
139
transport_state = 'STOPPED'
141
transport_state = 'PAUSED_PLAYBACK'
142
self.update(transport_state)
144
position = player.get_playing_time()
148
duration = player.get_playing_song_duration()
151
self.update_position(position,duration)
152
self.warning("playing_changed %r %r ", position, duration)
154
def elapsed_changed(self, player, time):
155
self.warning("elapsed_changed %r %r", player, time)
157
duration = player.get_playing_song_duration()
160
self.update_position(time,duration)
162
def update(self, state):
164
self.warning("update %r", state)
166
if state in ('STOPPED','READY'):
167
transport_state = 'STOPPED'
168
if state == 'PLAYING':
169
transport_state = 'PLAYING'
170
if state == 'PAUSED_PLAYBACK':
171
transport_state = 'PAUSED_PLAYBACK'
173
if self.state != transport_state:
174
self.state = transport_state
175
if self.server != None:
176
connection_id = self.server.connection_manager_server.lookup_avt_id(self.current_connection_id)
177
self.server.av_transport_server.set_variable(connection_id,
182
def update_position(self, position,duration):
183
self.warning("update_position %r %r", position,duration)
185
if self.server != None:
186
connection_id = self.server.connection_manager_server.lookup_avt_id(self.current_connection_id)
187
self.server.av_transport_server.set_variable(connection_id, 'CurrentTrack', 0)
189
if position is not None:
190
m,s = divmod( position, 60)
192
if self.server != None:
193
self.server.av_transport_server.set_variable(connection_id, 'RelativeTimePosition', '%02d:%02d:%02d' % (h,m,s))
194
self.server.av_transport_server.set_variable(connection_id, 'AbsoluteTimePosition', '%02d:%02d:%02d' % (h,m,s))
199
if duration is not None:
200
m,s = divmod( duration, 60)
203
if self.server != None:
204
self.server.av_transport_server.set_variable(connection_id, 'CurrentTrackDuration', '%02d:%02d:%02d' % (h,m,s))
205
self.server.av_transport_server.set_variable(connection_id, 'CurrentMediaDuration', '%02d:%02d:%02d' % (h,m,s))
207
if self.duration is None:
208
if self.metadata is not None:
209
self.warning("update_position %r", self.metadata)
210
elt = DIDLLite.DIDLElement.fromString(self.metadata)
212
for res in item.findall('res'):
213
res.attrib['duration'] = "%d:%02d:%02d" % (h,m,s)
214
self.metadata = elt.toString()
216
if self.server != None:
217
self.server.av_transport_server.set_variable(connection_id, 'AVTransportURIMetaData',self.metadata)
218
self.server.av_transport_server.set_variable(connection_id, 'CurrentTrackMetaData',self.metadata)
220
self.duration = duration
222
def load( self, uri, metadata):
223
self.warning("player load %r %r", uri, metadata)
224
#self.shell.load_uri(uri,play=False)
226
self.metadata = metadata
229
was_playing = self.playing
231
if was_playing == True:
235
elt = DIDLLite.DIDLElement.fromString(metadata)
236
if elt.numItems() == 1:
237
item = elt.getItems()[0]
239
if uri.startswith('track-'):
240
self.entry = self.shell.props.db.entry_lookup_by_id(int(uri[6:]))
242
self.entry = self.shell.props.db.entry_lookup_by_location(uri)
243
self.warning("check for entry %r %r %r", self.entry,item.server_uuid,uri)
244
if self.entry == None:
245
if item.server_uuid is not None:
246
entry_type = self.shell.props.db.entry_register_type("CoherenceUpnp:" + item.server_uuid)
247
self.entry = self.shell.props.db.entry_new(entry_type, uri)
248
self.warning("create new entry %r", self.entry)
250
entry_type = self.shell.props.db.entry_register_type("CoherencePlayer")
251
self.entry = self.shell.props.db.entry_new(entry_type, uri)
252
self.warning("load and check for entry %r", self.entry)
259
duration = res.duration
261
bitrate = res.bitrate
264
self.shell.props.db.set(self.entry, rhythmdb.PROP_TITLE, item.title)
266
if item.artist is not None:
267
self.shell.props.db.set(self.entry, rhythmdb.PROP_ARTIST, item.artist)
268
except AttributeError:
271
if item.album is not None:
272
self.shell.props.db.set(self.entry, rhythmdb.PROP_ALBUM, item.album)
273
except AttributeError:
277
self.info("%r %r", item.title,item.originalTrackNumber)
278
if item.originalTrackNumber is not None:
279
self.shell.props.db.set(self.entry, rhythmdb.PROP_TRACK_NUMBER, int(item.originalTrackNumber))
280
except AttributeError:
283
if duration is not None:
284
h,m,s = duration.split(':')
285
seconds = int(h)*3600 + int(m)*60 + int(s)
286
self.info("%r %r:%r:%r %r", duration, h, m , s, seconds)
287
self.shell.props.db.set(self.entry, rhythmdb.PROP_DURATION, seconds)
290
self.shell.props.db.set(self.entry, rhythmdb.PROP_FILE_SIZE,int(size))
293
if uri.startswith('track-'):
294
self.entry = self.shell.props.db.entry_lookup_by_id(int(uri[6:]))
296
#self.shell.load_uri(uri,play=False)
297
#self.entry = self.shell.props.db.entry_lookup_by_location(uri)
298
entry_type = self.shell.props.db.entry_register_type("CoherencePlayer")
299
self.entry = self.shell.props.db.entry_new(entry_type, uri)
303
self.metadata = metadata
305
connection_id = self.server.connection_manager_server.lookup_avt_id(self.current_connection_id)
306
self.server.av_transport_server.set_variable(connection_id, 'CurrentTransportActions','Play,Stop,Pause,Seek')
307
self.server.av_transport_server.set_variable(connection_id, 'NumberOfTracks',1)
308
self.server.av_transport_server.set_variable(connection_id, 'CurrentTrackURI',uri)
309
self.server.av_transport_server.set_variable(connection_id, 'AVTransportURI',uri)
310
self.server.av_transport_server.set_variable(connection_id, 'AVTransportURIMetaData',metadata)
311
self.server.av_transport_server.set_variable(connection_id, 'CurrentTrackURI',uri)
312
self.server.av_transport_server.set_variable(connection_id, 'CurrentTrackMetaData',metadata)
314
if was_playing == True:
317
def start(self, uri):
322
self.warning("player stop")
326
#self.server.av_transport_server.set_variable( \
327
# self.server.connection_manager_server.lookup_avt_id(self.current_connection_id),\
328
# 'TransportState', 'STOPPED')
331
self.warning("player play")
333
if self.playing == False:
334
self.player.play_entry(self.entry)
337
self.player.playpause()
338
#self.server.av_transport_server.set_variable( \
339
# self.server.connection_manager_server.lookup_avt_id(self.current_connection_id),\
340
# 'TransportState', 'PLAYING')
344
#self.server.av_transport_server.set_variable( \
345
# self.server.connection_manager_server.lookup_avt_id(self.current_connection_id),\
346
# 'TransportState', 'PAUSED_PLAYBACK')
348
def seek(self, location, old_state):
350
@param location: simple number = time to seek to, in seconds
351
+nL = relative seek forward n seconds
352
-nL = relative seek backwards n seconds
354
self.warning("player seek %r", location)
355
self.player.seek(location)
356
self.server.av_transport_server.set_variable(0, 'TransportState', old_state)
359
self.muted_volume = self.volume
360
self.player.set_volume(0)
361
rcs_id = self.server.connection_manager_server.lookup_rcs_id(self.current_connection_id)
362
self.server.rendering_control_server.set_variable(rcs_id, 'Mute', 'True')
365
if self.muted_volume is not None:
366
self.player.set_volume(self.muted_volume)
367
self.muted_volume = None
368
self.player.set_mute(False)
369
rcs_id = self.server.connection_manager_server.lookup_rcs_id(self.current_connection_id)
370
self.server.rendering_control_server.set_variable(rcs_id, 'Mute', 'False')
373
return self.player.get_mute()
375
def get_volume(self):
376
self.volume = self.player.get_volume()
377
self.warning("get_volume %r", self.volume)
378
return self.volume * 100
380
def set_volume(self, volume):
381
self.warning("set_volume %r", volume)
388
self.player.set_volume(float(volume/100.0))
391
self.current_connection_id = None
392
self.server.connection_manager_server.set_variable(0, 'SinkProtocolInfo',
393
['rhythmbox:%s:audio/mpeg:*' % self.server.coherence.hostname,
394
'http-get:*:audio/mpeg:*'],
396
self.server.av_transport_server.set_variable(0, 'TransportState', 'NO_MEDIA_PRESENT', default=True)
397
self.server.av_transport_server.set_variable(0, 'TransportStatus', 'OK', default=True)
398
self.server.av_transport_server.set_variable(0, 'CurrentPlayMode', 'NORMAL', default=True)
399
self.server.av_transport_server.set_variable(0, 'CurrentTransportActions', '', default=True)
400
self.server.rendering_control_server.set_variable(0, 'Volume', self.get_volume())
401
self.server.rendering_control_server.set_variable(0, 'Mute', self.get_mute())
403
def upnp_Play(self, *args, **kwargs):
404
InstanceID = int(kwargs['InstanceID'])
405
Speed = int(kwargs['Speed'])
409
def upnp_Pause(self, *args, **kwargs):
410
InstanceID = int(kwargs['InstanceID'])
414
def upnp_Stop(self, *args, **kwargs):
415
InstanceID = int(kwargs['InstanceID'])
419
def upnp_Seek(self, *args, **kwargs):
420
InstanceID = int(kwargs['InstanceID'])
421
Unit = kwargs['Unit']
422
Target = kwargs['Target']
423
if Unit in ['ABS_TIME','REL_TIME']:
424
old_state = self.server.av_transport_server.get_variable(0, 'TransportState')
425
self.server.av_transport_server.set_variable(0, 'TransportState', 'TRANSITIONING')
426
h,m,s = Target.split(':')
427
seconds = int(h)*3600 + int(m)*60 + int(s)
428
self.seek(seconds, old_state)
431
def upnp_SetAVTransportURI(self, *args, **kwargs):
432
InstanceID = int(kwargs['InstanceID'])
433
CurrentURI = kwargs['CurrentURI']
434
CurrentURIMetaData = kwargs['CurrentURIMetaData']
435
local_protocol_infos=self.server.connection_manager_server.get_variable('SinkProtocolInfo').value.split(',')
436
#print '>>>', local_protocol_infos
437
if len(CurrentURIMetaData)==0:
438
self.load(CurrentURI,CurrentURIMetaData)
440
elt = DIDLLite.DIDLElement.fromString(CurrentURIMetaData)
441
#import pdb; pdb.set_trace()
442
if elt.numItems() == 1:
443
item = elt.getItems()[0]
444
res = item.res.get_matching(local_protocol_infos, protocol_type='rhythmbox')
446
res = item.res.get_matching(local_protocol_infos)
449
remote_protocol,remote_network,remote_content_format,_ = res.protocolInfo.split(':')
450
self.load(res.data,CurrentURIMetaData)
452
return failure.Failure(errorCode(714))
454
def upnp_SetMute(self, *args, **kwargs):
455
InstanceID = int(kwargs['InstanceID'])
456
Channel = kwargs['Channel']
457
DesiredMute = kwargs['DesiredMute']
458
if DesiredMute in ['TRUE', 'True', 'true', '1','Yes','yes']:
464
def upnp_SetVolume(self, *args, **kwargs):
465
InstanceID = int(kwargs['InstanceID'])
466
Channel = kwargs['Channel']
467
DesiredVolume = int(kwargs['DesiredVolume'])
468
self.set_volume(DesiredVolume)