Skip to content

Commit

Permalink
Merge pull request libgdx#80 from pias-education/android-mediaplayer-…
Browse files Browse the repository at this point in the history
…sync

Android / iOS: Improve loading performance and reduce race conditions
  • Loading branch information
SimonIT authored Nov 13, 2023
2 parents 90d136f + 62506d1 commit 1103700
Show file tree
Hide file tree
Showing 3 changed files with 184 additions and 121 deletions.
219 changes: 131 additions & 88 deletions gdx-video-android/src/com/badlogic/gdx/video/VideoPlayerAndroid.java
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@
* @author Rob Bogie <[email protected]> */
public class VideoPlayerAndroid extends AbstractVideoPlayer implements VideoPlayer, OnFrameAvailableListener {
//@off
String vertexShaderCode =
static final String vertexShaderCode =
"#define highp\n" +
"attribute highp vec4 a_position; \n" +
"attribute highp vec2 a_texCoord0;" +
Expand All @@ -63,7 +63,7 @@ public class VideoPlayerAndroid extends AbstractVideoPlayer implements VideoPlay
" v_texCoord0 = a_texCoord0;\n" +
"} \n";

String fragmentShaderCode =
static final String fragmentShaderCode =
"#extension GL_OES_EGL_image_external : require\n" +
"uniform samplerExternalOES u_sampler0;" +
"varying highp vec2 v_texCoord0;" +
Expand All @@ -73,22 +73,51 @@ public class VideoPlayerAndroid extends AbstractVideoPlayer implements VideoPlay
"}";
//@on

private final ShaderProgram shader = new ShaderProgram(vertexShaderCode, fragmentShaderCode);
private void setupStep () {
if (stopped || setupRenderer()) return;
queueSetupStep();
}

private boolean setupRenderer () {
if (surface != null) {
return true;
}
if (renderer == null) {
shader = new ShaderProgram(vertexShaderCode, fragmentShaderCode);
renderer = new ImmediateModeRenderer20(4, false, false, 1, shader);
transform = new Matrix4().setToOrtho2D(0, 0, 1, 1);
} else if (initialized) {
videoTexture.setOnFrameAvailableListener(this);
surface = new Surface(videoTexture);
player.setSurface(surface);
}
return false;
}

private void queueSetupStep () {
Gdx.app.postRunnable(new Runnable() {
@Override
public void run () {
setupStep();
}
});
}

private ShaderProgram shader;
private Matrix4 transform;
private final int[] textures = new int[1];
private SurfaceTexture videoTexture;
private Surface surface;
private FrameBuffer fbo;
private Texture frame;
private ImmediateModeRenderer20 renderer;

private MediaPlayer player;
private volatile boolean initialized = false;
private boolean prepared = false;
/** Whether the video was requested to stay paused after loading
*
* To achieve this and still load the first video frame properly, we play the video muted until the first frame is available,
* and then pause. */
private boolean stopped = false;
private boolean pauseRequested = false;
private boolean frameAvailable = false;
private volatile boolean frameAvailable = false;
/** If the external should be drawn to the fbo and make it available thru {@link #getTexture()} */
public boolean renderToFbo = true;

Expand All @@ -103,8 +132,8 @@ public class VideoPlayerAndroid extends AbstractVideoPlayer implements VideoPlay
final Object lock = new Object();

public VideoPlayerAndroid () {
setupRenderTexture();
initializeMediaPlayer();
queueSetupStep();
}

private void initializeMediaPlayer () {
Expand All @@ -113,51 +142,56 @@ private void initializeMediaPlayer () {
handler.post(new Runnable() {
@Override
public void run () {
synchronized (lock) {
player = new MediaPlayer();
lock.notify();
}
player = new MediaPlayer();
videoTexture = new SurfaceTexture(textures[0]);
initialized = true;
}
});
}

@Override
public boolean play (final FileHandle file) throws FileNotFoundException {
if (!file.exists()) {
throw new FileNotFoundException("Could not find file: " + file.path());
}
private void playInternal (final FileHandle file) {
prepared = false;

// Wait for the player to be created. (If the Looper thread is busy,
if (player == null) {
synchronized (lock) {
while (player == null) {
try {
lock.wait();
} catch (InterruptedException e) {
return false;
}
frame = null;
stopped = false;

// If we haven't finished loading the media player yet, wait without blocking.
if (!initialized) {
Gdx.app.postRunnable(new Runnable() {
@Override
public void run () {
if (stopped) return;
playInternal(file);
}
}
});
return;
}

player.reset();

player.setOnPreparedListener(new OnPreparedListener() {
@Override
public void onPrepared (MediaPlayer mp) {
prepared = true;
if (sizeListener != null) {
sizeListener.onVideoSize(mp.getVideoWidth(), mp.getVideoHeight());
}
if (fbo != null && (fbo.getWidth() != mp.getVideoWidth() || fbo.getHeight() != mp.getVideoHeight())) {
fbo.dispose();
fbo = null;
}
mp.start();
if (pauseRequested) {
mp.pause();
}

Gdx.app.postRunnable(new Runnable() {
@Override
public void run () {
if (fbo != null && (fbo.getWidth() != player.getVideoWidth() || fbo.getHeight() != player.getVideoHeight())) {
fbo.dispose();
fbo = null;
}
if (fbo == null) {
fbo = new FrameBuffer(Pixmap.Format.RGB888, player.getVideoWidth(), player.getVideoHeight(), false);
}
prepared = true;
player.start();
if (pauseRequested) {
player.pause();
}
}
});
}
});
player.setOnErrorListener(new OnErrorListener() {
Expand All @@ -184,15 +218,23 @@ public void onCompletion (MediaPlayer mp) {
AssetManager assets = ((AndroidApplicationBase)Gdx.app).getContext().getAssets();
AssetFileDescriptor descriptor = assets.openFd(file.path());
player.setDataSource(descriptor.getFileDescriptor(), descriptor.getStartOffset(), descriptor.getLength());
descriptor.close();
} else {
player.setDataSource(file.file().getAbsolutePath());
}
player.setSurface(new Surface(videoTexture));
player.prepareAsync();
} catch (IOException e) {
e.printStackTrace();
}
}

@Override
public boolean play (FileHandle file) throws FileNotFoundException {
if (!file.exists()) {
throw new FileNotFoundException("Could not find file: " + file.path());
}

playInternal(file);
return true;
}

Expand All @@ -207,38 +249,36 @@ public int getTextureExternal () {

@Override
public boolean update () {
synchronized (this) {
if (frameAvailable) {
frameAvailable = false;
videoTexture.updateTexImage();
if (renderToFbo) {
if (fbo == null) {
fbo = new FrameBuffer(Pixmap.Format.RGB888, player.getVideoWidth(), player.getVideoHeight(), false);
frame = fbo.getColorBufferTexture();
frame.setFilter(minFilter, magFilter);
}
fbo.begin();
shader.bind();

Gdx.gl.glActiveTexture(GL20.GL_TEXTURE0);
Gdx.gl.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, getTextureExternal());
shader.setUniformi("u_sampler0", 0);
renderer.begin(transform, GL20.GL_TRIANGLE_STRIP);
renderer.texCoord(0, 0);
renderer.vertex(0, 0, 0);
renderer.texCoord(1, 0);
renderer.vertex(1, 0, 0);
renderer.texCoord(0, 1);
renderer.vertex(0, 1, 0);
renderer.texCoord(1, 1);
renderer.vertex(1, 1, 0);
renderer.end();
fbo.end();
}
return true;
}
if (surface == null || !frameAvailable || (fbo == null && renderToFbo)) return false;

frameAvailable = false;
videoTexture.updateTexImage();

if (!renderToFbo) return true;

fbo.begin();
shader.bind();

Gdx.gl.glActiveTexture(GL20.GL_TEXTURE0);
Gdx.gl.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, getTextureExternal());
shader.setUniformi("u_sampler0", 0);
renderer.begin(transform, GL20.GL_TRIANGLE_STRIP);
renderer.texCoord(0, 0);
renderer.vertex(0, 0, 0);
renderer.texCoord(1, 0);
renderer.vertex(1, 0, 0);
renderer.texCoord(0, 1);
renderer.vertex(0, 1, 0);
renderer.texCoord(1, 1);
renderer.vertex(1, 1, 0);
renderer.end();
fbo.end();
if (frame == null) {
frame = fbo.getColorBufferTexture();
frame.setFilter(minFilter, magFilter);
}
return false;

return true;
}

@Override
Expand All @@ -259,24 +299,15 @@ public boolean isBuffered () {
public void stop () {
if (player != null && player.isPlaying()) {
player.stop();
player.reset();
}
stopped = true;
prepared = false;
}

private void setupRenderTexture () {
renderer = new ImmediateModeRenderer20(4, false, false, 1);
renderer.setShader(shader);
transform = new Matrix4().setToOrtho2D(0, 0, 1, 1);

videoTexture = new SurfaceTexture(textures[0]);
videoTexture.setOnFrameAvailableListener(this);
}

@Override
public void onFrameAvailable (SurfaceTexture surfaceTexture) {
synchronized (this) {
frameAvailable = true;
}
frameAvailable = true;
}

@Override
Expand All @@ -301,15 +332,27 @@ public void resume () {
@Override
public void dispose () {
stop();
if (player != null) player.release();

videoTexture.detachFromGLContext();
surface = null;
handler.post(new Runnable() {
@Override
public void run () {
player.release();
player = null;
}
});

GLES20.glDeleteTextures(1, textures, 0);
if (videoTexture != null) {
videoTexture.detachFromGLContext();
GLES20.glDeleteTextures(1, textures, 0);
}

if (fbo != null) fbo.dispose();
shader.dispose();
renderer.dispose();
fbo = null;
frame = null;
if (renderer != null) {
renderer.dispose();
shader.dispose();
}
}

@Override
Expand Down
Loading

0 comments on commit 1103700

Please sign in to comment.