~ubuntu-branches/ubuntu/trusty/coherence/trusty

« back to all changes in this revision

Viewing changes to misc/Rhythmbox Plugin/upnp_coherence/MediaPlayer.py

  • Committer: Bazaar Package Importer
  • Author(s): Arnaud Quette
  • Date: 2009-03-10 23:35:49 UTC
  • mfrom: (1.1.5 upstream) (3.1.2 sid)
  • Revision ID: james.westby@ubuntu.com-20090310233549-lw87agzwejt0wb3i
Tags: 0.6.2-1
* New upstream release  (Closes: #518391)
* Merge from Ubuntu (Charlie Smotherman)
* debian/control:
  - add Build-Depends on libjs-mochikit
  - add Vcs-Browser and Vcs-Svn fields
* debian/rules: add binary-predeb to remove Mochikit as it is a "courtesy
  copy of code" (Policy, section 4.13). This fixes the lintian error
  "embedded-javascript-library".
* debian/links: added for letter case compatibility on MochiKit
* debian/coherence.1, debian/applet-coherence.1: created
* debian/manpages: created to use the above manpages
* debian/examples: renamed from python-coherence.examples for consistency

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Licensed under the MIT license
2
 
# http://opensource.org/licenses/mit-license.php
3
 
 
4
 
# Copyright 2008, Frank Scholz <coherence@beebits.net>
5
 
 
6
 
import urllib
7
 
 
8
 
import rhythmdb
9
 
 
10
 
from coherence.upnp.core.soap_service import errorCode
11
 
from coherence.upnp.core import DIDLLite
12
 
 
13
 
import louie
14
 
 
15
 
from coherence.extern.simple_plugin import Plugin
16
 
 
17
 
from coherence import log
18
 
 
19
 
TRACK_COUNT = 1000000
20
 
 
21
 
class RhythmboxPlayer(log.Loggable):
22
 
 
23
 
    """ a backend to the Rhythmbox
24
 
 
25
 
    """
26
 
    logCategory = 'rb_media_renderer'
27
 
 
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}}}
32
 
 
33
 
    def __init__(self, device, **kwargs):
34
 
        self.warning("__init__ RhythmboxPlayer %r", kwargs)
35
 
        self.shell = kwargs['shell']
36
 
        self.server = device
37
 
 
38
 
        self.player = None
39
 
        self.metadata = None
40
 
        self.name = "Rhythmbox on %s" % self.server.coherence.hostname
41
 
 
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',
46
 
                                 self.playing_changed)
47
 
        self.player.connect ('elapsed-changed',
48
 
                                 self.elapsed_changed)
49
 
        self.player.connect("notify::volume", self.volume_changed)
50
 
        louie.send('Coherence.UPnP.Backend.init_completed', None, backend=self)
51
 
 
52
 
        self.playing = False
53
 
        self.state = None
54
 
        self.duration = None
55
 
        self.volume = 1.0
56
 
        self.muted_volume = None
57
 
        self.view = []
58
 
        self.tags = {}
59
 
 
60
 
    def __repr__(self):
61
 
        return str(self.__class__).split('.')[-1]
62
 
 
63
 
    def volume_changed(self, player, parameter):
64
 
        self.volume = self.player.props.volume
65
 
        self.warning('volume_changed to %r', self.volume)
66
 
        if self.volume > 0:
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)
69
 
 
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)
74
 
        if entry == None:
75
 
            self.update('STOPPED')
76
 
            self.playing = False
77
 
            #self.entry = None
78
 
            self.metadata = None
79
 
            self.duration = None
80
 
        else:
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)
90
 
 
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)
96
 
 
97
 
            # create item
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
104
 
 
105
 
            item.res = []
106
 
 
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://"):]))
110
 
 
111
 
                # add a fake resource for the moment
112
 
                res = DIDLLite.Resource(location, 'http-get:*:%s:*' % mimetype)
113
 
                if size > 0:
114
 
                    res.size = size
115
 
                if self.duration > 0:
