Как стать автором
Обновить

Гаджеты в Google Wave на примере аудио-плеера

Время на прочтение11 мин
Количество просмотров1.4K

Введение


Всем привет! Сегодня я попытаюсь доступно рассказать о создании гаджетов для Google Wave, о подводных камнях на этом пути и об удобном способе их(гаджетов) разработки. В блоге Google Wave на Хабре уже объяснили различие между гаджетами и роботами в волне. Я выбрал для изучения именно гаджеты по двум причинам:
  • Не нужно добавлять в волну дополнительных контактов, как при создании робота
  • Минимальный порог вхождения — простой API и независимость от appspot.com

А вопрос «что писать» отпал сам собой. Когда я получил доступ к Wave Preview и попробовал ее доступные фичи, меня расстроило отсутствие аудио-плеера. Почему картинки можно просто так кинуть в блип и их можно просматривать потом, а музыку — нет? На самом деле причина скорее всего в том, что javascript сам по себе проигрывать музыку не умеет, все решения, что я находил в интернете, используют flash плеер. Волны и так сейчас испытывают проблемы с производительностью, а флэш довольно ресурсоемкое приложение. Плюс юзабилити нужно продумать — не добавлять, например, 10 плееров на 10 файлов в одном блипе, а создать один плеер с плейлистом на 10 файлов.

Разбираемся с основами


Но довольно лирики! Вооружаемся браузером и идем куритьчитать официальный мануал по гаджетам Google Wave. Из него можно почерпнуть, что гаджеты для Wave немного отличаются от остальных гаджетов Google — гаджеты, совместимые с Wave, могут:
  • получать доступ к управлению состоянием на более детальном уровне
  • определять текущего зрителя и всех других участников волны
  • работать с механизмом воспроизведения Wave

Я пишу гаджеты для google впервые и что меня больше всего интересовало — как управлять состоянием гаджета? То есть первая фича из вышеприведенного списка. Более простая аналогия выражению «состояние гаджета» — «настройки гаджета». Две другие фичи уже более специализированные и сильно зависят от функций гаджета, сохранять же состояние(настройки) гаджета нужно практически всегда.
Так же стоит отметить, что гаджеты принадлежат именно той волне, в которой они созданы и все настройки хранятся именно в волне, а не на уровне пользователя, добавившего гаджет в волну. Однако гаджет всегда «помнит», кто его добавил — это важно для нашей цели.

Структура гаджета очень простая:
  1. <?xml version="1.0" encoding="UTF-8" ?>
  2. <Module>
  3.  <ModulePrefs title="Hello Wave">
  4.   <Require feature="wave-preview" />
  5.  </ModulePrefs>
  6.  <Content type="html">
  7.   <![CDATA[  
  8.     Hello, Wave!
  9.   ]]>
  10.  </Content>
  11. </Module>
* This source code was highlighted with Source Code Highlighter.

Синтаксис очень простой:
<Module> — указывает, что в этом XML-файле содержится гаджет.
<ModulePrefs> — может содержать информацию о гаджете, его авторе, ссылки на скриншот и еще вагон и маленькая тележка параметров. Я оставил только title, height и author_email.
Хороший пример можно посмотреть тут

И здесь я, не зная еще того, нарвался на хитрые грабли. Этот код взят из официального русского руководства Google. Обратите внимание на 4 строчку:
<Require feature=«wave-preview»/>

В этом руководстве везде утверждается, что нужно писать именно так. Сама же строчка означает, что гаджету требуется API Google Wave. Однако с таким требование гаджет будет работать только в Firefox(может еще в Опере, не проверял).
Не буду рассказывать, сколько я перерыл мануалов и как мучал google, но решение нашел только в одном из примеров — надо писать просто
<Require feature="wave" />

* This source code was highlighted with Source Code Highlighter.

Только так браузеры на движке WebKit смогли получить доступ к API волны. Интересно, как Firefox работал без этого? Кстати, как подсказывает хабраюзер boxfrommars, в английской версии руководства все правильно написано, так что ориентируйтесь пока лучше на нее.

