美文网首页
android5.1音量调节播放调试音

android5.1音量调节播放调试音

作者: 97690CE50CC872D | 来源:发表于2017-08-19 11:37 被阅读77次

设置中音量相关的Fragment调整到了NotificationSettings.java中。
我们可以在R.xml.notification_settings中看到如下
xref: /packages/apps/Settings/res/xml/notification_settings.xml

<!-- Media volume -->
    <com.android.settings.notification.VolumeSeekBarPreference
            android:key="media_volume"
            android:icon="@*android:drawable/ic_audio_vol"
            android:title="@string/media_volume_option_title" />

    <!-- Alarm volume -->
    <com.android.settings.notification.VolumeSeekBarPreference
            android:key="alarm_volume"
            android:icon="@*android:drawable/ic_audio_alarm"
            android:title="@string/alarm_volume_option_title" />

    <!-- Ring volume -->
    <com.android.settings.notification.VolumeSeekBarPreference
            android:key="ring_volume"
            android:icon="@*android:drawable/ic_audio_ring_notif"
            android:title="@string/ring_volume_option_title" />

    <!-- Notification volume -->
    <com.android.settings.notification.VolumeSeekBarPreference
            android:key="notification_volume"
            android:icon="@*android:drawable/ic_audio_ring_notif"
            android:title="@string/notification_volume_option_title" />

这分别代表多媒体、闹钟、来电铃声和系统通知音量。所以在UI层面上用户是通过调节VolumeSeekBarPreference这个seekbar来最终对系统音量进行调整。
回头看NotificationSettings.java中初始化流程如下
xref: /packages/apps/Settings/src/com/android/settings/notification/NotificationSettings.java

//sound是包含上面4个SeekBar的PreferenceCategory
    final PreferenceCategory sound = (PreferenceCategory) findPreference(KEY_SOUND);
        initVolumePreference(KEY_MEDIA_VOLUME, AudioManager.STREAM_MUSIC);
        initVolumePreference(KEY_ALARM_VOLUME, AudioManager.STREAM_ALARM);
    //判断是否支持通话,支持移除NOTIFICATION_VOLUME否则移除RING_VOLUME
        if (mVoiceCapable) {
            mRingOrNotificationPreference =
                    initVolumePreference(KEY_RING_VOLUME, AudioManager.STREAM_RING);
            sound.removePreference(sound.findPreference(KEY_NOTIFICATION_VOLUME));
        } else {
            mRingOrNotificationPreference =
                    initVolumePreference(KEY_NOTIFICATION_VOLUME, AudioManager.STREAM_NOTIFICATION);
            sound.removePreference(sound.findPreference(KEY_RING_VOLUME));
        }
        initRingtones(sound);

    private final VolumePreferenceCallback mVolumeCallback = new VolumePreferenceCallback();

private VolumeSeekBarPreference initVolumePreference(String key, int stream) {
        final VolumeSeekBarPreference volumePref = (VolumeSeekBarPreference) findPreference(key);
    设置callback
        volumePref.setCallback(mVolumeCallback);
    //设置AudioManager的stream,可以在AudioSystem中看到具体int值代表的意义
        volumePref.setStream(stream);
        return volumePref;
}

mVolumeCallback如下,当你拖动seekbar对音量进行调节时这个callback会回馈响应,在界面上进行相应的调整。

private final class VolumePreferenceCallback implements VolumeSeekBarPreference.Callback {
    //SeekBarVolumizer是主要负责对系统音量进行调整的类,会在后面描述
        private SeekBarVolumizer mCurrent;  
        @Override
        public void onSampleStarting(SeekBarVolumizer sbv) {
            if (mCurrent != null && mCurrent != sbv) {
                mCurrent.stopSample();
            }
            mCurrent = sbv;
            if (mCurrent != null) {
                mHandler.removeMessages(H.STOP_SAMPLE);
                mHandler.sendEmptyMessageDelayed(H.STOP_SAMPLE, SAMPLE_CUTOFF);
            }
        }   
        @Override
        public void onStreamValueChanged(int stream, int progress) {
            if (stream == AudioManager.STREAM_RING) {
                mHandler.removeMessages(H.UPDATE_RINGER_ICON);
                mHandler.obtainMessage(H.UPDATE_RINGER_ICON, progress, 0).sendToTarget();
            }
    }
        public void stopSample() {
            if (mCurrent != null) {
                mCurrent.stopSample();
            }
        }
    };
    @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case UPDATE_PHONE_RINGTONE:
                    mPhoneRingtonePreference.setSummary((CharSequence) msg.obj);
                    break;
                case UPDATE_NOTIFICATION_RINGTONE:
                    mNotificationRingtonePreference.setSummary((CharSequence) msg.obj);
                    break;
                case STOP_SAMPLE:
                    mVolumeCallback.stopSample();
                    break;
                case UPDATE_RINGER_ICON:
                    updateRingOrNotificationIcon(msg.arg1);
                    break;
            }
    }

