MediaPlayer overview
Android多媒体框架支持播放各种常见媒体类型,因此可以轻松地将音频,视频和图像集成到您的应用程序中。 可以从存储在应用程序资源(原始资源)中的媒体文件,文件系统中的独立文件或通过网络连接到达的数据流中播放音频或视频,所有这些都使用MediaPlayer API。
注意:您只能将音频数据播放到标准输出设备。 目前,这是移动设备扬声器或蓝牙耳机。 在通话期间,您无法在对话音频中播放声音文件。
MediaPlayer
此类是播放声音和视频的主要API。
AudioManager
此类管理设备上的音频源和音频输出。
权限
<uses-permission android:name="android.permission.INTERNET" />
唤醒锁定权限 - 如果播放器应用程序需要防止屏幕变暗或处理器处于休眠状态,或者使用MediaPlayer.setScreenOnWhilePlaying()或MediaPlayer.setWakeMode()方法,则必须请求此权限。
<uses-permission android:name="android.permission.WAKE_LOCK" />
播放文件
下面是一个如何播放可用作本地原始资源的音频的示例(保存在应用程序的res / raw /目录中):
MediaPlayer mediaPlayer = MediaPlayer.create(context, R.raw.sound_file_1);
mediaPlayer.start(); // no need to call prepare(); create() does that for you
在这种情况下,“原始”资源是系统不会尝试以任何特定方式解析的文件。 但是,此资源的内容不应该是原始音频。 它应该是一种支持格式的正确编码和格式化的媒体文件。
以下是您可以从系统中本地可用的URI(例如,您通过内容解析器获得)中进行播放的方法:
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,因为您引用的文件可能不存在。
异步准备
对prepare()的调用可能需要很长时间才能执行,因为它可能涉及获取和解码媒体数据。因此,与任何可能需要很长时间才能执行的方法一样,永远不应该从应用程序的UI线程中调用它。
为了避免挂起你的UI线程,产生另一个线程来准备MediaPlayer并在完成后通知主线程。 但是,虽然可以自己编写线程逻辑,但在使用MediaPlayer时,此模式非常常见,框架提供了一种使用prepareAsync()方法完成此任务的便捷方法。 此方法开始在后台准备媒体并立即返回。 媒体完成准备后,将调用通过setOnPreparedListener()配置的MediaPlayer.OnPreparedListener的onPrepared()方法。
管理状态
MediaPlayer的另一个方面是它是基于状态的。 也就是说,MediaPlayer具有内部状态,在编写代码时必须始终注意这一状态,因为某些操作仅在播放器处于特定状态时才有效。 如果在处于错误状态时执行操作,系统可能会抛出异常或导致其他不良行为。
释放MediaPlayer
MediaPlayer可以消耗宝贵的系统资源。 因此,应该始终采取额外的预防措施,以确保没有超过必要的时间挂在MediaPlayer实例上。 完成后,应始终调用release()以确保分配给它的任何系统资源都已正确释放。 例如,如果您正在使用MediaPlayer并且活动接收到对onStop()的调用,则必须释放MediaPlayer,因为在您的活动未与用户交互时保持它是没有意义的(除非正在玩 媒体在后台,将在下一节讨论)。 当您的活动恢复或重新启动时,需要创建一个新的MediaPlayer并在重新开始播放之前再次准备它。
mediaPlayer.release();
mediaPlayer = null;
例如,考虑如果在活动停止时忘记释放MediaPlayer可能会发生的问题,但在活动再次启动时创建一个新问题。 当用户更改屏幕方向(或以其他方式更改设备配置)时,系统会通过重新启动活动来处理(默认情况下),因此可能会在用户旋转时快速占用所有系统资源 设备在纵向和横向之间来回切换,因为在每次方向更改时,都会创建一个永不释放的新MediaPlayer。
在服务中使用MediaPlayer
如果您希望媒体在后台播放,即使您的应用程序不在屏幕上 - 也就是说,您希望在用户与其他应用程序交互时继续播放 - 那么您必须启动一个服务并从那里控制MediaPlayer实例。 您需要将MediaPlayer嵌入MediaBrowserServiceCompat服务中,并使其与另一个活动中的MediaBrowserCompat交互。
异步运行
首先,像Activity一样,默认情况下,Service中的所有工作都在一个线程中完成 - 事实上,如果您从同一个应用程序运行一个活动和一个服务,它们会使用相同的线程(“主线程” “) 默认情况下。因此,服务需要快速处理传入的意图,并且在响应时不要执行冗长的计算。 如果需要任何繁重的工作或阻塞调用,则必须异步执行这些任务:从您自己实现的另一个线程,或使用框架的许多工具进行异步处理。
例如,当您使用主线程中的MediaPlayer时,您应该调用prepareAsync()而不是prepare(),并实现MediaPlayer.OnPreparedListener,以便在准备完成后收到通知并开始播放。 例如:
public class MyService extends Service implements MediaPlayer.OnPreparedListener {
private static final String ACTION_PLAY = "com.example.action.PLAY";
MediaPlayer mediaPlayer = null;
public int onStartCommand(Intent intent, int flags, int startId) {
...
if (intent.getAction().equals(ACTION_PLAY)) {
mediaPlayer = ... // initialize it here
mediaPlayer.setOnPreparedListener(this);
mediaPlayer.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 mediaPlayer;
public void initMediaPlayer() {
// ...initialize the MediaPlayer here...
mediaPlayer.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会进入错误状态,您必须先重置它才能再次使用它。
使用唤醒锁
在设计在后台播放媒体的应用程序时,设备可能会在您的服务运行时进入休眠状态。 由于Android系统在设备休眠时尝试节省电池,因此系统会尝试关闭不必要的任何电话功能,包括CPU和WiFi硬件。 但是,如果您正在播放或播放音乐,则需要防止系统干扰播放。
为了确保您的服务在这些条件下继续运行,您必须使用“唤醒锁定”。 唤醒锁定是一种向系统发出信号的方式,即您的应用程序正在使用某些功能,即使手机处于空闲状态也应保持可用。
要确保在播放MediaPlayer时CPU继续运行,请在初始化MediaPlayer时调用setWakeMode()方法。 完成后,MediaPlayer在播放时保持指定的锁定,并在暂停或停止时释放锁定:
mediaPlayer = new MediaPlayer();
// ... other initialization here ...
mediaPlayer.setWakeMode(getApplicationContext(), PowerManager.PARTIAL_WAKE_LOCK);
但是,在此示例中获取的唤醒锁仅保证CPU保持唤醒状态。 如果您通过网络流式传输媒体并且正在使用Wi-Fi,则可能还需要持有WifiLock,您必须手动获取和释放。 因此,当您开始使用远程URL准备MediaPlayer时,您应该创建并获取Wi-Fi锁。 例如:
WifiLock wifiLock = ((WifiManager) getSystemService(Context.WIFI_SERVICE))
.createWifiLock(WifiManager.WIFI_MODE_FULL, "mylock");
wifiLock.acquire();
暂停或停止媒体或不再需要网络时,应释放锁:
wifiLock.release();
执行清理
因此,在您使用服务时,应始终覆盖onDestroy()方法以确保释放MediaPlayer:
public class MyService extends Service {
MediaPlayer mediaPlayer;
// ...
@Override
public void onDestroy() {
super.onDestroy()
if (mediaPlayer != null) mediaPlayer.release();
}
}
数字版权管理(DRM)
从Android 8.0(API级别26)开始,MediaPlayer包含支持播放受DRM保护的材料的API。 它们类似于MediaDrm提供的低级API,但它们在更高级别运行,并且不公开底层提取器,drm和加密对象。
虽然MediaPlayer DRM API不提供MediaDrm的完整功能,但它支持最常见的用例。 当前实现可以处理以下内容类型:
- 受广泛保护的本地媒体文件
- 受广泛保护的远程/流媒体文件
以下代码段演示了如何在简单的同步实现中使用新的DRM MediaPlayer方法。 要管理DRM控制的媒体,您需要将新方法与通常的MediaPlayer调用流包括在一起,如下所示:
setDataSource();
setOnDrmConfigHelper(); // optional, for custom configuration
prepare();
if (getDrmInfo() != null) {
prepareDrm();
getKeyRequest();
provideKeyResponse();
}
// MediaPlayer is now ready to use
start();
// ...play/pause/resume...
stop();
releaseDrm();
首先,像往常一样初始化MediaPlayer对象并使用setDataSource()设置其源代码。 然后,要使用DRM,请执行以下步骤:
- 如果您希望应用程序执行自定义配置,请定义OnDrmConfigHelper接口,并使用setOnDrmConfigHelper()将其附加到播放器。
- 调用prepare()。
- 调用getDrmInfo()。如果源具有DRM内容,则该方法返回非空MediaPlayer.DrmInfo值。
如果MediaPlayer.DrmInfo存在:
-
检查可用UUID的映射并选择一个。
-
通过调用prepareDrm()为当前源准备DRM配置。
如果您创建并注册了OnDrmConfigHelper回调,则在prepareDrm()执行时调用它。 这使您可以在打开DRM会话之前执行DRM属性的自定义配置。 在调用prepareDrm()的线程中同步调用回调。 要访问DRM属性,请调用getDrmPropertyString()和setDrmPropertyString()。 避免执行冗长的操作。
如果尚未设置设备,则prepareDrm()还会访问配置服务器以配置设备。 这可能需要不同的时间,具体取决于网络连接。
-
要获取要发送到许可证服务器的不透明密钥请求字节数组,请调用getKeyRequest()。
-
要通知DRM引擎有关从许可证服务器收到的密钥响应,请调用provideKeyResponse()。 结果取决于密钥请求的类型:
如果响应是针对脱机密钥请求,则结果是密钥集标识符。 您可以将此键集标识符与restoreKeys()一起使用,以将键还原到新会话。
如果响应是针对流式传输或释放请求,则结果为null。
异步运行prepareDrm()
默认情况下,prepareDrm()同步运行,阻塞直到准备完成。 但是,新设备上的第一个DRM准备工作也可能需要配置,这由prepareDrm()内部处理,并且由于涉及网络操作可能需要一些时间才能完成。 您可以通过定义和设置MediaPlayer.OnDrmPreparedListener来避免在prepareDrm()上阻塞。
设置OnDrmPreparedListener时,prepareDrm()会在后台执行配置(如果需要)和准备。 配置和准备完成后,将调用监听器。 您不应该对调用序列或运行侦听器的线程做出任何假设(除非侦听器使用处理程序线程注册)。 可以在prepareDrm()返回之前或之后调用侦听器。
异步设置DRM
您可以通过创建和注册用于DRM准备的MediaPlayer.OnDrmInfoListener以及用于启动播放器的MediaPlayer.OnDrmPreparedListener来异步初始化DRM。 它们与prepareAsync()一起使用,如下所示:
setOnPreparedListener();
setOnDrmInfoListener();
setDataSource();
prepareAsync();
// ...
// If the data source content is protected you receive a call to the onDrmInfo() callback.
onDrmInfo() {
prepareDrm();
getKeyRequest();
provideKeyResponse();
}
// When prepareAsync() finishes, you receive a call to the onPrepared() callback.
// If there is a DRM, onDrmInfo() sets it up before executing this callback,
// so you can start the player.
onPrepared() {
start();
}
处理加密媒体
从Android 8.0(API级别26)开始,MediaPlayer还可以为基本流类型H.264和AAC解密通用加密方案(CENC)和HLS样本级加密媒体(METHOD = SAMPLE-AES)。 之前支持全段加密媒体(METHOD = AES-128)。
从ContentResolver检索媒体
在媒体播放器应用程序中可能有用的另一个特征是能够检索用户在设备上具有的音乐。 您可以通过查询ContentResolver获取外部媒体来实现:
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) {
// query failed, handle error.
} else if (!cursor.moveToFirst()) {
// no media on the device
} else {
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);
// ...process entry...
} while (cursor.moveToNext());
}
要将其与MediaPlayer一起使用,您可以执行以下操作:
long id = /* retrieve it from somewhere */;
Uri contentUri = ContentUris.withAppendedId(
android.provider.MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, id);
mediaPlayer = new MediaPlayer();
mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
mediaPlayer.setDataSource(getApplicationContext(), contentUri);
// ...prepare and start...
MediaPlayer + SurfaceView
private Context mContext;
private SurfaceView mSurfaceView;
private MediaPlayer mMediaPlayer;
mSurfaceView = new SurfaceView(mContext);
mSurfaceView.getHolder().addCallback(new Callback() {
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
mMediaPlayer.release();
}
@Override
public void surfaceCreated(SurfaceHolder holder) {
playback();
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
});
private void playback() {
mMediaPlayer = new MediaPlayer();
try {
mMediaPlayer.setDataSource(Environment.getExternalStorageDirectory().getPath() + "/test.320x240.mp4");
mMediaPlayer.setDisplay(mSurfaceView.getHolder());
mMediaPlayer.prepare();
mMediaPlayer.start();
} catch (Exception e) {
e.printStackTrace();
}
}
MediaPlayer + GLSurfaceView
private Context mContext;
private GLSurfaceView mSurfaceView;
private MediaPlayer mMediaPlayer;
mSurfaceView = new GLSurfaceView(mContext);
mSurfaceView.setRenderer(new Renderer() {
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
Log.e(TAG, "onSurfaceCreated");
}
@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
Log.e(TAG, "onSurfaceChanged");
}
@Override
public void onDrawFrame(GL10 gl) {
Log.e(TAG, "onDrawFrame");
}
});
mSurfaceView.getHolder().addCallback(new Callback() {
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
mMediaPlayer.release();
}
@Override
public void surfaceCreated(SurfaceHolder holder) {
playback();
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
});
private void playback() {
mMediaPlayer = new MediaPlayer();
try {
mMediaPlayer.setDataSource(Environment.getExternalStorageDirectory().getPath() + "/test.320x240.mp4");
mMediaPlayer.setDisplay(mSurfaceView.getHolder());
mMediaPlayer.prepare();
mMediaPlayer.start();
} catch (Exception e) {
e.printStackTrace();
}
}
MediaPlayer + GLSurfaceView + GLSurfaceView.Renderer
private Context mContext;
private Resources mResources;
private VideoSurfaceView mVideoView = null;
private MediaPlayer mMediaPlayer = null;
mResources = mContext.getResources();
mMediaPlayer = new MediaPlayer();
try {
AssetFileDescriptor afd = mResources.openRawResourceFd(R.raw.testvideo);
mMediaPlayer.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength());
afd.close();
} catch (Exception e) {
Log.e(TAG, e.getMessage(), e);
}
mVideoView = new VideoSurfaceView(this, mMediaPlayer);
class VideoSurfaceView extends GLSurfaceView {
VideoRender mRenderer;
private MediaPlayer mMediaPlayer = null;
public VideoSurfaceView(Context context, MediaPlayer mp) {
super(context);
setEGLContextClientVersion(2);
mMediaPlayer = mp;
mRenderer = new VideoRender(context);
setRenderer(mRenderer);
}
@Override
public void onResume() {
queueEvent(new Runnable(){
public void run() {
mRenderer.setMediaPlayer(mMediaPlayer);
}});
super.onResume();
}
private static class VideoRender
implements GLSurfaceView.Renderer, SurfaceTexture.OnFrameAvailableListener {
private static String TAG = "VideoRender";
private static final int FLOAT_SIZE_BYTES = 4;
private static final int TRIANGLE_VERTICES_DATA_STRIDE_BYTES = 5 * FLOAT_SIZE_BYTES;
private static final int TRIANGLE_VERTICES_DATA_POS_OFFSET = 0;
private static final int TRIANGLE_VERTICES_DATA_UV_OFFSET = 3;
private final float[] mTriangleVerticesData = {
// X, Y, Z, U, V
-1.0f, -1.0f, 0, 0.f, 0.f,
1.0f, -1.0f, 0, 1.f, 0.f,
-1.0f, 1.0f, 0, 0.f, 1.f,
1.0f, 1.0f, 0, 1.f, 1.f,
};
private FloatBuffer mTriangleVertices;
private final String mVertexShader =
"uniform mat4 uMVPMatrix;\n" +
"uniform mat4 uSTMatrix;\n" +
"attribute vec4 aPosition;\n" +
"attribute vec4 aTextureCoord;\n" +
"varying vec2 vTextureCoord;\n" +
"void main() {\n" +
" gl_Position = uMVPMatrix * aPosition;\n" +
" vTextureCoord = (uSTMatrix * aTextureCoord).xy;\n" +
"}\n";
private final String mFragmentShader =
"#extension GL_OES_EGL_image_external : require\n" +
"precision mediump float;\n" +
"varying vec2 vTextureCoord;\n" +
"uniform samplerExternalOES sTexture;\n" +
"void main() {\n" +
" gl_FragColor = texture2D(sTexture, vTextureCoord);\n" +
"}\n";
private float[] mMVPMatrix = new float[16];
private float[] mSTMatrix = new float[16];
private int mProgram;
private int mTextureID;
private int muMVPMatrixHandle;
private int muSTMatrixHandle;
private int maPositionHandle;
private int maTextureHandle;
private SurfaceTexture mSurface;
private boolean updateSurface = false;
private static int GL_TEXTURE_EXTERNAL_OES = 0x8D65;
private MediaPlayer mMediaPlayer;
public VideoRender(Context context) {
mTriangleVertices = ByteBuffer.allocateDirect(
mTriangleVerticesData.length * FLOAT_SIZE_BYTES)
.order(ByteOrder.nativeOrder()).asFloatBuffer();
mTriangleVertices.put(mTriangleVerticesData).position(0);
Matrix.setIdentityM(mSTMatrix, 0);
}
public void setMediaPlayer(MediaPlayer player) {
mMediaPlayer = player;
}
@Override
public void onDrawFrame(GL10 glUnused) {
synchronized(this) {
if (updateSurface) {
mSurface.updateTexImage();
mSurface.getTransformMatrix(mSTMatrix);
updateSurface = false;
}
}
GLES20.glClearColor(0.0f, 1.0f, 0.0f, 1.0f);
GLES20.glClear( GLES20.GL_DEPTH_BUFFER_BIT | GLES20.GL_COLOR_BUFFER_BIT);
GLES20.glUseProgram(mProgram);
checkGlError("glUseProgram");
GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
GLES20.glBindTexture(GL_TEXTURE_EXTERNAL_OES, mTextureID);
mTriangleVertices.position(TRIANGLE_VERTICES_DATA_POS_OFFSET);
GLES20.glVertexAttribPointer(maPositionHandle, 3, GLES20.GL_FLOAT, false,
TRIANGLE_VERTICES_DATA_STRIDE_BYTES, mTriangleVertices);
checkGlError("glVertexAttribPointer maPosition");
GLES20.glEnableVertexAttribArray(maPositionHandle);
checkGlError("glEnableVertexAttribArray maPositionHandle");
mTriangleVertices.position(TRIANGLE_VERTICES_DATA_UV_OFFSET);
GLES20.glVertexAttribPointer(maTextureHandle, 3, GLES20.GL_FLOAT, false,
TRIANGLE_VERTICES_DATA_STRIDE_BYTES, mTriangleVertices);
checkGlError("glVertexAttribPointer maTextureHandle");
GLES20.glEnableVertexAttribArray(maTextureHandle);
checkGlError("glEnableVertexAttribArray maTextureHandle");
Matrix.setIdentityM(mMVPMatrix, 0);
GLES20.glUniformMatrix4fv(muMVPMatrixHandle, 1, false, mMVPMatrix, 0);
GLES20.glUniformMatrix4fv(muSTMatrixHandle, 1, false, mSTMatrix, 0);
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
checkGlError("glDrawArrays");
GLES20.glFinish();
}
@Override
public void onSurfaceChanged(GL10 glUnused, int width, int height) {
}
@Override
public void onSurfaceCreated(GL10 glUnused, EGLConfig config) {
mProgram = createProgram(mVertexShader, mFragmentShader);
if (mProgram == 0) {
return;
}
maPositionHandle = GLES20.glGetAttribLocation(mProgram, "aPosition");
checkGlError("glGetAttribLocation aPosition");
if (maPositionHandle == -1) {
throw new RuntimeException("Could not get attrib location for aPosition");
}
maTextureHandle = GLES20.glGetAttribLocation(mProgram, "aTextureCoord");
checkGlError("glGetAttribLocation aTextureCoord");
if (maTextureHandle == -1) {
throw new RuntimeException("Could not get attrib location for aTextureCoord");
}
muMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMVPMatrix");
checkGlError("glGetUniformLocation uMVPMatrix");
if (muMVPMatrixHandle == -1) {
throw new RuntimeException("Could not get attrib location for uMVPMatrix");
}
muSTMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uSTMatrix");
checkGlError("glGetUniformLocation uSTMatrix");
if (muSTMatrixHandle == -1) {
throw new RuntimeException("Could not get attrib location for uSTMatrix");
}
int[] textures = new int[1];
GLES20.glGenTextures(1, textures, 0);
mTextureID = textures[0];
GLES20.glBindTexture(GL_TEXTURE_EXTERNAL_OES, mTextureID);
checkGlError("glBindTexture mTextureID");
GLES20.glTexParameterf(GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MIN_FILTER,
GLES20.GL_NEAREST);
GLES20.glTexParameterf(GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MAG_FILTER,
GLES20.GL_LINEAR);
/*
* Create the SurfaceTexture that will feed this textureID,
* and pass it to the MediaPlayer
*/
mSurface = new SurfaceTexture(mTextureID);
mSurface.setOnFrameAvailableListener(this);
Surface surface = new Surface(mSurface);
mMediaPlayer.setSurface(surface);
mMediaPlayer.setScreenOnWhilePlaying(true);
surface.release();
try {
mMediaPlayer.prepare();
} catch (IOException t) {
Log.e(TAG, "media player prepare failed");
}
synchronized(this) {
updateSurface = false;
}
mMediaPlayer.start();
}
synchronized public void onFrameAvailable(SurfaceTexture surface) {
updateSurface = true;
}
private int loadShader(int shaderType, String source) {
int shader = GLES20.glCreateShader(shaderType);
if (shader != 0) {
GLES20.glShaderSource(shader, source);
GLES20.glCompileShader(shader);
int[] compiled = new int[1];
GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compiled, 0);
if (compiled[0] == 0) {
Log.e(TAG, "Could not compile shader " + shaderType + ":");
Log.e(TAG, GLES20.glGetShaderInfoLog(shader));
GLES20.glDeleteShader(shader);
shader = 0;
}
}
return shader;
}
private int createProgram(String vertexSource, String fragmentSource) {
int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertexSource);
if (vertexShader == 0) {
return 0;
}
int pixelShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentSource);
if (pixelShader == 0) {
return 0;
}
int program = GLES20.glCreateProgram();
if (program != 0) {
GLES20.glAttachShader(program, vertexShader);
checkGlError("glAttachShader");
GLES20.glAttachShader(program, pixelShader);
checkGlError("glAttachShader");
GLES20.glLinkProgram(program);
int[] linkStatus = new int[1];
GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, linkStatus, 0);
if (linkStatus[0] != GLES20.GL_TRUE) {
Log.e(TAG, "Could not link program: ");
Log.e(TAG, GLES20.glGetProgramInfoLog(program));
GLES20.glDeleteProgram(program);
program = 0;
}
}
return program;
}
private void checkGlError(String op) {
int error;
while ((error = GLES20.glGetError()) != GLES20.GL_NO_ERROR) {
Log.e(TAG, op + ": glError " + error);
throw new RuntimeException(op + ": glError " + error);
}
}
} // End of class VideoRender.
} // End of class VideoSurfaceView.