Chromecast: що з ним можна робити
Chromecast — це одночасно і невеликий медіаплеєр від Google, і протокол для побудови медіадодатків. Цей протокол підтримують багато телевізорів (Sony, Philips, TCL) та інших плеєрів (Mi Box).
Розглянемо основи цього архітектурного рішення:
- Пошук пристроїв — реалізовано через mDNS, простір імен **_googlecast._tcp** .
$ avahi-browse -r _googlecast._tcp
= wlp0s20f3 IPv6 Chromecast-8483c593f9fe704813a5fd6d1e330e19 _googlecast._tcp local
hostname = [8483c593-f9fe-7048-13a5-fd6d1e330e19.local]
address = [fe80::e9e6:e06:54b5:ad52]
port = [8009]
txt = ["rs=CpuLoad" "ct=243D5A" "nf=1" "bs=FA8FCA5A3BCC" "st=1" "ca=463365" "fn=Chromecast" "ic=/setup/icon.png" "md=Chromecast" "ve=05" "rm=63F5EA264E7C9F1B" "cd=34D0CB70278B5433B344012D67742914" "id=8483c593f9fe704813a5fd6d1e330e19"]
Відправник (Sender) — Chromecast використовує звичайну клієнт-серверну архітектуру. Клієнти надсилають запити, а “сервер” Chromecast обробляє їх. Тож додаток, який надсилає запити, називається sender. Це може бути iOS, Android або десктопний застосунок.
Приймач (Receiver) — відповідно, з боку Chromecast-пристрою має бути щось, що обробляє запити від sender-ів, тут це називається receiver. Є стандартні приймачі, як-от відеоплеєр та аудіоплеєр, а є Custom Receiver — це ті, що можна написати самому на HTML та JavaScript. Код такого приймача пристрій завантажує з інтернету, тож для того, щоб це все працювало, ми маємо викладати його на якийсь хостинг з HTTPS.
Для демонстрації я покажу, як розробити простий додаток, що буде показувати завантаження процесора на Chromecast.
Хостинг
Крок перший — потрібен хостинг для HTML/JS частини нашого приймача. Це можна зробити на своєму хостингу. Але хостинг має бути на HTTPS сервері, зі звичайного HTTP серверу Chromecast вантажити сторінку не буде.
Або можна використати Firebase, саме це я і зробив. Я зареєстрував новий проєкт у Firebase і за інструкцією https://firebase.google.com/docs/hosting/quickstart підготував папку, яку потім завантажу на хостинг. Firebase також дав мені адресу з доменом третього рівня, яку я зможу далі використати для реєстрації на сторінці Chromecast Custom Receiver.
Реєстрація - Google Cast SDK Developer Console
Другий крок — реєструємо приймач у Google за адресою https://cast.google.com/publish/?pli=1#/overview. На цьому кроці Google стягне з нас 5 доларів за реєстрацію облікового запису розробника для Chromecast. Це одноразовий платіж.
Обираємо “Add New Application”.
На цьому екрані обираємо “Custom Receiver”.
На останньому екрані реєстрації вказуємо ім’я нашого приймача та https-адресу, яку отримали на Firebase (у мене це https://testwebrtcchomecast.web.app/custom_receiver_cpuload.html).
Важливий момент — щоб запустити свій обробник на хромкасті, він має бути опублікованим. Публікація не миттєва, у мене ще декілька годин приймач не запускався на хромкасті.
Python-скрипт в якості відправника
Для збору та відправки статистики на комп’ютері використаємо python. Нам потрібно буде дві додаткові біблотеки:
PyChromcast (https://github.com/home-assistant-libs/pychromecast) - для пошуку та взаємодії з Chromecast пристроями.
PsUtil - бібліотека для отримання інформації про навантаження процессору.
Встановимо їх:
pip install PyChromecast psutil
Для підключення до пристрою його треба знайти в мережі. PyChromecast може шукати або за вказаним ім’ям, або взагалі всі Chromecast пристрої, ось приклад коду обох методів:
import time
import pychromecast
import zeroconf
import sys
browser = pychromecast.CastBrowser(pychromecast.SimpleCastListener(), zeroconf.Zeroconf())
browser.start_discovery()
print("Discovering Google chromecast devices in local network...")
for i in range(5, 0, -1):
print(f"{i}", end="\r")
time.sleep(1)
print("Discovered devices:", browser.devices)
pychromecast.discovery.stop_discovery(browser)
## якщо відоме ім'я можно шукати по ньому
name = "MyChromecast"
chromecasts, browser = pychromecast.get_listed_chromecasts(friendly_names=[name])
if not chromecasts:
print(f'No chromecast with name "{name}" discovered')
sys.exit(1)
Давайте спробуємо під’єднатись до першого знайденого пристрою і запустити на ньому програвання чогось з ютубу. Ютуб програвач це теж Custom Web Receiver, його ідентифікатор “233637DE” (приклад інших ідентифікаторів можна подивитись в файлі https://github.com/home-assistant-libs/pychromecast/blob/master/pychromecast/config.py)
Для цього потрібно на хромкасті запустити Custom Web Receiver Youtube і передати йому відповідну команду на програвання потрібного відео:
import time
import pychromecast
import zeroconf
import sys
from pychromecast.controllers.youtube import YouTubeController
VIDEO_ID = "jNQXAC9IVRw"
name = "Chromecast"
chromecasts, browser = pychromecast.get_listed_chromecasts(friendly_names=[name])
if not chromecasts:
print(f'No chromecast with name "{name}" discovered')
sys.exit(1)
cast = chromecasts[0]
cast.wait()
yt = YouTubeController()
cast.register_handler(yt)
yt.play_video(VIDEO_ID)
input("Press any key to stop...\n")
cast.quit_app()
cast.disconnect()
pychromecast.discovery.stop_discovery(browser)
Я сподіваюсь цей код у вас запрацював і можна переходити до написання свого Custom Web Receiver’а.
Взаємодія між відправником та приймачем відбувається за допомогою повідомлень. Обидві сторони можуть надсилати повідомлення через іменовані канали (named channels) в будь-який час. Можна використовувати декілька каналів для різних типів даних або з різним функціоналом. Кожен канал має свій унікальний ідентифікатор – URN (Uniform Resource Name).
Основні стандартні URN, які використовуються в Chromecast API, включають: * urn:x-cast:com.google.cast.tp.connection - для встановлення та закриття віртуального з’єднання між відправником і приймачем. * urn:x-cast:com.google.cast.receiver – для керування програмами на приймачі (запуск, зупинка, отримання статусу). * urn:x-cast:com.google.cast.media – для керування відтворенням медіаконтенту.
Окрім стандартних URN, кожен розробник може використовувати свої власні унікальні ідентифікатори. Користувацькі URN зазвичай мають префікс urn:x-cast:, наприклад, для передачі даних про завантаження процесора я використаю urn:x-cast:com.example.cpuload.
Повідомлення мають бути у форматі JSON. Я буду передавати завантаження процесора та пам’яті, структура JSON виглядатиме так:{
"cpu": cpu_value,
"memory": memory_value
}
Логіка пошуку Chromecast пристроїв залишається такою ж, як у попередньому прикладі. Нам потрібно додати власний клас, успадкований від BaseController.
class CpuLoadController(BaseController):
def __init__(self, timeout: float = 10) -> None:
super().__init__("urn:x-cast:com.example.cpuload", "E53ABD1B")
def receive_message(self, message, data):
print(f"Wow, I received this message: {data}")
return True # indicate you handled this message
def send_system_stats(self, cpu, freq, memory):
self.send_message({"cpu" : cpu,
"freq" : freq,
"memory" : memory})
У конструкторі ми задаємо URN для нашого кастомного простору імен та ідентифікатор програми (App ID). Цей App ID ми отримали після реєстрації приймача в Google Cast SDK Developer Console.
Метод receive_message викликається, коли надходить повідомлення від приймача по нашому URN. У цьому прикладі ми не очікуємо специфічних даних від приймача, тому просто виводимо отримане повідомлення в консоль. Важливо повертати True, щоб система знала, що повідомлення було оброблене цим контролером.
Метод send_system_stats надсилає повідомлення приймачу у форматі JSON, як було описано раніше.
Далі розглянемо головний цикл програми:
try:
controller = CpuLoadController()
cast.register_handler(controller)
stop_thread = False
def wait_for_keypress():
global stop_thread
input("Press any key to stop...\n")
stop_thread = True
keypress_thread = threading.Thread(target=wait_for_keypress)
keypress_thread.start()
while not stop_thread:
cpu_load = psutil.cpu_percent(interval=1)
freq = psutil.cpu_freq()
memory = psutil.virtual_memory()
controller.send_system_stats(cpu_load, freq.current, memory.percent)
time.sleep(1)
finally:
cast.quit_app()
cast.disconnect()
Раз на секунду ми збираємо статистику за допомогою бібліотеки psutil: - psutil.cpu_percent(interval=1) - дає відсоток завантаження CPU. interval=1 означає, що вимірювання буде проводитись протягом 1 секунди для більш точного результату. - psutil.virtual_memory() - дає статистику про використання віртуальної пам’яті. Нас цікавить відсоток використання percent.
Ці дані потім передаються в метод send_system_stats нашого CpuLoadController для надсилання на Chromecast. Потік keypress_monitor_thread очікує натискання клавіші Enter для завершення циклу та коректного відключення від пристрою.
HTML та JS код приймача
Переходимо до розробки самого Custom Web Receiver. Це та частина, яка працює безпосередньо на Chromecast пристрої та відповідає за відображення контенту і взаємодію з відправником.
Документація для Web Receiver API знаходиться тут https://developers.google.com/cast/docs/reference/web_receiver.
Для використання цього API потрібно під’єднати JS файл:
<script src="//www.gstatic.com/cast/sdk/libs/caf_receiver/v3/cast_receiver_framework.js"> </script>
Я не буду детально описувати весь код приймача, адже це окрема велика тема. Натомість, сфокусуюся на ключових принципах роботи з Chromecast API, які допомогли мені обійти деякі неочевидні моменти.
Одразу після завантаження вікна ми починаємо ініціалізувати об’єкт Chromecast. Логіку взаємодії з ним я виніс в окремий клас для зручності:
class SourceChromecast extends Source {
constructor() {
super();
this.urn = 'urn:x-cast:com.example.cpuload'
this.context = cast.framework.CastReceiverContext.getInstance();
}
start() {
this.context.addCustomMessageListener(this.urn, (event) => {
try {
this.observer.onData({
cpu: event.data.cpu,
memory: event.data.memory
});
} catch (e) {
console.log("Error " + e);
}
});
this.context.start();
this.context.setApplicationState("CpuLoad");
}
}
Цей початковий варіант вже майже працює. Але є одна проблема: за 5 хвилин Chromecast самостійно вимикає додаток, і ми опиняємось на домашньому екрані.
Чому це відбувається? Все просто: Chromecast орієнтований на програвання медіа. Коли в приймачі нічого такого не відбувається (немає відео, аудіо тощо), він “думає”, що додаток не працює або завис, і його можна закрити. Щоб цьому запобігти, потрібно встановити спеціальний прапорець disableIdleTimeout на старті CastReceiverContext:
const castReceiverOptions = new cast.framework.CastReceiverOptions();
castReceiverOptions.disableIdleTimeout = true;
this.context.start(castReceiverOptions);
Це дійсно допомагає! Приймач більше не закривається через 5 хвилин. Проте, з’явилася інша проблема: за 10 хвилин Chromecast переходить у режим заставки, а потім і зовсім “засинає”. Причина та ж сама – немає активності або програвання медіа.
Я спробував кілька методів, зокрема встановив більші таймаути:
const castReceiverOptions = new cast.framework.CastReceiverOptions();
castReceiverOptions.maxInactivity = 3600;
castReceiverOptions.disableIdleTimeout = true;
this.context.setInactivityTimeout(3600);
this.context.start(castReceiverOptions);
Але це, на жаль, не допомогло. Здавалося, що Chromecast просто не звертає уваги на ці налаштування, якщо немає активного медіапотоку.
Тому я здався і пішов на невеличку хитрість, я вставив на сторінку приймача маленьку та майже непомітну анімовану “крутилку” у вигляді відео в самому низу графіка навантаження:
<video id="backgroundVideo"
autoplay
loop
muted
style="position: absolute; bottom: 5px; left: 50%; width: 32px;
height: 32px; object-fit: cover; z-index: 10;">
<source src="w200.mp4" type="video/mp4">
</video>
Це дійсно допомогло! Chromecast “бачить” активне відтворення відео (хоч і дуже маленького) і не вимикає додаток. Тепер приймач працює без вимикань протягом багатьох годин.
Повний код мого приймача, а також решти проєкту, можна знайти у репозиторії на GitHub: https://github.com/vshcryabets/ChromecastCpuLoad.



Коментарі
Дописати коментар