Воспроизведение медиафалов LMS на плеерах SqueezePlayer через IntraHouse



  • Давно возникла потребость озвучивать различные события в разных частях дома. Например в дальних частях дома и на втором этаже не слышно как открывается/закрывается входная дверь или позвать всех домашних к столу что-бы не кричать на весь дом. Или озвучить персональное сообщение в конкретной комнате.
    Частичным решением были потолочные динамики в коридорах первого и вторго этажей, кабеля которых шли на правый и левый канал звуковой карты компа на котором стоит IntraHouse. И воспроизведение сообщений происходило за счет проигрывания файлов локально на сервере IntraHouse. Но все же этого не хватало.

    После долгих поисков обнаружил что при помощи скриптов можно удаленно управлять плеерами Logitech Media Server(LMS) - нашел скрипт на pearl который делал как раз то что мне было нужно: https://www.domoticz.com/wiki/Logitech_Media_Server но, к сожалению, я так и не смог заставить этот скрипт заработать. А это было бы замечательным решением, т.к. на практически любой линукс или андройд можно поставить SqueezePlayer - клиент для LMS и воспроизводить медиафайлы как на каком-либо отдельном плеере так и синхронно на нескольких. В том числе таким образом можно организовать воспроизведение звука на клиентах где стоит IntraHouse Kiosk.

    Как оказалось из разбора того скрипта на pearl - LMS поддерживает протокол JSON-RPC, и плюс еще нашелся API JSON-RPC для LMS: https://gist.github.com/samtherussell/335bf9ba75363bd167d2470b8689d9f2

    В результате по мотивам того скрипта написал свой скрипт на python-е. Он приостанавливает воспроизведение тех радиостанций, которые играют в текущий момент на плеерах, сохраняет настройки медиаплееров, формирует синхрогруппу из тех плееров которые должны воспроизвести сообщение, и после сообщения разбирает синхрогруппу, восстанавливает настройки плееров и запускает воспроизведение тех радиостанций которые играли ранее.

    скрипт: lms_alert_player.py

    #-*- coding: UTF-8 -*-
    from sys import argv
    import requests
    import json
    import time
    
    script, alert_file = argv  # запоминаем аргументы переданные из коммандной строки - в данном случае - файл для воспроизведения
    
    #script, alert_file, alert_volume = argv
    #print  alert_file
    #print alert_volume
    
    
    # объявляем переменные 
    lms_ip = '10.0.0.2:9000' # IP адрес и порт логитеч медиа плеера
    player_mac = {'hall': 'd8:cb:8a:9c:67:63', 'kitchen': 'ad:d4:61:6d:65:33', 'parlor': 'dd:bb:cc:dd:00:44'} # словарь потенциальных плееров
    # на которых надо будет озвучивать сообщение  ключ - произвольный псевдоним плеера, значение - мак адрес плеера
    
    online_players =  list() # список экземпляров класса плеер на базе 
    master_player_mac = ""   # переменная в которой будет содержаться мак адрес мастер-плеера
    
    """---------- процедура для упрощения вызова процедуры протокола JSONRPC --------------------------"""
    def callRPC(params):
        data =  {"jsonrpc":"2.0", "method": "slim.request", "params": params ,'id':1}
        httpResponse = requests.post("http://%s/jsonrpc.js" % lms_ip, data=json.dumps(data))
        return json.loads(httpResponse.text)
    
    """  Класс для получения и хранения свойств удаленного плеера   """
    class LMS_Player(object):
    
        def __init__(self, alias, mac):
           
            self.alias = alias
            self.mac = mac
    
            try:
    	  answer = callRPC([mac, ["status","-"] ])
    	  self.name = answer["result"]["player_name"]
              self.power = answer["result"]["power"]
    	  self.volume = answer["result"]["mixer volume"]
              self.repeat = answer["result"]["playlist repeat"]
              self.mode = answer["result"]["mode"]
     	except Exception as e: # в том случае если не получили ответ от плеера (например он сейчас физически выключен)
      	  pass
    	  self.name = ""
              self.power = -1
              self.volume = -1
              self.repeat = -1
              self.time  = 0
              self.mode  = ""
    
            try:
              self.time = answer["result"]["time"]
            except Exception as e: # в том случае если был включен такой режим плеера в которм нет свойства time 
              pass
              self.time  = 0
    
            try:
              answer = callRPC([mac, ['playlist', 'path', '?'] ])
    	  self.path = answer["result"]["_path"]
            except Exception as e:  # в том случае если не получили ответ от плеера (например он сейчас физически выключен)
              pass
              self.path = ""
    
        def print_properties(self): # процедура вывода свойств плеера. Использовалась в процессе отладки 
    	print "*******************************"
     	print "Alias: %s" % self.alias
     	print "MAC: %s" % self.mac
     	print "Name: %s" % self.name
     	print "Power: %s" % self.power
     	print "Volume: %s" % self.volume
     	print "Repeat: %s" % self.repeat
     	print "Time: %s" % self.time
     	print "Mode: %s" % self.mode
     	print "Path: %s" % self.path
    
    
    # создаем список экземпляров класса LMS_Player в соответсвии с ранее объявленным словарем потенциальных плееров 
    for i in player_mac.keys():
     online_players.append(LMS_Player(i, player_mac.get(i)));
    
    z = range(len(online_players)); z.reverse() # сортируем список экземпляров плееров в обратном порядке в другой список что-бы удобнее было удалять физически отключенные
    for i in z: 
     online_players[i].print_properties()
     if (online_players[i].power == - 1): online_players.pop(i); print "^^^^^^ this player was deleted from syncro-list ^^^^^^" # удаляем плееры от котрых не получили ответ
    
    
    # подготавливаем плееры к воспроизведению нашего сообщения
    for current_player in online_players:
    
      if (current_player.mode == "play"): callRPC([current_player.mac, ['pause']])
      callRPC([current_player.mac, ['power', '1']])  
    #  callRPC([current_player.mac, ['mixer', 'volume', alert_volume]])  # на тот случай если в будущем захочется принудительно выставлять громкость на плеерах
      callRPC([current_player.mac, ['playlist', 'repeat', '0']])
      callRPC([current_player.mac, ['playlist', 'clear']])
    
      if (master_player_mac == ""):  master_player_mac = current_player.mac;  continue # запоминаем мак адрес  первого попавшегося плеера
    
      callRPC([master_player_mac, ["sync", current_player.mac]])   # назначаем  тот первый попавшийся плеер мастером для синхрогруппы
    
    # воспроизводим наше сообщение
    callRPC([master_player_mac, ['playlist', 'clear']])
    callRPC([master_player_mac, ['playlist', 'add', alert_file]])
    callRPC([master_player_mac, ['play']])
    
    # ждем пока закончится воспроизведение файла нашего сообщения 
    while True:
       answer = callRPC([current_player.mac, ["status","-"]])
       time.sleep(0.5)
       if (answer["result"]["mode"] != 'play'):  break
    
    
    # восстанавливаем настройки каждого плеера
    
    for current_player in online_players:
       callRPC([current_player.mac, ['mixer', 'volume', current_player.volume]])
       callRPC([current_player.mac, ["sync","-"]])
       callRPC([current_player.mac, ['playlist', 'clear']])
       callRPC([current_player.mac, ['playlist', 'repeat', current_player.repeat]])
       callRPC([current_player.mac, ['playlist', 'add', current_player.path]])
       callRPC([current_player.mac, ['mode', current_player.mode]])
       callRPC([current_player.mac, ['time', current_player.time]])
       callRPC([current_player.mac, ['power', current_player.power]])
    
    

    Что бы скрипт заработал в нем прописываем мак-адреса и псевдонимы плееров на которых хотим воспроизводить сообщение. А так же IP адрес и порт LMS. При первом запуске скрипт может ругаться на неустановленные питоновские пакеты, в таком случае доустанавливаем необходимые пакеты при помощи установщика пакетов pip. Далее при запуске скрипта указываем в качестве аргумента путь и имя медиафайла который хотим воспроизвести, при этом тот путь о котором знает и LMS у меня это предопределенная папка "музыка". Для запуска скрипта из среды IntraHouse это выглядит вот так при условии что скрипт называется lms_alert_player.py и находится в папке /media/:

    this.execOS(`python /media/lms_alert_player.py /media/go_eat.wav`);
    


  • Здравствуйте! Это конечно все хорошо, но все же это "костыль" к LMS. Хотя LMS - это скорее отличный мультирум!
    Посмотрите вот эту тему. mdmTerminal2 позволяет намного больше всего сделать. Причем плагин я "слепил" для iH (там правда много нужно чего причесать, но я не программист чтобы довести все хотелки до ума). Позволяет на всех разбросанных по дому Linux-устройствах (у меня на всех стоит squeezelite и mdmTerminal2) озвучивать любые события. А также отлично (в зависимости от микрофонной части) воспринимает речь и передает string на сервер. На сервере у меня есть большой!!! сценарий обрабатывающий все входящие стринги и в зависимости от них осуществляющий какие-либо действия (управление освещением, управление отоплением, вентиляцией, запрос состояния охранной системы, гаража, погреба, легкое общение с голосовым помощником как с одушевленным созданием...). Кроме этого, mdmTerminal2 имеет плагины "WiKi", "Скажи" и прочие. То есть, например, можно поинтересоваться у голосового помощника кто такой Наполеон, что такое Титаник и прочее. "Скажи" воспроизводит текст, который озвучен после слова "Скажи". Хотя удобнее сделать обработку на сервере строку "Позови всех кушать", на что отправить всем mdmTerminal2 фразу "Все приглашаются к столу на трапезу".



  • Добрый день, @Alex_Jet, да согласен, что скрипт - костыль но для меня пока это выход, тем более в ближайшее время все равно буду переходить на IntraHouse V5.
    С mdmTerminal2 я год тому назад пытался что-то сообразить, и вроде бы в той теме даже были мои комментарии. Но ключевой проблемой у меня тогда стало качество распознавания. Процент успешного распознавания был для меня не приемлемым. Тогда в качестве железа использовал 4х канальный микрофон от PS и OPI. Судя по всему я не смог сделать нормальные профили под snowboy, да и к тому времени проект snowboy помирал уже... И все закончилось покупкой Yandex колонки.
    А для воспроизведения звука mdmTerminal не подошел т.к. на кухне/столовой у меня планшет и большинство звуковых сообщений идет именно на него и коридорные колонки. А как я понял андройд подружить с mdmTerminalом проблематично.
    Но все же я считаю mdmTerminal перспективным вариантом и надеюсь что на V5 он будет как-то реализован.



  • @div115, snowboy как я понял стал локальным и автор mdmTerminal2 его реализовал. Сам не пробовал, поскольку под все свои микрофоны пока сохранены модели. Массив от PS2 - как показывает моя практика - не так хорош как связка Orange Pi Zero + микрофонный усилитель T472 + хороший электретный капсюль. Однако тот же Orange Pi PC явно быстрее реагирует (синтез речи происходит быстрее, распознавание тоже) чем Zero или Zero Plus.
    Звуковые модели нужно делать в реальных условиях эксплуатации. То есть смонтировали микрофон в месте где он будет реально располагаться и делаете 3 модели из точек где наиболее часто будете располагаться сами. Более 3-х моделей не стоит делать если железка не слишком производительная. Для создания моделей, я опять же прописал все ключевые слова в большом сценарии iH - ухожу куда надо и говорю своей "Алисе" что нужно сделать)
    По моему ребята в соответствующей группе телеграм (https://t.me/mdmPiTerminal) обсуждали установку mdmTerminal2 на Android, на сколько помню - это реально! Можете пообщаться там с разработчиком (Dar Adal), отвечает исправно.
    Чтобы в V5 работать с mdmTerminal2 нужно доработать плагин, который сделал я и портировать его в V5. Хотя желательно сделать плагин немного более нативным) в частности чтобы плагин мог сам отображать состояние подключенного mdmTerminal2, а не как я это все парсю через сценарий. Так что нужно чтобы разработчики повернулись лицом))) к mdmTerminal2 и выделили время на создание полноценного плагина (мне это под силу, но я потрачу слишком много времени на это).



  • ок, спасибо за информацию, как похолодает и у меня будет больше времени пробующую попытку №2 разобраться с mdmTerminalом т.к. у меня дома 4шт PI Zero, 4шт PI One и 1 PI PC, есть куда все повесить.
    И еще можете скинуть ссылку на - микрофонный усилитель T472?


Авторизуйтесь, чтобы ответить