Создание кроссплатформенного плеера для SoundCloud® с Fuse

Разработка /
Разработка: Безопасность: Кроссплатформенный плеер для SoundCloud® с Fuse

Мы постоянно получаем запросы от наших пользователей, которые хотят увидеть, как выглядят “реальные программы”, сделанные с . Наш учебник специально предназначен для быстрого начала работы с Fuse, но при этом он пока не содержит описаний сложных задач.

Поэтому я решил сделать полную программу, с публикацией в магазины приложений. Она будет служить примером по-настоящему сложных задач и будет великолепным мастер-классом.

Приложение будет работать с реальным бекендом, оно будет кроссплатформенным (работать в и ) и будет использовать несколько интересных нативных интеграций на обеих платформах.

Как мы видим, SoundCloud® имеет всё необходимое:

  • Они предоставляют REST API (и он бесплатен)
  • Есть много контента для работы (картинки и музыка)
  • Нашему приложению понадобятся нативные компоненты (контролы для управления музыкой и т.п.)


Перед тем, как начать...


Вы можете не устанавливать Fuse и не читать код на Github, чтобы увидеть готовый результат. Приложение FuseCloud можно скачать в Apple App Store и Google Play.

Внимание: приложение FuseCloud это неофициальный плеер для SoundCloud, и никаким образом не связано с SoundCloud. Оно просто использует SoundCloud API.

Реализация


Есть три главных подзадачи: пользовательский интерфейс, написанный на UX и JavaScript; обёртка вокруг SoundCloud REST API; нативный музыкальный плеер.

Навигация

Я использовал компоненты Router и Navigator для построения большей части навигации, с единственным исключением для PageControl в главном представлении, в котором вы можете переключаться между тремя табами (лента новостей, поиск, избранное). После создания каждой страницы приложения отдельным компонентом, навигация будет примерно такой:

// fusecloudnavigationstructure.ux
<Navigator DefaultTemplate="main">
	
	<FuseCloud.MainPage ux:Name="main">
		<PageControl ux:Name="pageControl" Active="searchPage">
			<Page ux:Name="newsFeedPage" />
			<Page ux:Name="searchPage" />
			<Page ux:Name="favoritesPage" />
		</PageControl>
	</FuseCloud.MainPage>
	
	<FuseCloud.CommentsPage ux:Template="comments" router="router" />
	
	<FuseCloud.TrackDetailsPage ux:Name="track" router="router"/>
	
</Navigator>


Бесконечная прокрутка

Благодаря новым возможностям Fuse, я легко создал плавную бесконечную прокрутку для отображения всех комментариев к каждой композиции. Вставляя отдельные комментарии в блок Deferred, я защищён от глюков при автоматической подгрузке новых элементов. Ниже вы можете увидеть пример UX-кода для создания такой прокрутки:

// fusecloudendlessscroller.ux
<ScrollView ClipToBounds="False">
	<StackPanel>
		<Each Items="{comments}">
			<Deferred>
				<FuseCloud.DividerLine Alignment="Top"/>
				<FuseCloud.Comment ux:Name="comment" ThumbnailUrl="{avatar_url}" Username="{username}" Body="{body}" />
			</Deferred>
		</Each>
	</StackPanel>
	<Scrolled To="End" Within="100">
		<Callback Handler="{showMoreComments}" />
	</Scrolled>
</ScrollView>


Прокрутка на 100 точек от нижнего края приложения вызывает JavaScript-функцию, которая подгружает следующие комментарии:

// fusecloudendlessscroller.js
function showMoreComments() {
	if (nCommentsShowing < allComments.length) {
		nCommentsShowing += nCommentsPerPage;
		while (comments.length < nCommentsShowing && comments.length < allComments.length - 1) {
			comments.add(allComments.getAt(comments.length));
		}
	}
}



Замутнение фона