下面来看VolumeSeekBarPreference.java中可以看到如下代码
xref: /packages/apps/Settings/src/com/android/settings/notification/VolumeSeekBarPreference.java

private SeekBarVolumizer mVolumizer;
    final Uri sampleUri = mStream == AudioManager.STREAM_MUSIC ? getMediaVolumeUri() : null;
        if (mVolumizer == null) {
            mVolumizer = new SeekBarVolumizer(getContext(), mStream, sampleUri, sbvc) {
                // we need to piggyback on SBV's SeekBar listener to update our icon
                @Override
                public void onProgressChanged(SeekBar seekBar, int progress,
                        boolean fromTouch) {
                    super.onProgressChanged(seekBar, progress, fromTouch);
            //这里的mCallback就是VolumePreferenceCallback对象
                    mCallback.onStreamValueChanged(mStream, progress);
                }
            };
        }
        mVolumizer.setSeekBar(mSeekBar);
    mIconView = (ImageView) view.findViewById(com.android.internal.R.id.icon);
        mCallback.onStreamValueChanged(mStream, mSeekBar.getProgress());
    @Override
        public void onProgressChanged(SeekBar seekBar, int progress,
            boolean fromTouch) {
            super.onProgressChanged(seekBar, progress, fromTouch);
            mCallback.onStreamValueChanged(mStream, progress);
}

这里我们进行下梳理在NotificationSettings.java内initVolumePreference函数的volumePref.setCallback(mVolumeCallback);把VolumePreferenceCallback这个callbak设置到VolumeSeekBarPreference.java内的mCallback中
使得SeekBarVolumizer内的onProgressChanged进行回调时NotificationSettings.java的相应回调函数都响应。

现在看下/frameworks/base/core/java/android/preference/的SeekBarVolumizer.java

private final Context mContext;
private final Handler mHandler;
private final H mUiHandler = new H();
private final Callback mCallback;
private final Uri mDefaultUri;
private final AudioManager mAudioManager;
private final int mStreamType;
private final int mMaxStreamVolume;
private final Receiver mReceiver = new Receiver();
private final Observer mVolumeObserver;

private int mOriginalStreamVolume;
private Ringtone mRingtone;
private int mLastProgress = -1;
private SeekBar mSeekBar;
private int mVolumeBeforeMute = -1;

private static final int MSG_SET_STREAM_VOLUME = 0;
private static final int MSG_START_SAMPLE = 1;
private static final int MSG_STOP_SAMPLE = 2;
private static final int MSG_INIT_SAMPLE = 3;
private static final int CHECK_RINGTONE_PLAYBACK_DELAY_MS = 1000;

public SeekBarVolumizer(Context context, int streamType, Uri defaultUri,
        Callback callback) {
    mContext = context;
    mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
    mStreamType = streamType;
    mMaxStreamVolume = mAudioManager.getStreamMaxVolume(mStreamType);
    HandlerThread thread = new HandlerThread(TAG + ".CallbackHandler");
    thread.start();
    mHandler = new Handler(thread.getLooper(), this);
    mCallback = callback;
    mOriginalStreamVolume = mAudioManager.getStreamVolume(mStreamType);
    mVolumeObserver = new Observer(mHandler);
    mContext.getContentResolver().registerContentObserver(
            System.getUriFor(System.VOLUME_SETTINGS[mStreamType]),
            false, mVolumeObserver);
    mReceiver.setListening(true);

//mDefaultUri为调整seekbar时听到的调试音
    if (defaultUri == null) {
        if (mStreamType == AudioManager.STREAM_RING) {
            defaultUri = Settings.System.DEFAULT_RINGTONE_URI;
        } else if (mStreamType == AudioManager.STREAM_NOTIFICATION) {
            defaultUri = Settings.System.DEFAULT_NOTIFICATION_URI;
        } else {
            defaultUri = Settings.System.DEFAULT_ALARM_ALERT_URI;
        }
    }
    mDefaultUri = defaultUri;
    mHandler.sendEmptyMessage(MSG_INIT_SAMPLE);
}

