Exemple #1
0
    def __init__(self, cover_dir, net_type='lan'):

        self.connected = False
        self.net_type = net_type
        self.proto = MpdProtocol()
        self.proto._event = self.dispatch_event
        self.status = {'state': 'stop'}
        self.playlist = Playlist(self)
        self.waiting = False
        self.calls = []
        self.cover_dir = cover_dir
        self._sources = [
            OrderedDict(
                sorted({
                    'Name': '_'.join((self.name, n)),
                    'Type': n,
                    'Visible': True
                }.items(),
                       key=lambda t: t[0])) for n in self.sources
        ]
        self.sourcexml = dict2xml(
            {'SourceList': [{
                'Source': n
            } for n in self._sources]})
        self.protocolinfo = self.mtlist = 'http-get:*:' + ''\
            .join([':*,http-get:*:'.join(self.mimetypes)]) + ':*'
Exemple #2
0
    def __init__(self, cover_dir, net_type='lan'):

        self.connected = False
        self.net_type = net_type
        self.proto = MpdProtocol()
        self.proto._event = self.dispatch_event
        self.status = {'state': 'stop'}
        self.playlist = Playlist(self)
        self.waiting = False
        self.calls = []
        self.cover_dir = cover_dir
        self._sources = [OrderedDict(sorted(
            {'Name': '_'.join((self.name, n)),
             'Type': n,
             'Visible': True}.
            items(), key=lambda t: t[0])) for n in self.sources]
        self.sourcexml = dict2xml(
            {'SourceList': [{'Source': n} for n in self._sources]})
        self.protocolinfo = self.mtlist = 'http-get:*:' + ''\
            .join([':*,http-get:*:'.join(self.mimetypes)]) + ':*'
Exemple #3
0
class Mpd_factory(ReconnectingClientFactory, Client):
    '''
    Twisted Reconnecting client factory for mpd server
    convert all UPnP and OpenHome service function to mpd remote calls
    '''

    _mtlist = ''
    _muted = True
    _rate = "1"
    _state = "pending"
    _track_duration = '0:00:00.000'
    ohduration = 0
    _track_URI = ''
    _volume = 100
    active_source = 0
    attributes = 'Info Time Volume'
    balance = 0
    balancemax = 10
    bitdepth = 0
    bitrate = 0
    cancelplay = False
    codecname = 'MP3'
    counter = 0
    detailscount = 0
    fade = 0
    fademax = 10
    idArray = ''
    lossless = False
    maxid = 0
    max_volume = 100
    metadata = {}
    metadata_str = 'NOT_IMPLEMENTED'
    metatext = ''
    metatextcount = 0
    mimetypes = [
        'audio/aac', 'audio/mp4', 'audio/mpeg', 'audio/mpegurl',
        'audio/vnd.rn-realaudio', 'audio/vorbis', 'audio/x-flac',
        'audio/x-mp3', 'audio/x-mpegurl', 'audio/x-ms-wma', 'audio/x-musepack',
        'audio/x-oggflac', 'audio/x-pn-realaudio', 'audio/x-scpls',
        'audio/x-speex', 'audio/x-vorbis', 'audio/x-wav',
        'application/x-ogm-audio', 'audio/x-vorbis+ogg', 'audio/ogg'
    ]
    mtlist = ''
    name = "MpdPlayer"
    oh_state = 'Buffering'
    old_vol = 0
    parent = None
    reltime = '0:00:00.000'
    repeat = False
    room = 'Home'
    samplerate = 0
    seconds = 0
    shuffle = False
    songid = 0
    sources = ['Playlist']
    sourceindex = 0
    upnp_state = "TRANSITIONNING"
    volumeunity = 3
    volumemillidbperstep = 600
    timer = None
    token = 0
    trackcount = 0
    tracksmax = 0
    transport_actions = ['PLAY']

    #     ohduration = 0

    def __init__(self, cover_dir, net_type='lan'):

        self.connected = False
        self.net_type = net_type
        self.proto = MpdProtocol()
        self.proto._event = self.dispatch_event
        self.status = {'state': 'stop'}
        self.playlist = Playlist(self)
        self.waiting = False
        self.calls = []
        self.cover_dir = cover_dir
        self._sources = [
            OrderedDict(
                sorted({
                    'Name': '_'.join((self.name, n)),
                    'Type': n,
                    'Visible': True
                }.items(),
                       key=lambda t: t[0])) for n in self.sources
        ]
        self.sourcexml = dict2xml(
            {'SourceList': [{
                'Source': n
            } for n in self._sources]})
        self.protocolinfo = self.mtlist = 'http-get:*:' + ''\
            .join([':*,http-get:*:'.join(self.mimetypes)]) + ':*'

    def buildProtocol(self, addr):
        return self.proto

    '''
    Internal functions
    '''

    def dispatch_event(self, events=None):
        def set_connected():
            self.connected = True
            p = self.call('plchanges', self.playlist.plsversion)
            p.addCallback(self.playlist.update)

        d = None
        for evt in events:
            if evt == 'Error':
                log.error('bad event: {name}', name=events[evt])
                return
            elif evt == 'changed':
                chg = events[evt]
            else:
                #                 log.msg(events[evt])
                return
            if chg == 'disconnected':
                self.connected = False
                continue
            if chg == 'playlist':
                log.error('update from: %s' % self.playlist.plsversion)
                d = self.call('plchanges', self.playlist.plsversion)
                d.addCallback(self.playlist.update)
            else:
                d = self.call('status')
                if chg == 'connected':
                    d.addCallback(self.filter_event)
                    reactor.callLater(1, set_connected)  # @UndefinedVariable
                elif chg == 'mixer':
                    d.addCallback(self.filter_event, 'volume')
                elif chg == 'player':
                    d.addCallback(self.filter_event)
            return d

    def filter_event(self, data, filtr=None):
        if data is None:
            return
        if not isinstance(data, list):
            data = [data]
        if filtr is not None:
            for d in data:
                self.status.update(d)
            self.changed_state({filtr: self.status[filtr]})
        else:
            dic = {}
            for d in data:
                for it in d.iteritems():
                    if it not in self.status.items():
                        self.status.update(dict([it]))
                        dic.update(dict([it]))
                        if it[0] == 'playlistlength':
                            self.playlist.length = int(it[1])
            self.changed_state(dic)

    def call(self, command=None, params=None):
        #         log.error('register %s' % command)
        d = defer.Deferred()
        if command is not None:
            self.proto.addCallback(d)
        if not self.connected:
            if command:
                self.calls.append((command, params))
        else:
            if len(self.calls) > 0:
                if command:
                    self.calls.append((command, params))
                else:
                    d = None
                command, params = self.calls.pop(0)
            if params is not None:
                c = ' '.join([command, str(params)]).encode('utf-8')
            else:
                c = command.encode('utf-8')
            if self.proto.idle:
                self.proto.noidle()
            self.proto.sendLine(c)
            #             log.error('send %s' % c)
            if len(self.calls) > 0:
                self.call()
        return d

    def changed_state(self, state):
        changed = state.keys()
        if 'state' in changed:
            self.set_state(state['state'])
        if "volume" in changed:
            log.debug('volume changed')
            vol = int(state['volume'])
            if vol != self._volume:
                if vol != 0:
                    self._volume = vol
                    muted = False
                else:
                    muted = True
                log.debug('send volume')
                self.oh_eventVOLUME(self._volume, 'volume')
                self.upnp_eventRCS(self._volume, 'volume')
                if muted is not self._muted:
                    self._muted = muted
                    self.upnp_eventRCS(self._muted, 'mute')
                self.oh_eventVOLUME(int(self._muted), 'mute')

        if 'songid' in changed:
            self.playlist.current_track(state['songid'])
        if 'repeat' in changed:
            #             log.debug('************repeat***********: %s' %
            #                 bool(int(state['repeat'])))
            if self.repeat != bool(int(state['repeat'])):
                self.repeat = bool(int(state['repeat']))
                if not self.shuffle:
                    self.upnp_eventAV(
                        'REPEAT_ALL' if self.repeat else 'NORMAL',
                        'currentplayMode')
                else:
                    self.upnp_eventAV(
                        'REPEAT_ALL SHUFFLE' if self.repeat else
                        'NORMAL SHUFFLE', 'currentplaymode')
                self.oh_eventPLAYLIST(self.repeat, 'repeat')
        if 'random' in changed:
            log.debug('************shuffle***********: %s' %
                      bool(int(state['random'])))
            if self.shuffle != bool(int(state['random'])):
                self.shuffle = bool(int(state['random']))
                if not self.repeat:
                    self.upnp_eventAV(
                        'NORMAL SHUFFLE' if self.shuffle else 'NORMAL',
                        'currentplaymode')
                else:
                    self.upnp_eventAV(
                        'REPEAT_ALL SHUFFLE' if self.shuffle else
                        'NORMAL SHUFFLE', 'currentplaymode')
                self.oh_eventPLAYLIST(self.shuffle, 'shuffle')
        if 'elapsed' in changed:
            if self.timer is not None:
                self.timer.set(float(state['elapsed']))
        if 'playlist' in changed:
            self.token = int(state['playlist'])
#             self.playlist.plsversion = state['playlist']
        if 'playlistdata' in changed:
            self.playlist.update(state['playlistdata'])
        if 'bitrate' in changed:
            self.detailscount += 1
            self.bitrate = int(state['bitrate'])
            self.oh_eventINFO(self.bitrate, 'bitrate')
        if 'audio' in changed:
            try:
                sr = int(state['audio'].split(':')[0])
                self.samplerate = sr
                self.oh_eventINFO(sr, 'samplerate')
            except:
                log.critical('Bad Samplerate: %s' %
                             state['audio'].split(':')[0])
            try:
                bd = int(state['audio'].split(':')[1])
                self.bitdepth = bd
                self.oh_eventINFO(bd, 'bitdepth')
            except:
                log.critical('Bad Bitdepth: %s' % state['audio'].split(':')[1])

    def update_state(self):
        #  log.err('Update State: %s' % self.mpd.status['state'])
        self.set_state(self.status['state'])

    def update_mimetypes(self):
        self.set_mimetypes(self.mimetypes)

    def upnp_eventAV(self, evt, var):
        pass

    def upnp_eventCM(self, evt, var):
        pass

    def upnp_eventRCS(self, evt, var):
        pass

    def oh_eventPLAYLIST(self, evt, var):
        pass

    def oh_eventINFO(self, evt, var):
        pass

    def oh_eventTIME(self, evt, var):
        pass

    def oh_eventVOLUME(self, evt, var):
        pass

    def getMetadata(self):
        return self.call('playlistid', self.status['songid'])

    def getMimeTypes(self):
        return self.call('decoders')

    def get_state(self):
        return self._state

    def get_rate(self):
        return self._rate

    def get_track_duration(self):
        try:
            duration = self._track_duration
        except:
            duration = '00:00:00'
        return duration

    def get_track_URI(self):
        try:
            uri = self._track_URI
        except:
            uri = ''
        return uri

    def get_track_md(self):
        return self.metadata_str

    def get_sticker(self, url, dic={}):
        def got_sticker(stklist, dic):
            if not isinstance(stklist, list):
                stklist = [stklist]
            for sticker in stklist:
                try:
                    dic.update({
                        sticker['sticker'].split('=')[0],
                        sticker['sticker'].split('=')[1]
                    })
                except:
                    continue
            return dic

        d = self.call('sticker', ' '.join(('list song', url.join('"' * 2))))
        d.addBoth(got_sticker, dic)
        return d

    def get_relcount(self):
        return 2147483647

    def get_abscount(self):
        return 2147483647

    def get_abstime(self):
        return '00:00:00'

    def get_reltime(self, fmt='UPNP'):
        if self.timer is not None:
            if fmt == 'UPNP':
                t = mpdtime_to_upnptime(self.timer.get())
            elif fmt == 'seconds':
                t = int(self.timer.get())
            else:
                # msec
                t = self.timer.get()
        else:
            if fmt == 'UPNP':
                t = self.reltime
            else:
                t = self.seconds
        return t

    def get_volume(self):
        def noVolume(err):
            if self._muted:
                return 0
            else:
                return self._volume

        def convert_volume(vol):
            self._volume = int(float(vol) * 100)
            return self._volume

        d = self.mediaplayer.get("Volume",
                                 interf='org.mpris.MediaPlayer2.Player')
        d.addCallbacks(convert_volume, noVolume)
        return d

    def get_transport_actions(self):
        return {','.join(self.transport_actions)}

    def set_mimetypes(self, mtlist):
        if self._mtlist != mtlist:
            self._mtlist = mtlist
            self.mtlist = 'http-get:*:' + ''\
                .join([':*,http-get:*:'.join(mtlist)]) + ':*'
            self.upnp_eventCM(self.mtlist, 'sinkprotocolinfo')
            self.oh_eventPLAYLIST(self.mtlist, 'protocolinfo')

    def set_metadata(self, metadata):
        if metadata != self.metadata:
            self.metadata.update(metadata)
            if 'duration' in metadata.keys():
                self._track_duration = metadata['duration']
            if 'url' in metadata.keys():
                self._track_URI = metadata['url']
            if 'file' in metadata.keys():
                self._track_URI = metadata['file']

    def set_reltime(self, t):
        self.seconds = int(float(t))
        self.reltime = mpdtime_to_upnptime(t)
        #         log.err('seconds: %s' % t)
        self.timer = None

    def set_state(self, state):
        log.debug("SET NEW STATE : %s " % state)
        if state == self._state:
            return
        self._state = state
        if state == 'pause':
            #             print(self.transport_actions)
            self.transport_actions = ['PLAY', 'STOP']
            #                 self.transport_actions.remove('PAUSE')
            #                 self.transport_actions.append('PLAY')
            if self.timer is not None:
                self.timer.stop()
                d = self.call('status')
                d.addCallback(lambda st: st['elapsed'])
                d.addCallbacks(
                    self.set_reltime,
                    lambda ignored: self.set_reltime(self.timer.get()))
            self.upnp_state = 'PAUSED_PLAYBACK'
            self.oh_state = 'Paused'
        elif state == 'play':
            self.transport_actions = ['STOP', 'PAUSE', 'SEEK']
            self.changed_state({'volume': self.status['volume']})
            self.timer = Timer()
            self.timer.set(self.seconds)
            d = self.call('status')
            d.addCallbacks(lambda st: st['elapsed'])
            d.addCallbacks(lambda t: self.timer.set(float(t)),
                           lambda ignored: self.timer.set(0.000))
            self.upnp_state = 'PLAYING'
            self.oh_state = 'Playing'
        elif state == 'stop':
            self.transport_actions = ['PLAY']
            self.set_reltime(0)
            self.upnp_state = 'STOPPED'
            self.oh_state = 'Stopped'
        elif state == 'pending':
            self.upnp_state = 'TRANSITIONNING'
            self.oh_state = 'Buffering'
        else:
            return
        self.upnp_eventAV(self.upnp_state, 'transportstate')
        self.oh_eventPLAYLIST(self.oh_state, 'transportstate')

    def set_songid(self, songid):
        self.songid = songid['Id']
        return self.songid

    def set_position(self, newpos, fmt='UPNP'):
        def transition(obj):
            current_state = self._state
            self.set_state('pending')
            reactor.callLater(
                0.5,  # @UndefinedVariable
                self.set_state,
                current_state)

        if fmt == 'UPNP':
            pos = upnptime_to_mpdtime(newpos)
        else:
            pos = newpos
        log.debug('seek to %s' % pos)
        d = self.call('seekcur', pos)
        d.addCallback(transition)
        return d

    def set_position_relative(self, delta, fmt='UPNP'):
        newpos = int(self.get_reltime('Seconds')) + int(delta)
        self.set_position(newpos, fmt)

    def set_track_URI(self, uri, md=''):
        log.debug("set track uri : %s " % uri)
        try:
            log.debug("current uri : %s " % self._track_URI)
        except:
            pass
        if uri != self._track_URI:
            self._track_URI = uri
            self.metadata_str = md
            self.set_metadata(didl_decode(md.encode('utf-8')))
            d = self.call('addid', uri)
            d.addCallback(self.set_songid)
            d.addCallback(self.play)

    def playing(self, *ret):
        log.debug('playing...')
        self.set_state('play')

    def playindex(self, index):
        return self.play(songid=self.playlist[int(index)])

    def playpause(self):
        if self._state == 'pause':
            return self.play()
        else:
            return self.pause()

    def insert_metadata(self, md):
        dic = didl_decode(md)
        #             log.err(dic)
        for i, tag in enumerate(dic.keys()):
            if tag.lower() in ('class', 'restricted', 'id', 'duration',
                               'parentid', 'protocolinfo', 'url', 'ownerudn'):
                continue
            if not isinstance(dic[tag], str):
                continue
            reactor.callLater(  # @UndefinedVariable
                float(i) / 2, self.call, ' '.join(
                    ('sticker', 'set song', dic['url'].join('"' * 2), tag,
                     '"' + dic[tag] + '"')))

    def delete(self, songid):
        self.call('deleteid', str(songid))

    def clear(self):
        self.call('clear')

    def volUp(self):
        if self._volume == 0:
            vol = self.r_get_volume()
            try:
                newvol = vol + 5
            except:
                newvol = self._volume + 5
            return self.r_set_volume(newvol, 'Master')
        else:
            if self._volume > 95:
                return
            newvol = self._volume + 5
            return self.r_set_volume(newvol, 'Master')

    def volDown(self):
        if self._volume > 5:
            newvol = self._volume - 5
            return self.r_set_volume(newvol, 'Master')
        else:
            newvol = self._volume
            self._volume = 0
            return self.r_set_volume(newvol, '0')

    '''
    UPNP wrapped functions
    '''

    # onDemand vendor method

    def r_get_room(self):
        return self.room

    '''
    AVTransport and OpenHome Playlist
    '''

    def r_delete_all(self):
        return self.clear()

    def r_delete_id(self, value):
        return self.delete(value)

    def r_get_current_transport_actions(self, instanceID):
        return self.get_transport_actions()

    def r_get_media_info(self, instanceID):
        return (
            str(len(self.playlist)),
            self.get_track_duration(),
            self.get_track_URI(),
            self.get_track_md(),
            '',
            '',
            'UNKNOWN',
            'UNKNOWN',
            'UNKNOWN',
        )

    def r_get_media_info_ext(self, instanceID):
        return (
            str(len(self.playlist)),
            'TRACK_AWARE',
            self.get_track_duration(),
            self.get_track_URI(),
            self.get_track_md(),
            '',
            '',
            'UNKNOWN',
            'UNKNOWN',
            'UNKNOWN',
        )

    def r_get_position_info(self, instanceID):
        return (
            self.player.get_track(),
            self.get_track_duration(),
            self.get_track_md(),
            self.player.get_track_URI(),
            self.player.get_reltime(),
            self.player.get_abstime(),
            self.player.get_relcount(),
            self.player.get_abscount(),
        )

    def r_get_transport_info(self, instanceID):
        return (
            self.get_state(),
            'OK',
            self.get_rate(),
        )

    def r_id(self):
        return self.songid

    def r_id_array(self):
        return (
            self.token,
            self.idArray,
        )

    def r_id_array_changed(self, token):
        if token != self.token:
            return 1
        return 0

    def r_insert(self, afterid, url, metadata, checked=False):
        log.debug('Insert :%s  --  %s  --  %s' % (afterid, url, metadata))

        def inserted(res, md):
            log.debug(res)
            return res['Id']

        if 'youtube' in url and not checked:
            # eclipse workaround !!!
            y = os.environ
            y.update({'PYTHONPATH': '/usr/bin/python'})
            # /eclipse workaround
            d = utils.getProcessOutput('/usr/bin/youtube-dl',
                                       ['-g', '-f', 'bestaudio', url],
                                       env=y,
                                       reactor=reactor)
            d.addCallback(lambda u: self.insert(
                u.split('\n')[0], afterid, metadata, True))
            return d
        if len(self.playlist.tracks) == 0:
            d = self.call('addid', url)
        elif int(afterid) == 0:
            d = self.call('addid', url + ' 0')
        else:
            log.critical('crash ? %s' % self.playlist.tracks)
            log.critical('here ? %s' %
                         str(self.playlist.tracks.index(int(afterid)) + 1))
            d = self.call(
                'addid', ' '.join(
                    (url.encode('utf-8'),
                     str(self.playlist.tracks.index(int(afterid)) + 1))))
        d.addCallback(inserted, metadata)
        return d

    def r_next(self, instanceID=0):
        if self._state not in ('play', 'pause'):
            if self.songid == 0:
                self.r_play(1)
            else:
                self.r_play()
        else:
            self.call('next')

    def r_play(self, instanceID=0, speed=1, songid=None, ignored=None):

        log.debug('entering play...')

        def success(result):
            return None

        if self.cancelplay:
            self.cancelplay = False
        else:
            if songid is not None:
                #                 log.err(songid)
                d = self.call('playid', str(songid))
            else:
                if self._state == 'pause':
                    d = self.call('pause', '0')
                else:
                    d = self.call('playid', self.songid)
            d.addCallback(self.playing)

    def r_pause(self, instanceID=0):
        print('pause')

        def paused(ret):
            if self._state == 'play':
                self.set_state('pause')

        d = self.call('pause', '1')
        d.addCallback(paused)
        return d

    def r_previous(self, instanceID=0):
        self.call('previous')

    def r_protocol_info(self):
        return self.protocolinfo

    def r_read(self, value):
        log.debug('read')
        d = self.playlist.get_track(value)
        return (d, )

    def r_read_list(self, items):
        log.debug('readlist')
        d = self.playlist.get_tracks([int(track) for track in items.split()])
        return d

    def r_repeat(self):
        return self.repeat

    def r_record(self, instanceID):
        raise NotImplementedError()

    def r_seek(self, instanceID, unit, pos):
        log.debug('seek: %s %s' % (unit, pos))
        self.set_position(pos)

    def r_seek_id(self, value):
        log.debug('SeekId')
        return self.r_play(songid=value)

    def r_seek_index(self, value):
        log.debug('Seekindex')
        return self.playindex(value)

    def r_seek_second_absolute(self, value):
        return self.set_position(value, 'SECONDS')

    def r_seek_second_relative(self, value):
        return self.set_position_relative(value, 'SECONDS')

    def r_set_repeat(self, repeat):
        self.call('repeat', str(int(repeat)))
        self.changed_state({'repeat': str(int(repeat))})

    def r_set_shuffle(self, shuffle):
        self.call('random', str(int(shuffle)))
        self.changed_state({'random': str(int(shuffle))})

    def r_set_avtransport_uri(self, instanceID, uri, uri_metadata):
        self.set_track_URI(uri, uri_metadata)

    def r_shuffle(self):
        return self.shuffle

    def r_stop(self, instanceID=0):
        def stopped(ret):
            self.set_state('stop')

        if self._state != 'STOPPED':
            d = self.call('stop')
            self.reltime = '00:00:00'
            d.addCallback(stopped)

    def r_tracks_max(self):
        return self.tracksmax

    def r_transport_state(self, instanceID=0):
        if self.parent.type == 'Source':
            return self.oh_state
        return self.upnp_state

    '''
    OpenHome Info
    '''

    def r_counters(self):
        return (
            self.trackcount,
            self.detailscount,
            self.metatextcount,
        )

    def r_track(self):
        return (
            self._track_URI,
            self.metadata_str,
        )

    def r_details(self):
        return (
            self.ohduration,
            self.bitrate,
            self.bitdepth,
            self.samplerate,
            self.lossless,
            self.codecname,
        )

    def r_metatext(self):
        return self.metatext

    '''
    Rendering Control and Open Home Volume
    '''

    def r_volume(self):
        return self._volume

    def r_set_volume(self, volume, channel=0):
        volume = str(volume)
        d = self.call('setvol', volume)
        d.addErrback(log.critical,
                     'Set Volume Error : %s - %d' % (channel, int(volume)))
        reactor.callLater(
            0.1,  # @UndefinedVariable
            self.changed_state,
            {'volume': str(volume)})

    def r_volume_inc(self):
        return self.volUp()

    def r_volume_dec(self):
        return self.volDown()

    def r_volume_limit(self):
        return self.max_volume

    def r_balance(self):
        return self.balance

    def r_balance_inc(self):
        # TODO
        self.balance += 1

    def r_balance_dec(self):
        # TODO
        self.balance -= 1

    def r_set_fade(self, value):
        # TODO
        self.fade = int(value)

    def r_fade_inc(self):
        # TODO
        self.fade += 1

    def r_fade_dec(self):
        # TODO
        self.fade -= 1

    def r_mute(self):
        return self._muted

    def r_set_mute(self, mute):
        if mute is not self._muted:
            self._muted = mute
            if mute:
                self.old_vol = self._volume
                self.r_set_volume('0')
            else:
                self.r_set_volume(self.old_vol)

    def r_characteristics(self):
        return self.max_volume, self.volumeunity, self.max_volume,\
            self.volumemillidbperstep, self.balancemax, self.fademax

    '''
    OpenHome Time
    '''

    def r_time(self):
        return (self.trackcount, self.ohduration, self.get_reltime('seconds'))

    '''
    OpenHome Product
    '''

    def r_manufacturer(self=None):
        log.debug('Manufacturer from Product')
        return (
            self.parent.manufacturer,
            self.parent.manufacturerInfo,
            self.parent.manufacturerURL,
            ''.join((self.parent.getLocation(get_default_v4_address()),
                     '/pictures/icon.png')),
        )

    def r_model(self=None):
        log.debug('Model from Product')
        return (self.parent.modelName, self.parent.modelDescription,
                self.parent.manufacturerURL, ''.join((
                    self.parent.getLocation(get_default_v4_address()),
                    '/pictures/',
                    self.parent.modelName,
                    '.png',
                )))

    def r_product(self):
        log.debug('Product from Product')
        return self.room, self.parent.modelName, self.parent.modelDescription,\
            self.parent.manufacturerURL,\
            ''.join((self.parent.getLocation(get_default_v4_address()),
                     '/pictures/', self.parent.modelName, '.png',))

    def r_standby(self):
        log.debug('Standby from Product')
        return self.standby

    def r_set_standby(self, val=None):
        log.debug('SetStandby from Product')
        if val is None:
            return self.standby
        raise NotImplementedError()

    def r_source_count(self):
        log.debug('SourceCount from Product')
        return len(self.sources)

    def r_source_xml(self, *args, **kwargs):
        log.debug('SourceXml from Product')
        return self.sourcexml


