package com.skcraft.playblock.player;
import java.nio.ByteBuffer;
import org.lwjgl.opengl.GL11;
import uk.co.caprica.vlcj.player.MediaPlayerFactory;
import uk.co.caprica.vlcj.player.direct.BufferFormat;
import uk.co.caprica.vlcj.player.direct.DirectMediaPlayer;
import uk.co.caprica.vlcj.player.direct.RenderCallback;
import com.skcraft.playblock.util.DrawUtils;
import com.sun.jna.Memory;
/**
* Renders a video from a player onto a surface.
*/
public final class MediaRenderer implements RenderCallback {
private final MediaManager mediaManager;
private final long creationTime;
private final int width;
private final int height;
private final int textureIndex;
private boolean released = false;
private RendererState state = RendererState.INITIALIZING;
private PlayerEventListener listener;
private DirectMediaPlayer player;
private ByteBuffer buffer = null;
/**
* Create a new media renderer with the given width and height.
*
* @param width
* the width
* @param height
* the height
* @param textureIndex
* the text of the index
*/
MediaRenderer(MediaManager mediaManager, int width, int height, int textureIndex) {
this.mediaManager = mediaManager;
this.creationTime = System.currentTimeMillis();
this.width = width;
this.height = height;
this.textureIndex = textureIndex;
}
/**
* Initialize the instance and create a new player.
*
* @param factory
* the factory for creating a new player
* @param volume
* the initial volume
*/
void initialize(final MediaPlayerFactory factory, final float volume) {
final MediaRenderer instance = this;
// Release the VLC player instance in the dedicated thread
mediaManager.executeThreadSafe(new Runnable() {
@Override
public void run() {
// Create the VLC media player instance
DirectMediaPlayer player = factory.newDirectMediaPlayer("RGBA", width, height, width * 4, instance);
player.setPlaySubItems(true);
player.setRepeat(true);
player.addMediaPlayerEventListener(listener = new PlayerEventListener(instance));
player.setVolume((int) (volume * 100));
instance.player = player;
}
});
}
/**
* Stop and release the player, then lose references.
*/
void release() {
final MediaRenderer instance = this;
// Release the VLC player instance in the dedicated thread
mediaManager.executeThreadSafe(new Runnable() {
@Override
public void run() {
player.setVolume(0); // The calls below may block for a bit
player.stop();
player.release();
player = null; // Lose reference
}
});
}
/**
* Get the time that this renderer was created at.
*
* @return UNIX timestamp in milliseconds
*/
public long getCreationTime() {
return creationTime;
}
/**
* Return true if this renderer is being released or it has been released.
*
* @return true if the renderer has been released
*/
public boolean isReleased() {
return released;
}
/**
* Mark this instance for release so that any object using this renderer
* (and also {@link MediaManager}) knows that it has been released already.
*
* @see MediaManager#release(MediaRenderer) the method where things are
* actually unloaded
*/
void markForRelease() {
released = true;
}
/**
* Get the underlying VLCJ player.
*
* @return the player
*/
DirectMediaPlayer getVLCJPlayer() {
return player;
}
/**
* Get the texture index for this instance.
*
* @return the texture index
*/
int getTextureIndex() {
return textureIndex;
}
/**
* Get the width of the screen.
*
* @return the width
*/
public int getWidth() {
return width;
}
/**
* Get the height of the screen.
*
* @return the height
*/
public int getHeight() {
return height;
}
/**
* Play a media file or URL.
*
* @param uri
* the path to play
* @param position
* milliseconds to skip in the video
* @param repeat
* true to repeat the video
*/
public void playMedia(final String uri, final long position, final boolean repeat) {
if (isReleased()) {
throw new RuntimeException("Cannot play media on released player");
}
final long startTime = System.currentTimeMillis() - position;
mediaManager.executeThreadSafe(new Runnable() {
@Override
public void run() {
listener.setSeekPosition(position);
player.playMedia(uri); // player should NOT be null
player.setRepeat(repeat);
buffer = null;
}
});
}
/**
* Play a media file or URL, defaulting to repeat.
*
* @param uri
* the path to play
*/
public void playMedia(final String uri) {
playMedia(uri, -1, true);
}
/**
* Stop whatever is playing.
*/
public void stop() {
mediaManager.executeThreadSafe(new Runnable() {
@Override
public void run() {
player.stop();
}
});
}
/**
* Set the volume of the player.
*
* @param volume
*/
public void setVolume(float volume) {
if (isReleased()) {
return;
}
player.setVolume((int) (volume * 100));
}
/**
* Get the state of this renderer (and the currently playing media)
*
* @return the state
*/
public RendererState getState() {
if (isReleased()) {
return RendererState.RELEASED;
}
return state;
}
/**
* Set the state of this renderer.
*
* <p>
* This is used by only {@link PlayerEventListener} so that state
* information can be updated on this renderer. Do not call this method from
* anywhere else.
* </p>
*
* @see PlayerEventListener calls this method
* @param state
* the new state
*/
void setState(RendererState state) {
this.state = state;
}
/**
* Draw the display at the given location in 2D.
*
* <p>
* The video will be resized accordingly.
* </p>
*
* @param x
* the X coordinate
* @param y
* the Y coordinate
* @param width
* the width of the screen
* @param height
* the height of the screen
*/
public void drawMedia(int x, int y, float width, float height) {
if (buffer != null && !isReleased()) {
GL11.glBindTexture(GL11.GL_TEXTURE_2D, textureIndex);
GL11.glBegin(GL11.GL_TRIANGLE_STRIP);
GL11.glTexCoord2f(0, 0);
GL11.glVertex3f(x, y, 0.0F);
GL11.glTexCoord2f(0, 1);
GL11.glVertex3f(x, y + height, 0.0F);
GL11.glTexCoord2f(1, 0);
GL11.glVertex3f(x + width, y, 0.0F);
GL11.glTexCoord2f(1, 1);
GL11.glVertex3f(x + width, y + height, 0.0F);
GL11.glEnd();
GL11.glTexSubImage2D(GL11.GL_TEXTURE_2D, 0, 0, 0, this.width, this.height, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, buffer);
} else {
// Because we don't zero out the texture data for the texture that
// we
// had created for this player, it may contain existing texture data
// that may have been used for who knows what before (UI drawing,
// another game, another video), and it's best we not draw that!
DrawUtils.drawRect(x, y, x + width, y + height, 0xff000000);
}
}
@Override
public void display(DirectMediaPlayer mediaPlayer, Memory[] nativeBuffers, BufferFormat bufferFormat) {
if (released) {
return;
}
buffer = nativeBuffers[0].getByteBuffer(0, width * height * 4);
}
}