116
 
                    res.duration = self.duration
117
 
                if bitrate > 0:
118
 
                    res.bitrate = str(bitrate)
119
 
                item.res.append(res)
120
 
 
121
 
            elt = DIDLLite.DIDLElement()
122
 
            elt.addItem(item)
123
 
            self.metadata = elt.toString()
124
 
            self.entry = entry
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')
132
 
 
133
 
    def playing_changed(self, player, state):
134
 
        self.warning("playing_changed", state)
135
 
        if state is True:
136
 
            transport_state = 'PLAYING'
137
 
        else:
138
 
            if self.playing is False:
139
 
                transport_state = 'STOPPED'
140
 
            else:
141
 
                transport_state = 'PAUSED_PLAYBACK'
142
 
        self.update(transport_state)
143
 
        try:
144
 
            position = player.get_playing_time()
145
 
        except:
146
 
            position = None
147
 
        try:
148
 
            duration = player.get_playing_song_duration()
149
 
        except:
150
 
            duration = None
151
 
        self.update_position(position,duration)
152
 
        self.warning("playing_changed %r %r ", position, duration)
153
 
 
154
 
    def elapsed_changed(self, player, time):
155
 
        self.warning("elapsed_changed %r %r", player, time)
156
 
        try:
157
 
            duration = player.get_playing_song_duration()
158
 
        except:
159
 
            duration = None
160
 
        self.update_position(time,duration)
161
 
 
162
 
    def update(self, state):
163
 
 
164
 
        self.warning("update %r", state)
165
 
 
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'
172
 
 
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,
178
 
                                                             'TransportState',
179
 
                                                             transport_state)
180
 
 
181
 
 
182
 
    def update_position(self, position,duration):
183
 
        self.warning("update_position %r %r", position,duration)
184
 
 
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)
188
 
 
189
 
        if position is not None:
190
 
            m,s = divmod( position, 60)
191
 
            h,m = divmod(m,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))
195
 
 
196
 
        if duration <= 0:
197
 
            duration = None
198
 
 
199
 
        if duration is not None:
200
 
            m,s = divmod( duration, 60)
201
 
            h,m = divmod(m,60)
202
 
 
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))
206
 
 
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)
211
 
                    for item in elt:
212
 
                        for res in item.findall('res'):
213
 
                            res.attrib['duration'] = "%d:%02d:%02d" % (h,m,s)
214
 
                    self.metadata = elt.toString()
215
 
 
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)
219
 
 
220
 
                self.duration = duration
221
 
 
222
 
    def load( self, uri, metadata):
223
 
        self.warning("player load %r %r", uri, metadata)
224
 
        #self.shell.load_uri(uri,play=False)
225
 
        self.duration = None
226
 
        self.metadata = metadata
227
 
        self.tags = {}
228
 
 
229
 
        was_playing = self.playing
230
 
 
231
 
        if was_playing == True:
232
 
            self.stop()
233
 
 
234
 
        if len(metadata)>0:
235
 
            elt = DIDLLite.DIDLElement.fromString(metadata)
236
 
            if elt.numItems() == 1:
237
 
                item = elt.getItems()[0]
238
 
 
239
 
                if uri.startswith('track-'):
240
 
                    self.entry = self.shell.props.db.entry_lookup_by_id(int(uri[6:]))
241
 
                else:
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)
249
 
                    else:
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)
253
 
 
254
 
                duration = None
255
 
                size = None
256
 
                bitrate = None
257
 
                for res in item.res:
258
 
                    if res.data == uri:
259
 
                        duration = res.duration
260
 
                        size = res.size
261
 
                        bitrate = res.bitrate
262
 
                        break
263
 
 
264
 
                self.shell.props.db.set(self.entry, rhythmdb.PROP_TITLE, item.title)
265
 
                try:
266
 
                    if item.artist is not None:
267
 
                        self.shell.props.db.set(self.entry, rhythmdb.PROP_ARTIST, item.artist)
268
 
                except AttributeError:
