И добавим нашему Manifest несколько разрешений и служб:
<!--Permisions for internet connection and vibrate function-->
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.VIBRATE" />
<!--Service for player and notification-->
<service android:name=".NotificationService" >
Теперь давайте получим ключ Last.fm API, он нужен нам для того, чтобы по имени исполнителя найти его фотографию и показать ее на главном экране. Для этого нужно перейти на страницу создания аккаунта и войти или зарегистрироваться, после чего вам потребуется создать приложение. Эта операция займет 30 секунд, и мы наконец доберемся до API KEY, его вам нужно ввести в поле LAST_FM_KEY класса Const.java:
public static String LAST_FM_KEY = "YOUR_API_KEY";
Далее предлагаю перейти к способу получения ссылки на прямую трансляцию, ее я беру отсюда. Для этого нам нужно запустить радио и, нажав правой кнопкой мыши в Chrome, выбрать пункт “посмотреть код”, после чего выбрать вкладку Network и найти ссылку с самым длинным timeline. Это и будет наш стрим, он уже добавлен в класс Const — аналогичным способом я получил ссылку на HTML-страницу с именем исполнителя и названием трека. В этом коде много костылей, так как парсить HTML — это само по себе странное занятие, но все же я постараюсь его объяснить:
Здесь вы можете видеть получаемый мною нужный фрагмент HTML-страницы:
On Air: Rockabilly Radio (136 connections)
Artist: Mack Stevens
Track: In The Groove (143)
А это его парсинг, надеюсь, что комментарии будут информативны:
// С помощью JSOUP получаем все содержание страницы выше
Document doc = Jsoup.connect(Const.TRACK_INFO_URL).get();
// Так как счет начинается с 0, мы получаем второй элемент:
// как видите, это имя исполнителя
после чего обрезаем первые 29 символов оставляя начало сразу с его имени.
String first_letter =
doc.getElementsByClass("boxed").select("p").get(1).toString().substring(29,
doc.getElementsByClass("boxed").select("p").get(1).toString().length());
/*
После этого с помощью функции split получаем массив из 2-х элементов
c текстом до введенной
в виде аргумента функции строки и текстом после, первый элемент
массива это "чистое" имя исполнителя,
а второй еще нужно "очистить" от ненужного "br" и "p" в конце
*/
String[] parts = first_letter.split(" Track: ");
String first = parts[0];
MainActivity.artist = first;
String second = parts[1];
// Здесь мы удаляем лишние символы путем простого обрезания строки
MainActivity.track = second.substring(0, second.length() - 9);
// После чего отправляем запрос Last.fm для получения информации
// об исполнителе и выводим ее на экран
readJsonFromUrl("http://ws.audioscrobbler.com/2.0/?method=artist.getinfo&artist=
"+ MainActivity.artist.replace(" ", "%20") + "&api_key=
"+Const.LAST_FM_KEY+"&format=json".replace(" ", "%20"));
Этот пример кода, вполне возможно, не является лучшей практикой, но все приложение было написано ночью за несколько часов. Парсинг HTML — занятие сложное, и если есть возможность его не использовать, то нужно этой возможностью пользоваться. Надеюсь, мой опыт обработки HTML-страницы кому-то окажется полезным.
Далее парсинг JSON и получение фото пользователя.
Вот пример отправляемого Last.fm JSON-ответа для певицы Adele:
Все, что нас с вами здесь интересует — это изображение размера “mega”, парсить мы его будем таким образом:
// Создаем JSONObject и передаем ему на вход наш ответ от Last.fm
JSONObject dataJsonObj = new JSONObject(sb.toString());
// После чего проверяем, не ошибка ли пришла
if (!isError(dataJsonObj)) {
// Если все хорошо, парсим 5й элемент массива,
// а именно фото размера "mega"
if (dataJsonObj.optJSONObject("artist").optJSONArray(
"image").optJSONObject(4).optString("#text").toString().length() > 10) {
MainActivity.album = dataJsonObj.optJSONObject("
artist").optJSONArray("image").optJSONObject(4).optString("#text");
} else MainActivity.album = "";
}
Теперь немного об ExoPlayer, и почему я не использовал стандартный MediaPlayer. MediaPlayer абсолютно не оптимизирован для таких нагрузок и частенько останавливал трансляцию. Также на старых (старше пятой) версиях Android перемотка, которую я собирался добавить позже, работает так, как будто плеер подгружает всю аудио дорожку между текущим положением и выбранным пользователем. После недолгих поисков выбор пал на ExoPlayer, сейчас коротко опишу его возможности, код ниже можно использовать как отдельный элемент в своем проекте:
static ExoPlayer exoPlayer;
static TrackRenderer audioRenderer;
public static void start(String URL, Context context)
{
// Объявляем URI со ссылкой на наш стрим,
// либо любой аудио файл в сети
Uri URI = Uri.parse(URL);
FrameworkSampleSource sampleSource =
new FrameworkSampleSource(context,URI, null);
audioRenderer =
new MediaCodecAudioTrackRenderer(sampleSource, null, true);
// Инициализируем плеер
exoPlayer = ExoPlayer.Factory.newInstance(1);
exoPlayer.prepare(audioRenderer);
// Говорим ему начинать проигрывание аудио,
// как только будет окончена буферизация
exoPlayer.setPlayWhenReady(true);
// Добавляем listener для того, чтобы знать когда он начал играть
exoPlayer.addListener(new ExoPlayer.Listener() {
@Override
public void onPlayerStateChanged(boolean playWhenReady,
int playbackState) {
// playbackState 4 означает готовность
// и начало проигрывания, здесь можно убрать диалог загрузки
if(playbackState == 4){
MainActivity.playing_animation.setVisibility(View.VISIBLE);
MainActivity.loading_animation.setVisibility(View.GONE);
MainActivity.control_button.setVisibility(View.VISIBLE);
MainActivity.control_button.setImageResource(R.drawable.pause);
}
}
@Override
public void onPlayWhenReadyCommitted() {
}
@Override
public void onPlayerError(ExoPlaybackException error) {
}
});
}
// Функция для остановки проигрывания,
// после которой нужно будет вызвать start()
public static void stop()
{
if(exoPlayer!=null) {
exoPlayer.stop();
}
}
// Установка громкости также присутствует
// и принимает значения от 0.0 до 1.0
public static void setVolume(float volume)
{
if(exoPlayer!= null) {
exoPlayer.sendMessage(audioRenderer,
MediaCodecAudioTrackRenderer.MSG_SET_VOLUME, volume);
}
}
Финальный этап урока — это уведомление в шторке, сервис и то, каким образом это работает.
Из главного Activity при начале проигрывания аудио мы запускаем сервис, и он берет всю работу на себя, освобождая работу в Activity. Выглядит это так:
// При нажатии на кнопку проигрывания вызывается эта функция,
// и мы запускаем сервис, описанный ранее в Manifest
public void startPlayerService() {
Intent serviceIntent = new Intent(MainActivity.this, NotificationService.class);
serviceIntent.setAction(Const.ACTION.STARTFOREGROUND_ACTION);
startService(serviceIntent);
}
Сервис управляется с помощью Intent. Когда пользователь нажимает на кнопку “play / pause” в шторке, сервис отправляет Intent сам себе и обрабатывает его, так мы отправляем Intent при нажатии:
views.setOnClickPendingIntent(R.id.buttonID, pplayIntent);
PendingIntent pplayIntent = PendingIntent.getService(this, 0,
playIntent, 0);
Intent playIntent = new Intent(this, NotificationService.class);
playIntent.setAction(Const.ACTION.PLAY_ACTION);
А так обрабатываем в onStartCommand:
if (intent.getAction().equals(Const.ACTION.PLAY_ACTION)) {
// Здесь можно изменить изображение на кнопке,
// проиграть звук, остановить сервис и многое другое
}
Поздравляю! Мы разработали клиент-серверное приложение с использованием дополнительных библиотек, воспроизведением аудио, а также работой с сервисами. В конечном итоге получится нечто подобное тому, что вы видите на скриншотах:
Oracle выпустила Java 23, которая включает новые примитивы в шаблонах, модульные импорты и улучшения производительности благодаря добавлению компилятора GraalVM JIT
Миниатюрный инструмент для пентестеров Flipper Zero получил крупное обновление до версии 1.0. В числе нововведений: увеличенная автономность, более быстрая передача данных через Bluetooth, ускоренное чтение NFC-карт и поддержка JavaScript для разработчиков