1. 程式人生 > >Android音樂播放模式切換-外放、聽筒、耳機

Android音樂播放模式切換-外放、聽筒、耳機

場景需求

在聊天場景中,收到對方語音時,使用者可以選擇外放播放,也可以選擇插入耳機收聽.更人性化一點當用戶把手機靠近耳朵時螢幕關閉自動切換到聽筒中播放,播放完畢後拿開手機螢幕自動點亮.比如微信就是如此.

需求分析

從上面場景中我們可以得出我們需要的要點:
播放模式切換:外放<—>耳機
播放模式切換:外放<—>聽筒
螢幕操作:亮屏<—>息屏<—>亮屏

解決問題

從需求分析我們可以得出需要程式碼進行控制的有:
音樂播放控制
外放,耳機,聽筒之間的切換
螢幕的息屏與亮屏

音樂播放控制

音樂播放控制最簡單,直接使用MediaPlayer即可,為了更好地與介面程式碼分離以及更好控制音樂,這裡寫了一個控制類:PlayerManager,如下:

/**
 * 音樂播放管理類
 */
public class PlayerManager {

    private static PlayerManager playerManager;

    private MediaPlayer mediaPlayer;
    private PlayCallback callback;
    private Context context;

    private String filePath;

    public static PlayerManager getManager(){
        if (playerManager == null
){ synchronized (PlayerManager.class){ playerManager = new PlayerManager(); } } return playerManager; } private PlayerManager(){ this.context = MyApplication.getContext(); mediaPlayer = new MediaPlayer(); } /** * 播放回調介面 */
public interface PlayCallback{ /** 音樂準備完畢 */ void onPrepared(); /** 音樂播放完成 */ void onComplete(); /** 音樂停止播放 */ void onStop(); } /** * 播放音樂 * @param path 音樂檔案路徑 * @param callback 播放回調函式 */ public void play(String path, final PlayCallback callback){ this.filePath = path; this.callback = callback; try { mediaPlayer.reset(); mediaPlayer.setDataSource(context, Uri.parse(path)); mediaPlayer.prepare(); mediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() { @Override public void onPrepared(MediaPlayer mp) { callback.onPrepared(); mediaPlayer.start(); } }); } catch (IOException e) { e.printStackTrace(); } } /** * 停止播放 */ public void stop(){ if (isPlaying()){ try { mediaPlayer.stop(); callback.onStop(); } catch (IllegalStateException e) { e.printStackTrace(); } } } /** * 是否正在播放 * @return 正在播放返回true,否則返回false */ public boolean isPlaying() { return mediaPlayer != null && mediaPlayer.isPlaying(); } }

為了方便獲取Context,覆寫了Application類如下:

/**
 * APP的Application
 */
public class MyApplication extends Application {

    private static Context context;

    @Override
    public void onCreate() {
        super.onCreate();
        context = this;
    }

    /**
     * 獲取APP的Context方便其他地方呼叫
     * @return
     */
    public static Context getContext(){
        return context;
    }
}

外放,耳機,聽筒之間的切換

在Android系統中是用AudioManager來管理播放模式的,通過AudioManager.setMode()方法來實現.
在setMode()方法中有以下幾種對應不同的播放模式:

  1. MODE_NORMAL: 普通模式,既不是鈴聲模式也不是通話模式
  2. MODE_RINGTONE:鈴聲模式
  3. MODE_IN_CALL:通話模式
  4. MODE_IN_COMMUNICATION:通訊模式,包括音/視訊,VoIP通話.(3.0加入的,與通話模式類似)

其中:
播放音樂的對應的就是MODE_NORMAL, 如果使用外放播則呼叫audioManager.setSpeakerphoneOn(true)即可.
若使用耳機和聽筒,則需要先設定模式為MODE_IN_CALL(3.0以前)或MODE_IN_COMMUNICATION(3.0以後).

注意:
需要許可權android.permission.MODIFY_AUDIO_SETTINGS
為什麼在3.0以後設定模式為MODE_IN_COMMUNICATION,而不設定為MODE_IN_CALL?
經驗證在華為的某些機型中,設定MODE_IN_CALL根本不起作用.
故在PlayerManager類中持有一個AudioManager變數,並新增如下幾個方法:

audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);

/**
 * 切換到外放
 */
public void changeToSpeaker(){
    audioManager.setMode(AudioManager.MODE_NORMAL);
    audioManager.setSpeakerphoneOn(true);
}

/**
 * 切換到耳機模式
 */
public void changeToHeadset(){
    audioManager.setSpeakerphoneOn(false);
}

