56. 【Android教程】媒体播放器:MediaPlayer

简介: 56. 【Android教程】媒体播放器:MediaPlayer

Android 系统提供了几种播放音频和视频的方式,其中最常用的就是 MediaPlayer,和其他功能组件一样都有很多第三方框架提供更加丰富完备的功能,但是基本用法和时序基本都是参照 MediaPlayer 来设计的,本节就来看看 MediaPlayer 的使用方法。



1. MediaPlayer 的状态

MediaPlayer 有一套完善的状态机,通常出现一些奇怪的报错或者 Crash 大概率就是状态流转出了问题,而市面上大多数的播放器也会遵循 Android 官方设计的这套状态机来实现。首先看看所有的状态:


Idle:

空闲态,刚创建或者调用了reset()之后的状态,此时不能进行播放

Initailized:

初始化态,仅仅设置了媒体源,但还未进行任何网络资源的拉取或者媒体流的解析,此时仍然不能播放

Preparing:

准备中,触发了媒体流的下载以及媒体流的解析,但均未完成,处于准备中,尚不能进行播放

Prepared:

准备好,已经将媒体资源拉取并解析完成,随时可以开始播放

Started:

播放态,在媒体资源准备好之后,调用了start()触发了媒体的播放,则进入视频 / 音频播放

Paused:

暂停态,这个很好理解,视频 / 音频播放暂停,此时可以随时调用start()继续播放回到Started状态

PlaybackCompleted:

播放结束态,视频 / 音频播放到结尾,自然结束

Stoped:

停止态,在播放或者暂停过程中主动调用stop()停止播放,注意它和暂停态不同,“Stoped”态不能直接回到播放态;它和

播放结束态也不同,“Stoped”一定是由开发者主动触发的

End:

释放态,播放器调用release()触发播放器资源的释放,此时播放器资源被回收将不能使用

Error:

错误态,如果由于某种原因 MediaPlayer 出现了错误,会触发 OnErrorListener.onError()事件,此时 MediaPlayer 即进入 Error 状态,及时捕捉并妥善处理这些错误是很重要的,可以帮助我们及时释放相关的软硬件资源,也可以改善用户体验。通过setOnErrorListener可以设置该监听器。如果MediaPlayer进入了Error状态,可以通过调用reset()来恢复,使得MediaPlayer重新返回到 Idle 状态。

下面可以对照着状态看看官方给的状态机流转图:

这个图非常经典,建议大家收藏此文章,今后使用 MediaPlayer 过程中出现任何问题都可以看看状态机是否出现异常。

2. MediaPlayer 常用 API

使用 MediaPlayer 的 API 之前一定要先熟悉熟悉再熟悉上一小节的状态机时序图,否则盲目使用 API 会出现很多状态错误的异常发生。

setDataSource(FileDescriptor fd):

设置音频 / 视频资源地址


  • isPlaying():
  • 判断当前视频 / 音频是否正在播放
  • seekTo(position):
  • 直接跳转到视频 / 音频的某个时间点
  • getCurrentPosition():
  • 获取当前的播放进度
  • getDuration():
  • 获取媒体文件的总时长
  • reset():
  • 重置 MediaPlayer,此后会进入 Idle 态
  • release():
  • 释放播放器,在不使用的时候调用,节省系统资源
  • setVolume(float leftVolume, float rightVolume):
  • 设置媒体音量
  • selectTrack(int index):
  • 设置媒体轨道
  • getTrackInfo():
  • 返回一个数组,包含所有的轨道信息

3. MediaPlayer 使用步骤

Android 系统为 MediaPlayer 适配了多种场景,也为不同的场景提供了不同的使用方式,但大体上有几个步骤:

  1. 创建播放器
    创建通常有两种方法
  2. 第一种方式直接在创建的时候传入媒体流地址,而第二种仅仅是创建一个“Idle”态的闲置播放器
  1. 设置媒体源
    Android 提供下面的方法设置媒体数据源
mediaPlayer.setDataSource(www.mc.com/mc.mp3);

