MediaPlayer 的使用介绍

本文是基于Google的官方文档进行翻译,由于个人水平有限,如有翻译错误,请谅解
转载请标明出处:http://blog.csdn.net/songshizhuyuan/article/details/32900965

Android的多媒体框架提供用于播放各种常用媒体类型的支持,这样就可以将音频,视频和图像很容易地集成到您的应用程序。您可以采用MediaPlayer的API播放以下获取的音频或视频,从存储在应用程序中的资源(raw resources)媒体文件,从文件系统中的独立的媒体文件,或通过网络连接的数据流。

本文向您介绍如何编写一个为了性能良好并具有较好用户体验,用户和系统交互的媒体播放应用程序。

注意:您可以播放音频数据到标准输出设备,包括移动设备的扬声器或蓝牙耳机。您无法在通话过程中播放媒体文件。

The Basics

下面两个类在Android 框架上用于媒体(声音或视频)文件的播放

MediaPlayer

    这个类包含用于播放声音和视频的主要API

AudioManager

这个类用于管理音源,并在设备上的音频输出。

Manifest  Declarations

在你的应用程序的开发之前,如果使用MediaPlayer,请确保您的manifest有适当的声明,允许使用的相关功能。

InternetPermission -如果您使用MediaPlayer播放基于网络的流内容,应用程序必须具有联网权限

<uses-permission android:name="android.permission.INTERNET" />

Wake LockPermission -如果您的播放器应用程序需要对屏幕变暗或进入休眠状态的处理,或使用MediaPlayer.setScreenOnWhilePlaying()或MediaPlayer.setWakeMode()方法时,你必须要求此权限。

<uses-permission android:name="android.permission.WAKE_LOCK" />

UsingMediaPlayer

Android媒体框架的最重要的组成部分就是MediaPlayer类。这个类的一个对象以最小的设置来获取,解码和播放音频和视频。它支持多种不同的媒体源,如:

  •   本地资源
  •  内部的URI,比如一个你可能会从ContentResolver获取的uri
  •  外部URL(流)

对于Android支持的媒体格式列表,请参阅Android Supported Media Formats文件。

下面是如何播放本地资源音频(保存在你的应用程序的res/raw/ directory)的一个例子:

MediaPlayer mediaPlayer = MediaPlayer.create(context, R.raw.sound_file_1);
mediaPlayer.start(); // no need to call prepare(); create() does that for you

在这种情况下,“raw”是该系统不会尝试使用任何特定的方法来解析的文件。然而,这种资源的内容不应该是原始音频。它应该是MediaPlayer支持的经过正确编码的媒体文件。

下面是使用从本地系统中获取的URI(通过 Content Resolver获得的,例如):

Uri myUri = ....; // initialize Uri here
MediaPlayer mediaPlayer = new MediaPlayer();
mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
mediaPlayer.setDataSource(getApplicationContext(), myUri);
mediaPlayer.prepare();
mediaPlayer.start();

通过HTTP流从远程URL播放,例如:

String url = "http://........"; // your URL here
MediaPlayer mediaPlayer = new MediaPlayer();
mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
mediaPlayer.setDataSource(url);
mediaPlayer.prepare(); // might take long! (for buffering, etc)
mediaPlayer.start();

注意:如果你传递一个URL来播放在线媒体文件,该文件必须能够渐进式下载。

注意:您使用setDataSource时必须捕获或抛出IllegalArgumentException异常和IOException异常,因为你引用的文件可能不存在。

异步Preparation

使用MediaPlayer可以在主线程中一步一步的加载和设置。然而调用prepare()方法,系统可能需要很长的时间来执行,因为该方法可能涉及获取和解码媒体数据。因此,当该方法与可能需要很长时间其他方法一起来执行的情况下,你不应该从你的Application的UI线程中调用它。这样做会导致UI挂起直到该方法返回,这是一个非常糟糕的用户体验,并可能导致ANR(应用程序无响应)错误。即使你认为你的资源可以快速加载,请记住,任何时间超过第二次UI响应的十分之一会造成明显的停顿,给用户带来你的应用程序是缓慢的印象。

为了避免挂起你的​​UI线程,可以产生另一个线程编写的MediaPlayer并在完成后通知主线程。不过,虽然你可以自己编写线程逻辑,但是这种模式是非常普遍的使用MediaPlayer的方法,该框架提供了一个方便的方式,使用prepareAsync()方法来完成这个任务,这种方法在后台preparing媒体,该方法立即返回。当媒体preparing完成,通过setOnPreparedListener()设置的MediaPlayer.OnPreparedListener的onPrepared()方法被调用,该方法的调用说明