# return dict2xml({'SourceList': [{'Source': n} for n in self.sources]})

    def r_source_index(self):
        log.debug('SourceIndex from Product')
        return self.sourceindex

    def r_set_source_index(self, idx=None):
        log.debug('SetSourceIndex from Product')
        if idx is None:
            return self.sourceindex
        else:
            try:
                self.sourceindex = int(idx)
                self.oh_product_event('sourceindex', self.sourceindex)
            except:
                for i, source in enumerate(self.sources.keys()):
                    if source['Name'] == idx:
                        self.sourceindex = i
                        self.oh_product_event('sourceindex', self.sourceindex)
                        return
                    log.critical('Unknown Source: %s' % idx)

    def r_set_source_index_by_name(self, value):
        log.debug('SetSourceIndexByName from Product')
        return self.set_source_index(value)

    def r_source(self, idx):
        idx = int(idx)
        return (
            self.parent.sources[idx].friendlyName,
            self.parent.sources[idx].type,
            True,
            self.parent.sources[idx].name,
        )

    def r_attributes(self):
        return self.attributes

    def r_source_xml_change_count(self):
        raise NotImplementedError()
Exemple #4
0
class Mpd_factory(ReconnectingClientFactory, Client):
    '''
    Twisted Reconnecting client factory for mpd server
    convert all UPnP and OpenHome service function to mpd remote calls
    '''

    _mtlist = ''
    _muted = True
    _rate = "1"
    _state = "pending"
    _track_duration = '0:00:00.000'
    ohduration = 0
    _track_URI = ''
    _volume = 100
    active_source = 0
    attributes = 'Info Time Volume'
    balance = 0
    balancemax = 10
    bitdepth = 0
    bitrate = 0
    cancelplay = False
    codecname = 'MP3'
    counter = 0
    detailscount = 0
    fade = 0
    fademax = 10
    idArray = ''
    lossless = False
    maxid = 0
    max_volume = 100
    metadata = {}
    metadata_str = 'NOT_IMPLEMENTED'
    metatext = ''
    metatextcount = 0
    mimetypes = ['audio/aac', 'audio/mp4', 'audio/mpeg', 'audio/mpegurl',
                 'audio/vnd.rn-realaudio', 'audio/vorbis', 'audio/x-flac',
                 'audio/x-mp3', 'audio/x-mpegurl', 'audio/x-ms-wma',
                 'audio/x-musepack', 'audio/x-oggflac', 'audio/x-pn-realaudio',
                 'audio/x-scpls', 'audio/x-speex', 'audio/x-vorbis',
                 'audio/x-wav', 'application/x-ogm-audio',
                 'audio/x-vorbis+ogg', 'audio/ogg']
    mtlist = ''
    name = "MpdPlayer"
    oh_state = 'Buffering'
    old_vol = 0
    parent = None
    reltime = '0:00:00.000'
    repeat = False
    room = 'Home'
    samplerate = 0
    seconds = 0
    shuffle = False
    songid = 0
    sources = ['Playlist']
    sourceindex = 0
    upnp_state = "TRANSITIONNING"
    volumeunity = 3
    volumemillidbperstep = 600
    timer = None
    token = 0
    trackcount = 0
    tracksmax = 0
    transport_actions = ['PLAY']
