多媒体播放——MediaPlayer
简介
Android的多媒体框架包栝支持播放多种常见的媒体类型,使您可以轻松地把音频、视频和图像集成到你的应用。你可以播放音频或视频媒体文件,这些文件是存储在你的应用程序的资源文件中的。应用程序的资源文件可以是文件系统中独立的文件,或通过网络连接获取的-一个数据流,所有使用MediaPlayer APIS的资源文件。
**注意:**你只能在标准输出设备上檑放音频数据。目前,标准输出设备是移动设备的扬声器或耳机。你不能在谈话音频调用期间播放声音文件。
使用MediaPlayer
媒体框架中最重要的逐渐之一就当是MediaPlayer类,此类对象可以用少量的设置就能获得,解码和播放音视频。它支持多种媒体源,比如:
- 本地资源
- 内部URI,比如你从ContentResolver取得的URI。
- 外部URI(流媒体)
权限添加
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
播放本地视频文件,需要在res下创建一个raw的文件夹,存视频文件
public void playFromRes(View view){
MediaPlayer mp=MediaPlayer.create(this,R.raw.a1);
mp.start();
}
播放系统视频文件
public void playFromSys(View view){
MediaPlayer mp=new MediaPlayer();
String path= Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MOVIES)+"/2.mp4";
try {
mp.setDataSource(this, Uri.parse(path));//设置数据流
mp.prepare();//同步执行
mp.start();
} catch (IOException e) {
e.printStackTrace();
}
}
播放网络视频文件
public void playFromNet(View view){
String path="http:///big_buck_bunny.mp4";
MediaPlayer mp=new MediaPlayer();
try {
mp.setDataSource(this,Uri.parse(path));
mp.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
@Override
public void onPrepared(MediaPlayer mp) {
mp.start();
}
});
} catch (IOException e) {
e.printStackTrace();
}
}
注意: prepare()调用可能很耗时,因为它可能需要获取打开解码媒体数据,会执行很长时间,那么你就不能从你的应用的UI线程中调用它,否则会导致UI挂起。
使用异步Preparation
prepareAsync()
MediaPlayer.OnPreparedListener的onPrepared()
管理状态
MediaPlayer有一个内部的状态,因为特定的操作只能在特定的状态时才有效。如果你在错误的状态下执行一个操作,系统可能抛出一个异常或导致一个意外的行为。
这张状态转换图清晰的描述了MediaPlayer的各个状态,也列举了主要的方法的调用时序,每种方法只能在一些特定的状态下使用,如果使用时MediaPlayer的状态不正确则会引发IllegalStateException异常。
Idle 状态:
当使用new()方法创建一个MediaPlayer对象或者调用了其reset()方法时,该MediaPlayer对象处于idle状态。这两种方法的一个重要差别就是:如果在这个状态下调用了getDuration()等方法(相当于调用时机不正确),通过reset()方法进入idle状态的话会触发OnErrorListener.onError(),并且MediaPlayer会进入Error状态;如果是新创建的MediaPlayer对象,则并不会触发onError(),也不会进入Error状态。
End 状态:
通过release()方法可以进入End状态,只要MediaPlayer对象不再被使用,就应当尽快将其通过release()方法释放掉,以释放相关的软硬件组件资源,这其中有些资源是只有一份的(相当于临界资源)。如果MediaPlayer对象进入了End状态,则不会在进入任何其他状态了。
Initialized 状态:
这个状态比较简单,MediaPlayer调用setDataSource()方法就进入Initialized状态,表示此时要播放的文件已经设置好了。
Prepared 状态:
初始化完成之后还需要通过调用prepare()或prepareAsync()方法,这两个方法一个是同步的一个是异步的,只有进入Prepared状态,才表明MediaPlayer到目前为止都没有错误,可以进行文件播放。
Preparing 状态:
这个状态比较好理解,主要是和prepareAsync()配合,如果异步准备完成,会触发OnPreparedListener.onPrepared(),进而进入Prepared状态。
Started 状态:
显然,MediaPlayer一旦准备好,就可以调用start()方法,这样MediaPlayer就处于Started状态,这表明MediaPlayer正在播放文件过程中。可以使用isPlaying()测试MediaPlayer是否处于了Started状态。如果播放完毕,而又设置了循环播放,则MediaPlayer仍然会处于Started状态,类似的,如果在该状态下MediaPlayer调用了seekTo()或者start()方法均可以让MediaPlayer停留在Started状态。
Paused 状态:
Started状态下MediaPlayer调用pause()方法可以暂停MediaPlayer,从而进入Paused状态,MediaPlayer暂停后再次调用start()则可以继续MediaPlayer的播放,转到Started状态,暂停状态时可以调用seekTo()方法,这是不会改变状态的。
Stop 状态:
Started或者Paused状态下均可调用stop()停止MediaPlayer,而处于Stop状态的MediaPlayer要想重新播放,需要通过prepareAsync()和prepare()回到先前的Prepared状态重新开始才可以。
PlaybackCompleted状态:
文件正常播放完毕,而又没有设置循环播放的话就进入该状态,并会触发OnCompletionListener的onCompletion()方法。此时可以调用start()方法重新从头播放文件,也可以stop()停止MediaPlayer,或者也可以seekTo()来重新定位播放位置。
Error状态:
如果由于某种原因MediaPlayer出现了错误,会触发OnErrorListener.onError()事件,此时MediaPlayer即进入Error状态,及时捕捉并妥善处理这些错误是很重要的,可以帮助我们及时释放相关的软硬件资源,也可以改善用户体验。通过setOnErrorListener(.MediaPlayer.OnErrorListener)可以设置该监听器。如果MediaPlayer进入了Error状态,可以通过调用reset()来恢复,使得MediaPlayer重新返回到Idle状态。
释放MediaPlayer
MediaPlayer可能消耗大量的系统资源,因此你应该总是采取一些额外的措施来确保在一个MediaPlayer实例上不会挂起太长的时间。当你完成MediaPlayer时,你应该总是调用release()来保证任何分配给MediaPlayer的系统资源被正确释放。
使用服务控制MediaPlayer
简单来说就是后台播放。通过Service来实现MediaPlayer实例。
musicService
package com.example.mediaplay;
import android.annotation.SuppressLint;
import .Service;
import android.content.Context;
import android.content.Intent;
import .MediaPlayer;
import android.net.wifi.WifiManager;
import android.os.Environment;
import android.os.IBinder;
import android.os.PowerManager;
import java.io.IOException;
public class MusicService extends Service implements MediaPlayer.OnPreparedListener {
public static final String ACTION_PLAY="com.example.ACTION_PLAY";
public static final String ACTION_PAUSE="com.example.ACTION_PAUSE";
public static final String ACTION_EXIT="com.example.ACTION_EXIT";
private MediaPlayer mediaPlayer;
private WifiManager.WifiLock lock;
public MusicService() {
}
@Override
public void onCreate() {
super.onCreate();
mediaPlayer=new MediaPlayer();
mediaPlayer.setWakeMode(getApplicationContext(), PowerManager.PARTIAL_WAKE_LOCK);
//保持wifi不被休眠
@SuppressLint("WifiManagerLeak") WifiManager wifiManager= (WifiManager) getSystemService(Context.WIFI_SERVICE);
WifiManager.WifiLock lock=wifiManager.createWifiLock("mylock");
lock.acquire();
mediaPlayer.setOnPreparedListener(this);
}
@Override
public void onDestroy() {
super.onDestroy();
lock.release();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
String action=intent.getAction();
if (ACTION_PLAY.equals(action)){
mediaPlayer.reset();
try {
mediaPlayer.setDataSource(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MOVIES)+"/2.mp4");
mediaPlayer.prepareAsync();
} catch (IOException e) {
e.printStackTrace();
}
}else if (ACTION_PAUSE.equals(action)){
if (mediaPlayer.isPlaying())mediaPlayer.pause();
}else if (ACTION_EXIT.equals(action)){
if (mediaPlayer.isPlaying())mediaPlayer.stop();
mediaPlayer.release();
}
return super.onStartCommand(intent, flags, startId);
}
@Override
public IBinder onBind(Intent intent) {
// TODO: Return the communication channel to the service.
throw new UnsupportedOperationException("Not yet implemented");
}
@Override
public void onPrepared(MediaPlayer mp) {
mp.start();
}
}
Main端
public void play(View view){
Intent intent=new Intent(this,MusicService.class);
intent.setAction(MusicService.ACTION_PLAY);
startService(intent);
}
public void pause(View view){
Intent intent=new Intent(this,MusicService.class);
intent.setAction(MusicService.ACTION_PAUSE);
startService(intent);
}
public void exit(View view){
Intent intent=new Intent(this,MusicService.class);
intent.setAction(MusicService.ACTION_EXIT);
startService(intent);
}
清单文件
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WAKE_LOCK"/>
<service
android:name=".MusicService"
android:enabled="true"
android:exported="true"
android:process=":music">
<intent-filter>
<action android:name="com.example.ACTION_PLAY"/>
<action android:name="com.example.ACTION_PAUSE"/>
<action android:name="com.example.ACTION_EXIT"/>
</intent-filter>
</service>
前台服务
在Service中添加方法
private void notification() {
Notification.Builder builder=new Notification.Builder(this);
builder.setTicker("我的第一个音乐播放器");
builder.setSmallIcon(R.mipmap.ic_launcher);
builder.setContentTitle("我的音乐播放器");
builder.setContentInfo("正在播放");
PendingIntent pi=PendingIntent.getActivity(this,0,new Intent(this,Main3Activity.class),PendingIntent.FLAG_UPDATE_CURRENT);
builder.setContentIntent(pi);
NotificationManager nm= (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
Notification notification=builder.build();
startForeground(0,notification);
nm.notify(0,notification);
}
处理音频焦点
从AudioManager调用requestAudioFcus();
参数focusChange告诉你音频焦点如何发生了变化:
AUDIOFOCUS_GAIN :你已获得了音频焦点
AUDIOFOCUS_LOSS :你已经丢失了音频焦点比较长的时间了,你必须停止所有的音频檑放,因为预料到你可能很长时间也不能再获音频焦点,所以这里是清理你的资源的好地方,比如,你必须释放MediaPlayer。
AUDIOFOCUS_LOSS_TRANSIENT :你临时性的丢棹了音频焦点,很快就会重新获得,你必须停止所有的音频播放,但是可以保留你的资源,因为你可能很快就能重新获得焦点。
AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK :你临时性的丢掉了音频焦点,但是你被允许继续以低音量播放,而不是完全停止。
添加 AudioManager.OnAudioFocusChangeListener接口
重写onAudioFocusChange方法
@Override
public void onAudioFocusChange(int focusChange) {
switch (focusChange){
case AudioManager.AUDIOFOCUS_GAIN:
//以获取焦点
initMediaPlayer();
mediaPlayer.setVolume(1.0f,1.0f);
break;
case AudioManager.AUDIOFOCUS_LOSS:
//长期失去焦点
if (mediaPlayer!=null){
if (mediaPlayer.isPlaying())mediaPlayer.stop();
mediaPlayer.release();
}
break;
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
//失去焦点,但很快会获取
if (mediaPlayer!=null){
if (mediaPlayer.isPlaying())mediaPlayer.stop();
}
break;
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
//允许低音量播放
if (mediaPlayer.isPlaying())mediaPlayer.setVolume(0.1f,0.1f);
break;
}
}
并在onCreate方法中获取焦点
//获取音频焦点
AudioManager am= (AudioManager) getSystemService(AUDIO_SERVICE);
am.requestAudioFocus(this,AudioManager.STREAM_MUSIC,AudioManager.AUDIOFOCUS_GAIN);
将封装为一个方法来调用
mediaPlayer=new MediaPlayer();
mediaPlayer.setWakeMode(getApplicationContext(), PowerManager.PARTIAL_WAKE_LOCK);
//保持WIFI正常工作
WifiManager wifiManager= (WifiManager)getApplicationContext().getSystemService(Context.WIFI_SERVICE);
lock= wifiManager.createWifiLock("mylock");
lock.acquire();
mediaPlayer.setOnPreparedListener(this);
notification();
处理AUDIO_BECOMING_NOISY意图
很多良好的音频播放的应用都会在那些到时声音变为噪音(通过外部扬声器输出)的事件发生时自动停止播放。例如,这可能发生在用户用耳机听音乐时忽然断开耳机连接。
创建一个广播接收器
package com.example.mediaplay;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
public class MusicIntentReceiver extends BroadcastReceiver {
private static final String action=".AUDIO_BECOMING_NOISY";
public MusicIntentReceiver(){
}
@Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals(action)){
Intent intent1=new Intent(MusicService.ACTION_STOP);
context.startService(intent1);
}
}
}
在MusicService中注册action
public static final String ACTION_STOP="com.example.ACTION_STOP";
//onStartCommand方法下添加
else if (ACTION_STOP.equals(action)){
if (mediaPlayer.isPlaying())mediaPlayer.stop();
}
清单文件
<receiver
android:name=".MusicIntentReceiver"
android:enabled="true"
android:exported="true">
<intent-filter>
<action android:name=".AUDIO_BECOMING_NOISY"/>
</intent-filter>
</receiver>
//在Service中
<action android:name="com.example.ACTION_STOP" />
从ContentResolver获取媒体
媒体播放应用的是另一个有用的特性是检索用户存放在设备上的音乐。你可以通过从ContentResolver查询媒体来做到:
图中的数据库文件为模拟器中存储图片、媒体的数据库。
public void queryMusic(View view){
ContentResolver cr=getContentResolver();
Cursor c=cr.query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,null,null,null,null);
if (c!=null){
while (c.moveToNext()){
String url=c.getString(c.getColumnIndex(MediaStore.Audio.Media.DATA));
String displayName=c.getString(c.getColumnIndex(MediaStore.Audio.Media.DISPLAY_NAME));
String artist=c.getString(c.getColumnIndex(MediaStore.Audio.Media.ARTIST));
String time=c.getString(c.getColumnIndex(MediaStore.Audio.Media.DURATION));
System.out.println("路径:"+url);
System.out.println("歌名:"+displayName);
System.out.println("歌手:"+artist);
System.out.println("时间:"+time);
System.out.println("------------------");
}
}
}