Commit f8d84eec by hschlueter Committed by Ian Baker

Allow multiple Transformer listeners to be registered.

Multiple listeners can be added to Transformer and its builder.
All or specific listeners can also be removed.

PiperOrigin-RevId: 421047650
parent bf32ae50
...@@ -83,6 +83,7 @@ ...@@ -83,6 +83,7 @@
* `TransformationException` is now used to describe errors that occur * `TransformationException` is now used to describe errors that occur
during a transformation. during a transformation.
* Add `TransformationRequest` for specifying the transformation options. * Add `TransformationRequest` for specifying the transformation options.
* Allow multiple listeners to be registered.
* MediaSession extension: * MediaSession extension:
* Remove deprecated call to `onStop(/* reset= */ true)` and provide an * Remove deprecated call to `onStop(/* reset= */ true)` and provide an
opt-out flag for apps that don't want to clear the playlist on stop. opt-out flag for apps that don't want to clear the playlist on stop.
......
...@@ -34,7 +34,7 @@ transformation that removes the audio track from the input: ...@@ -34,7 +34,7 @@ transformation that removes the audio track from the input:
Transformer transformer = Transformer transformer =
new Transformer.Builder(context) new Transformer.Builder(context)
.setRemoveAudio(true) .setRemoveAudio(true)
.setListener(transformerListener) .addListener(transformerListener)
.build(); .build();
// Start the transformation. // Start the transformation.
transformer.startTransformation(inputMediaItem, outputPath); transformer.startTransformation(inputMediaItem, outputPath);
...@@ -121,7 +121,7 @@ method. ...@@ -121,7 +121,7 @@ method.
Transformer transformer = Transformer transformer =
new Transformer.Builder(context) new Transformer.Builder(context)
.setFlattenForSlowMotion(true) .setFlattenForSlowMotion(true)
.setListener(transformerListener) .addListener(transformerListener)
.build(); .build();
transformer.startTransformation(inputMediaItem, outputPath); transformer.startTransformation(inputMediaItem, outputPath);
~~~ ~~~
......
...@@ -116,6 +116,21 @@ public final class ListenerSet<T extends @NonNull Object> { ...@@ -116,6 +116,21 @@ public final class ListenerSet<T extends @NonNull Object> {
*/ */
@CheckResult @CheckResult
public ListenerSet<T> copy(Looper looper, IterationFinishedEvent<T> iterationFinishedEvent) { public ListenerSet<T> copy(Looper looper, IterationFinishedEvent<T> iterationFinishedEvent) {
return copy(looper, clock, iterationFinishedEvent);
}
/**
* Copies the listener set.
*
* @param looper The new {@link Looper} for the copied listener set.
* @param clock The new {@link Clock} for the copied listener set.
* @param iterationFinishedEvent The new {@link IterationFinishedEvent} sent when all other events
* sent during one {@link Looper} message queue iteration were handled by the listeners.
* @return The copied listener set.
*/
@CheckResult
public ListenerSet<T> copy(
Looper looper, Clock clock, IterationFinishedEvent<T> iterationFinishedEvent) {
return new ListenerSet<>(listeners, looper, clock, iterationFinishedEvent); return new ListenerSet<>(listeners, looper, clock, iterationFinishedEvent);
} }
...@@ -150,6 +165,11 @@ public final class ListenerSet<T extends @NonNull Object> { ...@@ -150,6 +165,11 @@ public final class ListenerSet<T extends @NonNull Object> {
} }
} }
/** Removes all listeners from the set. */
public void clear() {
listeners.clear();
}
/** Returns the number of added listeners. */ /** Returns the number of added listeners. */
public int size() { public int size() {
return listeners.size(); return listeners.size();
......
...@@ -72,7 +72,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; ...@@ -72,7 +72,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
Transformer testTransformer = Transformer testTransformer =
transformer transformer
.buildUpon() .buildUpon()
.setListener( .addListener(
new Transformer.Listener() { new Transformer.Listener() {
@Override @Override
public void onTransformationCompleted(MediaItem inputMediaItem) { public void onTransformationCompleted(MediaItem inputMediaItem) {
......
...@@ -54,6 +54,7 @@ import com.google.android.exoplayer2.source.MediaSource; ...@@ -54,6 +54,7 @@ import com.google.android.exoplayer2.source.MediaSource;
import com.google.android.exoplayer2.text.TextOutput; import com.google.android.exoplayer2.text.TextOutput;
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector; import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
import com.google.android.exoplayer2.util.Clock; import com.google.android.exoplayer2.util.Clock;
import com.google.android.exoplayer2.util.ListenerSet;
import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.util.Util;
import com.google.android.exoplayer2.video.VideoRendererEventListener; import com.google.android.exoplayer2.video.VideoRendererEventListener;
...@@ -98,7 +99,7 @@ public final class Transformer { ...@@ -98,7 +99,7 @@ public final class Transformer {
private boolean removeVideo; private boolean removeVideo;
private String containerMimeType; private String containerMimeType;
private TransformationRequest transformationRequest; private TransformationRequest transformationRequest;
private Transformer.Listener listener; private ListenerSet<Transformer.Listener> listeners;
private DebugViewProvider debugViewProvider; private DebugViewProvider debugViewProvider;
private Looper looper; private Looper looper;
private Clock clock; private Clock clock;
...@@ -108,9 +109,9 @@ public final class Transformer { ...@@ -108,9 +109,9 @@ public final class Transformer {
@Deprecated @Deprecated
public Builder() { public Builder() {
muxerFactory = new FrameworkMuxer.Factory(); muxerFactory = new FrameworkMuxer.Factory();
listener = new Listener() {};
looper = Util.getCurrentOrMainLooper(); looper = Util.getCurrentOrMainLooper();
clock = Clock.DEFAULT; clock = Clock.DEFAULT;
listeners = new ListenerSet<>(looper, clock, (listener, flags) -> {});
encoderFactory = Codec.EncoderFactory.DEFAULT; encoderFactory = Codec.EncoderFactory.DEFAULT;
debugViewProvider = DebugViewProvider.NONE; debugViewProvider = DebugViewProvider.NONE;
containerMimeType = MimeTypes.VIDEO_MP4; containerMimeType = MimeTypes.VIDEO_MP4;
...@@ -125,9 +126,9 @@ public final class Transformer { ...@@ -125,9 +126,9 @@ public final class Transformer {
public Builder(Context context) { public Builder(Context context) {
this.context = context.getApplicationContext(); this.context = context.getApplicationContext();
muxerFactory = new FrameworkMuxer.Factory(); muxerFactory = new FrameworkMuxer.Factory();
listener = new Listener() {};
looper = Util.getCurrentOrMainLooper(); looper = Util.getCurrentOrMainLooper();
clock = Clock.DEFAULT; clock = Clock.DEFAULT;
listeners = new ListenerSet<>(looper, clock, (listener, flags) -> {});
encoderFactory = Codec.EncoderFactory.DEFAULT; encoderFactory = Codec.EncoderFactory.DEFAULT;
debugViewProvider = DebugViewProvider.NONE; debugViewProvider = DebugViewProvider.NONE;
containerMimeType = MimeTypes.VIDEO_MP4; containerMimeType = MimeTypes.VIDEO_MP4;
...@@ -143,7 +144,7 @@ public final class Transformer { ...@@ -143,7 +144,7 @@ public final class Transformer {
this.removeVideo = transformer.removeVideo; this.removeVideo = transformer.removeVideo;
this.containerMimeType = transformer.containerMimeType; this.containerMimeType = transformer.containerMimeType;
this.transformationRequest = transformer.transformationRequest; this.transformationRequest = transformer.transformationRequest;
this.listener = transformer.listener; this.listeners = transformer.listeners;
this.looper = transformer.looper; this.looper = transformer.looper;
this.encoderFactory = transformer.encoderFactory; this.encoderFactory = transformer.encoderFactory;
this.debugViewProvider = transformer.debugViewProvider; this.debugViewProvider = transformer.debugViewProvider;
...@@ -265,15 +266,51 @@ public final class Transformer { ...@@ -265,15 +266,51 @@ public final class Transformer {
} }
/** /**
* Sets the {@link Transformer.Listener} to listen to the transformation events. * @deprecated Use {@link #addListener(Listener)}, {@link #removeListener(Listener)} or {@link
* #removeAllListeners()} instead.
*/
@Deprecated
public Builder setListener(Transformer.Listener listener) {
this.listeners.clear();
this.listeners.add(listener);
return this;
}
/**
* Adds a {@link Transformer.Listener} to listen to the transformation events.
* *
* <p>This is equivalent to {@link Transformer#setListener(Listener)}. * <p>This is equivalent to {@link Transformer#addListener(Listener)}.
* *
* @param listener A {@link Transformer.Listener}. * @param listener A {@link Transformer.Listener}.
* @return This builder. * @return This builder.
*/ */
public Builder setListener(Transformer.Listener listener) { public Builder addListener(Transformer.Listener listener) {
this.listener = listener; this.listeners.add(listener);
return this;
}
/**
* Removes a {@link Transformer.Listener}.
*
* <p>This is equivalent to {@link Transformer#removeListener(Listener)}.
*
* @param listener A {@link Transformer.Listener}.
* @return This builder.
*/
public Builder removeListener(Transformer.Listener listener) {
this.listeners.remove(listener);
return this;
}
/**
* Removes all {@link Transformer.Listener listeners}.
*
* <p>This is equivalent to {@link Transformer#removeAllListeners()}.
*
* @return This builder.
*/
public Builder removeAllListeners() {
this.listeners.clear();
return this; return this;
} }
...@@ -288,6 +325,7 @@ public final class Transformer { ...@@ -288,6 +325,7 @@ public final class Transformer {
*/ */
public Builder setLooper(Looper looper) { public Builder setLooper(Looper looper) {
this.looper = looper; this.looper = looper;
this.listeners = listeners.copy(looper, (listener, flags) -> {});
return this; return this;
} }
...@@ -328,6 +366,7 @@ public final class Transformer { ...@@ -328,6 +366,7 @@ public final class Transformer {
@VisibleForTesting @VisibleForTesting
/* package */ Builder setClock(Clock clock) { /* package */ Builder setClock(Clock clock) {
this.clock = clock; this.clock = clock;
this.listeners = listeners.copy(looper, clock, (listener, flags) -> {});
return this; return this;
} }
...@@ -381,7 +420,7 @@ public final class Transformer { ...@@ -381,7 +420,7 @@ public final class Transformer {
removeVideo, removeVideo,
containerMimeType, containerMimeType,
transformationRequest, transformationRequest,
listener, listeners,
looper, looper,
clock, clock,
encoderFactory, encoderFactory,
...@@ -480,8 +519,8 @@ public final class Transformer { ...@@ -480,8 +519,8 @@ public final class Transformer {
private final Codec.EncoderFactory encoderFactory; private final Codec.EncoderFactory encoderFactory;
private final Codec.DecoderFactory decoderFactory; private final Codec.DecoderFactory decoderFactory;
private final Transformer.DebugViewProvider debugViewProvider; private final Transformer.DebugViewProvider debugViewProvider;
private final ListenerSet<Transformer.Listener> listeners;
private Transformer.Listener listener;
@Nullable private MuxerWrapper muxerWrapper; @Nullable private MuxerWrapper muxerWrapper;
@Nullable private ExoPlayer player; @Nullable private ExoPlayer player;
@ProgressState private int progressState; @ProgressState private int progressState;
...@@ -494,7 +533,7 @@ public final class Transformer { ...@@ -494,7 +533,7 @@ public final class Transformer {
boolean removeVideo, boolean removeVideo,
String containerMimeType, String containerMimeType,
TransformationRequest transformationRequest, TransformationRequest transformationRequest,
Transformer.Listener listener, ListenerSet<Transformer.Listener> listeners,
Looper looper, Looper looper,
Clock clock, Clock clock,
Codec.EncoderFactory encoderFactory, Codec.EncoderFactory encoderFactory,
...@@ -508,7 +547,7 @@ public final class Transformer { ...@@ -508,7 +547,7 @@ public final class Transformer {
this.removeVideo = removeVideo; this.removeVideo = removeVideo;
this.containerMimeType = containerMimeType; this.containerMimeType = containerMimeType;
this.transformationRequest = transformationRequest; this.transformationRequest = transformationRequest;
this.listener = listener; this.listeners = listeners;
this.looper = looper; this.looper = looper;
this.clock = clock; this.clock = clock;
this.encoderFactory = encoderFactory; this.encoderFactory = encoderFactory;
...@@ -523,20 +562,52 @@ public final class Transformer { ...@@ -523,20 +562,52 @@ public final class Transformer {
} }
/** /**
* Sets the {@link Transformer.Listener} to listen to the transformation events. * @deprecated Use {@link #addListener(Listener)}, {@link #removeListener(Listener)} or {@link
* #removeAllListeners()} instead.
*/
@Deprecated
public void setListener(Transformer.Listener listener) {
verifyApplicationThread();
this.listeners.clear();
this.listeners.add(listener);
}
/**
* Adds a {@link Transformer.Listener} to listen to the transformation events.
* *
* @param listener A {@link Transformer.Listener}. * @param listener A {@link Transformer.Listener}.
* @throws IllegalStateException If this method is called from the wrong thread. * @throws IllegalStateException If this method is called from the wrong thread.
*/ */
public void setListener(Transformer.Listener listener) { public void addListener(Transformer.Listener listener) {
verifyApplicationThread();
this.listeners.add(listener);
}
/**
* Removes a {@link Transformer.Listener}.
*
* @param listener A {@link Transformer.Listener}.
* @throws IllegalStateException If this method is called from the wrong thread.
*/
public void removeListener(Transformer.Listener listener) {
verifyApplicationThread();
this.listeners.remove(listener);
}
/**
* Removes all {@link Transformer.Listener listeners}.
*
* @throws IllegalStateException If this method is called from the wrong thread.
*/
public void removeAllListeners() {
verifyApplicationThread(); verifyApplicationThread();
this.listener = listener; this.listeners.clear();
} }
/** /**
* Starts an asynchronous operation to transform the given {@link MediaItem}. * Starts an asynchronous operation to transform the given {@link MediaItem}.
* *
* <p>The transformation state is notified through the {@link Builder#setListener(Listener) * <p>The transformation state is notified through the {@link Builder#addListener(Listener)
* listener}. * listener}.
* *
* <p>Concurrent transformations on the same Transformer object are not allowed. * <p>Concurrent transformations on the same Transformer object are not allowed.
...@@ -559,7 +630,7 @@ public final class Transformer { ...@@ -559,7 +630,7 @@ public final class Transformer {
/** /**
* Starts an asynchronous operation to transform the given {@link MediaItem}. * Starts an asynchronous operation to transform the given {@link MediaItem}.
* *
* <p>The transformation state is notified through the {@link Builder#setListener(Listener) * <p>The transformation state is notified through the {@link Builder#addListener(Listener)
* listener}. * listener}.
* *
* <p>Concurrent transformations on the same Transformer object are not allowed. * <p>Concurrent transformations on the same Transformer object are not allowed.
...@@ -840,16 +911,26 @@ public final class Transformer { ...@@ -840,16 +911,26 @@ public final class Transformer {
} }
if (exception == null && resourceReleaseException == null) { if (exception == null && resourceReleaseException == null) {
listener.onTransformationCompleted(mediaItem); // TODO(b/213341814): Add event flags for Transformer events.
listeners.queueEvent(
/* eventFlag= */ C.INDEX_UNSET,
listener -> listener.onTransformationCompleted(mediaItem));
listeners.flushEvents();
return; return;
} }
if (exception != null) { if (exception != null) {
listener.onTransformationError(mediaItem, exception); listeners.queueEvent(
/* eventFlag= */ C.INDEX_UNSET,
listener -> listener.onTransformationError(mediaItem, exception));
} }
if (resourceReleaseException != null) { if (resourceReleaseException != null) {
listener.onTransformationError(mediaItem, resourceReleaseException); TransformationException finalResourceReleaseException = resourceReleaseException;
listeners.queueEvent(
/* eventFlag= */ C.INDEX_UNSET,
listener -> listener.onTransformationError(mediaItem, finalResourceReleaseException));
} }
listeners.flushEvents();
} }
} }
} }
...@@ -22,6 +22,9 @@ import static com.google.android.exoplayer2.transformer.Transformer.PROGRESS_STA ...@@ -22,6 +22,9 @@ import static com.google.android.exoplayer2.transformer.Transformer.PROGRESS_STA
import static com.google.android.exoplayer2.transformer.Transformer.PROGRESS_STATE_WAITING_FOR_AVAILABILITY; import static com.google.android.exoplayer2.transformer.Transformer.PROGRESS_STATE_WAITING_FOR_AVAILABILITY;
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertThrows;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import android.content.Context; import android.content.Context;
import android.media.MediaCrypto; import android.media.MediaCrypto;
...@@ -249,6 +252,77 @@ public final class TransformerTest { ...@@ -249,6 +252,77 @@ public final class TransformerTest {
} }
@Test @Test
public void startTransformation_withMultipleListeners_callsEachOnCompletion() throws Exception {
Transformer.Listener mockListener1 = mock(Transformer.Listener.class);
Transformer.Listener mockListener2 = mock(Transformer.Listener.class);
Transformer.Listener mockListener3 = mock(Transformer.Listener.class);
Transformer transformer =
new Transformer.Builder(context)
.setClock(clock)
.setMuxerFactory(new TestMuxerFactory())
.addListener(mockListener1)
.addListener(mockListener2)
.addListener(mockListener3)
.build();
MediaItem mediaItem = MediaItem.fromUri(URI_PREFIX + FILE_AUDIO_VIDEO);
transformer.startTransformation(mediaItem, outputPath);
TransformerTestRunner.runUntilCompleted(transformer);
verify(mockListener1, times(1)).onTransformationCompleted(mediaItem);
verify(mockListener2, times(1)).onTransformationCompleted(mediaItem);
verify(mockListener3, times(1)).onTransformationCompleted(mediaItem);
}
@Test
public void startTransformation_withMultipleListeners_callsEachOnError() throws Exception {
Transformer.Listener mockListener1 = mock(Transformer.Listener.class);
Transformer.Listener mockListener2 = mock(Transformer.Listener.class);
Transformer.Listener mockListener3 = mock(Transformer.Listener.class);
Transformer transformer =
new Transformer.Builder(context)
.setClock(clock)
.setMuxerFactory(new TestMuxerFactory())
.addListener(mockListener1)
.addListener(mockListener2)
.addListener(mockListener3)
.build();
MediaItem mediaItem = MediaItem.fromUri(URI_PREFIX + FILE_AUDIO_UNSUPPORTED_BY_MUXER);
transformer.startTransformation(mediaItem, outputPath);
TransformationException exception = TransformerTestRunner.runUntilError(transformer);
verify(mockListener1, times(1)).onTransformationError(mediaItem, exception);
verify(mockListener2, times(1)).onTransformationError(mediaItem, exception);
verify(mockListener3, times(1)).onTransformationError(mediaItem, exception);
}
@Test
public void startTransformation_afterBuildUponWithListenerRemoved_onlyCallsRemainingListeners()
throws Exception {
Transformer.Listener mockListener1 = mock(Transformer.Listener.class);
Transformer.Listener mockListener2 = mock(Transformer.Listener.class);
Transformer.Listener mockListener3 = mock(Transformer.Listener.class);
Transformer transformer1 =
new Transformer.Builder(context)
.setClock(clock)
.setMuxerFactory(new TestMuxerFactory())
.addListener(mockListener1)
.addListener(mockListener2)
.addListener(mockListener3)
.build();
Transformer transformer2 = transformer1.buildUpon().removeListener(mockListener2).build();
MediaItem mediaItem = MediaItem.fromUri(URI_PREFIX + FILE_AUDIO_VIDEO);
transformer2.startTransformation(mediaItem, outputPath);
TransformerTestRunner.runUntilCompleted(transformer2);
verify(mockListener1, times(1)).onTransformationCompleted(mediaItem);
verify(mockListener2, times(0)).onTransformationCompleted(mediaItem);
verify(mockListener3, times(1)).onTransformationCompleted(mediaItem);
}
@Test
public void startTransformation_flattenForSlowMotion_completesSuccessfully() throws Exception { public void startTransformation_flattenForSlowMotion_completesSuccessfully() throws Exception {
Transformer transformer = Transformer transformer =
new Transformer.Builder(context) new Transformer.Builder(context)
......
...@@ -69,7 +69,7 @@ public final class TransformerTestRunner { ...@@ -69,7 +69,7 @@ public final class TransformerTestRunner {
private static TransformationException runUntilListenerCalled(Transformer transformer) private static TransformationException runUntilListenerCalled(Transformer transformer)
throws TimeoutException { throws TimeoutException {
TransformationResult transformationResult = new TransformationResult(); TransformationResult transformationResult = new TransformationResult();
Transformer.Listener listener = transformer.addListener(
new Transformer.Listener() { new Transformer.Listener() {
@Override @Override
public void onTransformationCompleted(MediaItem inputMediaItem) { public void onTransformationCompleted(MediaItem inputMediaItem) {
...@@ -81,8 +81,7 @@ public final class TransformerTestRunner { ...@@ -81,8 +81,7 @@ public final class TransformerTestRunner {
MediaItem inputMediaItem, TransformationException exception) { MediaItem inputMediaItem, TransformationException exception) {
transformationResult.exception = exception; transformationResult.exception = exception;
} }
}; });
transformer.setListener(listener);
runLooperUntil( runLooperUntil(
transformer.getApplicationLooper(), transformer.getApplicationLooper(),
() -> transformationResult.isCompleted || transformationResult.exception != null); () -> transformationResult.isCompleted || transformationResult.exception != null);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment