[android : kotlin] 코틀린 Notification MediaStyle 사용시 앨범 자켓(이미지) setLargeIcon 설정 방법 :Bitmap , MediaMetadataCompat
Notificaiton 구현시 미디어 스타일(MediaStyle) 사용하는 경우 아래 스크린샷과 같이 해당 노래의 앨범자켓을 표시할 수 있다. 그런데 이 방법에는 제약이 있다. 무조건 Bitmap 데이터로 가져와야한다는 것이다.
코틀린 mediaMetadata 설정하는 방법
NotificationCompat 빌더 코드이다. 빌더 생성시 mediaMetadata에서 description 데이터를 가져와서 설정해주고 있다. 그럼으로 mediaMetadata 설정하는 방법을 먼저 알아보자.
private fun getNotification(playbackState: Int) : NotificationCompat.Builder {
// Get the session's metadata
val controller = mediaSession.controller
val mediaMetadata = controller.metadata
val description = mediaMetadata.description
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val builder = NotificationCompat.Builder(this, CHANNEL_ID).apply {
setContentTitle(description.title)
setContentText(description.subtitle)
setSubText(description.description)
setLargeIcon(description.iconBitmap) // 반드시 bitmap호출
// Show controls on lock screen even when user hides sensitive content.
setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
// Enable launching the player by clicking the notification
setContentIntent(controller.sessionActivity)
// Add an app icon and set its accent color
// Be careful about the color
//color = ContextCompat.getColor(context, R.color.primaryDark)
setSmallIcon(R.mipmap.ic_launcher_round)
setShowWhen(false)
priority = NotificationCompat.PRIORITY_HIGH
//setOnlyAlertOnce(true)
//setChannelId(CHANNEL_ID)
setDeleteIntent(
MediaButtonReceiver.buildMediaButtonPendingIntent(
applicationContext,
PlaybackStateCompat.ACTION_STOP
)
)
addAction(
R.drawable.baseline_skip_previous_24,
getString(R.string.previous),
MediaButtonReceiver.buildMediaButtonPendingIntent(
applicationContext,
PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS
)
).build()
// Add a pause button
if(playbackState == PlaybackStateCompat.STATE_PLAYING) {
addAction(
NotificationCompat.Action(
R.drawable.baseline_pause_circle_outline_24,
getString(R.string.pause),
MediaButtonReceiver.buildMediaButtonPendingIntent(
applicationContext,
PlaybackStateCompat.ACTION_PLAY_PAUSE
)
)
)
}else{
addAction(
R.drawable.baseline_play_circle_outline_24,
getString(R.string.play),
MediaButtonReceiver.buildMediaButtonPendingIntent(
applicationContext,
PlaybackStateCompat.ACTION_PLAY_PAUSE
)
).build()
}
addAction(
R.drawable.baseline_skip_next_24,
getString(R.string.next),
MediaButtonReceiver.buildMediaButtonPendingIntent(
applicationContext,
PlaybackStateCompat.ACTION_SKIP_TO_NEXT
)
).build()
// Take advantage of MediaStyle features
setStyle(
androidx.media.app.NotificationCompat.MediaStyle()
.setMediaSession(sessionToken)
.setShowActionsInCompactView(0, 1, 2)
// Add a cancel button
.setShowCancelButton(true)
.setCancelButtonIntent(
MediaButtonReceiver.buildMediaButtonPendingIntent(
applicationContext, PlaybackStateCompat.ACTION_STOP
)
)
)
}
return builder
}
return NotificationCompat.Builder(this, CHANNEL_ID)
}
아래 코드스니펫을 보자. MyMediaSessionCallback메서드를 구현하였다. onPlay 메서드를 오버라이드 하였는데, 그 안에서 updateMetaData()메서드를 호출해주었다.
inner class MyMediaSessionCallback : MediaSessionCompat.Callback() {
var currentState = PlaybackStateCompat.STATE_STOPPED
override fun onPlay() {
if(!exoPlayer.playWhenReady) {
var item = SongLib.getCurrent()
updateMetadata(item)
prepareToPlay(item.contentUri)
if (!audioFocusRequested) {
audioFocusRequested = true
val audioFocusResult: Int
audioFocusResult = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
audioManager.requestAudioFocus(audioFocusRequest)
} else {
audioManager.requestAudioFocus(
audioFocusChangeListener,
AudioManager.STREAM_MUSIC,
AudioManager.AUDIOFOCUS_GAIN
)
}
if (audioFocusResult != AudioManager.AUDIOFOCUS_REQUEST_GRANTED) return
}
mediaSession.isActive = true
exoPlayer.playWhenReady = true
registerReceiver(
becomingNoisyReceiver,
IntentFilter(AudioManager.ACTION_AUDIO_BECOMING_NOISY)
)
mediaSession.setPlaybackState(
stateBuilder.setState(
PlaybackStateCompat.STATE_PLAYING,
PlaybackStateCompat.PLAYBACK_POSITION_UNKNOWN, 1F
).build()
)
currentState = PlaybackStateCompat.STATE_PLAYING
refreshNotifications(currentState)
}
super.onPlay()
}
updateMetadata메서드는 아래 코드 스니펫을 참고 하자. 그 어떤 방법도 먹히지 않았다. 무조건 bitmap 데이터로 전달해야한다.
private fun updateMetadata(item: MusicViewModel) {
//android.util.Log.i("jinsu", "vo.getImgUri() = " + vo.getImgUri() + "]");
//album.setImageURI(vo.getImgUri());
//java.io.FileNotFoundException: No entry for content://media/external/audio/albumart/291 오류 해결법
val bm: Bitmap? = SongLib.getAlbumArt(applicationContext, item.albumArtUri)
Log.e(TAG, "######### item.Bitmap :${bm.toString()}")
var metadataBuilder = MediaMetadataCompat.Builder()
// metadataBuilder.putBitmap(
// MediaMetadataCompat.METADATA_KEY_ART_URI, BitmapFactory.decodeResource(
// resources, track.getBitmapResId()
// )
// )
//iconBitmap
// "content://media/external/audio/media/" + Long.toString(songId) + "/albumart"
Log.e(TAG, "item.albumArtUri :${item.albumArtUri.toString()}")
// metadataBuilder.putString(MediaMetadataCompat.METADATA_KEY_ALBUM_ART_URI, "content://media/external/audio/media/393/albumart")
metadataBuilder.putBitmap(MediaMetadataCompat.METADATA_KEY_ALBUM_ART, bm)
//metadataBuilder.putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_ICON_URI, item.albumArtUri.toString())
//metadataBuilder.putString(MediaMetadataCompat.METADATA_KEY_ALBUM_ART_URI, item.albumArtUri.toString())
metadataBuilder.putString(MediaMetadataCompat.METADATA_KEY_TITLE, item.title)
metadataBuilder.putString(MediaMetadataCompat.METADATA_KEY_ALBUM, "dsfdsfdsfdsfdsdfsdf")
metadataBuilder.putString(MediaMetadataCompat.METADATA_KEY_ARTIST, item.artist)
metadataBuilder.putLong(MediaMetadataCompat.METADATA_KEY_DURATION, item.duration)
mediaSession.setMetadata(metadataBuilder.build())
}
[getAlbumArt 메서드]
fun getAlbumArt(context: Context, uri: Uri?): Bitmap? {
val cr: ContentResolver = context.contentResolver
var inputStream: InputStream? = null
inputStream = try {
cr.openInputStream(uri!!)
} catch (e: FileNotFoundException) {
//e.printStackTrace();
return null
} catch (ee: java.lang.Exception) {
return null
}
return if (inputStream != null) {
BitmapFactory.decodeStream(inputStream)
} else null
}
[build.gradle(:app)]
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
android {
compileSdkVersion 29
defaultConfig {
applicationId "edu.kotlin.study"
minSdkVersion 23
targetSdkVersion 29
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
// For Java compilers:
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
dependencies {
implementation fileTree(dir: "libs", include: ["*.jar"])
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation 'androidx.core:core-ktx:1.3.2'
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
implementation "androidx.media:media:1.2.0"
implementation "androidx.recyclerview:recyclerview:1.1.0"
//implementation "com.android.support:support-media-compat:29.+"
// full exoplayer library
//implementation 'com.google.android.exoplayer:exoplayer:2.11.5'
implementation 'com.google.android.exoplayer:exoplayer-core:2.11.5'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
}
[REFERENCE]
stackoverflow.com/questions/35647821/android-notification-addaction-deprecated-in-api-23
[연관 글 더보기]
[android : kotlin] 코틀린 Notification MediaStyle 사용시 SeekBar 설정 및 해제 하는 방법
[android : kotlin] 코틀린 RecyclerView 클릭시 미디어 재생 하는 방법 : MediaController ,SimpleExoPlayer
[android : kotlin] 코틀린 Notification setShowActionsInCompactView 사용 예제 : MediaStyle
[프로그래밍/Kotlin] – [android : kotlin] 코틀린 Notification addAction 추가하는 방법 : Notification.Action.Builder
[프로그래밍/Kotlin] – [android : kotlin] 코틀린 Notification 사용 예제
[프로그래밍/Android] – 잠금화면에 알림내용(NotificationCompat) 노출하기 (to show content in lock screen