Издеваемся над Google Cast, или мышь для телевизора
Как только я узнал про такую замечательную вещь, как Chromecast, сразу побежал его покупать, ведь превратить свой ТВ в SmartTV (ну или на худой конец не перетыкать больше HDMI для просмотра фильмов) за две тысячи рублей — очень весёлая перспектива. Однако ещё более весёлая перспектива — это начать программировать под него.
Большинство задач для Хромкаста, которые реализуют сейчас — это простейшие приложения-видеоплееры. Форменная несправедливость для среды, которая может выполнять HTML5 на уровне свежего Хрома. Но вот незадача: нет в этой среде никаких событий мыши, что логично. Но и это не проблема для нас с вами.Итак, для начала создадим простейшие Sender App и Reciever App. Инструкции для этого есть в официальной документации. Если коротко, то для этого вам нужно:
Зарегистрироваться здесь, зарегистрировать свой Chromecast и своё приложение; Создать HTML-приложение Sender для Chrome (инструкция); Создать HTML-приложение Reciever для Cast-устройства (инструкция). Есть маленький чит, который позволит вам избежать лишнего шага по заливке созданного Receiver-приложения на внешний хостинг. Когда вы зарегистрировали свой Chromecast и добавили приложение (можете зарегистрировать его на любую левую страницу на любом левом домене, главное, чтобы она открывалась), вы можете открыть дебаг вашего приложения, как описано здесь, и далее просто ввести в JS-консоли:
location.href = 'http://IP-вашего-локального-сервера/' и открыть, таким образом, страницу Receiver-приложения с локального веб-сервера.Ну, приступим, собственно, к самому эксперименту. Всё волшебство нашего приложения будет заключаться в том, что мы будем передавать из Хрома координаты указателя мыши с помощью стандартного метода Google Cast API для передачи сообщения. Суть в том, что соединение между Receiver и Sender приложений идёт через ваш локальный WiFi (и, судя по всему, WebSockets), поэтому задержка передачи данных минимальна.
Для начала:
Собственно, функция, которая творит волшебство в Sender-приложении:
JS function () { if (!$('body').data ('casting')) { $('body').data ('casting', true).on ('mousemove', (function (e) { return window.session.sendMessage (namespace, { x: e.clientX, y: e.clientY }, (function () {}), (function () {})); }).throttle (10)).on ('click', function () { return window.session.sendMessage (namespace, { event: 'click' }, (function () {}), (function () {})); }); } return; } CoffeeScript → unless (body = $('body')).data 'casting' body .data 'casting', true .on 'mousemove', ((e) → window.session.sendMessage namespace, { x: e.clientX, y: e.clientY }, (→), (→) ).throttle (10) .on 'click', → window.session.sendMessage namespace, { event: 'click'}, (→), (→) Волшебная функция throttle в данном случае мною позаимствована из Sugar.JS. Как многие догадались, она ограничивает вызов коллбека не чаще раза в 10 мс, чтобы не зафлудить наш Chromecast. Namespace — это просто уникальная строка, имя, которое даётся каналу данных. В моём случае это 'urn: x-cast: com.google.cast.magnum.remote_control'.
Вызывать эту функцию нам нужно в тот момент, когда мы устанавливаем сессию связи с Cast-устройством, т.е. 1) внутри sessionListener (в случае обновления страницы, если соединение уже было установлено), а так же 2) в success-коллбеке в requestSession.
Итак, Sender теперь отправляет данные о координатах указателя мыши, осталось их как-то обработать в Receiver’е:
JS this.cursor = document.createElement ('div'); this.cursor.style.position = 'absolute'; this.cursor.classList.add ('magnum-cursor'); document.body.appendChild (this.cursor); this.messageBus = receiverManager.getCastMessageBus (this.namespace, cast.receiver.CastMessageBus.MessageType.JSON); return this.messageBus.onMessage = (function (_this) { return function (e) { if (e.data.x && e.data.y) { var element; _this.cursor.style.left = e.data.x + 'px'; _this.cursor.style.top = e.data.y + 'px'; element = document.elementFromPoint (e.data.x — 1, e.data.y — 1); if (_this.currentHover!== element) { if (_this.currentHover) { _this.currentHover.dispatchEvent (new Event ('mouseleave')); _this.currentHover.classList.remove ('hover'); } _this.currentHover = element; _this.currentHover.dispatchEvent (new Event ('mouseenter')); return _this.currentHover.classList.add ('hover'); } } else if (e.data.event) { return _this.currentHover.dispatchEvent (new Event (e.data.event)); } }; })(this); CoffeeScript @messageBus = receiverManager.getCastMessageBus @namespace, cast.receiver.CastMessageBus.MessageType.JSON
@messageBus.onMessage = (e) => if e.data.x && e.data.y @cursor.style.left = e.data.x + 'px' @cursor.style.top = e.data.y + 'px'
# we should get neighboor pixel; otherwise we will get cursor element forever. element = document.elementFromPoint e.data.x — 1, e.data.y — 1
if @currentHover!= element if @currentHover @currentHover.dispatchEvent new Event ('mouseleave') @currentHover.classList.remove 'hover'
@currentHover = element
@currentHover.dispatchEvent new Event ('mouseenter') @currentHover.classList.add 'hover'
else if e.data.event @currentHover.dispatchEvent new Event (e) receiverManager мы создаём заранее в соответствии с документацией. cursor — это просто div-элемент, который будет бегать по экрану, заменяя нам курсор. Собственно, на этом мы всё и сделали.
Полный готовый пример можно посмотреть у меня на гитхабе. Жду ваших комментариев.
P.S.: Если тема будет интересна, и если мне будет не лень, в следующем выпуске расскажу, как сделать из вашего смартфона 3D-пульт для приложения Google Cast (прямо как LG Magic Remote, и даже круче, потому что интерактивный).