Skip to content

Commit

Permalink
Merge branch 'dev' into release/0.6.x
Browse files Browse the repository at this point in the history
  • Loading branch information
mikooomich committed Sep 8, 2024
2 parents c585c46 + f5f0996 commit 24b505e
Show file tree
Hide file tree
Showing 30 changed files with 356 additions and 254 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)

[<img src="https://github.com/machiav3lli/oandbackupx/blob/034b226cea5c1b30eb4f6a6f313e4dadcbb0ece4/badge_github.png" alt="Get it on GitHub" height="80">](https://github.com/DD3Boh/OuterTune/releases/latest)

[<img src="assets/badge_github.png" alt="Get it on GitHub" height="40">](https://github.com/DD3Boh/OuterTune/releases/latest)
[<img src="assets/IzzyOnDroidButtonGreyBorder.svg" alt="Get it on IzzyOnDroid" height="40">](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.
Expand Down
12 changes: 10 additions & 2 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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
Expand Down
Binary file modified app/prebuilt/ffMetadataEx-release.aar
Binary file not shown.
12 changes: 6 additions & 6 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -140,12 +140,12 @@
</intent-filter>
</service>

<service
android:name=".playback.KeepAlive"
android:exported="false"
android:foregroundServiceType="dataSync"
tools:ignore="ExportedService">
</service>
<receiver android:name="androidx.media3.session.MediaButtonReceiver"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MEDIA_BUTTON" />
</intent-filter>
</receiver>

<service
android:name=".playback.ExoDownloadService"
Expand Down
10 changes: 10 additions & 0 deletions app/src/main/java/com/dd3boh/outertune/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ import androidx.compose.ui.window.DialogProperties
import androidx.core.net.toUri
import androidx.core.util.Consumer
import androidx.core.view.WindowCompat
import androidx.datastore.preferences.core.edit
import androidx.lifecycle.lifecycleScope
import androidx.media3.common.MediaItem
import androidx.media3.common.Player
Expand All @@ -111,6 +112,7 @@ import com.dd3boh.outertune.constants.DefaultOpenTabNewKey
import com.dd3boh.outertune.constants.DynamicThemeKey
import com.dd3boh.outertune.constants.EnabledTabsKey
import com.dd3boh.outertune.constants.ExcludedScanPathsKey
import com.dd3boh.outertune.constants.LastPosKey
import com.dd3boh.outertune.constants.LibraryFilter
import com.dd3boh.outertune.constants.LibraryFilterKey
import com.dd3boh.outertune.constants.LookupYtmArtistsKey
Expand Down Expand Up @@ -211,6 +213,7 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
import java.net.URLDecoder
import javax.inject.Inject
Expand Down Expand Up @@ -266,6 +269,13 @@ class MainActivity : ComponentActivity() {

override fun onDestroy() {
super.onDestroy()
runBlocking {
// save last position
dataStore.edit { settings ->
settings[LastPosKey] = playerConnection!!.player.currentPosition
}
}

if (dataStore.get(StopMusicOnTaskClearKey, false) && playerConnection?.isPlaying?.value == true
&& isFinishing) {
stopService(Intent(this, MusicService::class.java))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down Expand Up @@ -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")
Expand Down Expand Up @@ -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")
Expand Down
19 changes: 15 additions & 4 deletions app/src/main/java/com/dd3boh/outertune/db/DatabaseDao.kt
Original file line number Diff line number Diff line change
Expand Up @@ -544,15 +544,15 @@ interface DatabaseDao {
fun albumWithSongs(albumId: String): Flow<AlbumWithSongs?>

@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<List<Playlist>>

@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<List<Playlist>>

@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<List<Playlist>>

@Transaction
Expand Down Expand Up @@ -595,7 +595,7 @@ interface DatabaseDao {
fun playlistByBrowseId(browseId: String): Flow<Playlist?>

@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
Expand Down Expand Up @@ -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")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
29 changes: 22 additions & 7 deletions app/src/main/java/com/dd3boh/outertune/lyrics/LyricsHelper.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -17,14 +20,30 @@ class LyricsHelper @Inject constructor(
private val lyricsProviders = listOf(YouTubeSubtitleLyricsProvider, LrcLibLyricsProvider, KuGouLyricsProvider, YouTubeLyricsProvider)
private val cache = LruCache<String, List<LyricsResult>>(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) {
Expand Down Expand Up @@ -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,
Expand All @@ -86,8 +103,6 @@ class LyricsHelper @Inject constructor(
mediaMetadata.duration
).onSuccess { lyrics ->
return lyrics
}.onFailure {
reportException(it)
}
}

Expand Down
77 changes: 0 additions & 77 deletions app/src/main/java/com/dd3boh/outertune/playback/KeepAlive.kt

This file was deleted.

30 changes: 10 additions & 20 deletions app/src/main/java/com/dd3boh/outertune/playback/MusicService.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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())
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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))
}
}
}
}
Expand Down Expand Up @@ -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)
Expand Down
Loading

0 comments on commit 24b505e

Please sign in to comment.