Pull to refresh

Издеваемся над Google Cast, или мышь для телевизора

Reading time 4 min
Views 31K


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

Articles