
Как только я узнал про такую замечательную вещь, как 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, и даже круче, потому что интерактивный).
