diff --git a/demos/main/src/main/assets/media.exolist.json b/demos/main/src/main/assets/media.exolist.json index e6cb246db1..0b479ff6d5 100644 --- a/demos/main/src/main/assets/media.exolist.json +++ b/demos/main/src/main/assets/media.exolist.json @@ -542,10 +542,6 @@ { "name": "Misc", "samples": [ - { - "name": "User File", - "uri": "content://user" - }, { "name": "Dizzy (MP4)", "uri": "https://html5demos.com/assets/dizzy.mp4" diff --git a/demos/main/src/main/java/com/google/android/exoplayer2/demo/SampleChooserActivity.java b/demos/main/src/main/java/com/google/android/exoplayer2/demo/SampleChooserActivity.java index 74464e914a..b79a7a62ca 100644 --- a/demos/main/src/main/java/com/google/android/exoplayer2/demo/SampleChooserActivity.java +++ b/demos/main/src/main/java/com/google/android/exoplayer2/demo/SampleChooserActivity.java @@ -19,7 +19,6 @@ import static com.google.android.exoplayer2.util.Assertions.checkArgument; import static com.google.android.exoplayer2.util.Assertions.checkNotNull; import static com.google.android.exoplayer2.util.Assertions.checkState; -import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; @@ -41,8 +40,6 @@ import android.widget.ExpandableListView.OnChildClickListener; import android.widget.ImageButton; import android.widget.TextView; import android.widget.Toast; -import androidx.activity.result.ActivityResultLauncher; -import androidx.activity.result.contract.ActivityResultContracts; import androidx.annotation.Nullable; import androidx.appcompat.app.AppCompatActivity; import com.google.android.exoplayer2.MediaItem; @@ -76,7 +73,6 @@ public class SampleChooserActivity extends AppCompatActivity private static final String TAG = "SampleChooserActivity"; private static final String GROUP_POSITION_PREFERENCE_KEY = "sample_chooser_group_position"; private static final String CHILD_POSITION_PREFERENCE_KEY = "sample_chooser_child_position"; - private static final Uri USER_CONTENT = new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority("user").build(); private String[] uris; private boolean useExtensionRenderers; @@ -84,13 +80,6 @@ public class SampleChooserActivity extends AppCompatActivity private SampleAdapter sampleAdapter; private MenuItem preferExtensionDecodersMenuItem; private ExpandableListView sampleListView; - private final ActivityResultLauncher openDocumentLauncher = registerForActivityResult( - new ActivityResultContracts.OpenDocument(), uri -> { - if (uri != null) { - final MediaItem mediaItem = new MediaItem.Builder().setUri(uri).build(); - startPlayer(Collections.singletonList(mediaItem)); - } - }); @Override public void onCreate(Bundle savedInstanceState) { @@ -234,25 +223,13 @@ public class SampleChooserActivity extends AppCompatActivity prefEditor.apply(); PlaylistHolder playlistHolder = (PlaylistHolder) view.getTag(); - final List mediaItems = playlistHolder.mediaItems; - if (!mediaItems.isEmpty()) { - final MediaItem mediaItem = mediaItems.get(0); - if (mediaItem.localConfiguration != null && USER_CONTENT.equals(mediaItem.localConfiguration.uri)) { - openDocumentLauncher.launch(new String[]{"video/*","audio/*"}); - return true; - } - } - startPlayer(playlistHolder.mediaItems); - return true; - } - - private void startPlayer(final List mediaItems) { Intent intent = new Intent(this, PlayerActivity.class); intent.putExtra( IntentUtil.PREFER_EXTENSION_DECODERS_EXTRA, isNonNullAndChecked(preferExtensionDecodersMenuItem)); - IntentUtil.addToIntent(mediaItems, intent); + IntentUtil.addToIntent(playlistHolder.mediaItems, intent); startActivity(intent); + return true; } private void onSampleDownloadButtonClicked(PlaylistHolder playlistHolder) { diff --git a/library/core/build.gradle b/library/core/build.gradle index 3196735378..5bf70ad151 100644 --- a/library/core/build.gradle +++ b/library/core/build.gradle @@ -21,11 +21,7 @@ android { testInstrumentationRunnerArguments clearPackageData: 'true' multiDexEnabled true } - testOptions{ - unitTests.all { - jvmArgs '-noverify' - } - } + buildTypes { debug { testCoverageEnabled = true diff --git a/library/core/src/main/java/com/google/android/exoplayer2/DefaultRenderersFactory.java b/library/core/src/main/java/com/google/android/exoplayer2/DefaultRenderersFactory.java index 4b0fff8ffc..0d1c126dc5 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/DefaultRenderersFactory.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/DefaultRenderersFactory.java @@ -27,7 +27,6 @@ import com.google.android.exoplayer2.audio.AudioRendererEventListener; import com.google.android.exoplayer2.audio.AudioSink; import com.google.android.exoplayer2.audio.DefaultAudioSink; import com.google.android.exoplayer2.audio.MediaCodecAudioRenderer; -import com.google.android.exoplayer2.video.BitmapFactoryVideoRenderer; import com.google.android.exoplayer2.mediacodec.DefaultMediaCodecAdapterFactory; import com.google.android.exoplayer2.mediacodec.MediaCodecAdapter; import com.google.android.exoplayer2.mediacodec.MediaCodecSelector; @@ -396,7 +395,6 @@ public class DefaultRenderersFactory implements RenderersFactory { eventListener, MAX_DROPPED_VIDEO_FRAME_COUNT_TO_NOTIFY); out.add(videoRenderer); - out.add(new BitmapFactoryVideoRenderer(eventHandler, eventListener)); if (extensionRendererMode == EXTENSION_RENDERER_MODE_OFF) { return; diff --git a/library/core/src/main/java/com/google/android/exoplayer2/video/BitmapFactoryVideoRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/video/BitmapFactoryVideoRenderer.java deleted file mode 100644 index 31dba8ef3e..0000000000 --- a/library/core/src/main/java/com/google/android/exoplayer2/video/BitmapFactoryVideoRenderer.java +++ /dev/null @@ -1,317 +0,0 @@ -package com.google.android.exoplayer2.video; - -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.graphics.Canvas; -import android.graphics.Rect; -import android.os.Handler; -import android.os.SystemClock; -import android.view.Surface; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.annotation.VisibleForTesting; -import androidx.annotation.WorkerThread; -import androidx.arch.core.util.Function; -import com.google.android.exoplayer2.BaseRenderer; -import com.google.android.exoplayer2.C; -import com.google.android.exoplayer2.ExoPlaybackException; -import com.google.android.exoplayer2.Format; -import com.google.android.exoplayer2.FormatHolder; -import com.google.android.exoplayer2.RendererCapabilities; -import com.google.android.exoplayer2.decoder.DecoderCounters; -import com.google.android.exoplayer2.decoder.DecoderInputBuffer; -import com.google.android.exoplayer2.source.SampleStream; -import com.google.android.exoplayer2.util.MimeTypes; -import java.nio.ByteBuffer; - -public class BitmapFactoryVideoRenderer extends BaseRenderer { - static final String TAG = "BitmapFactoryRenderer"; - - //Sleep Reasons - static final String STREAM_END = "Stream End"; - static final String STREAM_EMPTY = "Stream Empty"; - static final String RENDER_WAIT = "Render Wait"; - - private static int threadId; - - private final Rect rect = new Rect(); - private final RenderRunnable renderRunnable = new RenderRunnable(); - - final VideoRendererEventListener.EventDispatcher eventDispatcher; - final Thread thread = new Thread(renderRunnable, getClass().getSimpleName() + threadId++); - - @Nullable - volatile Surface surface; - - private VideoSize lastVideoSize = VideoSize.UNKNOWN; - private long currentTimeUs; - private long frameUs; - private boolean firstFrameRendered; - @Nullable - private DecoderCounters decoderCounters; - - public BitmapFactoryVideoRenderer(@Nullable Handler eventHandler, - @Nullable VideoRendererEventListener eventListener) { - super(C.TRACK_TYPE_VIDEO); - eventDispatcher = new VideoRendererEventListener.EventDispatcher(eventHandler, eventListener); - } - - @NonNull - @Override - public String getName() { - return TAG; - } - - @Override - protected void onEnabled(boolean joining, boolean mayRenderStartOfStream) - throws ExoPlaybackException { - decoderCounters = new DecoderCounters(); - eventDispatcher.enabled(decoderCounters); - if (mayRenderStartOfStream) { - thread.start(); - } - } - - @Override - protected void onStarted() throws ExoPlaybackException { - if (thread.getState() == Thread.State.NEW) { - thread.start(); - } - } - - @Override - protected void onDisabled() { - renderRunnable.stop(); - - @Nullable - final DecoderCounters decoderCounters = this.decoderCounters; - if (decoderCounters != null) { - eventDispatcher.disabled(decoderCounters); - } - } - - @Override - public void render(long positionUs, long elapsedRealtimeUs) throws ExoPlaybackException { - //Log.d(TAG, "Render: us=" + positionUs); - synchronized (renderRunnable) { - currentTimeUs = positionUs; - renderRunnable.notify(); - } - } - - @Override - protected void onPositionReset(long positionUs, boolean joining) throws ExoPlaybackException { - thread.interrupt(); - } - - @Override - public void handleMessage(int messageType, @Nullable Object message) throws ExoPlaybackException { - if (messageType == MSG_SET_VIDEO_OUTPUT) { - if (message instanceof Surface) { - surface = (Surface) message; - } else { - surface = null; - } - } - super.handleMessage(messageType, message); - } - - @Override - public boolean isReady() { - return surface != null; - } - - @Override - public boolean isEnded() { - return renderRunnable.isEnded(); - } - - @Override - public int supportsFormat(Format format) throws ExoPlaybackException { - //Technically could support any format BitmapFactory supports - if (MimeTypes.VIDEO_MJPEG.equals(format.sampleMimeType)) { - return RendererCapabilities.create(C.FORMAT_HANDLED); - } - return RendererCapabilities.create(C.FORMAT_UNSUPPORTED_TYPE); - } - - @WorkerThread - private void onFormatChanged(@NonNull FormatHolder formatHolder) { - @Nullable final Format format = formatHolder.format; - if (format != null) { - frameUs = (long)(1_000_000L / format.frameRate); - eventDispatcher.inputFormatChanged(format, null); - } - } - - @WorkerThread - void renderBitmap(@NonNull final Bitmap bitmap) { - @Nullable - final Surface surface = this.surface; - if (surface == null) { - return; - } - //Log.d(TAG, "Drawing: " + bitmap.getWidth() + "x" + bitmap.getHeight()); - final Canvas canvas = surface.lockCanvas(null); - - renderBitmap(bitmap, canvas); - - surface.unlockCanvasAndPost(canvas); - @Nullable - final DecoderCounters decoderCounters = BitmapFactoryVideoRenderer.this.decoderCounters; - if (decoderCounters != null) { - decoderCounters.renderedOutputBufferCount++; - } - if (!firstFrameRendered) { - firstFrameRendered = true; - eventDispatcher.renderedFirstFrame(surface); - } - } - - @WorkerThread - @VisibleForTesting - void renderBitmap(Bitmap bitmap, Canvas canvas) { - final VideoSize videoSize = new VideoSize(bitmap.getWidth(), bitmap.getHeight()); - if (!videoSize.equals(lastVideoSize)) { - lastVideoSize = videoSize; - eventDispatcher.videoSizeChanged(videoSize); - } - rect.set(0,0,canvas.getWidth(), canvas.getHeight()); - canvas.drawBitmap(bitmap, null, rect, null); - } - - class RenderRunnable implements Runnable, Function { - final DecoderInputBuffer decoderInputBuffer = - new DecoderInputBuffer(DecoderInputBuffer.BUFFER_REPLACEMENT_MODE_NORMAL); - - private volatile boolean running = true; - - @VisibleForTesting - Function sleepFunction = this; - - void stop() { - running = false; - thread.interrupt(); - } - - boolean isEnded() { - return !running || decoderInputBuffer.isEndOfStream(); - } - - @Nullable - private Bitmap decodeInputBuffer(final DecoderInputBuffer decoderInputBuffer) { - @Nullable final ByteBuffer byteBuffer = decoderInputBuffer.data; - if (byteBuffer != null) { - final Bitmap bitmap; - try { - bitmap = BitmapFactory.decodeByteArray(byteBuffer.array(), byteBuffer.arrayOffset(), - byteBuffer.arrayOffset() + byteBuffer.position()); - if (bitmap == null) { - throw new NullPointerException("Decode bytes failed"); - } else { - return bitmap; - } - } catch (Exception e) { - eventDispatcher.videoCodecError(e); - } - } - return null; - } - - /** - * - * @return true if interrupted - */ - public synchronized Boolean apply(String why) { - try { - wait(); - return false; - } catch (InterruptedException e) { - //If we are interrupted, treat as a cancel - return true; - } - } - - private boolean sleep(String why) { - return sleepFunction.apply(why); - } - - @WorkerThread - public void run() { - final FormatHolder formatHolder = getFormatHolder(); - long start = SystemClock.uptimeMillis(); - main: - while (running) { - decoderInputBuffer.clear(); - final int result = readSource(formatHolder, decoderInputBuffer, - formatHolder.format == null ? SampleStream.FLAG_REQUIRE_FORMAT : 0); - switch (result) { - case C.RESULT_BUFFER_READ: { - if (decoderInputBuffer.isEndOfStream()) { - //Wait for shutdown or stream to be changed - sleep(STREAM_END); - continue; - } - final long leadUs = decoderInputBuffer.timeUs - currentTimeUs; - //If we are more than 1/2 a frame behind, skip the next frame - if (leadUs < -frameUs / 2) { - eventDispatcher.droppedFrames(1, SystemClock.uptimeMillis() - start); - start = SystemClock.uptimeMillis(); - continue; - } - start = SystemClock.uptimeMillis(); - - @Nullable - final Bitmap bitmap = decodeInputBuffer(decoderInputBuffer); - if (bitmap == null) { - continue; - } - while (currentTimeUs < decoderInputBuffer.timeUs) { - //Log.d(TAG, "Sleep: us=" + currentTimeUs); - if (sleep(RENDER_WAIT)) { - //Sleep was interrupted, discard Bitmap - continue main; - } - } - if (running) { - renderBitmap(bitmap); - } - } - break; - case C.RESULT_FORMAT_READ: - onFormatChanged(formatHolder); - break; - case C.RESULT_NOTHING_READ: - sleep(STREAM_EMPTY); - break; - } - } - } - } - - @VisibleForTesting(otherwise = VisibleForTesting.NONE) - Rect getRect() { - return rect; - } - - @Nullable - @VisibleForTesting - DecoderCounters getDecoderCounters() { - return decoderCounters; - } - - @VisibleForTesting(otherwise = VisibleForTesting.NONE) - Thread getThread() { - return thread; - } - - @Nullable - @VisibleForTesting(otherwise = VisibleForTesting.NONE) - Surface getSurface() { - return surface; - } - - RenderRunnable getRenderRunnable() { - return renderRunnable; - } -} diff --git a/library/core/src/test/java/com/google/android/exoplayer2/video/BitmapFactoryVideoRendererTest.java b/library/core/src/test/java/com/google/android/exoplayer2/video/BitmapFactoryVideoRendererTest.java deleted file mode 100644 index 914f59786b..0000000000 --- a/library/core/src/test/java/com/google/android/exoplayer2/video/BitmapFactoryVideoRendererTest.java +++ /dev/null @@ -1,237 +0,0 @@ -package com.google.android.exoplayer2.video; - -import static com.google.android.exoplayer2.testutil.FakeSampleStream.FakeSampleStreamItem.END_OF_STREAM_ITEM; -import static com.google.android.exoplayer2.testutil.FakeSampleStream.FakeSampleStreamItem.oneByteSample; -import static com.google.android.exoplayer2.testutil.FakeSampleStream.FakeSampleStreamItem.sample; - -import android.content.Context; -import android.graphics.Bitmap; -import android.graphics.Canvas; -import android.graphics.Rect; -import android.os.Handler; -import android.os.Looper; -import android.view.Surface; -import androidx.arch.core.util.Function; -import androidx.test.core.app.ApplicationProvider; -import androidx.test.ext.junit.runners.AndroidJUnit4; -import com.google.android.exoplayer2.C; -import com.google.android.exoplayer2.ExoPlaybackException; -import com.google.android.exoplayer2.Format; -import com.google.android.exoplayer2.PlaybackException; -import com.google.android.exoplayer2.Renderer; -import com.google.android.exoplayer2.RendererConfiguration; -import com.google.android.exoplayer2.drm.DrmSessionEventListener; -import com.google.android.exoplayer2.drm.DrmSessionManager; -import com.google.android.exoplayer2.testutil.FakeSampleStream; -import com.google.android.exoplayer2.testutil.TestUtil; -import com.google.android.exoplayer2.upstream.DefaultAllocator; -import com.google.android.exoplayer2.util.MimeTypes; -import com.google.common.collect.ImmutableList; -import java.io.IOException; -import org.junit.After; -import org.junit.Assert; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.robolectric.annotation.Config; -import org.robolectric.shadow.api.Shadow; -import org.robolectric.shadows.ShadowBitmapFactory; -import org.robolectric.shadows.ShadowLooper; - -@RunWith(AndroidJUnit4.class) -@Config(shadows = {ShadowSurfaceExtended.class}) -public class BitmapFactoryVideoRendererTest { - private final static Format FORMAT_MJPEG = new Format.Builder(). - setSampleMimeType(MimeTypes.VIDEO_MJPEG). - setWidth(320).setHeight(240). - setFrameRate(15f).build(); - - FakeEventListener fakeEventListener = new FakeEventListener(); - BitmapFactoryVideoRenderer bitmapFactoryVideoRenderer; - - @Before - public void before() { - fakeEventListener = new FakeEventListener(); - final Handler handler = new Handler(Looper.getMainLooper()); - bitmapFactoryVideoRenderer = new BitmapFactoryVideoRenderer(handler, fakeEventListener); - } - - @After - public void after() { - //Kill the Thread - bitmapFactoryVideoRenderer.onDisabled(); - } - - @Test - public void getName() { - Assert.assertEquals(BitmapFactoryVideoRenderer.TAG, bitmapFactoryVideoRenderer.getName()); - } - - @Test - public void onEnabled_givenMayRenderStartOfStream() throws PlaybackException { - bitmapFactoryVideoRenderer.onEnabled(false, true); - ShadowLooper.idleMainLooper(); - Assert.assertNotNull(bitmapFactoryVideoRenderer.getDecoderCounters()); - Assert.assertEquals(Thread.State.RUNNABLE, bitmapFactoryVideoRenderer.getThread().getState()); - Assert.assertTrue(fakeEventListener.isVideoEnabled()); - } - - @Test - public void onStarted_givenThreadNotStarted() throws PlaybackException { - bitmapFactoryVideoRenderer.onStarted(); - ShadowLooper.idleMainLooper(); - Assert.assertEquals(Thread.State.RUNNABLE, bitmapFactoryVideoRenderer.getThread().getState()); - } - - @Test - public void onDisabled_givenOnEnabled() throws PlaybackException, InterruptedException { - onEnabled_givenMayRenderStartOfStream(); - bitmapFactoryVideoRenderer.onDisabled(); - ShadowLooper.idleMainLooper(); - Assert.assertFalse(fakeEventListener.isVideoEnabled()); - //Ensure Thread is shutdown - bitmapFactoryVideoRenderer.getThread().join(500L); - Assert.assertTrue(bitmapFactoryVideoRenderer.isEnded()); - } - - private FakeSampleStream getSampleStream() throws IOException { - final Context context = ApplicationProvider.getApplicationContext(); - final byte[] bytes = TestUtil.getByteArray(context, "media/jpeg/image-320-240.jpg"); - FakeSampleStream fakeSampleStream = - new FakeSampleStream( - new DefaultAllocator(/* trimOnReset= */ true, /* individualAllocationSize= */ 1024), - /* mediaSourceEventDispatcher= */ null, - DrmSessionManager.DRM_UNSUPPORTED, - new DrmSessionEventListener.EventDispatcher(), - /* initialFormat= */ FORMAT_MJPEG, - ImmutableList.of( - sample(0L, C.BUFFER_FLAG_KEY_FRAME, bytes), - END_OF_STREAM_ITEM)); - return fakeSampleStream; - } - - private Surface setSurface() throws ExoPlaybackException { - final Surface surface = ShadowSurfaceExtended.newInstance(); - final ShadowSurfaceExtended shadowSurfaceExtended = Shadow.extract(surface); - shadowSurfaceExtended.setSize(1080, 1920); - bitmapFactoryVideoRenderer.handleMessage(Renderer.MSG_SET_VIDEO_OUTPUT, surface); - return surface; - } - - @Test - public void handleMessage_givenSurface() throws ExoPlaybackException { - final Surface surface = setSurface(); - Assert.assertSame(surface, bitmapFactoryVideoRenderer.getSurface()); - bitmapFactoryVideoRenderer.handleMessage(Renderer.MSG_SET_VIDEO_OUTPUT, null); - Assert.assertNull(bitmapFactoryVideoRenderer.getSurface()); - } - - @Test - public void isReady_givenSurface() throws ExoPlaybackException { - Assert.assertFalse(bitmapFactoryVideoRenderer.isReady()); - setSurface(); - Assert.assertTrue(bitmapFactoryVideoRenderer.isReady()); - } - - @Test - public void render_givenJpegAndSurface() throws IOException, ExoPlaybackException { - final Surface surface = setSurface(); - final ShadowSurfaceExtended shadowSurfaceExtended = Shadow.extract(surface); - - FakeSampleStream fakeSampleStream = getSampleStream(); - fakeSampleStream.writeData(0L); - bitmapFactoryVideoRenderer.enable(RendererConfiguration.DEFAULT, new Format[]{FORMAT_MJPEG}, - fakeSampleStream, 0L, false, true, 0L, 0L); - bitmapFactoryVideoRenderer.render(0L, 0L); - // This test actually decodes the JPEG (very cool!), - // May need to bump up timers for slow machines - Assert.assertTrue(shadowSurfaceExtended.waitForPost(500L)); - } - - @Test - public void supportsFormat_givenMjpegFormat() throws ExoPlaybackException{ - Assert.assertEquals(C.FORMAT_HANDLED, - bitmapFactoryVideoRenderer.supportsFormat(FORMAT_MJPEG) & C.FORMAT_HANDLED); - } - - @Test - public void supportsFormat_givenMp4vFormat() throws ExoPlaybackException{ - final Format format = new Format.Builder().setSampleMimeType(MimeTypes.VIDEO_MP4V).build(); - Assert.assertEquals(0, - bitmapFactoryVideoRenderer.supportsFormat(format) & C.FORMAT_HANDLED); - } - - @Test - public void renderBitmap_given4by3BitmapAnd16by9Canvas() { - final Bitmap bitmap = Bitmap.createBitmap(FORMAT_MJPEG.width, FORMAT_MJPEG.height, Bitmap.Config.ARGB_8888); - final Bitmap canvasBitmap = Bitmap.createBitmap(1080, 1920, Bitmap.Config.ARGB_8888); - final Canvas canvas = new Canvas(canvasBitmap); - bitmapFactoryVideoRenderer.renderBitmap(bitmap, canvas); - ShadowLooper.idleMainLooper(); - - final Rect rect = bitmapFactoryVideoRenderer.getRect(); - Assert.assertEquals(canvas.getWidth(), rect.width()); - Assert.assertEquals(canvas.getHeight(), rect.height()); - final VideoSize videoSize = fakeEventListener.videoSize; - Assert.assertEquals(bitmap.getWidth(), videoSize.width); - - bitmapFactoryVideoRenderer.renderBitmap(bitmap, canvas); - ShadowLooper.idleMainLooper(); - Assert.assertSame(videoSize, fakeEventListener.videoSize); - } - - @Test - public void RenderRunnable_run_givenLateFrame() throws IOException, ExoPlaybackException { - final Function sleep = why -> {throw new RuntimeException(why);}; - - FakeSampleStream fakeSampleStream = getSampleStream(); - fakeSampleStream.writeData(0L); - //Don't enable so the Thread is not running - bitmapFactoryVideoRenderer.replaceStream(new Format[]{FORMAT_MJPEG}, fakeSampleStream, 0L, 0L); - BitmapFactoryVideoRenderer.RenderRunnable renderRunnable = - bitmapFactoryVideoRenderer.getRenderRunnable(); - renderRunnable.sleepFunction = sleep; - bitmapFactoryVideoRenderer.render(1_000_000L, 0L); - try { - renderRunnable.run(); - } catch (RuntimeException e) { - Assert.assertEquals(BitmapFactoryVideoRenderer.STREAM_EMPTY, e.getMessage()); - } - ShadowLooper.idleMainLooper(); - Assert.assertEquals(1, fakeEventListener.getDroppedFrames()); - } - - @Test - public void RenderRunnable_run_givenBadJpeg() throws IOException, ExoPlaybackException { - final Function sleep = why -> {throw new RuntimeException(why);}; - FakeSampleStream fakeSampleStream = - new FakeSampleStream( - new DefaultAllocator(/* trimOnReset= */ true, /* individualAllocationSize= */ 1024), - /* mediaSourceEventDispatcher= */ null, - DrmSessionManager.DRM_UNSUPPORTED, - new DrmSessionEventListener.EventDispatcher(), - /* initialFormat= */ FORMAT_MJPEG, - ImmutableList.of( - oneByteSample(0L, C.BUFFER_FLAG_KEY_FRAME), - END_OF_STREAM_ITEM)); - fakeSampleStream.writeData(0L); - - //Don't enable so the Thread is not running - bitmapFactoryVideoRenderer.replaceStream(new Format[]{FORMAT_MJPEG}, fakeSampleStream, 0L, 0L); - BitmapFactoryVideoRenderer.RenderRunnable renderRunnable = - bitmapFactoryVideoRenderer.getRenderRunnable(); - renderRunnable.sleepFunction = sleep; - bitmapFactoryVideoRenderer.render(0L, 0L); - // There is a bug in Robolectric where it doesn't handle null images, - // so we won't get our Exception - ShadowBitmapFactory.setAllowInvalidImageData(false); - try { - renderRunnable.run(); - } catch (RuntimeException e) { - Assert.assertEquals(BitmapFactoryVideoRenderer.STREAM_EMPTY, e.getMessage()); - } - ShadowLooper.idleMainLooper(); - Assert.assertTrue(fakeEventListener.getVideoCodecError() instanceof NullPointerException); - - } -} diff --git a/library/core/src/test/java/com/google/android/exoplayer2/video/FakeEventListener.java b/library/core/src/test/java/com/google/android/exoplayer2/video/FakeEventListener.java deleted file mode 100644 index ebc70e3f94..0000000000 --- a/library/core/src/test/java/com/google/android/exoplayer2/video/FakeEventListener.java +++ /dev/null @@ -1,64 +0,0 @@ -package com.google.android.exoplayer2.video; - -import androidx.annotation.Nullable; -import com.google.android.exoplayer2.decoder.DecoderCounters; - -public class FakeEventListener implements VideoRendererEventListener { - @Nullable - VideoSize videoSize; - @Nullable - DecoderCounters decoderCounters; - - private long firstFrameRenderMs = Long.MIN_VALUE; - - private int droppedFrames; - - private Exception videoCodecError; - - @Override - public void onVideoSizeChanged(VideoSize videoSize) { - this.videoSize = videoSize; - } - - public boolean isVideoEnabled() { - return decoderCounters != null; - } - - @Override - public void onVideoEnabled(DecoderCounters counters) { - decoderCounters = counters; - } - - @Override - public void onVideoDisabled(DecoderCounters counters) { - decoderCounters = null; - } - - public long getFirstFrameRenderMs() { - return firstFrameRenderMs; - } - - @Override - public void onRenderedFirstFrame(Object output, long renderTimeMs) { - firstFrameRenderMs = renderTimeMs; - } - - public int getDroppedFrames() { - return droppedFrames; - } - - @Override - public void onDroppedFrames(int count, long elapsedMs) { - droppedFrames+=count; - } - - public Exception getVideoCodecError() { - return videoCodecError; - } - - @Override - public void onVideoCodecError(Exception videoCodecError) { - this.videoCodecError = videoCodecError; - } - -} diff --git a/library/core/src/test/java/com/google/android/exoplayer2/video/ShadowSurfaceExtended.java b/library/core/src/test/java/com/google/android/exoplayer2/video/ShadowSurfaceExtended.java deleted file mode 100644 index c107832310..0000000000 --- a/library/core/src/test/java/com/google/android/exoplayer2/video/ShadowSurfaceExtended.java +++ /dev/null @@ -1,43 +0,0 @@ -package com.google.android.exoplayer2.video; - -import android.graphics.Bitmap; -import android.graphics.Canvas; -import android.graphics.Rect; -import android.view.Surface; -import java.util.concurrent.Semaphore; -import java.util.concurrent.TimeUnit; -import org.robolectric.annotation.Implements; -import org.robolectric.shadow.api.Shadow; -import org.robolectric.shadows.ShadowSurface; - -@Implements(Surface.class) -public class ShadowSurfaceExtended extends ShadowSurface { - private final Semaphore postSemaphore = new Semaphore(0); - private int width; - private int height; - - public static Surface newInstance() { - return Shadow.newInstanceOf(Surface.class); - } - - public void setSize(final int width, final int height) { - this.width = width; - this.height = height; - } - - public Canvas lockCanvas(Rect canvas) { - return new Canvas(Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)); - } - - public void unlockCanvasAndPost(Canvas canvas) { - postSemaphore.release(); - } - - public boolean waitForPost(long millis) { - try { - return postSemaphore.tryAcquire(millis, TimeUnit.MILLISECONDS); - } catch (InterruptedException e) { - return false; - } - } -} diff --git a/testdata/src/test/assets/media/jpeg/image-320-240.jpg b/testdata/src/test/assets/media/jpeg/image-320-240.jpg deleted file mode 100644 index d1796c66fd..0000000000 Binary files a/testdata/src/test/assets/media/jpeg/image-320-240.jpg and /dev/null differ