/**
 * 切換到聽筒
 */
public void changeToReceiver(){
    audioManager.setSpeakerphoneOn(false);
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB){
        audioManager.setMode(AudioManager.MODE_IN_COMMUNICATION);
    } else {
        audioManager.setMode(AudioManager.MODE_IN_CALL);
    }
}

如何判斷使用者是否插入耳機呢?
在插入或者拔出耳機時系統會發出Action為Intent.ACTION_HEADSET_PLUG的廣播,並且該廣播不能使用靜態接收器處理,故寫一個廣播接收器處理耳機事件即可.

class HeadsetReceiver extends BroadcastReceiver{

    @Override
    public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();
        switch (action){
            //插入和拔出耳機會觸發此廣播
            case Intent.ACTION_HEADSET_PLUG:
                int state = intent.getIntExtra("state", 0);
                if (state == 1){
                    playerManager.changeToHeadset();
                } else if (state == 0){
                    playerManager.changeToSpeaker();
                }
                break;
            default:
                break;
        }
    }
}

螢幕的息屏與亮屏

螢幕息屏與亮屏有個前提是正確判斷使用者是否靠近聽筒,如何判斷?
現在幾乎每個手機都有距離感應器,通過舉例感應器可獲得距離.距離感應器由SensorManager管理:

sensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);
sensor = sensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY);
sensorManager.registerListener(this, sensor, SensorManager.SENSOR_DELAY_NORMAL);

註冊監聽的方法的最後一個引數是敏感度,敏感度越高越費電,此處選擇一般敏感度即可.此外Activity還需實現SensorEventListener介面,覆寫其方法:

@Override
public void onSensorChanged(SensorEvent event) {
    float value = event.values[0];

    if (playerManager.isPlaying()){
        if (value == sensor.getMaximumRange()) {
            playerManager.changeToSpeaker();
            setScreenOn();
        } else {
            playerManager.changeToReceiver();
            setScreenOff();
        }
    } else {
        if(value == sensor.getMaximumRange()){
            playerManager.changeToSpeaker();
            setScreenOn();
        }
    }
}

在Android系統中硬體的工作狀態的控制由PowerManager與WakeLock掌管.PowerManager通過不同的WakeLock來控制CPU,螢幕,鍵盤等硬體的工作狀態.

powerManager = (PowerManager) getSystemService(POWER_SERVICE);
wakeLock = powerManager.newWakeLock(PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK, TAG);

注意:需要許可權android.Manifest.permission.DEVICE_POWER和android.permission.WAKE_LOCK
其中第一個引數代表控制級別,可選值有:

  1. PARTIAL_WAKE_LOCK : CPU執行,螢幕和鍵盤可能關閉
  2. SCREEN_DIM_WAKE_LOCK : 螢幕亮,鍵盤燈可能關閉
  3. SCREEN_BRIGHT_WAKE_LOCK : 螢幕全亮,鍵盤燈可能關閉
  4. FULL_WAKE_LOCK : 螢幕和鍵盤燈全亮
  5. PROXIMITY_SCREEN_OFF_WAKE_LOCK : 螢幕關閉,鍵盤燈關閉,CPU執行
  6. DOZE_WAKE_LOCK : 螢幕灰顯,CPU延緩工作

此處我們選取5.PROXIMITY_SCREEN_OFF_WAKE_LOCK.WakeLock通過acquire()和release()方法上鎖和解鎖.

private void setScreenOff(){
    if (wakeLock == null){
        wakeLock = powerManager.newWakeLock(PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK, TAG);
    }
    wakeLock.acquire();
}

private void setScreenOn(){
    if (wakeLock != null){
        wakeLock.setReferenceCounted(false);
        wakeLock.release();
        wakeLock = null;
    }
}

開始驗證

通過以上三個解決方案,然後執行程式可知基本滿足功能需求.但是有以下幾個問題:

  1. 耳機模式下用手遮擋距離感應器會切換到聽筒
  2. 三星Note,華為P,華為Mate系列會出現外放切換到聽筒,聽筒切換到外放出現卡頓現象
  3. 耳機切換到外放會出現丟失語音
  4. 三星,華為手機在熄滅螢幕是會呼叫Activity的onPause(),onStop()方法!

解決新問題

耳機模式用手遮擋距離感應器問題

此問題只需在耳機模式下對距離感應器不做響應即可,在PlayerManager中新增:

/**
 * 耳機是否插入
 * @return 插入耳機返回true,否則返回false
 */
@SuppressWarnings("deprecation")
public boolean isWiredHeadsetOn(){
    return audioManager.isWiredHeadsetOn();
}