如果在创建 MediaPlayer 的时候就设置了媒体文件,那么可以跳过这一步

  1. 开始播放
    在设置好媒体源地址之后,就可以开始播放了:
mediaPlayer.start();
  1. 播放控制
    在播放过程中可以调用一些控制 API 进行播放状态的控制
  2. 结束播放
    调用stop()可以结束播放,并且记得在不用的时候还要调用release()

4. 播放器使用示例

本节来用 MediaPlayer 实现一个简单的播放器,并通过几个 API 来实现基本的播放控制。

4.1 MediaPlayer 的使用

首先看看 MainActivity:

 
package com.emercy.myapplication;
 
import android.app.Activity;
import android.media.MediaPlayer;
import android.os.Bundle;
import android.os.Handler;
import android.view.View;
import android.widget.Button;
import android.widget.SeekBar;
import android.widget.TextView;
import android.widget.Toast;
import java.util.concurrent.TimeUnit;
 
public class MainActivity extends Activity {
    private Button b1, b2, b3, b4;
    private MediaPlayer mediaPlayer;
 
    private double startTime = 0;
    private double finalTime = 0;
 
    private Handler myHandler = new Handler();
    private int forwardTime = 5000;
    private int backwardTime = 5000;
    private SeekBar seekbar;
    private TextView tx1, tx2, tx3;
 
    public static int oneTimeOnly = 0;
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
 
        b1 = (Button) findViewById(R.id.button);
        b2 = (Button) findViewById(R.id.button2);
        b3 = (Button) findViewById(R.id.button3);
        b4 = (Button) findViewById(R.id.button4);
 
        tx1 = (TextView) findViewById(R.id.textView2);
        tx2 = (TextView) findViewById(R.id.textView3);
        tx3 = (TextView) findViewById(R.id.textView4);
 
        mediaPlayer = MediaPlayer.create(this, R.raw.video);
        seekbar = (SeekBar) findViewById(R.id.seekBar);
        seekbar.setClickable(false);
        b2.setEnabled(false);
 
        b1.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                int temp = (int) startTime;
 
                if ((temp + forwardTime) <= finalTime) {
                    startTime = startTime + forwardTime;
                    mediaPlayer.seekTo((int) startTime);
                    Toast.makeText(getApplicationContext(), "前进5秒", Toast.LENGTH_SHORT).show();
                } else {
                    Toast.makeText(getApplicationContext(), "前方还剩不到5秒", Toast.LENGTH_SHORT).show();
                }
            }
        });
 
        b2.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Toast.makeText(getApplicationContext(), "Pausing sound", Toast.LENGTH_SHORT).show();
                mediaPlayer.pause();
                b2.setEnabled(false);
                b3.setEnabled(true);
            }
        });
 
        b3.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Toast.makeText(getApplicationContext(), "音频播放", Toast.LENGTH_SHORT).show();
                mediaPlayer.start();
 
                finalTime = mediaPlayer.getDuration();
                startTime = mediaPlayer.getCurrentPosition();
 
                if (oneTimeOnly == 0) {
                    seekbar.setMax((int) finalTime);
                    oneTimeOnly = 1;
                }
 
                tx2.setText(String.format("%d min, %d sec",
                        TimeUnit.MILLISECONDS.toMinutes((long) finalTime),
                        TimeUnit.MILLISECONDS.toSeconds((long) finalTime) -
                                TimeUnit.MINUTES.toSeconds(TimeUnit.MILLISECONDS.toMinutes((long)
                                        finalTime)))
                );
 
                tx1.setText(String.format("%d min, %d sec",
                        TimeUnit.MILLISECONDS.toMinutes((long) startTime),
                        TimeUnit.MILLISECONDS.toSeconds((long) startTime) -
                                TimeUnit.MINUTES.toSeconds(TimeUnit.MILLISECONDS.toMinutes((long)
                                        startTime)))
                );
 
                seekbar.setProgress((int) startTime);
                myHandler.postDelayed(UpdateSongTime, 100);
                b2.setEnabled(true);
                b3.setEnabled(false);
            }
        });
 
        b4.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                int temp = (int) startTime;
 
                if ((temp - backwardTime) > 0) {
                    startTime = startTime - backwardTime;
                    mediaPlayer.seekTo((int) startTime);
                    Toast.makeText(getApplicationContext(), "后退5秒", Toast.LENGTH_SHORT).show();
                } else {
                    Toast.makeText(getApplicationContext(), "后方还剩不到5秒", Toast.LENGTH_SHORT).show();
                }
            }
        });
    }
 
    private Runnable UpdateSongTime = new Runnable() {
        public void run() {
            startTime = mediaPlayer.getCurrentPosition();
            tx1.setText(String.format("%d min, %d sec",
                    TimeUnit.MILLISECONDS.toMinutes((long) startTime),
                    TimeUnit.MILLISECONDS.toSeconds((long) startTime) -
                            TimeUnit.MINUTES.toSeconds(TimeUnit.MILLISECONDS.
                                    toMinutes((long) startTime)))
            );
            seekbar.setProgress((int) startTime);
            myHandler.postDelayed(this, 100);
        }
    };
}

