无法设置音量,音量控制未转发给系统

Can't set volume, volume control is not forwarded to the system

我尝试使用 Android MediaPlayer 框架来播放 mp3 文件 (see this question).

在我设法让它工作之后,我很快意识到,up/down 数量的事件被 class javafxports.android.KeyEventProcessor 捕获并且永远不会被转发。我试图绕过它,但无济于事。

是否有任何方法可以将事件分派到它不会 被捕获的系统?

感谢和问候, 丹尼尔

虽然我讨厌不断地回答自己的问题,但我在使用 Android API 几个小时后找到了解决方案,并仔细阅读了一些文档等等。

我的解决方案部分基于@josé-pereda 就主题 "javafxports how to call android native Media Player" 给出的答案。

我为任务“volumeUp”、“volumeDown”和“mute[=46”创建了一个界面=]":

public interface NativeVolumeService {
    void volumeUp();
    void volumeDown();
    void mute();
}

然后,根据以下关于如何设置 system volume on Android 的回答,我在 Android 上想出了以下实现:

import java.util.ArrayList;
import java.util.logging.Logger;

import android.content.Context;
import android.media.AudioManager;
import android.view.KeyEvent;
import android.view.View;
import android.view.ViewGroup;
import my.package.platform.NativeVolumeService;
import javafxports.android.FXActivity;

public class NativeVolumeServiceAndroid implements NativeVolumeService {

    private static final Logger LOG = Logger.getLogger(NativeVolumeServiceAndroid.class.getName());

    private final AudioManager audioManager;

    private final int maxVolume;
    private int preMuteVolume = 0;
    private int currentVolume = 0;

    public NativeVolumeServiceAndroid() {
        audioManager = (AudioManager) FXActivity.getInstance().getSystemService(Context.AUDIO_SERVICE);
        maxVolume = audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
    }

    @Override
    public void volumeUp() {
        LOG.info("dispatch volume up event");
        KeyEvent event = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_VOLUME_UP);
        dispatchEvent(event, true, false);
    }

    @Override
    public void volumeDown() {
        LOG.info("dispatch volume down event");
        KeyEvent event = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_VOLUME_DOWN);
        dispatchEvent(event, false, false);
    }

    @Override
    public void mute() {
        LOG.info("dispatch volume mute event");
        KeyEvent event = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_VOLUME_MUTE);
        dispatchEvent(event, false, true);
    }

    private void dispatchEvent(KeyEvent event, boolean up, boolean mute) {

        // hardware key events (amongst others) get caught by the JavaFXPorts engine (or better: the Dalvik impl from Oracle)
        // to circumvent this, we need to do the volume adjustment the hard way: via the AudioManager

        // see: https://developer.android.com/reference/android/media/AudioManager.html
        // see: 

        // reason: 
        // FXActivity registers a FXDalvikEntity, which etends the surface view and passing a key processor 
        // called KeyEventProcessor - this one catches all key events and matches them to JavaFX representations.
        // Unfortunately, we cannot bypass this, so we need the AudioManager

        currentVolume = audioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
        if (mute) {
            if (currentVolume > 0) {
                preMuteVolume = currentVolume;
                audioManager.adjustStreamVolume(AudioManager.STREAM_MUSIC, AudioManager.ADJUST_SAME,
                        AudioManager.FLAG_REMOVE_SOUND_AND_VIBRATE | AudioManager.FLAG_SHOW_UI);
            } else {
                preMuteVolume = 0;
                audioManager.adjustStreamVolume(AudioManager.STREAM_MUSIC, preMuteVolume, AudioManager.FLAG_SHOW_UI);
            }
        } else if (up) {
            audioManager.adjustStreamVolume(AudioManager.STREAM_MUSIC,
                    (currentVolume + 1) <= maxVolume ? AudioManager.ADJUST_RAISE : AudioManager.ADJUST_SAME, AudioManager.FLAG_SHOW_UI);
        } else if (!up) {
            audioManager.adjustStreamVolume(AudioManager.STREAM_MUSIC,
                    (currentVolume - 1) >= 0 ? AudioManager.ADJUST_LOWER : AudioManager.ADJUST_SAME, AudioManager.FLAG_SHOW_UI);
        }
    }
}

为了集成它,我在我的主 class 中创建了适当的实例(我需要这个全局 - 你会看到,为什么)

private void instantiateNativeVolumeService() {
    String serviceName = NativeVolumeService.class.getName();
    if (Platform.isDesktop()) {
        serviceName += "Desktop";
    } else if (Platform.isAndroid()) {
        serviceName += "Android";
    }
    try {
        volumeService = (NativeVolumeService) Class.forName(serviceName).newInstance();
    } catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
        LOG.log(Level.SEVERE, "Could not get an instance of NativeAudioService for platform " + Platform.getCurrent(), e);
    }
}

volumeService 是一个 class 变量。

然后我在我的 Stage 上注册了一个事件处理程序 Scene:

@Override
public void start(Stage stage) throws Exception {
    // initiate everything
    scene.addEventFilter(KeyEvent.ANY, this::handleGlobalKeyEvents);
    // do more stuff, if needed
}

最后,方法 handleGlobalKeyEvents 如下所示:

private void handleGlobalKeyEvents(KeyEvent event) {
    // use a more specific key event type like
    // --> KeyEvent.KEY_RELEASED == event.getEventType()
    // --> KeyEvent.KEY_PRESSED == event.getEventType()
    // without it, we would react on both events, thus doing one operation too much
    if (event.getCode().equals(KeyCode.VOLUME_UP) && KeyEvent.KEY_PRESSED == event.getEventType()) {
        if (volumeService != null) {
            volumeService.volumeUp();
            event.consume();
        }
    }
    if (event.getCode().equals(KeyCode.VOLUME_DOWN) && KeyEvent.KEY_PRESSED == event.getEventType()) {
        if (volumeService != null) {
            volumeService.volumeDown();
            event.consume();
        }
    }
}

最后,解决方案是最干净的,也不太复杂。只是直到它起作用的方式有点令人讨厌。

@JoséPereda:如果您想将此解决方案集成为 charm down 插件左右,请随意,但如果您愿意,能被提及和通知会很高兴。

此致, 丹尼尔