all: break off musikr
This commit is contained in:
parent
f33377cf26
commit
e908d0e102
84 changed files with 151 additions and 286 deletions
5
.gitmodules
vendored
5
.gitmodules
vendored
|
@ -1,7 +1,8 @@
|
|||
[submodule "media"]
|
||||
path = media
|
||||
url = https://github.com/OxygenCobalt/media.git
|
||||
[submodule "taglib"]
|
||||
path = ktaglib/src/main/cpp/taglib
|
||||
|
||||
[submodule "musikr/src/main/cpp/taglib"]
|
||||
path = musikr/src/main/cpp/taglib
|
||||
url = https://github.com/taglib/taglib.git
|
||||
tag = v2.0.2
|
||||
|
|
|
@ -2,7 +2,6 @@ plugins {
|
|||
id "com.android.application"
|
||||
id "kotlin-android"
|
||||
id "androidx.navigation.safeargs.kotlin"
|
||||
id "com.diffplug.spotless"
|
||||
id "kotlin-parcelize"
|
||||
id "dagger.hilt.android.plugin"
|
||||
id "kotlin-kapt"
|
||||
|
@ -12,11 +11,9 @@ plugins {
|
|||
|
||||
android {
|
||||
compileSdk 35
|
||||
// NDK is not used in Auxio explicitly (used in the ffmpeg extension), but we need to specify
|
||||
// it here so that binary stripping will work.
|
||||
// TODO: Eventually you might just want to start vendoring the FFMpeg extension so the
|
||||
// NDK use is unified
|
||||
ndkVersion "26.3.11579264"
|
||||
// Auxio implicitly depends on the native modules, explicitly specify it
|
||||
// here so the libraries are still stripped.
|
||||
ndkVersion ndk_version
|
||||
namespace "org.oxycblt.auxio"
|
||||
|
||||
defaultConfig {
|
||||
|
@ -24,8 +21,8 @@ android {
|
|||
versionName "3.6.3"
|
||||
versionCode 53
|
||||
|
||||
minSdk 24
|
||||
targetSdk 35
|
||||
minSdk target_sdk
|
||||
targetSdk target_sdk
|
||||
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
|
@ -80,9 +77,8 @@ dependencies {
|
|||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||
|
||||
def coroutines_version = '1.7.2'
|
||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version"
|
||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-guava:$coroutines_version"
|
||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlin_coroutines_version"
|
||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-guava:$kotlin_coroutines_version"
|
||||
|
||||
// --- SUPPORT ---
|
||||
|
||||
|
@ -125,20 +121,23 @@ dependencies {
|
|||
implementation "androidx.preference:preference-ktx:1.2.1"
|
||||
|
||||
// Database
|
||||
def room_version = '2.6.1'
|
||||
implementation "androidx.room:room-runtime:$room_version"
|
||||
ksp "androidx.room:room-compiler:$room_version"
|
||||
implementation "androidx.room:room-ktx:$room_version"
|
||||
|
||||
// Build
|
||||
coreLibraryDesugaring "com.android.tools:desugar_jdk_libs:2.1.3"
|
||||
|
||||
// --- SECOND PARTY ---
|
||||
|
||||
// Musikr
|
||||
implementation project(":musikr")
|
||||
|
||||
// --- THIRD PARTY ---
|
||||
|
||||
// Exoplayer (Vendored)
|
||||
implementation project(":media-lib-exoplayer")
|
||||
implementation project(":media-lib-decoder-ffmpeg")
|
||||
coreLibraryDesugaring "com.android.tools:desugar_jdk_libs:2.1.3"
|
||||
|
||||
// Taglib
|
||||
implementation project(":ktaglib")
|
||||
|
||||
// Image loading
|
||||
implementation 'io.coil-kt.coil3:coil-core:3.0.2'
|
||||
|
@ -175,15 +174,3 @@ dependencies {
|
|||
androidTestImplementation 'androidx.test.ext:junit:1.2.1'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.6.1'
|
||||
}
|
||||
|
||||
spotless {
|
||||
kotlin {
|
||||
target "src/**/*.kt"
|
||||
ktfmt().dropboxStyle()
|
||||
licenseHeaderFile("NOTICE")
|
||||
}
|
||||
}
|
||||
|
||||
afterEvaluate {
|
||||
preDebugBuild.dependsOn spotlessApply
|
||||
}
|
||||
|
|
|
@ -54,7 +54,6 @@ import org.oxycblt.musikr.Music
|
|||
import org.oxycblt.musikr.MusicParent
|
||||
import org.oxycblt.musikr.Playlist
|
||||
import org.oxycblt.musikr.Song
|
||||
import org.oxycblt.musikr.metadata.AudioProperties
|
||||
import timber.log.Timber as L
|
||||
|
||||
/**
|
||||
|
@ -69,7 +68,6 @@ class DetailViewModel
|
|||
constructor(
|
||||
private val listSettings: ListSettings,
|
||||
private val musicRepository: MusicRepository,
|
||||
private val audioPropertiesFactory: AudioProperties.Factory,
|
||||
private val playbackSettings: PlaybackSettings,
|
||||
detailGeneratorFactory: DetailGenerator.Factory
|
||||
) : ViewModel(), DetailGenerator.Invalidator {
|
||||
|
@ -89,10 +87,6 @@ constructor(
|
|||
val currentSong: StateFlow<Song?>
|
||||
get() = _currentSong
|
||||
|
||||
private val _songAudioProperties = MutableStateFlow<AudioProperties?>(null)
|
||||
/** The [AudioProperties] of the currently shown [Song]. Null if not loaded yet. */
|
||||
val songAudioProperties: StateFlow<AudioProperties?> = _songAudioProperties
|
||||
|
||||
// --- ALBUM ---
|
||||
|
||||
private val _currentAlbum = MutableStateFlow<Album?>(null)
|
||||
|
@ -308,7 +302,7 @@ constructor(
|
|||
}
|
||||
|
||||
/**
|
||||
* Set a new [currentSong] from it's [Music.UID]. [currentSong] and [songAudioProperties] will
|
||||
* Set a new [currentSong] from it's [Music.UID]. [currentSong] will
|
||||
* be updated to align with the new [Song].
|
||||
*
|
||||
* @param uid The UID of the [Song] to load. Must be valid.
|
||||
|
@ -511,17 +505,7 @@ constructor(
|
|||
}
|
||||
|
||||
private fun refreshAudioInfo(song: Song) {
|
||||
L.d("Refreshing audio info")
|
||||
// Clear any previous job in order to avoid stale data from appearing in the UI.
|
||||
currentSongJob?.cancel()
|
||||
_songAudioProperties.value = null
|
||||
currentSongJob =
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
val info = audioPropertiesFactory.extract(song)
|
||||
yield()
|
||||
L.d("Updating audio info to $info")
|
||||
_songAudioProperties.value = info
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private inline fun <T : MusicParent> refreshDetail(
|
||||
|
|
|
@ -41,7 +41,6 @@ import org.oxycblt.auxio.util.collectImmediately
|
|||
import org.oxycblt.auxio.util.concatLocalized
|
||||
import org.oxycblt.musikr.Music
|
||||
import org.oxycblt.musikr.Song
|
||||
import org.oxycblt.musikr.metadata.AudioProperties
|
||||
import org.oxycblt.musikr.tag.Name
|
||||
import timber.log.Timber as L
|
||||
|
||||
|
@ -72,63 +71,63 @@ class SongDetailDialog : ViewBindingMaterialDialogFragment<DialogSongDetailBindi
|
|||
// DetailViewModel handles most initialization from the navigation argument.
|
||||
detailModel.setSong(args.songUid)
|
||||
detailModel.toShow.consume()
|
||||
collectImmediately(detailModel.currentSong, detailModel.songAudioProperties, ::updateSong)
|
||||
collectImmediately(detailModel.currentSong, ::updateSong)
|
||||
}
|
||||
|
||||
private fun updateSong(song: Song?, info: AudioProperties?) {
|
||||
if (song == null) {
|
||||
private fun updateSong(song: Song?) {
|
||||
// if (song == null) {
|
||||
L.d("No song to show, navigating away")
|
||||
findNavController().navigateUp()
|
||||
return
|
||||
}
|
||||
|
||||
if (info != null) {
|
||||
val context = requireContext()
|
||||
detailAdapter.update(
|
||||
buildList {
|
||||
add(SongProperty(R.string.lbl_name, song.zipName(context)))
|
||||
add(SongProperty(R.string.lbl_album, song.album.zipName(context)))
|
||||
add(SongProperty(R.string.lbl_artists, song.artists.zipNames(context)))
|
||||
add(SongProperty(R.string.lbl_genres, song.genres.resolveNames(context)))
|
||||
song.date?.let { add(SongProperty(R.string.lbl_date, it.resolve(context))) }
|
||||
song.track?.let {
|
||||
add(SongProperty(R.string.lbl_track, getString(R.string.fmt_number, it)))
|
||||
}
|
||||
song.disc?.let {
|
||||
val formattedNumber = getString(R.string.fmt_number, it.number)
|
||||
val zipped =
|
||||
if (it.name != null) {
|
||||
getString(R.string.fmt_zipped_names, formattedNumber, it.name)
|
||||
} else {
|
||||
formattedNumber
|
||||
}
|
||||
add(SongProperty(R.string.lbl_disc, zipped))
|
||||
}
|
||||
add(SongProperty(R.string.lbl_path, song.path.resolve(context)))
|
||||
// info.format.resolveName(context)?.let {
|
||||
// add(SongProperty(R.string.lbl_format, it))
|
||||
// }
|
||||
add(
|
||||
SongProperty(
|
||||
R.string.lbl_size, Formatter.formatFileSize(context, song.size)))
|
||||
add(SongProperty(R.string.lbl_duration, song.durationMs.formatDurationMs(true)))
|
||||
info.bitrateKbps?.let {
|
||||
add(SongProperty(R.string.lbl_bitrate, getString(R.string.fmt_bitrate, it)))
|
||||
}
|
||||
info.sampleRateHz?.let {
|
||||
add(
|
||||
SongProperty(
|
||||
R.string.lbl_sample_rate, getString(R.string.fmt_sample_rate, it)))
|
||||
}
|
||||
song.replayGainAdjustment.track?.let {
|
||||
add(SongProperty(R.string.lbl_replaygain_track, it.formatDb(context)))
|
||||
}
|
||||
song.replayGainAdjustment.album?.let {
|
||||
add(SongProperty(R.string.lbl_replaygain_album, it.formatDb(context)))
|
||||
}
|
||||
},
|
||||
UpdateInstructions.Replace(0))
|
||||
}
|
||||
// }
|
||||
//
|
||||
// if (info != null) {
|
||||
// val context = requireContext()
|
||||
// detailAdapter.update(
|
||||
// buildList {
|
||||
// add(SongProperty(R.string.lbl_name, song.zipName(context)))
|
||||
// add(SongProperty(R.string.lbl_album, song.album.zipName(context)))
|
||||
// add(SongProperty(R.string.lbl_artists, song.artists.zipNames(context)))
|
||||
// add(SongProperty(R.string.lbl_genres, song.genres.resolveNames(context)))
|
||||
// song.date?.let { add(SongProperty(R.string.lbl_date, it.resolve(context))) }
|
||||
// song.track?.let {
|
||||
// add(SongProperty(R.string.lbl_track, getString(R.string.fmt_number, it)))
|
||||
// }
|
||||
// song.disc?.let {
|
||||
// val formattedNumber = getString(R.string.fmt_number, it.number)
|
||||
// val zipped =
|
||||
// if (it.name != null) {
|
||||
// getString(R.string.fmt_zipped_names, formattedNumber, it.name)
|
||||
// } else {
|
||||
// formattedNumber
|
||||
// }
|
||||
// add(SongProperty(R.string.lbl_disc, zipped))
|
||||
// }
|
||||
// add(SongProperty(R.string.lbl_path, song.path.resolve(context)))
|
||||
// // info.format.resolveName(context)?.let {
|
||||
// // add(SongProperty(R.string.lbl_format, it))
|
||||
// // }
|
||||
// add(
|
||||
// SongProperty(
|
||||
// R.string.lbl_size, Formatter.formatFileSize(context, song.size)))
|
||||
// add(SongProperty(R.string.lbl_duration, song.durationMs.formatDurationMs(true)))
|
||||
// info.bitrateKbps?.let {
|
||||
// add(SongProperty(R.string.lbl_bitrate, getString(R.string.fmt_bitrate, it)))
|
||||
// }
|
||||
// info.sampleRateHz?.let {
|
||||
// add(
|
||||
// SongProperty(
|
||||
// R.string.lbl_sample_rate, getString(R.string.fmt_sample_rate, it)))
|
||||
// }
|
||||
// song.replayGainAdjustment.track?.let {
|
||||
// add(SongProperty(R.string.lbl_replaygain_track, it.formatDb(context)))
|
||||
// }
|
||||
// song.replayGainAdjustment.album?.let {
|
||||
// add(SongProperty(R.string.lbl_replaygain_album, it.formatDb(context)))
|
||||
// }
|
||||
// },
|
||||
// UpdateInstructions.Replace(0))
|
||||
// }
|
||||
}
|
||||
|
||||
private fun <T : Music> T.zipName(context: Context): String {
|
||||
|
|
|
@ -1,105 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2023 Auxio Project
|
||||
* AudioProperties.kt is part of Auxio.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.oxycblt.musikr.metadata
|
||||
|
||||
import android.content.Context
|
||||
import android.media.MediaExtractor
|
||||
import android.media.MediaFormat
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import javax.inject.Inject
|
||||
import org.oxycblt.musikr.Song
|
||||
import timber.log.Timber as L
|
||||
|
||||
/**
|
||||
* The properties of a [Song]'s file.
|
||||
*
|
||||
* @param bitrateKbps The bit rate, in kilobytes-per-second. Null if it could not be parsed.
|
||||
* @param sampleRateHz The sample rate, in hertz.
|
||||
* @author Alexander Capehart (OxygenCobalt)
|
||||
*/
|
||||
data class AudioProperties(val bitrateKbps: Int?, val sampleRateHz: Int?) {
|
||||
/** Implements the process of extracting [AudioProperties] from a given [Song]. */
|
||||
interface Factory {
|
||||
/**
|
||||
* Extract the [AudioProperties] of a given [Song].
|
||||
*
|
||||
* @param song The [Song] to read.
|
||||
* @return The [AudioProperties] of the [Song], if possible to obtain.
|
||||
*/
|
||||
suspend fun extract(song: Song): AudioProperties
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A framework-backed implementation of [AudioProperties.Factory].
|
||||
*
|
||||
* @param context [Context] required to read audio files.
|
||||
*/
|
||||
class AudioPropertiesFactoryImpl
|
||||
@Inject
|
||||
constructor(@ApplicationContext private val context: Context) : AudioProperties.Factory {
|
||||
|
||||
override suspend fun extract(song: Song): AudioProperties {
|
||||
// While we would use ExoPlayer to extract this information, it doesn't support
|
||||
// common data like bit rate in progressive data sources due to there being no
|
||||
// demand. Thus, we are stuck with the inferior OS-provided MediaExtractor.
|
||||
val extractor = MediaExtractor()
|
||||
|
||||
try {
|
||||
extractor.setDataSource(context, song.uri, emptyMap())
|
||||
} catch (e: Exception) {
|
||||
// Can feasibly fail with invalid file formats. Note that this isn't considered
|
||||
// an error condition in the UI, as there is still plenty of other song information
|
||||
// that we can show.
|
||||
L.w("Unable to extract song attributes.")
|
||||
L.w(e.stackTraceToString())
|
||||
return AudioProperties(null, null)
|
||||
}
|
||||
|
||||
// Get the first track from the extractor (This is basically always the only
|
||||
// track we need to analyze).
|
||||
val format = extractor.getTrackFormat(0)
|
||||
|
||||
// Accessing fields can throw an exception if the fields are not present, and
|
||||
// the new method for using default values is not available on lower API levels.
|
||||
// So, we are forced to handle the exception and map it to a saner null value.
|
||||
val bitrate =
|
||||
try {
|
||||
// Convert bytes-per-second to kilobytes-per-second.
|
||||
format.getInteger(MediaFormat.KEY_BIT_RATE) / 1000
|
||||
} catch (e: NullPointerException) {
|
||||
L.d("Unable to extract bit rate field")
|
||||
null
|
||||
}
|
||||
|
||||
val sampleRate =
|
||||
try {
|
||||
format.getInteger(MediaFormat.KEY_SAMPLE_RATE)
|
||||
} catch (e: NullPointerException) {
|
||||
L.e("Unable to extract sample rate field")
|
||||
null
|
||||
}
|
||||
|
||||
extractor.release()
|
||||
|
||||
L.d("Finished extracting audio properties")
|
||||
|
||||
return AudioProperties(bitrate, sampleRate)
|
||||
}
|
||||
}
|
|
@ -1,31 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2023 Auxio Project
|
||||
* MetadataModule.kt is part of Auxio.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.oxycblt.musikr.metadata
|
||||
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
interface MetadataModule {
|
||||
@Binds
|
||||
fun audioPropertiesFactory(interpreter: AudioPropertiesFactoryImpl): AudioProperties.Factory
|
||||
}
|
31
build.gradle
31
build.gradle
|
@ -1,8 +1,16 @@
|
|||
buildscript {
|
||||
ext {
|
||||
agp_version = '8.7.3'
|
||||
kotlin_version = '2.0.21'
|
||||
kotlin_coroutines_version = '1.7.2'
|
||||
navigation_version = "2.8.3"
|
||||
hilt_version = '2.51.1'
|
||||
room_version = '2.6.1'
|
||||
|
||||
min_sdk = 24
|
||||
target_sdk = 35
|
||||
ndk_version = "27.2.12479018"
|
||||
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
@ -12,14 +20,27 @@ buildscript {
|
|||
}
|
||||
|
||||
plugins {
|
||||
id "com.android.application" version '8.7.2' apply false
|
||||
// Android studio doesn't understand this syntax
|
||||
//noinspection GradlePluginVersion
|
||||
id "com.android.application" version "$agp_version" apply false
|
||||
id "androidx.navigation.safeargs.kotlin" version "$navigation_version" apply false
|
||||
//noinspection GradlePluginVersion
|
||||
id 'com.android.library' version "$agp_version" apply false
|
||||
id "org.jetbrains.kotlin.android" version "$kotlin_version" apply false
|
||||
id "com.google.devtools.ksp" version '2.0.21-1.0.25' apply false
|
||||
id "com.diffplug.spotless" version "6.25.0" apply false
|
||||
id 'com.android.library' version '8.7.2' apply false
|
||||
id "com.diffplug.spotless" version "6.25.0" apply true
|
||||
}
|
||||
|
||||
tasks.register('clean', Delete) {
|
||||
delete rootProject.buildDir
|
||||
spotless {
|
||||
kotlin {
|
||||
target "*/src/**/*.kt"
|
||||
ktfmt().dropboxStyle()
|
||||
licenseHeaderFile("NOTICE")
|
||||
}
|
||||
|
||||
cpp {
|
||||
target "*/src/**/*.cpp"
|
||||
clangFormat()
|
||||
licenseHeaderFile("NOTICE")
|
||||
}
|
||||
}
|
0
ktaglib/.gitignore → musikr/.gitignore
vendored
0
ktaglib/.gitignore → musikr/.gitignore
vendored
|
@ -3,15 +3,19 @@ import org.apache.tools.ant.taskdefs.condition.Os
|
|||
plugins {
|
||||
id 'com.android.library'
|
||||
id 'org.jetbrains.kotlin.android'
|
||||
id "com.google.devtools.ksp"
|
||||
id "com.diffplug.spotless"
|
||||
id "kotlin-parcelize"
|
||||
}
|
||||
|
||||
android {
|
||||
namespace 'org.oxycblt.ktaglib'
|
||||
compileSdk 34
|
||||
ndkVersion "26.3.11579264"
|
||||
namespace 'org.oxycblt.musikr'
|
||||
compileSdk target_sdk
|
||||
ndkVersion "$ndk_version"
|
||||
|
||||
defaultConfig {
|
||||
minSdk 24
|
||||
minSdk min_sdk
|
||||
targetSdk target_sdk
|
||||
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
consumerProguardFiles "consumer-rules.pro"
|
||||
|
@ -28,19 +32,45 @@ android {
|
|||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
path "src/main/cpp/CMakeLists.txt"
|
||||
version "3.22.1"
|
||||
}
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_11
|
||||
targetCompatibility JavaVersion.VERSION_11
|
||||
coreLibraryDesugaringEnabled true
|
||||
sourceCompatibility JavaVersion.VERSION_17
|
||||
targetCompatibility JavaVersion.VERSION_17
|
||||
}
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = '11'
|
||||
jvmTarget = "17"
|
||||
freeCompilerArgs += "-Xjvm-default=all"
|
||||
}
|
||||
|
||||
buildFeatures {
|
||||
buildConfig true
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
// Kotlin
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
|
||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlin_coroutines_version"
|
||||
|
||||
// AndroidX
|
||||
implementation "androidx.core:core-ktx:1.15.0"
|
||||
|
||||
// Database
|
||||
implementation "androidx.room:room-runtime:$room_version"
|
||||
ksp "androidx.room:room-compiler:$room_version"
|
||||
implementation "androidx.room:room-ktx:$room_version"
|
||||
|
||||
// Build
|
||||
coreLibraryDesugaring "com.android.tools:desugar_jdk_libs:2.1.3"
|
||||
}
|
||||
|
||||
task assembleTaglib(type: Exec) {
|
0
musikr/consumer-rules.pro
Normal file
0
musikr/consumer-rules.pro
Normal file
|
@ -80,7 +80,7 @@ public:
|
|||
/*!
|
||||
* Reset the end-of-stream and error flags on the stream.
|
||||
*/
|
||||
void clear();
|
||||
void clear() override;
|
||||
|
||||
/*!
|
||||
* Returns the current offset within the stream.
|
|
@ -27,14 +27,16 @@ build_for_arch() {
|
|||
cd $TAGLIB_SRC_DIR
|
||||
cmake -B $DST_DIR -DANDROID_NDK_PATH=${NDK_PATH} -DCMAKE_TOOLCHAIN_FILE=${NDK_TOOLCHAIN} \
|
||||
-DANDROID_ABI=$ARCH -DBUILD_SHARED_LIBS=OFF -DVISIBILITY_HIDDEN=ON -DBUILD_TESTING=OFF \
|
||||
-DBUILD_EXAMPLES=OFF -DBUILD_BINDINGS=OFF -DWITH_ZLIB=OFF -DCMAKE_BUILD_TYPE=Release
|
||||
cmake --build $DST_DIR --config Release
|
||||
-DBUILD_EXAMPLES=OFF -DBUILD_BINDINGS=OFF -DWITH_ZLIB=OFF -DCMAKE_BUILD_TYPE=Release \
|
||||
-DCMAKE_CXX_FLAGS="-fPIC"
|
||||
cmake --build $DST_DIR --config Release -j$(nproc)
|
||||
cd $WORKING_DIR
|
||||
|
||||
cmake --install $DST_DIR --config Release --prefix $PKG_DIR --strip
|
||||
}
|
||||
|
||||
build_for_arch $X86_ARCH
|
||||
build_for_arch $X86_64_ARCH
|
||||
build_for_arch $ARMV7_ARCH
|
||||
build_for_arch $ARMV8_ARCH
|
||||
build_for_arch $X86_ARCH&
|
||||
build_for_arch $X86_64_ARCH&
|
||||
build_for_arch $ARMV7_ARCH&
|
||||
build_for_arch $ARMV8_ARCH&
|
||||
wait
|
|
@ -22,7 +22,6 @@ import android.content.ContentUris
|
|||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import android.provider.DocumentsContract
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import java.io.File
|
||||
import org.oxycblt.musikr.fs.Components
|
||||
import org.oxycblt.musikr.fs.Path
|
||||
|
@ -78,7 +77,7 @@ interface DocumentPathFactory {
|
|||
}
|
||||
|
||||
private class DocumentPathFactoryImpl(
|
||||
@ApplicationContext private val context: Context,
|
||||
private val context: Context,
|
||||
private val volumeManager: VolumeManager,
|
||||
private val mediaStorePathInterpreterFactory: MediaStorePathInterpreter.Factory
|
||||
) : DocumentPathFactory {
|
|
@ -23,7 +23,6 @@ import android.os.Build
|
|||
import android.provider.MediaStore
|
||||
import org.oxycblt.musikr.fs.Components
|
||||
import org.oxycblt.musikr.fs.Path
|
||||
import timber.log.Timber as L
|
||||
|
||||
/**
|
||||
* Wrapper around a [Cursor] that interprets path information on a per-API/manufacturer basis.
|
||||
|
@ -114,8 +113,6 @@ private constructor(private val cursor: Cursor, volumeManager: VolumeManager) :
|
|||
}
|
||||
}
|
||||
|
||||
L.e("Could not find volume for $data [tried: ${volumes.map { it.components }}]")
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
|
@ -183,8 +180,6 @@ private constructor(private val cursor: Cursor, volumeManager: VolumeManager) :
|
|||
val displayName = cursor.getString(displayNameIndex)
|
||||
val volume = volumes.find { it.mediaStoreName == volumeName }
|
||||
if (volume == null) {
|
||||
L.e(
|
||||
"Could not find volume for $volumeName:$relativePath/$displayName [tried: ${volumes.map { it.mediaStoreName }}]")
|
||||
return null
|
||||
}
|
||||
val components = Components.parseUnix(relativePath).child(displayName)
|
|
@ -24,7 +24,6 @@ import org.oxycblt.musikr.tag.interpret.PreArtist
|
|||
import org.oxycblt.musikr.tag.interpret.PreGenre
|
||||
import org.oxycblt.musikr.tag.interpret.PreSong
|
||||
import org.oxycblt.musikr.util.unlikelyToBeNull
|
||||
import timber.log.Timber as L
|
||||
|
||||
data class MusicGraph(
|
||||
val songVertex: List<SongVertex>,
|
||||
|
@ -52,7 +51,6 @@ private class MusicGraphBuilderImpl : MusicGraph.Builder {
|
|||
override fun add(preSong: PreSong) {
|
||||
val uid = preSong.computeUid()
|
||||
if (songVertices.containsKey(uid)) {
|
||||
L.d("Song ${preSong.path} already in graph at ${songVertices[uid]?.preSong?.path}")
|
||||
return
|
||||
}
|
||||
|
|
@ -26,7 +26,6 @@ import org.oxycblt.musikr.fs.Path
|
|||
import org.oxycblt.musikr.fs.path.DocumentPathFactory
|
||||
import org.oxycblt.musikr.fs.query.contentResolverSafe
|
||||
import org.oxycblt.musikr.playlist.m3u.M3U
|
||||
import timber.log.Timber as L
|
||||
|
||||
/**
|
||||
* Generic playlist file importing abstraction.
|
||||
|
@ -111,7 +110,6 @@ class ExternalPlaylistManagerImpl(
|
|||
return ImportedPlaylist(newName, imported.paths)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
L.e("Failed to import playlist: $e")
|
||||
null
|
||||
}
|
||||
}
|
||||
|
@ -125,17 +123,12 @@ class ExternalPlaylistManagerImpl(
|
|||
filePath.directory
|
||||
}
|
||||
return try {
|
||||
val outputStream = context.contentResolverSafe.openOutputStream(uri)
|
||||
if (outputStream == null) {
|
||||
L.e("Failed to export playlist: Could not open output stream")
|
||||
return false
|
||||
}
|
||||
val outputStream = context.contentResolverSafe.openOutputStream(uri) ?: return false
|
||||
outputStream.use {
|
||||
m3u.write(playlist, it, workingDirectory, config)
|
||||
true
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
L.e("Failed to export playlist: $e")
|
||||
false
|
||||
}
|
||||
}
|
|
@ -19,7 +19,6 @@
|
|||
package org.oxycblt.musikr.playlist.m3u
|
||||
|
||||
import android.content.Context
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import java.io.BufferedReader
|
||||
import java.io.BufferedWriter
|
||||
import java.io.InputStream
|
||||
|
@ -36,7 +35,6 @@ import org.oxycblt.musikr.playlist.PossiblePaths
|
|||
import org.oxycblt.musikr.tag.Name
|
||||
import org.oxycblt.musikr.tag.util.correctWhitespace
|
||||
import org.oxycblt.musikr.util.unlikelyToBeNull
|
||||
import timber.log.Timber as L
|
||||
|
||||
/**
|
||||
* Minimal M3U file format implementation.
|
||||
|
@ -75,12 +73,11 @@ interface M3U {
|
|||
/** The mime type used for M3U files by the android system. */
|
||||
const val MIME_TYPE = "audio/x-mpegurl"
|
||||
|
||||
fun from(context: Context): M3U = M3UImpl(context, VolumeManager.from(context))
|
||||
fun from(context: Context): M3U = M3UImpl(VolumeManager.from(context))
|
||||
}
|
||||
}
|
||||
|
||||
private class M3UImpl(
|
||||
@ApplicationContext private val context: Context,
|
||||
private val volumeManager: VolumeManager
|
||||
) : M3U {
|
||||
override fun read(stream: InputStream, workingDirectory: Path): ImportedPlaylist? {
|
||||
|
@ -117,15 +114,10 @@ private class M3UImpl(
|
|||
}
|
||||
}
|
||||
|
||||
if (path == null) {
|
||||
L.e("Expected a path, instead got an EOF")
|
||||
break@consumeFile
|
||||
}
|
||||
|
||||
// There is basically no formal specification of file paths in M3U, and it differs
|
||||
// based on the programs that generated it. I more or less have to consider any possible
|
||||
// interpretation as valid.
|
||||
val interpretations = interpretPath(path)
|
||||
val interpretations = interpretPath(unlikelyToBeNull(path))
|
||||
val possibilities =
|
||||
interpretations.flatMap { expandInterpretation(it, workingDirectory, volumes) }
|
||||
|
|
@ -21,7 +21,7 @@ package org.oxycblt.musikr.util
|
|||
import java.security.MessageDigest
|
||||
import java.util.UUID
|
||||
import kotlin.reflect.KClass
|
||||
import org.oxycblt.auxio.BuildConfig
|
||||
import org.oxycblt.musikr.BuildConfig
|
||||
import org.oxycblt.musikr.tag.Date
|
||||
|
||||
/**
|
|
@ -20,4 +20,4 @@ apply from: file("media/core_settings.gradle")
|
|||
|
||||
rootProject.name = "Auxio"
|
||||
include ':app'
|
||||
include ':ktaglib'
|
||||
include ':musikr'
|
||||
|
|
Loading…
Reference in a new issue