public void setSeekBar(SeekBar seekBar) {
    if (mSeekBar != null) {
        mSeekBar.setOnSeekBarChangeListener(null);
    }
    mSeekBar = seekBar;
    mSeekBar.setOnSeekBarChangeListener(null);
    mSeekBar.setMax(mMaxStreamVolume);
    mSeekBar.setProgress(mLastProgress > -1 ? mLastProgress : mOriginalStreamVolume);
    mSeekBar.setOnSeekBarChangeListener(this);
}

@Override
public boolean handleMessage(Message msg) {
    switch (msg.what) {
    //真正调节系统音量的地方
        case MSG_SET_STREAM_VOLUME:
            mAudioManager.setStreamVolume(mStreamType, mLastProgress,
                    AudioManager.FLAG_SHOW_UI_WARNINGS);
            break;
        case MSG_START_SAMPLE:
            onStartSample();
            break;
        case MSG_STOP_SAMPLE:
            onStopSample();
            break;
        case MSG_INIT_SAMPLE:
            onInitSample();
            break;
        default:
            Log.e(TAG, "invalid SeekBarVolumizer message: "+msg.what);
    }
    return true;
}

//初始化mRingtone,我们知道系统播放的铃声最后是通过mRingtone.play()来实现的
private void onInitSample() {
    mRingtone = RingtoneManager.getRingtone(mContext, mDefaultUri);
    if (mRingtone != null) {
        mRingtone.setStreamType(mStreamType);
    }
}

private void postStartSample() {
    mHandler.removeMessages(MSG_START_SAMPLE);
    mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_START_SAMPLE),
            isSamplePlaying() ? CHECK_RINGTONE_PLAYBACK_DELAY_MS : 0);
}

//开始播放调试音
private void onStartSample() {
    if (!isSamplePlaying()) {
        if (mCallback != null) {
            mCallback.onSampleStarting(this);
        }
        if (mRingtone != null) {
            try {
                mRingtone.play();
            } catch (Throwable e) {
                Log.w(TAG, "Error playing ringtone, stream " + mStreamType, e);
            }
        }
    }
}

void postStopSample() {
    // remove pending delayed start messages
    mHandler.removeMessages(MSG_START_SAMPLE);
    mHandler.removeMessages(MSG_STOP_SAMPLE);
    mHandler.sendMessage(mHandler.obtainMessage(MSG_STOP_SAMPLE));
}

//停止播放调试音
private void onStopSample() {
    if (mRingtone != null) {
        mRingtone.stop();
    }
}

public void stop() {
    postStopSample();
    mContext.getContentResolver().unregisterContentObserver(mVolumeObserver);
    mSeekBar.setOnSeekBarChangeListener(null);
    mReceiver.setListening(false);
    mHandler.getLooper().quitSafely();
}

public void revertVolume() {
    mAudioManager.setStreamVolume(mStreamType, mOriginalStreamVolume, 0);
}

public void onProgressChanged(SeekBar seekBar, int progress,
        boolean fromTouch) {
    if (!fromTouch) {
        return;
    }

    postSetVolume(progress);
}

void postSetVolume(int progress) {
    // Do the volume changing separately to give responsive UI
    mLastProgress = progress;
    mHandler.removeMessages(MSG_SET_STREAM_VOLUME);
    mHandler.sendMessage(mHandler.obtainMessage(MSG_SET_STREAM_VOLUME));
}

public void onStartTrackingTouch(SeekBar seekBar) {
}

public void onStopTrackingTouch(SeekBar seekBar) {
    postStartSample();
}

//判断是否正在播放铃声
public boolean isSamplePlaying() {
    return mRingtone != null && mRingtone.isPlaying();
}

public void startSample() {
    postStartSample();
}

public void stopSample() {
    postStopSample();
}

public SeekBar getSeekBar() {
    return mSeekBar;
}

public void changeVolumeBy(int amount) {
    mSeekBar.incrementProgressBy(amount);
    postSetVolume(mSeekBar.getProgress());
    postStartSample();
    mVolumeBeforeMute = -1;
}

public void muteVolume() {
    if (mVolumeBeforeMute != -1) {
        mSeekBar.setProgress(mVolumeBeforeMute);
        postSetVolume(mVolumeBeforeMute);
        postStartSample();
        mVolumeBeforeMute = -1;
    } else {
        mVolumeBeforeMute = mSeekBar.getProgress();
        mSeekBar.setProgress(0);
        postStopSample();
        postSetVolume(0);
    }
}

