diff --git a/README.md b/README.md index 503f6adb7..75669baef 100644 --- a/README.md +++ b/README.md @@ -8,8 +8,8 @@ A Material 3 YouTube Music client & local music player for Android [![License](https://img.shields.io/github/license/DD3Boh/OuterTune)](https://www.gnu.org/licenses/gpl-3.0) [![Downloads](https://img.shields.io/github/downloads/DD3Boh/OuterTune/total)](https://github.com/DD3Boh/OuterTune/releases) -[Get it on GitHub](https://github.com/DD3Boh/OuterTune/releases/latest) - +[Get it on GitHub](https://github.com/DD3Boh/OuterTune/releases/latest) +[Get it on IzzyOnDroid](https://apt.izzysoft.de/fdroid/index/apk/com.dd3boh.outertune) ## Features OuterTune is a supercharged fork of [InnerTune](https://github.com/z-huang/InnerTune), with advanced account synchronization, local media playback, multiple queues, and a new take on UI design. diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 8e16aeb3c..927349826 100755 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -19,8 +19,8 @@ android { applicationId = "com.dd3boh.outertune" minSdk = 24 targetSdk = 34 - versionCode = 21 - versionName = "0.6.0" + versionCode = 22 + versionName = "0.6.1" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" } buildTypes { @@ -99,6 +99,14 @@ android { jvmTarget = "17" } + // for IzzyOnDroid + dependenciesInfo { + // Disables dependency metadata when building APKs. + includeInApk = false + // Disables dependency metadata when building Android App Bundles. + includeInBundle = false + } + testOptions { unitTests.isIncludeAndroidResources = true unitTests.isReturnDefaultValues = true diff --git a/app/prebuilt/ffMetadataEx-release.aar b/app/prebuilt/ffMetadataEx-release.aar index af618f9a9..7d229167b 100644 Binary files a/app/prebuilt/ffMetadataEx-release.aar and b/app/prebuilt/ffMetadataEx-release.aar differ diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 4b85e956b..891c6d6d6 100755 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -140,12 +140,12 @@ - - + + + + + + settings[LastPosKey] = playerConnection!!.player.currentPosition + } + } + if (dataStore.get(StopMusicOnTaskClearKey, false) && playerConnection?.isPlaying?.value == true && isFinishing) { stopService(Intent(this, MusicService::class.java)) diff --git a/app/src/main/java/com/dd3boh/outertune/constants/PreferenceKeys.kt b/app/src/main/java/com/dd3boh/outertune/constants/PreferenceKeys.kt index 23576e56b..2c723061d 100644 --- a/app/src/main/java/com/dd3boh/outertune/constants/PreferenceKeys.kt +++ b/app/src/main/java/com/dd3boh/outertune/constants/PreferenceKeys.kt @@ -3,6 +3,7 @@ package com.dd3boh.outertune.constants import androidx.datastore.preferences.core.booleanPreferencesKey import androidx.datastore.preferences.core.floatPreferencesKey import androidx.datastore.preferences.core.intPreferencesKey +import androidx.datastore.preferences.core.longPreferencesKey import androidx.datastore.preferences.core.stringPreferencesKey val DynamicThemeKey = booleanPreferencesKey("dynamicTheme") @@ -32,7 +33,6 @@ val SkipSilenceKey = booleanPreferencesKey("skipSilence") val SkipOnErrorKey = booleanPreferencesKey("skipOnError") val AudioNormalizationKey = booleanPreferencesKey("audioNormalization") val YtmSyncKey = booleanPreferencesKey("ytmSync") -val KeepAliveKey = booleanPreferencesKey("keepAlive") val StopMusicOnTaskClearKey = booleanPreferencesKey("stopMusicOnTaskClear") val MaxImageCacheSizeKey = intPreferencesKey("maxImageCacheSize") @@ -126,6 +126,7 @@ val LyricsTextPositionKey = stringPreferencesKey("lyricsTextPosition") val PlayerVolumeKey = floatPreferencesKey("playerVolume") val RepeatModeKey = intPreferencesKey("repeatMode") +val LastPosKey = longPreferencesKey("lastPosKey") val LockQueueKey = booleanPreferencesKey("lockQueue") val SearchSourceKey = stringPreferencesKey("searchSource") diff --git a/app/src/main/java/com/dd3boh/outertune/db/DatabaseDao.kt b/app/src/main/java/com/dd3boh/outertune/db/DatabaseDao.kt index e6c77c839..f2fc6960e 100644 --- a/app/src/main/java/com/dd3boh/outertune/db/DatabaseDao.kt +++ b/app/src/main/java/com/dd3boh/outertune/db/DatabaseDao.kt @@ -544,15 +544,15 @@ interface DatabaseDao { fun albumWithSongs(albumId: String): Flow @Transaction - @Query("SELECT *, (SELECT COUNT(*) FROM playlist_song_map WHERE playlistId = playlist.id) AS songCount FROM playlist WHERE bookmarkedAt IS NOT NULL OR isLocal = true ORDER BY rowId") + @Query("SELECT *, (SELECT COUNT(*) FROM playlist_song_map WHERE playlistId = playlist.id) AS songCount FROM playlist WHERE bookmarkedAt IS NOT NULL OR isLocal = 1 ORDER BY rowId") fun playlistsByCreateDateAsc(): Flow> @Transaction - @Query("SELECT *, (SELECT COUNT(*) FROM playlist_song_map WHERE playlistId = playlist.id) AS songCount FROM playlist WHERE bookmarkedAt IS NOT NULL OR isLocal = true ORDER BY name COLLATE NOCASE ASC") + @Query("SELECT *, (SELECT COUNT(*) FROM playlist_song_map WHERE playlistId = playlist.id) AS songCount FROM playlist WHERE bookmarkedAt IS NOT NULL OR isLocal = 1 ORDER BY name COLLATE NOCASE ASC") fun playlistsByNameAsc(): Flow> @Transaction - @Query("SELECT *, (SELECT COUNT(*) FROM playlist_song_map WHERE playlistId = playlist.id) AS songCount FROM playlist WHERE bookmarkedAt IS NOT NULL OR isLocal = true ORDER BY songCount") + @Query("SELECT *, (SELECT COUNT(*) FROM playlist_song_map WHERE playlistId = playlist.id) AS songCount FROM playlist WHERE bookmarkedAt IS NOT NULL OR isLocal = 1 ORDER BY songCount") fun playlistsBySongCountAsc(): Flow> @Transaction @@ -595,7 +595,7 @@ interface DatabaseDao { fun playlistByBrowseId(browseId: String): Flow @Transaction - @Query("UPDATE playlist SET isLocal = true WHERE id = :playlistId") + @Query("UPDATE playlist SET isLocal = 1 WHERE id = :playlistId") fun playlistDesync(playlistId: String): Unit @Transaction @@ -662,9 +662,20 @@ interface DatabaseDao { incrementPlayCount(songId, time.year, time.monthValue) } + @Transaction + fun toggleInLibrary(songId: String, inLibrary: LocalDateTime?) { + inLibrary(songId, inLibrary) + if (inLibrary == null) { + removeLike(songId) + } + } + @Query("UPDATE song SET inLibrary = :inLibrary WHERE id = :songId") fun inLibrary(songId: String, inLibrary: LocalDateTime?) + @Query("UPDATE song SET liked = 0, likedDate = null WHERE id = :songId") + fun removeLike(songId: String) + @Query("UPDATE song SET inLibrary = null WHERE localPath = null") fun disableInvalidLocalSongs() @Query("UPDATE song SET inLibrary = null, localPath = null WHERE id = :songId") diff --git a/app/src/main/java/com/dd3boh/outertune/db/entities/SongEntity.kt b/app/src/main/java/com/dd3boh/outertune/db/entities/SongEntity.kt index 244b9e778..27d9369b4 100644 --- a/app/src/main/java/com/dd3boh/outertune/db/entities/SongEntity.kt +++ b/app/src/main/java/com/dd3boh/outertune/db/entities/SongEntity.kt @@ -61,7 +61,11 @@ data class SongEntity( } } - fun toggleLibrary() = copy(inLibrary = if (inLibrary == null) LocalDateTime.now() else null) + fun toggleLibrary() = copy( + inLibrary = if (inLibrary == null) LocalDateTime.now() else null, + liked = if (inLibrary == null) liked else false, + likedDate = if (inLibrary == null) likedDate else null + ) /** * Returns a full date string. If no full date is present, returns the year. diff --git a/app/src/main/java/com/dd3boh/outertune/lyrics/LyricsHelper.kt b/app/src/main/java/com/dd3boh/outertune/lyrics/LyricsHelper.kt index 1b191746d..1c970ddd8 100644 --- a/app/src/main/java/com/dd3boh/outertune/lyrics/LyricsHelper.kt +++ b/app/src/main/java/com/dd3boh/outertune/lyrics/LyricsHelper.kt @@ -3,10 +3,13 @@ package com.dd3boh.outertune.lyrics import android.content.Context import android.os.Build import android.util.LruCache +import androidx.annotation.RequiresApi +import com.dd3boh.outertune.db.MusicDatabase import com.dd3boh.outertune.db.entities.LyricsEntity.Companion.LYRICS_NOT_FOUND import com.dd3boh.outertune.models.MediaMetadata import com.dd3boh.outertune.utils.reportException import dagger.hilt.android.qualifiers.ApplicationContext +import kotlinx.coroutines.flow.first import javax.inject.Inject // true will prioritize local lyric files over all cloud providers, true is vice versa @@ -17,14 +20,30 @@ class LyricsHelper @Inject constructor( private val lyricsProviders = listOf(YouTubeSubtitleLyricsProvider, LrcLibLyricsProvider, KuGouLyricsProvider, YouTubeLyricsProvider) private val cache = LruCache>(MAX_CACHE_SIZE) - suspend fun getLyrics(mediaMetadata: MediaMetadata): String { + /** + * Retrieve lyrics from all sources + * + * @param mediaMetadata Song to fetch lyrics for + * @param database MusicDatabase connection. Database lyrics are prioritized over all sources. + * If no database is provided, the database source is disabled + */ + suspend fun getLyrics(mediaMetadata: MediaMetadata, database: MusicDatabase? = null): String { val cached = cache.get(mediaMetadata.id)?.firstOrNull() if (cached != null) { return cached.lyrics } + val dbLyrics = database?.lyrics(mediaMetadata.id)?.let { it.first()?.lyrics } + if (dbLyrics != null) { + return dbLyrics + } + + // Nougat support is likely going to be dropped soon + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { + return getRemoteLyrics(mediaMetadata) ?: LYRICS_NOT_FOUND + } val localLyrics = getLocalLyrics(mediaMetadata) - var remoteLyrics: String? + val remoteLyrics: String? // fallback to secondary provider when primary is unavailable if (PREFER_LOCAL_LYRIC) { @@ -74,10 +93,8 @@ class LyricsHelper @Inject constructor( /** * Lookup lyrics from local disk (.lrc) file */ + @RequiresApi(Build.VERSION_CODES.O) suspend fun getLocalLyrics(mediaMetadata: MediaMetadata): String? { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { - throw Exception("Local lyrics are not supported below SDK 26 (Oreo)") - } if (LocalLyricsProvider.isEnabled(context)) { LocalLyricsProvider.getLyrics( mediaMetadata.id, @@ -86,8 +103,6 @@ class LyricsHelper @Inject constructor( mediaMetadata.duration ).onSuccess { lyrics -> return lyrics - }.onFailure { - reportException(it) } } diff --git a/app/src/main/java/com/dd3boh/outertune/playback/KeepAlive.kt b/app/src/main/java/com/dd3boh/outertune/playback/KeepAlive.kt deleted file mode 100644 index c2cd6842c..000000000 --- a/app/src/main/java/com/dd3boh/outertune/playback/KeepAlive.kt +++ /dev/null @@ -1,77 +0,0 @@ -package com.dd3boh.outertune.playback - -import android.app.Notification -import android.app.NotificationChannel -import android.app.NotificationManager -import android.app.PendingIntent -import android.app.Service -import android.content.Intent -import android.os.Binder -import android.os.Build -import androidx.core.app.NotificationCompat -import com.dd3boh.outertune.MainActivity -import com.dd3boh.outertune.R - -/** - * Dummy service that does nothing but post a notification - */ -class KeepAlive : Service() { - private lateinit var notif: Notification - private val binder = DummyBinder() - - inner class DummyBinder : Binder() { - val service: KeepAlive - get() = this@KeepAlive - } - - override fun onCreate() { - super.onCreate() - notif = getNotification() - createNotificationChannel() - } - override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { - startForeground(1, notif) - return START_STICKY - } - - override fun onBind(intent: Intent?) = binder - - private fun createNotificationChannel() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - val serviceChannel = NotificationChannel( - KEEP_ALIVE_CHANNEL_ID, - "ot_keep_alive", - NotificationManager.IMPORTANCE_MIN - ) - val manager = getSystemService(NotificationManager::class.java) - manager.createNotificationChannel(serviceChannel) - } - } - - private fun getNotification(): Notification { - val notificationIntent = Intent(this, MainActivity::class.java) - val pendingIntent = PendingIntent.getActivity( - this, 0, notificationIntent, - PendingIntent.FLAG_IMMUTABLE - ) - val builder = NotificationCompat.Builder(this, KEEP_ALIVE_CHANNEL_ID) - .setContentTitle("Keep alive") - .setSmallIcon(R.drawable.small_icon) - .setContentIntent(pendingIntent) - .setPriority(NotificationCompat.PRIORITY_MIN) - .setCategory(NotificationCompat.CATEGORY_SERVICE) - .setVisibility(NotificationCompat.VISIBILITY_SECRET) - .setAutoCancel(false) - .setShowWhen(false) - .setLocalOnly(true) - .setOngoing(true) - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { - builder.setForegroundServiceBehavior(Notification.FOREGROUND_SERVICE_IMMEDIATE) - } - return builder.build() - } - - companion object { - const val KEEP_ALIVE_CHANNEL_ID = "outertune_keep_alive" - } -} \ No newline at end of file diff --git a/app/src/main/java/com/dd3boh/outertune/playback/MusicService.kt b/app/src/main/java/com/dd3boh/outertune/playback/MusicService.kt index 52fba41f6..aed70b6dc 100644 --- a/app/src/main/java/com/dd3boh/outertune/playback/MusicService.kt +++ b/app/src/main/java/com/dd3boh/outertune/playback/MusicService.kt @@ -55,7 +55,7 @@ import com.dd3boh.outertune.R import com.dd3boh.outertune.constants.AudioNormalizationKey import com.dd3boh.outertune.constants.AudioQuality import com.dd3boh.outertune.constants.AudioQualityKey -import com.dd3boh.outertune.constants.KeepAliveKey +import com.dd3boh.outertune.constants.LastPosKey import com.dd3boh.outertune.constants.MediaSessionConstants.CommandToggleLike import com.dd3boh.outertune.constants.MediaSessionConstants.CommandToggleRepeatMode import com.dd3boh.outertune.constants.MediaSessionConstants.CommandToggleShuffle @@ -189,22 +189,6 @@ class MusicService : MediaLibraryService(), setSmallIcon(R.drawable.small_icon) } ) - - // FG notification - if (dataStore.get(KeepAliveKey, false)) { - try { - startService(Intent(this, KeepAlive::class.java)) - } catch (e: Exception) { - reportException(e) - } - } else { - try { - stopService(Intent(this, KeepAlive::class.java)) - } catch (e: Exception) { - reportException(e) - } - } - player = ExoPlayer.Builder(this) .setMediaSourceFactory(DefaultMediaSourceFactory(createDataSourceFactory())) .setRenderersFactory(createRenderersFactory()) @@ -278,10 +262,8 @@ class MusicService : MediaLibraryService(), if (player.currentMediaItemIndex == 0 && lastMediaItemIndex == player.mediaItemCount - 1 && (reason == MEDIA_ITEM_TRANSITION_REASON_AUTO || reason == MEDIA_ITEM_TRANSITION_REASON_SEEK) && isShuffleEnabled.value && player.repeatMode == REPEAT_MODE_ALL) { - player.pause() queueBoard.shuffleCurrent(this@MusicService, false) // reshuffle queue queueBoard.setCurrQueue(this@MusicService) - player.play() } lastMediaItemIndex = player.currentMediaItemIndex @@ -387,7 +369,10 @@ class MusicService : MediaLibraryService(), if (queue != null) { isShuffleEnabled.value = queue.shuffled CoroutineScope(Dispatchers.Main).launch { - queueBoard.setCurrQueue(this@MusicService) + val queuePos = queueBoard.setCurrQueue(this@MusicService, false) + if (queuePos != null) { + player.seekTo(queuePos, dataStore.get(LastPosKey, C.TIME_UNSET)) + } } } } @@ -828,6 +813,11 @@ class MusicService : MediaLibraryService(), override fun onDestroy() { if (dataStore.get(PersistentQueueKey, true)) { saveQueueToDisk() + scope.launch { + dataStore.edit { settings -> + settings[LastPosKey] = player.currentPosition + } + } } mediaSession.release() player.removeListener(this) diff --git a/app/src/main/java/com/dd3boh/outertune/playback/PlayerConnection.kt b/app/src/main/java/com/dd3boh/outertune/playback/PlayerConnection.kt index 47d8f50ca..928857db1 100644 --- a/app/src/main/java/com/dd3boh/outertune/playback/PlayerConnection.kt +++ b/app/src/main/java/com/dd3boh/outertune/playback/PlayerConnection.kt @@ -47,19 +47,17 @@ class PlayerConnection( database.song(it?.id) } val currentLyrics = mediaMetadata.flatMapLatest { mediaMetadata -> - // local songs will always look at lrc files first - if (mediaMetadata?.isLocal == true) { - val lyrics = service.lyricsHelper.getLocalLyrics(mediaMetadata) - if (lyrics != null) { - return@flatMapLatest flowOf( - LyricsEntity( - id = mediaMetadata.id, - lyrics = lyrics - ) + if (mediaMetadata != null) { + val lyrics = service.lyricsHelper.getLyrics(mediaMetadata, database) + return@flatMapLatest flowOf( + LyricsEntity( + id = mediaMetadata.id, + lyrics = lyrics ) - } + ) + } else { + return@flatMapLatest flowOf() } - database.lyrics(mediaMetadata?.id) } val currentFormat = mediaMetadata.flatMapLatest { mediaMetadata -> database.format(mediaMetadata?.id) diff --git a/app/src/main/java/com/dd3boh/outertune/ui/menu/AlbumMenu.kt b/app/src/main/java/com/dd3boh/outertune/ui/menu/AlbumMenu.kt index ddbc38a3c..26b74b90e 100644 --- a/app/src/main/java/com/dd3boh/outertune/ui/menu/AlbumMenu.kt +++ b/app/src/main/java/com/dd3boh/outertune/ui/menu/AlbumMenu.kt @@ -19,6 +19,8 @@ import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.rounded.PlaylistAdd import androidx.compose.material.icons.automirrored.rounded.PlaylistPlay import androidx.compose.material.icons.automirrored.rounded.QueueMusic +import androidx.compose.material.icons.rounded.LibraryAdd +import androidx.compose.material.icons.rounded.LibraryAddCheck import androidx.compose.material.icons.rounded.Share import androidx.compose.material.icons.rounded.Sync import androidx.compose.material3.HorizontalDivider @@ -77,6 +79,7 @@ import com.dd3boh.outertune.ui.component.ListDialog import com.zionhuang.innertube.YouTube import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch +import java.time.LocalDateTime @Composable fun AlbumMenu( @@ -94,6 +97,13 @@ fun AlbumMenu( var songs by remember { mutableStateOf(emptyList()) } + val allInLibrary = remember(songs) { + songs.all { it.song.inLibrary != null } + } +// for when local albums are a thing +// val allLocal by remember(songs) { // if only local songs in this selection +// mutableStateOf(songs.isNotEmpty() && songs.all { it.song.isLocal }) +// } val coroutineScope = rememberCoroutineScope() @@ -269,6 +279,29 @@ fun AlbumMenu( ) { showChoosePlaylistDialog = true } + if (allInLibrary) { + GridMenuItem( + icon = Icons.Rounded.LibraryAddCheck, + title = R.string.remove_all_from_library + ) { + database.transaction { + songs.forEach { + toggleInLibrary(it.id, null) + } + } + } + } else { + GridMenuItem( + icon = Icons.Rounded.LibraryAdd, + title = R.string.add_all_to_library + ) { + database.transaction { + songs.forEach { + toggleInLibrary(it.id, LocalDateTime.now()) + } + } + } + } DownloadGridMenu( state = downloadState, onDownload = { diff --git a/app/src/main/java/com/dd3boh/outertune/ui/menu/PlayerMenu.kt b/app/src/main/java/com/dd3boh/outertune/ui/menu/PlayerMenu.kt index 111f28a46..6e76f679f 100644 --- a/app/src/main/java/com/dd3boh/outertune/ui/menu/PlayerMenu.kt +++ b/app/src/main/java/com/dd3boh/outertune/ui/menu/PlayerMenu.kt @@ -23,6 +23,8 @@ import androidx.compose.material.icons.automirrored.rounded.PlaylistAdd import androidx.compose.material.icons.automirrored.rounded.VolumeUp import androidx.compose.material.icons.rounded.AddCircleOutline import androidx.compose.material.icons.rounded.Info +import androidx.compose.material.icons.rounded.LibraryAdd +import androidx.compose.material.icons.rounded.LibraryAddCheck import androidx.compose.material.icons.rounded.Radio import androidx.compose.material.icons.rounded.RemoveCircleOutline import androidx.compose.material.icons.rounded.Share @@ -83,6 +85,7 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay import kotlinx.coroutines.isActive import kotlinx.coroutines.launch +import java.time.LocalDateTime import kotlin.math.log2 import kotlin.math.pow import kotlin.math.round @@ -102,6 +105,7 @@ fun PlayerMenu( val playerConnection = LocalPlayerConnection.current ?: return val playerVolume = playerConnection.service.playerVolume.collectAsState() val activityResultLauncher = rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) { } + val librarySong by database.song(mediaMetadata.id).collectAsState(initial = null) val coroutineScope = rememberCoroutineScope() val download by LocalDownloadUtil.current.getDownload(mediaMetadata.id).collectAsState(initial = null) @@ -325,6 +329,26 @@ fun PlayerMenu( ) } ) + if (librarySong?.song?.inLibrary != null) { + GridMenuItem( + icon = Icons.Rounded.LibraryAddCheck, + title = R.string.remove_from_library, + ) { + database.query { + toggleInLibrary(mediaMetadata.id, null) + } + } + } else { + GridMenuItem( + icon = Icons.Rounded.LibraryAdd, + title = R.string.add_to_library, + ) { + database.transaction { + insert(mediaMetadata) + toggleInLibrary(mediaMetadata.id, LocalDateTime.now()) + } + } + } GridMenuItem( icon = R.drawable.artist, title = R.string.view_artist diff --git a/app/src/main/java/com/dd3boh/outertune/ui/menu/SelectionSongsMenu.kt b/app/src/main/java/com/dd3boh/outertune/ui/menu/SelectionSongsMenu.kt index 946b2a0a6..04a4ae26a 100644 --- a/app/src/main/java/com/dd3boh/outertune/ui/menu/SelectionSongsMenu.kt +++ b/app/src/main/java/com/dd3boh/outertune/ui/menu/SelectionSongsMenu.kt @@ -7,6 +7,8 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.systemBars import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.rounded.PlaylistAdd +import androidx.compose.material.icons.rounded.LibraryAdd +import androidx.compose.material.icons.rounded.LibraryAddCheck import androidx.compose.material.icons.rounded.PlaylistRemove import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text @@ -43,6 +45,7 @@ import com.dd3boh.outertune.ui.component.DefaultDialog import com.dd3boh.outertune.ui.component.DownloadGridMenu import com.dd3boh.outertune.ui.component.GridMenu import com.dd3boh.outertune.ui.component.GridMenuItem +import java.time.LocalDateTime @Composable @@ -57,6 +60,13 @@ fun SelectionSongMenu( val downloadUtil = LocalDownloadUtil.current val playerConnection = LocalPlayerConnection.current ?: return + val allInLibrary by remember(songSelection) { // exclude local songs + mutableStateOf(songSelection.isNotEmpty() && songSelection.all { !it.song.isLocal && it.song.inLibrary != null }) + } + val allLocal by remember(songSelection) { // if only local songs in this selection + mutableStateOf(songSelection.isNotEmpty() && songSelection.all { it.song.isLocal }) + } + var downloadState by remember { mutableIntStateOf(Download.STATE_STOPPED) } @@ -219,6 +229,34 @@ fun SelectionSongMenu( } ) + if (!allLocal) { + if (allInLibrary) { + GridMenuItem( + icon = Icons.Rounded.LibraryAddCheck, + title = R.string.remove_all_from_library + ) { + database.transaction { + songSelection.forEach { song -> + toggleInLibrary(song.id, null) + } + } + } + } else { + GridMenuItem( + icon = Icons.Rounded.LibraryAdd, + title = R.string.add_all_to_library + ) { + database.transaction { + songSelection.forEach { song -> + if (!song.song.isLocal) { + toggleInLibrary(song.id, LocalDateTime.now()) + } + } + } + } + } + } + GridMenuItem( icon = if (songSelection.all{it.song.liked}) R.drawable.favorite else R.drawable.favorite_border, title = R.string.like_all diff --git a/app/src/main/java/com/dd3boh/outertune/ui/menu/SongMenu.kt b/app/src/main/java/com/dd3boh/outertune/ui/menu/SongMenu.kt index 98d4a4683..61122c4e5 100644 --- a/app/src/main/java/com/dd3boh/outertune/ui/menu/SongMenu.kt +++ b/app/src/main/java/com/dd3boh/outertune/ui/menu/SongMenu.kt @@ -20,6 +20,8 @@ import androidx.compose.material.icons.automirrored.rounded.QueueMusic import androidx.compose.material.icons.rounded.Album import androidx.compose.material.icons.rounded.Delete import androidx.compose.material.icons.rounded.Edit +import androidx.compose.material.icons.rounded.LibraryAdd +import androidx.compose.material.icons.rounded.LibraryAddCheck import androidx.compose.material.icons.rounded.PlaylistRemove import androidx.compose.material.icons.rounded.Radio import androidx.compose.material.icons.rounded.Share @@ -343,6 +345,27 @@ fun SongMenu( } context.startActivity(Intent.createChooser(intent, null)) } + if (!song.song.isLocal) { + if (song.song.inLibrary == null) { + GridMenuItem( + icon = Icons.Rounded.LibraryAdd, + title = R.string.add_to_library + ) { + database.query { + update(song.song.toggleLibrary()) + } + } + } else { + GridMenuItem( + icon = Icons.Rounded.LibraryAddCheck, + title = R.string.remove_from_library + ) { + database.query { + update(song.song.toggleLibrary()) + } + } + } + } if (event != null) { GridMenuItem( icon = Icons.Rounded.Delete, diff --git a/app/src/main/java/com/dd3boh/outertune/ui/menu/YouTubePlaylistMenu.kt b/app/src/main/java/com/dd3boh/outertune/ui/menu/YouTubePlaylistMenu.kt index 49e15fcda..e027eda1d 100644 --- a/app/src/main/java/com/dd3boh/outertune/ui/menu/YouTubePlaylistMenu.kt +++ b/app/src/main/java/com/dd3boh/outertune/ui/menu/YouTubePlaylistMenu.kt @@ -49,6 +49,7 @@ import com.dd3boh.outertune.extensions.toMediaItem import com.dd3boh.outertune.models.toMediaMetadata import com.dd3boh.outertune.playback.ExoDownloadService import com.dd3boh.outertune.playback.PlayerConnection.Companion.queueBoard +import com.dd3boh.outertune.playback.queues.ListQueue import com.dd3boh.outertune.playback.queues.YouTubeQueue import com.dd3boh.outertune.ui.component.DefaultDialog import com.dd3boh.outertune.ui.component.DownloadGridMenu @@ -261,7 +262,14 @@ fun YouTubePlaylistMenu( title = R.string.play ) { println("Play: ${it.playlistId}, ${it.params}") - playerConnection.playQueue(YouTubeQueue(it)) + playerConnection.playQueue( + ListQueue( + playlistId = playlist.playEndpoint!!.playlistId, + title = playlist.title, + items = songs.map { it.toMediaMetadata() }, + ) + ) + onDismiss() } } @@ -272,7 +280,13 @@ fun YouTubePlaylistMenu( title = R.string.shuffle ) { println("Shuffle: id: ${shuffleEndpoint.playlistId}, params: ${shuffleEndpoint.params}") - playerConnection.playQueue(YouTubeQueue(shuffleEndpoint)) + playerConnection.playQueue( + ListQueue( + playlistId = playlist.playEndpoint!!.playlistId, + title = playlist.title, + items = songs.map { it.toMediaMetadata() }.shuffled(), + ) + ) onDismiss() } } diff --git a/app/src/main/java/com/dd3boh/outertune/ui/screens/artist/ArtistScreen.kt b/app/src/main/java/com/dd3boh/outertune/ui/screens/artist/ArtistScreen.kt index 7c9e84495..ac5205332 100644 --- a/app/src/main/java/com/dd3boh/outertune/ui/screens/artist/ArtistScreen.kt +++ b/app/src/main/java/com/dd3boh/outertune/ui/screens/artist/ArtistScreen.kt @@ -23,6 +23,7 @@ import androidx.compose.foundation.layout.windowInsetsPadding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyRow import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.rounded.ArrowBack @@ -236,10 +237,10 @@ fun ArtistScreen( ) } - items( + itemsIndexed( items = librarySongs, - key = { "local_${it.id}" } - ) { song -> + key = { _, item -> item.hashCode() } + ) { index, song -> SwipeToQueueBox( item = song.toMediaItem(), content = { @@ -270,19 +271,13 @@ fun ArtistScreen( .combinedClickable { if (song.id == mediaMetadata?.id) { playerConnection.player.togglePlayPause() - } else if (song.song.isLocal) { + } else { playerConnection.playQueue( ListQueue( title = "Library: ${artistPage.artist.title}", - items = librarySongs.filter { it.song.isLocal } .toList().shuffled().map { it.toMediaMetadata() } - ) - ) - } else { - playerConnection.playQueue( - YouTubeQueue( - WatchEndpoint( - videoId = song.id - ), song.toMediaMetadata() + items = librarySongs.filter { it.song.isLocal }.toList() + .shuffled().map { it.toMediaMetadata() }, + startIndex = index ) ) } diff --git a/app/src/main/java/com/dd3boh/outertune/ui/screens/playlist/OnlinePlaylistScreen.kt b/app/src/main/java/com/dd3boh/outertune/ui/screens/playlist/OnlinePlaylistScreen.kt index d4371f5c3..4d604a9c4 100644 --- a/app/src/main/java/com/dd3boh/outertune/ui/screens/playlist/OnlinePlaylistScreen.kt +++ b/app/src/main/java/com/dd3boh/outertune/ui/screens/playlist/OnlinePlaylistScreen.kt @@ -16,7 +16,7 @@ import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.windowInsetsPadding import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.text.ClickableText @@ -87,7 +87,7 @@ import com.dd3boh.outertune.extensions.toMediaItem import com.dd3boh.outertune.extensions.togglePlayPause import com.dd3boh.outertune.models.toMediaMetadata import com.dd3boh.outertune.playback.ExoDownloadService -import com.dd3boh.outertune.playback.queues.YouTubeQueue +import com.dd3boh.outertune.playback.queues.ListQueue import com.dd3boh.outertune.ui.component.AutoResizeText import com.dd3boh.outertune.ui.component.DefaultDialog import com.dd3boh.outertune.ui.component.FontSizeRange @@ -106,7 +106,6 @@ import com.dd3boh.outertune.ui.utils.ItemWrapper import com.dd3boh.outertune.ui.utils.backToMain import com.dd3boh.outertune.viewmodels.OnlinePlaylistViewModel import com.zionhuang.innertube.models.SongItem -import com.zionhuang.innertube.models.WatchEndpoint import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch @@ -442,7 +441,13 @@ fun OnlinePlaylistScreen( playlist.playEndpoint?.let { playEndpoint -> Button( onClick = { - playerConnection.playQueue(YouTubeQueue(playEndpoint)) + playerConnection.playQueue( + ListQueue( + playlistId = playlist.playEndpoint!!.playlistId, + title = playlist.title, + items = songs.map { it.toMediaMetadata() }, + ) + ) }, contentPadding = ButtonDefaults.ButtonWithIconContentPadding, modifier = Modifier.weight(1f) @@ -457,10 +462,16 @@ fun OnlinePlaylistScreen( } } - playlist.shuffleEndpoint?.let { shuffleEndpoint -> + playlist.shuffleEndpoint?.let { OutlinedButton( onClick = { - playerConnection.playQueue(YouTubeQueue(shuffleEndpoint)) + playerConnection.playQueue( + ListQueue( + playlistId = playlist.playEndpoint!!.playlistId, + title = playlist.title, + items = songs.map { it.toMediaMetadata() }.shuffled(), + ) + ) }, contentPadding = ButtonDefaults.ButtonWithIconContentPadding, modifier = Modifier.weight(1f) @@ -492,9 +503,9 @@ fun OnlinePlaylistScreen( } } - items( + itemsIndexed( items = wrappedSongs - ) { song -> + ) { index, song -> SwipeToQueueBox( item = song.item.toMediaItem(), content = { @@ -528,9 +539,11 @@ fun OnlinePlaylistScreen( playerConnection.player.togglePlayPause() } else { playerConnection.playQueue( - YouTubeQueue( - song.item.endpoint ?: WatchEndpoint(videoId = song.item.id), - song.item.toMediaMetadata() + ListQueue( + playlistId = playlist.id, + title = playlist.title, + items = songs.map { it.toMediaMetadata() }, + startIndex = index ) ) } diff --git a/app/src/main/java/com/dd3boh/outertune/ui/screens/search/OnlineSearchResult.kt b/app/src/main/java/com/dd3boh/outertune/ui/screens/search/OnlineSearchResult.kt index 79405a25f..0f5529145 100644 --- a/app/src/main/java/com/dd3boh/outertune/ui/screens/search/OnlineSearchResult.kt +++ b/app/src/main/java/com/dd3boh/outertune/ui/screens/search/OnlineSearchResult.kt @@ -44,7 +44,6 @@ import com.dd3boh.outertune.constants.SearchFilterHeight import com.dd3boh.outertune.extensions.toMediaItem import com.dd3boh.outertune.extensions.togglePlayPause import com.dd3boh.outertune.models.toMediaMetadata -import com.dd3boh.outertune.playback.queues.ListQueue import com.dd3boh.outertune.playback.queues.YouTubeQueue import com.dd3boh.outertune.ui.component.ChipsRow import com.dd3boh.outertune.ui.component.EmptyPlaceholder @@ -161,15 +160,6 @@ fun OnlineSearchResult( is SongItem -> { if (item.id == mediaMetadata?.id) { playerConnection.player.togglePlayPause() - } else if (item.id.startsWith("LA")) { - playerConnection.playQueue( - ListQueue( - title = "Search", - items = listOf(item.toMediaMetadata()) - ), - replace = true, - title = "Search", - ) } else { playerConnection.playQueue( YouTubeQueue( diff --git a/app/src/main/java/com/dd3boh/outertune/ui/screens/settings/PlayerSettings.kt b/app/src/main/java/com/dd3boh/outertune/ui/screens/settings/PlayerSettings.kt index 97dbf6733..204284784 100644 --- a/app/src/main/java/com/dd3boh/outertune/ui/screens/settings/PlayerSettings.kt +++ b/app/src/main/java/com/dd3boh/outertune/ui/screens/settings/PlayerSettings.kt @@ -1,11 +1,5 @@ package com.dd3boh.outertune.ui.screens.settings -import android.Manifest -import android.app.Activity -import android.content.Intent -import android.content.pm.PackageManager -import android.os.Build -import android.widget.Toast import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth @@ -21,7 +15,6 @@ import androidx.compose.material.icons.rounded.ClearAll import androidx.compose.material.icons.rounded.FastForward import androidx.compose.material.icons.rounded.GraphicEq import androidx.compose.material.icons.rounded.Lyrics -import androidx.compose.material.icons.rounded.NoCell import androidx.compose.material.icons.rounded.Sync import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon @@ -31,7 +24,6 @@ import androidx.compose.material3.Text import androidx.compose.material3.TopAppBar import androidx.compose.material3.TopAppBarScrollBehavior import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf @@ -39,24 +31,20 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp -import androidx.core.app.ActivityCompat import androidx.navigation.NavController import com.dd3boh.outertune.LocalPlayerAwareWindowInsets import com.dd3boh.outertune.R import com.dd3boh.outertune.constants.AudioNormalizationKey import com.dd3boh.outertune.constants.AudioQuality import com.dd3boh.outertune.constants.AudioQualityKey -import com.dd3boh.outertune.constants.KeepAliveKey import com.dd3boh.outertune.constants.PersistentQueueKey import com.dd3boh.outertune.constants.SkipOnErrorKey import com.dd3boh.outertune.constants.SkipSilenceKey import com.dd3boh.outertune.constants.StopMusicOnTaskClearKey import com.dd3boh.outertune.constants.minPlaybackDurKey -import com.dd3boh.outertune.playback.KeepAlive import com.dd3boh.outertune.ui.component.ActionPromptDialog import com.dd3boh.outertune.ui.component.EnumListPreference import com.dd3boh.outertune.ui.component.IconButton @@ -66,7 +54,6 @@ import com.dd3boh.outertune.ui.component.SwitchPreference import com.dd3boh.outertune.ui.utils.backToMain import com.dd3boh.outertune.utils.rememberEnumPreference import com.dd3boh.outertune.utils.rememberPreference -import com.dd3boh.outertune.utils.reportException @OptIn(ExperimentalMaterial3Api::class) @Composable @@ -74,8 +61,6 @@ fun PlayerSettings( navController: NavController, scrollBehavior: TopAppBarScrollBehavior, ) { - val context = LocalContext.current - val (audioQuality, onAudioQualityChange) = rememberEnumPreference(key = AudioQualityKey, defaultValue = AudioQuality.AUTO) val (persistentQueue, onPersistentQueueChange) = rememberPreference(key = PersistentQueueKey, defaultValue = true) val (skipSilence, onSkipSilenceChange) = rememberPreference(key = SkipSilenceKey, defaultValue = false) @@ -83,7 +68,6 @@ fun PlayerSettings( val (audioNormalization, onAudioNormalizationChange) = rememberPreference(key = AudioNormalizationKey, defaultValue = true) val (stopMusicOnTaskClear, onStopMusicOnTaskClearChange) = rememberPreference(key = StopMusicOnTaskClearKey, defaultValue = false) val (minPlaybackDur, onMinPlaybackDurChange) = rememberPreference(minPlaybackDurKey, defaultValue = 30) - val (keepAlive, onKeepAliveChange) = rememberPreference(key = KeepAliveKey, defaultValue = false) var showMinPlaybackDur by remember { mutableStateOf(false) @@ -92,51 +76,6 @@ fun PlayerSettings( mutableIntStateOf(minPlaybackDur) } - fun toggleKeepAlive(newValue: Boolean) { - // disable and request if disabled - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU - && context.checkSelfPermission(Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) { - onKeepAliveChange(false) - Toast.makeText( - context, - "Notification permission is required", - Toast.LENGTH_SHORT - ).show() - - ActivityCompat.requestPermissions( - context as Activity, - arrayOf( Manifest.permission.POST_NOTIFICATIONS), PackageManager.PERMISSION_GRANTED - ) - return - } - - if (keepAlive != newValue) { - onKeepAliveChange(newValue) - // start/stop service accordingly - if (newValue) { - try { - context.startService(Intent(context, KeepAlive::class.java)) - } catch (e: Exception) { - reportException(e) - } - } else { - try { - context.stopService(Intent(context, KeepAlive::class.java)) - } catch (e: Exception) { - reportException(e) - } - } - } - } - - // reset if no permission - LaunchedEffect(keepAlive) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU - && context.checkSelfPermission(Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) { - onKeepAliveChange(false) - } - } - if (showMinPlaybackDur) { ActionPromptDialog( @@ -246,13 +185,6 @@ fun PlayerSettings( checked = stopMusicOnTaskClear, onCheckedChange = onStopMusicOnTaskClearChange ) - SwitchPreference( - title = { Text(stringResource(R.string.keep_alive_title)) }, - description = stringResource(R.string.keep_alive_description), - icon = { Icon(Icons.Rounded.NoCell, null) }, - checked = keepAlive, - onCheckedChange = { toggleKeepAlive(it) } - ) } TopAppBar( diff --git a/app/src/main/java/com/dd3boh/outertune/utils/scanners/FFProbeScanner.kt b/app/src/main/java/com/dd3boh/outertune/utils/scanners/FFMpegScanner.kt similarity index 95% rename from app/src/main/java/com/dd3boh/outertune/utils/scanners/FFProbeScanner.kt rename to app/src/main/java/com/dd3boh/outertune/utils/scanners/FFMpegScanner.kt index bfb1f9a53..d77815129 100644 --- a/app/src/main/java/com/dd3boh/outertune/utils/scanners/FFProbeScanner.kt +++ b/app/src/main/java/com/dd3boh/outertune/utils/scanners/FFMpegScanner.kt @@ -1,5 +1,6 @@ package com.dd3boh.outertune.utils.scanners +import com.dd3boh.ffMetadataEx.FFMpegWrapper import com.dd3boh.outertune.db.entities.AlbumEntity import com.dd3boh.outertune.db.entities.ArtistEntity import com.dd3boh.outertune.db.entities.FormatEntity @@ -9,7 +10,6 @@ import com.dd3boh.outertune.db.entities.SongEntity import com.dd3boh.outertune.models.SongTempData import com.dd3boh.outertune.ui.utils.ARTIST_SEPARATORS import timber.log.Timber -import wah.mikooomich.ffMetadataEx.FFprobeWrapper import java.io.File import java.lang.Integer.parseInt import java.lang.Long.parseLong @@ -21,15 +21,15 @@ import kotlin.math.roundToLong const val EXTRACTOR_DEBUG = false const val DEBUG_SAVE_OUTPUT = false // ignored (will be false) when EXTRACTOR_DEBUG IS false -const val EXTRACTOR_TAG = "FFProbeExtractor" +const val EXTRACTOR_TAG = "FFMpegExtractor" const val toSeconds = 1000 * 60 * 16.7 // convert FFmpeg duration to seconds -class FFProbeScanner : MetadataScanner { +class FFMpegScanner : MetadataScanner { // load advanced scanner libs init { System.loadLibrary("avcodec") System.loadLibrary("avdevice") - System.loadLibrary("ffprobejni") + System.loadLibrary("ffmetaexjni") System.loadLibrary("avfilter") System.loadLibrary("avformat") System.loadLibrary("avutil") @@ -45,8 +45,8 @@ class FFProbeScanner : MetadataScanner { override fun getMediaStoreSupplement(path: String): ExtraMetadataWrapper { if (EXTRACTOR_DEBUG) Timber.tag(EXTRACTOR_TAG).d("Starting MediaStoreSupplement session on: $path") - val ffprobe = FFprobeWrapper() - val data = ffprobe.getAudioMetadata(path) + val ffmpeg = FFMpegWrapper() + val data = ffmpeg.getAudioMetadata(path) if (EXTRACTOR_DEBUG && DEBUG_SAVE_OUTPUT) { Timber.tag(EXTRACTOR_TAG).d("Full output for: $path \n $data") @@ -77,8 +77,8 @@ class FFProbeScanner : MetadataScanner { override fun getAllMetadata(path: String): SongTempData { if (EXTRACTOR_DEBUG) Timber.tag(EXTRACTOR_TAG).d("Starting Full Extractor session on: $path") - val ffprobe = FFprobeWrapper() - val data = ffprobe.getFullAudioMetadata(path) + val ffmpeg = FFMpegWrapper() + val data = ffmpeg.getFullAudioMetadata(path) if (EXTRACTOR_DEBUG && DEBUG_SAVE_OUTPUT) { Timber.tag(EXTRACTOR_TAG).d("Full output for: $path \n $data") diff --git a/app/src/main/java/com/dd3boh/outertune/utils/scanners/LocalMediaScanner.kt b/app/src/main/java/com/dd3boh/outertune/utils/scanners/LocalMediaScanner.kt index feb48aa52..a4ff76c9d 100644 --- a/app/src/main/java/com/dd3boh/outertune/utils/scanners/LocalMediaScanner.kt +++ b/app/src/main/java/com/dd3boh/outertune/utils/scanners/LocalMediaScanner.kt @@ -613,7 +613,7 @@ class LocalMediaScanner { */ fun getAdvancedScanner(): MetadataScanner? { // kotlin won't let me return MetadataScanner even if it cant possibly be null broooo - return if (advancedScannerImpl is FFProbeScanner) advancedScannerImpl else FFProbeScanner() + return if (advancedScannerImpl is FFMpegScanner) advancedScannerImpl else FFMpegScanner() } fun unloadAdvancedScanner() { diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 209ffb764..ba3142888 100755 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -87,6 +87,10 @@ Play Play next Add to queue + Add to library + Add all to library + Remove from library + Remove all from library Download Downloading Remove download diff --git a/assets/IzzyOnDroidButtonGreyBorder.svg b/assets/IzzyOnDroidButtonGreyBorder.svg new file mode 100644 index 000000000..184cfd7bb --- /dev/null +++ b/assets/IzzyOnDroidButtonGreyBorder.svg @@ -0,0 +1,76 @@ + +IzzyOnDroid Button + + + + + +GET IT ONIzzyOnDroidimage/svg+xmlIzzyOnDroid ButtonIzzy, Wolfshappen diff --git a/assets/badge_github.png b/assets/badge_github.png new file mode 100644 index 000000000..4440f3767 Binary files /dev/null and b/assets/badge_github.png differ diff --git a/ffMetadataEx/build.gradle.kts b/ffMetadataEx/build.gradle.kts index f46c1967a..5c717e66e 100644 --- a/ffMetadataEx/build.gradle.kts +++ b/ffMetadataEx/build.gradle.kts @@ -4,7 +4,7 @@ plugins { } android { - namespace = "com.dd3boh.ffmpegex" + namespace = "com.dd3boh.ffMetadataEx" compileSdk = 35 defaultConfig { @@ -12,14 +12,14 @@ android { externalNativeBuild { cmake { - cppFlags("") + arguments += listOf("-DCMAKE_SHARED_LINKER_FLAGS=-Wl,--build-id=none") } } } buildTypes { release { - isMinifyEnabled = false + isMinifyEnabled = false // proguard or whatever isn't set up } } diff --git a/ffMetadataEx/src/main/cpp/CMakeLists.txt b/ffMetadataEx/src/main/cpp/CMakeLists.txt index ab614bf1d..2f13a56d1 100644 --- a/ffMetadataEx/src/main/cpp/CMakeLists.txt +++ b/ffMetadataEx/src/main/cpp/CMakeLists.txt @@ -38,14 +38,14 @@ set_target_properties( # Specifies the target library. # Include FFmpeg headers include_directories(${CMAKE_SOURCE_DIR}/ffmpeg-android-maker/output/include/${ANDROID_ABI}) -add_library(ffprobejni SHARED ffprobejni.c) +add_library(ffmetaexjni SHARED ffMetaExJni.c) # Link FFmpeg libraries -target_link_libraries(ffprobejni +target_link_libraries(ffmetaexjni avformat avutil avcodec ) # Set the output directory for the .so file -set_target_properties(ffprobejni PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/src/main/jniLibs/${ANDROID_ABI}) \ No newline at end of file +set_target_properties(ffmetaexjni PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/src/main/jniLibs/${ANDROID_ABI}) \ No newline at end of file diff --git a/ffMetadataEx/src/main/cpp/ffprobejni.c b/ffMetadataEx/src/main/cpp/ffMetaExJni.c similarity index 96% rename from ffMetadataEx/src/main/cpp/ffprobejni.c rename to ffMetadataEx/src/main/cpp/ffMetaExJni.c index bb6a2a376..48c7cc378 100644 --- a/ffMetadataEx/src/main/cpp/ffprobejni.c +++ b/ffMetadataEx/src/main/cpp/ffMetaExJni.c @@ -4,7 +4,7 @@ #include JNIEXPORT jstring JNICALL -Java_wah_mikooomich_ffMetadataEx_FFprobeWrapper_getAudioMetadata(JNIEnv* env, jobject obj, jstring filePath) { +Java_com_dd3boh_ffMetadataEx_FFMpegWrapper_getAudioMetadata(JNIEnv* env, jobject obj, jstring filePath) { const char* file_path = (*env)->GetStringUTFChars(env, filePath, NULL); if (!file_path) { return (*env)->NewStringUTF(env, "Error getting file path"); @@ -63,7 +63,7 @@ Java_wah_mikooomich_ffMetadataEx_FFprobeWrapper_getAudioMetadata(JNIEnv* env, jo JNIEXPORT jstring JNICALL -Java_wah_mikooomich_ffMetadataEx_FFprobeWrapper_getFullAudioMetadata(JNIEnv* env, jobject obj, jstring filePath) { +Java_com_dd3boh_ffMetadataEx_FFMpegWrapper_getFullAudioMetadata(JNIEnv* env, jobject obj, jstring filePath) { const char* file_path = (*env)->GetStringUTFChars(env, filePath, NULL); if (!file_path) { return (*env)->NewStringUTF(env, "Error getting file path"); diff --git a/ffMetadataEx/src/main/java/wah/mikooomich/ffMetadataEx/FFprobeWrapper.kt b/ffMetadataEx/src/main/java/com/dd3boh/ffMetadataEx/FFMpegWrapper.kt similarity index 72% rename from ffMetadataEx/src/main/java/wah/mikooomich/ffMetadataEx/FFprobeWrapper.kt rename to ffMetadataEx/src/main/java/com/dd3boh/ffMetadataEx/FFMpegWrapper.kt index 069c841d8..06cc3a456 100644 --- a/ffMetadataEx/src/main/java/wah/mikooomich/ffMetadataEx/FFprobeWrapper.kt +++ b/ffMetadataEx/src/main/java/com/dd3boh/ffMetadataEx/FFMpegWrapper.kt @@ -1,9 +1,9 @@ -package wah.mikooomich.ffMetadataEx +package com.dd3boh.ffMetadataEx /** * Pain and suffering. */ -class FFprobeWrapper { +class FFMpegWrapper { external fun getAudioMetadata(filePath: String): String external fun getFullAudioMetadata(filePath: String): String