通过MediaPlayer.create(this, R.raw.video)创建一个 MediaPlayer 对象并初始化媒体流地址,然后分别设置几个控制 Button 的监听事件,实现播放器的前进、后退、播放、暂停操作,当中还有一个UpdateSongTime的 Runnable 变量,用来每隔 100 毫秒更新一次播放进度,实现播放进度的同步刷新。



4.2 布局文件

布局文件就按照片一般播放器的摆放方式就可以,上面通常是页面的主题和描述,下方就是进度条、歌曲名称时长等等。

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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:padding="30dp"
    tools:context=".MainActivity">
 
    <TextView
        android:id="@+id/textview"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentTop="true"
        android:layout_centerHorizontal="true"
        android:text="音乐播放器"
        android:textSize="35dp" />
 
    <TextView
        android:id="@+id/textView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@+id/textview"
        android:layout_centerHorizontal="true"
        android:text="Android教程"
        android:textColor="#ff7aff24"
        android:textSize="35dp" />
 
    <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentStart="true"
        android:layout_alignParentBottom="true"
        android:text="前进" />
 
    <Button
        android:id="@+id/button2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_toRightOf="@id/button"
        android:text="暂停" />
 
    <Button
        android:id="@+id/button3"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignTop="@+id/button2"
        android:layout_toEndOf="@+id/button2"
        android:text="播放" />
 
    <Button
        android:id="@+id/button4"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignTop="@+id/button3"
        android:layout_toEndOf="@+id/button3"
        android:layout_toRightOf="@+id/button3"
        android:text="后退" />
 
    <SeekBar
        android:id="@+id/seekBar"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_above="@+id/button"
        android:layout_alignStart="@+id/textview"
        android:layout_alignEnd="@+id/textview" />
 
    <TextView
        android:id="@+id/textView2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_above="@+id/seekBar"
        android:text="超哥音乐选集"
        android:textAppearance="?android:attr/textAppearanceSmall" />
 
    <TextView
        android:id="@+id/textView3"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_above="@+id/seekBar"
        android:layout_alignEnd="@+id/button4"
        android:text="02:23"
        android:textAppearance="?android:attr/textAppearanceSmall" />
 
    <TextView
        android:id="@+id/textView4"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignBaseline="@+id/textView2"
        android:layout_alignBottom="@+id/textView2"
        android:layout_centerHorizontal="true"
        android:text="超哥吉他"
        android:textAppearance="?android:attr/textAppearanceMedium" />
 
</RelativeLayout>

当然也可以按照你自己的设计去摆放播放器的布局样式.

5. 小结

本节学习了 Android 内置的最常用的播放器,最关键的是要熟悉它的状态机时序图,因为可能在实际开发中你会使用更强大的第三方播放器,但是基本时序仍然是参照 Android 官方设计的,在了解时序之后就可以按照本节的步骤使用各种 API 来播放音视频了。在掌握了本节内容之后,如果感兴趣也可以研究研究市面上常用的开源播放器,可以让你对播放器有更深的理解。