#     ohduration = 0

    def __init__(self, cover_dir, net_type='lan'):

        self.connected = False
        self.net_type = net_type
        self.proto = MpdProtocol()
        self.proto._event = self.dispatch_event
        self.status = {'state': 'stop'}
        self.playlist = Playlist(self)
        self.waiting = False
        self.calls = []
        self.cover_dir = cover_dir
        self._sources = [OrderedDict(sorted(
            {'Name': '_'.join((self.name, n)),
             'Type': n,
             'Visible': True}.
            items(), key=lambda t: t[0])) for n in self.sources]
        self.sourcexml = dict2xml(
            {'SourceList': [{'Source': n} for n in self._sources]})
        self.protocolinfo = self.mtlist = 'http-get:*:' + ''\
            .join([':*,http-get:*:'.join(self.mimetypes)]) + ':*'

    def buildProtocol(self, addr):
        return self.proto
    '''
    Internal functions
    '''

    def dispatch_event(self, events=None):

        def set_connected():
            self.connected = True
            p = self.call('plchanges', self.playlist.plsversion)
            p.addCallback(self.playlist.update)

        d = None
        for evt in events:
            if evt == 'Error':
                log.error('bad event: {name}', name=events[evt])
                return
            elif evt == 'changed':
                chg = events[evt]
            else:
                #                 log.msg(events[evt])
                return
            if chg == 'disconnected':
                self.connected = False
                continue
            if chg == 'playlist':
                log.error('update from: %s' % self.playlist.plsversion)
                d = self.call('plchanges', self.playlist.plsversion)
                d.addCallback(self.playlist.update)
            else:
                d = self.call('status')
                if chg == 'connected':
                    d.addCallback(self.filter_event)
                    reactor.callLater(1, set_connected)  # @UndefinedVariable
                elif chg == 'mixer':
                    d.addCallback(self.filter_event,
                                  'volume')
                elif chg == 'player':
                    d.addCallback(self.filter_event)
            return d

    def filter_event(self, data, filtr=None):
        if data is None:
            return
        if not isinstance(data, list):
            data = [data]
        if filtr is not None:
            for d in data:
                self.status.update(d)
            self.changed_state({filtr: self.status[filtr]})
        else:
            dic = {}
            for d in data:
                for it in d.iteritems():
                    if it not in self.status.items():
                        self.status.update(dict([it]))
                        dic.update(dict([it]))
                        if it[0] == 'playlistlength':
                            self.playlist.length = int(it[1])
            self.changed_state(dic)

    def call(self, command=None, params=None):
        #         log.error('register %s' % command)
        d = defer.Deferred()
        if command is not None:
            self.proto.addCallback(d)
        if not self.connected:
            if command:
                self.calls.append((command, params))
        else:
            if len(self.calls) > 0:
                if command:
                    self.calls.append((command, params))
                else:
                    d = None
                command, params = self.calls.pop(0)
            if params is not None:
                c = ' '.join([command, str(params)]).encode('utf-8')
            else:
                c = command.encode('utf-8')
            if self.proto.idle:
                self.proto.noidle()
            self.proto.sendLine(c)
