Ankündigung

Einklappen
Keine Ankündigung bisher.

- √ - Neues Plugin: Logitech Squeezebox - Anregungen?

Einklappen
X
 
  • Filter
  • Zeit
  • Anzeigen
Alles löschen
neue Beiträge

    - √ - Neues Plugin: Logitech Squeezebox - Anregungen?

    Hallo smarthome.py-Nutzer,

    anbei findet ihr die "Beta" meines Plugins für die Logitech Squeezebox-Familie.

    "Beta", weil ich evtl. noch mal am Konzept feilen muss, da das LMS-Interface teilweise nicht ganz so einheitlich antwortet wie es für eine "glatte" Implementierung wünschenswert wäre. Daher sind schon einige Quirks eingebaut (Zustände Play, Stop, Pause, Volume bzw das Abfragen andersnamiger Felder beim Starten um Werte zu bekommen). Die in der untenstehenden Item-Definition "squeezebox.conf" aufgeführten Items funktionieren.

    Mithilfe wäre gewünscht, indem jemand mit Kenntnis der Playlist-Struktur sich mal mit Telnet auf dem LMS (ip:9090) einloggt, und mir näher bringt, wie ich jetzt eine "Standard-Playliste" lade und dann in dieser vor- und zurückspringen kann. Oder allgemein Playlisten. Mutige können auch versuchen die Item-Definitionen anzulegen.

    plugin/squeezebox/__init__.py
    Code:
    #!/usr/bin/env python
    # vim: set encoding=utf-8 tabstop=4 softtabstop=4 shiftwidth=4 expandtab
    #########################################################################
    # Copyright 2013 Robert Budde                        robert@projekt131.de
    #########################################################################
    #  Squeezebox-Plugin for SmartHome.py.  http://mknx.github.com/smarthome/
    #
    #  This plugin is free software: you can redistribute it and/or modify
    #  it under the terms of the GNU General Public License as published by
    #  the Free Software Foundation, either version 3 of the License, or
    #  (at your option) any later version.
    #
    #  This plugin is distributed in the hope that it will be useful,
    #  but WITHOUT ANY WARRANTY; without even the implied warranty of
    #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    #  GNU General Public License for more details.
    #
    #  You should have received a copy of the GNU General Public License
    #  along with this plugin. If not, see <http://www.gnu.org/licenses/>.
    #########################################################################
    
    import logging
    import struct
    import time
    import urllib2
    import lib.my_asynchat
    import re
    
    logger = logging.getLogger('Squeezebox')
    
    class Squeezebox(lib.my_asynchat.AsynChat):
    
        def __init__(self, smarthome, host='127.0.0.1', port=9090):
            lib.my_asynchat.AsynChat.__init__(self, smarthome, host, port)
            self._sh = smarthome
            self._val = {}
            self._obj = {}
            self._init_cmds = []
            smarthome.monitor_connection(self)
    
        def _check_mac(self, mac):
            return re.match("[0-9a-f]{2}([:])[0-9a-f]{2}(\\1[0-9a-f]{2}){4}$", mac.lower())
        
        def _resolv_full_cmd(self, item, attr):
            if (item.conf[attr][0] == '*'):
                # check if PlayerID (MAC) is given - if not get from parent object
                parent_item = item.return_parent()
                if (parent_item != None) and ('squeezebox_playerid' in parent_item.conf) and self._check_mac(parent_item.conf['squeezebox_playerid']):
                    item.conf[attr] = parent_item.conf['squeezebox_playerid'] + item.conf[attr][1::]
                else:
                    logger.warning("squeezebox: could not resolve playerid for {0} from parent item {1}".format(item, parent_item))
                    return None
            return item.conf[attr] 
    
        def parse_item(self, item):
            
            if 'squeezebox_recv' in item.conf:         
                cmd = self._resolv_full_cmd(item,'squeezebox_recv')
                if (cmd == None):
                    return None 
                
                logger.debug("squeezebox: {0} receives updates by {1}".format(item, cmd))
                if not cmd in self._val:
                    self._val[cmd] = {'items': [item], 'logics': []}
                else:
                    if not item in self._val[cmd]['items']:
                        self._val[cmd]['items'].append(item)
    
                if ('squeezebox_init' in item.conf):
                    cmd = self._resolv_full_cmd(item,'squeezebox_init')
                    if (cmd == None):
                        return None 
                    
                    logger.debug("squeezebox: {0} is initialized by {1}".format(item, cmd))
                    if not cmd in self._val:
                        self._val[cmd] = {'items': [item], 'logics': []}
                    else:
                        if not item in self._val[cmd]['items']:
                            self._val[cmd]['items'].append(item)
                            
                if not cmd in self._init_cmds:
                    self._init_cmds.append(cmd)
                
            if 'squeezebox_send' in item.conf:         
                cmd = self._resolv_full_cmd(item,'squeezebox_send')
                if (cmd == None):
                    return None 
                
                logger.debug("squeezebox: {0} is send to {1}".format(item, cmd))
                return self.update_item
            else:        
                return None
    
        def parse_logic(self, logic):
            pass
    
        def update_item(self, item, caller=None, source=None, dest=None):
            # be careful: as the server echoes ALL comands not using this will result in a loop
            if caller != 'Squeezebox Media Server':
                cmd = self._resolv_full_cmd(item, 'squeezebox_send').split()
                if self._check_mac(cmd[0]):
                    cmd[0] = urllib2.quote(cmd[0]) 
                if isinstance(item, str):
                    value = urllib2.quote(item())
                elif (item._type == 'bool'):
                    # convert to get '0'/'1' instead of 'True'/'False'
                    value = int(item()) 
                else:
                    value = item()
                    
                # special handling
                if (len(cmd) >= 2) and (cmd[1] == 'play') and not item():
                    # if 'play' was set to false, send 'pause'
                    cmd[1] = 'pause' 
                self._send(' '.join(cmd_str for cmd_str in cmd).format(value))
    
        def _send(self, cmd):
            logger.debug("Sending request: {0}".format(cmd))
            self.push(cmd+'\r\n')
    
        def _parse_response(self, response):
            data = [urllib2.unquote(data_str) for data_str in response.split()]
            logger.debug("Got: {0}".format(data))
            
    #        if (data[0].lower() == 'player'):
    #            if (data[1].lower() == 'count'):
    #                num_players = int(data[2])
    #                logger.info("Found {0} player".format(num_players))
    #                for i in range(0,num_players):
    #                    self._send("player name {0} ?".format(i))
    #                    self._send("player id {0} ?".format(i))                    
    #            elif (data[1].lower() == 'name'):
    #                logger.info("Players {0} name is: {1}".format(data[2], data[3]))
    #            elif (data[1].lower() == 'id'):
    #                logger.info("Players {0} id is: {1}".format(data[2], data[3]))
            if (data[0].lower() == 'listen'):
                value = int(data[1]) 
                if (value == 1):
                    logger.info("Listen-mode enabled")
                else:
                    logger.info("Listen-mode disabled")
    
            if self._check_mac(data[0]):
                if (data[1] == 'play'):
                    self._update_items_with_data([data[0], 'play', 1])
                    self._update_items_with_data([data[0], 'stop', 0])
                    self._update_items_with_data([data[0], 'pause', 0])
                    return
                elif (data[1] == 'stop'):
                    self._update_items_with_data([data[0], 'play', 0])
                    self._update_items_with_data([data[0], 'stop', 1])
                    self._update_items_with_data([data[0], 'pause', 0])
                    return
                elif (data[1] == 'pause'):
                    self._send(data[0] + ' mode ?')
                    return
                elif (data[1] == 'mode'):
                    self._update_items_with_data([data[0], 'play', data[2] == 'play'])
                    self._update_items_with_data([data[0], 'stop', data[2] == 'stop'])
                    self._update_items_with_data([data[0], 'pause', data[2] == 'pause'])
                    return
                elif (data[1] == 'prefset'):
                    if (data[2] == 'server'):
                        if (data[3] == 'volume'):
                            # make sure value is always positive - also if muted!
                            data[4] = abs(int(data[4]))
                elif (data[1] == 'playlist'):
                    if (data[2] == 'newsong'):
                        # trigger reading of other song fields
                        for field in ['genre', 'artist', 'album', 'title', 'duration']:
                            self._send(data[0] + ' ' + field + ' ?')
                elif (data[1] in ['genre', 'artist', 'album', 'title', 'duration']) and (len(data) == 2):
                    # these fields are returned empty so no update takes plase - append '' to trigger update
                    data.append('')           
                    
            self._update_items_with_data(data)
                                    
        def _update_items_with_data(self, data):        
            cmd = ' '.join(data_str for data_str in data[:-1])
            if (cmd in self._val):
                for item in self._val[cmd]['items']:                  
                    item(data[-1], 'Squeezebox Media Server', "{}:{}".format(self.addr[0],self.addr[1]))
                     
        def found_terminator(self):
            response = self.buffer
            self.buffer = ''
            self._parse_response(response)
    
        def handle_connect(self):
            self.discard_buffers()
            # start discovering players and their respective IDs and names        
    #        self._send('players 0')
            self._send('listen 1')
            if self._init_cmds != []:
                if self.is_connected:
                    logger.debug('squeezebox: init read')
                    for cmd in self._init_cmds:
                        self._send(cmd+' ?')
            
        def run(self):
            self.alive = True
    
        def stop(self):
            self.alive = False
            self.handle_close()
    etc/plugin.conf
    Code:
    [squeezebox]
        class_name = Squeezebox
        class_path = plugins.squeezebox
    items/squeezebox.conf
    Code:
    [Squeezebox_2]
      squeezebox_playerid = 00:04:20:27:ca:1f
    
      [[Name]]
        type = str
        visu = yes
        squeezebox_recv = * player name ?   
        squeezebox_init = * player name    
      [[IP]]
        type = str
        visu = yes
        squeezebox_recv = * player ip ?   
        squeezebox_init = * player ip    
      [[Signal_Strength]]
        type = num
        visu = yes
        squeezebox_recv = * signalstrength    
    
      [[Power]]
        type = bool
        visu = yes
        squeezebox_send = * power {}
        squeezebox_recv = * prefset server power
        squeezebox_init = * power    
        
      [[Mute]]
        type = bool
        visu = yes
        squeezebox_send = * mixer muting {}
        squeezebox_recv = * prefset server mute
        squeezebox_init = * mixer muting
      [[Volume]]
        type = num
        visu = yes
        squeezebox_send = * mixer volume {}
        squeezebox_recv = * prefset server volume
        squeezebox_init = * mixer volume    
      [[Volume_Up]]
        type = bool
        enforce_updates = true
        visu = yes
        squeezebox_send = * button volup
      [[Volume_Down]]
        type = bool
        enforce_updates = true
        visu = yes
        squeezebox_send = * button voldown
        
      [[Play]]
        type = bool
        visu = yes
        squeezebox_send = * play
        squeezebox_recv = * play
        squeezebox_init = * mode
      [[Stop]]
        type = bool
        visu = yes
        squeezebox_send = * stop
        squeezebox_recv = * stop
        squeezebox_init = * mode
      [[Pause]]
        type = bool
        visu = yes
        squeezebox_send = * pause {}
        squeezebox_recv = * pause
        squeezebox_init = * mode
     
      [[Current_Title]]
        type = str
        visu = yes
        squeezebox_recv = * playlist newsong
        squeezebox_init = * current_title
    
      [[Genre]]
        type = str
        visu = yes
        squeezebox_recv = * genre
      [[Artist]]
        type = str
        visu = yes
        squeezebox_recv = * artist
      [[Album]]
        type = str
        visu = yes
        squeezebox_recv = * album
      [[Title]]
        type = str
        visu = yes
        squeezebox_recv = * title
      [[Duration]]
        type = num
        visu = yes
        squeezebox_recv = * duration

    Kurzerklärung:

    • squeezebox_playerid = MAC der Squeezebox
    • squeezebox_recv = String der das Item updated (von LMS gesendet)
      • * bedeutet player-id vom parent_item nehmen - sonst direkt angeben! So kann man einfacher bei mehreren Squeezeboxen die Items kopieren.
      • ? ist ein extra Zeichen (manchmal braucht es zwei Fragezeichen - eins wird automatisch angefügt!)

    • squeezebox_init = String der zu Anfang das Item initialisiert (manchmal ist dieser anders als squeezebox_recv)
      • wird squeezebox_init nicht angegeben, wird squeezebox_recv beim Start abgefragt!

    • squeezebox_send = String zum Senden zum LMS
      • {} ist für das Eintragen des Value - fehlt dies, wird kein Value gesendet ('play' etc.!)


    Viel Spaß!


    Grüße
    Robert
    Angehängte Dateien

    #2
    AW: Neues Plugin: Logitech Squeezebox - Anregungen?

    Wegen dem 'einheitlich antworten' : wie ich sehe nimmst du das telnet interface, welches das am schlechtesten gewartete von Logitech ist.
    Ein bisschen robuster ist die Kommunikation über JSon, so reden auch die Squeezeboxen mit dem Server (die zwar über einen cometd Kanal, das Nachrichtenformat ist aber gleich). JSon müsste sich doch auch mit Python deutlich leichter verarbeiten laden als selber telnet Antworten zu parsen ?
    Hast du unter forums.slimdevices.com schon mal nach jsonrpc gesucht. Da sollten eigentlich einige Artikel findbar sein.
    Was auch immer hilft: die Webseite des Squeezebox Servers aufmachen und die Entwicklungstools des Browsers aufmachen. Dann einfach die gewünschte Aktion auf der Webseite durchführen und das Netzwerkprotokoll des Browsers angucken.

    Wenn ich mich richtig erinnere, verwendet die Webseite auch den jsonrpc Endpunkt, so dass man sich dann das ganze ganz gut angucken kann.
    Autor der SonoPhone, SonoPad und SqueezePad Apps.

    Kommentar


      #3
      ich hatte mir mal einige Befehle notiert gehabt (für den Eibpc)..
      vielleicht kannst du davon ja welche brauchen:

      Code:
      00:xx:c6:1d:xx:dc show line1:Hello%20World line2:Second%20line duration:1 centered:1  (Text anzeigen)
      00:xx:c6:1d:xx:dc playlist index +1    (nächstes Lied)
      00:xx:c6:1d:xx:dc playlist index -1    (vorheriges Lied)
      00:xx:c6:1d:xx:dc mixer volume +10    (lauter)
      00:xx:c6:1d:xx:dc mixer volume -10    (leiser) 
      00:xx:c6:1d:xx:dc pause            (pause)
      00:xx:c6:1d:xx:dc playlist play file:///h:/mp3/mixmeister%2520remix.mp3   (Musiktitel abspielen)  leerzeichen durch %2520 erstzen !!!
      00:xx:c6:1d:xx:dc playlist play file:///h:/mp3/Funky%2520House%2520Electro%2520Mixtape.mp3
      00:xx:c6:1d:xx:dc playlist play file:///h:/OnProblemDetected_0407.mp3
      00:xx:c6:1d:xx:dc time ?               (Titelspielzeit abfragen)
      00:xx:c6:1d:xx:dc time XYZ        (Titelspielzeit an Pos. XYZ abspielen)
      00:xx:c6:1d:xx:dc time +60        (Titelzeit + 60 sec)
      00:xx:c6:1d:xx:dc power 0        (Ein/Aus)
      00:xx:c6:1d:xx:dc current_title ?    (Aktueller Titel)
      00:xx:c6:1d:xx:dc path ?        (Pfad zum Lied)
      00:xx:c6:1d:xx:dc Play X           (X = Sec. Fade in)
      00:xx:c6:1d:xx:dc playlist loadalbum <genre> <artist> <album>    
      00:xx:c6:1d:xx:dc playlist loadalbum * Labrassbanda *
      00:xx:c6:1d:xx:dc playlist loadalbum * Soulounge *
      00:xx:c6:1d:xx:dc playlist loadalbum * Loveparade%202008 *
      gibt aber natürlich noch deutlich mehr !

      Gruß Martin
      Die Selbsthilfegruppe "UTF-8-Probleme" trifft sich diesmal abweichend im groüen Saal.

      Kommentar


        #4
        vieleicht hilft unter Umständen auch die Docu unter http://IP-des-SBsever:9000/html/docs/index.html?player=

        Kommentar


          #5
          Ja da hab ich ja die Befehle her! Ich bin allerdings aus der Beschreibung der Playlisten nicht schlau geworden, da es scheinbar teilweise um Vertauschoperationen geht (also Liste nur managen) und eben scheinbar um "Springen" in der Liste.

          Werde dann doch mal ein paar Playlists anlegen müssen um selber zu testen.

          Mit dem Interface - hm, schade, hatte nicht erwartet dass es da zig verschiedene gibt. So funktioniert das ganze sehr zuverlässig (auch eben mit korrekten Stati für Play, Pause, Stop) - nur die Playlisten-Geschichte erschließt sich mir halt momentan noch nicht.

          Wenn hier die Leute erzählen, Sprachnachrichten abspielen zu lassen - gibt dann eine fixe Playlist für Sprachnachrichten die dann geladen und ein bestimmter Index dann abgespielt wird? Was passiert dann mit der alten Playlist? Vor dem Wechsel "merken" und dann wieder laden? Oder geht das eleganter?

          Grüße
          Robert

          Kommentar


            #6
            Zitat von Robert Beitrag anzeigen
            Wenn hier die Leute erzählen, Sprachnachrichten abspielen zu lassen - gibt dann eine fixe Playlist für Sprachnachrichten die dann geladen und ein bestimmter Index dann abgespielt wird? Was passiert dann mit der alten Playlist? Vor dem Wechsel "merken" und dann wieder laden? Oder geht das eleganter?
            Im konkreten Beispiel der Enertex.lib kann ich sagen, das die bei Alarm abzuspielende Datei als File oder Playlist vorliegen muß.
            Wenn ein Alarm auslöst und die SB gerade irgend eine Musik abspielt, wird diese in einer Temp gespeichert. Dann wird das "Alarmfile" abgespeielt (je nach Zeitvorgebe), und später das Temp wieder geladen.

            Code:
            // Squeezebox
            // @date    11.01.2012
            // @version    1 
            // @author    Enertex Bayern GmbH
            :begin SignalVar(PlayerID, Var, Zeit, Signal)
            :info $Wenn die Variable den Wert EIN annimmt, spielt die Squeezebox das Signal ab. Davor wird die aktuelle Playlist gespeichert und danach wieder geladen und die Variable auf AUS gesetzt. Kann man z.B. als Alarmsignal benutzen. Um dieses Makro verwenden zu können, müssen sie einmal das Makro "Squeezebox" eingebunden haben.$\\
                $PlayerID(MAC-Adresse oder Name) der Squeezebox als String, z.B. §00:04:20:12:85:fc§ oder §Squeezebox§ (Anstatt § das Dollarzeichen verwenden) $\\
                $Variable$\\
                $Gibt die Zeit in s an, wie lange das Signal abgespielt werden soll, bis die alte Playlist wieder geladen wird.(z.B. "30" für 30s)$\\
                $Relativer Pfad zum Signal, ausgehend vom Musik-Ordner.(z.B. "/Signale/Signal1.mp3")$
            :shortinfo $Die Squeezebox spielt über eine Variable ein Signal ab$
            if Var == EIN then connecttcp(ServerPort, ServerIP) endif
            if after(Var == EIN, TimeLag) and TCPConnected then {
                sendtcp(ServerPort, ServerIP, PlayerID + $ playlist save Temp$);
                sendtcp(ServerPort, ServerIP, PlayerID + $ playlist play ^Signal^$);
                }endif        
            if after(Var == EIN, Zeit^000u64 + 3000u64 + TimeLag) and TCPConnected then {
                sendtcp(ServerPort, ServerIP, PlayerID + $ playlist load Temp$);
                sendtcp(ServerPort, ServerIP, PlayerID + $ stop$);
                Var = AUS
                }endif
            irgendwo konnte man sogar festlegen mit welcher Lautstärke das "Alarfile" abgespeilt wird.

            Kommentar


              #7
              Hallo Robert!

              +1

              falls du "optisch" was brauchst, einfach melden

              Gruss
              Join smartVISU on facebook. Web: smartvisu.de.
              Dir gefällt smartVISU? Bitte spenden für die Weiterentwicklung.

              Kommentar


                #8
                Hi!

                Danke dir! Das mit dem Abspeichern und Laden einer Playliste scheint dann also der Weg zu sein. Lautstärke wäre dann ja in der Tat genauso: Vorher abspeichern, hinterher wiederherstellen. Ist dann wahrscheinlich eher was für eine "Logik" als für das Plugin.

                Folgende Items funktionieren jetzt auch (ohne Änderungen am Plugin!):
                Code:
                  [[Playlist_Index]]
                    type = num
                    visu = yes
                    squeezebox_send = * playlist index {}
                    squeezebox_recv = * playlist index
                  [[Playlist_Forward]]
                    type = bool
                    enforce_updates = true
                    visu = yes
                    squeezebox_send = * playlist index +1
                  [[Playlist_Backward]]
                    type = bool
                    enforce_updates = true
                    visu = yes
                    squeezebox_send = * playlist index -1
                    
                  [[Playlist_Name]]
                    type = str
                    visu = yes
                    squeezebox_send = * playlist name {}
                    squeezebox_recv = * playlist name
                  [[Playlist_Save]]
                    type = str
                    visu = yes
                    squeezebox_send = * playlist save {}   
                  [[Playlist_Load]]
                    type = str
                    enforce_updates = true
                    visu = yes
                    squeezebox_send = * playlist play {}
                  [[Playlist_Load_Internetradio]]
                    type = bool
                    enforce_updates = true
                    visu = yes
                    squeezebox_send = * playlist play [URL]file:///home/robert/playlists/Internetradio.m3u[/URL]
                Damit müsste es jetzt auch Alarmierungen möglich sein.

                Kommentar


                  #9
                  Zitat von Apollo Beitrag anzeigen
                  falls du "optisch" was brauchst, einfach melden
                  Hallo Martin,

                  wo du das so sagst... *fg*

                  Die Optik des Players ist momentan quasi eine Kopie von Axel Otterstätter "multimedia.music". Da habe ich nur die Sachen angepasst die nicht zu den Items der Squeezebox passen. Wenn alle Features mal fix sind, kann da vielleicht jemand der kreativer als ich ist Hand anlegen.

                  Was für mich momentan interessant wäre:

                  "Oben rechts" ist momentan der Playlisten-Button, welcher die Standard-Playliste (bei mir "Internetradio") lädt. Schöner wäre es natürlich, wenn sich ein Popup öffnen würde, welches die verfügbaren Playlisten anzeigt (dynamisch, vl. 5 direkt, sonst zum scrollen?). Wenn man dann eine auswählt wird diese geladen.

                  Ich könnte ein Item erstellen ("* playlists 0 99"), welches alle Playlisten zurückgeben würde. Evtl. kann man das umformatieren, denn momentan kommt dann:
                  Code:
                  id=1823 playlist=Internetradio id=1824 playlist=Test2 id=1825 playlist=Test3 Count=3
                  Was für ein Format bräuchte so eine Liste? Popup Möglich?

                  <bei Klick öffne Popup mit diesen Auswahl-Items ("gad_list") und erlaube schließen ohne Auswahl oder Auswahl mit Rückgabe Item ("gad_select")>

                  Sowas könnte man wohl auch an noch mehr Stellen einsetzen (Songauswahl, Nachrichten, Stati setzen etc.).

                  Grüße
                  Robert

                  Kommentar


                    #10
                    Nachtrag:

                    Hab gerade in der Docu-Visu geguckt: "popup3" wäre als Basis Klasse! Allerdings weiß ich nicht, wie ich da eine dynamische Liste reinbringe? Sonst ginge ja nur (z.B.) 5 Items für die ersten 5 Playlisten die dann über einen Button ausgewählt werden könnten. Wäre etwas umständlich und halt von der Anzahl limitiert und unübersichtlich (sowohl item.conf als auch in der Visu-Erstellung).

                    Kommentar


                      #11
                      Sobald wieder mehr Zeit ist werde ich das testen ... momentan zu viele Baustellen ... sieht aber sauber aus ... du arbeitest auch mit dem "listen" via telnet um den Status der Squeeze aktuell zu halten wenn ich das richtig interpretiere (bin da nur kurz drübergeflogen) ?
                      Umgezogen? Ja! ... Fertig? Nein!
                      Baustelle 2.0 !

                      Kommentar


                        #12
                        Hi Mirko,

                        ja, das ist korrekt. Man könnte wohl auch selektiv mit "subscribe" arbeiten, aber in meinen Augen macht das angesichts der wohl üblicherweise verlangten Informationen keinen Sinn.

                        Grüße
                        Robert

                        Kommentar


                          #13
                          Neee ... hätte ich auch so gemacht ... wie gesagt ... Zeit oh Zeit ... vielleicht schmeiß ich zum testen mal den LMS auf meinen (noch Windows) HomeServer.
                          Umgezogen? Ja! ... Fertig? Nein!
                          Baustelle 2.0 !

                          Kommentar


                            #14
                            Sag mir vorher Bescheid, hab das Plugin noch ein wenig verfeinert (IP und Name wurden u.A. noch falsch ausgelesen bei mehreren Boxen)

                            Kommentar


                              #15
                              Geile Arbeit..... Geiles Plugin.
                              Habs zwar noch nicht zu 100% eigebaut. Aber Lauter, Leiser, Play, Vor, Zurück und Mute gehen bei mir schonmal perfekt.
                              Außer...... das "bekannte Problem" mit Umlauten in Künstler- oder Songnamen bringt das Plugin bei der Bedienung ins Stocken.

                              Hinterlegte Playlists (z.B. Radiosender) könnte man ja "vorläufig" via multimedia.station aufrufen.

                              Kommentar

                              Lädt...
                              X