From 474ce65081ecd452215bc07ab866666cb11ca8b1 Mon Sep 17 00:00:00 2001
From: Sven Gothel
- * Audio maybe supported and played back internally or via an {@link AudioSink} implementation,
- * if an audio stream is selected in {@link #initGLStream(GL, int, URI, int, int)}.
+ * Audio maybe supported and played back internally or via an {@link AudioSink} implementation.
+ *
+ * Audio and video streams can be selected or muted via {@link #initStream(URI, int, int, int)}
+ * using the appropriate stream id's.
*
+ * Most of the stream processing is performed on the decoding thread, a.k.a. StreamWorker:
+ * StreamWorker Decoding Thread
+ *
+ *
+ * StreamWorker generates it's own {@link GLContext}, shared with the one passed to {@link #initGL(GL)}.
+ * The shared {@link GLContext} allows the decoding thread to push the video frame data directly into
+ * the designated {@link TextureFrame}, later returned via {@link #getNextTexture(GL)} and used by the user.
+ *
+ * Caught exceptions on StreamWorker are delivered as {@link StreamException}s, + * which either degrades the {@link State} to {@link State#Uninitialized} or {@link State#Paused}. + *
+ *+ * An occurring {@link StreamException} triggers a {@link GLMediaEventListener#EVENT_CHANGE_ERR EVENT_CHANGE_ERR} event, + * which can be listened to via {@link GLMediaEventListener#attributesChanged(GLMediaPlayer, int, long)}. + *
+ *+ * An occurred {@link StreamException} can be read via {@link #getStreamException()}. + *
+ * + * **
action | state before | state after | |
---|---|---|---|
{@link #initGLStream(GL, int, URI, int, int)} | Uninitialized | Paused | |
{@link #play()} | Paused | Playing | |
{@link #pause()} | Playing | Paused | |
{@link #seek(int)} | Playing, Paused | Unchanged | |
{@link #destroy(GL)} | ANY | Uninitialized | |
Action | {@link State} Before | {@link State} After | {@link GLMediaEventListener Event} |
{@link #initStream(URI, int, int, int)} | {@link State#Uninitialized Uninitialized} | {@link State#Initialized Initialized}1, {@link State#Uninitialized Uninitialized} | {@link GLMediaEventListener#EVENT_CHANGE_INIT EVENT_CHANGE_INIT} or ( {@link GLMediaEventListener#EVENT_CHANGE_ERR EVENT_CHANGE_ERR} + {@link GLMediaEventListener#EVENT_CHANGE_UNINIT EVENT_CHANGE_UNINIT} ) |
{@link #initGL(GL)} | {@link State#Initialized Initialized} | {@link State#Paused Paused}, {@link State#Initialized Initialized} | {@link GLMediaEventListener#EVENT_CHANGE_PAUSE EVENT_CHANGE_PAUSE} |
{@link #play()} | {@link State#Paused Paused} | {@link State#Playing Playing} | {@link GLMediaEventListener#EVENT_CHANGE_PLAY EVENT_CHANGE_PLAY} |
{@link #pause()} | {@link State#Playing Playing} | {@link State#Paused Paused} | {@link GLMediaEventListener#EVENT_CHANGE_PAUSE EVENT_CHANGE_PAUSE} |
{@link #seek(int)} | {@link State#Paused Paused}, {@link State#Playing Playing} | {@link State#Paused Paused}, {@link State#Playing Playing} | none |
{@link #getNextTexture(GL)} | {@link State#Paused Paused}, {@link State#Playing Playing} | {@link State#Paused Paused}, {@link State#Playing Playing} | none |
{@link #getLastTexture()} | {@link State#Paused Paused}, {@link State#Playing Playing} | {@link State#Paused Paused}, {@link State#Playing Playing} | none |
{@link TextureFrame#END_OF_STREAM_PTS END_OF_STREAM} | {@link State#Playing Playing} | {@link State#Paused Paused} | {@link GLMediaEventListener#EVENT_CHANGE_EOS EVENT_CHANGE_EOS} + {@link GLMediaEventListener#EVENT_CHANGE_PAUSE EVENT_CHANGE_PAUSE} |
{@link StreamException} | ANY | {@link State#Paused Paused}, {@link State#Uninitialized Uninitialized} | {@link GLMediaEventListener#EVENT_CHANGE_ERR EVENT_CHANGE_ERR} + ( {@link GLMediaEventListener#EVENT_CHANGE_PAUSE EVENT_CHANGE_PAUSE} or {@link GLMediaEventListener#EVENT_CHANGE_UNINIT EVENT_CHANGE_UNINIT} ) |
{@link #destroy(GL)} | ANY | {@link State#Uninitialized Uninitialized} | {@link GLMediaEventListener#EVENT_CHANGE_UNINIT EVENT_CHANGE_UNINIT} |
* The class follows a passive A/V synchronization pattern. - * Audio is being untouched, while {@link #getNextTexture(GL, boolean)} delivers a new video frame - * only, if its timestamp is less than 22ms ahead of time. - * Otherwise the early frame is cached for later retrieval and the previous frame is returned. - * FIXME: Refine! + * Audio is being untouched, while {@link #getNextTexture(GL)} delivers a new video frame + * only, if its timestamp is less than {@link #MAXIMUM_VIDEO_ASYNC} ahead of time. + * If its timestamp is more than {@link #MAXIMUM_VIDEO_ASYNC} ahead of time, + * the previous frame is returned. + * If its timestamp is more than {@link #MAXIMUM_VIDEO_ASYNC} after time, + * the frame is dropped and the next frame is being fetched. *
*
* https://en.wikipedia.org/wiki/Audio_to_video_synchronization
@@ -149,37 +184,80 @@ public interface GLMediaPlayer extends TextureSequence {
public static final boolean DEBUG = Debug.debug("GLMediaPlayer");
public static final boolean DEBUG_NATIVE = Debug.debug("GLMediaPlayer.Native");
+ /** Minimum texture count, value {@value}. */
+ public static final int TEXTURE_COUNT_MIN = 4;
+
/** Constant {@value} for mute or not available. See Audio and video Stream IDs. */
public static final int STREAM_ID_NONE = -2;
/** Constant {@value} for auto or unspecified. See Audio and video Stream IDs. */
public static final int STREAM_ID_AUTO = -1;
- /** Maximum video frame async .. */
+ /** Maximum video frame async of {@value} milliseconds. */
public static final int MAXIMUM_VIDEO_ASYNC = 22;
+ /**
+ * A StreamException encapsulates a caught exception in the decoder thread, a.k.a StreamWorker,
+ * see See StreamWorker Error Handling.
+ */
+ @SuppressWarnings("serial")
+ public static class StreamException extends Exception {
+ public StreamException(Throwable cause) {
+ super(cause);
+ }
+ public StreamException(String message, Throwable cause) {
+ super(message, cause);
+ }
+ }
public interface GLMediaEventListener extends TexSeqEventListener
+ * Lifecycle: {@link State#Uninitialized} -> {@link State#Initialized}1 or {@link State#Uninitialized}
+ *
+ * {@link State#Initialized} is reached asynchronous,
+ * i.e. user gets notified via {@link GLMediaEventListener#attributesChanged(GLMediaPlayer, int, long) attributesChanges(..)}.
+ *
+ * A possible caught asynchronous {@link StreamException} while initializing the stream off-thread
+ * will be thrown at {@link #initGL(GL)}.
+ *
+ * Muted audio can be achieved by passing {@link #STREAM_ID_NONE} to
- * GLMediaPlayer Lifecycle: Uninitialized -> Paused
+ * Muted video can be achieved by passing {@link #STREAM_ID_NONE} to
+ * Lifecycle: {@link State#Initialized} -> {@link State#Paused} or {@link State#Initialized}
+ *
- * The {@link AudioSink} instance is available after {@link #initGLStream(GL, int, URI, int, int)},
+ * The {@link AudioSink} instance is available after {@link #initStream(URI, int, int, int)},
* if used by implementation.
*
- * GLMediaPlayer Lifecycle:
* See audio and video synchronization.
*
* See audio and video synchronization.
* gl
is not null.
+ * Issues asynchronous stream initialization.
+ * aid
.
+ * vid
,
+ * in which case textureCount
is ignored as well as the passed GL object of the subsequent {@link #initGL(GL)} call.
* 1
for on-thread decoding.
* @param streamLoc the stream location
* @param vid video stream id, see audio and video Stream IDs
* @param aid video stream id, see audio and video Stream IDs
- * @return the new state
+ * @param textureCount desired number of buffered textures to be decoded off-thread, will be validated by implementation.
+ * The minimum value is {@link #TEXTURE_COUNT_MIN}.
+ * Ignored if video is muted.
+ * @throws IllegalStateException if not invoked in {@link State#Uninitialized}
+ * @throws IllegalArgumentException if arguments are invalid
+ */
+ public void initStream(URI streamLoc, int vid, int aid, int textureCount) throws IllegalStateException, IllegalArgumentException;
+
+ /**
+ * Returns the {@link StreamException} caught in the decoder thread, or null
.
+ * @see GLMediaEventListener#EVENT_CHANGE_ERR
+ * @see StreamException
+ */
+ public StreamException getStreamException();
+
+ /**
+ * Initializes OpenGL related resources.
+ * gl
is ignored if video is muted, see {@link #initStream(URI, int, int, int)}.
*
- * @throws IllegalStateException if not invoked in state Uninitialized
- * @throws IOException in case of difficulties to open or process the stream
+ * @param gl current GL object. Maybe null
, for audio only.
+ * @throws IllegalStateException if not invoked in {@link State#Initialized}.
+ * @throws IllegalArgumentException if arguments are invalid
+ * @throws StreamException forwarded from the off-thread stream initialization
* @throws GLException in case of difficulties to initialize the GL resources
*/
- public State initGLStream(GL gl, int textureCount, URI streamLoc, int vid, int aid) throws IllegalStateException, GLException, IOException;
+ public void initGL(GL gl) throws IllegalStateException, IllegalArgumentException, StreamException, GLException;
/**
* If implementation uses a {@link AudioSink}, it's instance will be returned.
* ANY
-> Uninitialized
+ * Lifecycle: ANY
-> {@link State#Uninitialized}
*