1. 程式人生 > >Android 支援多個VideoView聲音調節

Android 支援多個VideoView聲音調節

有個場景就是一個頁面裡有多個VideoView播放視訊,然後每個視訊都有一個音量值,但是VideoView並不支援直接設定音量,而是要通過調節系統音量來實現,那麼這樣的話,就不能實現為每個視訊獨立調節音量了我們知道MediaPlayer+SurfaceView也能實現視訊的播放,並且MediaPlayer是可以直接通過setVolume來調節視訊音量的,但是因為這裡已經用了VideoView實現了播放,再改的話就太浪費時間和人力了。想到VideoView是繼承SurfaceView的,那會不會VideoView的視訊播放控制是由MediaPlayer實現的呢,檢視VideoView原始碼:

try 
{ mMediaPlayer = new MediaPlayer(); // TODO: create SubtitleController in MediaPlayer, but we need // a context for the subtitle renderers final Context context = getContext(); final SubtitleController controller = new SubtitleController( context, mMediaPlayer.getMediaTimeProvider(),
mMediaPlayer); controller.registerRenderer(new WebVttRenderer(context)); controller.registerRenderer(new TtmlRenderer(context)); controller.registerRenderer(new Cea708CaptionRenderer(context)); controller.registerRenderer(new ClosedCaptionRenderer(context)); mMediaPlayer.setSubtitleAnchor(controller, this
); if (mAudioSession != 0) { mMediaPlayer.setAudioSessionId(mAudioSession); } else { mAudioSession = mMediaPlayer.getAudioSessionId(); } mMediaPlayer.setOnPreparedListener(mPreparedListener); mMediaPlayer.setOnVideoSizeChangedListener(mSizeChangedListener); mMediaPlayer.setOnCompletionListener(mCompletionListener); mMediaPlayer.setOnErrorListener(mErrorListener); mMediaPlayer.setOnInfoListener(mInfoListener); mMediaPlayer.setOnBufferingUpdateListener(mBufferingUpdateListener); mCurrentBufferPercentage = 0; mMediaPlayer.setDataSource(mContext, mUri, mHeaders); mMediaPlayer.setDisplay(mSurfaceHolder); mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC); mMediaPlayer.setScreenOnWhilePlaying(true); mMediaPlayer.prepareAsync();
果然如此,VideoView僅僅只是封裝了MediaPlayer和SurfaceView。那麼既然這樣的話就好辦了,只需要獲取到VideoView裡面的MediaPlayer就可以單獨修改這個視訊的聲音大小了,但是發現MediaPlayer是私有的並且VideoView並沒有留出方法給外層呼叫,那麼只能通過反射了。
/**
 * @param volume 音量大小
 * @param object VideoView例項
 * */
public void setVolume(float volume,Object object) {
    try {
        Class<?> forName = Class.forName("android.widget.VideoView");
Field field = forName.getDeclaredField("mMediaPlayer");
field.setAccessible(true);
MediaPlayer mMediaPlayer = (MediaPlayer) field.get(object);
mMediaPlayer.setVolume(volume, volume);
} catch (Exception e) {
    }
}
據此思想,當遇到系統Api限制的問題時,我們不妨另闢蹊徑,通過反射解除限制,從而解決問題,比如下面這個例子:
某些情況下我們想在ViewPager滑動的時候才建立頁面,並不希望ViewPager給我們快取頁面,所以我們先找到ViewPager裡面設定快取個數相關的引數和方法。
private static final int DEFAULT_OFFSCREEN_PAGES = 1;
private int mOffscreenPageLimit = DEFAULT_OFFSCREEN_PAGES;
public void setOffscreenPageLimit(int limit) {
    if (limit < DEFAULT_OFFSCREEN_PAGES) {
        Log.w(TAG, "Requested offscreen page limit " + limit + " too small; defaulting to " +
                DEFAULT_OFFSCREEN_PAGES);
limit = DEFAULT_OFFSCREEN_PAGES;
}
    if (limit != mOffscreenPageLimit) {
        mOffscreenPageLimit = limit;
populate();
}
}
 可以看到ViewPager限制了傳入快取頁數不能小於1,所以無論如何ViewPager都會給我們快取一個以上的頁面。沒關係,同樣的道理,我們可以修改這個限制,這裡我們直接通過反射修改mOffscreenPageLimit的值來實現。
try {
    Class<?> forName = Class.forName("android.support.v4.view.ViewPager");
Field defauleField = forName.getDeclaredField("mOffscreenPageLimit");
defauleField.setAccessible(true);
defauleField.set(0,viewPager);
} catch (Exception e) {
}
通過這兩個例子主要是想告訴大家不要被系統Api所限制,當發現SDK裡沒提供你所需要的方法時候,別放棄,細心看看原始碼,也許會柳暗花明又一村,程式碼是死的,人是活的,為達目的,可以不擇手段,正常路徑走不通,那麼可以通過一些非正常的方式來達到目的。