Last active
April 16, 2018 05:40
-
-
Save iscomad/83d758ecff906a78d8372f545318a721 to your computer and use it in GitHub Desktop.
Quran.kz audio player feature
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package kz.sdu.qurankz.audioplayer | |
import android.net.Uri | |
/** | |
* An interface for working with audio player. | |
* | |
* Created by Isco on 2/24/18. | |
* You'll Never Walk Alone | |
*/ | |
interface AudioPlayer { | |
fun prepare(uri: Uri) | |
fun prepare(srcList: List<Uri>) | |
fun play() | |
fun pause() | |
fun stop() | |
fun skipToPrevious() | |
fun skipToNext() | |
fun restart() | |
fun release() | |
fun setListener(listener: AudioPlayerListener) | |
fun getCurrentPosition(): Int | |
fun isPlaying(): Boolean | |
interface AudioPlayerListener { | |
fun onPlayerError(exception: Exception) | |
fun onPlayEnded() | |
fun onLoaded() | |
fun onTrackChanged() | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package kz.sdu.qurankz.audioplayer | |
import android.app.PendingIntent | |
import android.app.Service | |
import android.content.BroadcastReceiver | |
import android.content.Context | |
import android.content.Intent | |
import android.content.IntentFilter | |
import android.media.AudioAttributes | |
import android.media.AudioFocusRequest | |
import android.media.AudioManager | |
import android.os.Build | |
import android.os.Bundle | |
import android.os.IBinder | |
import android.support.v4.app.NotificationManagerCompat | |
import android.support.v4.media.MediaMetadataCompat | |
import android.support.v4.media.session.MediaButtonReceiver | |
import android.support.v4.media.session.MediaSessionCompat | |
import android.support.v4.media.session.PlaybackStateCompat | |
import android.util.Log | |
import kz.sdu.qurankz.activity.QuranActivity | |
import kz.sdu.qurankz.model.AyatModel | |
import kz.sdu.qurankz.util.QuranSQLiteOpenHelper | |
private val TAG = AudioPlayerService::class.java.simpleName | |
private const val NOTIFICATION_ID = 404 | |
/** | |
* A service that provides an ability to play an audio in background. | |
* | |
* Created by Isco on 1/21/18. | |
* You'll Never Walk Alone | |
*/ | |
class AudioPlayerService : Service() { | |
lateinit var mediaSession: MediaSessionCompat | |
private val binder = AudioServiceBinder(this) | |
private lateinit var audioPlayer: AudioPlayer | |
private lateinit var sqlHelper: QuranSQLiteOpenHelper | |
private var audioManager: AudioManager? = null | |
private var audioFocusRequest: AudioFocusRequest? = null | |
private var audioFocusRequested = false | |
private var playbackNowAuthorized = false | |
private var resumeOnFocusGain = false | |
private var currentPlaybackState = PlaybackStateCompat.STATE_STOPPED | |
private var mediaRepository: MediaRepository? = null | |
private lateinit var notificationProvider: MediaStyleNotificationProvider | |
private val metadataBuilder = MediaMetadataCompat.Builder() | |
private val stateBuilder = PlaybackStateCompat.Builder().setActions( | |
PlaybackStateCompat.ACTION_PLAY | |
or PlaybackStateCompat.ACTION_STOP | |
or PlaybackStateCompat.ACTION_PAUSE | |
or PlaybackStateCompat.ACTION_PLAY_PAUSE | |
or PlaybackStateCompat.ACTION_SKIP_TO_NEXT | |
or PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS | |
) | |
private val becomingNoisyReceiver: BroadcastReceiver = object : BroadcastReceiver() { | |
override fun onReceive(context: Context, intent: Intent) { | |
if (AudioManager.ACTION_AUDIO_BECOMING_NOISY == intent.action) { | |
mediaSessionCallback.onPause() | |
} | |
} | |
} | |
private val mediaSessionCallback: MediaSessionCompat.Callback = object : MediaSessionCompat.Callback() { | |
override fun onPlay() { | |
startService(Intent(applicationContext, AudioPlayerService::class.java)) | |
val trackPosition = audioPlayer.getCurrentPosition() | |
val track = mediaRepository?.tracks?.get(trackPosition) ?: return | |
updateMetadataFromTrack(track) | |
if (!requestAudioFocus()) return | |
registerReceiver(becomingNoisyReceiver, IntentFilter(AudioManager.ACTION_AUDIO_BECOMING_NOISY)) | |
resumeOnFocusGain = false | |
mediaSession.isActive = true | |
audioPlayer.play() | |
mediaSession.setPlaybackState( | |
stateBuilder.setState( | |
PlaybackStateCompat.STATE_PLAYING, | |
PlaybackStateCompat.PLAYBACK_POSITION_UNKNOWN, | |
1F | |
).build() | |
) | |
currentPlaybackState = PlaybackStateCompat.STATE_PLAYING | |
refreshNotificationAndForegroundStatus(currentPlaybackState) | |
} | |
override fun onPause() { | |
if (audioPlayer.isPlaying()) { | |
audioPlayer.pause() | |
unregisterReceiver(becomingNoisyReceiver) | |
} | |
mediaSession.setPlaybackState( | |
stateBuilder.setState( | |
PlaybackStateCompat.STATE_PAUSED, | |
PlaybackStateCompat.PLAYBACK_POSITION_UNKNOWN, | |
1F | |
).build() | |
) | |
currentPlaybackState = PlaybackStateCompat.STATE_PAUSED | |
resumeOnFocusGain = false | |
refreshNotificationAndForegroundStatus(currentPlaybackState) | |
} | |
override fun onStop() { | |
if (audioPlayer.isPlaying()) { | |
audioPlayer.stop() | |
unregisterReceiver(becomingNoisyReceiver) | |
} | |
abandonAudioFocus() | |
mediaSession.isActive = false | |
mediaSession.setPlaybackState( | |
stateBuilder.setState( | |
PlaybackStateCompat.STATE_STOPPED, | |
PlaybackStateCompat.PLAYBACK_POSITION_UNKNOWN, | |
1F | |
).build() | |
) | |
currentPlaybackState = PlaybackStateCompat.STATE_STOPPED | |
refreshNotificationAndForegroundStatus(currentPlaybackState) | |
stopSelf() | |
} | |
override fun onSkipToNext() { | |
val nextPosition = audioPlayer.getCurrentPosition() + 1 | |
val tracks = mediaRepository?.tracks ?: return | |
if (nextPosition >= tracks.size) return | |
val track = tracks[nextPosition] | |
updateMetadataFromTrack(track) | |
refreshNotificationAndForegroundStatus(currentPlaybackState) | |
audioPlayer.skipToNext() | |
} | |
override fun onSkipToPrevious() { | |
val prevPosition = audioPlayer.getCurrentPosition() - 1 | |
if (prevPosition < 0) return | |
val track = mediaRepository?.tracks?.get(prevPosition) ?: return | |
updateMetadataFromTrack(track) | |
refreshNotificationAndForegroundStatus(currentPlaybackState) | |
audioPlayer.skipToPrevious() | |
} | |
override fun onCustomAction(action: String?, extras: Bundle?) { | |
if (action == null || extras == null) return | |
when (action) { | |
CUSTOM_ACTION_PREPARE -> { | |
val fromAyat = AyatModel().apply { | |
surahId = extras.getInt(EXTRA_FROM_SURA) | |
ayatNumber = extras.getInt(EXTRA_FROM_AYAT) | |
} | |
val toAyat = AyatModel().apply { | |
surahId = extras.getInt(EXTRA_TO_SURA) | |
ayatNumber = extras.getInt(EXTRA_TO_AYAT) | |
} | |
val sourceUrl = extras.getString(EXTRA_SOURCE_URL) | |
mediaRepository = QuranMediaUriRepository(sqlHelper, fromAyat, toAyat, sourceUrl) | |
audioPlayer.prepare(mediaRepository!!.tracks.map { it.uri }) | |
} | |
} | |
} | |
} | |
private val audioFocusChangeListener = AudioManager.OnAudioFocusChangeListener { focusChange -> | |
when (focusChange) { | |
AudioManager.AUDIOFOCUS_GAIN -> { | |
if (resumeOnFocusGain) { | |
mediaSessionCallback.onPlay() | |
} | |
} | |
AudioManager.AUDIOFOCUS_LOSS_TRANSIENT, | |
AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK -> { | |
if (audioPlayer.isPlaying()) { | |
mediaSessionCallback.onPause() | |
resumeOnFocusGain = true | |
} | |
} | |
else -> { | |
mediaSessionCallback.onPause() | |
resumeOnFocusGain = false | |
} | |
} | |
} | |
override fun onCreate() { | |
super.onCreate() | |
sqlHelper = QuranSQLiteOpenHelper(this) | |
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { | |
val audioAttributes = AudioAttributes.Builder() | |
.setUsage(AudioAttributes.USAGE_MEDIA) | |
.setContentType(AudioAttributes.CONTENT_TYPE_MUSIC) | |
.build() | |
audioFocusRequest = AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN) | |
.setOnAudioFocusChangeListener(audioFocusChangeListener) | |
.setAcceptsDelayedFocusGain(false) | |
.setWillPauseWhenDucked(true) | |
.setAudioAttributes(audioAttributes) | |
.build() | |
} | |
notificationProvider = MediaStyleNotificationProvider(this) | |
audioManager = getSystemService(Context.AUDIO_SERVICE) as AudioManager | |
initMediaSession() | |
initAudioPlayer() | |
} | |
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int { | |
MediaButtonReceiver.handleIntent(mediaSession, intent) | |
return super.onStartCommand(intent, flags, startId) | |
} | |
override fun onDestroy() { | |
audioPlayer.release() | |
mediaSession.release() | |
super.onDestroy() | |
} | |
override fun onBind(intent: Intent?): IBinder { | |
return binder | |
} | |
private fun initMediaSession() { | |
mediaSession = MediaSessionCompat(this, TAG) | |
mediaSession.setFlags( | |
MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS | |
or MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS | |
) | |
mediaSession.setCallback(mediaSessionCallback) | |
val intent = Intent(applicationContext, QuranActivity::class.java) | |
mediaSession.setSessionActivity(PendingIntent.getActivity(applicationContext, 0, intent, 0)) | |
} | |
private fun initAudioPlayer() { | |
audioPlayer = ExoAudioPlayer(baseContext) | |
audioPlayer.setListener(object : AudioPlayer.AudioPlayerListener { | |
override fun onPlayerError(exception: Exception) { | |
Log.e(TAG, exception.localizedMessage, exception) | |
} | |
override fun onPlayEnded() { | |
abandonAudioFocus() | |
mediaSession.isActive = false | |
mediaSession.setPlaybackState( | |
stateBuilder.setState( | |
PlaybackStateCompat.STATE_STOPPED, | |
PlaybackStateCompat.PLAYBACK_POSITION_UNKNOWN, | |
1F | |
).build() | |
) | |
currentPlaybackState = PlaybackStateCompat.STATE_STOPPED | |
refreshNotificationAndForegroundStatus(currentPlaybackState) | |
} | |
override fun onLoaded() { | |
Log.i(TAG, "player loading finished.") | |
} | |
override fun onTrackChanged() { | |
val position = audioPlayer.getCurrentPosition() | |
val tracks = mediaRepository?.tracks ?: return | |
if (position < 0 || position >= tracks.size) return | |
val track = tracks[position] | |
updateMetadataFromTrack(track) | |
refreshNotificationAndForegroundStatus(currentPlaybackState) | |
} | |
}) | |
} | |
private fun updateMetadataFromTrack(track: MediaRepository.Track) { | |
metadataBuilder.putString(MediaMetadataCompat.METADATA_KEY_TITLE, track.title) | |
metadataBuilder.putString(MediaMetadataCompat.METADATA_KEY_ALBUM, track.album) | |
mediaSession.setMetadata(metadataBuilder.build()) | |
} | |
private fun refreshNotificationAndForegroundStatus(playbackState: Int) { | |
when (playbackState) { | |
PlaybackStateCompat.STATE_PLAYING -> { | |
startForeground( | |
NOTIFICATION_ID, | |
notificationProvider.getNotification(mediaSession, playbackState) | |
) | |
} | |
PlaybackStateCompat.STATE_PAUSED -> { | |
// На паузе мы перестаем быть foreground, однако оставляем уведомление, | |
// чтобы пользователь мог нажать play | |
NotificationManagerCompat.from(this) | |
.notify( | |
NOTIFICATION_ID, | |
notificationProvider.getNotification(mediaSession, playbackState) | |
) | |
stopForeground(false) | |
} | |
else -> { | |
// Все, можно прятать уведомление | |
stopForeground(true) | |
} | |
} | |
} | |
private fun requestAudioFocus(): Boolean { | |
if (!audioFocusRequested) { | |
audioFocusRequested = true | |
val audioFocusResult = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { | |
audioManager?.requestAudioFocus(audioFocusRequest) | |
} else { | |
@Suppress("DEPRECATION") | |
audioManager?.requestAudioFocus( | |
audioFocusChangeListener, | |
AudioManager.STREAM_MUSIC, | |
AudioManager.AUDIOFOCUS_GAIN | |
) | |
} | |
playbackNowAuthorized = audioFocusResult == AudioManager.AUDIOFOCUS_REQUEST_GRANTED | |
} | |
return playbackNowAuthorized | |
} | |
private fun abandonAudioFocus() { | |
if (audioFocusRequested) { | |
audioFocusRequested = false | |
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { | |
audioManager?.abandonAudioFocusRequest(audioFocusRequest) | |
} else { | |
@Suppress("DEPRECATION") | |
audioManager?.abandonAudioFocus(audioFocusChangeListener) | |
} | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package kz.sdu.qurankz.audioplayer | |
import android.os.Binder | |
import android.support.v4.media.session.MediaSessionCompat | |
/** | |
* A service binder class of [AudioPlayerService] | |
* | |
* Created by Isco on 1/22/18. | |
* You'll Never Walk Alone | |
*/ | |
class AudioServiceBinder(private val service: AudioPlayerService) : Binder() { | |
fun getMediaSessionToken(): MediaSessionCompat.Token = service.mediaSession.sessionToken | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package kz.sdu.qurankz.audioplayer | |
import android.content.Context | |
import android.net.Uri | |
import com.google.android.exoplayer2.ExoPlaybackException | |
import com.google.android.exoplayer2.ExoPlayer | |
import com.google.android.exoplayer2.ExoPlayerFactory | |
import com.google.android.exoplayer2.PlaybackParameters | |
import com.google.android.exoplayer2.Player | |
import com.google.android.exoplayer2.Timeline | |
import com.google.android.exoplayer2.source.ConcatenatingMediaSource | |
import com.google.android.exoplayer2.source.ExtractorMediaSource | |
import com.google.android.exoplayer2.source.TrackGroupArray | |
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector | |
import com.google.android.exoplayer2.trackselection.TrackSelectionArray | |
import com.google.android.exoplayer2.trackselection.TrackSelector | |
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory | |
import com.google.android.exoplayer2.util.Util | |
/** | |
* Default implementation of [AudioPlayer] via ExoPlayer. | |
* | |
* Created by Isco on 2/24/18. | |
* You'll Never Walk Alone | |
*/ | |
class ExoAudioPlayer(context: Context) : AudioPlayer, Player.EventListener { | |
private val player: ExoPlayer | |
private val mediaSourceFactory: ExtractorMediaSource.Factory | |
private var listener: AudioPlayer.AudioPlayerListener? = null | |
private var currentIndex = 0 | |
init { | |
val trackSelector: TrackSelector = DefaultTrackSelector() | |
val userAgent = Util.getUserAgent(context, context.applicationInfo.name) | |
val dataSourceFactory = DefaultDataSourceFactory(context, userAgent, null) | |
mediaSourceFactory = ExtractorMediaSource.Factory(dataSourceFactory) | |
player = ExoPlayerFactory.newSimpleInstance(context, trackSelector) | |
player.addListener(this) | |
} | |
override fun prepare(uri: Uri) { | |
player.prepare(mediaSourceFactory.createMediaSource(uri)) | |
} | |
override fun prepare(srcList: List<Uri>) { | |
val mediaSources = mutableListOf<ExtractorMediaSource>() | |
srcList.forEach { mediaSources.add(mediaSourceFactory.createMediaSource(it)) } | |
val concatenatingMediaSource = ConcatenatingMediaSource(*mediaSources.toTypedArray()) | |
player.prepare(concatenatingMediaSource) | |
} | |
override fun play() { | |
player.playWhenReady = true | |
} | |
override fun pause() { | |
player.playWhenReady = false | |
} | |
override fun stop() { | |
player.apply { | |
playWhenReady = false | |
seekTo(0) | |
} | |
} | |
override fun skipToPrevious() { | |
player.apply { seekTo(previousWindowIndex, 0) } | |
} | |
override fun skipToNext() { | |
player.apply { seekTo(nextWindowIndex, 0) } | |
} | |
override fun restart() { | |
player.seekTo(0) | |
} | |
override fun release() { | |
player.release() | |
} | |
override fun setListener(listener: AudioPlayer.AudioPlayerListener) { | |
this.listener = listener | |
} | |
override fun getCurrentPosition(): Int = player.currentWindowIndex | |
override fun isPlaying(): Boolean = player.playWhenReady | |
//region Exo player listener | |
override fun onPlaybackParametersChanged(playbackParameters: PlaybackParameters?) { | |
} | |
override fun onSeekProcessed() { | |
} | |
override fun onTracksChanged(trackGroups: TrackGroupArray?, trackSelections: TrackSelectionArray?) { | |
listener?.onTrackChanged() | |
} | |
override fun onPlayerError(error: ExoPlaybackException?) { | |
listener?.onPlayerError(error ?: IllegalStateException("Unknown ExoPlayer exception")) | |
} | |
override fun onLoadingChanged(isLoading: Boolean) { | |
if (!isLoading) { | |
listener?.onLoaded() | |
} | |
} | |
override fun onPositionDiscontinuity(reason: Int) { | |
val newIndex = player.currentWindowIndex | |
if (currentIndex != newIndex) { | |
listener?.onTrackChanged() | |
currentIndex = newIndex | |
} | |
} | |
override fun onRepeatModeChanged(repeatMode: Int) { | |
} | |
override fun onShuffleModeEnabledChanged(shuffleModeEnabled: Boolean) { | |
} | |
override fun onTimelineChanged(timeline: Timeline?, manifest: Any?) { | |
} | |
override fun onPlayerStateChanged(playWhenReady: Boolean, playbackState: Int) { | |
if (playbackState == Player.STATE_ENDED) { | |
listener?.onPlayEnded() | |
} | |
} | |
//endregion | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package kz.sdu.qurankz.audioplayer | |
import android.net.Uri | |
/** | |
* A repository interface for audio tracks [Track] | |
* | |
* Created by Isco on 2/28/18. | |
* You'll Never Walk Alone | |
*/ | |
interface MediaRepository { | |
val tracks: List<Track> | |
data class Track( | |
val title: String, | |
val album: String, | |
val uri: Uri | |
) | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package kz.sdu.qurankz.audioplayer | |
import android.app.Notification | |
import android.app.NotificationChannel | |
import android.app.NotificationManager | |
import android.content.Context | |
import android.os.Build | |
import android.support.v4.app.NotificationCompat | |
import android.support.v4.content.ContextCompat | |
import android.support.v4.media.MediaDescriptionCompat | |
import android.support.v4.media.MediaMetadataCompat | |
import android.support.v4.media.session.MediaButtonReceiver | |
import android.support.v4.media.session.MediaControllerCompat | |
import android.support.v4.media.session.MediaSessionCompat | |
import android.support.v4.media.session.PlaybackStateCompat | |
import kz.sdu.qurankz.R | |
private const val NOTIFICATION_DEFAULT_CHANNEL_ID = "default_channel" | |
/** | |
* A helper class that provides a media-styled notification with audio controller buttons. | |
* | |
* Created by Isco on 2/28/18. | |
* You'll Never Walk Alone | |
*/ | |
class MediaStyleNotificationProvider(private val context: Context) { | |
init { | |
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { | |
val notificationChannel = NotificationChannel( | |
NOTIFICATION_DEFAULT_CHANNEL_ID, | |
context.getString(R.string.notification_channel_name), | |
NotificationManager.IMPORTANCE_DEFAULT | |
) | |
val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager | |
notificationManager.createNotificationChannel(notificationChannel) | |
} | |
} | |
fun getNotification(mediaSession: MediaSessionCompat, playbackState: Int): Notification { | |
val builder = createNotificationBuilder(mediaSession) | |
builder.addAction( | |
NotificationCompat.Action( | |
R.drawable.ic_skip_previous_white_24px, | |
context.getString(R.string.notification_previous), | |
MediaButtonReceiver.buildMediaButtonPendingIntent( | |
context, | |
PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS | |
) | |
) | |
) | |
if (playbackState == PlaybackStateCompat.STATE_PLAYING) { | |
builder.addAction( | |
NotificationCompat.Action( | |
R.drawable.ic_pause_circle_filled_white_24px, | |
context.getString(R.string.notification_pause), | |
MediaButtonReceiver.buildMediaButtonPendingIntent( | |
context, | |
PlaybackStateCompat.ACTION_PLAY_PAUSE | |
) | |
) | |
) | |
} else { | |
builder.addAction( | |
NotificationCompat.Action( | |
R.drawable.ic_play_circle_filled_white_24px, | |
context.getString(R.string.notification_play), | |
MediaButtonReceiver.buildMediaButtonPendingIntent( | |
context, | |
PlaybackStateCompat.ACTION_PLAY_PAUSE | |
) | |
) | |
) | |
} | |
builder.addAction( | |
NotificationCompat.Action( | |
R.drawable.ic_skip_next_white_24px, | |
context.getString(R.string.notification_next), | |
MediaButtonReceiver.buildMediaButtonPendingIntent( | |
context, | |
PlaybackStateCompat.ACTION_SKIP_TO_NEXT | |
) | |
) | |
) | |
builder.setStyle(android.support.v4.media.app.NotificationCompat.MediaStyle() | |
// В компактном варианте показывать Action с данным порядковым номером. | |
// В нашем случае это play/pause. | |
.setShowActionsInCompactView(1) | |
// Отображать крестик в углу уведомления для его закрытия. | |
// Это связано с тем, что для API < 21 из-за ошибки во фреймворке | |
// пользователь не мог смахнуть уведомление foreground-сервиса | |
// даже после вызова stopForeground(false). | |
// Так что это костыль. | |
// На API >= 21 крестик не отображается, там просто смахиваем уведомление. | |
.setShowCancelButton(true) | |
.setCancelButtonIntent( | |
MediaButtonReceiver.buildMediaButtonPendingIntent( | |
context, | |
PlaybackStateCompat.ACTION_STOP | |
) | |
) | |
// Передаем токен. Это важно для Android Wear. Если токен не передать, | |
// кнопка на Android Wear будет отображаться, но не будет ничего делать | |
.setMediaSession(mediaSession.sessionToken)) | |
builder.setSmallIcon(R.mipmap.ic_launcher) | |
builder.color = ContextCompat.getColor(context, R.color.primary) | |
builder.setShowWhen(false) | |
// Это важно. Без этой строчки уведомления не отображаются на Android Wear | |
// и криво отображаются на самом телефоне. | |
builder.priority = NotificationCompat.PRIORITY_HIGH | |
builder.setOnlyAlertOnce(true) | |
return builder.build() | |
} | |
private fun createNotificationBuilder(mediaSession: MediaSessionCompat): NotificationCompat.Builder { | |
val controller: MediaControllerCompat = mediaSession.controller | |
val mediaMetadata: MediaMetadataCompat = controller.metadata | |
val description: MediaDescriptionCompat = mediaMetadata.description | |
val builder: NotificationCompat.Builder = NotificationCompat.Builder(context, NOTIFICATION_DEFAULT_CHANNEL_ID) | |
builder.setContentTitle(description.title) | |
.setContentText(description.subtitle) | |
.setSubText(description.description) | |
.setLargeIcon(description.iconBitmap) | |
.setContentIntent(controller.sessionActivity) | |
.setDeleteIntent( | |
MediaButtonReceiver.buildMediaButtonPendingIntent(context, PlaybackStateCompat.ACTION_STOP) | |
) | |
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC) | |
return builder | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package kz.sdu.qurankz.audioplayer | |
import android.net.Uri | |
import kz.sdu.qurankz.model.AyatModel | |
import kz.sdu.qurankz.util.QuranSQLiteOpenHelper | |
/** | |
* An implementation of [MediaRepository] that provides a data to load parts of Quran from the given source | |
* | |
* Created by Isco on 3/1/18. | |
* You'll Never Walk Alone | |
*/ | |
class QuranMediaUriRepository( | |
sqlHelper: QuranSQLiteOpenHelper, | |
fromAyat: AyatModel, | |
toAyat: AyatModel, | |
sourceUrl: String | |
) : MediaRepository { | |
override val tracks: List<MediaRepository.Track> | |
init { | |
val surahList = sqlHelper.getSurahInRange(fromAyat.surahId, toAyat.surahId) | |
val mutableList = mutableListOf<MediaRepository.Track>() | |
surahList.forEachIndexed { index, item -> | |
var fromIndex = 1 | |
var toIndex = item.ayaNumber | |
if (index == 0) { | |
fromIndex = fromAyat.ayatNumber | |
} | |
if (index == surahList.size - 1) { | |
toIndex = toAyat.ayatNumber | |
} | |
if (item.id != 1L && fromIndex == 1 && toIndex > 1) { | |
// HARDCODE: we are adding an extra ayat, the base one | |
fromIndex = 0 | |
} | |
(fromIndex..toIndex).mapTo(mutableList) { | |
MediaRepository.Track( | |
"$it-аят", // TODO eliminate the hardcode | |
item.nameKazakh, | |
Uri.parse(getAudioFileUrl(sourceUrl, item.id.toInt(), it)) | |
) | |
} | |
} | |
tracks = mutableList | |
} | |
private fun getAudioFileUrl(sourceUrl: String, surahId: Int, ayatId: Int): String { | |
return String.format(sourceUrl + "mp3/%03d%03d.mp3", surahId, ayatId) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment