Merge branch 'develop' into fix_#759
This commit is contained in:
commit
737563bf6b
|
@ -19,17 +19,45 @@ By default Pull Request should be opened against **develop** branch, PR against
|
||||||
|
|
||||||
1. **License Acceptance:** All contributions must be licensed as [GNU GPLv3](LICENSE) to be accepted.
|
1. **License Acceptance:** All contributions must be licensed as [GNU GPLv3](LICENSE) to be accepted.
|
||||||
Use `git commit --signoff` to acknowledge this.
|
Use `git commit --signoff` to acknowledge this.
|
||||||
2. **App is migrating to [Kotlin](https://kotlinlang.org/) programming language:** new Pull Requests
|
2. **No Breakage:** New features or changes to existing ones must not degrade the user experience.
|
||||||
should be written in this programming language.
|
3. **Coding standards:** best-practices should be followed, comment generously, and avoid "clever" algorithms.
|
||||||
3. **No Breakage:** New features or changes to existing ones must not degrade the user experience.
|
|
||||||
4. **Coding standards:** best-practices should be followed, comment generously, and avoid "clever" algorithms.
|
|
||||||
Refactoring existing messes is great, but watch out for breakage.
|
Refactoring existing messes is great, but watch out for breakage.
|
||||||
5. **No large PR:** Try to limit the scope of PR only to the related issue, so it will be easier to review
|
4. **No large PR:** Try to limit the scope of PR only to the related issue, so it will be easier to review
|
||||||
and test.
|
and test.
|
||||||
|
|
||||||
### Pull Request Process
|
### Pull Request Process
|
||||||
|
On each Pull Request Github runs a number of checks to make sure there are no problems.
|
||||||
|
|
||||||
|
#### Signed commits
|
||||||
|
Commits must be signed. [See here how to set it up](https://docs.github.com/en/authentication/managing-commit-signature-verification/signing-commits)
|
||||||
|
|
||||||
|
#### KtLint
|
||||||
|
This programm checks if the source code is formatted correctly.
|
||||||
|
You can run it yourself locally with
|
||||||
|
|
||||||
|
`./gradlew -Pqc ktlintFormat`
|
||||||
|
|
||||||
|
Running this command will fix common problems and will notify you of problems it couldn't fix automatically.
|
||||||
|
|
||||||
|
#### Detekt
|
||||||
|
|
||||||
|
Detekt is a static analyser. It helps to find potential bugs in our code.
|
||||||
|
|
||||||
|
You can run it yourself locally with
|
||||||
|
|
||||||
|
`./gradlew -Pqc detekt`
|
||||||
|
|
||||||
|
There is a "baseline" file, in which errors which have been in the code base before are noted.
|
||||||
|
Sometimes it is necessary to regenerate this file by running:
|
||||||
|
|
||||||
|
`./gradlew -Pqc detektBaseline`
|
||||||
|
|
||||||
|
#### Lint
|
||||||
|
Lint looks for general problems in the code or unused resources etc.
|
||||||
|
You can run it with
|
||||||
|
|
||||||
|
`./gradlew -Pqc lintRelease`
|
||||||
|
|
||||||
|
If there is a need to regenerate the baseline, remove `ultrasonic/lint-baseline.xml` and rerun the command.
|
||||||
|
|
||||||
|
|
||||||
1. Ensure [all commits are signed-off](https://docs.github.com/en/free-pro-team@latest/github/authenticating-to-github/about-commit-signature-verification).
|
|
||||||
2. Check tests for the new code are added.
|
|
||||||
3. Check code style is passing.
|
|
||||||
4. Check code static analysis is passing.
|
|
||||||
|
|
|
@ -10,7 +10,7 @@ ktlintGradle = "10.2.0"
|
||||||
detekt = "1.19.0"
|
detekt = "1.19.0"
|
||||||
preferences = "1.1.1"
|
preferences = "1.1.1"
|
||||||
media = "1.3.1"
|
media = "1.3.1"
|
||||||
media3 = "1.0.0-alpha03"
|
media3 = "1.0.0-beta01"
|
||||||
|
|
||||||
androidSupport = "28.0.0"
|
androidSupport = "28.0.0"
|
||||||
androidLegacySupport = "1.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" }
|
mockWebServer = { module = "com.squareup.okhttp3:mockwebserver", version.ref = "okhttp" }
|
||||||
apacheCodecs = { module = "commons-codec:commons-codec", version.ref = "apacheCodecs" }
|
apacheCodecs = { module = "commons-codec:commons-codec", version.ref = "apacheCodecs" }
|
||||||
robolectric = { module = "org.robolectric:robolectric", version.ref = "robolectric" }
|
robolectric = { module = "org.robolectric:robolectric", version.ref = "robolectric" }
|
||||||
|
|
||||||
|
|
|
@ -55,7 +55,7 @@
|
||||||
errorLine2=" ~~~~~~~~">
|
errorLine2=" ~~~~~~~~">
|
||||||
<location
|
<location
|
||||||
file="src/main/AndroidManifest.xml"
|
file="src/main/AndroidManifest.xml"
|
||||||
line="151"
|
line="155"
|
||||||
column="10"/>
|
column="10"/>
|
||||||
</issue>
|
</issue>
|
||||||
|
|
||||||
|
@ -66,7 +66,7 @@
|
||||||
errorLine2=" ~~~~~~~~">
|
errorLine2=" ~~~~~~~~">
|
||||||
<location
|
<location
|
||||||
file="src/main/AndroidManifest.xml"
|
file="src/main/AndroidManifest.xml"
|
||||||
line="75"
|
line="79"
|
||||||
column="10"/>
|
column="10"/>
|
||||||
</issue>
|
</issue>
|
||||||
|
|
||||||
|
@ -77,18 +77,7 @@
|
||||||
errorLine2=" ~~~~~~~">
|
errorLine2=" ~~~~~~~">
|
||||||
<location
|
<location
|
||||||
file="src/main/AndroidManifest.xml"
|
file="src/main/AndroidManifest.xml"
|
||||||
line="65"
|
line="68"
|
||||||
column="10"/>
|
|
||||||
</issue>
|
|
||||||
|
|
||||||
<issue
|
|
||||||
id="IntentFilterExportedReceiver"
|
|
||||||
message="As of Android 12, `android:exported` must be set; use `true` to make the activity \
available to other apps, and `false` otherwise. For launcher activities, this should be set to `true`."
|
|
||||||
errorLine1=" <activity android:name=".activity.NavigationActivity""
|
|
||||||
errorLine2=" ~~~~~~~~">
|
|
||||||
<location
|
|
||||||
file="src/main/AndroidManifest.xml"
|
|
||||||
line="41"
|
|
||||||
column="10"/>
|
column="10"/>
|
||||||
</issue>
|
</issue>
|
||||||
|
|
||||||
|
@ -180,6 +169,61 @@
|
||||||
column="1"/>
|
column="1"/>
|
||||||
</issue>
|
</issue>
|
||||||
|
|
||||||
|
<issue
|
||||||
|
id="UnusedResources"
|
||||||
|
message="The resource `R.drawable.media3_notification_pause` appears to be unused"
|
||||||
|
errorLine1="<vector android:height="48dp""
|
||||||
|
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="<vector android:height="48dp""
|
||||||
|
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="<vector android:height="32dp""
|
||||||
|
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="<vector android:height="32dp""
|
||||||
|
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="<vector xmlns:android="http://schemas.android.com/apk/res/android""
|
||||||
|
errorLine2="^">
|
||||||
|
<location
|
||||||
|
file="src/main/res/drawable/media3_notification_small_icon.xml"
|
||||||
|
line="1"
|
||||||
|
column="1"/>
|
||||||
|
</issue>
|
||||||
|
|
||||||
<issue
|
<issue
|
||||||
id="IconDuplicates"
|
id="IconDuplicates"
|
||||||
message="The following unrelated icon files have identical contents: list_pressed_holo_dark.9.png, list_pressed_holo_light.9.png">
|
message="The following unrelated icon files have identical contents: list_pressed_holo_dark.9.png, list_pressed_holo_light.9.png">
|
||||||
|
|
|
@ -67,6 +67,7 @@
|
||||||
<!-- Needs to be exported: https://android.googlesource.com/platform/developers/build/+/4de32d4/prebuilts/gradle/MediaBrowserService/README.md -->
|
<!-- Needs to be exported: https://android.googlesource.com/platform/developers/build/+/4de32d4/prebuilts/gradle/MediaBrowserService/README.md -->
|
||||||
<service android:name=".playback.PlaybackService"
|
<service android:name=".playback.PlaybackService"
|
||||||
android:label="@string/common.appname"
|
android:label="@string/common.appname"
|
||||||
|
android:foregroundServiceType="mediaPlayback"
|
||||||
android:exported="true">
|
android:exported="true">
|
||||||
|
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
|
|
|
@ -21,7 +21,6 @@ import androidx.media3.common.Player
|
||||||
import androidx.media3.session.LibraryResult
|
import androidx.media3.session.LibraryResult
|
||||||
import androidx.media3.session.MediaLibraryService
|
import androidx.media3.session.MediaLibraryService
|
||||||
import androidx.media3.session.MediaSession
|
import androidx.media3.session.MediaSession
|
||||||
import androidx.media3.session.SessionResult
|
|
||||||
import com.google.common.collect.ImmutableList
|
import com.google.common.collect.ImmutableList
|
||||||
import com.google.common.util.concurrent.Futures
|
import com.google.common.util.concurrent.Futures
|
||||||
import com.google.common.util.concurrent.ListenableFuture
|
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 DISPLAY_LIMIT = 100
|
||||||
private const val SEARCH_LIMIT = 10
|
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
|
* MediaBrowserService implementation for e.g. Android Auto
|
||||||
*/
|
*/
|
||||||
@Suppress("TooManyFunctions", "LargeClass", "UnusedPrivateMember")
|
@Suppress("TooManyFunctions", "LargeClass", "UnusedPrivateMember")
|
||||||
class AutoMediaBrowserCallback(var player: Player) :
|
class AutoMediaBrowserCallback(var player: Player) :
|
||||||
MediaLibraryService.MediaLibrarySession.MediaLibrarySessionCallback, KoinComponent {
|
MediaLibraryService.MediaLibrarySession.Callback, KoinComponent {
|
||||||
|
|
||||||
private val mediaPlayerController by inject<MediaPlayerController>()
|
private val mediaPlayerController by inject<MediaPlayerController>()
|
||||||
private val activeServerProvider: ActiveServerProvider by inject()
|
private val activeServerProvider: ActiveServerProvider by inject()
|
||||||
|
@ -181,39 +177,23 @@ class AutoMediaBrowserCallback(var player: Player) :
|
||||||
return onLoadChildren(parentId)
|
return onLoadChildren(parentId)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setMediaItemFromSearchQuery(query: String) {
|
/*
|
||||||
// Only accept query with pattern "play [Title]" or "[Title]"
|
* For some reason the LocalConfiguration of MediaItem are stripped somewhere in ExoPlayer,
|
||||||
// Where [Title]: must be exactly matched
|
* and thereby customarily it is required to rebuild it..
|
||||||
// If no media with exact name found, play a random media instead
|
* See also: https://stackoverflow.com/questions/70096715/adding-mediaitem-when-using-the-media3-library-caused-an-error
|
||||||
val mediaTitle =
|
*/
|
||||||
if (query.startsWith("play ", ignoreCase = true)) {
|
override fun onAddMediaItems(
|
||||||
query.drop(5)
|
mediaSession: MediaSession,
|
||||||
} else {
|
|
||||||
query
|
|
||||||
}
|
|
||||||
|
|
||||||
playFromMediaId(mediaTitle)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onSetMediaUri(
|
|
||||||
session: MediaSession,
|
|
||||||
controller: MediaSession.ControllerInfo,
|
controller: MediaSession.ControllerInfo,
|
||||||
uri: Uri,
|
mediaItems: MutableList<MediaItem>
|
||||||
extras: Bundle
|
): ListenableFuture<MutableList<MediaItem>> {
|
||||||
): Int {
|
|
||||||
|
|
||||||
if (uri.toString().startsWith(SEARCH_QUERY_PREFIX) ||
|
val updatedMediaItems = mediaItems.map { mediaItem ->
|
||||||
uri.toString().startsWith(SEARCH_QUERY_PREFIX_COMPAT)
|
mediaItem.buildUpon()
|
||||||
) {
|
.setUri(mediaItem.requestMetadata.mediaUri)
|
||||||
val searchQuery =
|
.build()
|
||||||
uri.getQueryParameter("query")
|
|
||||||
?: return SessionResult.RESULT_ERROR_NOT_SUPPORTED
|
|
||||||
setMediaItemFromSearchQuery(searchQuery)
|
|
||||||
|
|
||||||
return SessionResult.RESULT_SUCCESS
|
|
||||||
} else {
|
|
||||||
return SessionResult.RESULT_ERROR_NOT_SUPPORTED
|
|
||||||
}
|
}
|
||||||
|
return Futures.immediateFuture(updatedMediaItems.toMutableList())
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("ReturnCount", "ComplexMethod")
|
@Suppress("ReturnCount", "ComplexMethod")
|
||||||
|
|
|
@ -50,7 +50,7 @@ class LegacyPlaylistManager : KoinComponent {
|
||||||
|
|
||||||
for (i in 0 until n) {
|
for (i in 0 until n) {
|
||||||
val item = controller.getMediaItemAt(i)
|
val item = controller.getMediaItemAt(i)
|
||||||
val file = mediaItemCache[item.mediaMetadata.mediaUri.toString()]
|
val file = mediaItemCache[item.requestMetadata.toString()]
|
||||||
if (file != null)
|
if (file != null)
|
||||||
_playlist.add(file)
|
_playlist.add(file)
|
||||||
}
|
}
|
||||||
|
@ -59,11 +59,11 @@ class LegacyPlaylistManager : KoinComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun addToCache(item: MediaItem, file: DownloadFile) {
|
fun addToCache(item: MediaItem, file: DownloadFile) {
|
||||||
mediaItemCache.put(item.mediaMetadata.mediaUri.toString(), file)
|
mediaItemCache.put(item.requestMetadata.toString(), file)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateCurrentPlaying(item: MediaItem?) {
|
fun updateCurrentPlaying(item: MediaItem?) {
|
||||||
currentPlaying = mediaItemCache[item?.mediaMetadata?.mediaUri.toString()]
|
currentPlaying = mediaItemCache[item?.requestMetadata.toString()]
|
||||||
}
|
}
|
||||||
|
|
||||||
@Synchronized
|
@Synchronized
|
||||||
|
|
|
@ -7,144 +7,38 @@
|
||||||
|
|
||||||
package org.moire.ultrasonic.playback
|
package org.moire.ultrasonic.playback
|
||||||
|
|
||||||
import android.app.Notification
|
|
||||||
import android.app.NotificationChannel
|
|
||||||
import android.app.NotificationManager
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.graphics.BitmapFactory
|
|
||||||
import android.os.Build
|
|
||||||
import android.os.Bundle
|
|
||||||
import androidx.core.app.NotificationCompat
|
import androidx.core.app.NotificationCompat
|
||||||
import androidx.core.graphics.drawable.IconCompat
|
|
||||||
import androidx.media3.common.Player
|
import androidx.media3.common.Player
|
||||||
import androidx.media3.common.util.Assertions
|
|
||||||
import androidx.media3.common.util.UnstableApi
|
import androidx.media3.common.util.UnstableApi
|
||||||
import androidx.media3.common.util.Util
|
import androidx.media3.session.CommandButton
|
||||||
import androidx.media3.session.MediaController
|
import androidx.media3.session.DefaultMediaNotificationProvider
|
||||||
import androidx.media3.session.MediaNotification
|
import androidx.media3.session.MediaNotification
|
||||||
import androidx.media3.session.MediaNotification.ActionFactory
|
import androidx.media3.session.MediaSession
|
||||||
import org.moire.ultrasonic.R
|
|
||||||
|
|
||||||
/*
|
|
||||||
* 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
|
@UnstableApi
|
||||||
/* package */
|
class MediaNotificationProvider(context: Context) : DefaultMediaNotificationProvider(context) {
|
||||||
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
|
|
||||||
)
|
|
||||||
|
|
||||||
@Suppress("LongMethod")
|
override fun addNotificationActions(
|
||||||
override fun createNotification(
|
mediaSession: MediaSession,
|
||||||
mediaController: MediaController,
|
mediaButtons: MutableList<CommandButton>,
|
||||||
actionFactory: ActionFactory,
|
builder: NotificationCompat.Builder,
|
||||||
onNotificationChangedCallback: MediaNotification.Provider.Callback
|
actionFactory: MediaNotification.ActionFactory
|
||||||
): MediaNotification {
|
): IntArray {
|
||||||
ensureNotificationChannel()
|
return super.addNotificationActions(mediaSession, mediaButtons, builder, actionFactory)
|
||||||
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 handleCustomAction(
|
override fun getMediaButtons(
|
||||||
mediaController: MediaController,
|
playerCommands: Player.Commands,
|
||||||
action: String,
|
customLayout: MutableList<CommandButton>,
|
||||||
extras: Bundle
|
playWhenReady: Boolean
|
||||||
) {
|
): MutableList<CommandButton> {
|
||||||
// We don't handle custom commands.
|
val commands = super.getMediaButtons(playerCommands, customLayout, playWhenReady)
|
||||||
|
|
||||||
|
commands.forEachIndexed { index, command ->
|
||||||
|
command.extras.putInt(COMMAND_KEY_COMPACT_VIEW_INDEX, index)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun ensureNotificationChannel() {
|
return commands
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,9 +11,7 @@ import android.content.Intent
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import androidx.media3.common.AudioAttributes
|
import androidx.media3.common.AudioAttributes
|
||||||
import androidx.media3.common.C
|
import androidx.media3.common.C
|
||||||
import androidx.media3.common.C.CONTENT_TYPE_MUSIC
|
|
||||||
import androidx.media3.common.C.USAGE_MEDIA
|
import androidx.media3.common.C.USAGE_MEDIA
|
||||||
import androidx.media3.common.MediaItem
|
|
||||||
import androidx.media3.datasource.DataSource
|
import androidx.media3.datasource.DataSource
|
||||||
import androidx.media3.exoplayer.DefaultRenderersFactory
|
import androidx.media3.exoplayer.DefaultRenderersFactory
|
||||||
import androidx.media3.exoplayer.ExoPlayer
|
import androidx.media3.exoplayer.ExoPlayer
|
||||||
|
@ -38,29 +36,12 @@ class PlaybackService : MediaLibraryService(), KoinComponent {
|
||||||
private lateinit var mediaLibrarySession: MediaLibrarySession
|
private lateinit var mediaLibrarySession: MediaLibrarySession
|
||||||
private lateinit var apiDataSource: APIDataSource.Factory
|
private lateinit var apiDataSource: APIDataSource.Factory
|
||||||
|
|
||||||
private lateinit var librarySessionCallback: MediaLibrarySession.MediaLibrarySessionCallback
|
private lateinit var librarySessionCallback: MediaLibrarySession.Callback
|
||||||
|
|
||||||
private var rxBusSubscription = CompositeDisposable()
|
private var rxBusSubscription = CompositeDisposable()
|
||||||
|
|
||||||
private var isStarted = false
|
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() {
|
override fun onCreate() {
|
||||||
Timber.i("onCreate called")
|
Timber.i("onCreate called")
|
||||||
super.onCreate()
|
super.onCreate()
|
||||||
|
@ -134,7 +115,6 @@ class PlaybackService : MediaLibraryService(), KoinComponent {
|
||||||
|
|
||||||
// This will need to use the AutoCalls
|
// This will need to use the AutoCalls
|
||||||
mediaLibrarySession = MediaLibrarySession.Builder(this, player, librarySessionCallback)
|
mediaLibrarySession = MediaLibrarySession.Builder(this, player, librarySessionCallback)
|
||||||
.setMediaItemFiller(CustomMediaItemFiller())
|
|
||||||
.setSessionActivity(getPendingIntentForContent())
|
.setSessionActivity(getPendingIntentForContent())
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
|
@ -171,7 +151,7 @@ class PlaybackService : MediaLibraryService(), KoinComponent {
|
||||||
private fun getAudioAttributes(): AudioAttributes {
|
private fun getAudioAttributes(): AudioAttributes {
|
||||||
return AudioAttributes.Builder()
|
return AudioAttributes.Builder()
|
||||||
.setUsage(USAGE_MEDIA)
|
.setUsage(USAGE_MEDIA)
|
||||||
.setContentType(CONTENT_TYPE_MUSIC)
|
.setContentType(C.AUDIO_CONTENT_TYPE_MUSIC)
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -659,16 +659,21 @@ fun Track.toMediaItem(): MediaItem {
|
||||||
val bitrate = Settings.maxBitRate
|
val bitrate = Settings.maxBitRate
|
||||||
val uri = "$id|$bitrate|$filePath"
|
val uri = "$id|$bitrate|$filePath"
|
||||||
|
|
||||||
|
val rmd = MediaItem.RequestMetadata.Builder()
|
||||||
|
.setMediaUri(uri.toUri())
|
||||||
|
.build()
|
||||||
|
|
||||||
val metadata = MediaMetadata.Builder()
|
val metadata = MediaMetadata.Builder()
|
||||||
metadata.setTitle(title)
|
metadata.setTitle(title)
|
||||||
.setArtist(artist)
|
.setArtist(artist)
|
||||||
.setAlbumTitle(album)
|
.setAlbumTitle(album)
|
||||||
.setMediaUri(uri.toUri())
|
|
||||||
.setAlbumArtist(artist)
|
.setAlbumArtist(artist)
|
||||||
|
.build()
|
||||||
|
|
||||||
val mediaItem = MediaItem.Builder()
|
val mediaItem = MediaItem.Builder()
|
||||||
.setUri(uri)
|
.setUri(uri)
|
||||||
.setMediaId(id)
|
.setMediaId(id)
|
||||||
|
.setRequestMetadata(rmd)
|
||||||
.setMediaMetadata(metadata.build())
|
.setMediaMetadata(metadata.build())
|
||||||
|
|
||||||
return mediaItem.build()
|
return mediaItem.build()
|
||||||
|
|
|
@ -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>
|
Loading…
Reference in New Issue