diff --git a/app/build.gradle b/app/build.gradle index 2317ea8..0b51ca8 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -13,7 +13,8 @@ apply plugin: 'com.google.android.gms.oss-licenses-plugin' String DEFAULT_MANIFEST = "215:https://storage.googleapis.com/music-iq-db/updatable_ytm_db/20220612-030100/manifest.json" -def tagName = '2.0.1' +def tagName = '2.0.2' +def version = 202 def getKeystoreProperties() { def properties = new Properties() @@ -40,7 +41,7 @@ android { applicationId "com.kieronquinn.app.ambientmusicmod" minSdk 28 targetSdk 32 - versionCode 201 + versionCode version versionName tagName buildConfigField "String", "DEFAULT_MANIFEST", "\"" + DEFAULT_MANIFEST + "\"" @@ -69,6 +70,8 @@ android { proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } debug { + minifyEnabled false + signingConfig signingConfigs.release manifestPlaceholders = [usesCleartextTraffic:"true"] } } diff --git a/app/release/output-metadata.json b/app/release/output-metadata.json index ef61408..64698aa 100644 --- a/app/release/output-metadata.json +++ b/app/release/output-metadata.json @@ -11,8 +11,8 @@ "type": "SINGLE", "filters": [], "attributes": [], - "versionCode": 200, - "versionName": "2.0", + "versionCode": 201, + "versionName": "2.0.1", "outputFile": "app-release.apk" } ], diff --git a/app/src/main/java/com/kieronquinn/app/ambientmusicmod/components/navigation/Navigation.kt b/app/src/main/java/com/kieronquinn/app/ambientmusicmod/components/navigation/Navigation.kt index 4988a52..cb93a80 100644 --- a/app/src/main/java/com/kieronquinn/app/ambientmusicmod/components/navigation/Navigation.kt +++ b/app/src/main/java/com/kieronquinn/app/ambientmusicmod/components/navigation/Navigation.kt @@ -134,6 +134,9 @@ suspend fun NavHostFragment.setupWithNavigation(navigation: BaseNavigation) { } ProcessPhoenix.triggerRebirth(requireContext(), mainIntent) } + is NavigationEvent.ContextInjectedMethod -> { + it.method.invoke(requireContext()) + } } } } diff --git a/app/src/main/java/com/kieronquinn/app/ambientmusicmod/repositories/DeviceConfigRepository.kt b/app/src/main/java/com/kieronquinn/app/ambientmusicmod/repositories/DeviceConfigRepository.kt index c9618c4..3778b7b 100644 --- a/app/src/main/java/com/kieronquinn/app/ambientmusicmod/repositories/DeviceConfigRepository.kt +++ b/app/src/main/java/com/kieronquinn/app/ambientmusicmod/repositories/DeviceConfigRepository.kt @@ -23,6 +23,7 @@ interface DeviceConfigRepository { val recordingGain: AmbientMusicModSetting val showAlbumArt: AmbientMusicModSetting val enableLogging: AmbientMusicModSetting + val alternativeEncoding: AmbientMusicModSetting fun getAllDeviceConfigValues(): List> suspend fun sendValues() @@ -79,6 +80,10 @@ class DeviceConfigRepositoryImpl( private const val ENABLE_LOGGING = "NowPlaying__enable_logging" private const val ENABLE_LOGGING_DEFAULT = false + //Custom, enables BIG_ENDIAN encoding to fix crackle on some devices + private const val ALTERNATIVE_ENCODING = "NowPlaying__alternative_audio_encoding" + private const val ALTERNATIVE_ENCODING_DEFAULT = false + private val KEY_MAP = mapOf( CACHE_SHARD_ENABLED to DeviceConfigRepositoryImpl::cacheShardEnabled, INDEX_MANIFEST to DeviceConfigRepositoryImpl::indexManifest, @@ -90,7 +95,8 @@ class DeviceConfigRepositoryImpl( SUPERPACKS_REQUIRE_WIFI to DeviceConfigRepositoryImpl::superpacksRequireWiFi, RECORDING_GAIN to DeviceConfigRepositoryImpl::recordingGain, SHOW_ALBUM_ART to DeviceConfigRepositoryImpl::showAlbumArt, - ENABLE_LOGGING to DeviceConfigRepositoryImpl::enableLogging + ENABLE_LOGGING to DeviceConfigRepositoryImpl::enableLogging, + ALTERNATIVE_ENCODING to DeviceConfigRepositoryImpl::alternativeEncoding ) /** @@ -166,6 +172,10 @@ class DeviceConfigRepositoryImpl( ENABLE_LOGGING, ENABLE_LOGGING_DEFAULT, onChange ) + override val alternativeEncoding = boolean( + ALTERNATIVE_ENCODING, ALTERNATIVE_ENCODING_DEFAULT, onChange + ) + override fun getAllDeviceConfigValues(): List> { val staticValues = STATIC_DEVICE_CONFIG.map { Pair(it.key, it.value) diff --git a/app/src/main/java/com/kieronquinn/app/ambientmusicmod/repositories/RecognitionRepository.kt b/app/src/main/java/com/kieronquinn/app/ambientmusicmod/repositories/RecognitionRepository.kt index 98aad9c..3887206 100644 --- a/app/src/main/java/com/kieronquinn/app/ambientmusicmod/repositories/RecognitionRepository.kt +++ b/app/src/main/java/com/kieronquinn/app/ambientmusicmod/repositories/RecognitionRepository.kt @@ -136,6 +136,7 @@ class RecognitionRepositoryImpl( } val metadata = RecognitionCallbackMetadata(source, includeAudio) val service = getService() ?: run { + hasStarted = true trySend(RecognitionState.Error(ErrorReason.API_INCOMPATIBLE)) close() return@callbackFlow diff --git a/app/src/main/java/com/kieronquinn/app/ambientmusicmod/service/AmbientMusicModForegroundService.kt b/app/src/main/java/com/kieronquinn/app/ambientmusicmod/service/AmbientMusicModForegroundService.kt index f4c6bda..23d1d23 100644 --- a/app/src/main/java/com/kieronquinn/app/ambientmusicmod/service/AmbientMusicModForegroundService.kt +++ b/app/src/main/java/com/kieronquinn/app/ambientmusicmod/service/AmbientMusicModForegroundService.kt @@ -440,14 +440,14 @@ class AmbientMusicModForegroundService: LifecycleService() { val launchIntent = Intent(this, MainActivity::class.java) val title = when(error.errorReason) { ErrorReason.SHIZUKU_ERROR -> R.string.notification_error_shizuku_title - ErrorReason.TIMEOUT -> R.string.notification_error_timeout_title + ErrorReason.TIMEOUT -> return ErrorReason.API_INCOMPATIBLE -> R.string.notification_error_api_title ErrorReason.NEEDS_ROOT -> R.string.notification_error_needs_root_title ErrorReason.DISABLED -> return } val subtitle = when(error.errorReason){ ErrorReason.SHIZUKU_ERROR -> R.string.notification_error_shizuku_subtitle - ErrorReason.TIMEOUT -> R.string.notification_error_timeout_subtitle + ErrorReason.TIMEOUT -> return ErrorReason.API_INCOMPATIBLE -> R.string.notification_error_api_subtitle ErrorReason.NEEDS_ROOT -> R.string.notification_error_needs_root_subtitle ErrorReason.DISABLED -> return diff --git a/app/src/main/java/com/kieronquinn/app/ambientmusicmod/service/ShizukuService.kt b/app/src/main/java/com/kieronquinn/app/ambientmusicmod/service/ShizukuService.kt index 370c079..65f1e5e 100644 --- a/app/src/main/java/com/kieronquinn/app/ambientmusicmod/service/ShizukuService.kt +++ b/app/src/main/java/com/kieronquinn/app/ambientmusicmod/service/ShizukuService.kt @@ -113,9 +113,12 @@ class ShizukuService: IShellProxy.Stub() { override fun AudioRecord_read(audioData: ByteArray, offsetInShorts: Int, sizeInShorts: Int): Int { val outShorts = ShortArray(sizeInShorts) - return audioRecord.read(outShorts, offsetInShorts, sizeInShorts).also { + val result = audioRecord.read(outShorts, offsetInShorts, sizeInShorts).also { outShorts.toByteArray().copyInto(audioData) } + val bufferSize = AudioRecord_getBufferSizeInFrames() + if(result <= 0) return result //Error or empty, don't process + return bufferSize } override fun AudioRecord_startRecording() { diff --git a/app/src/main/java/com/kieronquinn/app/ambientmusicmod/ui/screens/recognition/RecognitionFragment.kt b/app/src/main/java/com/kieronquinn/app/ambientmusicmod/ui/screens/recognition/RecognitionFragment.kt index 53dec11..eebbba0 100644 --- a/app/src/main/java/com/kieronquinn/app/ambientmusicmod/ui/screens/recognition/RecognitionFragment.kt +++ b/app/src/main/java/com/kieronquinn/app/ambientmusicmod/ui/screens/recognition/RecognitionFragment.kt @@ -477,6 +477,7 @@ class RecognitionFragment: BoundDialogFragment(Fragm } private fun runSuccess(state: State.Success) = with(binding.recognitionSuccess) { + root.bringToFront() binding.recognitionCircle.root.setImageResource(R.drawable.ic_recognition_circle_success) viewLifecycleOwner.lifecycleScope.launchWhenResumed { recognitionSuccessPlayback.onClicked().collect { @@ -489,6 +490,7 @@ class RecognitionFragment: BoundDialogFragment(Fragm } private fun runFailed(state: State.Failed) = with(binding.recognitionFailed) { + root.bringToFront() binding.recognitionCircle.root.setImageResource(R.drawable.ic_recognition_circle_failed) prepFailed(state.result) viewLifecycleOwner.lifecycleScope.launchWhenResumed { @@ -509,6 +511,7 @@ class RecognitionFragment: BoundDialogFragment(Fragm } private fun runError(state: State.Error) = with(binding.recognitionFailed) { + root.bringToFront() binding.recognitionCircle.root.setImageResource(R.drawable.ic_recognition_circle_failed) prepError(state.result) viewLifecycleOwner.lifecycleScope.launchWhenResumed { @@ -522,6 +525,7 @@ class RecognitionFragment: BoundDialogFragment(Fragm } private fun runPlayback(state: State.Playback) = with(binding.recognitionPlayback) { + root.bringToFront() //Apply a gain just to the UI to make the waveform useful recognitionPlaybackWaveform.setRawData(state.audio.clone().applyGain(4f).toByteArray()) recognitionPlaybackPlay.updatePlayingState( diff --git a/app/src/main/java/com/kieronquinn/app/ambientmusicmod/ui/screens/settings/advanced/SettingsAdvancedFragment.kt b/app/src/main/java/com/kieronquinn/app/ambientmusicmod/ui/screens/settings/advanced/SettingsAdvancedFragment.kt index 5dd7144..c85857e 100644 --- a/app/src/main/java/com/kieronquinn/app/ambientmusicmod/ui/screens/settings/advanced/SettingsAdvancedFragment.kt +++ b/app/src/main/java/com/kieronquinn/app/ambientmusicmod/ui/screens/settings/advanced/SettingsAdvancedFragment.kt @@ -58,6 +58,13 @@ class SettingsAdvancedFragment: BaseSettingsFragment(), BackAvailable { R.drawable.ic_settings_advanced_gain, viewModel::onGainClicked ), + GenericSettingsItem.SwitchSetting( + state.alternativeEncoding, + getString(R.string.settings_advanced_alternative_encoding), + getString(R.string.settings_advanced_alternative_encoding_content), + R.drawable.ic_settings_advanced_alternative_encoding, + onChanged = viewModel::onAlternativeEncodingChanged + ), GenericSettingsItem.SwitchSetting( state.runOnSmallCores, getString(R.string.settings_advanced_small_cores), diff --git a/app/src/main/java/com/kieronquinn/app/ambientmusicmod/ui/screens/settings/advanced/SettingsAdvancedViewModel.kt b/app/src/main/java/com/kieronquinn/app/ambientmusicmod/ui/screens/settings/advanced/SettingsAdvancedViewModel.kt index f12b648..eb3894d 100644 --- a/app/src/main/java/com/kieronquinn/app/ambientmusicmod/ui/screens/settings/advanced/SettingsAdvancedViewModel.kt +++ b/app/src/main/java/com/kieronquinn/app/ambientmusicmod/ui/screens/settings/advanced/SettingsAdvancedViewModel.kt @@ -19,6 +19,7 @@ abstract class SettingsAdvancedViewModel: ViewModel() { abstract val state: StateFlow abstract fun onGainClicked() + abstract fun onAlternativeEncodingChanged(enabled: Boolean) abstract fun onRunOnLittleCoresChanged(enabled: Boolean) abstract fun onNnfpv3Changed(enabled: Boolean) abstract fun onSuperpacksRequireWiFiChanged(enabled: Boolean) @@ -29,6 +30,7 @@ abstract class SettingsAdvancedViewModel: ViewModel() { sealed class State { object Loading: State() data class Loaded( + val alternativeEncoding: Boolean, val runOnSmallCores: Boolean, val nnfpv3: Boolean, val superpacksRequireWifi: Boolean, @@ -50,15 +52,23 @@ class SettingsAdvancedViewModelImpl( private val superpacksRequireWifi = deviceConfigRepository.superpacksRequireWiFi private val superpacksRequireCharging = deviceConfigRepository.superpacksRequireCharging private val enableLogging = deviceConfigRepository.enableLogging + private val alternativeEncoding = deviceConfigRepository.alternativeEncoding + + private val superpacksConfig = combine( + superpacksRequireWifi.asFlow(), + superpacksRequireCharging.asFlow() + ) { wifi, charging -> + Pair(wifi, charging) + } override val state = combine( runOnSmallCores.asFlow(), nnfpv3.asFlow(), - superpacksRequireWifi.asFlow(), - superpacksRequireCharging.asFlow(), - enableLogging.asFlow() - ) { small, nnfpv3, requireWifi, requireCharging, logging -> - State.Loaded(small, nnfpv3, requireWifi, requireCharging, logging) + superpacksConfig, + enableLogging.asFlow(), + alternativeEncoding.asFlow(), + ) { small, nnfpv3, superpacks, logging, alternative -> + State.Loaded(alternative, small, nnfpv3, superpacks.first, superpacks.second, logging) }.stateIn(viewModelScope, SharingStarted.Eagerly, State.Loading) override fun onGainClicked() { @@ -97,6 +107,12 @@ class SettingsAdvancedViewModelImpl( } } + override fun onAlternativeEncodingChanged(enabled: Boolean) { + viewModelScope.launch { + alternativeEncoding.set(enabled) + } + } + override fun onClearAlbumArtClicked(context: Context) { viewModelScope.launch { serviceRepository.getService()?.clearAlbumArtCache() diff --git a/app/src/main/java/com/kieronquinn/app/ambientmusicmod/utils/extensions/Extensions+Audio.kt b/app/src/main/java/com/kieronquinn/app/ambientmusicmod/utils/extensions/Extensions+Audio.kt index ba12255..e03530f 100644 --- a/app/src/main/java/com/kieronquinn/app/ambientmusicmod/utils/extensions/Extensions+Audio.kt +++ b/app/src/main/java/com/kieronquinn/app/ambientmusicmod/utils/extensions/Extensions+Audio.kt @@ -1,7 +1,6 @@ package com.kieronquinn.app.ambientmusicmod.utils.extensions import java.nio.ByteBuffer -import java.nio.ByteOrder.LITTLE_ENDIAN fun ShortArray.toByteArray(): ByteArray { return ByteBuffer.allocate(size * 2).apply { @@ -9,13 +8,6 @@ fun ShortArray.toByteArray(): ByteArray { }.array() } -fun ByteArray.toShortArray(): ShortArray { - return ShortArray(this.size / 2).apply { - ByteBuffer.wrap(this@toShortArray) - .order(LITTLE_ENDIAN).asShortBuffer()[this] - } -} - fun ShortArray.applyGain(gain: Float): ShortArray { if (isNotEmpty()) { for (i in indices) { diff --git a/app/src/main/java/com/kieronquinn/app/ambientmusicmod/utils/extensions/Extensions+Flow.kt b/app/src/main/java/com/kieronquinn/app/ambientmusicmod/utils/extensions/Extensions+Flow.kt index ee11ce6..3d8e509 100644 --- a/app/src/main/java/com/kieronquinn/app/ambientmusicmod/utils/extensions/Extensions+Flow.kt +++ b/app/src/main/java/com/kieronquinn/app/ambientmusicmod/utils/extensions/Extensions+Flow.kt @@ -1,28 +1,10 @@ package com.kieronquinn.app.ambientmusicmod.utils.extensions -import kotlinx.coroutines.InternalCoroutinesApi import kotlinx.coroutines.delay -import kotlinx.coroutines.flow.* -import kotlinx.coroutines.launch - -fun Flow.debounceIf(requirement: Boolean, time: Long): Flow { - return this.debounce { _: T -> - if(requirement) time else 0L - } -} - -fun tickerFlow(timePeriod: Long, initialDelay: Long) = flow { - delay(initialDelay) - while(true){ - emit(Unit) - delay(timePeriod) - } -} - -fun delayFlow(delay: Long) = flow { - delay(delay) - emit(Unit) -} +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.channelFlow +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.flow.first suspend fun Flow.await(check: (T) -> Boolean): T { return first { @@ -42,28 +24,4 @@ fun Flow.autoClearAfterBy(delay: suspend (T) -> Long?): Flow = channe suspend fun Flow.firstNotNull(): T { return first { it != null }!! -} - -@OptIn(InternalCoroutinesApi::class) -suspend fun Flow.collectUntilNull(flowCollector: FlowCollector) = - takeWhile { it != null }.mapNotNull { it }.collect(flowCollector) - -inline fun instantCombine(vararg flows: Flow) = channelFlow { - val array = Array(flows.size) { - false to (null as T?) - } - - flows.forEachIndexed { index, flow -> - launch { - flow.collect { emittedElement -> - array[index] = true to emittedElement - send(array.filter { it.first }.map { it.second }) - } - } - } -} - -suspend fun Flow.collectUntilTimeout(timeoutMillis: Long, collector: FlowCollector) = mapLatest { - delay(timeoutMillis) - null -}.collectUntilNull(collector) \ No newline at end of file +} \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_settings_advanced_alternative_encoding.xml b/app/src/main/res/drawable/ic_settings_advanced_alternative_encoding.xml new file mode 100644 index 0000000..58bd38b --- /dev/null +++ b/app/src/main/res/drawable/ic_settings_advanced_alternative_encoding.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/layout/fragment_settings_advanced_gain_bottom_sheet.xml b/app/src/main/res/layout/fragment_settings_advanced_gain_bottom_sheet.xml index 8792e93..c9ddc18 100644 --- a/app/src/main/res/layout/fragment_settings_advanced_gain_bottom_sheet.xml +++ b/app/src/main/res/layout/fragment_settings_advanced_gain_bottom_sheet.xml @@ -34,7 +34,7 @@ android:layout_height="wrap_content" android:layout_margin="@dimen/margin_16" android:valueFrom="0.1" - android:valueTo="2.0" + android:valueTo="3.0" android:stepSize="0.05" app:layout_constraintBottom_toTopOf="@id/settings_advanced_gain_positive" app:layout_constraintTop_toBottomOf="@id/settings_advanced_gain_content" /> diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 626c108..cd154ba 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -276,6 +276,9 @@ Reset %1sx + Use Alternative Encoding + Enable the use of an alternative encoding method which may fix distortion on some devices.\nNote: A higher gain value may be required in combination with this option + Run on Little Cores Run recognition on the little CPU cores. May use less battery, may not work on all devices