diff --git a/.circleci/config.yml b/.circleci/config.yml index efe2d719..6eee27c0 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -41,7 +41,6 @@ jobs: name: unit-tests command: | ./gradlew ciTest testDebugUnitTest - ./gradlew jacocoFullReport - run: name: lint command: ./gradlew :ultrasonic:lintRelease @@ -61,8 +60,6 @@ jobs: - store_artifacts: path: subsonic-api/build/reports destination: reports - - store_artifacts: - path: build/reports/jacoco/jacocoFullReport/ push_translations: docker: - image: cimg/python:3.6 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d52377f1..963f73f1 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -18,18 +18,46 @@ By default Pull Request should be opened against **develop** branch, PR against ### Here are a few guidelines you should follow before submitting: 1. **License Acceptance:** All contributions must be licensed as [GNU GPLv3](LICENSE) to be accepted. -Use `git commit --signoff` to acknowledge this. -2. **App is migrating to [Kotlin](https://kotlinlang.org/) programming language:** new Pull Requests -should be written in this programming language. -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. +Use `git commit --signoff` to acknowledge this. +2. **No Breakage:** New features or changes to existing ones must not degrade the user experience. +3. **Coding standards:** best-practices should be followed, comment generously, and avoid "clever" algorithms. 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. ### 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. diff --git a/build.gradle b/build.gradle index 925569fa..c44d7f84 100644 --- a/build.gradle +++ b/build.gradle @@ -17,7 +17,6 @@ buildscript { classpath libs.kotlin classpath libs.ktlintGradle classpath libs.detekt - classpath libs.jacoco } } @@ -44,8 +43,6 @@ allprojects { } } -apply from: 'gradle_scripts/jacoco.gradle' - wrapper { gradleVersion(libs.versions.gradle.get()) distributionType("all") diff --git a/core/domain/build.gradle b/core/domain/build.gradle index 0b0ab96c..3ed1e681 100644 --- a/core/domain/build.gradle +++ b/core/domain/build.gradle @@ -1,12 +1,6 @@ apply from: bootstrap.androidModule apply plugin: 'kotlin-kapt' -ext { - jacocoExclude = [ - '**/domain/**' - ] -} - dependencies { implementation libs.roomRuntime implementation libs.roomKtx diff --git a/core/subsonic-api/build.gradle b/core/subsonic-api/build.gradle index 9ad09193..6db3ffcb 100644 --- a/core/subsonic-api/build.gradle +++ b/core/subsonic-api/build.gradle @@ -20,11 +20,3 @@ dependencies { testImplementation libs.mockWebServer testImplementation libs.apacheCodecs } - -ext { - // Excluding data classes - jacocoExclude = [ - '**/models/**', - '**/di/**' - ] -} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index adf99594..ef64d935 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -8,10 +8,9 @@ androidxcore = "1.6.0" ktlint = "0.43.2" ktlintGradle = "10.2.0" detekt = "1.19.0" -jacoco = "0.8.7" 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" @@ -49,7 +48,6 @@ gradle = { module = "com.android.tools.build:gradle", version.r kotlin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" } ktlintGradle = { module = "org.jlleitschuh.gradle:ktlint-gradle", version.ref = "ktlintGradle" } detekt = { module = "io.gitlab.arturbosch.detekt:detekt-gradle-plugin", version.ref = "detekt" } -jacoco = { module = "org.jacoco:org.jacoco.core", version.ref = "jacoco" } core = { module = "androidx.core:core-ktx", version.ref = "androidxcore" } support = { module = "androidx.legacy:legacy-support-v4", version.ref = "androidLegacySupport" } @@ -103,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" } - diff --git a/gradle_scripts/android-module-bootstrap.gradle b/gradle_scripts/android-module-bootstrap.gradle index 84aa4e41..70a8a479 100644 --- a/gradle_scripts/android-module-bootstrap.gradle +++ b/gradle_scripts/android-module-bootstrap.gradle @@ -3,7 +3,6 @@ */ apply plugin: 'com.android.library' apply plugin: 'kotlin-android' -apply plugin: 'jacoco' apply from: "${project.rootDir}/gradle_scripts/code_quality.gradle" android { @@ -48,10 +47,6 @@ android { tasks.withType(Test) { useJUnitPlatform() - jacoco { - includeNoLocationClasses = true - excludes += jacocoExclude - } } dependencies { @@ -61,11 +56,4 @@ dependencies { testRuntimeOnly libs.junitVintage } -jacoco { - toolVersion(libs.versions.jacoco.get()) -} - -ext { - jacocoExclude = ['jdk.internal.*'] -} diff --git a/gradle_scripts/jacoco.gradle b/gradle_scripts/jacoco.gradle deleted file mode 100644 index c8ba2c07..00000000 --- a/gradle_scripts/jacoco.gradle +++ /dev/null @@ -1,92 +0,0 @@ -apply plugin: 'jacoco' - -jacoco { - toolVersion(libs.versions.jacoco.get()) -} - -def mergedJacocoExec = file("${project.buildDir}/jacoco/jacocoMerged.exec") - -def merge = tasks.register('jacocoMergeReports', JacocoMerge) { - group = "Reporting" - description = "Merge all jacoco reports from projects into one." - - ListProperty jacocoFiles = project.objects.listProperty(File.class) - project.subprojects { subproject -> - subproject.plugins.withId("jacoco") { - project.logger.info("${subproject.name} has Jacoco plugin applied") - subproject.tasks.withType(Test) { task -> - File destFile = task.extensions.getByType(JacocoTaskExtension.class).destinationFile - if (destFile.exists() && !task.name.contains("Release")) { - jacocoFiles.add(destFile) - } - } - } - } - - executionData(jacocoFiles) - destinationFile(mergedJacocoExec) -} - -tasks.register('jacocoFullReport', JacocoReport) { - dependsOn merge - group = "Reporting" - description = "Generate full Jacoco coverage report including all modules." - - getClassDirectories().setFrom(files()) - getSourceDirectories().setFrom(files()) - getExecutionData().setFrom(files()) - - reports { - xml.enabled = true - html.enabled = true - csv.enabled = false - } - - // Always run merging, as all input calculation is done in doFirst {} - outputs.upToDateWhen { false } - // Task will run anyway even if initial inputs are empty - onlyIf = { true } - - project.subprojects { subproject -> - subproject.plugins.withId("jacoco") { - project.logger.info("${subproject.name} has Jacoco plugin applied") - subproject.plugins.withId("kotlin-android") { - project.logger.info("${subproject.name} is android project") - def mainSources = subproject.extensions.findByName("android").sourceSets['main'] - project.logger.info("Android sources: ${mainSources.java.srcDirs}") - mainSources.java.srcDirs.forEach { - additionalSourceDirs(it) - } - project.logger.info("Subproject exclude: ${subproject.jacocoExclude}") - additionalClassDirs(fileTree( - dir: "${subproject.buildDir}/tmp/kotlin-classes/debug", - excludes: subproject.jacocoExclude - )) - } - subproject.plugins.withId("kotlin") { plugin -> - project.logger.info("${subproject.name} is common kotlin project") - SourceDirectorySet mainSources = subproject.extensions.getByName("kotlin") - .sourceSets[SourceSet.MAIN_SOURCE_SET_NAME] - .kotlin - mainSources.srcDirs.forEach { - project.logger.debug("Adding sources: $it") - additionalSourceDirs(it) - } - project.logger.info("Subproject exclude: ${subproject.jacocoExclude}") - additionalClassDirs(fileTree( - dir: "${subproject.buildDir}/classes/kotlin/main", - excludes: subproject.jacocoExclude - )) - } - - subproject.tasks.withType(Test) { task -> - File destFile = task.extensions.getByType(JacocoTaskExtension.class).destinationFile - if (destFile.exists() && !task.name.contains("Release")) { - project.logger.info("Adding execution data: $destFile") - executionData(destFile) - } - } - } - } - -} diff --git a/gradle_scripts/kotlin-module-bootstrap.gradle b/gradle_scripts/kotlin-module-bootstrap.gradle index 4a17c80f..d440dec5 100644 --- a/gradle_scripts/kotlin-module-bootstrap.gradle +++ b/gradle_scripts/kotlin-module-bootstrap.gradle @@ -3,7 +3,6 @@ */ apply plugin: 'kotlin' apply plugin: 'kotlin-kapt' -apply plugin: 'jacoco' apply from: "${project.rootDir}/gradle_scripts/code_quality.gradle" sourceSets { @@ -21,36 +20,8 @@ dependencies { testRuntimeOnly libs.junitVintage } -jacoco { - toolVersion(libs.versions.jacoco.get()) -} - -ext { - // override it in the module - jacocoExclude = ['jdk.internal.*'] -} - -jacocoTestReport { - reports { - html.required = true - xml.required = false - csv.required = false - } - - afterEvaluate { - getClassDirectories().setFrom(files(classDirectories.files.collect { - fileTree(dir: it, excludes: jacocoExclude) - })) - } -} - tasks.named("test").configure { useJUnitPlatform() - jacoco { - excludes += jacocoExclude - includeNoLocationClasses = true - } - finalizedBy jacocoTestReport } tasks.register("ciTest") { diff --git a/ultrasonic/build.gradle b/ultrasonic/build.gradle index 95196a9c..986ebf0e 100644 --- a/ultrasonic/build.gradle +++ b/ultrasonic/build.gradle @@ -1,7 +1,6 @@ apply plugin: 'com.android.application' apply plugin: 'kotlin-android' apply plugin: 'kotlin-kapt' -apply plugin: 'jacoco' apply from: "../gradle_scripts/code_quality.gradle" android { @@ -135,36 +134,3 @@ dependencies { implementation libs.timber } - -jacoco { - toolVersion(libs.versions.jacoco.get()) -} - -// Excluding all java classes and stuff that should not be covered -ext { - jacocoExclude = [ - '**/activity/**', - '**/audiofx/**', - '**/fragment/**', - '**/provider/**', - '**/receiver/**', - '**/service/**', - '**/Test/**', - '**/util/**', - '**/view/**', - '**/R$*.class', - '**/R.class', - '**/BuildConfig.class', - '**/di/**', - 'jdk.internal.*' - ] -} - -jacoco { - toolVersion(libs.versions.jacoco.get()) -} - -tasks.withType(Test) { - jacoco.includeNoLocationClasses = true - jacoco.excludes += jacocoExclude -} diff --git a/ultrasonic/lint-baseline.xml b/ultrasonic/lint-baseline.xml index 8e4cba66..620004eb 100644 --- a/ultrasonic/lint-baseline.xml +++ b/ultrasonic/lint-baseline.xml @@ -5,7 +5,7 @@ id="ObsoleteLintCustomCheck" message="Library lint checks out of date. Lint found an issue registry (`androidx.annotation.experimental.lint.ExperimentalIssueRegistry`) which was compiled against an older version of lint than this one. This often works just fine, but some basic verification shows that the lint check jar references (for example) the following API which is no longer valid in this version of lint: com.android.tools.lint.client.api.AnnotationLookup: org.jetbrains.uast.UAnnotation findRealAnnotation(com.intellij.psi.PsiAnnotation,com.intellij.psi.PsiClass,org.jetbrains.uast.UElement) (Referenced from androidx/annotation/experimental/lint/ExperimentalDetector.class) Recompile the checks against the latest version, or if this is a check bundled with a third-party library, see if there is a more recent version available. Version of Lint API this lint check is using is 11. The Lint API version currently running is 12 (7.2)."> + file="../../../../.gradle/caches/transforms-3/0939f771fd60f77a6733c1fbba02a5be/transformed/jetified-annotation-experimental-1.2.0/jars/lint.jar"/> @@ -73,7 +73,7 @@ errorLine2=" ~~~~~~~~"> @@ -209,6 +209,61 @@ column="1"/> + + + + + + + + + + + + + + + + + + + + diff --git a/ultrasonic/src/main/AndroidManifest.xml b/ultrasonic/src/main/AndroidManifest.xml index d8ed9a5f..ab6384c3 100644 --- a/ultrasonic/src/main/AndroidManifest.xml +++ b/ultrasonic/src/main/AndroidManifest.xml @@ -67,6 +67,7 @@ diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/playback/AutoMediaBrowserCallback.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/playback/AutoMediaBrowserCallback.kt index e2abb257..1f1b8e85 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/playback/AutoMediaBrowserCallback.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/playback/AutoMediaBrowserCallback.kt @@ -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() 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 + ): ListenableFuture> { - 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") diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/playback/LegacyPlaylistManager.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/playback/LegacyPlaylistManager.kt index 88d5dc13..77013e45 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/playback/LegacyPlaylistManager.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/playback/LegacyPlaylistManager.kt @@ -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 diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/playback/MediaNotificationProvider.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/playback/MediaNotificationProvider.kt index 5ecb4fe4..d9cd7c36 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/playback/MediaNotificationProvider.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/playback/MediaNotificationProvider.kt @@ -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, + 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, + playWhenReady: Boolean + ): MutableList { + val commands = super.getMediaButtons(playerCommands, customLayout, playWhenReady) - private fun ensureNotificationChannel() { - if (Util.SDK_INT < Build.VERSION_CODES.O || - notificationManager.getNotificationChannel(NOTIFICATION_CHANNEL_ID) != null - ) { - return + commands.forEachIndexed { index, command -> + command.extras.putInt(COMMAND_KEY_COMPACT_VIEW_INDEX, index) } - 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 } } diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/playback/PlaybackService.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/playback/PlaybackService.kt index 8404d0f9..1cc994d2 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/playback/PlaybackService.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/playback/PlaybackService.kt @@ -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() } } diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/MediaPlayerController.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/MediaPlayerController.kt index 749e30e0..73a5ef25 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/MediaPlayerController.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/MediaPlayerController.kt @@ -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() diff --git a/ultrasonic/src/main/res/drawable/media3_notification_small_icon.xml b/ultrasonic/src/main/res/drawable/media3_notification_small_icon.xml new file mode 100644 index 00000000..81e0ed96 --- /dev/null +++ b/ultrasonic/src/main/res/drawable/media3_notification_small_icon.xml @@ -0,0 +1,9 @@ + + +