269
 
                    pass
270
 
                try:
271
 
                    if item.album is not None:
272
 
                        self.shell.props.db.set(self.entry, rhythmdb.PROP_ALBUM, item.album)
273
 
                except AttributeError:
274
 
                    pass
275
 
 
276
 
                try:
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:
281
 
                    pass
282
 
 
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)
288
 
 
289
 
                if size is not None:
290
 
                    self.shell.props.db.set(self.entry, rhythmdb.PROP_FILE_SIZE,int(size))
291
 
 
292
 
        else:
293
 
            if uri.startswith('track-'):
294
 
                self.entry = self.shell.props.db.entry_lookup_by_id(int(uri[6:]))
295
 
            else:
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)
300
 
 
301
 
 
302
 
        self.playing = False
303
 
        self.metadata = metadata
304
 
 
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)
313
 
 
314
 
        if was_playing == True:
315
 
            self.play()
316
 
 
317
 
    def start(self, uri):
318
 
        self.load(uri)
319
 
        self.play()
320
 
 
321
 
    def stop(self):
322
 
        self.warning("player stop")
323
 
 
324
 
        self.player.stop()
325
 
        self.playing = False
326
 
        #self.server.av_transport_server.set_variable( \
327
 
        #    self.server.connection_manager_server.lookup_avt_id(self.current_connection_id),\
328
 
        #                     'TransportState', 'STOPPED')
329
 
 
330
 
    def play(self):
331
 
        self.warning("player play")
332
 
 
333
 
        if self.playing == False:
334
 
            self.player.play_entry(self.entry)
335
 
            self.playing = True
336
 
        else:
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')
341
 
 
342
 
    def pause(self):
343
 
        self.player.pause()
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')
347
 
 
348
 
    def seek(self, location, old_state):
349
 
        """
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
353
 
        """
354
 
        self.warning("player seek %r", location)
355
 
        self.player.seek(location)
356
 
        self.server.av_transport_server.set_variable(0, 'TransportState', old_state)
357
 
 
358
 
    def mute(self):
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')
363
 
 
364
 
    def unmute(self):
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')
371
 
 
372
 
    def get_mute(self):
373
 
        return self.player.get_mute()
374
 
 
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
379
 
 
380
 
    def set_volume(self, volume):
381
 
        self.warning("set_volume %r", volume)
382
 
        volume = int(volume)
383
 
        if volume < 0:
384
 
            volume=0
385
 
        if volume > 100:
386
 
            volume=100
387
 
 
388
 
        self.player.set_volume(float(volume/100.0))
389
 
 
390
 
    def upnp_init(self):
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:*'],
395
 
                            default=True)
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())
402
 
 
403
 
    def upnp_Play(self, *args, **kwargs):
404
 
        InstanceID = int(kwargs['InstanceID'])
405
 
        Speed = int(kwargs['Speed'])
406
 
        self.play()
407
 
        return {}
408
 
 
409
 
    def upnp_Pause(self, *args, **kwargs):
410
 
        InstanceID = int(kwargs['InstanceID'])
411
 
        self.pause()
412
 
        return {}
413
 
 
414
 
    def upnp_Stop(self, *args, **kwargs):
415
 
        InstanceID = int(kwargs['InstanceID'])
416
 
        self.stop()
417
 
        return {}
418
 
 
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)
429
 
        return {}
430
 
 
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)
439
 
        else:
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')
445
 
                if len(res) == 0:
446
 
                    res = item.res.get_matching(local_protocol_infos)
447
 
                if len(res) > 0:
448
 
                    res = res[0]
449
 
                    remote_protocol,remote_network,remote_content_format,_ = res.protocolInfo.split(':')
450
 
                    self.load(res.data,CurrentURIMetaData)
451
 
                    return {}
452
 
        return failure.Failure(errorCode(714))
453
 
 
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']:
459
 
            self.mute()
460
 
        else:
461
 
            self.unmute()
462
 
        return {}
463
 
 
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)
469
 
        return {}