MediaPlayer Prepare完成。

状态管理

你应该牢记MediaPlayer的另一个方面是,它是基于状态的。也就是说,MediaPlayer有一个内部的状态,在你写代码的时候必须时刻意识到这个内部状态,因为某些方法仅适用当Mediaplayer在某些状态时,即player在特定状态。如果你在错误的状态下进行操作时,系统可能会抛出一个异常,或导致其他不可预知的行为。

在MediaPlayer类的文档显示了一个完整的状态图,该状态图标明了哪些方法将MediaPlayer从一个状态转到另一个状态。例如,当你创建一个新的MediaPlayer,它是在idle状态。在这一点上,你应该调用setDataSource()初始化这个对象,把它编变成初始化状态(Initialized)。在此之后,您可以选择使用的prepare()或prepareAsync()方法来让他进行prepare。当MediaPlayer prepare完成之后,它就会进入Prepared状态,这意味着你可以调用start()播放媒体信息。在这一点上,如状态图所示,您可以在Started,Paused和PlaybackCompleted状态之间进行切换,通过调用这些方法start(),pause()seekTo()等等。当你调用stop()之后,你不能调用start(),直到你再次prepare  MediaPlayer完成之前。

在你写代码的时候,需要时刻记住状态转换图,因为,在错误的状态调用方法是产生问题的一个最常见的错误。

Releasing the MediaPlayer 释放MediaPlayer

MediaPlayer会消耗系统资源。因此,你应该总是采取一些预防措施,以确保你不会在不必要的情况下一直持有一个MediaPlayer实例。当你不再使用MediaPlayer对象时,你应该调用release(),以确保分配给它的所有系统资源都被正确释放。例如,如果您正在使用一个MediaPlayer,你的Activity收到你一个电话时执行onStop(),你必须释放的MediaPlayer,因为继续持有这个Mediaplayer对象是毫无意义的,而你的Activity(含有MediaPlayer对象)不再与用户交互(除非你的应用在后台进行媒体文件播放,这个在下一章中进行讨论)。当你的Activity resumed 或者 restarted,在需要恢复播放之前,你需要创建一个新的MediaPlayer,并且重新prepare,下面是你应该如何释放你的MediaPlayer:

mediaPlayer.release();
mediaPlayer = null;

如果当你的Activity被停止时你忘了释放MediaPlayer,但创建一个新的Activity时再次创建一个MediaPlayer对象。正如你可能知道,当用户更改屏幕方向(或以另一种方式更改设备配置),系统默认情况下会通过重新启动Activity进行处理,如果用户频繁的旋转该设备,你的应用会迅速的消耗掉系统资源(所谓的内存泄露),因为在每一个方向变化,将创建一个新的MediaPlayer,创建的对象永远不释放。有关运行时重新启动的更多信息,请参见处理运行时更改。)

您可能想知道,当用户离开你的Activity时想在后台继续播放媒体文件,类似很多的内置音乐应用程序。在这种情况下,你需要由Service控制一个MediaPlayer,该内容在使用Service 进行MediaPlayer播放中进行讨论。

Using a Service with MediaPlayer.(在Service中使用MediaPlayer)

如果你想即使您的应用程序不在前台,用户与其它应用程序交互,你的程序被挂到后台也进行媒体文件的播放,也就是你想要它继续播放,那么必须启动一个Service,并从那里控制MediaPlayer的实例。你应该小心这种设置,因为用户和系统对于如何运行一个后台服务的应用程序应该与系统的其余部分进行交互条件。如果您的应用程序没有满足这些条件,用户可能有一个不好的体验。本节介绍你了解的主要问题,并提供有关如何处理它们的建议。

异步运行

首先,像一个Activity一样,默认情况下Service是在一个单独的线程中完成所有工作,事实上,如果你从同一个应用程序中运行的activity 和Service,在默认情况下它们使用相同的线程(“主线程中完成“)。因此,服务需要快速处理传入的intents,不应对他们进行冗长的计算,需要快速的返回。如果有任何繁重的工作或阻塞调用,你必须做到那些任务异步的:无论是你自己创建的另一个线程,或者使用框架的许多现有的异步线程进行异步处理。

例如,在主线程使用一个MediaPlayer的时候,你应该叫prepareAsync(),而不是prepare(),并以实现MediaPlayer.OnPreparedListener,当prepare完成后,你可以开始播放。例如:

public class MyService extends Service implements MediaPlayer.OnPreparedListener {
    private static final String ACTION_PLAY = "com.example.action.PLAY";
    MediaPlayer mMediaPlayer = null;

    public int onStartCommand(Intent intent, int flags, int startId) {
        ...
        if (intent.getAction().equals(ACTION_PLAY)) {
            mMediaPlayer = ... // initialize it here
            mMediaPlayer.setOnPreparedListener(this);
            mMediaPlayer.prepareAsync(); // prepare async to not block main thread
        }
    }

    /** Called when MediaPlayer is ready */
    public void onPrepared(MediaPlayer player) {
        player.start();
    }
}

处理异步错误

在同步操作,错误通常会用一个异常或错误的代码信号,但每当你使用异步的资源,你应该确保你的应用程序被适当告知错误的。在使用一个MediaPlayer的情况下,可以通过实现MediaPlayer.OnErrorListener接口,并在您的MediaPlayer实例中设置它的监听:

public class MyService extends Service implements MediaPlayer.OnErrorListener {
    MediaPlayer mMediaPlayer;

    public void initMediaPlayer() {
        // ...initialize the MediaPlayer here...

        mMediaPlayer.setOnErrorListener(this);
    }

    @Override
    public boolean onError(MediaPlayer mp, int what, int extra) {
        // ... react appropriately ...
        // The MediaPlayer has moved to the Error state, must be reset!
    }
}

重要的是要记住,当错误发生时,MediaPlayer的进入到错误状态(见MediaPlayer类的全状态图的文档),你必须重新reset之后,你可以再次使用它。

Using wake locks

如果你希望你的应用在后台进行媒体文件的播放,当你的service在后台运行的时候,设备可能会进入到休眠状态,因为Android 系统为节省电量会尝试在系统进入休眠后,关闭所有不必要的功能。包括CPU ,WIFI  以及硬件设备,然而,如果你的service正在播放媒体文件,如果你希望阻止系统进入休眠打断你的播放

为了确保您的服务继续在这些条件下运行,你必须使用“wake locks. 唤醒锁。”一个wake locks. 唤醒锁是一种标记到你的应用程序正在使用的一些功能,即使手机处于idle状态也应保持cpu唤醒。

注意:你应该确保必要的时候使用wake locks. 唤醒锁,因为他们显著降低设备的电池寿命。

为了确保你的MediaPlayer播放时CPU继续运行,需要在你的MediaPlayer创建时调用setWakeMode()方法进行初始化。一旦你这样做时,MediaPlayer在start时保存指定的锁,而暂停或停止播放时,需要释放锁,需要用户释放,系统不会自动释放:

mMediaPlayer = new MediaPlayer();
// ... other initialization here ...
mMediaPlayer.setWakeMode(getApplicationContext(), PowerManager.PARTIAL_WAKE_LOCK);

然而,在这个例子中获取的唤醒锁保证只有该CPU保持清醒。如果您使用的是Wi-Fi进行网络上的流媒体文件播放,你可能需要保持一个WifiLock为好,你必须获取并手动释放。所以,当你开始准备MediaPlayer与远程的URL进行连接时,你应该创建并获得了Wi-Fi锁。例如:

WifiLock wifiLock = ((WifiManager) getSystemService(Context.WIFI_SERVICE))
    .createWifiLock(WifiManager.WIFI_MODE_FULL, "mylock");
wifiLock.acquire();

当您暂停或停止媒体,或当你不再需要的网络,你应该释放锁:

wifiLock.release();

作为前台服务运行

服务通常用于执行后台任务,如获取电子邮件,同步数据,下载内容,或者其他可能性。在这些情况下,用户并不知道服务的执行,如果一些服务被中断并在此后重新启动用户也不会注意到。

但是考虑到使用service进行例如音乐播放,显然,如果这些服务被其他程序所干扰,这是用户可以明显感知得到的。此外,用户将很可能希望在其执行期间与用户进行互动。在这种情况下,服务应该作为运行“前台服务”。一个前台服务,在系统中拥有更高级别,系统甚至不会结束services ,因为他对于用户来说是相当重要的,如果service运行在前台,service还必须提供一个状态栏通知,确认用户可以知道服务的运行,并且可以通过状态栏进入一个Activity与之进行交互

为了将你的服务变成前台服务,您必须创建一个通知的状态栏,并从服务调用startForeground()。例如
String songName;
// assign the song name to songName
PendingIntent pi = PendingIntent.getActivity(getApplicationContext(), 0,
                new Intent(getApplicationContext(), MainActivity.class),
                PendingIntent.FLAG_UPDATE_CURRENT);