#             log.error('send %s' % c)
            if len(self.calls) > 0:
                self.call()
        return d

    def changed_state(self, state):
        changed = state.keys()
        if 'state' in changed:
            self.set_state(state['state'])
        if "volume" in changed:
            log.debug('volume changed')
            vol = int(state['volume'])
            if vol != self._volume:
                if vol != 0:
                    self._volume = vol
                    muted = False
                else:
                    muted = True
                log.debug('send volume')
                self.oh_eventVOLUME(self._volume, 'volume')
                self.upnp_eventRCS(self._volume, 'volume')
                if muted is not self._muted:
                    self._muted = muted
                    self.upnp_eventRCS(self._muted, 'mute')
                self.oh_eventVOLUME(int(self._muted), 'mute')

        if 'songid' in changed:
            self.playlist.current_track(state['songid'])
        if 'repeat' in changed:
            #             log.debug('************repeat***********: %s' %
            #                 bool(int(state['repeat'])))
            if self.repeat != bool(int(state['repeat'])):
                self.repeat = bool(int(state['repeat']))
                if not self.shuffle:
                    self.upnp_eventAV(
                        'REPEAT_ALL' if self.repeat else 'NORMAL',
                        'currentplayMode')
                else:
                    self.upnp_eventAV('REPEAT_ALL SHUFFLE' if self.repeat
                                      else 'NORMAL SHUFFLE', 'currentplaymode')
                self.oh_eventPLAYLIST(self.repeat, 'repeat')
        if 'random' in changed:
            log.debug('************shuffle***********: %s' %
                      bool(int(state['random'])))
            if self.shuffle != bool(int(state['random'])):
                self.shuffle = bool(int(state['random']))
                if not self.repeat:
                    self.upnp_eventAV(
                        'NORMAL SHUFFLE' if self.shuffle else 'NORMAL',
                        'currentplaymode')
                else:
                    self.upnp_eventAV(
                        'REPEAT_ALL SHUFFLE' if self.shuffle
                        else 'NORMAL SHUFFLE', 'currentplaymode')
                self.oh_eventPLAYLIST(self.shuffle, 'shuffle')
        if 'elapsed' in changed:
            if self.timer is not None:
                self.timer.set(float(state['elapsed']))
        if 'playlist' in changed:
            self.token = int(state['playlist'])
