天天看点

Android开发指南(34) —— Multimedia and Camera - Media Playback

前言

声明

  欢迎转载,但请保留文章原始出处:) 

媒体播放

译者署名: 呆呆大虾

版本:android 4.0 r1

原文

<a href="http://developer.android.com/guide/topics/media/mediaplayer.html">http://developer.android.com/guide/topics/media/mediaplayer.html</a>

在本文中

<a href="http://www.cnblogs.com/over140/archive/2011/11/17/2252210.html#basic">简介</a>

<a href="http://www.cnblogs.com/over140/archive/2011/11/17/2252210.html#manifest">manifest声明</a>

<a href="http://www.cnblogs.com/over140/archive/2011/11/17/2252210.html#mediaplayer">mediaplayer的使用</a>

<a href="http://www.cnblogs.com/over140/archive/2011/11/17/2252210.html#async_prep">异步准备</a>

<a href="http://www.cnblogs.com/over140/archive/2011/11/17/2252210.html#state">状态管理</a>

<a href="http://www.cnblogs.com/over140/archive/2011/11/17/2252210.html#release">释放mediaplayer</a>

<a href="http://www.cnblogs.com/over140/archive/2011/11/17/2252210.html#service">使用带mediaplayer的服务</a>

<a href="http://www.cnblogs.com/over140/archive/2011/11/17/2252210.html#async_run">异步运行</a>

<a href="http://www.cnblogs.com/over140/archive/2011/11/17/2252210.html#async_error">异步错误的处理</a>

<a href="http://www.cnblogs.com/over140/archive/2011/11/17/2252210.html#wakelock">wake lock的使用</a>

<a href="http://www.cnblogs.com/over140/archive/2011/11/17/2252210.html#run_as_service">作为后台服务运行</a>

<a href="http://www.cnblogs.com/over140/archive/2011/11/17/2252210.html#cleanup">进行清理</a>

<a href="http://www.cnblogs.com/over140/archive/2011/11/17/2252210.html#contentresolver">从content resolver中读取媒体</a>

关键类

<a href="http://developer.android.com/reference/android/media/mediaplayer.html">mediaplayer</a>

<a href="http://developer.android.com/reference/android/media/audiomanager.html">audiomanager</a>

<a href="http://developer.android.com/reference/android/media/soundpool.html">soundpool</a>

参阅

<a href="http://developer.android.com/guide/topics/media/jetplayer.html">jetplayer</a>

<a href="http://developer.android.com/guide/topics/media/audio-capture.html">音频捕获</a>

<a href="http://developer.android.com/guide/appendix/media-formats.html">android支持的媒体格式</a>

<a href="http://developer.android.com/guide/topics/data/data-storage.html">数据存储</a>

本文演示了如何编写一个媒体播放程序。为了兼顾良好的性能和舒适的用户体验,它还实现了播放期间用户和系统之间的交互。

注意: 只能在标准的输出设备上播放音频数据,目前即为移动设备的扬声器或蓝牙耳机。并且不能在通话期间同时播放音频文件。

简介

下列类用于在android框架中播放音视频:

本类是播放音视频的主要api。

本类管理音频源和设备的音频输出。

manifest声明

在开始开发mediaplayer的应用程序之前,请确保manifest已经正确地声明了以下相关feature:

·       internet permission —— 如果正在用mediaplayer来播放基于网络的流媒体,应用程序必须请求网络访问权限。

&lt;uses-permission android:name="android.permission.internet" /&gt;

&lt;uses-permission android:name="android.permission.wake_lock" /&gt;

媒体播放器的使用

·       本地资源

·       内部uri,比如可能来自content resolver

·       外部url(流)

下面的例子展示了如何播放本地以裸资源方式提供的音频(保存于程序的res/raw/目录下):

mediaplayer mediaplayer = mediaplayer.create(context, r.raw.sound_file_1); 

mediaplayer.start(); // 不必调用prepare(); create()会自动调用

这里的“裸”资源是指系统不会以任何特定方式进行解析的文件。当然,这个资源的内容不应该是原始音频数据,而应是用所支持的格式正确编码并格式化过的媒体文件。

下面是如何播放来自系统本地提供的uri资源(比如通过content resolver获取的):

uri myuri = ....; // 在此初始化uri

mediaplayer mediaplayer = new mediaplayer(); 

mediaplayer.setaudiostreamtype(audiomanager.stream_music); 

mediaplayer.setdatasource(getapplicationcontext(), myuri); 

mediaplayer.prepare(); 

mediaplayer.start();

下例是播放来自远程url的http流:

string url = "http://........"; // 在此指定url

mediaplayer.setdatasource(url); 

mediaplayer.prepare(); // 可能会耗时很长! (需创建缓存等)mediaplayer.start();

注意:如果通过url来传送一个来自在线媒体文件的数据流,则该文件必须支持渐进下载(progressive download)。

<a>异步准备</a>