然後修改距離感應器回撥方法為:

@Override
public void onSensorChanged(SensorEvent event) {
    float value = event.values[0];

    if (playerManager.isWiredHeadsetOn()){
        return;
    }

    if (playerManager.isPlaying()){
        if (value == sensor.getMaximumRange()) {
            playerManager.changeToSpeaker();
            setScreenOn();
        } else {
            playerManager.changeToReceiver();
            setScreenOff();
        }
    } else {
        if(value == sensor.getMaximumRange()){
            playerManager.changeToSpeaker();
            setScreenOn();
        }
    }
}

三星,華為聽筒外放切換卡頓

這個問題只能採用折中的辦法:重新播放
為何採用此方法?

短的語音本來就短,切換重播幾乎不受影響
長得音樂一般不會用聽筒聽
不是所有的手機都會出現卡頓

故在PlayerManager中修改方法:

/**
 * 切換到聽筒
 */
public void changeToReceiver(){
    if (isPlaying()){
        stop();
        changeToReceiverNoStop();
        play(filePath, callback);
    } else {
        changeToReceiverNoStop();
    }
}

/**
 * 切換到外放
 */
public void changeToSpeaker(){
    if (PhoneModelUtil.isSamsungPhone() || PhoneModelUtil.isHuaweiPhone()){
        stop();
        changeToSpeakerNoStop();
        play(filePath, callback);
    } else {
        changeToSpeakerNoStop();
    }
}

public void changeToSpeakerNoStop(){
    audioManager.setMode(AudioManager.MODE_NORMAL);
    audioManager.setSpeakerphoneOn(true);
}

耳機切換到外放會出現丟失語音

此問題由於耳機切換到外放需要一段時間導致,故解決此問題的方法是先暫停再續播.那麼什麼時候暫停什麼時候續播呢?
查資料得知,在耳機拔出時系統還會發出Action為AudioManager.ACTION_AUDIO_BECOMING_NOISY的廣播,且此廣播比Intent.ACTION_HEADSET_PLUG要早,所以解決方案也出來了:

收到AudioManager.ACTION_AUDIO_BECOMING_NOISY時暫停播放
收到Intent.ACTION_HEADSET_PLUG並且附帶的state=1時續播

三星,華為手機在熄滅螢幕是會呼叫Activity的onPause(),onStop()方法

這個問題嘛,其實也不算問題,但是值得注意.如果你在onStop()中做了某些釋放資源的操作,那麼在onStart()中就要重新獲取,防止出現其他問題.

相關推薦

Android音樂播放模式切換-聽筒耳機

場景需求 在聊天場景中,收到對方語音時,使用者可以選擇外放播放,也可以選擇插入耳機收聽.更人性化一點當用戶把手機靠近耳朵時螢幕關閉自動切換到聽筒中播放,播放完畢後拿開手機螢幕自動點亮.比如微信就是如此. 需求分析 從上面場景中我們可以得出我們需要的要點

音樂視訊播放模式切換實現方案及原理解析(基於vuevuexh5 audio)