Экран проигрывателя показывает изображение альбома текущей дорожки посередине страницы, при этом заполняя весь фон затемнённой копией этого же изображения. В Fuse такое делается одной строчкой кода: <Blur />, но замутнение больших элементов может плохо отразиться на производительности. Поэтому я использую классический трюк с программной GPU-обработкой для улучшения скорости:

// fusecloudscaledblur.ux
<FuseCloud.AlbumArt Width="20%" Height="20%">
	<Blur Radius="2"/>
	<Scaling Factor="5" />
</FuseCloud.AlbumArt>


Здесь я уменьшаю размер изображения до 20% от оригинального и замутняю уменьшенное изображение, затем увеличиваю получившееся изображение до нормального размера.

Разработка: Кроссплатформенный плеер для SoundCloud® с Fuse

Работаем с SoundCloud API

Вообще это несложная задача и в ней нет никаких специфичных для Fuse техник. Я структурировал обёртку, поэтому каждый запрос возвращает promise. Модель приложения была использована как интерфейс к API через набор функций-геттеров, возвращавших promise-ы в Observable. Код ниже наглядно иллюстрирует этот подход:

Функция, используемая для получения статуса лайка для трека, возвращает promise:

// fusecloudislikingtrackfetch.js
function isLikingTrack(trackId) {
	return Auth.getAccessToken()
		.then(function(token) {
			return FuseCloudGet("me/favorites/" + trackId, {}, token);
		});
}


Модель превращает этот promise в observable, используя удобную функцию (DelayedObservable):

// fusecloudislikingtrackobservable.js
function GetIsLikingTrack(trackId) {
	return DelayedObservable(function(obs) {
		FuseCloud.isLikingTrack(trackId)
			.then(function(result) {
				obs.add(result);
			});
	});
}


Это реально удобно, учитывая что возвращённый observable будет заполнен сразу при получении данных. Затем мы можем сделать привязку к нему и не беспокоиться об обратных вызовах или обновлениях интерфейса приложения.

Функция DelayedObservable работает как мост между API, основанной на promise-ах, и API, основанной на Observable:

// fuseclouddelayedobservable.js
function DelayedObservable(getter) {
	var ret = Observable();
	getter(ret);
	return ret;
}


Эта функция отвечает за обновление Observable при загрузке данных.

OAuth 2.0


SoundCloud API позволяет авторизоваться с помощью протокола OAuth 2.0. Используя модуль InterApp, я легко перекидываю пользователя на авторизацию с помощью нативного браузера:

// fusecloudlaunchuri.js
var uri = "https://soundcloud.com/connect?client_id=" + clientId
		+ "&display=popup"
		+ "&response_type=code"
		+ "&redirect_uri=fuse-soundcloud://fuse";
InterApp.launchUri(uri);


В URL выше передаётся URI обратного вызова, который SoundCloud использует для возврата токена. Fuse позволяет зарегистрировать свою URI-схему в файле проекта:

// fusecloudcustomurischeme.json
"Mobile":{
	"UriScheme": "fuse-soundcloud"
}


Таким образом, SoundCloud API автоматически вернётся в нашу программу, как только токен доступа будет готов.

Создание кроссплатформенного аудиоплеера


Реализация обёртки для нативных плееров была наиболее интересной частью процесса. Частично из-за того, что API для этого различны у Android и iOS, но также по причине монолитной природы медиа-плееров. Я начал с минимального набора требований.

Наш StreamingPlayer должен:

  • Транслировать аудио по URL
  • Продолжать играть, когда приложение уходит в фон
  • Позволять переключаться между треками, пока приложение находится в фоне (используя нативные контролы на экране блокировки)
  • Отображать обложку альбома на экране блокировки

Разработка: Кроссплатформенный плеер для SoundCloud® с Fuse

На бумаге это не кажется слишком сложной задачей, но на деле она оказалась настоящим вызовом.