Notification notification = new Notification();
notification.tickerText = text;
notification.icon = R.drawable.play0;
notification.flags |= Notification.FLAG_ONGOING_EVENT;
notification.setLatestEventInfo(getApplicationContext(), "MusicPlayerSample",
                "Playing: " + songName, pi);
startForeground(NOTIFICATION_ID, notification);

当你的服务是在前台运行,您配置的通知是在设备的通知区域中可见。如果用户选择了该通知时,系统将调用你提供的PendingIntent。在上面的例子中,它会打开一个活动(MainActivity)。

您应该确保 “foreground service”的状态,也就是说您的服务实际上是执行一些用户用户的操作,用户是可以感知到的。一旦你的service不需要作为前台服务存在,你应该通过调用stopForeground()释放它
stopForeground(true);

欲了解更多信息,请参阅有关ServicesStatus Bar Notifications.文件。

处理音频焦点

在任何给定时间只有一个Activity可以运行,Android是一个多任务环境。在使用音频的应用中总会带来一些特殊的问题,因为只有一个音频输出,因此可能有几个媒体服务竞争使用。安卓2.2之前,没有内置的机制来解决这个问题,这可能在某些情况下导致一个不好的用户体验。例如,当用户正在听音乐和其它应用程序需要通知的一些非常重要的使用给用户时,由于大声的音乐用户可能无法听到的通知音。与Android2.2开始,该平台提供了一种应用程序进行谈判的机制其使用该设备的音频输出。这种机制被称为Audio Focus。

当你的应用程序需要输出音频,如音乐或通知时,您应该总是要获取音频焦点。一旦它具有焦点,它可以自由的使用设备声音输出,但它应该始终侦听焦点的变化。如果您被提示,它已经失去了音讯焦点,你的应用应立即结束音频播放或将音量降低到一个安静的水平(称为“回避”,有一个标志,指示哪一个是合适的),当他它重新获取到焦点后,可以恢复进行继续播放。

音频重点是合作性。也就是说,应用程序应该尽量配合,以符合音频焦点的指引,但规则不是由系统执行。如果应用程序失去音讯焦点后,大声播放音乐,系统也不会阻止。但是,这样会给用户一个不好的体验,设置导致用户卸载掉你的应用。

要请求音频焦点,您必须从AudioManager类调用requestAudioFocus(),如下面的例子所示:

AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
int result = audioManager.requestAudioFocus(this, AudioManager.STREAM_MUSIC,
    AudioManager.AUDIOFOCUS_GAIN);

if (result != AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
    // could not get audio focus.
}

第一个参数为requestAudioFocus()是一个AudioManager.OnAudioFocusChangeListener,其onAudioFocusChange()方法被调用每当有声音焦点的变化。所以,你也应该实现对应的接口在您的service和Activity。例如:

class MyService extends Service
                implements AudioManager.OnAudioFocusChangeListener {
    // ....
    public void onAudioFocusChange(int focusChange) {
        // Do something based on focus change...
    }
}

该focusChange参数将告诉您如何将音频发生了变化,而且可以是下列值(它们是在AudioManager类中定义的常量):

  •  AUDIOFOCUS_GAIN: You have gained the audio focus.
  • AUDIOFOCUS_LOSS: You have lost the audio focus for a presumably longtime. You must stop all audio playback. Because you should expect not to havefocus back for a long time, this would be a good place to clean up yourresources as much as possible. For example, you should release the MediaPlayer.
  • AUDIOFOCUS_LOSS_TRANSIENT: You have temporarily lost audio focus,but should receive it back shortly. You must stop all audio playback, but youcan keep your resources because you will probably get focus back shortly.
  •  AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK: You have temporarily lost audiofocus, but you are allowed to continue to play audio quietly (at a low volume)instead of killing audio completely.

下面是一个实现

public void onAudioFocusChange(int focusChange) {
    switch (focusChange) {
        case AudioManager.AUDIOFOCUS_GAIN:
            // resume playback
            if (mMediaPlayer == null) initMediaPlayer();
            else if (!mMediaPlayer.isPlaying()) mMediaPlayer.start();
            mMediaPlayer.setVolume(1.0f, 1.0f);
            break;

        case AudioManager.AUDIOFOCUS_LOSS:
            // Lost focus for an unbounded amount of time: stop playback and release media player
            if (mMediaPlayer.isPlaying()) mMediaPlayer.stop();
            mMediaPlayer.release();
            mMediaPlayer = null;
            break;

        case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
            // Lost focus for a short time, but we have to stop
            // playback. We don't release the media player because playback
            // is likely to resume
            if (mMediaPlayer.isPlaying()) mMediaPlayer.pause();
            break;

        case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
            // Lost focus for a short time, but it's ok to keep playing
            // at an attenuated level
            if (mMediaPlayer.isPlaying()) mMediaPlayer.setVolume(0.1f, 0.1f);
            break;
    }
}