音樂、視訊播放模式切換實現方案及原理解析(基於vue、vuex、h5 audio) 播放模式有三種: 順序播放 隨機播放 單曲迴圈 定義為一個playMode物件並向外暴露,內含三種播放模式,即為: export const playMode = { sequen

android音樂播放器開發 SweetMusicPlayer 載入歌曲列表

路徑 本地 exc tao near import 設置 優先 特殊 上一篇寫了播放器的總體實現思路,http://blog.csdn.net/huweigoodboy/article/details/39855653,如今來總結下載入歌曲列表。 代碼地址

android 音樂播放器介面

一、團隊成員 姓名 學號 部落格地址 成凱 1600802002 http://www.cnblogs.com/ck03/ 黨康 1600802004 http://www.cnblogs.com/lxxxy/ 趙樂 1600802034 http://www.cnblogs.com/Z-y-H/ 二、

Android音樂播放

主要功能介紹         實現音樂暫停,播放,下一首,上一首; 程式執行截圖 核心程式碼解析     功能鍵的實現 public void onClick(View v) { switch(v.getId()){

Android音樂播放器的簡單實現

1、MusicService 音樂播放器的Service,裡面獲取音樂檔案,封裝了MediaPlayer,實現播放上一首和下一首,播放,停止,封裝成方法供Activity呼叫,獲取音樂的當前進度,總長度、名字,通過傳送廣播的方式發給Activity pa

android-音樂播放器實現及原始碼下載(一)

從本文開始,詳細講述一個音樂播放器的實現,以及從網路解析資料獲取最新推薦歌曲以及歌曲下載的功能。 功能介紹如下: 1、獲取本地歌曲列表,實現歌曲播放功能。 2、利用硬體加速感應器,搖動手機實現切換歌曲的功能 3、利用jsoup解析網頁資料,從網路獲取歌曲

android 音樂播放器小案例

案例主目的是為了複習一下Service服務 Handler訊息機制 學習一下mediaplayer類 和了解自定義控制元件 MainAcitivity.java package com.alleged.musicPlay; import android

android-----音樂播放器的音量控制功能(開發)

一、佈局檔案 在RelativeLayout佈局裡設定一個ImageButton,點選其彈出一個SeekBar(用於音量調節), 再在其下面巢狀一個RelativeLayout,裡面包含兩個ImageView元件、一個SeekBar元件。 此外, anim---push_u

android-音樂播放器實現及原始碼下載(四)

本系列博文,詳細講述一個音樂播放器的實現,以及從網路解析資料獲取最新推薦歌曲以及歌曲下載的功能。 功能介紹如下: 1、獲取本地歌曲列表,實現歌曲播放功能。 2、利用硬體加速感應器,搖動手機實現切換歌曲的功能 3、利用jsoup解析網頁資料,從網路

android 音樂播放工具類MediaPlayer

上程式碼 public class Player implements OnBufferingUpdateListener, OnCompletionListener, OnPreparedListener { public Me

android 使用代理模式切換不同的框架

我們平時在開發迭代中,隨著版本的更新以及需求的變化,可能這時候框架不能達到我們的要求,而這個時候另外有個框架可以達到,那我們如果要換的框架的話,這會涉及很多程式碼要改動,比如網路框架最早是用xutils,然後volley,現在大部分用的是okhttp,那麼我們怎麼用一行程式碼

android音樂播放播放音樂

自己在練習時寫了一個音樂播放器,但是放音樂的時候會卡 經過調查,才知道是在設定seekbar監聽的onProgressChanged這個方法中設定了player.seekTo(seekBar.getProgress());這樣就造成了音樂播放進度條改變,然後進度條的改變又會

android 音樂播放器最簡單的實現

package com.example.mouse.laymen; import android.app.Activity; import android.media.MediaPlayer; import android.os.Bundle; import androi

Android音樂播放器中的歌詞同步學習分析

在網上查了一下資料,感謝 http://www.cr173.com/html/20184_1.html 給了我思路,可以說他提供了最基本的歌詞同步的功能,我在其上面添加了自己的修改的程式碼。 主要是自己為了實現歌詞同步,並且通過移動seekbar,改變歌曲的歌詞位置。當然還

android 音樂播放器關於歌詞的處理

         當我們製作音樂播放器中我覺得歌詞的處理是比較難的一塊, 對於音樂播放和媒體控制我們可以使用MediaPlayer來搞定,它提供了媒體控制的介面,使得我們對於媒體控制來說變得比較簡單。但對於顯示歌詞來說就比較複雜了一點,例如讓歌詞一個字一個字高亮、快進時控制

android 音樂播放 啟動方式 (3)服務通過傳送廣播來控制activity顯示進度等

播放列表存放在裡application中,這個地方可以優化 1 PlayService中:播放路徑從intent中獲取 @Override public void onCreate() { super.onCreate(); mPlayer = ne

雙系統切換後Windows系統有聲音但耳機沒有聲音(筆記本)

在固態硬碟下,裝了兩個系統,一個是win10,一個是ubuntu系統。我發現在登入ubuntu系統並關閉後重啟win10,耳機沒有聲音,但是電腦外放是有聲音的。按照以往經驗,我以為是驅動問題,但是重灌了驅動也並沒有解決好。參考以前的帖子:參考方法一(我這樣就可以了,親測有效)

android 音樂播放控制元件

之前看到網頁版的網易音樂播放控制元件, 正好在一個開源學習專案中需要簡單的音樂播放功能。所以想是不是可以封裝一個音樂播放控制元件,提供一個類似網易播放控制元件的預設介面,而且提供更換介面的功能。使用時,只需要去設計介面, 而不用再去管音樂播放的邏輯,所以就實現了

如何寫一個正經的Android音樂播放器 一

關於音樂列表的讀取,不同的音樂播放器都會有不同的方案,有的是有多重方案並用。例如說,全盤掃描音樂格式檔案等,但是Android本身有媒體庫,可以讀取本地媒體庫的資料,來快速獲知裝置上的音樂。 首先我封裝了一個Audio類來儲存讀取的音訊資訊。 public class Audio { privat