#             self.playlist.plsversion = state['playlist']
        if 'playlistdata' in changed:
            self.playlist.update(state['playlistdata'])
        if 'bitrate' in changed:
            self.detailscount += 1
            self.bitrate = int(state['bitrate'])
            self.oh_eventINFO(self.bitrate, 'bitrate')
        if 'audio' in changed:
            try:
                sr = int(state['audio'].split(':')[0])
                self.samplerate = sr
                self.oh_eventINFO(sr, 'samplerate')
            except:
                log.critical('Bad Samplerate: %s' %
                             state['audio'].split(':')[0])
            try:
                bd = int(state['audio'].split(':')[1])
                self.bitdepth = bd
                self.oh_eventINFO(bd, 'bitdepth')
            except:
                log.critical('Bad Bitdepth: %s' % state['audio'].split(':')[1])

    def update_state(self):
        #  log.err('Update State: %s' % self.mpd.status['state'])
        self.set_state(self.status['state'])

    def update_mimetypes(self):
        self.set_mimetypes(self.mimetypes)

    def upnp_eventAV(self, evt, var):
        pass

    def upnp_eventCM(self, evt, var):
        pass

    def upnp_eventRCS(self, evt, var):
        pass

    def oh_eventPLAYLIST(self, evt, var):
        pass

    def oh_eventINFO(self, evt, var):
        pass

    def oh_eventTIME(self, evt, var):
        pass

    def oh_eventVOLUME(self, evt, var):
        pass

    def getMetadata(self):
        return self.call('playlistid', self.status['songid'])

    def getMimeTypes(self):
        return self.call('decoders')

    def get_state(self):
        return self._state

    def get_rate(self):
        return self._rate

    def get_track_duration(self):
        try:
            duration = self._track_duration
        except:
            duration = '00:00:00'
        return duration

    def get_track_URI(self):
        try:
            uri = self._track_URI
        except:
            uri = ''
        return uri

    def get_track_md(self):
        return self.metadata_str

    def get_sticker(self, url, dic={}):

        def got_sticker(stklist, dic):
            if not isinstance(stklist, list):
                stklist = [stklist]
            for sticker in stklist:
                try:
                    dic.update(
                        {sticker['sticker'].split('=')[0],
                         sticker['sticker'].split('=')[1]})
                except:
                    continue
            return dic

        d = self.call('sticker', ' '.join(('list song', url.join('"' * 2))))
        d.addBoth(got_sticker, dic)
        return d

    def get_relcount(self):
        return 2147483647

    def get_abscount(self):
        return 2147483647

    def get_abstime(self):
        return '00:00:00'

    def get_reltime(self, fmt='UPNP'):
        if self.timer is not None:
            if fmt == 'UPNP':
                t = mpdtime_to_upnptime(self.timer.get())
            elif fmt == 'seconds':
                t = int(self.timer.get())
            else:
                # msec
                t = self.timer.get()
        else:
            if fmt == 'UPNP':
                t = self.reltime
            else:
                t = self.seconds
        return t

    def get_volume(self):

        def noVolume(err):
            if self._muted:
                return 0
            else:
                return self._volume

        def convert_volume(vol):
            self._volume = int(float(vol) * 100)
            return self._volume

        d = self.mediaplayer.get("Volume",
                                 interf='org.mpris.MediaPlayer2.Player')
        d.addCallbacks(convert_volume, noVolume)
        return d

    def get_transport_actions(self):
        return {','.join(self.transport_actions)}

    def set_mimetypes(self, mtlist):
        if self._mtlist != mtlist:
            self._mtlist = mtlist
            self.mtlist = 'http-get:*:' + ''\
                .join([':*,http-get:*:'.join(mtlist)]) + ':*'
            self.upnp_eventCM(self.mtlist, 'sinkprotocolinfo')
            self.oh_eventPLAYLIST(self.mtlist, 'protocolinfo')

    def set_metadata(self, metadata):
        if metadata != self.metadata:
            self.metadata.update(metadata)
            if 'duration' in metadata.keys():
                self._track_duration = metadata['duration']
            if 'url' in metadata.keys():
                self._track_URI = metadata['url']
            if 'file' in metadata.keys():
                self._track_URI = metadata['file']

    def set_reltime(self, t):
        self.seconds = int(float(t))
        self.reltime = mpdtime_to_upnptime(t)
#         log.err('seconds: %s' % t)
        self.timer = None

    def set_state(self, state):
        log.debug("SET NEW STATE : %s " % state)
        if state == self._state:
            return
        self._state = state
        if state == 'pause':
            #             print(self.transport_actions)
            self.transport_actions = ['PLAY', 'STOP']