需要注意的是,音频焦点API​​仅适用于API级别8(安卓2.2)及以上,所以如果你想支持Android的早期版本中,你应该采取向后兼容的策略,

您可以通过调用音频方法,重点通过反射或通过实现所有在一个单独的类对音频焦点功能(例如,AudioFocusHelper)实现向后兼容。这里是这样的类的例子:

public class AudioFocusHelper implements AudioManager.OnAudioFocusChangeListener {
    AudioManager mAudioManager;

    // other fields here, you'll probably hold a reference to an interface
    // that you can use to communicate the focus changes to your Service

    public AudioFocusHelper(Context ctx, /* other arguments here */) {
        mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
        // ...
    }

    public boolean requestFocus() {
        return AudioManager.AUDIOFOCUS_REQUEST_GRANTED ==
            mAudioManager.requestAudioFocus(mContext, AudioManager.STREAM_MUSIC,
            AudioManager.AUDIOFOCUS_GAIN);
    }

    public boolean abandonFocus() {
        return AudioManager.AUDIOFOCUS_REQUEST_GRANTED ==
            mAudioManager.abandonAudioFocus(this);
    }

    @Override
    public void onAudioFocusChange(int focusChange) {
        // let your service know about the focus change
    }
}

如果检测到系统正在运行的API级别8或以上,您可以创建AudioFocusHelper类的一个实例。例如:

if (android.os.Build.VERSION.SDK_INT >= 8) {
    mAudioFocusHelper = new AudioFocusHelper(getApplicationContext(), this);
} else {
    mAudioFocusHelper = null;
}

执行清理

正如前面提到的,一个MediaPlayer对象会显著的消耗一个系统资源,所以你应该只有在你需要的时候持有它,当你不需要的时候地调用release()进行及时的资源释放。需要注意的是需要手动调MediaPlayer的清理方法,而不是依赖于系统的垃圾回收,因为垃圾回收机制可能需要一些时间才能发现并回收MediaPlayer。由于MediaPlayer占用的是敏感的内存资源。所以,当你使用一个服务进行媒体文件的播放时,你应该总是覆盖的onDestroy()方法,以确保您可以释放MediaPlayer:

public class MyService extends Service {
   MediaPlayer mMediaPlayer;
   // ...

   @Override
   public void onDestroy() {
       if (mMediaPlayer != null) mMediaPlayer.release();
   }
}

除了在被关闭的时候释放,你应该考虑在其他的情况下释放MediaPlayer。例如,如果你很长一段时间不能够播放媒体(失去音讯焦点),你一定要释放现有的MediaPlayer,当再次使用时重新创建它。在另一方面,如果你只是希望停止播放很短的时间,你应该持有现在的MediaPlayer,以避免再次创建和prepare它的资源开销。

许多精心编写的应用程序,通过自动停止播放处理了播放音频时其他事件突然发生,举例来说,用户通过耳机听音乐时,不慎设备与耳机断开。(音乐不会继续播放,音乐会自动停止,即使耳机与手机断开,音乐也不会通过外放设备进行播放,除非用户在此点击开始)。如果不进行特殊的处理,音频将通过设备的外部扬声器播放,这可能不是用户想要的。

You can ensure your app stops playing musicin these situations by handling the ACTION_AUDIO_BECOMING_NOISY intent, forwhich you can register a receiver by adding the following to your manifest:

<receiver android:name=".MusicIntentReceiver">
   <intent-filter>
      <action android:name="android.media.AUDIO_BECOMING_NOISY" />
   </intent-filter>
</receiver>

This registers the MusicIntentReceiverclass as a broadcast receiver for that intent. You should then implement thisclass:

public class MusicIntentReceiver extends android.content.BroadcastReceiver {
   @Override
   public void onReceive(Context ctx, Intent intent) {
      if (intent.getAction().equals(
                    android.media.AudioManager.ACTION_AUDIO_BECOMING_NOISY)) {
          // signal your service to stop playback
          // (via an Intent, for instance)
      }
   }
}

转载请标明出处:http://blog.csdn.net/songshizhuyuan/article/details/32900965


  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值