Прежде всего, подключение к нативному аудиоплееру для проигрывания URL было суперпростым. API у Android MediaPlayer-а и AVPlayer у iOS предлагают это прямо из коробки. Моей начальной задумкой было использовать минимальную обёртку вокруг обоих этих API и просто делать остальную работу (типа управления плейлистами и состоянием) в JavaScript. Но ограничение на фоновое выполнение JS на этих платформах поставило крест на этом (одно из наших требований — возможность использовать контролы на экране блокировки).

Это означало, что я должен реализовать работу с плейлистами в нативном коде, при этом учитывая особенности Android и iOS. К счастью, всё оказалось гораздо проще, так как возможности внешнего кода Fuse позволяют вам легко интегрировать код на Java и Objective-C в проекты Fuse. Это очень удобно!

Другой интересной задачей было получение текущего состояния плеера для обоих компонентов, MediaPlayer и AVPlayer. Оба этих API имеют разные модели состояния и разные пути управления ими, но я нашёл универсальный способ.

И, наконец, работа с экраном блокировки. В iOS это крайне просто; достаточно зарегистрировать несколько системных вызовов. В Android же это не такая простая задача. В API, начиная с уровня 21, Android может получать медиа-нотификации, которые замещают обычные контролы на экране блокировки. Но нужно копать в сторону системы intent-ов для настройки коммуникации между нотификациями и фоновой службой.

Возможности


В приложение FuseCloud встроены очень большие возможности и механизмы, и так как я люблю перечисления, вот небольшой список фич, заложенных в этой программе (и исходном коде):

  • Аутентификация в SoundCloud® по протоколу OAuth 2.0
  • Использование пакета InterApp для запуска url во внешнем браузере и передача отклика по URI
  • Автоматическое обновление некорректных токенов
  • Получение данных по REST API
  • Лента новостей, поиск треков, избранное
  • Возможность поставить лайк и дизлайк треку
  • Обложки треков
  • Отображение комментариев к треку
  • Размещение комментариев
  • Статистика пользователя
  • Смахивание влево/вправо для переключения дорожки
  • Потягивание экрана для обновления
  • Бесконечный список прокрутки
  • Смахивание для показа действий с элементом (дизлайк в избранном)
  • Сохранение состояния UI с использованием Storage API (приветственная информация показывается только один раз при начале работы с программой)®
  • HTTP Audio StreamingPlayer для iOS и Android
  • Трансляция музыки из SoundCloud®
  • Настраиваемая панель перемотки
  • Фоновое проигрывание
  • Контролы на экране блокировки в iOS и Android
  • iOS: следующий, предыдущий, играть/пауза, перемотка на экране блокировки
  • Обложка альбома на экране блокировки
  • Нотификации в Android: следующий, предыдущий, играть/пауза
  • Показ обложки альбома в нотификации и в фоне
  • Плейлисты
  • Автопроигрывание следующего при окончании трека

Выводы и загрузки


Было реально классно работать над этим проектом. Я необъективен, но Fuse реально впечатлила меня  — в который раз.

И механизмы внешнего кода Fuse оказались действительно хорошим способом создания нативных компонентов. Они позволяют использовать документацию к API каждой платформы на своём языке, где это возможно, и без обёрток на JavaScript.

Вы можете скачать приложение FuseCloud для Android и iOS в Apple App Store, в Google Play, и исходный код на Github.

Внимание: ещё раз о создании “реальной” программы (с нативными компонентами и интеграциями с бекендом) — вы почти наверняка столкнетесь с некоторыми трудностями. Мы постоянно улучшаем нашу документацию, но если всё-таки встретите такой случай, дайте знать об этом нам и сообществу, и мы с радостью вам поможем :)

Узнать больше о Fuse можно посмотрев постоянно растущий список примеров (с исходным кодом, конечно), вступайте в наше сообщество (у нас есть классный форум и группа в Slack) или подписывайтесь на нас в Twitter или Facebook.

Автор оригинала Kristian Hasselknippe, Software Engineer at Fuse
0 комментариев
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.