Далее указываем, какой тип контента у нас будет — html или url. Url значит, что гаджет просто загрузит удаленную страничку себе в фрейм. Мы же выберем html и напишем все в нашей xml-ке.

<![ CDATA[…]]> — включает основное содержание гаджета, в том числе весь код HTML, CSS и JavaScript (или ссылки на соответствующие файлы). Содержание этого раздела следует рассматривать как содержание тега body на обычной HTML-странице.
Просто? Не то слово!

Разрбираемся с Google gadget API


Приступим же наконец-то к разработке нашего гаджета плеера. Как я уже сказал, javascript не умеет играть музыку сам, дадим ему в помощь flash. На хабре недавно рассказывали про проект uppod.ru. Зарегистрировался, создал плеер и стиль к нему, скачал — красота! Хорошая помощь на сайте разъяснила мне все неясные моменты. И что мне было еще очень интересно — управление плеером через javascript. Логично будет сделать управление через javascript нашей флешкой? Логично. Накидал быстро код, попробовал и…
И, к сожалению, такое не получится реализовать. Почему? Дело в том, что javascript выполняется на одном домене, а flash — на другом. Настройки безопасности никак не позволяют управлять flash плеером. По крайней мере, у меня не вышло запустить.
Тогда возьмем самый простой вариант — собирать плеер по частям и при смене настроек прописывать по новой innerHTML нашего div-а с плеером(перезагрузить его). Однако делается это обычно один раз — при добавлении гаджета в блип, поэтому считаю не критичным. Так же можно передавать все настройки через GET-запрос, то есть прямо в url.
Рассмотрим нужные нам функции из wave API для гаджетов(все функции описаны по вышеприведенной ссылке):
  • setStateCallback(callback) — определяет функцию, которая будет реагировать на изменение состояния нашего гаджета. Этот метод может встречаться в гаджете только ОДИН раз.
  • wave.getState() — возвращает объект состояния гаджета, представляющий собой таблицу пар ключ-значение.
    Для чтения же определенного ключа используется вызов wave.getState().get('KeyName')
  • submitDelta(delta) — обновляет объект состояния, добавляя(или перезаписывая уже существующую) к нему таблицу пар ключ-значение delta. Например так: wave.getState().submitDelta({'count': 5})
  • wave.getViewer() — возвращает объект, идентифицирующий того, кто просматривает гаджет
  • wave.getHost() — идентифицирует участника волны, добавившего данный гаджет


Google рекомендует придерживаться определенной структуры кода при написании гаджетов — будем же ей следовать:
  • Не обращаться к объектам состояния или участников в функции init(), которая часто вызывается при загрузке для инициализации. Объекты состояния и участников не имеют содержательных значений до выполнения соответствующих обратных вызовов (setParticipantCallback и setStateCallback). Ее можно использовать, чтобы удостовериться в работе волны и как раз таки зарегистрировать обратные вызовы.
  • Изменять объект состояния при отправке события элементом пользовательского интерфейса. Например нажали на кнопку и только вызвали submitDelta, не изменяя при этом больше ничего, на что это изменение повлияет.
  • Размещать программную логику в функциях обратного вызова, то есть основная часть кода должна быть размещена в функциях обратного вызова. Функции обратного вызова вызываются при изменении объекта состояния (setStateCallback) или участников волны (setParticipantCallback). Добавление кода в эти функции помогает удостовериться в получении последних изменений.


В итоге получаем следующий «скелет» для написания нашего гаджета:
  1. <?xml version="1.0" encoding="UTF-8" ?>
  2. <Module>
  3. <ModulePrefs title="TitleHere" height="100" author_email="mail@mail.com">
  4.  <Require feature="wave" />
  5. </ModulePrefs>
  6. <Content type="html">
  7. <![CDATA[
  8.   <div id="content_div" style="height: 100px;"></div>
  9.  
  10.   <script type="text/javascript">
  11.   var div = document.getElementById('content_div');
  12.  
  13.   function stateUpdated() {
  14.    
  15.   }
  16.   function init() {
  17.    if (wave && wave.isInWaveContainer()) {
  18.     wave.setStateCallback(stateUpdated);
  19.    }
  20.   }
  21.   gadgets.util.registerOnLoadHandler(init);
  22.  
  23.   </script>
  24.  ]]>
  25.  </Content>
  26. </Module>
