Как заставить Amazon Alexa играть музыку с Google Music, хоть она этого и не хочет
KOGDATA.RU — Среди популярных голосовых ассистентов (Siri от Apple, Assistant от Google, или, Боже упаси, Cortana от Microsoft) Алексу считают самой умной и полезной. Одна из основных причин - возможность обучать ее новым умениям и относительная легкость этого процесса. Вы можете добавить к вашей колонки новое умение из большого магазина скиллов, а если не найдете там нужен - создать скилл самостоятельно. Именно этим мы сегодня и займемся.
Больше года я дома пользуюсь двумя колонками Amazon Echo Dot, которые подключены к стереосистеме на кухне и в гостиной. Мои колонки умеют включать / выключать телевизор и андроид-приставку к нему (Alexa, TV on), находить мой телефон (Alexa, ask Tracker to ring my phone), проигрывать подкасты и аудиокниги, рассказывать о погоде, читать «Википедию» и другие. Колонки знают друг о друге, умеют друг друга включать / выключать (Alexa, stop in the kitchen), а также играть музыку синхронно (Alexa, play Nirvana everywhere).
Основной причиной покупки колонок, конечно, было желание слушать музыку. Однако именно с этим и возникло больше всего проблем. Во-первых, Алекса официально в Украине не поддерживается. Из-за этого во время регистрации вам будут недоступны большинство музыкальных сервисов, а из тех, которые останутся (кажется, только радио TuneIn) пользы не будет практически никакой. Автор: Артем Маслов
Сохраните в соц.сети, ок?
Эту проблему я решил, указав в регистрационных данных регион США и адрес супермаркета в Чикаго. Благодаря этому мне, как новоиспеченному американцу, стал доступным целый набор музыкальных сервисов, среди которых самые полезные - Pandora и iHeartRadio. На этих сервисах есть немало действительно хорошей музыки, и этим можно было бы и ограничиться, если бы не одна проблема. Дело в том, что вы можете задать Алексе только исполнителя, а не конкретную песню. С указанного вами исполнителя Алекса создаст треклист, в который, кроме желаемого артиста, придаст песни такого же жанра других исполнителей. Например, в ответ на команду Alexa, play Metallica on iHeart, кроме самой Metallica, вы услышите Black Sabbath, Nirvana, Scorpions и даже Queen.
Для большинства случаев такого поведения полностью достаточно: заказали желаемый жанр и занимаетесь своими делами под хорошую музыку. Однако, если вам вдруг захочется послушать именно Unforgiven II, Алекса в ответ предложит купить платную подписку на Amazon Music за 10 долларов в месяц. Не то чтобы это было дорого, но я уже оплачиваю премиум-подписку Google и покупать еще одну, только чтобы слушать ее дома, мне не хотелось. Наиболее очевидным решением было бы активировать на Алексе скилл Google Music, однако такого просто не существует. Говорят, причина в конкуренции компаний Google и Amazon.
Ну что же, challenge accepted! Создадим скилл самостоятельно.
Для того чтобы в ответ на наш голосовой запрос Алекса начала проигрывать заданную песню, мы должны дать ссылку на нее. То есть нам нужен программный интерфейс, который найдет и вернет нам эту ссылку. Здесь мы сталкиваемся с еще одной проблемой: официального Google Music API не существует.Есть официальный API для YouTube, однако он возвращает ссылку на видео, а не на аудиофайл. После определенного исследования я пришел к выводу, что компания Google приложила немало усилий, чтобы осложнить выделения аудиопотока из видеофайла (как минимум, это было бы неудобно с точки зрения временных затрат на выполнение команды), и оставил это дело.
После коротких дополнительных поисков обнаружил, что есть хороший неофициальный API для Google Music , написанный на Python, а также Java wrapper к нему. Последнее - именно то, что нам и нужно, берем.
Начинаем создание скилла. Для этого заходим на Alexa Developer Console и регистрируем там аккаунт, к которому привязана наша умная колонка. После регистрации попадаем в консоль, в которой видим кнопку Create Skill.
Нажимаем ее, вводим нужное название скилла, оставляем тип модели Custom, а язык взаимодействия - английский. В следующем окне выбираем Start from scratch. Попадаем в главную админку нашего скилла.
Начинаем с секции Invocation, где указываем, какое обращение активировать наш скилл (Alexa, ask Google Music to play ...), и выбираем Google Music.
Переходим к секции Intents, где указываем основные параметры взаимодействия с нашим Скилом. Intent - это намерение, и здесь Алекса узнает, что вы хотите сделать. Есть ряд встроенных Интент, которые мы можем использовать при создании скилла, например, AMAZON.StopIntent остановит выполнение определенного действия, AMAZON.PauseIntent поставит ее на паузу и тому подобное.
Название Интент - это не голосовая команда, поэтому называем его так, как нам нравится. Например GoogleMusic. Нажимаем Create custom intent.
Затем консоль предлагает нам указать Sample Utterances, то есть голосовую фразу, активирует Интент и укажет ему, что нужно сделать. Нас интересует только одна команда - play. Пишем ее.
Далее нам нужна переменная, чтобы передать название песни, которую мы хотим послушать. Такие переменные называют слотами. Можно было бы указать два слота: отдельно для песни и исполнителя, однако это сделало бы взаимодействие с Алексой менее удобной. Тем более, что наш Google Music API прекрасно умеет искать практически по любой построением фразы. Поэтому оставляем один слот и называем его song. Кроме названия, нам также нужно выбрать один из имеющихся типов слота. В общем тип (например, Actor, Airline, Book, Drink, Duration) может потом помочь Алексе лучше определить, что именно мы хотим, и выполнить команду качественнее, но, поскольку наш поиск будет осуществлять НЕ Алекса, а Google, то для нас это не имеет никакого значения. Как тип я выбрал AMAZON.MusicCreativeWorkType.
Возвращаемся наверх к нашему Sample Utterances, который мы назвали play, и в фигурных скобках после него вписываем наш слот song.
Это означает, что фразу, которую мы произнесем после слова play, будет записано в слот song. Жмем кнопку Save Model вверху.
Переходим в раздел Interfaces, активируем Audio Player и жмем Save Interfaces.
Переходим в раздел Endpoint. Здесь мы должны указать, где живет бэкенд, который обработает нашу команду и вернет ответ. У нас есть два варианта: AWS Lambda ARN i HTTPS. Поскольку второй вариант означает развертывание сервера, мы останавливаемся на первом, очень простом и удобном для наших целей. Однако Lambda следует сначала настроить, поэтому пока откладываем консоль Алексы и в соседней вкладке открываем консоль Amazon Web Services (AWS).
Если у вас еще нет аккаунта в AWS, самое время его создать. Если же есть, возможно, есть смысл создать новый. Во всяком случае, я для большинства своих AWS-проектов создаю новые аккаунты на отдельные почтовые адреса, чтобы не делать проекты зависимыми друг от друга. Например, иногда благодаря такому подходу можно неплохо сэкономить.
Во время регистрации Amazon попросит вас указать данные кредитной карты на случай, если захотите пользоваться платными сервисами. Эти данные указать придется, однако деньги на этом проекте вы точно не потратите: Lambda предоставляет бесплатно 1 млн запросов или 400 тыс. гб / с в месяц. Расслабляемся и регистрируемся.
Зайдя в консоль AWS, находим среди сервисов Lambda, переходим в раздел Functions и жмем кнопку Create function. Среди вариантов создания функции оставляем активным Author from scratch, указываем название функции (например googleMusic) и выбираем язык Java 8. В разделе Choose or create an execution role выбираем Create a new role with basic lambda permissions. Жмем Create function.
Попадаем в консоль нашей новой функции. Прежде нам надо указать, что именно ее активировать. Жмем Add trigger и выбираем Alexa Skills Kit. Возвращаемся в консоль Алексы, находим в разделе Endpoint строку с ID нашего скилла (amzn1.ask.skill ...), копируем, возвращаемся в консоль Lambda и вставляем его в поле Skill ID.
Жмем Save вверху экрана.
Рядом с кнопкой Save видим идентификатор нашей функции arn: aws: lambda ... Копируем эту строку и возвращаемся в консоль Алексы. Здесь, в разделе Endpoint, вставляем строку в поле Default Region.
Теперь наши скиллы и функция Lambda связаны между собой. Переходим в раздел Intents и жмем кнопку Build model.
Поздравляю, ваш скилл создан и он уже даже доступен на вашей разумной колонке. Правда, пока он ничего не умеет.
Пишем бэкенд
Я буду писать бэкенд на Java в среде IntelliJ IDEA, а проект собирать с помощью Maven.Создаем обычный Java-проект. Прежде добавляем все необходимые зависимости. Для работы с Алексой и Lambda нам нужно:
<dependency>
<groupId>com.amazon.alexa</groupId>
<artifactId>alexa-skills-kit</artifactId>
<version>1.8.1</version>
</dependency>
<dependency>
<groupId>com.amazonaws</groupId>
<artifactId>aws-lambda-java-core</artifactId>
<version>1.2.0</version>
</dependency>
<dependency>
<groupId>com.amazonaws</groupId>
<artifactId>aws-lambda-java-events</artifactId>
<version>2.2.6</version>
</dependency>
Подсоединяем неофициальный Google Music API:
<dependency>
<groupId>com.github.felixgail</groupId>
<artifactId>gplaymusic</artifactId>
<version>0.3.6</version>
</dependency>
Впоследствии во время ДЭПЛ готового кода на Lambda я столкнулся с проблемой: функция не могла найти путь к основному хендлера. Погуглив, я обнаружил, что эта проблема является типичной и ее решают упаковкой проекта в over-jar со всеми зависимостями вместо обычного jar. Для этого добавляем плагин shade для Maven:
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.2.1</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
Переходим к написанию кода. Нам нужно создать спичлет (ну, вы поняли: апплет, сервлет, спичлет ...).
Наш класс должен интерфейс SpeechletV2 и заоверрайдить четыре метода:
onSessionStarted;
onLaunch;
onIntent;
onSessionEnded.
onSessionStarted
В методе onSessionStarted происходят инициализация скилла и его подготовка к работе. Здесь мы должны настроить основной объект GPlayMusic, который и будет искать музыку по нашему запросу. Для этого нам следует залогиниться в Google.
Прежде всего мы должны создать токен (объект AuthToken), предоставив ему адрес Google почты, пароль от нее (panic mode: on!) И IMEI мобильного устройства, на котором сейчас или когда-то было установлено приложение Google Play Music.
Забегая вперед, скажу, что вы сможете успешно протестировать такой логин с локального компьютера, однако после ДЭПЛ вашего кода на Lambda ничего не будет. Дело в том, что Google не поймет, как это вы, только будучи в Украине, вдруг логинитеся из Северной Вирджинии. Даже после подтверждения с вашей стороны, что это действительно были вы и все в порядке, Google не позволит такой логин.
Решить эту проблему можно, создав специальный пароль для приложений. Для этого вам следует зайти в Google-аккаунт, перейти в раздел «Безопасность» и выбрать «Пароль приложений». В ответ Google создаст специальный 16-значный пароль, который вы сможете использовать при создании AuthToken и успешно залогиниться в вашем Google-аккаунте. Итак, вы не передаете в программу ваш настоящий пароль.
Однако и здесь есть нюанс: создать пароль приложений возможно только для аккаунта, у которого включен двухфакторную авторизацию. Поэтому вам придется ее включить. Поэтому парадоксально, но наш скилл даже повысит безопасность вашего Google-аккаунта, заставив вас перейти на более безопасный вариант авторизации.
Конечно, мы не прописывать учетные данные прямо в коде. На Lambda можно указать переменные среды (Environment variables), в которых мы и впишем наши данные: USER_NAME, USER_PASSWORD и IMEI). Также Lambda позволяет зенкриптиты эти данные.
Итак, логинимся:
AuthToken token = null;
try {
token = TokenProvider.provideToken(System.getenv("USER_NAME"), System.getenv("USER_PASSWORD"), System.getenv("IMEI"));
} catch (IOException | Gpsoauth.TokenRequestFailed e) {
log.error("Error while auth token generating", e);
}
api = new GPlayMusic.Builder()
.setAuthToken(token)
.build();
На этом задача метода onSessionStarted выполнена, переходим к onLaunch.
onLaunch
Метод onLaunch вызывают с помощью команды Alexa, open Google Music. В ответ мы просто поздороваемся.
Алекса присылает нам запрос от пользователя в объекте SpeechletRequestEnvelope. В ответ мы должны передать объект SpeechletResponse. В этот объект мы должны поместить все, что нужно нашей колонке для ответа пользователю. В данном случае это будет просто приветствие, которое мы поместим в объект PlainTextOutputSpeech. Кроме самого приветствия, мы добавим repromptSpeech - дополнительную фразу, которой Алекса объяснит пользователю, что именно он может делать:
PlainTextOutputSpeech speech = new PlainTextOutputSpeech();
speech.setText(WELCOME_TEXT);
PlainTextOutputSpeech repromptSpeech = new PlainTextOutputSpeech();
repromptSpeech.setText(CHOOSE_THE_SONG_REQUEST);
Reprompt reprompt = new Reprompt();
reprompt.setOutputSpeech(repromptSpeech);
return SpeechletResponse.newAskResponse(speech, reprompt);;
onIntent
Основная работа происходит именно здесь. Вместе с запросом мы получим объект Intent, который включает в себя название Интент и слот. Мы ожидаем от пользователя два действия: проигрывать музыку и остановить ее. Если пользователь хочет проигрывать музыку, вытаскиваем из Интент слот и передаем его как поисковый запрос в наш Google Music API. Если пользователь захотел тишины, вешаем на наш SpeechletResponse текст Goodbye и отправляем Алексе директиву StopDirective. Если нам послышалось что-то другое, сообщаем пользователя об ошибке и просим повторить.
@Override
public SpeechletResponse onIntent(SpeechletRequestEnvelope<IntentRequest> requestEnvelope) {
logMethodStart("onIntent", requestEnvelope);
IntentRequest request = requestEnvelope.getRequest();
Intent intent = request.getIntent();
String name = intent.getName();
log.info("Requested intent: {}", name);
switch (name) {
case GOOGLE_MUSIC_INTENT:
String song = intent.getSlot(SONG_SLOT).getValue();
try {
return playMusicResponse(song);
} catch (Exception e) {
log.error("Couldn't play {}", song, e);
return newAskRequest(ERROR);
}
case "AMAZON.StopIntent":
case "AMAZON.CancelIntent":
return goodbye();
default:
log.error("Unexpected intent: " + name);
return newAskRequest(WRONG_REQUEST);
}
}
Рассмотрим метод playMusicResponse, который и осуществляет поиск. Прежде мы передаем распознанный Алексой запрос пользователя на песню, которую он хочет слушать, как входной параметр нашем API для поиска. В ответ API может вернуть большой список треков, но, поскольку мы хотим слушать именно ту песню, которую заказали, то обращаем на то, что запрос был задан максимально корректно и первый результат в выдаче есть найрелевантнишим, поэтому ограничиваем количество результатов другом. Если список оказался пустым, сообщаем пользователя, ничего найти не удалось.
Если результаты все же есть, нам нужно создать несколько различных объектов и передать их Алексе для проигрывания музыки:
Stream, содержащий ссылки на песню и несколько дополнительных технических параметров;
AudioItem, содержащий Stream;
PlayDirective - команда Алексе проигрывать музыку, содержащий AudioItem, а также некоторые дополнительные параметры;
SpeechletResponse, содержащий объект PlayDirective, а также текст с названием песни и именем исполнителя, Алекса скажет перед началом проигрывания музыки.
Вот как это выглядит:
private SpeechletResponse playMusicResponse(String songRequest) throws Exception {
List<Track> trackList = api.getTrackApi().search(songRequest, 1);
if (trackList.isEmpty())
return noTrackFoundResponse(songRequest);
Track track = trackList.get(0);
Stream stream = new Stream();
stream.setUrl(track.getStreamURL(StreamQuality.HIGH).toString());
stream.setOffsetInMilliseconds(0);
stream.setExpectedPreviousToken(null);
stream.setToken("0");
AudioItem song = new AudioItem();
song.setStream(stream);
PlayDirective directive = new PlayDirective();
directive.setAudioItem(song);
directive.setPlayBehavior(PlayBehavior.REPLACE_ALL);
SpeechletResponse response = new SpeechletResponse();
response.setDirectives(singletonList(directive));
response.setNullableShouldEndSession(null);
PlainTextOutputSpeech speech = new PlainTextOutputSpeech();
speech.setText("Playing " + track.getTitle() + " by " + track.getArtist());
response.setOutputSpeech(speech);
return response;
}
onSessionEnded
В нашем случае дополнительные действия в этом методе не нужны, поэтому просто лога его вызов.
Кроме класса GoogleMusicSpeechlet, нам следует создать еще один, последует класс SpeechletRequestStreamHandler и станет точкой входа для Алексы. Единственное его задача - сохранять ID скилла, который может вызвать этот функционал.
public class GoogleMusicRequestStreamHandler extends SpeechletRequestStreamHandler {
private static final Set<String> supportedApplicationIds = new HashSet<>();
static {
supportedApplicationIds.add("amzn1.ask.skill.da7a7858-5bf8-46be-a12a-30f85a7b3283");
}
public GoogleMusicRequestStreamHandler() {
super(new GoogleMusicSpeechlet(), supportedApplicationIds);
}
}
Наш бэкенд готов. Приправить логами по вкусу, добавить немного юнит-тестов и можно подавать. Билд jar (mvn package) и Деплой его на Lambda.
В поле Runtime выбираем Java 8, в поле Handler указываем полностью путь к GoogleMusicRequestStreamHandler и загружаем наш jar.
Готово. Теперь наш скилл доступен на колонке и готов выполнить заказ. Кроме того, тестировать его можно через вебинтерфейс в консоли Алексы:
Иногда во время тестирования скилла вы можете получить ошибку, а в логах появится сообщение о сбое аутентификации. Это происходит потому, что переменные окружения еще не... Обычно перезагрузки браузера решает проблему.
Из-за ограничений браузера собственно проигрывания музыки не начнется, однако такого тестирования в большинстве случаев достаточно, чтобы не бегать каждый раз к колонке. Также тестировать скилл можно через мобильное приложение Reverb for Amazon Alexa , который прекрасно имитирует общение с Алексой.
Логи можно читать на CloudWatch Logs.
Код бекенда.
Что дальше?
Конечно, мы не можем опубликовать скилл в магазине Amazon: компания не утвердит его по понятным причинам. Однако спокойно можем пользоваться им дома в режиме In Development.
Можно ли что-то усовершенствовать в нашем скиллы? Конечно. На момент написания этой статьи скилл не умеет работать с командами next, previous, pause и repeat и не умеет создавать плейлисты. Все это можно довольно легко реализовать и, возможно, когда-то это сделаю, но пока скилл выполняет основную функцию, ради которой его и создавал: позволяет слушать именно ту музыку, которую я хочу здесь и сейчас.
Alexa, play We are the champions.
Категория: Новости о онлайн-медиа и IT-корпорациях Ваш IP записан: 3.12.161.151 (03.09.19)
Да 1561 нет :(
Оставить комментарий +25 баллов
Смотреть видео +20 баллов
«попробуйте оставить ваш первый комментарий здесь — это проще, чем кажется!»