public void onSaveInstanceState(VolumeStore volumeStore) {
    if (mLastProgress >= 0) {
        volumeStore.volume = mLastProgress;
        volumeStore.originalVolume = mOriginalStreamVolume;
    }
}

public void onRestoreInstanceState(VolumeStore volumeStore) {
    if (volumeStore.volume != -1) {
        mOriginalStreamVolume = volumeStore.originalVolume;
        mLastProgress = volumeStore.volume;
        postSetVolume(mLastProgress);
    }
}

private final class H extends Handler {
    private static final int UPDATE_SLIDER = 1;

    @Override
    public void handleMessage(Message msg) {
        if (msg.what == UPDATE_SLIDER) {
            if (mSeekBar != null) {
                mSeekBar.setProgress(msg.arg1);
                mLastProgress = mSeekBar.getProgress();
            }
        }
    }

    public void postUpdateSlider(int volume) {
        obtainMessage(UPDATE_SLIDER, volume, 0).sendToTarget();
    }
}

private final class Observer extends ContentObserver {
    public Observer(Handler handler) {
        super(handler);
    }

    @Override
    public void onChange(boolean selfChange) {
        super.onChange(selfChange);
        if (mSeekBar != null && mAudioManager != null) {
            final int volume = mAudioManager.getStreamVolume(mStreamType);
            mUiHandler.postUpdateSlider(volume);
        }
    }
}

这里忽视其他所有操作关注一下主要流程,我们知道这个类implements了OnSeekBarChangeListener,这个listener包含了当值发生变化响应的onProgressChanged和开始完成操作的onStartTrackingTouch和onStopTrackingTouch 所以当开始拖动seekbar时onStartTrackingTouch响应无操作,然后onProgressChanged发生变化postSetVolume(progress);从而调用最关键函数mAudioManager.setStreamVolume(mStreamType, mLastProgress, AudioManager.FLAG_SHOW_UI_WARNINGS);实现音量调整,然后调用onStopTrackingTouch,通过handler调用onStartSample函数来播放调试音,在这过程中通过和setting模块绑定的 callback来对setting模块进行响应,这就时和上层setting模块互动的一个主要流程。

感谢闲庭信步_wwdandroid5.0设置模块音量调节流程启发

相关文章

  • android5.1音量调节播放调试音

    设置中音量相关的Fragment调整到了NotificationSettings.java中。我们可以在R.xml...

  • 隐藏系统的音量视图MPVolumeView

    我们在做播放器的时候会调节播放器的音量.但是会弹出系统的音量视图. 但是产品还偏偏不要这个音量视图.人家要自己音量...

  • FFmpeg音频播放器(1)-简介

    本项目是Android下基于FFmpeg的音频播放器,主要实现了音频解码播放,音量调节,速度调节,多音轨混音播放。...

  • 一个具备录音的开源库(CyhRecord)

    集成 使用 录音 停止录音 播放录音 使用过上面的方法初始化后,可直接使用的播放 暂停 停止 调节播放进度 调节音量

  • 基于IJKPlayer框架的 iOS视频播放器

    这是一个基于ijkplayer框架的iOS视频播放器,包含全屏、播放、暂停、亮度调节、音量调节、进度条(边播边缓存...

  • Axure入门案例系列 -- 拖动等级调节

    拖动调节一般在手机端主要用于快速的数值调节,主要用于音量的调节、播放进度调节、亮度调节等 准备 Axure 8(或...

  • iOS干货收集<十三>高质量iOS文章链接

    视频播放 ZFPlayer 基于AVPlayer,支持横屏、竖屏(全屏播放还可锁定屏幕方向),上下滑动调节音量、...

  • AVFoundation:播放音频和录制音频

    播放音频 1.配置音频回话 2.播放音频创建AVAudioPlayer volume:调节声音的音量pan:修改播...

  • IOS音量

    ��有时候有这样的需求进入某个界面音量调节到到最大, 离开界面音量恢复到之前的大小或者播放视频,音频文件时候将音量...

  • 调节,是一种变通

    调节,是变通的一种 调节,我们知道的。比如说,我们听音乐时,可以调节音量的大小。比如说,我们冲凉时,可以调...

网友评论

      本文标题:android5.1音量调节播放调试音

      本文链接:https://www.haomeiwen.com/subject/ebktdxtx.html