Как только я узнал про такую замечательную вещь, как Chromecast, сразу побежал его покупать, ведь превратить свой ТВ в SmartTV (ну или на худой конец не перетыкать больше HDMI для просмотра фильмов) за две тысячи рублей — очень весёлая перспектива. Однако ещё более весёлая перспектива — это начать программировать под него.
Большинство задач для Хромкаста, которые реализуют сейчас — это простейшие приложения-видеоплееры. Форменная несправедливость для среды, которая может выполнять HTML5 на уровне свежего Хрома. Но вот незадача: нет в этой среде никаких событий мыши, что логично. Но и это не проблема для нас с вами.
Итак, для начала создадим простейшие Sender App и Reciever App. Инструкции для этого есть в официальной документации. Если коротко, то для этого вам нужно:
- Зарегистрироваться здесь, зарегистрировать свой Chromecast и своё приложение;
- Создать HTML-приложение Sender для Chrome (инструкция);
- Создать HTML-приложение Reciever для Cast-устройства (инструкция).
Есть маленький чит, который позволит вам избежать лишнего шага по заливке созданного Receiver-приложения на внешний
location.href = 'http://IP-вашего-локального-сервера/'
и открыть, таким образом, страницу Receiver-приложения с локального веб-сервера.
Ну, приступим, собственно, к самому эксперименту. Всё волшебство нашего приложения будет заключаться в том, что мы будем передавать из Хрома координаты указателя мыши с помощью стандартного метода Google Cast API для передачи сообщения. Суть в том, что соединение между Receiver и Sender приложений идёт через ваш локальный WiFi (и, судя по всему, WebSockets), поэтому задержка передачи данных минимальна.
Для начала:
Собственно, функция, которая творит волшебство в Sender-приложении:
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;
}
->
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'е:
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);
@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, и даже круче, потому что интерактивный).
Автор: GearHead