#                 self.transport_actions.remove('PAUSE')
#                 self.transport_actions.append('PLAY')
            if self.timer is not None:
                self.timer.stop()
                d = self.call('status')
                d.addCallback(lambda st: st['elapsed'])
                d.addCallbacks(
                    self.set_reltime,
                    lambda ignored: self.set_reltime(self.timer.get()))
            self.upnp_state = 'PAUSED_PLAYBACK'
            self.oh_state = 'Paused'
        elif state == 'play':
            self.transport_actions = ['STOP', 'PAUSE', 'SEEK']
            self.changed_state({'volume': self.status['volume']})
            self.timer = Timer()
            self.timer.set(self.seconds)
            d = self.call('status')
            d.addCallbacks(lambda st: st['elapsed'])
            d.addCallbacks(lambda t: self.timer.set(float(t)),
                           lambda ignored: self.timer.set(0.000))
            self.upnp_state = 'PLAYING'
            self.oh_state = 'Playing'
        elif state == 'stop':
            self.transport_actions = ['PLAY']
            self.set_reltime(0)
            self.upnp_state = 'STOPPED'
            self.oh_state = 'Stopped'
        elif state == 'pending':
            self.upnp_state = 'TRANSITIONNING'
            self.oh_state = 'Buffering'
        else:
            return
        self.upnp_eventAV(self.upnp_state, 'transportstate')
        self.oh_eventPLAYLIST(self.oh_state, 'transportstate')

    def set_songid(self, songid):
        self.songid = songid['Id']
        return self.songid

    def set_position(self, newpos, fmt='UPNP'):
        def transition(obj):
            current_state = self._state
            self.set_state('pending')
            reactor.callLater(0.5,  # @UndefinedVariable
                              self.set_state,
                              current_state)
        if fmt == 'UPNP':
            pos = upnptime_to_mpdtime(newpos)
        else:
            pos = newpos
        log.debug('seek to %s' % pos)
        d = self.call('seekcur', pos)
        d.addCallback(transition)
        return d

    def set_position_relative(self, delta, fmt='UPNP'):
        newpos = int(self.get_reltime('Seconds')) + int(delta)
        self.set_position(newpos, fmt)

    def set_track_URI(self, uri, md=''):
        log.debug("set track uri : %s " % uri)
        try:
            log.debug("current uri : %s " % self._track_URI)
        except:
            pass
        if uri != self._track_URI:
            self._track_URI = uri
            self.metadata_str = md
            self.set_metadata(didl_decode(md.encode('utf-8')))
            d = self.call('addid', uri)
            d.addCallback(self.set_songid)
            d.addCallback(self.play)

    def playing(self, *ret):
        log.debug('playing...')
        self.set_state('play')

    def playindex(self, index):
        return self.play(songid=self.playlist[int(index)])

    def playpause(self):
        if self._state == 'pause':
            return self.play()
        else:
            return self.pause()

    def insert_metadata(self, md):
        dic = didl_decode(md)