* This source code was highlighted with Source Code Highlighter.


Функциональность плеера


Что же нам потребуется от плеера? Да чтобы играл! Правда играть музыку из файлов, добавленных в волну, не получится — гаджет не имеет доступа к блипам, либо я не нашел соответствующее API. Поэтому будем играть музыку по ссылке из остального интернета. Кроме того, будет правильно дать доступ к изменению настроек только тому, кто добавил данный гаджет в волну, остальным — только проигрывать! Ну и в качестве бонуса неплохо дать ссылку на файл для тех, кому понравится мелодия.
Итак, список функций:
  1. Проигрывание музыки по ссылке из интернета
  2. Доступ к настройкам имеет только добавивший гаджет в волну. Настройки должны при желании скрываться.
  3. Прямая ссылка на файл для скачивания ниже плеера


Так как настройки будут скрываться, добавим еще одну фичу — гаджет будет подгонять свою высоту до минимальной. Для это запросим &#60Require feature=«dynamic-height» /&#62 и используем метод gadgets.window.adjustHeight() в местах, где изменяется высота гаджета.

Приступаем к коду!


Для написания собственно кода можете использовать любой редактор, я использовал NetBeans, не суть. А вот для удобной отладки стоит обратить внимание на сервис Get DropBox или аналогичный. Размещаем проект в папке Public/WavePlayer и спокойно пишем код или делаем отладку уже существующего. DropBox автоматом загрузит измененный файл на сервер и при обновлении волны гаджет возьмет уже новую спецификацию xml, на данный момент они никак не кешируются. Там же разместим файл плеера и файл стилей для плеера. Туда же закинем install.xml — спецификация, скормив ссылку на которую гаджету New Extension Installer(можно найти в волне, в которую вас добавляют автоматом при регистрации и где вы можете найти некоторые полезные гаджеты), можно удобно инсталлировать наш гаджет прямо на панель!
Что представляет из себя этот файл:
<extension name="Wave mp3" description="Play mp3 inside a Wave.">
 <version>0.2</version>
 <author name="Author" email="mail@mail.com"/>
 <menuHook location="toolbar" text="Add Wave mp3 Gadget" iconUrl="">
 <insertGadget url="http://dl.dropbox.com/u/983333/WavePlayer_v0.2/WavePlayer.xml" />
 </menuHook>
</extension>

* This source code was highlighted with Source Code Highlighter.

