diff --git a/api/client/src/main/kotlin/su/plo/voice/api/client/config/ClientConfig.kt b/api/client/src/main/kotlin/su/plo/voice/api/client/config/ClientConfig.kt index 5e807ca1..65fcd4d3 100644 --- a/api/client/src/main/kotlin/su/plo/voice/api/client/config/ClientConfig.kt +++ b/api/client/src/main/kotlin/su/plo/voice/api/client/config/ClientConfig.kt @@ -79,6 +79,8 @@ interface ClientConfig { val panning: BooleanConfigEntry + val mutePlayerOnDirect: BooleanConfigEntry + val cameraSoundListener: BooleanConfigEntry val exponentialVolumeSlider: BooleanConfigEntry diff --git a/client/changelog.md b/client/changelog.md index 4748ef33..7ad7ee22 100644 --- a/client/changelog.md +++ b/client/changelog.md @@ -9,4 +9,5 @@ so there’s no need to worry if the server hasn't been updated to 2.1.x. - Fixed buffer overflow when using AudioSender with delayed first frame. - Fixed EncoderException on server switch on servers with proxy and proxy-side addons (e.g, groups addon). - Fixed `pv.activation.*` permission is not being updated on the client without reconnect. -- Fixed textfield input not being handled. \ No newline at end of file +- Fixed textfield input not being handled. +- Player sources are now automatically muted if the direct source from the same player becomes active. Can be disabled in menu `Advanced`/`Mute Player On Direct`. \ No newline at end of file diff --git a/client/src/main/java/su/plo/voice/client/config/VoiceClientConfig.java b/client/src/main/java/su/plo/voice/client/config/VoiceClientConfig.java index 2005ab0c..ad1ce62e 100644 --- a/client/src/main/java/su/plo/voice/client/config/VoiceClientConfig.java +++ b/client/src/main/java/su/plo/voice/client/config/VoiceClientConfig.java @@ -400,6 +400,9 @@ public static class Advanced implements ClientConfig.Advanced { @ConfigField private BooleanConfigEntry panning = new BooleanConfigEntry(true); + @ConfigField + private BooleanConfigEntry mutePlayerOnDirect = new BooleanConfigEntry(true); + @ConfigField private BooleanConfigEntry cameraSoundListener = new BooleanConfigEntry(true); diff --git a/client/src/main/java/su/plo/voice/client/gui/settings/tab/AdvancedTabWidget.java b/client/src/main/java/su/plo/voice/client/gui/settings/tab/AdvancedTabWidget.java index e4caa401..2c36715c 100644 --- a/client/src/main/java/su/plo/voice/client/gui/settings/tab/AdvancedTabWidget.java +++ b/client/src/main/java/su/plo/voice/client/gui/settings/tab/AdvancedTabWidget.java @@ -55,6 +55,11 @@ public void init() { )); addEntry(createStereoToMonoSources()); addEntry(createPanning()); + addEntry(createToggleEntry( + McTextComponent.translatable("gui.plasmovoice.advanced.mute_player_on_direct"), + McTextComponent.translatable("gui.plasmovoice.advanced.mute_player_on_direct.tooltip"), + config.getAdvanced().getMutePlayerOnDirect() + )); addEntry(new CategoryEntry(McTextComponent.translatable("gui.plasmovoice.advanced.exponential_volume"))); addEntry(createToggleEntry( diff --git a/client/src/main/kotlin/su/plo/voice/client/audio/source/BaseClientAudioSource.kt b/client/src/main/kotlin/su/plo/voice/client/audio/source/BaseClientAudioSource.kt index 96cc5670..69a5e4e3 100644 --- a/client/src/main/kotlin/su/plo/voice/client/audio/source/BaseClientAudioSource.kt +++ b/client/src/main/kotlin/su/plo/voice/client/audio/source/BaseClientAudioSource.kt @@ -344,42 +344,44 @@ abstract class BaseClientAudioSource( // so we need to make sure that source is not closed rn if (closed.get()) return - // packet compensation - if (lastSequenceNumber >= 0) { - val packetsToCompensate = (packet.sequenceNumber - (lastSequenceNumber + 1)).toInt() - if (packetsToCompensate in 1..4) { - BaseVoice.DEBUG_LOGGER.warn("Compensate {} lost packets", packetsToCompensate) - for (i in 0 until packetsToCompensate) { - val compensatedSequenceNumber = lastSequenceNumber + i + 1 - - if (decoder != null && decoder is AudioDecoderPlc && !sourceInfo.isStereo) { - try { - write((decoder as AudioDecoderPlc).decodePLC(), compensatedSequenceNumber) - } catch (e: CodecException) { - LOGGER.warn("Failed to decode source audio", e) - return + if (shouldWrite()) { + // packet compensation + if (lastSequenceNumber >= 0) { + val packetsToCompensate = (packet.sequenceNumber - (lastSequenceNumber + 1)).toInt() + if (packetsToCompensate in 1..4) { + BaseVoice.DEBUG_LOGGER.warn("Compensate {} lost packets", packetsToCompensate) + for (i in 0 until packetsToCompensate) { + val compensatedSequenceNumber = lastSequenceNumber + i + 1 + + if (decoder != null && decoder is AudioDecoderPlc && !sourceInfo.isStereo) { + try { + write((decoder as AudioDecoderPlc).decodePLC(), compensatedSequenceNumber) + } catch (e: CodecException) { + LOGGER.warn("Failed to decode source audio", e) + return + } + } else { + write(ShortArray(0), compensatedSequenceNumber) } - } else { - write(ShortArray(0), compensatedSequenceNumber) } } } - } - // decrypt & decode samples - try { - val decrypted = encryption?.decrypt(packet.data) ?: packet.data - val decoded = decoder?.decode(decrypted) ?: AudioUtil.bytesToShorts(decrypted) + // decrypt & decode samples + try { + val decrypted = encryption?.decrypt(packet.data) ?: packet.data + val decoded = decoder?.decode(decrypted) ?: AudioUtil.bytesToShorts(decrypted) - if (sourceInfo.isStereo && config.advanced.stereoSourcesToMono.value()) { - write(AudioUtil.convertToMonoShorts(decoded), packet.sequenceNumber) - } else { - write(decoded, packet.sequenceNumber) + if (sourceInfo.isStereo && config.advanced.stereoSourcesToMono.value()) { + write(AudioUtil.convertToMonoShorts(decoded), packet.sequenceNumber) + } else { + write(decoded, packet.sequenceNumber) + } + } catch (e: EncryptionException) { + BaseVoice.DEBUG_LOGGER.warn("Failed to decrypt source audio", e) + } catch (e: CodecException) { + BaseVoice.DEBUG_LOGGER.warn("Failed to decode source audio", e) } - } catch (e: EncryptionException) { - BaseVoice.DEBUG_LOGGER.warn("Failed to decrypt source audio", e) - } catch (e: CodecException) { - BaseVoice.DEBUG_LOGGER.warn("Failed to decode source audio", e) } lastSequenceNumber = packet.sequenceNumber @@ -548,6 +550,14 @@ abstract class BaseClientAudioSource( return sourceInfo.isStereo && !config.advanced.stereoSourcesToMono.value() } + /** + * Determines if audio should be written to the underlying AL source. + * + * Because we don't want to disable source icons in some cases, + * we should just disable writing samples to the actual source. + */ + protected open fun shouldWrite(): Boolean = true + companion object { private val OUTER_ANGLE: Double = 180.0 private val LOGGER: Logger = LogManager.getLogger(BaseClientAudioSource::class.java) diff --git a/client/src/main/kotlin/su/plo/voice/client/audio/source/ClientPlayerSource.kt b/client/src/main/kotlin/su/plo/voice/client/audio/source/ClientPlayerSource.kt index 66e010ad..e40e64e2 100644 --- a/client/src/main/kotlin/su/plo/voice/client/audio/source/ClientPlayerSource.kt +++ b/client/src/main/kotlin/su/plo/voice/client/audio/source/ClientPlayerSource.kt @@ -41,6 +41,13 @@ class ClientPlayerSource( override fun isPanningDisabled(): Boolean = sourcePlayer == getListener() || super.isPanningDisabled() + override fun shouldWrite(): Boolean = + !voiceClient.config.advanced.mutePlayerOnDirect.value() || + voiceClient.sourceManager.sources + .filterIsInstance() + .filter { it.isActivated() } + .none { it.sourceInfo.sender?.id == sourceInfo.playerInfo.playerId } + private val sourceMute: BooleanConfigEntry get() { return config.voice diff --git a/client/src/main/resources/assets/plasmovoice/lang/en_us.json b/client/src/main/resources/assets/plasmovoice/lang/en_us.json index d4ee048c..78168afc 100644 --- a/client/src/main/resources/assets/plasmovoice/lang/en_us.json +++ b/client/src/main/resources/assets/plasmovoice/lang/en_us.json @@ -70,7 +70,9 @@ "gui.plasmovoice.advanced.directional_sources_angle.tooltip": "An angle in which you will hear the source at 100% if directional sources option is enabled.\n\n360 is like if the option is disabled.", "gui.plasmovoice.advanced.stereo_sources_to_mono": "Mono Stereo Sources", "gui.plasmovoice.advanced.stereo_sources_to_mono.tooltip": "Addons use stereo sources for better audio quality. The quality is much better, but facing doesn't affect the panning. Sound only fades out with distance.\n\nWhen this option is enabled, stereo sources are converted to the usual mono sources. Quality is worse, but they will now have panning.", - "gui.plasmovoice.advanced.panning": "Stereo positioning", + "gui.plasmovoice.advanced.panning": "Stereo Positioning", + "gui.plasmovoice.advanced.mute_player_on_direct": "Mute Player On Direct", + "gui.plasmovoice.advanced.mute_player_on_direct.tooltip": "When enabled, the player's source will be automatically muted if the direct source from the same player becomes active. Use this to avoid overlapping audio or conflicting playback from multiple sources.", "gui.plasmovoice.advanced.exponential_volume": "Exponential Volume", "gui.plasmovoice.advanced.exponential_volume.volume_slider": "Volume Slider",