В этой статье я расскажу про написание скрипта HA, который продолжает воспроизведение видео запущенного с Plex на другом устройстве: выключает источник, включает устройство назначения, включает Plex, включает текущее видео, перематывает видео в нужную позицию, устанавливает необходимую громкость.
Написание скрипта и тестирование заняло больше времени, чем сэкономит его использование, тем более Plex и так сохраняет текущую позицию, но скрипт писался скорее в исследовательских целях. Возможно вы сможете подчерпнуть полезные решения, которые были использованы в нем, а может подскажете мне в комментариях как что-то можно было сделать проще.
В статье будет рассмотрено использование переменных (variables), триггеров (wait triggers), циклов (loops), условий (conditions), шаблонов значений (value templates).
А теперь давайте посмотрим каждое действие по отдельности.
1. Определение переменных target, source, plextarget и volume
Эти переменные будут использоваться в скрипте:
target - телевизор назначение
source - телевизор источник
plextarget - медиаплеер Plex на ТВ нахначении. Каждый клиент plex регистрируется в Home Assistant как отдельный device - media_player. Управление контентом и позицией возможно только через него, так как сам телевизор знает только какой запущен источник (hdmi или приложение) и статус (idle, playing, paused) и не понимает, что конкретно на нем проигрывается и какая текущая позиция.
volume - текущая громкость на источнике. Так как в фильмах используется разный уровень аудиодорожек, то не хочется руками регулировать громкость на ТВ назначении, а тем более чтобы он заорал на всю квартиру, если там был установлен высокий уровень.
Сначала нужно определить откуда и куда необходимо перенести просмотр фильма. Так как я автоматизировал только для сценария перехода между двумя комнатами (кухней и спальней), то предполагаем что один ТВ в момент запуска скрипта включен, другой выключен (можно реализовать и более сложные проверки, например, что включен источник plex, телевизор в статусе playing и т.д.).
Соответственно если ТВ спальня включен, то target = ТВ кухня, иначе target = ТВ спальня. Аналогично поступаем с source, plextarget и volume.
Для получения значения entity необходимо воспользоваться states, параметром в который передается entity, а states возвращает его значение (статус).
Код
variables:
target: >-
{{ 'media_player.lg_webos_kitchen' if
(states('media_player.lg_webos_bedroom') == 'on') else
'media_player.lg_webos_bedroom' }}
source: >-
{{ 'media_player.lg_webos_bedroom' if
(states('media_player.lg_webos_bedroom') == 'on') else
'media_player.lg_webos_kitchen' }}
plextarget: >-
{{ 'media_player.plex_plex_for_lg_lg_65up751c0zf' if
(states('media_player.lg_webos_bedroom') == 'on') else
'media_player.plex_plex_for_lg_lg_oled65c1rla' }}
volume: >-
{{ state_attr('media_player.lg_webos_bedroom', 'volume_level') if
(states('media_player.lg_webos_bedroom') == 'on') else
state_attr('media_player.lg_webos_kitchen', 'volume_level') }}
2. Поставить Plex на паузу
Придется немного забежать вперед, чтобы объяснить зачем ставить Plex на паузу на источнике. Как выяснилось, Plex обновляет атрибут media_position с большим интервалом, а значит узнать точную текущую позицию нельзя, но оказалось, что позиция обновляется при постановке на паузу.
Код
action: media_player.media_pause
metadata: {}
data: {}
target:
entity_id: "{{ source }}"
alias: Поставить Plex на паузу, чтобы обновилась media_position
3. Дождаться пока Plex станет на паузу
Поскольку в wait trigger нельзя использовать template entity, то я не смог использовать переменную source. А так как один из медиаплееров все равно выключен и находится в статусе unavailable, то просто добавляем оба в entity_id (далее по этой причине в wait trigger везде будут добавляться и источник и назначение) и при изменении статуса любого на paused продолжаем выполнение скрипта.
Код
wait_for_trigger:
- trigger: state
entity_id:
- media_player.plex_plex_for_lg_lg_65up751c0zf
- media_player.plex_plex_for_lg_lg_oled65c1rla
to: paused
for:
hours: 0
minutes: 0
seconds: 1
timeout:
hours: 0
minutes: 0
seconds: 10
milliseconds: 0
4. Определить переменные media_content_id и media_position
В интерфейсе HA идем в Developer tools\States, находим media_player Plex и смотрим какие там есть атрибуты:
Entity |
State |
Attributes |
---|---|---|
media_player.plex_plex_for_lg_lg_oled65c1rla Plex (Plex for LG - LG OLED65C1RLA) |
playing |
friendly_name: Plex (Plex for LG - LG OLED65C1RLA) |
Из полезного видим media_content_id и media_position. Проверяем и убеждаемся, что в этих атрибутах содержится id видео и текущая позиция в секундах.
Сохраняем эти значения в соответствующие переменные, они нам еще пригодятся. Для этого нам понадобится if, который мы использовали ранее и state_attr. Если у entity, кроме state еще есть атрибуты, то их значения можно получить с помощью state_attr, первым параметром в него передается entity, вторым имя атрибута.
Код
variables:
media_content_id: >-
{{ state_attr('media_player.plex_plex_for_lg_lg_oled65c1rla',
'media_content_id') if (states('media_player.lg_webos_bedroom') == 'on')
else state_attr('media_player.plex_plex_for_lg_lg_65up751c0zf',
'media_content_id') }}
media_position: >-
{{ state_attr('media_player.plex_plex_for_lg_lg_oled65c1rla',
'media_position') if (states('media_player.lg_webos_bedroom') == 'on') else
state_attr('media_player.plex_plex_for_lg_lg_65up751c0zf', 'media_position')
}}
5. Выключить ТВ источник
Код
action: media_player.turn_off
metadata: {}
data: {}
target:
entity_id: "{{ source }}"
alias: Выключить ТВ источник
6. Включить ТВ назначение
Код
action: media_player.turn_on
metadata: {}
data: {}
target:
entity_id: "{{ target }}"
alias: Включить ТВ назначение
7. Ждать пока ТВ не включится
Код
wait_for_trigger:
- trigger: state
entity_id:
- media_player.lg_webos_kitchen
- media_player.lg_webos_bedroom
from: "off"
to: "on"
for:
hours: 0
minutes: 0
seconds: 2
timeout:
hours: 0
minutes: 1
seconds: 0
milliseconds: 0
alias: Ждать пока ТВ не включится
8. Убавить звук до 0
Код
action: media_player.volume_set
metadata: {}
data:
volume_level: 0
target:
entity_id: "{{ target }}"
alias: Убавить громкость до 0
9. Запустить приложение Plex на ТВ
Код
action: media_player.select_source
metadata: {}
data:
source: Plex
target:
entity_id: "{{ target }}"
alias: Включить источник Plex
10. Определить переменную timestamp
Получаем ее с помощью now().timestamp() и преобразовываем в int. Объясню ее назначение в следующем пункте.
Код
variables:
timestamp: "{{ (now().timestamp() | int) }}"
11. Сканировать клиентов Plex
Как выяснилось, media_player в HA остается очень долгое время в статусе unavailable после запуска Plex на ТВ. В документации на интеграцию не нашел никакой информации, что с этим можно было бы сделать. Но если нет документации, то идем методом научного тыка. Интеграция Plex кроме медиаплееров создает еще одно устройство с несколькими сенсорами и одной кнопкой "Scan clients". Пробуем ее нажимать и видим, что это существенно ускоряет переход клиента в статус idle (запущен, но ничего не проигрывается). В этом статусе можно управлять клиентом.
Действие выглядит предельно простым: нажимать scan clients каждые 5 секунд, пока один из медиаплееров (кухня, спальня) не перейдет в состояние idle. Но чтобы скрипт не завис, хочется ожидание статуса idle ограничить одной минутой. Иначе я устану ждать включу фильм сам и вдруг в какой-то момент скрипт начнет управлять медиаплеером, когда это уже не нужно.
Казалось бы элементарная задача, объявить переменную, после каждой итерации делать инкремент и на 12 раз (цикл длится 5 секунд) остановить цикл, однако в HA область действия переменных не позволяет это сделать. Можно почитать подробнее здесь.
Поэтому воспользуемся созданной в предыдущем пункте переменной timestamp и будем ждать пока текущее время в секундах не станет больше timestamp на 60 или пока один из медиаплееров не перейдет в статус idle.
Код
alias: Сканировать клиентов Plex каждые 5 секунд, пока не найдется клиент
repeat:
sequence:
- delay:
hours: 0
minutes: 0
seconds: 5
milliseconds: 0
- action: button.press
metadata: {}
data: {}
target:
entity_id: button.media_scan_clients
while:
- condition: and
conditions:
- condition: template
value_template: "{{ (now().timestamp() | int) - timestamp < 60 }}"
- condition: not
conditions:
- condition: or
conditions:
- condition: state
entity_id: media_player.plex_plex_for_lg_lg_oled65c1rla
state: idle
- condition: state
entity_id: media_player.plex_plex_for_lg_lg_65up751c0zf
state: idle
enabled: true
12. Включить фильм на Plex
Для этого воспользуемся переменной media_content_id
Код
action: media_player.play_media
metadata: {}
data:
media_content_id: "{{ media_content_id }}"
media_content_type: movie
target:
entity_id: "{{ plextarget }}"
alias: Включить фильм на Plex
13. Ждать пока статус Plex не станет Playing
Пару секунд фильм успеет проиграться пока встанет на паузу, на случай если там громкая заставка мы ранее убавили звук на ТВ на 0.
Код
alias: Ждать пока статус Plex не станет Playing
wait_for_trigger:
- trigger: state
entity_id:
- media_player.plex_plex_for_lg_lg_oled65c1rla
- media_player.plex_plex_for_lg_lg_65up751c0zf
to: playing
for:
hours: 0
minutes: 0
seconds: 1
timeout:
hours: 0
minutes: 1
seconds: 0
milliseconds: 0
14. Поставить на паузу
Код
action: media_player.media_pause
metadata: {}
data: {}
target:
entity_id: "{{ target }}"
alias: Поставить на паузу
15. Ждать пока не станет на паузу
Код
wait_for_trigger:
- trigger: state
entity_id:
- media_player.lg_webos_kitchen
- media_player.lg_webos_bedroom
to: paused
for:
hours: 0
minutes: 0
seconds: 1
timeout:
hours: 0
minutes: 0
seconds: 10
milliseconds: 0
alias: Ждать пока не станет на паузу
16. Перемотать фильм
Воспользуемся переменной media_position чтобы перемотать фильм в нужное место
Код
action: media_player.media_seek
metadata: {}
data:
seek_position: "{{ media_position }}"
target:
entity_id: "{{ plextarget }}"
alias: Перемотать фильм в нужное место
17. Установить уровень громкости
Воспользуемся переменной volume, чтобы установить уровень громкости.
Код
action: media_player.volume_set
metadata: {}
data:
volume_level: "{{ volume }}"
target:
entity_id: "{{ target }}"
alias: Установить уровень громкости как на источнике
Ну и на последок скрипт полностью.
Скрипт
alias: plex_transfer_movie_position
sequence:
- variables:
target: >-
{{ 'media_player.lg_webos_kitchen' if
(states('media_player.lg_webos_bedroom') == 'on') else
'media_player.lg_webos_bedroom' }}
source: >-
{{ 'media_player.lg_webos_bedroom' if
(states('media_player.lg_webos_bedroom') == 'on') else
'media_player.lg_webos_kitchen' }}
plextarget: >-
{{ 'media_player.plex_plex_for_lg_lg_65up751c0zf' if
(states('media_player.lg_webos_bedroom') == 'on') else
'media_player.plex_plex_for_lg_lg_oled65c1rla' }}
volume: >-
{{ state_attr('media_player.lg_webos_bedroom', 'volume_level') if
(states('media_player.lg_webos_bedroom') == 'on') else
state_attr('media_player.lg_webos_kitchen', 'volume_level') }}
- action: media_player.media_pause
metadata: {}
data: {}
target:
entity_id: "{{ source }}"
alias: Поставить Plex на паузу, чтобы обновилась media_position
- wait_for_trigger:
- trigger: state
entity_id:
- media_player.plex_plex_for_lg_lg_65up751c0zf
- media_player.plex_plex_for_lg_lg_oled65c1rla
to: paused
for:
hours: 0
minutes: 0
seconds: 1
timeout:
hours: 0
minutes: 0
seconds: 10
milliseconds: 0
- variables:
media_content_id: >-
{{ state_attr('media_player.plex_plex_for_lg_lg_oled65c1rla',
'media_content_id') if (states('media_player.lg_webos_bedroom') == 'on')
else state_attr('media_player.plex_plex_for_lg_lg_65up751c0zf',
'media_content_id') }}
media_position: >-
{{ state_attr('media_player.plex_plex_for_lg_lg_oled65c1rla',
'media_position') if (states('media_player.lg_webos_bedroom') == 'on')
else state_attr('media_player.plex_plex_for_lg_lg_65up751c0zf',
'media_position') }}
- action: media_player.turn_off
metadata: {}
data: {}
target:
entity_id: "{{ source }}"
alias: Выключить ТВ источник
- action: media_player.turn_on
metadata: {}
data: {}
target:
entity_id: "{{ target }}"
alias: Включить ТВ назначение
- wait_for_trigger:
- trigger: state
entity_id:
- media_player.lg_webos_kitchen
- media_player.lg_webos_bedroom
from: "off"
to: "on"
for:
hours: 0
minutes: 0
seconds: 2
timeout:
hours: 0
minutes: 1
seconds: 0
milliseconds: 0
alias: Ждать пока ТВ не включится
- action: media_player.volume_set
metadata: {}
data:
volume_level: 0
target:
entity_id: "{{ target }}"
alias: Убавить громкость до 0
- action: media_player.select_source
metadata: {}
data:
source: Plex
target:
entity_id: "{{ target }}"
alias: Включить источник Plex
- variables:
timestamp: "{{ (now().timestamp() | int) }}"
- alias: Сканировать клиентов Plex каждые 5 секунд, пока не найдется клиент
repeat:
sequence:
- delay:
hours: 0
minutes: 0
seconds: 5
milliseconds: 0
- action: button.press
metadata: {}
data: {}
target:
entity_id: button.media_scan_clients
while:
- condition: and
conditions:
- condition: template
value_template: "{{ (now().timestamp() | int) - timestamp < 60 }}"
- condition: not
conditions:
- condition: or
conditions:
- condition: state
entity_id: media_player.plex_plex_for_lg_lg_oled65c1rla
state: idle
- condition: state
entity_id: media_player.plex_plex_for_lg_lg_65up751c0zf
state: idle
enabled: true
- action: telegram_bot.send_message
metadata: {}
data:
message: |-
media_content: {{ media_content_id }}
media_position: {{ media_position }}
target: -1001108604669
enabled: false
- action: media_player.play_media
metadata: {}
data:
media_content_id: "{{ media_content_id }}"
media_content_type: movie
target:
entity_id: "{{ plextarget }}"
alias: Включить фильм на Plex
- alias: Ждать пока статус Plex не станет Playing
wait_for_trigger:
- trigger: state
entity_id:
- media_player.plex_plex_for_lg_lg_oled65c1rla
- media_player.plex_plex_for_lg_lg_65up751c0zf
to: playing
for:
hours: 0
minutes: 0
seconds: 1
timeout:
hours: 0
minutes: 1
seconds: 0
milliseconds: 0
- action: media_player.media_pause
metadata: {}
data: {}
target:
entity_id: "{{ target }}"
alias: Поставить на паузу
- wait_for_trigger:
- trigger: state
entity_id:
- media_player.lg_webos_kitchen
- media_player.lg_webos_bedroom
to: paused
for:
hours: 0
minutes: 0
seconds: 1
timeout:
hours: 0
minutes: 0
seconds: 10
milliseconds: 0
alias: Ждать пока не станет на паузу
- action: media_player.media_seek
metadata: {}
data:
seek_position: "{{ media_position }}"
target:
entity_id: "{{ plextarget }}"
alias: Перемотать фильм в нужное место
- action: media_player.volume_set
metadata: {}
data:
volume_level: "{{ volume }}"
target:
entity_id: "{{ target }}"
alias: Установить уровень громкости как на источнике
description: ""
icon: mdi:movie
Буду рад вашим комментариям и советам по улучшению. Спасибо.
Комментарии (14)
Endovask
07.01.2025 08:06С Jellyfin такое можно провернуть, опенсурс все же. Платить за plex при наличии jellyfin?
tmv002 Автор
07.01.2025 08:06Я plex использую с 2015 года, около 3-4 лет использовал бесплатную версию, потом за 70$ купил пожизненную подписку, она уже давно отработала каждый заплаченный доллар.
dbond
07.01.2025 08:06Объясните, зачем эти сложности, если можно просто открыть фильм на другом устройстве и на нем продолжить воспроизведение?
Зыж плекс и ХА давно использую, три смарт ТВ, пара планшетов и телефон, но потребности в такой автоматизации не ощущаю.
tmv002 Автор
07.01.2025 08:06Об этом во втором абзаце написано. Просто интересно было, можно ли это сделать. Да, всего 10 нажатий на пульте и одна минута времени (не считая времени поиска пульта), но умный дом же для того и нужен, чтобы все само работало, а не для того чтобы клацать пультом. 5-6 раз в неделю пользуюсь, иногда 5-6 раз в день.
empenoso
А почему именно Plex, а не Kodi?
tmv002 Автор
Потому что лет 7-8 назад купил Plex Pass Lifetime. На тот момент Kodi не был конкурентом, на сколько понимаю и сейчас в AppStore нет клиента.
А есть какие-то плюсы у Kodi перед Plex? Знаю что можно через торрент смотреть, мне такое редко требуется, даже если что-то неожиданно захотелось посмотреть, то трансмишн это скачивает за 10 минут и можно смотреть.
empenoso
Kodi не требует сервера
kapustor
Если у вас есть опыт использования обоих, было бы очень интересно почитать статью-сравнение. Я тоже довольный пользователь Плекса, сетап - сервер с торрентокачалкой, два телевизора на android с приложениями Плекса в той же гигабитной сети + смартфон с приложением Плекса. Сервер торчит наружу, соответственно со смартфона могу смотреть скачанное по всему миру.
Телевизоры кстати пришлось подключать через гигабитные USB Ethernet адаптеры, иначе тяжёлые 4к фильмы тормозят, и на было сервере видно полочку в 100мбит отдачи.
Acidter
Это разные сценарии. Мне наоборот с сервером удобнее, одно место для хранения и перекодирования форматов в автоматическом режиме под каждое устройство в потоке и доступ откуда угодно с любого девайса.
AquariusStar
А в чём плюс перекодировки сервером форматов? Ведь вся нагрузка уйдёт на сервер. А это означает повышенные требования к серверу. Особенно, когда будут смотреть и слушать сразу несколько пользователей.
tmv002 Автор
Плюс в том, что можно проиграть контент на устройстве для которого не подходит исходный формат (разрешение, поддерживаемые кодеки, полоса пропускания и т.д.)