Этот xml просто прописывает нужные ссылки до гаджета и иконки для него на панели.
А теперь взгляните на прокомментированный код и, я думаю, все встанет на свои места!
<?xml version="1.0" encoding="UTF-8" ?>
<Module>
 <ModulePrefs title="WavePlayer" height="115" author_email="maksng@gmail.com">
  <Require feature="wave" />
  <Require feature="dynamic-height" />
 </ModulePrefs>
 <Content type="html">
  <![CDATA[
  <div id="flash_div" style="height: 50px;">Wave player is loading...</div> <!-- Тут живет flash -->
  <div id="showlink_div"></div> <!-- Это для линка на файл -->
  <div id="showSettings_div" onClick="showSettings()"></div> <!-- кликается чтобы вернуть настройки -->

  <script type="text/javascript">
  var flashdiv = document.getElementById('flash_div');

  //Части плеера для вставки в innerHTML
  var player_part_1 = '<object id="waveplayer02" type="application/x-shockwave-flash" data="" width="315" height="50"><param name="allowScriptAccess" value="always" /><param name="wmode" value="transparent" /><param name="movie" value="" /><param name="flashvars" value="comment=Wave Player v0.2&st=http://dl.dropbox.com/u/983333/WavePlayer_v0.2/main.txt&file=';
  var player_part_2 = '" /></object>';
  //Хранит ссылку на файл
  var mp3file = 'http://dl.dropbox.com/u/983333/worldapart_.mp3';

  function buttonChange() { //Обработчик события клика по кнопке изменения ссылки
   if (wave.getViewer() == wave.getHost()) { //Меняет настройки тот, кто добавил гаджет?
    mp3file = document.getElementById("mp3file").value; //берем ссылку из input
    wave.getState().submitDelta({'mp3file': mp3file}); //Прописываем изменение в состояние
   } else { //Иначе(если вдруг это произошло) выкидываем соответствующий алерт
    alert("Sorry, you are not the owner of this gadget.\n\nOnly the participant who inserted this gadget in the wave (\"the owner\") can change the properties.");
   }
  }

   function buttonHide() { //Обработчик события клика по кнопке скрыть настройки
    wave.getState().submitDelta({'hided': 1}); //Прописываем изменение в состояние(настройки скрыты)
  }

   function showSettings() { //Обработчик события клика по кнопке показать настройки
    wave.getState().submitDelta({'hided': 0}); //Прописываем изменение в состояние(настройки видны)
  }

  function waveStateUpdated() { //callback обработки изменения состояний
   if(wave.getState().get('mp3file')) { //Есть ключ mp3file?
    mp3file = wave.getState().get('mp3file'); //Получаем его
   }
   flashdiv.innerHTML = player_part_1 + mp3file + player_part_2; //Грузим новый плеер
   //Ну и ссылочку дадим на файл, скачать
   document.getElementById("showlink_div").innerHTML = "<a href=\""+mp3file+"\">Download mp3!</a>";

   if (wave.getViewer() != wave.getHost()){ //Просматривает тот, кто добавил гаджет?
    document.getElementById("showSettings_div").style.display = "none"; //Нет, скроем от остальных все
    document.getElementById("settingsDiv").style.display = "none"; //настройки
   }
   else { //Да, именно он, создатель
    if(!wave.getState().get('hided')){ //Устанавливали ключ hided(скрыто)?
      document.getElementById("showSettings_div").style.display = "none"; //Нет - покажем настройки
      document.getElementById("settingsDiv").style.display = "";
    } else { //Да
      var isHided = wave.getState().get('hided'); //Получим это ключ
      document.getElementById("showSettings_div").style.display = (isHided==1)?"":"none";//Скрыто - показать кнопку Показать настройки
      document.getElementById("settingsDiv").style.display = (isHided==1)?"none":"";//Скрыто - скрыть настройки
    }
   }
   gadgets.window.adjustHeight(); //Подогнать высоту гаджета
  }

  function init() { //Инициализация
   if (wave && wave.isInWaveContainer()) { //Запущены в волне
    wave.setStateCallback(waveStateUpdated); //Пропишем функцию для Callback
   }
  }
  gadgets.util.registerOnLoadHandler(init); //При загрузке вызвать функцию init
  </script>
  <br>
  <div id="settingsDiv">
    <form name="mainForm">
      Link to mp3 : <input type="text" id="mp3file" value="" size "40"></input>
      <input type=button value="Change mp3!" id="butChange" onClick="buttonChange()">
    </form><br>
    To view it again click on "Show settings!"
    <input type=button value="Hide!" id="butHide" onClick="buttonHide()">
   </div>
  ]]>
 </Content>
</Module>


* This source code was highlighted with Source Code Highlighter.

А вот и результат моих трудов:
image

Полезные ссылки


  1. Руководство по гаджетам Google Wave
  2. Хороший пример хедера для xml
  3. Руководство разработчика API Google gadgets вообще(не только Wave)
  4. Последняя версия исходников с комментами и без


Очевидно, что есть куда еще развиваться, но всему свое время!
В комментариях с удовольствием отвечу на возможные вопросы.
Спасибо за внимание! Запустим волну на полную!
Теги:
Хабы:
+8
Комментарии8

Публикации

Изменить настройки темы

Истории

Ближайшие события

Weekend Offer в AliExpress
Дата20 – 21 апреля
Время10:00 – 20:00
Место
Онлайн
Конференция «Я.Железо»
Дата18 мая
Время14:00 – 23:59
Место
МоскваОнлайн