Skip to content

Commit

Permalink
Add possiblity to send messages at playback position.
Browse files Browse the repository at this point in the history
This adds options to ExoPlayer.sendMessages which allow to specify a window index
and position at which the message should be sent. Additionally, the options can be
configured to use a custom Handler for the messages and whether the message should
be repeated when playback reaches the same position again.

The internal player converts these window positions to period index and position
at the earliest possibility. The internal player also attempts to update these
when the source info is refreshed. A sorted list of pending posts is kept and the
player triggers these posts when the playback position moves over the specified
position.

Issue:#2189

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=179563355
  • Loading branch information
tonihei authored and ojw28 committed Jan 2, 2018
1 parent 539d291 commit 6c2d1e1
Show file tree
Hide file tree
Showing 18 changed files with 1,281 additions and 233 deletions.
4 changes: 4 additions & 0 deletions RELEASENOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@
* Add optional parameter to `stop` to reset the player when stopping.
* Add a reason to `EventListener.onTimelineChanged` to distinguish between
initial preparation, reset and dynamic updates.
* Replaced `ExoPlayer.sendMessages` with `ExoPlayer.createMessage` to allow
more customization of the message. Now supports setting a message delivery
playback position and/or a delivery handler.
([#2189](https://github.com/google/ExoPlayer/issues/2189)).
* Buffering:
* Allow a back-buffer of media to be retained behind the current playback
position, for fast backward seeking. The back-buffer can be configured by
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,9 +119,11 @@ public void run() {
new DefaultDataSourceFactory(context, "ExoPlayerExtVp9Test"))
.setExtractorsFactory(MatroskaExtractor.FACTORY)
.createMediaSource(uri);
player.sendMessages(new ExoPlayer.ExoPlayerMessage(videoRenderer,
LibvpxVideoRenderer.MSG_SET_OUTPUT_BUFFER_RENDERER,
new VpxVideoSurfaceView(context)));
player
.createMessage(videoRenderer)
.setType(LibvpxVideoRenderer.MSG_SET_OUTPUT_BUFFER_RENDERER)
.setMessage(new VpxVideoSurfaceView(context))
.send();
player.prepare(mediaSource);
player.setPlayWhenReady(true);
Looper.loop();
Expand Down

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ public int supportsMixedMimeTypeAdaptation() throws ExoPlaybackException {
return ADAPTIVE_NOT_SUPPORTED;
}

// ExoPlayerComponent implementation.
// PlayerMessage.Target implementation.

@Override
public void handleMessage(int what, Object object) throws ExoPlaybackException {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,40 +34,43 @@
import com.google.android.exoplayer2.video.MediaCodecVideoRenderer;

/**
* An extensible media player that plays {@link MediaSource}s. Instances can be obtained from
* {@link ExoPlayerFactory}.
* An extensible media player that plays {@link MediaSource}s. Instances can be obtained from {@link
* ExoPlayerFactory}.
*
* <h3>Player components</h3>
*
* <p>ExoPlayer is designed to make few assumptions about (and hence impose few restrictions on) the
* type of the media being played, how and where it is stored, and how it is rendered. Rather than
* implementing the loading and rendering of media directly, ExoPlayer implementations delegate this
* work to components that are injected when a player is created or when it's prepared for playback.
* Components common to all ExoPlayer implementations are:
*
* <ul>
* <li>A <b>{@link MediaSource}</b> that defines the media to be played, loads the media, and from
* which the loaded media can be read. A MediaSource is injected via {@link #prepare(MediaSource)}
* at the start of playback. The library modules provide default implementations for regular media
* files ({@link ExtractorMediaSource}), DASH (DashMediaSource), SmoothStreaming (SsMediaSource)
* and HLS (HlsMediaSource), an implementation for loading single media samples
* ({@link SingleSampleMediaSource}) that's most often used for side-loaded subtitle files, and
* implementations for building more complex MediaSources from simpler ones
* ({@link MergingMediaSource}, {@link ConcatenatingMediaSource},
* {@link DynamicConcatenatingMediaSource}, {@link LoopingMediaSource} and
* {@link ClippingMediaSource}).</li>
* which the loaded media can be read. A MediaSource is injected via {@link
* #prepare(MediaSource)} at the start of playback. The library modules provide default
* implementations for regular media files ({@link ExtractorMediaSource}), DASH
* (DashMediaSource), SmoothStreaming (SsMediaSource) and HLS (HlsMediaSource), an
* implementation for loading single media samples ({@link SingleSampleMediaSource}) that's
* most often used for side-loaded subtitle files, and implementations for building more
* complex MediaSources from simpler ones ({@link MergingMediaSource}, {@link
* ConcatenatingMediaSource}, {@link DynamicConcatenatingMediaSource}, {@link
* LoopingMediaSource} and {@link ClippingMediaSource}).
* <li><b>{@link Renderer}</b>s that render individual components of the media. The library
* provides default implementations for common media types ({@link MediaCodecVideoRenderer},
* {@link MediaCodecAudioRenderer}, {@link TextRenderer} and {@link MetadataRenderer}). A Renderer
* consumes media from the MediaSource being played. Renderers are injected when the player is
* created.</li>
* provides default implementations for common media types ({@link MediaCodecVideoRenderer},
* {@link MediaCodecAudioRenderer}, {@link TextRenderer} and {@link MetadataRenderer}). A
* Renderer consumes media from the MediaSource being played. Renderers are injected when the
* player is created.
* <li>A <b>{@link TrackSelector}</b> that selects tracks provided by the MediaSource to be
* consumed by each of the available Renderers. The library provides a default implementation
* ({@link DefaultTrackSelector}) suitable for most use cases. A TrackSelector is injected when
* the player is created.</li>
* consumed by each of the available Renderers. The library provides a default implementation
* ({@link DefaultTrackSelector}) suitable for most use cases. A TrackSelector is injected
* when the player is created.
* <li>A <b>{@link LoadControl}</b> that controls when the MediaSource buffers more media, and how
* much media is buffered. The library provides a default implementation
* ({@link DefaultLoadControl}) suitable for most use cases. A LoadControl is injected when the
* player is created.</li>
* much media is buffered. The library provides a default implementation ({@link
* DefaultLoadControl}) suitable for most use cases. A LoadControl is injected when the player
* is created.
* </ul>
*
* <p>An ExoPlayer can be built using the default components provided by the library, but may also
* be built using custom implementations if non-standard behaviors are required. For example a
* custom LoadControl could be injected to change the player's buffering strategy, or a custom
Expand All @@ -81,30 +84,32 @@
* it's possible to load data from a non-standard source, or through a different network stack.
*
* <h3>Threading model</h3>
* <p>The figure below shows ExoPlayer's threading model.</p>
* <p align="center">
* <img src="doc-files/exoplayer-threading-model.svg" alt="ExoPlayer's threading model">
* </p>
*
* <p>The figure below shows ExoPlayer's threading model.
*
* <p align="center"><img src="doc-files/exoplayer-threading-model.svg" alt="ExoPlayer's threading
* model">
*
* <ul>
* <li>It is recommended that ExoPlayer instances are created and accessed from a single application
* thread. The application's main thread is ideal. Accessing an instance from multiple threads is
* discouraged, however if an application does wish to do this then it may do so provided that it
* ensures accesses are synchronized.</li>
* <li>Registered listeners are called on the thread that created the ExoPlayer instance, unless
* the thread that created the ExoPlayer instance does not have a {@link Looper}. In that case,
* registered listeners will be called on the application's main thread.</li>
* <li>An internal playback thread is responsible for playback. Injected player components such as
* Renderers, MediaSources, TrackSelectors and LoadControls are called by the player on this
* thread.</li>
* <li>When the application performs an operation on the player, for example a seek, a message is
* delivered to the internal playback thread via a message queue. The internal playback thread
* consumes messages from the queue and performs the corresponding operations. Similarly, when a
* playback event occurs on the internal playback thread, a message is delivered to the application
* thread via a second message queue. The application thread consumes messages from the queue,
* updating the application visible state and calling corresponding listener methods.</li>
* <li>Injected player components may use additional background threads. For example a MediaSource
* may use background threads to load data. These are implementation specific.</li>
* <li>It is recommended that ExoPlayer instances are created and accessed from a single
* application thread. The application's main thread is ideal. Accessing an instance from
* multiple threads is discouraged, however if an application does wish to do this then it may
* do so provided that it ensures accesses are synchronized.
* <li>Registered listeners are called on the thread that created the ExoPlayer instance, unless
* the thread that created the ExoPlayer instance does not have a {@link Looper}. In that
* case, registered listeners will be called on the application's main thread.
* <li>An internal playback thread is responsible for playback. Injected player components such as
* Renderers, MediaSources, TrackSelectors and LoadControls are called by the player on this
* thread.
* <li>When the application performs an operation on the player, for example a seek, a message is
* delivered to the internal playback thread via a message queue. The internal playback thread
* consumes messages from the queue and performs the corresponding operations. Similarly, when
* a playback event occurs on the internal playback thread, a message is delivered to the
* application thread via a second message queue. The application thread consumes messages
* from the queue, updating the application visible state and calling corresponding listener
* methods.
* <li>Injected player components may use additional background threads. For example a MediaSource
* may use background threads to load data. These are implementation specific.
* </ul>
*/
public interface ExoPlayer extends Player {
Expand All @@ -115,54 +120,28 @@ public interface ExoPlayer extends Player {
@Deprecated
interface EventListener extends Player.EventListener {}

/**
* A component of an {@link ExoPlayer} that can receive messages on the playback thread.
* <p>
* Messages can be delivered to a component via {@link #sendMessages} and
* {@link #blockingSendMessages}.
*/
interface ExoPlayerComponent {

/**
* Handles a message delivered to the component. Called on the playback thread.
*
* @param messageType The message type.
* @param message The message.
* @throws ExoPlaybackException If an error occurred whilst handling the message.
*/
void handleMessage(int messageType, Object message) throws ExoPlaybackException;

}
/** @deprecated Use {@link PlayerMessage.Target} instead. */
@Deprecated
interface ExoPlayerComponent extends PlayerMessage.Target {}

/**
* Defines a message and a target {@link ExoPlayerComponent} to receive it.
*/
/** @deprecated Use {@link PlayerMessage} instead. */
@Deprecated
final class ExoPlayerMessage {

/**
* The target to receive the message.
*/
public final ExoPlayerComponent target;
/**
* The type of the message.
*/
/** The target to receive the message. */
public final PlayerMessage.Target target;
/** The type of the message. */
public final int messageType;
/**
* The message.
*/
/** The message. */
public final Object message;

/**
* @param target The target of the message.
* @param messageType The message type.
* @param message The message.
*/
public ExoPlayerMessage(ExoPlayerComponent target, int messageType, Object message) {
/** @deprecated Use {@link ExoPlayer#createMessage(PlayerMessage.Target)} instead. */
@Deprecated
public ExoPlayerMessage(PlayerMessage.Target target, int messageType, Object message) {
this.target = target;
this.messageType = messageType;
this.message = message;
}

}

/**
Expand Down Expand Up @@ -236,20 +215,25 @@ public ExoPlayerMessage(ExoPlayerComponent target, int messageType, Object messa
void prepare(MediaSource mediaSource, boolean resetPosition, boolean resetState);

/**
* Sends messages to their target components. The messages are delivered on the playback thread.
* If a component throws an {@link ExoPlaybackException} then it is propagated out of the player
* as an error.
*
* @param messages The messages to be sent.
* Creates a message that can be sent to a {@link PlayerMessage.Target}. By default, the message
* will be delivered immediately without blocking on the playback thread. The default {@link
* PlayerMessage#getType()} is 0 and the default {@link PlayerMessage#getMessage()} is null. If a
* position is specified with {@link PlayerMessage#setPosition(long)}, the message will be
* delivered at this position in the current window defined by {@link #getCurrentWindowIndex()}.
* Alternatively, the message can be sent at a specific window using {@link
* PlayerMessage#setPosition(int, long)}.
*/
PlayerMessage createMessage(PlayerMessage.Target target);

/** @deprecated Use {@link #createMessage(PlayerMessage.Target)} instead. */
@Deprecated
void sendMessages(ExoPlayerMessage... messages);

/**
* Variant of {@link #sendMessages(ExoPlayerMessage...)} that blocks until after the messages have
* been delivered.
*
* @param messages The messages to be sent.
* @deprecated Use {@link #createMessage(PlayerMessage.Target)} with {@link
* PlayerMessage#blockUntilDelivered()}.
*/
@Deprecated
void blockingSendMessages(ExoPlayerMessage... messages);

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import android.support.annotation.Nullable;
import android.util.Log;
import android.util.Pair;
import com.google.android.exoplayer2.PlayerMessage.Target;
import com.google.android.exoplayer2.source.MediaSource;
import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId;
import com.google.android.exoplayer2.source.TrackGroupArray;
Expand All @@ -31,6 +32,8 @@
import com.google.android.exoplayer2.trackselection.TrackSelectorResult;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.Util;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CopyOnWriteArraySet;

/**
Expand All @@ -45,6 +48,7 @@
private final TrackSelectorResult emptyTrackSelectorResult;
private final Handler eventHandler;
private final ExoPlayerImplInternal internalPlayer;
private final Handler internalPlayerHandler;
private final CopyOnWriteArraySet<Player.EventListener> listeners;
private final Timeline.Window window;
private final Timeline.Period period;
Expand Down Expand Up @@ -113,6 +117,7 @@ public void handleMessage(Message msg) {
shuffleModeEnabled,
eventHandler,
this);
internalPlayerHandler = new Handler(internalPlayer.getPlaybackLooper());
}

@Override
Expand Down Expand Up @@ -326,12 +331,47 @@ public void release() {

@Override
public void sendMessages(ExoPlayerMessage... messages) {
internalPlayer.sendMessages(messages);
for (ExoPlayerMessage message : messages) {
createMessage(message.target).setType(message.messageType).setMessage(message.message).send();
}
}

@Override
public PlayerMessage createMessage(Target target) {
return new PlayerMessage(
internalPlayer,
target,
playbackInfo.timeline,
getCurrentWindowIndex(),
internalPlayerHandler);
}

@Override
public void blockingSendMessages(ExoPlayerMessage... messages) {
internalPlayer.blockingSendMessages(messages);
List<PlayerMessage> playerMessages = new ArrayList<>();
for (ExoPlayerMessage message : messages) {
playerMessages.add(
createMessage(message.target)
.setType(message.messageType)
.setMessage(message.message)
.send());
}
boolean wasInterrupted = false;
for (PlayerMessage message : playerMessages) {
boolean blockMessage = true;
while (blockMessage) {
try {
message.blockUntilDelivered();
blockMessage = false;
} catch (InterruptedException e) {
wasInterrupted = true;
}
}
}
if (wasInterrupted) {
// Restore the interrupted status.
Thread.currentThread().interrupt();
}
}

@Override
Expand Down
Loading

0 comments on commit 6c2d1e1

Please sign in to comment.