<a>状态管理</a>

mediaplayer.release(); 

mediaplayer = null;

使用带mediaplayer的服务

<a>异步运行</a>

public class myservice extends service implements mediaplayer.onpreparedlistener { 

    private static final 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 = ... // 在此初始化

            mmediaplayer.setonpreparedlistener(this); 

            mmediaplayer.prepareasync();

            // 为了不阻塞主线程而异步准备

        } 

    } 

    /** 由mediaplayer准备完毕后调用 */ 

    public void onprepared(mediaplayer player) { 

        player.start(); 

}

<a>异步错误的处理</a>

public class myservice extends service implements mediaplayer.onerrorlistener { 

    mediaplayer mmediaplayer; 

    public void initmediaplayer() { 

        // ...在此初始化mediaplayer... 

        mmediaplayer.setonerrorlistener(this); 

    @override 

    public boolean onerror(mediaplayer mp, int what, int extra) { 

        // ... 合适地处理 ... 

        // mediaplayer已经切换到error状态,必须重启! 

如果应用程序是为后台播放媒体而设计的,那么即使服务仍在运行,但设备可能会进入休眠状态。因为设备休眠时android系统会尝试节省电力,任何不必要的手机功能将会关闭,包括cpu和wifi部件。但是,如果服务正在播放音乐或读取音乐数据流,就需要防止系统对播放进行干扰。

为了确保服务在上述情况下能维持正常运行,必须使用“唤醒锁”(wake lock)。wake lock是一种通知系统的途径:表示应用程序需要用到一些手机空闲时也保持可用的功能。

注意: 应该尽量少用wake lock,并且仅当确实需要时才保持锁定状态,因为它会显著减少设备的电池寿命。

mmediaplayer = new mediaplayer(); 

// ... 在此执行其它初始化工作 ... 

mmediaplayer.setwakemode(getapplicationcontext(), powermanager.partial_wake_lock);

wifilock wifilock = ((wifimanager) getsystemservice(context.wifi_service)) 

    .createwifilock(wifimanager.wifi_mode_full, "mylock"); 

wifilock.acquire();

如果暂停或停止播放媒体,或者不再需要使用网络,就应该及时释放该锁:

wifilock.release();

<a>作为后台服务运行</a>

服务经常用于执行一些后台任务,比如读取邮件、同步数据、下载数据等。这些情况下,用户不会明显察觉服务正在运行,甚至可能都不会注意到某些服务曾被中止而过段时间又重新开始运行。

但是就播放音乐的服务而言,显然这是用户能明显察觉的服务,任何中断都会显著影响到用户的体验。此外,该服务还是用户可能期望与其交互的服务。在这种情况下,此服务应该作为“后台服务”来运行。后台服务保持较高的系统重要性级别——系统几乎永远都不会关闭服务,因为服务对于用户而言至关重要。即使是运行在后台,服务仍必须提供状态栏通知,以保证用户知晓服务正在运行,并允许用户打开与服务交互的activity。

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);

图1展示了通知如何显示给用户:

Android开发指南(34) —— Multimedia and Camera - Media Playback
Android开发指南(34) —— Multimedia and Camera - Media Playback

图1.后台服务通知的屏幕截图,左图是状态栏的通知图标,右图是展开的view。

stopforeground(true);

虽然在任一给定时刻只能运行一个activity,android仍是一个多任务环境。这向使用音频的应用程序提出了一个特殊的挑战,因为系统只有一路音频输出,但可能会存在多个媒体服务,它们会相互争夺这个音频输出的使用权。android 2.2之前,没有什么内部机制来解决这个问题,某些情况下这可能会导致用户体验很糟糕。比如在用户听音乐时,其它应用程序需要通知用户一个非常重要的事件,用户可能会由于音乐声音较大而听不到通知提示音。自android 2.2开始,系统为应用程序提供了一种使用设备音频输出的协调机制。这种机制叫做audio focus。

当应用程序需要输出诸如音乐或通知音之类的音频时,应该总是提出audio focus请求。一但获得了focus,就可以自由使用音频输出,但应该时刻注意focus的变化情况。一旦接到放弃audio focus的通知,就应该立即关闭音频或者把音量调低至静音状态(正如“ducking”——此标志表明哪一个更合适),并且在再次获得focus之后才能恢复正常音量播放。

audio focus事实上具有良好的协作性。也就是说,虽然期望(强烈建议)应用程序能遵守audio focus规则,但此规则并不是系统强制要求的。如果应用程序需要在失去audio focus后也能以正常音量播放音乐,系统也不会阻止。但是用户体验很可能会很糟糕,并且很可能会删除这种不够礼貌的应用程序。

audiomanager audiomanager = (audiomanager) getsystemservice(context.audio_service); 

int result = audiomanager.requestaudiofocus(this, audiomanager.stream_music, 

    audiomanager.audiofocus_gain); 

if (result != audiomanager.audiofocus_request_granted) { 

    // 无法获取audio focus. 

class myservice extends service 

                           implements audiomanager.onaudiofocuschangelistener { 

    // .... 

    public void onaudiofocuschange(int focuschange) { 

        // 根据focus的改变进行处理... 

以下是实现的例子:

public void onaudiofocuschange(int focuschange) { 

    switch (focuschange) { 

        case audiomanager.audiofocus_gain: 

            // 恢复播放

            if (mmediaplayer == null) initmediaplayer(); 

            else if (!mmediaplayer.isplaying()) mmediaplayer.start(); 

            mmediaplayer.setvolume(1.0f, 1.0f); 

            break; 

        case audiomanager.audiofocus_loss: 

            // 长时间失去focus:停止播放并释放media player 

            if (mmediaplayer.isplaying()) mmediaplayer.stop(); 

            mmediaplayer.release(); 

            mmediaplayer = null; 

        case audiomanager.audiofocus_loss_transient: 

            // 暂时失去focus,但必须停止播放

            // 可能会很快恢复播放,所以不释放media player

            if (mmediaplayer.isplaying()) mmediaplayer.pause(); 

        case audiomanager.audiofocus_loss_transient_can_duck: 

            // 暂时失去focus,但可以保持较低级别的播放

            if (mmediaplayer.isplaying()) mmediaplayer.setvolume(0.1f, 0.1f); 

请记住audio focus api在api level 8 (android 2.2)以上版本才可用。假如需要支持较低版本的android,应该采取向后兼容的方案,使得程序能在获得支持时使用此功能、未获支持时则向下平滑过渡。

通过反射机制调用audio focus方法,或者在单独的类中(叫做audiofocushelper)实现全部audio focus功能,就可以获得良好的向后兼容性。以下是这种类的一个示例:

public class audiofocushelper implements audiomanager.onaudiofocuschangelistener { 

    audiomanager maudiomanager; 

// 在此写入其它内容,

// 将保存一个接口的引用,用于通知服务focus 已发生改变。

    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() { 

            maudiomanager.abandonaudiofocus(this); 

        // 服务获知focus的变动

仅当检测到系统运行于api level 8以上版本时,才可以创建audiofocushelper类的实例。比如:

if (android.os.build.version.sdk_int &gt;= 8) { 

    maudiofocushelper = new audiofocushelper(getapplicationcontext(), this); 

} else { 

    maudiofocushelper = null; 

<a>进行清理</a>

public class myservice extends service { 

   mediaplayer mmediaplayer; 

   // ... 

   @override 

   public void ondestroy() { 

       if (mmediaplayer != null) mmediaplayer.release(); 

   } 

对意图audio_becoming_noisy的处理

在事件到来时,很多编码优秀的音频播放程序都会自动停止播放,因为事件会让音频输出产生噪音(通过外部扬声器播放出来)。比如,用户用耳机听音乐时突然把耳机从设备上拔出来,就可能会产生噪音。不过好在这种现象不会自动发生。如果不实现暂停播放,声音就会从外部扬声器中传出,这可能是用户不愿意听到的。

&lt;receiver android:name=".musicintentreceiver"&gt; 

   &lt;intent-filter&gt; 

      &lt;action android:name="android.media.audio_becoming_noisy" /&gt; 

   &lt;/intent-filter&gt; 

&lt;/receiver&gt;

以下代码注册了musicintentreceiver类,用作该意图的广播接收器:

public class musicintentreceiver implements android.content.broadcastreceiver { 

   public void onreceive(context ctx, intent intent) { 

      if (intent.getaction().equals( 

                    android.media.audiomanager.action_audio_becoming_noisy)) { 

          // 通知服务来停止播放

          // (比如通过一个意图)

      } 

从content resolver中读取媒体

contentresolver contentresolver = getcontentresolver(); 

uri uri = android.provider.mediastore.audio.media.external_content_uri; 

cursor cursor = contentresolver.query(uri, null, null, null, null); 

if (cursor == null) { 

    // 查询失败,处理错误。

} else if (!cursor.movetofirst()) { 

    //设备上不存在媒体文件

    int titlecolumn = cursor.getcolumnindex(android.provider.mediastore.audio.media.title); 

    int idcolumn = cursor.getcolumnindex(android.provider.mediastore.audio.media._id); 

    do { 

       long thisid = cursor.getlong(idcolumn); 

       string thistitle = cursor.getstring(titlecolumn); 

       // ...开始处理... 

    } while (cursor.movetonext()); 

long id = /* 从某处读取 */; 

uri contenturi = contenturis.withappendedid( 

        android.provider.mediastore.audio.media.external_content_uri, id); 

mmediaplayer.setaudiostreamtype(audiomanager.stream_music); 

mmediaplayer.setdatasource(getapplicationcontext(), contenturi); 

// ...准备并开始播放...

转载:http://www.cnblogs.com/over140/archive/2011/11/17/2252210.html

继续阅读