yandex_music - неофициальная библиотека yandex music под flutter
Изначально было сделано для аудиоплеера quark
made by z3nsh0w
Примечание
Приношу извинения пользователям первой версии за ранний выпуск неготовой библиотеки и за существенные изменения в версии 1.0.
Скорее всего, для получения треков используется старый API яндекса. Он полностью работоспособен (возможно яндекс оставили его как legacy), однако ограничен выбором качества аудио и некоторыми функциями.
На данный момент:
- Вы не можете получить качество lossless (Superb) или aac64 (efficient), можно получить только MP3 320 и MP3 192 (в редких случаях aac)
- Вы не можете загружать свои треки на сервера яндекс
Делаем все возможное чтобы узнать по какому принципу работает новое апи (ждите v1.1)
Токен
Для использования библиотеки необходим OAuth-токен
Рекомендуемый метод (если вы разрабатываете приложение с интерфейсом)
Метод заключается в том, чтобы использовать WebView, перебрасывать пользователя на официальную страницу авторизации яндекса и отслеживать его перемещение. Когда пользователь авторизуется, в ссылке обязательно будет храниться токен, который можно использовать в библиотеке. Пример в самом конце.
Альтернативные способы
Альтернативные методы получения токена описаны в инструкции для самостоятельного получения токена
Важно! Нет гарантий что токен не попадет в 3-и руки при таких способах. Все на ваш страх и риск!
Установка
dependencies:
yandex_music: ^latest
flutter pub get
или
flutter pub add yandex_music
Использование
Инициализация
import 'package:yandex_music/yandex_music.dart';
void main() async {
late final YandexMusic ymInstance;
// ... some code
ymInstance = YandexMusic(token: 'your_api_token_here');
await ymInstance.init();
}
Исключения
Во время работы с библиотекой желательно каждый запрос оборачивать в try catch.
Даже правильно составленый запрос может вернуть ошибку, например если запрашиваемая информация не найдена
try {
final playlist = await ymInstance.playlists.getPlaylist(999); // Данный запрос вернет notFound
final result = await ymInstance.search.all(''); // Этот запрос вернет badRequest тк поле поиска пустое
} on YandexMusicException catch (e) {
switch (e.type) {
case YandexMusicExceptionType.network:
print('Network exception: ${e.message}');
break;
case YandexMusicExceptionType.unauthorized:
print('Authorization exception: ${e.message}');
break;
case YandexMusicExceptionType.badRequest:
print('Bad request: ${e.message}');
break;
case YandexMusicExceptionType.wrongRevision:
print('Wrong revision: ${e.message}');
break;
case YandexMusicExceptionType.notFound:
print('Not found: ${e.message}');
break;
// ETC. все типы можно увидеть внутри YandexMusicExceptionType
default:
print('Another error: ${e.message}');
}
} catch (e) {
logger.e('Error not related to yandex music: $e');
rethrow;
}
Информация об аккаунте
// Информация об аккаунте кэшируется в момент инициализации класса.
// Если нужно получить актуальную информацию, используем метод updateCache();
await ymInstance.updateCache();
// Уникальный идентификатор пользователя
int accountId = await ymInstance.account.getAccountID();
// Логин пользователя
String login = await ymInstance.account.getLogin();
// Никнейм пользователя
String displayName = await ymInstance.account.getDisplayName();
// Почта
String email = await ymInstance.account.getEmail();
// Имеет ли пользователь активную подписку PLUS
bool hasPlus = await ymInstance.account.hasPlusSubscription();
// Настройки аккаунта музыки (тема, громкость, видимость аккаунта, настройки радио итд)
// Выдается не из кэша
Map<String, dynamic> settings = await ymInstance.account.getAccountSettings();
// Полная (необработанная) информация об аккаунте
// Выдается не из кэша
Map<String, dynamic> accountInfo = await ymInstance.account.getAllAccountInformation();
Плейлисты
// Для получение разного рода информации о плейлисте необходим KIND - (неуникальный для всего яндекса, но уникальный для пользователя) идентификатор плейлиста. Для примера взят 1000
// При каждом изменении плейлиста ревизия меняется на +1!
// Получение информации о плейлисте вместе с треками
Map<String, dynamic> playlistInfo = await ymInstance.playlists.getPlaylist(1000);
// Если плейлист создан кем-то другим, нужно передать accountId создателя
Map<String, dynamic> playlistInfo = await ymInstance.playlists.getPlaylist(1000, 19578195222);
// Получение всех плейлистов пользователя
List<dynamic> usersPlaylists = await ymInstance.playlists.getUsersPlaylists();
// Получение нескольких плейлистов
List<dynamic> multiplePlaylists = await ymInstance.playlists.getMultiplePlaylists([1000, 1001]);
// Получение нескольких плейлистов от других владельцев
// Плейлист указывается в формате accountId:kind
List<dynamic> playlists = await ymInstance.playlists.getPlaylists(['19578195222:1000']);
// Получение моей волны (рекомендаций) для определенного плейлиста
List<dynamic> recomendations = await ymInstance.playlists.getRecomendationsForPlaylist(1000);
// Создание нового плейлиста
// Создаст публичный плейлист с названием title
Map<String, dynamic> newPlaylist = await ymInstance.playlists.createPlaylist('title', ymInstance.playlists.publicPlaylist);
// Создаст приватный плейлист с названием title56
Map<String, dynamic> newPlaylist = await ymInstance.playlists.createPlaylist('title56', ymInstance.playlists.privatePlaylist);
// Переименование плейлиста (например переименовываем плейлист 1000 на title78)
await ymInstance.playlists.renamePlaylist(1000, 'title78');
// Удаление плейлиста
await ymInstance.playlists.deletePlaylist(1000);
// Добавление треков в плейлист
// ! Если добавить трек дважды, то он добавиться дважды.
// Тоесть если вы захотели переместить трек, то его нужно сначала убрать, потом добавить
// Для изменения плейлиста обычно требуется его ревизия. Однако библиотека расчитана на автоматическое получение ревизии
// Более удобный метод:
// 1000 - KIND
// 35724293 - trackId | пример трека Radiohead - No Surprises
// 4468564 - albumId | Альбом OK Computer OKNOTOK 1997 2017
// 0 - Индекс вставки трека. Трек можно вставить на любой индекс. По умолчанию значение 0. Можно не указывать
// Такой метод можно вызвать несколько раз
await ymInstance.playlists.insertTrackIntoPlaylist(1000, 35724293.toString(), 4468564.toString(), 0);
// Для добавления несколько треков за 1 запрос можно использовать другой метод.
// В этом методе треки передаются списком со словарями
// Все остальное также
// Интересный факт: используя такой метод подразумевается изменение плейлиста столько раз, сколько вы добавите треков. Тоесть на серверах яндекса это по сути вызов insertTrackIntoPlaylist несколько раз.
await ymInstance.playlists.addTracksToPlaylist(1000, [{'trackId': '35724293', 'albumId': '4468564'}], 0);
// Удаление треков из плейлиста
// В этом методе нельзя удалить треки указывая их ID
// Треки удаляются по индексам
// Например, выше мы вставили трек на индекс 0
// Вызываем метод с указанием индекса 0
//
// Первый 0 - индекс с которого начнется удаление
// Второй 0 - индекс которым закончится удаление
// Таким образом можно снести весь плейлист указывая конечный индекс
await ymInstance.playlists.deleteTracksFromPlaylist(1000, 0, 0);
// Изменение доступности плейлиста (публичный, приватный)
await ymInstance.playlists.changeVisibility(1000, ymInstance.playlists.publicPlaylist);
// или
await ymInstance.playlists.changeVisibility(1000, ymInstance.playlists.privatePlaylist);
Треки
// Получение полной информации о треках
List tracks = await ymInstance.tracks.getTracks(['35724293']);
// Получение похожих треков
List similar = await ymInstance.tracks.getSimilarTracks('35724293');
// Получение дополнительной информации о треке
// Может вернуть микроклип, текст песни итд
Map<String, dynamic> trackInfo = await ymInstance.tracks.getAdditionalTrackInfo('35724293');
// Скачивание трека
// Для скачивания трека много функций и букв, самому страшно
// Важно! Трек не скачивается в файл! Трек возвращается в байтовом формате, с которым вы работаете самостоятельно
// 1. Автоматическое скачивание
// Возвращает трек в MP3 320kbps
final track = await ymInstance.tracks.autoDownloadTrack('35724293');
// Также можно получить чисто ссылку для скаичвания
String link = await ymInstance.tracks.autoGetTrackLink('35724293');
// 2. Мануальное скачивание (если хотите заморочиться или скачать в другом качестве)
// 2.1 Вызываем функцию которая вернет информацию о скачивании трека
List downloadInfo = await ymInstance.tracks.getTrackDownloadInfo('35724293');
// Вывод будет таким [{codec: mp3, gain: false, preview: false, downloadInfoUrl: 'link', direct: false, bitrateInKbps: 192}, {codec: mp3, gain: false, preview: false, downloadInfoUrl: 'link', direct: false, bitrateInKbps: 320}]
// Отсюда нам нужна downloadInfoUrl. Для примера возьмем 192 kbps, т.е индекс 0
// Важно! Ссылку для скачивания можно посмотреть только 1 раз и определенное кол-во времени.
// После этого ссылка перестанет быть действительной и будет выдавать 401
// 2.2
String downloadLink = await ymInstance.tracks.getTrackDownloadLink(downloadInfo[0]['downloadInfoUrl']);
// 2.3 Наконец получаем трек
final track = await ymInstance.tracks.getTrackAsBytes(downloadLink);
// 2.4 Для примера запишем в файл
File file = File('$path/track3.mp3');
file.writeAsBytes(track);
Пользовательские треки В будущем здесь также будет возможность загрузить свои треки
// Получение всех понравившихся треков пользователя
List likedTracks = await ymInstance.usertracks.getDislikedTracks();
// Получение всех треков, которые пользователь дизлайкнул.
List dislikedTracks = await ymInstance.usertracks.getLikedTracks();
// Методы ниже не являются void.
// Они возвращают актуальную ревизию плейлиста Понравившиеся (внутри словаря). С точки зрения яндекса понравившиеся треки - это плейлист, но здесь мы не работаем с точки зрения плейлиста.
// Добавление треков в понравившиеся
await ymInstance.usertracks.likeTracks(['35724293']);
// Удаление треков из понравившихся. Не убирает трек из рекомендаций!
await ymInstance.usertracks.unlikeTracks(['35724293']);
Поиск
// При вызове каждого метода можно передать страницу поиска (по умолчанию 0).
// Можно передать bool значение noCorrect. Незнаю что оно делает
// Поиск треков
final tracks = ymInstance.search.tracks('Execution is fun!', 1);
// Поиск артистов
final artists = ymInstance.search.artists('Rammstein');
// Поиск альбомов
final albums = ymInstance.search.albums('Ok computer');
// Поиск подскастов
final podcasts = ymInstance.search.podcasts('кто вообще слушает подкасты?');
// Поиск везде
final result = ymInstance.search.all('mr kitty');
Альбомы
// Получение информации об альбоме
final albumInfo = await ymInstance.albums.getAlbumInformation(3645134);
// Получение альбома с треками
final album = await ymInstance.albums.getAlbum(3456134);
// Получение нескольких альбомов
final albums = await ymInstance.albums.getAlbums([3456134]);
Лендинг Лендинг - то что вы видите заходя на сайт яндекс музыки: подборки, новые релизы итд. Лендинг делится на блоки исходя из контента, который в нем содержится.
// Получение всех блоков
// Возвращает все блоки лендинга, а именно:
// Персональные плейлисты
// Акции
// Новые релизы
// Новые плейлисты
// Миксы
// Чарты
// Артисты
// Альбомы
// Плейлисты
// Контексты проигрывания трека
// Подкасты
final blocks = await ymInstance.landing.getAllLandingBlocks();
// Получение отдельного блока
// Поддерживает:
// landing.newReleases
// landing.newPlaylists
// landing.chart
// landing.podcasts
final block = await ymInstance.landing.getBlock(ymInstance.landing.newReleases);
Получение токена через webview
Для примера используем Flutter inappwebview (не работает под линукс :(
import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
InAppWebViewController? webViewController;
@override
Widget build(BuildContext context) {
return MaterialApp(
// Добавить MaterialApp
home: Scaffold(
// Добавить Scaffold
body: ClipRRect(
borderRadius: const BorderRadius.all(Radius.circular(15)),
child: BackdropFilter(
filter: ImageFilter.blur(sigmaX: 75, sigmaY: 75),
child: Container(
width: 400,
height: 650,
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.2),
border: Border.all(
color: Colors.white.withOpacity(0.2),
width: 1,
),
borderRadius: const BorderRadius.all(Radius.circular(20)),
gradient: LinearGradient(
begin: Alignment.topRight,
end: Alignment.bottomRight,
colors: [
Colors.white.withOpacity(0.15),
Colors.white.withOpacity(0.05),
],
),
),
child: Expanded(
child: InAppWebView(
initialUrlRequest: URLRequest(
url: WebUri(
'https://oauth.yandex.ru/authorize?response_type=token&client_id=23cabbbdc6cd418abb4b39c32c41195d',
),
),
initialSettings: InAppWebViewSettings(
javaScriptEnabled: true,
),
onWebViewCreated: (controller) {
webViewController = controller;
},
onLoadStop: (controller, url) async {
if (url.toString().contains('access_token')) {
String token = url
.toString()
.split('#')[1]
.split('&')[0]
.replaceAll('access_token=', '');
if (token.length > 3) {
print('Got token: $token');
}
}
},
),
),
),
),
),
),
);
}
}