Представьте, что вы можете слушать свои любимые песни на Яндекс.Музыке, прямо из своего любимого редактора кода, не переключаясь между приложениями. Это уже не мечта, а реальность! В этой статье мы рассмотрим, как интегрировать Яндекс.Музыку в Visual Studio Code и наслаждаться любимой музыкой прямо во время работы.
Перед тем перейти к описанию реализации давайте краем глаза взглянем на само расширение и его возможности.
Я думаю, легко заметить, что левая панель по большому счёту просто повторяет реализацию главной страницы Яндекс Музыки. Здесь вам:
Подборки пока отсутствуют, но со временем и они должны появиться (если не хватает ещё чего-то — дайте знать ?).
Конечно же, расширение — лишь урезанная версия Я.Музыки, поэтому вы можете быстро перейти к нужному треку, альбому или плейлисту с помощью кнопки “Открыть в браузере”.
Не буду углубляться в детали, это всё-таки разбор реализации, а не демо. Если интересно поближе посмотреть на расширение — можете просто установить его.
Есть два способа авторизоваться в расширении:
Почему так? Яндекс постепенно уходит от авторизации по логину и паролю, ведь способ не очень безопасный, и всё меньше и меньше пользователей могут использовать данный способ. Если вы уверены, что ввели корректные данные, но всё равно видите данную ошибку, то вам стоит использовать второй вариант — вход с помощью токена.
Существует 3 способа получить токен:
Оба браузерных расширения используют последний способ и просто перехватывают токен во время редиректа, поэтому вам нужно уже быть авторизованным в Яндекс.Музыке. Исходники всех способов собраны здесь в репозитории (спасибо Илье, что всё это дело собрал вместе).
Самый простой способ — расширение для Хрома, установите его и нажмите на кнопку “Скопировать токен”.
Теперь самое время взглянуть под капот. Реализация расширения будет состоять из 3-х частей:
Я думаю будет логично начать рассказ с базовой вещи, без которой это расширение не увидело бы свет — с API. Подробностей уже не помню, но мне кажется дело было так:
Работа над генерацией клиента всё ещё продолжается, и когда появится первая более-менее стабильная версия — я напишу отдельную статью.
Теперь рассмотрим самые популярные методы.
Первое, что необходимо сделать для использования большинства методов API — авторизация. Вы не сможете получить персональные плейлисты или же ваши любимые треки, если у вас нет токена.
Если для вашего аккаунта всё ещё работает вход по логину и паролю — используйте метод getToken
как показано ниже, иначе — скопируйте токен с помощью Google Chrome Extension.
Большинство плейлистов, которые вы видите на главной странице, можно получить с помощью метода client.landing.getLandingBlocks
(GET /landing3
)
Есть разные типы лендинг блоков:
client.landing.getLandingBlocks("personalplaylists")
Можно получить сразу несколько блоков, указав их через запятую:
Именно такой запрос отправляет официальное приложение Яндекс.Музыки.
Все понравившиеся треки нужно получать в 2 захода:
/users/{userId}/likes/tracks
)/tracks
). Идентификаторы должны выглядеть как строка “<trackId>:<albumId>”.Код будет выглядеть вот так:
Почему нужно делать 2 запроса? Возможно за всё время использования вы налайкали несколько тысяч треков и загружать их все одним махом будет достаточно жирно. Правильнее будет делать пагинацию и загружать все треки постепенно.
Стоит упомянуть ещё несколько методов:
client.tracks.likeTracks
(POST /users/{userId}/likes/tracks/add-multiple
)client.tracks.removeLikedTracks
(POST /users/{userId}/likes/tracks/remove
)client.tracks.getDislikedTracksIds
(GET /users/{userId}/likes/tracks/remove
)Тут ничего интересного — просто перечислю существующие методы работы с плейлистами:
client.playlists.createPlaylist
(POST /users/{userId}/playlists/create
)client.playlists.renamePlaylist
(POST /users/{userId}/playlists/{kind}/name
)client.playlists.deletePlaylist
(POST /users/{userId}/playlists/{kind}/delete
)client.playlists.changePlaylistTracks
(POST /users/{userId}/playlists/{kind}/change-relative
)client.playlists.getPlayLists
(GET /users/{userId}/playlists/list
)kind
(такой идентификатор, уникальный внутри плейлистов пользователя, у других пользователей будут такие же айдишки) — client.playlists.getPlaylistById(userId, playlistKind)
(GET /users/{userId}/playlists/{kind}
)kind
, позволяет получить треки вместе с плейлистами, если передать rich-tracks
как true
— client.playlists.getUserPlaylistsByIds
(GET /users/{userId}/playlists
)kind
— client.playlists.getPlaylistById
(GET /users/{userId}/playlists/{kind}
)Методы работы с радио:
client.rotor.getStationInfo
(GET /rotor/station/{stationId}/info
)client.rotor.getStationTracks
(GET /rotor/station/{stationId}/tracks
)client.rotor.getStationsList
(GET /rotor/stations/list
)client.rotor.getRotorStationsDashboard
(GET /rotor/stations/dashboard
)/rotor/station/{stationId}/feedback
)Если до этого, я просто перечислял запросы, то с радио всё сложнее. Тут мы остановимся поподробнее. Если мы включим HTTP Analyzer, и запустим радио в официальном виндовом приложении Я.Музыки (например “Моя волна” — user:anyourwave
) мы получим вот такую портянку запросов.
Всё выглядит гораздо проще, если нарисовать диаграмму. Стрелочки используются, чтобы показать как связаны между собой запросы, и как одни запросы используют в качестве параметров ответы других запросов.
Если вдруг вы собираетесь создавать свой клиент для радио, то можно реализовать проигрывание без отправки фидбека, что упростит логику. Но мы тут уже обсуждали и пришли к выводу, что фидбек необходим для системы рекомендаций и чтобы избежать повтора треков в персональных плейлистах.
В расширении пока реализовано только одно радио — Моя волна (исходники тут).
Один из самых частых вопросов в чате по Яндекс.Музыке — как получить трек, который играет в данный момент. Мы уже шутили, что нужно интегрировать чат GPT, чтобы он отвечал на данный вопрос, но к сожалению он начал придумывать несуществующие методы. Так вот — получать текущий трек нужно именно на основе очередей.
Очереди создаются при любом воспроизведении плейлиста, альбома или радио. Например, вот так происходит воспроизведение альбома.
GET /albums/{albumId}/with-tracks
POST /queue
, куда мы передаём все треки из плейлистаPOST /queues/{queueId}/update-position?currentIndex=0
Именно благодаря этому мы можем продолжить слушать трек на любом другом устройстве. Например, я включил альбом в официальном виндовом приложении
и теперь могу продолжить слушать трек из браузера или со своего мобильного.
Чтобы получить текущий проигрываемый трек, достаточно нескольких шагов:
client.queues.getQueues()
(GET /queues)id
последней воспроизводимой очереди — первая в массиве полученном на прошлом шаге.client.queues.getQueueById()
(GET /queues/{queueId})client.tracks.getTracks()
(GET /tracks/)Код целиком будет выглядеть вот так:
Хоть для радио и создаётся очередь — получить текущий трек не получится. В этой очереди нет треков, так как треки для радио генерируются динамически. Поэтому если вы продолжите слушать радио на другом устройстве, воспроизведение начнётся совсем с другого трека.
Никому не было бы интересно API, если бы не могли скачивать музыку, ведь это самое главное.
Если вы используете библиотеку yandex-music-client, то для скачивания трека достаточно знать его id
и использовать метод getTrackUrl. Но под капотом скачивание происходит вот так:
Совсем забыл упомянуть очень важную вещь, вы не сможете просто взять и написать веб приложение с помощью моего API. Дело в том, что Яндекс запрещает выполнение кросс доменных запросов.
В своём проекте с OpenAPI схемой я обхожу это ограничение с помощью proxy-сервера на NodeJS, но в этом случае некоторые запросы могут не работать из-за того, что proxy-server не находится в России.
Если вы собираетесь писать своё приложение, в котором будет присутствовать бэкенд — то вы просто можете просто использовать yandex-music-client на бэке и, таким образом, не будет никаких проблем с крос-доменными запросами (но помните, что некоторые методы не доступны вне СНГ). Если вы пишите консольное приложение, телеграмм бота или мобильное приложение — то никаких проблем не будет, ведь CORS существует лишь в браузере.
Теперь, когда у нас есть API для Яндекс Музыки, мы можем всё это дело интегрировать в VS Code. Я не буду описывать всё очень подробно, поэтому, если вам интересна базовая структура расширений VS Code, можете почитать о ней здесь.
Но есть одна из главных вещей, которую необходимо понимать. VS Code — обычное NodeJS приложение, поэтому вы можете использовать совершенно любые библиотеки, которые вы привыкли использовать, будь то axios для выполнения запросов или MobX для управления состоянием.
Ниже описаны основные компоненты, которые необходимы для разработки расширения.
Создание большинства компонентов начинается с добавления так называемых contribution points. Все они описываются в package.json в поле contributes.
Именно здесь необходимо определять:
Чтобы было более понятно как работать с компонентами, давайте рассмотрим пару примеров.
Большая часть расширения представляет собой деревья с плейлистами, альбомами и треками. Прежде чем создать TreeView, необходимо определить соответствующий contribution point в package.json.
Здесь мы определяем 4 дерева, которые будут использоваться в расширении:
Далее для каждого дерева нужно определить data provider, который будет решать какие узлы необходимо отобразить в дереве. Для простоты возьмём дерево, отображающее Чарт.
Код немного упрощён, полную версию можно посмотреть тут и тут.
В VS Code есть альтернатива привычных нам alert/confirm, которые существуют в браузере (и которыми мы обычно не пользуемся) — window.showInformationMessage. Первым аргументом вы указываете сообщение, а затем передаёте сколько угодно кнопок.
VS Code предоставляет 2 возможности хранения данных, обе схожи с localStorage:
Так как нам необходимо хранить пароли, то первый вариант нам не подходит. Все настройки хранятся в общем файле settings.json
и доступны для любого расширения. Это именно те настройки VS Code, которые вы изменяете, чтобы настроить размер шрифта или темы.
Мы же собираемся хранить токен авторизации, поэтому важно использовать именно второй вариант — SecretStorage. Хранится SecretStorage в контексте нашего расширения, который передаётся в метод activate, выполняющийся при запуске расширения. API такой же простой, как и API localStorage в браузере.
Очень просто и понятно оба способа хранения настроек описаны в статье SecretStorage VSCode extension API. В ней же описывается тот же подход с реализацией класс-синглтона для настроек, который я использую в расширении.
Мы разобрались с получением и отображением треков и находимся на финишной прямой, теперь осталось самое главное — воспроизвести их. Кажется, что всё довольно просто — VS Code работает на электроне, значит мы легко сможем воспроизвести музыку, так же как и в браузере. Всё так, да немного не так, немного погуглив, я наткнулся на гитхаб ишью.
В этом ишью есть две новости:
После долгих поисков подходящего npm-пакета я нахожу play-sound. Но после недолгого использования я сразу же понимаю, что использовать этот пакет просто невозможно:
Далее, я нахожу mplayer — обёртку для MPlayer, которая поддерживает все данные функции. Кажется, что всё гораздо лучше — но нет, через некоторое время использования я понимаю, что работает он ужасно:
Тут я понимаю, что расширение vscode для командной разработки поддерживает голосовые звонки, а значит поддерживает воспроизведение аудио внутри vscode. Очевидно, я решил заглянуть под капот этого расширения.
Все расширения в vscode находятся в /Users/<username>/.vscode/extensions
и представляют собой обычное JavaScript приложение, где есть package.json
и набор js
файлов, которые можно изучать и даже дебажить. Интересующее нас расширение находится в папке ms-vsliveshare.vsliveshare-audio-0.1.93
Как дебажить сторонние VS Code расширения
На самом деле — всё очень просто. Открываете папку с нужным расширением в Vs Code, затем нажимаете F5 и выбираете “VS Code Extension Development” — готово.
Немного подебажив исходники, несложно заметить, что расширение под капотом использует electron для совершения звонков с помощью Skype API. Для этого достаточно открыть файл ExternalAppCallingService — в котором одноимённый класс отвечает за запуск электрона.
./out/calling/externalApp/dist
— путь к электрон приложению, с помощью которого будут осуществляться голосовые звонкиЭтот код показывает, как правильно запускать electron
в качестве дочернего процесса vscode — это то, что нам нужно. Получается, чтобы воспроизвести музыку, нам нужно запустить электрон из электрона (VS Code тот же электрон).
Также если покопаться, можно заметить, что электрон скачивается в рантайме только один раз при первом запуске расширения, но к этому мы ещё вернемся.
Теперь мы умеем запускать электрон — круто, теперь нам необходимо научиться воспроизводить треки, а для этого нужно понимать его архитектуру.
Процесс электрона состоит из 2-х частей:
Архитектура Electron
Взаимодействуют эти части с помощью межпроцессовых каналов коммуникации (inter process communication (IPC) channels) — ipcMain и ipcRenderer. По названиям очевидно, что:
Оба канала могут как отправлять, так и получать сообщения.
Подробнее об архитектуре Electron можно почитать здесь, а о IPC-каналах здесь.
Для воспроизведения будем использоваться обычное Audio-API, поэтому здесь всё просто. Самая интересная часть — передача трека, который мы хотим воспроизвести, от VS Code в Renderer-процесс электрона. Передавать мы будем пейлоад следующего типа:
Чтобы понять как это реализовать — давайте взглянем на диаграмму, сейчас нас интересуют лишь зелёные стрелки, начало процесса в VS Code extension ⇒ Store.
Всё ещё сложнее, когда нужно воспроизвести следующий, трек, когда предыдущий завершился. В этом случае — нужно преодолеть целый круг:
При передаче данных от VS Code extension до Electron Process — необходимо их сериализовать в JSON, потому что между процессами мы не можем передавать JavaScript объекты.
Кажется можно уже радоваться, всё прекрасно работает, треки крутятся, а звёзды на гитхабе мутятся (нет!), но начали появляться ишью на гитхабе для мака и линукса.
Изначально, я просто добавил electron, как зависимость к проекту и всё работало хорошо. Как оказалось, нужная версия электрона скачивается при установке npm пакетов, а я работал на винде и соответственно, расширение работало только на винде.
Снова покопавшись в Live Share Audio, я обнаружил, что расширение cкачивает нужную версию электрона в рантайме с собственных серверов.
Мне не хотелось хостить электрон для всевозможных версий, как это сделано в Live Share Audio, из-за чего приостановил работу над расширением.
Через некоторое время я понял, что если electron устанавливает необходимые бинарники в рантайме, то код скачивания должен быть где-то в их репозитории. Немного покопавшись, я нашёл пакет electron/get — именно он используется под капотом, когда вы устанавливаете электрон в зависимости. Также я нашёл почти готовый скрипт для установки нужной версии электрона в рантайме.
На этом всё. Спасибо, если смогли дочитать до самого конца, я думаю таких не много, статья получилась достаточно длинной. Если у вас есть какие-либо вопросы или предложения — обязательно пишите в личку или в комментариях. Буду признателен, если сможете поддержать проект любым способом: