多媒体编程
- 文字、图片、音频、视频(这里仅仅对银频和视频详解)
音频播放器
创建方法
方式一、
MediaPlayer mediaPlayer = MediaPlayer.create(context, R.raw.sound_file_1);
mediaPlayer.start(); // no need to call prepare(); create() does that for you
方式二、
MediaPlayer player= new MediaPlayer();
播放视频来源
1.播放本地文件
player.setDataSource("sdcard/zxmzf.mp3");
2.播放内部URI文件
Uri myUri = ....; // initialize Uri here
MediaPlayer mediaPlayer = new MediaPlayer();
mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
mediaPlayer.setDataSource(getApplicationContext(), myUri);
mediaPlayer.prepare();
mediaPlayer.start();
3.播放网络文件
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();
状态转换图
基本使用
MediaPlayer播放音频的基本使用步骤:
①创建一个MediaPlayer实例
②设置播放的数据格式
③设置数据源
④准备播放
⑤开始播放
整体的示例代码很简单:
// 1. 创建一个音频播放器实例
final MediaPlayer player = new MediaPlayer();
// 2. 设置数据格式
player.setAudioStreamType(AudioManager.STREAM_MUSIC);
player.setDataSource("/mnt/sdcard/zxxpg.mp3");// 此处也可以是网络路径
// player.prepare(); // 此处会阻塞主线程,建议prepareAsync
player.prepareAsync();
// 4. 当准备成功后回调
player.setOnPreparedListener(new OnPreparedListener() {
@Override
public void onPrepared(MediaPlayer mp) {
// 5. 开始播放
player.start();
}
});
音乐播放器的完善
该案例的实现分为以下几步:
①点击播放、暂停、继续按钮达到播放、暂停、继续的目的
②增加进度条,让进度随着播放进度而调整
③手指拖动进度条时,可以修改播放的位置。
④使用文字显示当前播放进度
由于这个小案例是在之前案例的基础之上修改过来了,所以先把骨架代码贴出来,然后再一步一步增加功能。
IService接口如下
public interface IService {
public void callPlay();
public void callPause();
public void callReplay();
}
MainActivity类如下:
public class MainActivity extends Activity implements OnSeekBarChangeListener {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.activity_main);
// 启动和绑定服务
Intent service = new Intent(this, MusicService.class);
startService(service);
conn = new MyServiceConnection();
bindService(service, conn, Context.BIND_AUTO_CREATE);
}
// 播放
public void play(View v) {iService.callPlay();}
// 暂停
public void pause(View v) { iService.callPause();}
// 重新播放
public void replay(View v) { iService.callReplay();}
private class MyServiceConnection implements ServiceConnection {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {iService = (IService) service; }
@Override
public void onServiceDisconnected(ComponentName name) {}
}
@Override
protected void onDestroy() {
super.onDestroy();
unbindService(conn);
}
}
MusicService类文件如下:
public class MusicService extends Service {
@Override
public void onCreate() {super.onCreate();}
@Override
public IBinder onBind(Intent intent) {
return new MyBinder();
}
// 播放音乐
public void play() {}
// 暂停
public void pause() {}
// 继续播放
public void replay() {}
// 中间人
private class MyBinder extends Binder implements IService {
@Override
public void callPlay() { play(); }
@Override
public void callPause() {pause();}
@Override
public void callReplay() {replay();}
}
}
activity_main.xml文件如下:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity" >
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="play"
android:text="播放音乐" />
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="pause"
android:text="暂停播放" />
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="replay"
android:text="继续播放音乐" />
</LinearLayout>
整体来说骨架代码还是很简单的,当应用启动时会启动并绑定服务,这样就可以通过中间人来调用服务中的方法了。
接下来就一步一步添加功能。
第一步,点击播放、暂停、继续按钮达到播放、暂停、继续的目的
首先回想一下如何播放音乐文件:初始化MediaPlay实例,配置数据源、类型,准备一下,然后就可以start播放了。
那么在这一步中,可以在服务的onCreate()方法中,实例化MediaPlay对象;当调用播放时,初始化资源并播放;暂停和继续播放就很简单了,只需要调用相应的方法就OK了。
增加的代码如下:
// 播放器实例
private MediaPlayer mediaPlayer;
@Override
public void onCreate() {
super.onCreate();
// 初始化实例对象
mediaPlayer = new MediaPlayer();
}
// 播放
public void play() {
try {
// 重置资源
mediaPlayer.reset();
// 初始化配置
mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
mediaPlayer.setDataSource("/mnt/sdcard/zuoqujia.mp3");
// 准备
mediaPlayer.prepareAsync();
// 准备完成的回调
mediaPlayer.setOnPreparedListener(new OnPreparedListener() {
@Override
public void onPrepared(MediaPlayer mp) {
mediaPlayer.start();
}
});
} catch (Exception e) {
e.printStackTrace();
}
}
// 暂停播放
public void pause() {mediaPlayer.pause();}
// 继续播放
public void replay() {mediaPlayer.start();}
第二步,增加进度条,让进度随着播放进度而调整
其实增加一个进度条是很简单的,直接拖一个SeekBar就可以达到目的。但是怎么样在服务中达到修改UI的目的却是这一步的难点,此外,SeekBar的进度条是要实时更新的,这些都是这一步的关键。
仔细回想一下之前的知识点,我们可以使用Handler在子线程中修改UI线程的数据,但是服务中怎么获取到Handler?可以这样,把MainActivity中的Handler改变为静态变量,这样就可以在服务类中使用了。
音乐播放要求每秒更新一次进度条的位置,我们可以使用SE中的Timer和TimerTask达到此目的。
有了以上这些解决问题的技术,赶快来看看代码是怎么写的。
首先在布局文件中加入SeekBar控件,并在MainActivity的找到该控件。
<SeekBar
android:id="@+id/sb_progress"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
private static SeekBar sbProgress; // 进度条
protected void onCreate(Bundle savedInstanceState) {
... ...
sbProgress = (SeekBar) findViewById(R.id.sb_progress);
... ...
}
然后在MainActivity中增加静态的Handler,在MusicService类增加发送消息的逻辑。
MainActivity类增加如下内容:
private static int duration; // 歌曲总时长,毫秒单位
private static int currentPosition; // 歌曲当前进度,毫秒单位
public static Handler handler = new Handler() {
public void handleMessage(android.os.Message msg) {
switch (msg.what) {
case MusicService.CURRENTPOSITION: // 设置进度
// 获取消息、更新进度条、更新当前播放位置
currentPosition = msg.arg1;
sbProgress.setProgress(currentPosition);
break;
case MusicService.DURATION: // 设置总时长
duration = msg.arg1;
sbProgress.setMax(duration);
break;
default:
break;
}
};
};
MusicService增加如下代码
public static final int DURATION = 0; // 音乐总时长
public static final int CURRENTPOSITION = 1;//音乐当前播放位置
public void play() {
try {
// 重置
mediaPlayer.reset();
// 初始化
mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
mediaPlayer.setDataSource("/mnt/sdcard/zuoqujia.mp3");
// 准备
mediaPlayer.prepareAsync();
// 准备完成的回调
mediaPlayer.setOnPreparedListener(new OnPreparedListener() {
@Override
public void onPrepared(MediaPlayer mp) {
// 开始播放
mediaPlayer.start();
// 获取音乐总进度,并发送消息
int duration = mediaPlayer.getDuration();
Message msg = Message.obtain();
msg.what = DURATION;
msg.arg1 = duration;
MainActivity.handler.sendMessage(msg);
// 每隔一秒更新进度
Timer timer = new Timer();
TimerTask task = new TimerTask() {
@Override
public void run() {
// 获取当前进度,并发送消息更新UI
int currentPosition = mediaPlayer.getCurrentPosition();
Message msg = Message.obtain();
msg.what = CURRENTPOSITION;
msg.arg1 = currentPosition;
MainActivity.handler.sendMessage(msg);
}
};
// 每隔一秒钟调用一次
timer.schedule(task, 0, 1000);
}
});
} catch (Exception e) {
e.printStackTrace();
}
}
做完这些,音乐播放时,进度到就会“动”了。
第三步,手指拖动进度条时,可以修改播放的位置。
达到这个目的,可以利用MediaPlayer中的seekTo()方法,定位播放位置。
那么我们需要做的很简单了,在IService中增加一个callSeekToProgress()方法,并在MainActivity和MusicService调用和编写。
IService增加如下内容:
public void callSeekToProgress(int currentPosition);
MainActivity增加如下内容:
// 把播放进度移动到指定位置
public void seekToProgress(int currentPosition) {
iService.callSeekToProgress(currentPosition);
}
在MusicService增加如下内容:
// 中间人
private class MyBinder extends Binder implements IService {
... ...
@Override
public void callSeekToProgress(int currentPosition) {
seekToProgress(currentPosition);
}
}
// 修改播放位置
public void seekToProgress(int currentPosition) {
mediaPlayer.seekTo(currentPosition);
}
第四步,使用文字显示当前播放进度
写完上面这些,增加这个功能就略显容易了。在布局文件中增加一个TextView,然后再Handler消息处理时,设置其内容就可以了。
tvCurrentProgress.setText("音乐共" + (int)(duration / 1000.0f) + "秒/当前" + (int)(currentPosition / 1000.0f) + "秒");
视频播放器
SurfaceView
- 对画面的实时更新要求较高
- 双缓冲技术:内存中有两个画布,A画布显示至屏幕,B画布在内存中绘制下一帧画面,绘制完毕后B显示至屏幕,A在内存中继续绘制下一帧画面
播放视频也是用MediaPlayer,不过跟音频不同,要设置显示在哪个SurfaceView,遗憾的是它只支持3GP和MP4格式的文件。
SurfaceView sv = (SurfaceView) findViewById(R.id.sv); SurfaceHolder sh = sv.getHolder(); MediaPlayer player = new MediaPlayer(); player.reset(); try { player.setDataSource("sdcard/2.3gp"); player.setDisplay(sh); player.prepare(); } catch (Exception e) { e.printStackTrace(); } player.start();
- SurfaceView是重量级组件,可见时才会创建
给SurfaceHolder设置CallBack,类似于侦听,可以知道SurfaceView的状态
sh.addCallback(new Callback() { //SurfaceView销毁时调用 @Override public void surfaceDestroyed(SurfaceHolder holder) { // TODO Auto-generated method stub } //SurfaceView创建时调用 @Override public void surfaceCreated(SurfaceHolder holder) { // TODO Auto-generated method stub } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { // TODO Auto-generated method stub } });
- SurfaceView一旦不可见,就会被销毁,一旦可见,就会被创建,销毁时停止播放,再次创建时再开始播放
视频播放(VideoView)
上面的SurfaceView略显麻烦,Google为我们提供了另外一个封装的控件VideoView,只需要几行代码就可以轻松展示一个3GP或者MP4文件。
VideoView vv = (VideoView) findViewById(R.id.vv);
vv.setVideoPath("/mnt/sdcard/proto.3gp");
vv.start();
vitamio框架
Android原生的SurfaceView和VideoView,在播放视频格式上都有限制,只能播放3GP和MP4。
Vitamio是一款开源框架,由一大堆C大神开发的,其中提供了Andoird版本的类库支持。
其中有一个VideoView类,和Andoird中原生的VideoView方法差不多,但是完美支持市面上的各种视频格式。
<io.vov.vitamio.widget.VideoView
android:id="@+id/vv"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
io.vov.vitamio.widget.VideoView vv = (VideoView) findViewById(R.id.vv);
vv.setVideoPath("/mnt/sdcard/test.3gp");
摄像头
启动系统提供的拍照程序
//隐式启动系统提供的拍照Activity Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); //设置照片的保存路径 File file = new File(Environment.getExternalStorageDirectory(), "haha.jpg"); intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(file)); startActivityForResult(intent, 0);
启动系统提供的摄像程序
Intent intent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE); File file = new File(Environment.getExternalStorageDirectory(), "haha.3gp"); intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(file)); //设置保存视频文件的质量 intent.putExtra(MediaStore.EXTRA_VIDEO_QUALITY, 1); startActivityForResult(intent, 0);