Merge branch 'develop' into tzugen-patch-1

This commit is contained in:
tzugen 2022-06-20 23:05:21 +02:00 committed by GitHub
commit 98ce519014
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 115 additions and 203 deletions

View File

@ -10,7 +10,7 @@ ktlintGradle = "10.2.0"
detekt = "1.19.0"
preferences = "1.1.1"
media = "1.3.1"
media3 = "1.0.0-alpha03"
media3 = "1.0.0-beta01"
androidSupport = "28.0.0"
androidLegacySupport = "1.0.0"
@ -101,4 +101,3 @@ kluentAndroid = { module = "org.amshove.kluent:kluent-android", versio
mockWebServer = { module = "com.squareup.okhttp3:mockwebserver", version.ref = "okhttp" }
apacheCodecs = { module = "commons-codec:commons-codec", version.ref = "apacheCodecs" }
robolectric = { module = "org.robolectric:robolectric", version.ref = "robolectric" }

View File

@ -55,7 +55,7 @@
errorLine2=" ~~~~~~~~">
<location
file="src/main/AndroidManifest.xml"
line="151"
line="155"
column="10"/>
</issue>
@ -66,7 +66,7 @@
errorLine2=" ~~~~~~~~">
<location
file="src/main/AndroidManifest.xml"
line="75"
line="79"
column="10"/>
</issue>
@ -77,18 +77,7 @@
errorLine2=" ~~~~~~~">
<location
file="src/main/AndroidManifest.xml"
line="65"
column="10"/>
</issue>
<issue
id="IntentFilterExportedReceiver"
message="As of Android 12, `android:exported` must be set; use `true` to make the activity \&#xA;available to other apps, and `false` otherwise. For launcher activities, this should be set to `true`."
errorLine1=" &lt;activity android:name=&quot;.activity.NavigationActivity&quot;"
errorLine2=" ~~~~~~~~">
<location
file="src/main/AndroidManifest.xml"
line="41"
line="68"
column="10"/>
</issue>
@ -180,6 +169,61 @@
column="1"/>
</issue>
<issue
id="UnusedResources"
message="The resource `R.drawable.media3_notification_pause` appears to be unused"
errorLine1="&lt;vector android:height=&quot;48dp&quot;"
errorLine2="^">
<location
file="src/main/res/drawable/media3_notification_pause.xml"
line="1"
column="1"/>
</issue>
<issue
id="UnusedResources"
message="The resource `R.drawable.media3_notification_play` appears to be unused"
errorLine1="&lt;vector android:height=&quot;48dp&quot;"
errorLine2="^">
<location
file="src/main/res/drawable/media3_notification_play.xml"
line="1"
column="1"/>
</issue>
<issue
id="UnusedResources"
message="The resource `R.drawable.media3_notification_seek_to_next` appears to be unused"
errorLine1="&lt;vector android:height=&quot;32dp&quot;"
errorLine2="^">
<location
file="src/main/res/drawable/media3_notification_seek_to_next.xml"
line="1"
column="1"/>
</issue>
<issue
id="UnusedResources"
message="The resource `R.drawable.media3_notification_seek_to_previous` appears to be unused"
errorLine1="&lt;vector android:height=&quot;32dp&quot;"
errorLine2="^">
<location
file="src/main/res/drawable/media3_notification_seek_to_previous.xml"
line="1"
column="1"/>
</issue>
<issue
id="UnusedResources"
message="The resource `R.drawable.media3_notification_small_icon` appears to be unused"
errorLine1="&lt;vector xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;"
errorLine2="^">
<location
file="src/main/res/drawable/media3_notification_small_icon.xml"
line="1"
column="1"/>
</issue>
<issue
id="IconDuplicates"
message="The following unrelated icon files have identical contents: list_pressed_holo_dark.9.png, list_pressed_holo_light.9.png">

View File

@ -67,6 +67,7 @@
<!-- Needs to be exported: https://android.googlesource.com/platform/developers/build/+/4de32d4/prebuilts/gradle/MediaBrowserService/README.md -->
<service android:name=".playback.PlaybackService"
android:label="@string/common.appname"
android:foregroundServiceType="mediaPlayback"
android:exported="true">
<intent-filter>

View File

@ -21,7 +21,6 @@ import androidx.media3.common.Player
import androidx.media3.session.LibraryResult
import androidx.media3.session.MediaLibraryService
import androidx.media3.session.MediaSession
import androidx.media3.session.SessionResult
import com.google.common.collect.ImmutableList
import com.google.common.util.concurrent.Futures
import com.google.common.util.concurrent.ListenableFuture
@ -81,15 +80,12 @@ private const val MEDIA_SEARCH_SONG_ITEM = "MEDIA_SEARCH_SONG_ITEM"
private const val DISPLAY_LIMIT = 100
private const val SEARCH_LIMIT = 10
private const val SEARCH_QUERY_PREFIX_COMPAT = "androidx://media3-session/playFromSearch"
private const val SEARCH_QUERY_PREFIX = "androidx://media3-session/setMediaUri"
/**
* MediaBrowserService implementation for e.g. Android Auto
*/
@Suppress("TooManyFunctions", "LargeClass", "UnusedPrivateMember")
class AutoMediaBrowserCallback(var player: Player) :
MediaLibraryService.MediaLibrarySession.MediaLibrarySessionCallback, KoinComponent {
MediaLibraryService.MediaLibrarySession.Callback, KoinComponent {
private val mediaPlayerController by inject<MediaPlayerController>()
private val activeServerProvider: ActiveServerProvider by inject()
@ -181,39 +177,23 @@ class AutoMediaBrowserCallback(var player: Player) :
return onLoadChildren(parentId)
}
private fun setMediaItemFromSearchQuery(query: String) {
// Only accept query with pattern "play [Title]" or "[Title]"
// Where [Title]: must be exactly matched
// If no media with exact name found, play a random media instead
val mediaTitle =
if (query.startsWith("play ", ignoreCase = true)) {
query.drop(5)
} else {
query
}
playFromMediaId(mediaTitle)
}
override fun onSetMediaUri(
session: MediaSession,
/*
* For some reason the LocalConfiguration of MediaItem are stripped somewhere in ExoPlayer,
* and thereby customarily it is required to rebuild it..
* See also: https://stackoverflow.com/questions/70096715/adding-mediaitem-when-using-the-media3-library-caused-an-error
*/
override fun onAddMediaItems(
mediaSession: MediaSession,
controller: MediaSession.ControllerInfo,
uri: Uri,
extras: Bundle
): Int {
mediaItems: MutableList<MediaItem>
): ListenableFuture<MutableList<MediaItem>> {
if (uri.toString().startsWith(SEARCH_QUERY_PREFIX) ||
uri.toString().startsWith(SEARCH_QUERY_PREFIX_COMPAT)
) {
val searchQuery =
uri.getQueryParameter("query")
?: return SessionResult.RESULT_ERROR_NOT_SUPPORTED
setMediaItemFromSearchQuery(searchQuery)
return SessionResult.RESULT_SUCCESS
} else {
return SessionResult.RESULT_ERROR_NOT_SUPPORTED
val updatedMediaItems = mediaItems.map { mediaItem ->
mediaItem.buildUpon()
.setUri(mediaItem.requestMetadata.mediaUri)
.build()
}
return Futures.immediateFuture(updatedMediaItems.toMutableList())
}
@Suppress("ReturnCount", "ComplexMethod")

View File

@ -50,7 +50,7 @@ class LegacyPlaylistManager : KoinComponent {
for (i in 0 until n) {
val item = controller.getMediaItemAt(i)
val file = mediaItemCache[item.mediaMetadata.mediaUri.toString()]
val file = mediaItemCache[item.requestMetadata.toString()]
if (file != null)
_playlist.add(file)
}
@ -59,11 +59,11 @@ class LegacyPlaylistManager : KoinComponent {
}
fun addToCache(item: MediaItem, file: DownloadFile) {
mediaItemCache.put(item.mediaMetadata.mediaUri.toString(), file)
mediaItemCache.put(item.requestMetadata.toString(), file)
}
fun updateCurrentPlaying(item: MediaItem?) {
currentPlaying = mediaItemCache[item?.mediaMetadata?.mediaUri.toString()]
currentPlaying = mediaItemCache[item?.requestMetadata.toString()]
}
@Synchronized

View File

@ -7,144 +7,38 @@
package org.moire.ultrasonic.playback
import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager
import android.content.Context
import android.graphics.BitmapFactory
import android.os.Build
import android.os.Bundle
import androidx.core.app.NotificationCompat
import androidx.core.graphics.drawable.IconCompat
import androidx.media3.common.Player
import androidx.media3.common.util.Assertions
import androidx.media3.common.util.UnstableApi
import androidx.media3.common.util.Util
import androidx.media3.session.MediaController
import androidx.media3.session.CommandButton
import androidx.media3.session.DefaultMediaNotificationProvider
import androidx.media3.session.MediaNotification
import androidx.media3.session.MediaNotification.ActionFactory
import org.moire.ultrasonic.R
import androidx.media3.session.MediaSession
/*
* This is a copy of DefaultMediaNotificationProvider.java with some small changes
* I have opened a bug https://github.com/androidx/media/issues/65 to make it easier to customize
* the icons and actions without creating our own copy of this class..
*/
@UnstableApi
/* package */
internal class MediaNotificationProvider(context: Context) :
MediaNotification.Provider {
private val context: Context = context.applicationContext
private val notificationManager: NotificationManager = Assertions.checkStateNotNull(
context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
)
class MediaNotificationProvider(context: Context) : DefaultMediaNotificationProvider(context) {
@Suppress("LongMethod")
override fun createNotification(
mediaController: MediaController,
actionFactory: ActionFactory,
onNotificationChangedCallback: MediaNotification.Provider.Callback
): MediaNotification {
ensureNotificationChannel()
val builder: NotificationCompat.Builder = NotificationCompat.Builder(
context,
NOTIFICATION_CHANNEL_ID
)
// Skip to previous action.
builder.addAction(
actionFactory.createMediaAction(
IconCompat.createWithResource(
context,
R.drawable.media3_notification_seek_to_previous
),
context.getString(R.string.media3_controls_seek_to_previous_description),
ActionFactory.COMMAND_SKIP_TO_PREVIOUS
)
)
if (mediaController.playbackState == Player.STATE_ENDED ||
!mediaController.playWhenReady
) {
// Play action.
builder.addAction(
actionFactory.createMediaAction(
IconCompat.createWithResource(context, R.drawable.media3_notification_play),
context.getString(R.string.media3_controls_play_description),
ActionFactory.COMMAND_PLAY
)
)
} else {
// Pause action.
builder.addAction(
actionFactory.createMediaAction(
IconCompat.createWithResource(context, R.drawable.media3_notification_pause),
context.getString(R.string.media3_controls_pause_description),
ActionFactory.COMMAND_PAUSE
)
)
}
// Skip to next action.
builder.addAction(
actionFactory.createMediaAction(
IconCompat.createWithResource(context, R.drawable.media3_notification_seek_to_next),
context.getString(R.string.media3_controls_seek_to_next_description),
ActionFactory.COMMAND_SKIP_TO_NEXT
)
)
// Set metadata info in the notification.
val metadata = mediaController.mediaMetadata
builder.setContentTitle(metadata.title).setContentText(metadata.artist)
if (metadata.artworkData != null) {
val artworkBitmap =
BitmapFactory.decodeByteArray(metadata.artworkData, 0, metadata.artworkData!!.size)
builder.setLargeIcon(artworkBitmap)
}
val mediaStyle = androidx.media.app.NotificationCompat.MediaStyle()
.setShowActionsInCompactView(0, 1, 2)
val notification: Notification = builder
.setContentIntent(mediaController.sessionActivity)
.setOnlyAlertOnce(true)
.setSmallIcon(getSmallIconResId())
.setStyle(mediaStyle)
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
.setOngoing(false)
.build()
return MediaNotification(
NOTIFICATION_ID,
notification
)
override fun addNotificationActions(
mediaSession: MediaSession,
mediaButtons: MutableList<CommandButton>,
builder: NotificationCompat.Builder,
actionFactory: MediaNotification.ActionFactory
): IntArray {
return super.addNotificationActions(mediaSession, mediaButtons, builder, actionFactory)
}
override fun handleCustomAction(
mediaController: MediaController,
action: String,
extras: Bundle
) {
// We don't handle custom commands.
override fun getMediaButtons(
playerCommands: Player.Commands,
customLayout: MutableList<CommandButton>,
playWhenReady: Boolean
): MutableList<CommandButton> {
val commands = super.getMediaButtons(playerCommands, customLayout, playWhenReady)
commands.forEachIndexed { index, command ->
command.extras.putInt(COMMAND_KEY_COMPACT_VIEW_INDEX, index)
}
private fun ensureNotificationChannel() {
if (Util.SDK_INT < Build.VERSION_CODES.O ||
notificationManager.getNotificationChannel(NOTIFICATION_CHANNEL_ID) != null
) {
return
}
val channel = NotificationChannel(
NOTIFICATION_CHANNEL_ID,
NOTIFICATION_CHANNEL_NAME,
NotificationManager.IMPORTANCE_LOW
)
channel.setShowBadge(false)
notificationManager.createNotificationChannel(channel)
}
companion object {
private const val NOTIFICATION_CHANNEL_ID = "org.moire.ultrasonic"
private const val NOTIFICATION_CHANNEL_NAME = "Ultrasonic background service"
private const val NOTIFICATION_ID = 3032
private fun getSmallIconResId(): Int {
return R.drawable.ic_stat_ultrasonic
}
return commands
}
}

View File

@ -11,9 +11,7 @@ import android.content.Intent
import android.os.Build
import androidx.media3.common.AudioAttributes
import androidx.media3.common.C
import androidx.media3.common.C.CONTENT_TYPE_MUSIC
import androidx.media3.common.C.USAGE_MEDIA
import androidx.media3.common.MediaItem
import androidx.media3.datasource.DataSource
import androidx.media3.exoplayer.DefaultRenderersFactory
import androidx.media3.exoplayer.ExoPlayer
@ -38,29 +36,12 @@ class PlaybackService : MediaLibraryService(), KoinComponent {
private lateinit var mediaLibrarySession: MediaLibrarySession
private lateinit var apiDataSource: APIDataSource.Factory
private lateinit var librarySessionCallback: MediaLibrarySession.MediaLibrarySessionCallback
private lateinit var librarySessionCallback: MediaLibrarySession.Callback
private var rxBusSubscription = CompositeDisposable()
private var isStarted = false
/*
* For some reason the LocalConfiguration of MediaItem are stripped somewhere in ExoPlayer,
* and thereby customarily it is required to rebuild it..
*/
private class CustomMediaItemFiller : MediaSession.MediaItemFiller {
override fun fillInLocalConfiguration(
session: MediaSession,
controller: MediaSession.ControllerInfo,
mediaItem: MediaItem
): MediaItem {
// Again, set the Uri, so that it will get a LocalConfiguration
return mediaItem.buildUpon()
.setUri(mediaItem.mediaMetadata.mediaUri)
.build()
}
}
override fun onCreate() {
Timber.i("onCreate called")
super.onCreate()
@ -134,7 +115,6 @@ class PlaybackService : MediaLibraryService(), KoinComponent {
// This will need to use the AutoCalls
mediaLibrarySession = MediaLibrarySession.Builder(this, player, librarySessionCallback)
.setMediaItemFiller(CustomMediaItemFiller())
.setSessionActivity(getPendingIntentForContent())
.build()
@ -171,7 +151,7 @@ class PlaybackService : MediaLibraryService(), KoinComponent {
private fun getAudioAttributes(): AudioAttributes {
return AudioAttributes.Builder()
.setUsage(USAGE_MEDIA)
.setContentType(CONTENT_TYPE_MUSIC)
.setContentType(C.AUDIO_CONTENT_TYPE_MUSIC)
.build()
}
}

View File

@ -659,16 +659,21 @@ fun Track.toMediaItem(): MediaItem {
val bitrate = Settings.maxBitRate
val uri = "$id|$bitrate|$filePath"
val rmd = MediaItem.RequestMetadata.Builder()
.setMediaUri(uri.toUri())
.build()
val metadata = MediaMetadata.Builder()
metadata.setTitle(title)
.setArtist(artist)
.setAlbumTitle(album)
.setMediaUri(uri.toUri())
.setAlbumArtist(artist)
.build()
val mediaItem = MediaItem.Builder()
.setUri(uri)
.setMediaId(id)
.setRequestMetadata(rmd)
.setMediaMetadata(metadata.build())
return mediaItem.build()

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FFF"
android:pathData="m12,3.8438c-4.9703,0 -9,4.0292 -9,9l0,5.0625c0,1.2426 1.0074,2.25 2.25,2.25 1.2426,0 2.25,-1.0074 2.25,-2.25l0,-3.375c0,-1.2426 -1.0074,-2.25 -2.25,-2.25 -0.4067,0 -0.783,0.1164 -1.1121,0.3049C4.2752,8.3573 7.7379,4.9688 12,4.9688 16.2621,4.9688 19.7242,8.3573 19.8621,12.5861 19.5336,12.3977 19.1567,12.2813 18.75,12.2813c-1.2426,0 -2.25,1.0074 -2.25,2.25l0,3.375c0,1.2426 1.0074,2.25 2.25,2.25 1.2426,0 2.25,-1.0074 2.25,-2.25L21,12.8438C21,7.8729 16.9708,3.8438 12,3.8438ZM5.25,13.4063c0.621,0 1.125,0.504 1.125,1.125l0,3.375c0,0.621 -0.504,1.125 -1.125,1.125 -0.621,0 -1.125,-0.504 -1.125,-1.125l0,-3.375c0,-0.621 0.504,-1.125 1.125,-1.125zM19.875,17.9063c0,0.621 -0.504,1.125 -1.125,1.125 -0.621,0 -1.125,-0.504 -1.125,-1.125l0,-3.375c0,-0.621 0.504,-1.125 1.125,-1.125 0.621,0 1.125,0.504 1.125,1.125z"/>
</vector>