diff --git a/README.md b/README.md index 19606c7..12658f0 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ Contains some easy-to-use tools to go beyond the level of control allowed by And - [x] **Debloater** - Uninstall system apps and bloatware, with app information from [UAD](https://github.com/Universal-Debloater-Alliance/universal-android-debloater-next-generation). - [x] **ThemePatcher** - Unlocks premium content for free, from the Oppo/Realme/Oneplus theme store. - [x] **MixedAudio** - Allows multiple media apps to play at the same time, or mute audio of specific apps. -- [x] **SoundMaster** - Independent volume control for every app, and more! Requires Android 10 or later. +- [x] **SoundMaster** - Independent volume control for every app, play on multiple audio outputs simultaneously, and more! Requires Android 10 or later. ⚠ SoundMaster may not work on apps with strong copyright protection, like Spotify. In case SoundMaster crashes and some apps lose sound output, use MixedAudio to unmute them. diff --git a/app/build.gradle b/app/build.gradle index c830a91..8ea62ee 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -12,7 +12,7 @@ android { minSdk 27 targetSdk 34 versionCode 1 - versionName "1.4.1" + versionName "1.4.2" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } diff --git a/app/release/app-release.apk b/app/release/app-release.apk index 5f40095..7268d32 100644 Binary files a/app/release/app-release.apk and b/app/release/app-release.apk differ diff --git a/app/release/output-metadata.json b/app/release/output-metadata.json index 559cf10..784175a 100644 --- a/app/release/output-metadata.json +++ b/app/release/output-metadata.json @@ -12,7 +12,7 @@ "filters": [], "attributes": [], "versionCode": 1, - "versionName": "1.4.1", + "versionName": "1.4.2", "outputFile": "app-release.apk" } ], diff --git a/app/src/main/java/com/legendsayantan/adbtools/adapters/VolumeBarAdapter.kt b/app/src/main/java/com/legendsayantan/adbtools/adapters/VolumeBarAdapter.kt index eff600f..4f7452d 100644 --- a/app/src/main/java/com/legendsayantan/adbtools/adapters/VolumeBarAdapter.kt +++ b/app/src/main/java/com/legendsayantan/adbtools/adapters/VolumeBarAdapter.kt @@ -26,11 +26,13 @@ class VolumeBarAdapter( val data: List, val onVolumeChanged: (Int, Float) -> Unit, val onItemDetached: (Int) -> Unit, - val onSliderGet:(Int, Int)->Float, - val onSliderSet:(Int, Int, Float)->Unit, - val getDevices: ()->List, - val setDeviceFor: (Int, AudioDeviceInfo)->Unit + val onSliderGet: (Int, Int) -> Float, + val onSliderSet: (Int, Int, Float) -> Unit, + val getDevices: () -> List, + val setDeviceFor: (Int, AudioDeviceInfo) -> Unit ) : RecyclerView.Adapter() { + val devices = getDevices() + inner class VolumeBarHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { val image = itemView.findViewById(R.id.image) val volumeBar = itemView.findViewById(R.id.volume) @@ -77,8 +79,8 @@ class VolumeBarAdapter( holder.volumeBar.addOnChangeListener { _, value, _ -> onVolumeChanged(position, value) } - getDevices().find { it.id==currentItem.outputDevice }?.let { - showDevice(holder.outputName,it) + devices.find { it.id == currentItem.outputDevice }?.let { + showDevice(holder.outputName, it) } holder.switchOutput.setOnClickListener { if (holder.outputExpanded.visibility == View.VISIBLE) { @@ -88,12 +90,12 @@ class VolumeBarAdapter( holder.outputExpanded.visibility = View.VISIBLE //spawn radiobuttons holder.outputGroup.removeAllViews() - getDevices().forEachIndexed { index, device -> + getDevices().forEach { device -> val rButton = RadioButton(context) - showDevice(rButton,device) + showDevice(rButton, device) rButton.setOnClickListener { - setDeviceFor(position,device) - showDevice(holder.outputName,device) + setDeviceFor(position, device) + showDevice(holder.outputName, device) } holder.outputGroup.addView(rButton) } @@ -108,9 +110,9 @@ class VolumeBarAdapter( holder.expanded.visibility = View.VISIBLE holder.expand.animate().rotationX(180f) holder.otherSliders.forEachIndexed { index, slider -> - slider.value = onSliderGet(position,index) + slider.value = onSliderGet(position, index) slider.addOnChangeListener { s, value, fromUser -> - onSliderSet(position,index,value) + onSliderSet(position, index, value) } } } @@ -120,7 +122,6 @@ class VolumeBarAdapter( holder.expanded.visibility = View.GONE - //reset holder.resetBtns.forEachIndexed { index, imageView -> imageView.setOnClickListener { @@ -133,7 +134,8 @@ class VolumeBarAdapter( } - private fun showDevice(v:TextView, d:AudioDeviceInfo){ + + private fun showDevice(v: TextView, d: AudioDeviceInfo) { v.text = "${d.productName} (${AudioOutputMap.getName(d.type)})" } } \ No newline at end of file diff --git a/app/src/main/java/com/legendsayantan/adbtools/lib/PlayBackThread.kt b/app/src/main/java/com/legendsayantan/adbtools/lib/PlayBackThread.kt index 5682b2a..c2afa04 100644 --- a/app/src/main/java/com/legendsayantan/adbtools/lib/PlayBackThread.kt +++ b/app/src/main/java/com/legendsayantan/adbtools/lib/PlayBackThread.kt @@ -31,7 +31,6 @@ class PlayBackThread( var targetVolume: Float = 100f val dataBuffer = ByteArray(BUF_SIZE) var loadedCycles = 0 - var latencyUpdate: (Int) -> Unit = {} lateinit var mCapture: AudioRecord var mPlayers = (hashMapOf ()) @@ -70,20 +69,14 @@ class PlayBackThread( .addMatchingUid(Utils.getAppUidFromPackage(context, pkg)) .build() val audioFormat = AudioFormat.Builder() - .setEncoding(AudioFormat.ENCODING_PCM_16BIT) + .setEncoding(ENCODING) .setSampleRate(SAMPLE_RATE) .setChannelMask(CHANNEL) .build() mCapture = AudioRecord.Builder() .setAudioFormat(audioFormat) - .setBufferSizeInBytes( - AudioRecord.getMinBufferSize( - SAMPLE_RATE, - CHANNEL, - AudioFormat.ENCODING_PCM_16BIT - ) - ) + .setBufferSizeInBytes(BUF_SIZE) .setAudioPlaybackCaptureConfig(config) .build() @@ -113,7 +106,7 @@ class PlayBackThread( mPlayers[device?.id?:-1] = AudioPlayer( AudioManager.STREAM_MUSIC, SAMPLE_RATE, CHANNEL, - AudioFormat.ENCODING_PCM_16BIT, BUF_SIZE, + ENCODING, BUF_SIZE, AudioTrack.MODE_STREAM ).also { it.setCurrentVolume(targetVolume) @@ -186,10 +179,11 @@ class PlayBackThread( } companion object{ - const val SAMPLE_RATE = 44100 const val LOG_TAG = "SoundMaster" - const val CHANNEL = AudioFormat.CHANNEL_IN_STEREO + const val SAMPLE_RATE = 44100 + const val CHANNEL = AudioFormat.CHANNEL_IN_STEREO or AudioFormat.CHANNEL_OUT_STEREO + const val ENCODING = AudioFormat.ENCODING_PCM_16BIT val BUF_SIZE = - AudioRecord.getMinBufferSize(SAMPLE_RATE, CHANNEL, AudioFormat.ENCODING_PCM_16BIT) + AudioRecord.getMinBufferSize(SAMPLE_RATE, CHANNEL, ENCODING) } } \ No newline at end of file diff --git a/app/src/main/java/com/legendsayantan/adbtools/services/SoundMasterService.kt b/app/src/main/java/com/legendsayantan/adbtools/services/SoundMasterService.kt index fa71fce..ace869c 100644 --- a/app/src/main/java/com/legendsayantan/adbtools/services/SoundMasterService.kt +++ b/app/src/main/java/com/legendsayantan/adbtools/services/SoundMasterService.kt @@ -9,9 +9,7 @@ import android.content.pm.PackageManager import android.content.pm.ServiceInfo import android.database.ContentObserver import android.media.AudioDeviceInfo -import android.media.AudioFormat import android.media.AudioManager -import android.media.AudioRecord import android.media.projection.MediaProjection import android.media.projection.MediaProjectionManager import android.os.Build @@ -26,7 +24,6 @@ import com.legendsayantan.adbtools.SoundMasterActivity import com.legendsayantan.adbtools.data.AudioOutputKey import com.legendsayantan.adbtools.lib.PlayBackThread import com.legendsayantan.adbtools.lib.ShizukuRunner -import java.lang.Byte import java.util.Timer import kotlin.Boolean import kotlin.Float @@ -35,6 +32,8 @@ import kotlin.String import kotlin.Unit import kotlin.arrayOf import kotlin.concurrent.timerTask +import kotlin.getValue +import kotlin.lazy import kotlin.let @@ -140,9 +139,6 @@ class SoundMasterService : Service() { mediaProjection!! ) mThread.targetVolume = volumeTemp[key] ?: 100f - mThread.latencyUpdate = { value -> - latency.add(value) - } packageThreads[key.pkg] = mThread mThread.start() } @@ -232,7 +228,6 @@ class SoundMasterService : Service() { const val NOTI_ID = 1 const val notiUpdateTime = 30000L - val zeroByte = Byte.valueOf(0) lateinit var uiIntent: Intent } diff --git a/app/src/main/res/layout/item_volumebar.xml b/app/src/main/res/layout/item_volumebar.xml index 8103089..c593428 100644 --- a/app/src/main/res/layout/item_volumebar.xml +++ b/app/src/main/res/layout/item_volumebar.xml @@ -76,7 +76,6 @@ + ShizuTools This app requires Shizuku.\nPlease make sure you have shizuku installed and running. Force applying MixedAudio may crash some apps, and doing so on system apps may crash your entire system. @@ -16,13 +16,13 @@ Uninstall unnecessary system apps to remove ads, save battery and improve performance. 100 % - SoundMaster - Control audio output of individual apps, on Android 10 or newer device. + SoundMaster beta + Control audio output of individual apps, play on multiple outputs simultaneously, and more! Select which app to control : No sliders added New Slider Removable apps - - Please wait... + Please wait… Welcome! Ensure you do not have any adb restrictions, from developer options. Select an apk file to be installed as a downgrade. @@ -41,7 +41,7 @@ Switch Output Device: Default Run - Enter Command... + Enter Command… Accept adb intents Intent shell locks for 5 minutes after usage of a wrong access key, to prevent brute force attempts. Access key