#             log.err(dic)
        for i, tag in enumerate(dic.keys()):
            if tag.lower() in ('class', 'restricted', 'id', 'duration',
                               'parentid', 'protocolinfo', 'url',
                               'ownerudn'):
                continue
            if not isinstance(dic[tag], str):
                continue
            reactor.callLater(  # @UndefinedVariable
                float(i) / 2,
                self.call,
                ' '.join(('sticker',
                          'set song',
                          dic['url'].join('"' * 2),
                          tag,
                          '"' + dic[tag] + '"')))

    def delete(self, songid):
        self.call('deleteid', str(songid))

    def clear(self):
        self.call('clear')

    def volUp(self):
        if self._volume == 0:
            vol = self.r_get_volume()
            try:
                newvol = vol + 5
            except:
                newvol = self._volume + 5
            return self.r_set_volume(newvol, 'Master')
        else:
            if self._volume > 95:
                return
            newvol = self._volume + 5
            return self.r_set_volume(newvol, 'Master')

    def volDown(self):
        if self._volume > 5:
            newvol = self._volume - 5
            return self.r_set_volume(newvol, 'Master')
        else:
            newvol = self._volume
            self._volume = 0
            return self.r_set_volume(newvol, '0')

    '''
    UPNP wrapped functions
    '''
    # onDemand vendor method

    def r_get_room(self):
        return self.room

    '''
    AVTransport and OpenHome Playlist
    '''

    def r_delete_all(self):
        return self.clear()

    def r_delete_id(self, value):
        return self.delete(value)

    def r_get_current_transport_actions(self, instanceID):
        return self.get_transport_actions()

    def r_get_media_info(self, instanceID):
        return (str(len(self.playlist)), self.get_track_duration(),
                self.get_track_URI(), self.get_track_md(), '', '', 'UNKNOWN',
                'UNKNOWN', 'UNKNOWN',)

    def r_get_media_info_ext(self, instanceID):
        return (str(len(self.playlist)), 'TRACK_AWARE',
                self.get_track_duration(), self.get_track_URI(),
                self.get_track_md(), '', '', 'UNKNOWN', 'UNKNOWN',
                'UNKNOWN',)

    def r_get_position_info(self, instanceID):
        return (self.player.get_track(), self.get_track_duration(),
                self.get_track_md(), self.player.get_track_URI(),
                self.player.get_reltime(), self.player.get_abstime(),
                self.player.get_relcount(), self.player.get_abscount(),)

    def r_get_transport_info(self, instanceID):
        return (self.get_state(), 'OK', self.get_rate(),)

    def r_id(self):
        return self.songid

    def r_id_array(self):
        return (self.token, self.idArray,)

    def r_id_array_changed(self, token):
        if token != self.token:
            return 1
        return 0

    def r_insert(self, afterid, url, metadata, checked=False):
        log.debug('Insert :%s  --  %s  --  %s' % (afterid, url, metadata))

        def inserted(res, md):
            log.debug(res)
            return res['Id']

        if 'youtube' in url and not checked:
            # eclipse workaround !!!
            y = os.environ
            y.update({'PYTHONPATH': '/usr/bin/python'})
            # /eclipse workaround
            d = utils.getProcessOutput(
                '/usr/bin/youtube-dl',
                ['-g', '-f', 'bestaudio', url],
                env=y,
                reactor=reactor)
            d.addCallback(
                lambda u: self.insert(
                    u.split('\n')[0], afterid, metadata, True))
            return d
        if len(self.playlist.tracks) == 0:
            d = self.call('addid', url)
        elif int(afterid) == 0:
            d = self.call('addid', url + ' 0')
        else:
            log.critical('crash ? %s' % self.playlist.tracks)
            log.critical('here ? %s' %
                         str(self.playlist.tracks.index(int(afterid)) + 1))
            d = self.call(
                'addid',
                ' '.join(
                    (url.encode('utf-8'),
                     str(self.playlist.tracks.index(int(afterid)) + 1))))
        d.addCallback(inserted, metadata)
        return d

    def r_next(self, instanceID=0):
        if self._state not in ('play', 'pause'):
            if self.songid == 0:
                self.r_play(1)
            else:
                self.r_play()
        else:
            self.call('next')

    def r_play(self, instanceID=0, speed=1, songid=None,
               ignored=None):

        log.debug('entering play...')

        def success(result):
            return None
        if self.cancelplay:
            self.cancelplay = False
        else:
            if songid is not None:
                #                 log.err(songid)
                d = self.call('playid', str(songid))
            else:
                if self._state == 'pause':
                    d = self.call('pause', '0')
                else:
                    d = self.call('playid', self.songid)
            d.addCallback(self.playing)

    def r_pause(self, instanceID=0):
        print('pause')

        def paused(ret):
            if self._state == 'play':
                self.set_state('pause')
        d = self.call('pause', '1')
        d.addCallback(paused)
        return d

    def r_previous(self, instanceID=0):
        self.call('previous')

    def r_protocol_info(self):
        return self.protocolinfo

    def r_read(self, value):
        log.debug('read')
        d = self.playlist.get_track(value)
        return (d,)

    def r_read_list(self, items):
        log.debug('readlist')
        d = self.playlist.get_tracks(
            [int(track) for track in items.split()])
        return d

    def r_repeat(self):
        return self.repeat

    def r_record(self, instanceID):
        raise NotImplementedError()

    def r_seek(self, instanceID, unit, pos):
        log.debug('seek: %s %s' % (unit, pos))
        self.set_position(pos)

    def r_seek_id(self, value):
        log.debug('SeekId')
        return self.r_play(songid=value)

    def r_seek_index(self, value):
        log.debug('Seekindex')
        return self.playindex(value)

    def r_seek_second_absolute(self, value):
        return self.set_position(value, 'SECONDS')

    def r_seek_second_relative(self, value):
        return self.set_position_relative(value, 'SECONDS')

    def r_set_repeat(self, repeat):
        self.call('repeat', str(int(repeat)))
        self.changed_state({'repeat': str(int(repeat))})

    def r_set_shuffle(self, shuffle):
        self.call('random', str(int(shuffle)))
        self.changed_state({'random': str(int(shuffle))})

    def r_set_avtransport_uri(self, instanceID, uri, uri_metadata):
        self.set_track_URI(uri, uri_metadata)

    def r_shuffle(self):
        return self.shuffle

    def r_stop(self, instanceID=0):
        def stopped(ret):
            self.set_state('stop')
        if self._state != 'STOPPED':
            d = self.call('stop')
            self.reltime = '00:00:00'
            d.addCallback(stopped)

    def r_tracks_max(self):
        return self.tracksmax

    def r_transport_state(self, instanceID=0):
        if self.parent.type == 'Source':
            return self.oh_state
        return self.upnp_state

    '''
    OpenHome Info
    '''

    def r_counters(self):
        return (self.trackcount, self.detailscount, self.metatextcount,)

    def r_track(self):
        return (self._track_URI, self.metadata_str,)

    def r_details(self):
        return (
            self.ohduration, self.bitrate, self.bitdepth,
            self.samplerate, self.lossless, self.codecname,)

    def r_metatext(self):
        return self.metatext

    '''
    Rendering Control and Open Home Volume
    '''

    def r_volume(self):
        return self._volume

    def r_set_volume(self, volume, channel=0):
        volume = str(volume)
        d = self.call('setvol', volume)
        d.addErrback(
            log.critical,
            'Set Volume Error : %s - %d' % (channel, int(volume)))
        reactor.callLater(0.1,  # @UndefinedVariable
                          self.changed_state,
                          {'volume': str(volume)})

    def r_volume_inc(self):
        return self.volUp()

    def r_volume_dec(self):
        return self.volDown()

    def r_volume_limit(self):
        return self.max_volume

    def r_balance(self):
        return self.balance

    def r_balance_inc(self):
        # TODO
        self.balance += 1

    def r_balance_dec(self):
        # TODO
        self.balance -= 1

    def r_set_fade(self, value):
        # TODO
        self.fade = int(value)

    def r_fade_inc(self):
        # TODO
        self.fade += 1

    def r_fade_dec(self):
        # TODO
        self.fade -= 1

    def r_mute(self):
        return self._muted

    def r_set_mute(self, mute):
        if mute is not self._muted:
            self._muted = mute
            if mute:
                self.old_vol = self._volume
                self.r_set_volume('0')
            else:
                self.r_set_volume(self.old_vol)

    def r_characteristics(self):
        return self.max_volume, self.volumeunity, self.max_volume,\
            self.volumemillidbperstep, self.balancemax, self.fademax

    '''
    OpenHome Time
    '''

    def r_time(self):
        return (self.trackcount, self.ohduration, self.get_reltime('seconds'))

    '''
    OpenHome Product
    '''

    def r_manufacturer(self=None):
        log.debug('Manufacturer from Product')
        return (self.parent.manufacturer,
                self.parent.manufacturerInfo, self.parent.manufacturerURL,
                ''.join((self.parent.getLocation(get_default_v4_address()),
                         '/pictures/icon.png')),)

    def r_model(self=None):
        log.debug('Model from Product')
        return (self.parent.modelName, self.parent.modelDescription,
                self.parent.manufacturerURL,
                ''.join((self.parent.getLocation(get_default_v4_address()),
                         '/pictures/', self.parent.modelName, '.png',)))

    def r_product(self):
        log.debug('Product from Product')
        return self.room, self.parent.modelName, self.parent.modelDescription,\
            self.parent.manufacturerURL,\
            ''.join((self.parent.getLocation(get_default_v4_address()),
                     '/pictures/', self.parent.modelName, '.png',))

    def r_standby(self):
        log.debug('Standby from Product')
        return self.standby

    def r_set_standby(self, val=None):
        log.debug('SetStandby from Product')
        if val is None:
            return self.standby
        raise NotImplementedError()

    def r_source_count(self):
        log.debug('SourceCount from Product')
        return len(self.sources)

    def r_source_xml(self, *args, **kwargs):
        log.debug('SourceXml from Product')
        return self.sourcexml
# return dict2xml({'SourceList': [{'Source': n} for n in self.sources]})

    def r_source_index(self):
        log.debug('SourceIndex from Product')
        return self.sourceindex

    def r_set_source_index(self, idx=None):
        log.debug('SetSourceIndex from Product')
        if idx is None:
            return self.sourceindex
        else:
            try:
                self.sourceindex = int(idx)
                self.oh_product_event('sourceindex', self.sourceindex)
            except:
                for i, source in enumerate(self.sources.keys()):
                    if source['Name'] == idx:
                        self.sourceindex = i
                        self.oh_product_event('sourceindex', self.sourceindex)
                        return
                    log.critical('Unknown Source: %s' % idx)

    def r_set_source_index_by_name(self, value):
        log.debug('SetSourceIndexByName from Product')
        return self.set_source_index(value)

    def r_source(self, idx):
        idx = int(idx)
        return (self.parent.sources[idx].friendlyName,
                self.parent.sources[idx].type, True,
                self.parent.sources[idx].name,)

    def r_attributes(self):
        return self.attributes

    def r_source_xml_change_count(self):
        raise NotImplementedError()