相关文章
|
1月前
|
Android开发 数据安全/隐私保护 虚拟化
安卓手机远程连接登录Windows服务器教程
安卓手机远程连接登录Windows服务器教程
61 4
|
1月前
|
Android开发
布谷语音软件开发:android端语音软件搭建开发教程
语音软件搭建android端语音软件开发教程!
|
2月前
|
Java 程序员 开发工具
Android|修复阿里云播放器下载不回调的问题
虽然 GC 带来了很多便利,但在实际编码时,我们也需要注意对象的生命周期管理,该存活的存活,该释放的释放,避免因为 GC 导致的问题。
40 2
|
6月前
|
XML 存储 数据库
如何使用Android Studio创建一个基本的音乐播放器应用
如何使用Android Studio创建一个基本的音乐播放器应用
296 0
|
4月前
|
Android开发
Android 利用MediaPlayer实现音乐播放
本文提供了一个简单的Android MediaPlayer音乐播放示例,包括创建PlayerActivity、配置AndroidManifest.xml和activity_player.xml布局,以及实现播放和暂停功能的代码。
33 0
Android 利用MediaPlayer实现音乐播放
|
4月前
|
编解码 开发工具 Android开发
Android平台RTSP|RTMP播放器如何实现TextureView渲染
本文介绍了在Android平台上使用TextureView进行RTSP和RTMP视频流渲染的技术背景和实现方法。TextureView相较于SurfaceView具备更高性能、更强功能性和更灵活的绘制方式等优势,但也有必须在硬件加速环境下运行和较高内存占用等局限。文中详细展示了如何在SmartPlayerV2工程中创建和配置TextureView,并通过代码示例解释了如何根据视频分辨率信息调整显示比例,以及处理TextureView的各种生命周期回调。此外,还列举了该播放器SDK支持的多项高级功能,如多实例播放、多种编码格式支持、硬解码能力等,旨在帮助开发者更好地理解和实现高性能的直播播放器。
|
4月前
|
算法 数据处理 开发工具
Android平台RTSP|RTMP播放器如何回调YUV或RGB数据
在开发Android平台上的RTSP或RTMP播放器时,开发者不仅追求低延迟播放,还希望获取解码后的视频数据(如YUV或RGB格式),以便进行视觉算法分析。使用大牛直播SDK中的SmartPlayer,可在确保播放流畅的同时,通过设置外部渲染器(`SmartPlayerSetExternalRender`)来高效地回调原始视频数据。例如,对于RGBA数据,需实现`NTExternalRender`接口,并重写相关方法以处理数据和尺寸变化。同样地,对于I420(YUV)数据,也需要相应地实现接口以满足需求。这种方式使得开发者能在不影响常规播放功能的情况下,进行定制化的视频处理任务。
|
4月前
|
编解码 网络协议 开发工具
Android平台RTSP|RTMP直播播放器技术接入说明
大牛直播SDK自2015年发布RTSP、RTMP直播播放模块,迭代从未停止,SmartPlayer功能强大、性能强劲、高稳定、超低延迟、超低资源占用。无需赘述,全自研内核,行业内一致认可的跨平台RTSP、RTMP直播播放器。本文以Android平台为例,介绍下如何集成RTSP、RTMP播放模块。
177 0
|
6月前
|
Android开发
杨老师课堂_安卓教程第一篇之入门
杨老师课堂_安卓教程第一篇之入门
39 0
|
XML 缓存 Android开发
Android MediaPlayer 音乐播放器扫描 本地音乐、上一曲、下一曲切歌、播放本地音乐(下)
Android MediaPlayer 音乐播放器扫描 本地音乐、上一曲、下一曲切歌、播放本地音乐(下)
236 0
Android MediaPlayer 音乐播放器扫描 本地音乐、上一曲、下一曲切歌、播放本地音乐(下)