From dc80be0ca1c8c87ea4522f9efbb55dd346c836bd Mon Sep 17 00:00:00 2001 From: Andrew Orobator Date: Wed, 3 May 2017 10:49:36 -0400 Subject: [PATCH 01/28] Improved Documentation Added missing coma and fixed typo for EventListener#onTimelineChanged --- .../main/java/com/google/android/exoplayer2/ExoPlayer.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayer.java b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayer.java index ab521e3733..82ec610be7 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayer.java @@ -113,8 +113,8 @@ public interface ExoPlayer { * Called when the timeline and/or manifest has been refreshed. *

* Note that if the timeline has changed then a position discontinuity may also have occurred. - * For example the current period index may have changed as a result of periods being added or - * removed from the timeline. The will not be reported via a separate call to + * For example, the current period index may have changed as a result of periods being added or + * removed from the timeline. This will not be reported via a separate call to * {@link #onPositionDiscontinuity()}. * * @param timeline The latest timeline. Never null, but may be empty. From 2e2b0085c1de98acfe054b3797de298a767566b1 Mon Sep 17 00:00:00 2001 From: aquilescanta Date: Thu, 27 Apr 2017 07:49:14 -0700 Subject: [PATCH 02/28] Prevent text tracks with no language being selected by default ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=154421706 --- .../exoplayer2/trackselection/DefaultTrackSelector.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelector.java b/library/core/src/main/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelector.java index 9db77fd7ad..941df66e4d 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelector.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelector.java @@ -867,7 +867,8 @@ public class DefaultTrackSelector extends MappingTrackSelector { } protected static boolean formatHasLanguage(Format format, String language) { - return TextUtils.equals(language, Util.normalizeLanguageCode(format.language)); + return language != null + && TextUtils.equals(language, Util.normalizeLanguageCode(format.language)); } // Viewport size util methods. From e90e2caec4fe94d7fbb706c7da827c9f0ad3a1c7 Mon Sep 17 00:00:00 2001 From: andrewlewis Date: Tue, 2 May 2017 06:38:57 -0700 Subject: [PATCH 03/28] Improve DefaultTimeBar color customization Add attributes for the scrubber handle color and unplayed color. If attributes are missing, derive defaults from the played color. Issue: #2740 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=154825736 --- .../android/exoplayer2/ui/DefaultTimeBar.java | 50 +++++++++++++------ library/ui/src/main/res/values/attrs.xml | 2 + 2 files changed, 36 insertions(+), 16 deletions(-) diff --git a/library/ui/src/main/java/com/google/android/exoplayer2/ui/DefaultTimeBar.java b/library/ui/src/main/java/com/google/android/exoplayer2/ui/DefaultTimeBar.java index d40da451a2..12f31f5da1 100644 --- a/library/ui/src/main/java/com/google/android/exoplayer2/ui/DefaultTimeBar.java +++ b/library/ui/src/main/java/com/google/android/exoplayer2/ui/DefaultTimeBar.java @@ -61,22 +61,21 @@ public class DefaultTimeBar extends View implements TimeBar { private static final int DEFAULT_INCREMENT_COUNT = 20; private static final int DEFAULT_BAR_HEIGHT = 4; private static final int DEFAULT_TOUCH_TARGET_HEIGHT = 26; - private static final int DEFAULT_PLAYED_COLOR = 0x33FFFFFF; - private static final int DEFAULT_BUFFERED_COLOR = 0xCCFFFFFF; + private static final int DEFAULT_PLAYED_COLOR = 0xFFFFFFFF; private static final int DEFAULT_AD_MARKER_COLOR = 0xB2FFFF00; private static final int DEFAULT_AD_MARKER_WIDTH = 4; private static final int DEFAULT_SCRUBBER_ENABLED_SIZE = 12; private static final int DEFAULT_SCRUBBER_DISABLED_SIZE = 0; private static final int DEFAULT_SCRUBBER_DRAGGED_SIZE = 16; - private static final int OPAQUE_COLOR = 0xFF000000; private final Rect seekBounds; private final Rect progressBar; private final Rect bufferedBar; private final Rect scrubberBar; - private final Paint progressPaint; - private final Paint bufferedPaint; + private final Paint playedPaint; private final Paint scrubberPaint; + private final Paint bufferedPaint; + private final Paint unplayedPaint; private final Paint adMarkerPaint; private final int barHeight; private final int touchTargetHeight; @@ -115,9 +114,10 @@ public class DefaultTimeBar extends View implements TimeBar { progressBar = new Rect(); bufferedBar = new Rect(); scrubberBar = new Rect(); - progressPaint = new Paint(); - bufferedPaint = new Paint(); + playedPaint = new Paint(); scrubberPaint = new Paint(); + bufferedPaint = new Paint(); + unplayedPaint = new Paint(); adMarkerPaint = new Paint(); // Calculate the dimensions and paints for drawn elements. @@ -147,13 +147,18 @@ public class DefaultTimeBar extends View implements TimeBar { scrubberDraggedSize = a.getDimensionPixelSize( R.styleable.DefaultTimeBar_scrubber_dragged_size, defaultScrubberDraggedSize); int playedColor = a.getInt(R.styleable.DefaultTimeBar_played_color, DEFAULT_PLAYED_COLOR); + int scrubberColor = a.getInt(R.styleable.DefaultTimeBar_scrubber_color, + getDefaultScrubberColor(playedColor)); int bufferedColor = a.getInt(R.styleable.DefaultTimeBar_buffered_color, - DEFAULT_BUFFERED_COLOR); + getDefaultBufferedColor(playedColor)); + int unplayedColor = a.getInt(R.styleable.DefaultTimeBar_unplayed_color, + getDefaultUnplayedColor(playedColor)); int adMarkerColor = a.getInt(R.styleable.DefaultTimeBar_ad_marker_color, DEFAULT_AD_MARKER_COLOR); - progressPaint.setColor(playedColor); - scrubberPaint.setColor(OPAQUE_COLOR | playedColor); + playedPaint.setColor(playedColor); + scrubberPaint.setColor(scrubberColor); bufferedPaint.setColor(bufferedColor); + unplayedPaint.setColor(unplayedColor); adMarkerPaint.setColor(adMarkerColor); } finally { a.recycle(); @@ -165,9 +170,10 @@ public class DefaultTimeBar extends View implements TimeBar { scrubberEnabledSize = defaultScrubberEnabledSize; scrubberDisabledSize = defaultScrubberDisabledSize; scrubberDraggedSize = defaultScrubberDraggedSize; - scrubberPaint.setColor(OPAQUE_COLOR | DEFAULT_PLAYED_COLOR); - progressPaint.setColor(DEFAULT_PLAYED_COLOR); - bufferedPaint.setColor(DEFAULT_BUFFERED_COLOR); + playedPaint.setColor(DEFAULT_PLAYED_COLOR); + scrubberPaint.setColor(getDefaultScrubberColor(DEFAULT_PLAYED_COLOR)); + bufferedPaint.setColor(getDefaultBufferedColor(DEFAULT_PLAYED_COLOR)); + unplayedPaint.setColor(getDefaultUnplayedColor(DEFAULT_PLAYED_COLOR)); adMarkerPaint.setColor(DEFAULT_AD_MARKER_COLOR); } formatBuilder = new StringBuilder(); @@ -502,21 +508,21 @@ public class DefaultTimeBar extends View implements TimeBar { int barTop = progressBar.centerY() - progressBarHeight / 2; int barBottom = barTop + progressBarHeight; if (duration <= 0) { - canvas.drawRect(progressBar.left, barTop, progressBar.right, barBottom, progressPaint); + canvas.drawRect(progressBar.left, barTop, progressBar.right, barBottom, unplayedPaint); return; } int bufferedLeft = bufferedBar.left; int bufferedRight = bufferedBar.right; int progressLeft = Math.max(Math.max(progressBar.left, bufferedRight), scrubberBar.right); if (progressLeft < progressBar.right) { - canvas.drawRect(progressLeft, barTop, progressBar.right, barBottom, progressPaint); + canvas.drawRect(progressLeft, barTop, progressBar.right, barBottom, unplayedPaint); } bufferedLeft = Math.max(bufferedLeft, scrubberBar.right); if (bufferedRight > bufferedLeft) { canvas.drawRect(bufferedLeft, barTop, bufferedRight, barBottom, bufferedPaint); } if (scrubberBar.width() > 0) { - canvas.drawRect(scrubberBar.left, barTop, scrubberBar.right, barBottom, scrubberPaint); + canvas.drawRect(scrubberBar.left, barTop, scrubberBar.right, barBottom, playedPaint); } int adMarkerOffset = adMarkerWidth / 2; for (int i = 0; i < adBreakCount; i++) { @@ -577,4 +583,16 @@ public class DefaultTimeBar extends View implements TimeBar { return (int) (dps * displayMetrics.density + 0.5f); } + private static int getDefaultScrubberColor(int playedColor) { + return 0xFF000000 | playedColor; + } + + private static int getDefaultUnplayedColor(int playedColor) { + return 0x33000000 | (playedColor & 0x00FFFFFF); + } + + private static int getDefaultBufferedColor(int playedColor) { + return 0xCC000000 | (playedColor & 0x00FFFFFF); + } + } diff --git a/library/ui/src/main/res/values/attrs.xml b/library/ui/src/main/res/values/attrs.xml index 521e535ce3..d8340c21cd 100644 --- a/library/ui/src/main/res/values/attrs.xml +++ b/library/ui/src/main/res/values/attrs.xml @@ -68,7 +68,9 @@ + + From 60bf31ff24f492e8c0bbd798b76d3081e270a4ba Mon Sep 17 00:00:00 2001 From: falhassen Date: Tue, 2 May 2017 10:09:55 -0700 Subject: [PATCH 04/28] Use Looper.getMainLooper() in Handler constructors in ExoPlayer when needed. Looper.myLooper(), the default looper, may be null in background threads. This adds a fallback to use the main app looper. This will allow ExoPlayer instances to be built in background threads in Photos. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=154845446 --- .../google/android/exoplayer2/ExoPlayer.java | 9 ++++-- .../android/exoplayer2/ExoPlayerFactory.java | 28 ++++++------------- .../android/exoplayer2/ExoPlayerImpl.java | 3 +- .../android/exoplayer2/SimpleExoPlayer.java | 7 +++-- 4 files changed, 23 insertions(+), 24 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayer.java b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayer.java index 82ec610be7..18bf9eeb8c 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayer.java @@ -15,6 +15,8 @@ */ package com.google.android.exoplayer2; +import android.os.Looper; +import android.support.annotation.IntDef; import android.support.annotation.Nullable; import com.google.android.exoplayer2.audio.MediaCodecAudioRenderer; import com.google.android.exoplayer2.metadata.MetadataRenderer; @@ -88,7 +90,9 @@ import com.google.android.exoplayer2.video.MediaCodecVideoRenderer; * 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. - *

  • Registered listeners are called on the thread that created the ExoPlayer instance.
  • + *
  • 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.
  • *
  • 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.
  • @@ -253,7 +257,8 @@ public interface ExoPlayer { /** * Register a listener to receive events from the player. The listener's methods will be called on - * the thread that was used to construct the player. + * the thread that was used to construct the player. However, if the thread used to construct the + * player does not have a {@link Looper}, then the listener will be called on the main thread. * * @param listener The listener to register. */ diff --git a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerFactory.java b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerFactory.java index 7aecd20d4e..97a310c3da 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerFactory.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerFactory.java @@ -16,7 +16,6 @@ package com.google.android.exoplayer2; import android.content.Context; -import android.os.Looper; import com.google.android.exoplayer2.drm.DrmSessionManager; import com.google.android.exoplayer2.drm.FrameworkMediaCrypto; import com.google.android.exoplayer2.trackselection.TrackSelector; @@ -29,8 +28,7 @@ public final class ExoPlayerFactory { private ExoPlayerFactory() {} /** - * Creates a {@link SimpleExoPlayer} instance. Must be called from a thread that has an associated - * {@link Looper}. + * Creates a {@link SimpleExoPlayer} instance. * * @param context A {@link Context}. * @param trackSelector The {@link TrackSelector} that will be used by the instance. @@ -45,8 +43,7 @@ public final class ExoPlayerFactory { } /** - * Creates a {@link SimpleExoPlayer} instance. Must be called from a thread that has an associated - * {@link Looper}. Available extension renderers are not used. + * Creates a {@link SimpleExoPlayer} instance. Available extension renderers are not used. * * @param context A {@link Context}. * @param trackSelector The {@link TrackSelector} that will be used by the instance. @@ -63,8 +60,7 @@ public final class ExoPlayerFactory { } /** - * Creates a {@link SimpleExoPlayer} instance. Must be called from a thread that has an associated - * {@link Looper}. + * Creates a {@link SimpleExoPlayer} instance. * * @param context A {@link Context}. * @param trackSelector The {@link TrackSelector} that will be used by the instance. @@ -86,8 +82,7 @@ public final class ExoPlayerFactory { } /** - * Creates a {@link SimpleExoPlayer} instance. Must be called from a thread that has an associated - * {@link Looper}. + * Creates a {@link SimpleExoPlayer} instance. * * @param context A {@link Context}. * @param trackSelector The {@link TrackSelector} that will be used by the instance. @@ -112,8 +107,7 @@ public final class ExoPlayerFactory { } /** - * Creates a {@link SimpleExoPlayer} instance. Must be called from a thread that has an associated - * {@link Looper}. + * Creates a {@link SimpleExoPlayer} instance. * * @param context A {@link Context}. * @param trackSelector The {@link TrackSelector} that will be used by the instance. @@ -123,8 +117,7 @@ public final class ExoPlayerFactory { } /** - * Creates a {@link SimpleExoPlayer} instance. Must be called from a thread that has an associated - * {@link Looper}. + * Creates a {@link SimpleExoPlayer} instance. * * @param renderersFactory A factory for creating {@link Renderer}s to be used by the instance. * @param trackSelector The {@link TrackSelector} that will be used by the instance. @@ -135,8 +128,7 @@ public final class ExoPlayerFactory { } /** - * Creates a {@link SimpleExoPlayer} instance. Must be called from a thread that has an associated - * {@link Looper}. + * Creates a {@link SimpleExoPlayer} instance. * * @param renderersFactory A factory for creating {@link Renderer}s to be used by the instance. * @param trackSelector The {@link TrackSelector} that will be used by the instance. @@ -148,8 +140,7 @@ public final class ExoPlayerFactory { } /** - * Creates an {@link ExoPlayer} instance. Must be called from a thread that has an associated - * {@link Looper}. + * Creates an {@link ExoPlayer} instance. * * @param renderers The {@link Renderer}s that will be used by the instance. * @param trackSelector The {@link TrackSelector} that will be used by the instance. @@ -159,8 +150,7 @@ public final class ExoPlayerFactory { } /** - * Creates an {@link ExoPlayer} instance. Must be called from a thread that has an associated - * {@link Looper}. + * Creates an {@link ExoPlayer} instance. * * @param renderers The {@link Renderer}s that will be used by the instance. * @param trackSelector The {@link TrackSelector} that will be used by the instance. diff --git a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java index 4131b97954..cb0958a3b1 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java @@ -92,7 +92,8 @@ import java.util.concurrent.CopyOnWriteArraySet; trackGroups = TrackGroupArray.EMPTY; trackSelections = emptyTrackSelections; playbackParameters = PlaybackParameters.DEFAULT; - eventHandler = new Handler() { + Looper eventLooper = Looper.myLooper() != null ? Looper.myLooper() : Looper.getMainLooper(); + eventHandler = new Handler(eventLooper) { @Override public void handleMessage(Message msg) { ExoPlayerImpl.this.handleEvent(msg); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java b/library/core/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java index 28ba8cf9d7..6094513913 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java @@ -20,6 +20,7 @@ import android.graphics.SurfaceTexture; import android.media.MediaCodec; import android.media.PlaybackParams; import android.os.Handler; +import android.os.Looper; import android.support.annotation.Nullable; import android.util.Log; import android.view.Surface; @@ -111,8 +112,10 @@ public class SimpleExoPlayer implements ExoPlayer { protected SimpleExoPlayer(RenderersFactory renderersFactory, TrackSelector trackSelector, LoadControl loadControl) { componentListener = new ComponentListener(); - renderers = renderersFactory.createRenderers(new Handler(), componentListener, - componentListener, componentListener, componentListener); + Looper eventLooper = Looper.myLooper() != null ? Looper.myLooper() : Looper.getMainLooper(); + Handler eventHandler = new Handler(eventLooper); + renderers = renderersFactory.createRenderers(eventHandler, componentListener, componentListener, + componentListener, componentListener); // Obtain counts of video and audio renderers. int videoRendererCount = 0; From 8bffe5d11ef5bbdbaa892226b4cba4273351967a Mon Sep 17 00:00:00 2001 From: olly Date: Wed, 3 May 2017 01:32:22 -0700 Subject: [PATCH 05/28] Add DummySurface for use with MediaCodec A DummySurface is useful with MediaCodec on API levels 23+. Rather than having to release a MediaCodec instance when the app no longer has a real surface to output to, it's possible to retain the MediaCodec, using MediaCodec.setOutputSurface to target a DummySurface instance instead. When the app has a real surface to output to again, it can call swap this surface back in instantaneously. Without DummySurface a new MediaCodec has to be instantiated at this point, and decoding can only start from a key-frame in the media. A future change may hook this up internally in MediaCodecRenderer for supported use cases, although this looks a little awkward. If this approach isn't viable, we can require applications wanting this to set a DummySurface themselves. This isn't easy to do with the way SimpleExoPlayerView.setPlayer works at the moment, however, so some changes will be needed either way. Issue: #677 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=154931778 --- .../exoplayer2/video/DummySurface.java | 306 ++++++++++++++++++ 1 file changed, 306 insertions(+) create mode 100644 library/core/src/main/java/com/google/android/exoplayer2/video/DummySurface.java diff --git a/library/core/src/main/java/com/google/android/exoplayer2/video/DummySurface.java b/library/core/src/main/java/com/google/android/exoplayer2/video/DummySurface.java new file mode 100644 index 0000000000..5298c82f61 --- /dev/null +++ b/library/core/src/main/java/com/google/android/exoplayer2/video/DummySurface.java @@ -0,0 +1,306 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.exoplayer2.video; + +import static android.opengl.EGL14.EGL_ALPHA_SIZE; +import static android.opengl.EGL14.EGL_BLUE_SIZE; +import static android.opengl.EGL14.EGL_CONFIG_CAVEAT; +import static android.opengl.EGL14.EGL_CONTEXT_CLIENT_VERSION; +import static android.opengl.EGL14.EGL_DEFAULT_DISPLAY; +import static android.opengl.EGL14.EGL_DEPTH_SIZE; +import static android.opengl.EGL14.EGL_GREEN_SIZE; +import static android.opengl.EGL14.EGL_HEIGHT; +import static android.opengl.EGL14.EGL_NONE; +import static android.opengl.EGL14.EGL_OPENGL_ES2_BIT; +import static android.opengl.EGL14.EGL_RED_SIZE; +import static android.opengl.EGL14.EGL_RENDERABLE_TYPE; +import static android.opengl.EGL14.EGL_SURFACE_TYPE; +import static android.opengl.EGL14.EGL_TRUE; +import static android.opengl.EGL14.EGL_WIDTH; +import static android.opengl.EGL14.EGL_WINDOW_BIT; +import static android.opengl.EGL14.eglChooseConfig; +import static android.opengl.EGL14.eglCreateContext; +import static android.opengl.EGL14.eglCreatePbufferSurface; +import static android.opengl.EGL14.eglGetDisplay; +import static android.opengl.EGL14.eglInitialize; +import static android.opengl.EGL14.eglMakeCurrent; +import static android.opengl.GLES20.glDeleteTextures; +import static android.opengl.GLES20.glGenTextures; + +import android.annotation.TargetApi; +import android.graphics.SurfaceTexture; +import android.graphics.SurfaceTexture.OnFrameAvailableListener; +import android.opengl.EGL14; +import android.opengl.EGLConfig; +import android.opengl.EGLContext; +import android.opengl.EGLDisplay; +import android.opengl.EGLSurface; +import android.os.Handler; +import android.os.Handler.Callback; +import android.os.HandlerThread; +import android.os.Message; +import android.util.Log; +import android.view.Surface; +import com.google.android.exoplayer2.util.Assertions; +import com.google.android.exoplayer2.util.Util; +import javax.microedition.khronos.egl.EGL10; + +/** + * A dummy {@link Surface}. + */ +@TargetApi(17) +public final class DummySurface extends Surface { + + private static final String TAG = "DummySurface"; + + private static final int EGL_PROTECTED_CONTENT_EXT = 0x32C0; + + /** + * Whether the device supports secure dummy surfaces. + */ + public static final boolean SECURE_SUPPORTED; + static { + if (Util.SDK_INT >= 17) { + EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY); + String extensions = EGL14.eglQueryString(display, EGL10.EGL_EXTENSIONS); + SECURE_SUPPORTED = extensions.contains("EGL_EXT_protected_content"); + } else { + SECURE_SUPPORTED = false; + } + } + + /** + * Whether the surface is secure. + */ + public final boolean secure; + + private final DummySurfaceThread thread; + private boolean threadReleased; + + /** + * Returns a newly created dummy surface. The surface must be released by calling {@link #release} + * when it's no longer required. + *

    + * Must only be called if {@link Util#SDK_INT} is 17 or higher. + * + * @param secure Whether a secure surface is required. Must only be requested if + * {@link #SECURE_SUPPORTED} is {@code true}. + */ + public static DummySurface newInstanceV17(boolean secure) { + assertApiLevel17OrHigher(); + Assertions.checkState(!secure || SECURE_SUPPORTED); + DummySurfaceThread thread = new DummySurfaceThread(); + return thread.init(secure); + } + + private DummySurface(DummySurfaceThread thread, SurfaceTexture surfaceTexture, boolean secure) { + super(surfaceTexture); + this.thread = thread; + this.secure = secure; + } + + @Override + public void release() { + super.release(); + // The Surface may be released multiple times (explicitly and by Surface.finalize()). The + // implementation of super.release() has its own deduplication logic. Below we need to + // deduplicate ourselves. Synchronization is required as we don't control the thread on which + // Surface.finalize() is called. + synchronized (thread) { + if (!threadReleased) { + thread.release(); + threadReleased = true; + } + } + } + + private static void assertApiLevel17OrHigher() { + if (Util.SDK_INT < 17) { + throw new UnsupportedOperationException("Unsupported prior to API level 17"); + } + } + + private static class DummySurfaceThread extends HandlerThread implements OnFrameAvailableListener, + Callback { + + private static final int MSG_INIT = 1; + private static final int MSG_UPDATE_TEXTURE = 2; + private static final int MSG_RELEASE = 3; + + private final int[] textureIdHolder; + private Handler handler; + private SurfaceTexture surfaceTexture; + + private Error initError; + private RuntimeException initException; + private DummySurface surface; + + public DummySurfaceThread() { + super("dummySurface"); + textureIdHolder = new int[1]; + } + + public DummySurface init(boolean secure) { + start(); + handler = new Handler(getLooper(), this); + boolean wasInterrupted = false; + synchronized (this) { + handler.obtainMessage(MSG_INIT, secure ? 1 : 0, 0).sendToTarget(); + while (surface == null && initException == null && initError == null) { + try { + wait(); + } catch (InterruptedException e) { + wasInterrupted = true; + } + } + } + if (wasInterrupted) { + // Restore the interrupted status. + Thread.currentThread().interrupt(); + } + if (initException != null) { + throw initException; + } else if (initError != null) { + throw initError; + } else { + return surface; + } + } + + public void release() { + handler.sendEmptyMessage(MSG_RELEASE); + } + + @Override + public void onFrameAvailable(SurfaceTexture surfaceTexture) { + handler.sendEmptyMessage(MSG_UPDATE_TEXTURE); + } + + @Override + public boolean handleMessage(Message msg) { + switch (msg.what) { + case MSG_INIT: + try { + initInternal(msg.arg1 != 0); + } catch (RuntimeException e) { + Log.e(TAG, "Failed to initialize dummy surface", e); + initException = e; + } catch (Error e) { + Log.e(TAG, "Failed to initialize dummy surface", e); + initError = e; + } finally { + synchronized (this) { + notify(); + } + } + return true; + case MSG_UPDATE_TEXTURE: + surfaceTexture.updateTexImage(); + return true; + case MSG_RELEASE: + try { + releaseInternal(); + } catch (Throwable e) { + Log.e(TAG, "Failed to release dummy surface", e); + } finally { + quit(); + } + return true; + default: + return true; + } + } + + private void initInternal(boolean secure) { + EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY); + Assertions.checkState(display != null, "eglGetDisplay failed"); + + int[] version = new int[2]; + boolean eglInitialized = eglInitialize(display, version, 0, version, 1); + Assertions.checkState(eglInitialized, "eglInitialize failed"); + + int[] eglAttributes = new int[] { + EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, + EGL_RED_SIZE, 8, + EGL_GREEN_SIZE, 8, + EGL_BLUE_SIZE, 8, + EGL_ALPHA_SIZE, 8, + EGL_DEPTH_SIZE, 0, + EGL_CONFIG_CAVEAT, EGL_NONE, + EGL_SURFACE_TYPE, EGL_WINDOW_BIT, + EGL_NONE + }; + EGLConfig[] configs = new EGLConfig[1]; + int[] numConfigs = new int[1]; + boolean eglChooseConfigSuccess = eglChooseConfig(display, eglAttributes, 0, configs, 0, 1, + numConfigs, 0); + Assertions.checkState(eglChooseConfigSuccess && numConfigs[0] > 0 && configs[0] != null, + "eglChooseConfig failed"); + + EGLConfig config = configs[0]; + int[] glAttributes; + if (secure) { + glAttributes = new int[] { + EGL_CONTEXT_CLIENT_VERSION, 2, + EGL_PROTECTED_CONTENT_EXT, + EGL_TRUE, EGL_NONE}; + } else { + glAttributes = new int[] { + EGL_CONTEXT_CLIENT_VERSION, 2, + EGL_NONE}; + } + EGLContext context = eglCreateContext(display, config, android.opengl.EGL14.EGL_NO_CONTEXT, + glAttributes, 0); + Assertions.checkState(context != null, "eglCreateContext failed"); + + int[] pbufferAttributes; + if (secure) { + pbufferAttributes = new int[] { + EGL_WIDTH, 1, + EGL_HEIGHT, 1, + EGL_PROTECTED_CONTENT_EXT, EGL_TRUE, + EGL_NONE}; + } else { + pbufferAttributes = new int[] { + EGL_WIDTH, 1, + EGL_HEIGHT, 1, + EGL_NONE}; + } + EGLSurface pbuffer = eglCreatePbufferSurface(display, config, pbufferAttributes, 0); + Assertions.checkState(pbuffer != null, "eglCreatePbufferSurface failed"); + + boolean eglMadeCurrent = eglMakeCurrent(display, pbuffer, pbuffer, context); + Assertions.checkState(eglMadeCurrent, "eglMakeCurrent failed"); + + glGenTextures(1, textureIdHolder, 0); + surfaceTexture = new SurfaceTexture(textureIdHolder[0]); + surfaceTexture.setOnFrameAvailableListener(this); + surface = new DummySurface(this, surfaceTexture, secure); + } + + private void releaseInternal() { + try { + surfaceTexture.release(); + } finally { + surface = null; + surfaceTexture = null; + glDeleteTextures(1, textureIdHolder, 0); + } + } + + } + +} From ed836d31e28867fc81005b4d2dff4891289e5a74 Mon Sep 17 00:00:00 2001 From: aquilescanta Date: Thu, 4 May 2017 03:24:19 -0700 Subject: [PATCH 06/28] Propagate EXT-X-DATERANGE tags with media playlists ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=155062718 --- .../exoplayer2/source/hls/playlist/HlsMediaPlaylist.java | 9 ++++++--- .../source/hls/playlist/HlsPlaylistParser.java | 7 ++++++- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsMediaPlaylist.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsMediaPlaylist.java index c7708a1d2f..69b95e6d3d 100644 --- a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsMediaPlaylist.java +++ b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsMediaPlaylist.java @@ -91,12 +91,14 @@ public final class HlsMediaPlaylist extends HlsPlaylist { public final boolean hasProgramDateTime; public final Segment initializationSegment; public final List segments; + public final List dateRanges; public final long durationUs; public HlsMediaPlaylist(@PlaylistType int playlistType, String baseUri, long startOffsetUs, long startTimeUs, boolean hasDiscontinuitySequence, int discontinuitySequence, int mediaSequence, int version, long targetDurationUs, boolean hasEndTag, - boolean hasProgramDateTime, Segment initializationSegment, List segments) { + boolean hasProgramDateTime, Segment initializationSegment, List segments, + List dateRanges) { super(baseUri); this.playlistType = playlistType; this.startTimeUs = startTimeUs; @@ -117,6 +119,7 @@ public final class HlsMediaPlaylist extends HlsPlaylist { } this.startOffsetUs = startOffsetUs == C.TIME_UNSET ? C.TIME_UNSET : startOffsetUs >= 0 ? startOffsetUs : durationUs + startOffsetUs; + this.dateRanges = Collections.unmodifiableList(dateRanges); } /** @@ -155,7 +158,7 @@ public final class HlsMediaPlaylist extends HlsPlaylist { public HlsMediaPlaylist copyWith(long startTimeUs, int discontinuitySequence) { return new HlsMediaPlaylist(playlistType, baseUri, startOffsetUs, startTimeUs, true, discontinuitySequence, mediaSequence, version, targetDurationUs, hasEndTag, - hasProgramDateTime, initializationSegment, segments); + hasProgramDateTime, initializationSegment, segments, dateRanges); } /** @@ -170,7 +173,7 @@ public final class HlsMediaPlaylist extends HlsPlaylist { } return new HlsMediaPlaylist(playlistType, baseUri, startOffsetUs, startTimeUs, hasDiscontinuitySequence, discontinuitySequence, mediaSequence, version, targetDurationUs, - true, hasProgramDateTime, initializationSegment, segments); + true, hasProgramDateTime, initializationSegment, segments, dateRanges); } } diff --git a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylistParser.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylistParser.java index d24264cae6..8e01dec6fe 100644 --- a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylistParser.java +++ b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylistParser.java @@ -57,6 +57,7 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser segments = new ArrayList<>(); + List dateRanges = new ArrayList<>(); long segmentDurationUs = 0; boolean hasDiscontinuitySequence = false; @@ -343,6 +345,8 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser Date: Thu, 4 May 2017 03:29:10 -0700 Subject: [PATCH 07/28] Fix javadocs typos Issue:#2773 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=155062917 --- .../google/android/exoplayer2/ext/vp9/LibvpxVideoRenderer.java | 2 +- .../com/google/android/exoplayer2/upstream/cache/CacheUtil.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/extensions/vp9/src/main/java/com/google/android/exoplayer2/ext/vp9/LibvpxVideoRenderer.java b/extensions/vp9/src/main/java/com/google/android/exoplayer2/ext/vp9/LibvpxVideoRenderer.java index c44c703bb1..4b629c8d2a 100644 --- a/extensions/vp9/src/main/java/com/google/android/exoplayer2/ext/vp9/LibvpxVideoRenderer.java +++ b/extensions/vp9/src/main/java/com/google/android/exoplayer2/ext/vp9/LibvpxVideoRenderer.java @@ -286,7 +286,7 @@ public final class LibvpxVideoRenderer extends BaseRenderer { * * @param outputBufferTimeUs The timestamp of the current output buffer. * @param nextOutputBufferTimeUs The timestamp of the next output buffer or - * {@link TIME_UNSET} if the next output buffer is unavailable. + * {@link C#TIME_UNSET} if the next output buffer is unavailable. * @param positionUs The current playback position. * @param joiningDeadlineMs The joining deadline. * @return Returns whether to drop the current output buffer. diff --git a/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheUtil.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheUtil.java index f6251dbbf1..bb1f88e5ea 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheUtil.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheUtil.java @@ -64,7 +64,7 @@ public final class CacheUtil { } /** - * Returns already cached and missing bytes in the {@cache} for the data defined by {@code + * Returns already cached and missing bytes in the {@code cache} for the data defined by {@code * dataSpec}. * * @param dataSpec Defines the data to be checked. From 943141fb076a4aeb7e7c10d4830aef32caa46aca Mon Sep 17 00:00:00 2001 From: olly Date: Thu, 4 May 2017 11:30:25 -0700 Subject: [PATCH 08/28] Some minor cleanup related to track selection and caching ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=155103828 --- .../exoplayer2/demo/PlayerActivity.java | 6 ++-- .../trackselection/DefaultTrackSelector.java | 16 ++++++++-- .../upstream/cache/CacheDataSource.java | 30 ++++++++++++++----- .../cache/CacheDataSourceFactory.java | 18 +++++++---- 4 files changed, 53 insertions(+), 17 deletions(-) diff --git a/demo/src/main/java/com/google/android/exoplayer2/demo/PlayerActivity.java b/demo/src/main/java/com/google/android/exoplayer2/demo/PlayerActivity.java index 2542f23e95..d01a64a051 100644 --- a/demo/src/main/java/com/google/android/exoplayer2/demo/PlayerActivity.java +++ b/demo/src/main/java/com/google/android/exoplayer2/demo/PlayerActivity.java @@ -261,10 +261,10 @@ public class PlayerActivity extends Activity implements OnClickListener, ExoPlay DefaultRenderersFactory renderersFactory = new DefaultRenderersFactory(this, drmSessionManager, extensionRendererMode); - TrackSelection.Factory videoTrackSelectionFactory = + TrackSelection.Factory adaptiveTrackSelectionFactory = new AdaptiveTrackSelection.Factory(BANDWIDTH_METER); - trackSelector = new DefaultTrackSelector(videoTrackSelectionFactory); - trackSelectionHelper = new TrackSelectionHelper(trackSelector, videoTrackSelectionFactory); + trackSelector = new DefaultTrackSelector(adaptiveTrackSelectionFactory); + trackSelectionHelper = new TrackSelectionHelper(trackSelector, adaptiveTrackSelectionFactory); lastSeenTrackGroupArray = null; player = ExoPlayerFactory.newSimpleInstance(renderersFactory, trackSelector); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelector.java b/library/core/src/main/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelector.java index 941df66e4d..361fcf0b57 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelector.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelector.java @@ -24,6 +24,7 @@ import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.RendererCapabilities; import com.google.android.exoplayer2.source.TrackGroup; import com.google.android.exoplayer2.source.TrackGroupArray; +import com.google.android.exoplayer2.upstream.BandwidthMeter; import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Util; import java.util.ArrayList; @@ -376,10 +377,21 @@ public class DefaultTrackSelector extends MappingTrackSelector { private final AtomicReference paramsReference; /** - * Constructs an instance that does not support adaptive tracks. + * Constructs an instance that does not support adaptive track selection. */ public DefaultTrackSelector() { - this(null); + this((TrackSelection.Factory) null); + } + + /** + * Constructs an instance that supports adaptive track selection. Adaptive track selections use + * the provided {@link BandwidthMeter} to determine which individual track should be used during + * playback. + * + * @param bandwidthMeter The {@link BandwidthMeter}. + */ + public DefaultTrackSelector(BandwidthMeter bandwidthMeter) { + this(new AdaptiveTrackSelection.Factory(bandwidthMeter)); } /** diff --git a/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheDataSource.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheDataSource.java index 86dc5cfedf..bb2a952b11 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheDataSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheDataSource.java @@ -54,8 +54,8 @@ public final class CacheDataSource implements DataSource { FLAG_IGNORE_CACHE_FOR_UNSET_LENGTH_REQUESTS}) public @interface Flags {} /** - * A flag indicating whether we will block reads if the cache key is locked. If this flag is - * set, then we will read from upstream if the cache key is locked. + * A flag indicating whether we will block reads if the cache key is locked. If unset then data is + * read from upstream if the cache key is locked, regardless of whether the data is cached. */ public static final int FLAG_BLOCK_ON_CACHE = 1 << 0; @@ -110,7 +110,23 @@ public final class CacheDataSource implements DataSource { /** * Constructs an instance with default {@link DataSource} and {@link DataSink} instances for - * reading and writing the cache and with {@link #DEFAULT_MAX_CACHE_FILE_SIZE}. + * reading and writing the cache. + * + * @param cache The cache. + * @param upstream A {@link DataSource} for reading data not in the cache. + */ + public CacheDataSource(Cache cache, DataSource upstream) { + this(cache, upstream, 0, DEFAULT_MAX_CACHE_FILE_SIZE); + } + + /** + * Constructs an instance with default {@link DataSource} and {@link DataSink} instances for + * reading and writing the cache. + * + * @param cache The cache. + * @param upstream A {@link DataSource} for reading data not in the cache. + * @param flags A combination of {@link #FLAG_BLOCK_ON_CACHE}, {@link #FLAG_IGNORE_CACHE_ON_ERROR} + * and {@link #FLAG_IGNORE_CACHE_FOR_UNSET_LENGTH_REQUESTS}, or 0. */ public CacheDataSource(Cache cache, DataSource upstream, @Flags int flags) { this(cache, upstream, flags, DEFAULT_MAX_CACHE_FILE_SIZE); @@ -123,8 +139,8 @@ public final class CacheDataSource implements DataSource { * * @param cache The cache. * @param upstream A {@link DataSource} for reading data not in the cache. - * @param flags A combination of {@link #FLAG_BLOCK_ON_CACHE} and {@link - * #FLAG_IGNORE_CACHE_ON_ERROR} or 0. + * @param flags A combination of {@link #FLAG_BLOCK_ON_CACHE}, {@link #FLAG_IGNORE_CACHE_ON_ERROR} + * and {@link #FLAG_IGNORE_CACHE_FOR_UNSET_LENGTH_REQUESTS}, or 0. * @param maxCacheFileSize The maximum size of a cache file, in bytes. If the cached data size * exceeds this value, then the data will be fragmented into multiple cache files. The * finer-grained this is the finer-grained the eviction policy can be. @@ -145,8 +161,8 @@ public final class CacheDataSource implements DataSource { * @param cacheReadDataSource A {@link DataSource} for reading data from the cache. * @param cacheWriteDataSink A {@link DataSink} for writing data to the cache. If null, cache is * accessed read-only. - * @param flags A combination of {@link #FLAG_BLOCK_ON_CACHE} and {@link - * #FLAG_IGNORE_CACHE_ON_ERROR} or 0. + * @param flags A combination of {@link #FLAG_BLOCK_ON_CACHE}, {@link #FLAG_IGNORE_CACHE_ON_ERROR} + * and {@link #FLAG_IGNORE_CACHE_FOR_UNSET_LENGTH_REQUESTS}, or 0. * @param eventListener An optional {@link EventListener} to receive events. */ public CacheDataSource(Cache cache, DataSource upstream, DataSource cacheReadDataSource, diff --git a/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheDataSourceFactory.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheDataSourceFactory.java index b6fa3b4e2c..f0285da274 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheDataSourceFactory.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheDataSourceFactory.java @@ -33,18 +33,26 @@ public final class CacheDataSourceFactory implements DataSource.Factory { private final int flags; private final EventListener eventListener; + /** + * @see CacheDataSource#CacheDataSource(Cache, DataSource) + */ + public CacheDataSourceFactory(Cache cache, DataSource.Factory upstreamFactory) { + this(cache, upstreamFactory, 0); + } + /** * @see CacheDataSource#CacheDataSource(Cache, DataSource, int) */ - public CacheDataSourceFactory(Cache cache, DataSource.Factory upstreamFactory, int flags) { + public CacheDataSourceFactory(Cache cache, DataSource.Factory upstreamFactory, + @CacheDataSource.Flags int flags) { this(cache, upstreamFactory, flags, CacheDataSource.DEFAULT_MAX_CACHE_FILE_SIZE); } /** * @see CacheDataSource#CacheDataSource(Cache, DataSource, int, long) */ - public CacheDataSourceFactory(Cache cache, DataSource.Factory upstreamFactory, int flags, - long maxCacheFileSize) { + public CacheDataSourceFactory(Cache cache, DataSource.Factory upstreamFactory, + @CacheDataSource.Flags int flags, long maxCacheFileSize) { this(cache, upstreamFactory, new FileDataSourceFactory(), new CacheDataSinkFactory(cache, maxCacheFileSize), flags, null); } @@ -54,8 +62,8 @@ public final class CacheDataSourceFactory implements DataSource.Factory { * EventListener) */ public CacheDataSourceFactory(Cache cache, Factory upstreamFactory, - Factory cacheReadDataSourceFactory, - DataSink.Factory cacheWriteDataSinkFactory, int flags, EventListener eventListener) { + Factory cacheReadDataSourceFactory, DataSink.Factory cacheWriteDataSinkFactory, + @CacheDataSource.Flags int flags, EventListener eventListener) { this.cache = cache; this.upstreamFactory = upstreamFactory; this.cacheReadDataSourceFactory = cacheReadDataSourceFactory; From 68b3e0b54d0c34842c7ec35174e431b2d8f0284b Mon Sep 17 00:00:00 2001 From: aquilescanta Date: Fri, 5 May 2017 03:49:22 -0700 Subject: [PATCH 09/28] Expose no CC tracks if CLOSED-CAPTIONS=NONE is present Issue:#2743 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=155182859 --- .../playlist/HlsMasterPlaylistParserTest.java | 14 ++++- .../exoplayer2/source/hls/HlsChunkSource.java | 3 +- .../exoplayer2/source/hls/HlsMediaChunk.java | 11 +++- .../hls/playlist/HlsMasterPlaylist.java | 62 ++++++++++++++++--- .../hls/playlist/HlsPlaylistParser.java | 13 +++- 5 files changed, 90 insertions(+), 13 deletions(-) diff --git a/library/hls/src/androidTest/java/com/google/android/exoplayer2/source/hls/playlist/HlsMasterPlaylistParserTest.java b/library/hls/src/androidTest/java/com/google/android/exoplayer2/source/hls/playlist/HlsMasterPlaylistParserTest.java index aa279f23f4..912dcb28b2 100644 --- a/library/hls/src/androidTest/java/com/google/android/exoplayer2/source/hls/playlist/HlsMasterPlaylistParserTest.java +++ b/library/hls/src/androidTest/java/com/google/android/exoplayer2/source/hls/playlist/HlsMasterPlaylistParserTest.java @@ -23,6 +23,7 @@ import com.google.android.exoplayer2.util.MimeTypes; import java.io.ByteArrayInputStream; import java.io.IOException; import java.nio.charset.Charset; +import java.util.Collections; import java.util.List; import junit.framework.TestCase; @@ -56,16 +57,22 @@ public class HlsMasterPlaylistParserTest extends TestCase { private static final String MASTER_PLAYLIST_WITH_CC = " #EXTM3U \n" + "#EXT-X-MEDIA:TYPE=CLOSED-CAPTIONS,LANGUAGE=\"es\",NAME=\"Eng\",INSTREAM-ID=\"SERVICE4\"\n" - + "\n" + "#EXT-X-STREAM-INF:BANDWIDTH=1280000,CODECS=\"mp4a.40.2,avc1.66.30\",RESOLUTION=304x128\n" + "http://example.com/low.m3u8\n"; + private static final String MASTER_PLAYLIST_WITHOUT_CC = " #EXTM3U \n" + + "#EXT-X-MEDIA:TYPE=CLOSED-CAPTIONS,LANGUAGE=\"es\",NAME=\"Eng\",INSTREAM-ID=\"SERVICE4\"\n" + + "#EXT-X-STREAM-INF:BANDWIDTH=1280000,CODECS=\"mp4a.40.2,avc1.66.30\",RESOLUTION=304x128," + + "CLOSED-CAPTIONS=NONE\n" + + "http://example.com/low.m3u8\n"; + public void testParseMasterPlaylist() throws IOException{ HlsMasterPlaylist masterPlaylist = parseMasterPlaylist(PLAYLIST_URI, MASTER_PLAYLIST); List variants = masterPlaylist.variants; assertNotNull(variants); assertEquals(5, variants.size()); + assertNull(masterPlaylist.muxedCaptionFormats); assertEquals(1280000, variants.get(0).format.bitrate); assertNotNull(variants.get(0).format.codecs); @@ -117,6 +124,11 @@ public class HlsMasterPlaylistParserTest extends TestCase { assertEquals("es", closedCaptionFormat.language); } + public void testPlaylistWithoutClosedCaptions() throws IOException { + HlsMasterPlaylist playlist = parseMasterPlaylist(PLAYLIST_URI, MASTER_PLAYLIST_WITHOUT_CC); + assertEquals(Collections.emptyList(), playlist.muxedCaptionFormats); + } + private static HlsMasterPlaylist parseMasterPlaylist(String uri, String playlistString) throws IOException { Uri playlistUri = Uri.parse(uri); diff --git a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsChunkSource.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsChunkSource.java index ea99dae345..49c4d04abc 100644 --- a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsChunkSource.java +++ b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsChunkSource.java @@ -111,7 +111,8 @@ import java.util.Locale; * @param timestampAdjusterProvider A provider of {@link TimestampAdjuster} instances. If * multiple {@link HlsChunkSource}s are used for a single playback, they should all share the * same provider. - * @param muxedCaptionFormats List of muxed caption {@link Format}s. + * @param muxedCaptionFormats List of muxed caption {@link Format}s. Null if no closed caption + * information is available in the master playlist. */ public HlsChunkSource(HlsPlaylistTracker playlistTracker, HlsUrl[] variants, HlsDataSourceFactory dataSourceFactory, TimestampAdjusterProvider timestampAdjusterProvider, diff --git a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaChunk.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaChunk.java index 6f516923f9..6997324f02 100644 --- a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaChunk.java +++ b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaChunk.java @@ -39,6 +39,7 @@ import com.google.android.exoplayer2.util.ParsableByteArray; import com.google.android.exoplayer2.util.TimestampAdjuster; import com.google.android.exoplayer2.util.Util; import java.io.IOException; +import java.util.Collections; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; @@ -104,7 +105,8 @@ import java.util.concurrent.atomic.AtomicInteger; * @param dataSpec Defines the data to be loaded. * @param initDataSpec Defines the initialization data to be fed to new extractors. May be null. * @param hlsUrl The url of the playlist from which this chunk was obtained. - * @param muxedCaptionFormats List of muxed caption {@link Format}s. + * @param muxedCaptionFormats List of muxed caption {@link Format}s. Null if no closed caption + * information is available in the master playlist. * @param trackSelectionReason See {@link #trackSelectionReason}. * @param trackSelectionData See {@link #trackSelectionData}. * @param startTimeUs The start time of the chunk in microseconds. @@ -356,9 +358,12 @@ import java.util.concurrent.atomic.AtomicInteger; // This flag ensures the change of pid between streams does not affect the sample queues. @DefaultTsPayloadReaderFactory.Flags int esReaderFactoryFlags = DefaultTsPayloadReaderFactory.FLAG_IGNORE_SPLICE_INFO_STREAM; - if (!muxedCaptionFormats.isEmpty()) { + List closedCaptionFormats = muxedCaptionFormats; + if (closedCaptionFormats != null) { // The playlist declares closed caption renditions, we should ignore descriptors. esReaderFactoryFlags |= DefaultTsPayloadReaderFactory.FLAG_OVERRIDE_CAPTION_DESCRIPTORS; + } else { + closedCaptionFormats = Collections.emptyList(); } String codecs = trackFormat.codecs; if (!TextUtils.isEmpty(codecs)) { @@ -373,7 +378,7 @@ import java.util.concurrent.atomic.AtomicInteger; } } extractor = new TsExtractor(TsExtractor.MODE_HLS, timestampAdjuster, - new DefaultTsPayloadReaderFactory(esReaderFactoryFlags, muxedCaptionFormats)); + new DefaultTsPayloadReaderFactory(esReaderFactoryFlags, closedCaptionFormats)); } if (usingNewExtractor) { extractor.init(extractorOutput); diff --git a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsMasterPlaylist.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsMasterPlaylist.java index 5a8c63f609..874c865049 100644 --- a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsMasterPlaylist.java +++ b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsMasterPlaylist.java @@ -30,15 +30,31 @@ public final class HlsMasterPlaylist extends HlsPlaylist { */ public static final class HlsUrl { + /** + * The http url from which the media playlist can be obtained. + */ public final String url; + /** + * Format information associated with the HLS url. + */ public final Format format; - public static HlsUrl createMediaPlaylistHlsUrl(String baseUri) { + /** + * Creates an HLS url from a given http url. + * + * @param url The url. + * @return An HLS url. + */ + public static HlsUrl createMediaPlaylistHlsUrl(String url) { Format format = Format.createContainerFormat("0", MimeTypes.APPLICATION_M3U8, null, null, Format.NO_VALUE, 0, null); - return new HlsUrl(baseUri, format); + return new HlsUrl(url, format); } + /** + * @param url See {@link #url}. + * @param format See {@link #format}. + */ public HlsUrl(String url, Format format) { this.url = url; this.format = format; @@ -46,13 +62,39 @@ public final class HlsMasterPlaylist extends HlsPlaylist { } + /** + * The list of variants declared by the playlist. + */ public final List variants; + /** + * The list of demuxed audios declared by the playlist. + */ public final List audios; + /** + * The list of subtitles declared by the playlist. + */ public final List subtitles; + /** + * The format of the audio muxed in the variants. May be null if the playlist does not declare any + * muxed audio. + */ public final Format muxedAudioFormat; + /** + * The format of the closed captions declared by the playlist. May be empty if the playlist + * explicitly declares no captions are available, or null if the playlist does not declare any + * captions information. + */ public final List muxedCaptionFormats; + /** + * @param baseUri The base uri. Used to resolve relative paths. + * @param variants See {@link #variants}. + * @param audios See {@link #audios}. + * @param subtitles See {@link #subtitles}. + * @param muxedAudioFormat See {@link #muxedAudioFormat}. + * @param muxedCaptionFormats See {@link #muxedCaptionFormats}. + */ public HlsMasterPlaylist(String baseUri, List variants, List audios, List subtitles, Format muxedAudioFormat, List muxedCaptionFormats) { super(baseUri); @@ -60,14 +102,20 @@ public final class HlsMasterPlaylist extends HlsPlaylist { this.audios = Collections.unmodifiableList(audios); this.subtitles = Collections.unmodifiableList(subtitles); this.muxedAudioFormat = muxedAudioFormat; - this.muxedCaptionFormats = Collections.unmodifiableList(muxedCaptionFormats); + this.muxedCaptionFormats = muxedCaptionFormats != null + ? Collections.unmodifiableList(muxedCaptionFormats) : null; } - public static HlsMasterPlaylist createSingleVariantMasterPlaylist(String variantUri) { - List variant = Collections.singletonList(HlsUrl.createMediaPlaylistHlsUrl(variantUri)); + /** + * Creates a playlist with a single variant. + * + * @param variantUrl The url of the single variant. + * @return A master playlist with a single variant for the provided url. + */ + public static HlsMasterPlaylist createSingleVariantMasterPlaylist(String variantUrl) { + List variant = Collections.singletonList(HlsUrl.createMediaPlaylistHlsUrl(variantUrl)); List emptyList = Collections.emptyList(); - return new HlsMasterPlaylist(null, variant, emptyList, emptyList, null, - Collections.emptyList()); + return new HlsMasterPlaylist(null, variant, emptyList, emptyList, null, null); } } diff --git a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylistParser.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylistParser.java index 8e01dec6fe..664306baff 100644 --- a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylistParser.java +++ b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylistParser.java @@ -29,6 +29,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.util.ArrayList; +import java.util.Collections; import java.util.LinkedList; import java.util.List; import java.util.Queue; @@ -70,6 +71,8 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser audios = new ArrayList<>(); ArrayList subtitles = new ArrayList<>(); Format muxedAudioFormat = null; - ArrayList muxedCaptionFormats = new ArrayList<>(); + List muxedCaptionFormats = null; + boolean noClosedCaptions = false; String line; while (iterator.hasNext()) { @@ -210,6 +214,9 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser(); + } muxedCaptionFormats.add(Format.createTextContainerFormat(id, null, mimeType, null, Format.NO_VALUE, selectionFlags, language, accessibilityChannel)); break; @@ -221,6 +228,7 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser Date: Fri, 5 May 2017 07:21:48 -0700 Subject: [PATCH 10/28] Don't set MAX_INPUT_SIZE to unnecessarily large values If the codec isn't adaptive, there's no need to accommodate the width/height/input-size of streams that don't have the same resolution as the current stream. This is because we'll always need to instantiate a new codec anyway. Issue: #2607 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=155194458 --- .../video/MediaCodecVideoRenderer.java | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java index ac4bb36035..473d61e3fa 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java @@ -408,11 +408,9 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { @Override protected boolean canReconfigureCodec(MediaCodec codec, boolean codecIsAdaptive, Format oldFormat, Format newFormat) { - return areAdaptationCompatible(oldFormat, newFormat) + return areAdaptationCompatible(codecIsAdaptive, oldFormat, newFormat) && newFormat.width <= codecMaxValues.width && newFormat.height <= codecMaxValues.height - && newFormat.maxInputSize <= codecMaxValues.inputSize - && (codecIsAdaptive - || (oldFormat.width == newFormat.width && oldFormat.height == newFormat.height)); + && newFormat.maxInputSize <= codecMaxValues.inputSize; } @Override @@ -664,7 +662,7 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { } boolean haveUnknownDimensions = false; for (Format streamFormat : streamFormats) { - if (areAdaptationCompatible(format, streamFormat)) { + if (areAdaptationCompatible(codecInfo.adaptive, format, streamFormat)) { haveUnknownDimensions |= (streamFormat.width == Format.NO_VALUE || streamFormat.height == Format.NO_VALUE); maxWidth = Math.max(maxWidth, streamFormat.width); @@ -817,17 +815,19 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { } /** - * Returns whether an adaptive codec with suitable {@link CodecMaxValues} will support adaptation - * between two {@link Format}s. + * Returns whether a codec with suitable {@link CodecMaxValues} will support adaptation between + * two {@link Format}s. * + * @param codecIsAdaptive Whether the codec supports seamless resolution switches. * @param first The first format. * @param second The second format. - * @return Whether an adaptive codec with suitable {@link CodecMaxValues} will support adaptation - * between two {@link Format}s. + * @return Whether the codec will support adaptation between the two {@link Format}s. */ - private static boolean areAdaptationCompatible(Format first, Format second) { + private static boolean areAdaptationCompatible(boolean codecIsAdaptive, Format first, + Format second) { return first.sampleMimeType.equals(second.sampleMimeType) - && getRotationDegrees(first) == getRotationDegrees(second); + && getRotationDegrees(first) == getRotationDegrees(second) + && (codecIsAdaptive || (first.width == second.width && first.height == second.height)); } private static float getPixelWidthHeightRatio(Format format) { From 65607b2736d3e51c59b66fa1a76712c98576b854 Mon Sep 17 00:00:00 2001 From: arakawa_yusuke Date: Mon, 8 May 2017 20:05:10 +0900 Subject: [PATCH 11/28] Remove fully-qualified name --- .../android/exoplayer2/video/MediaCodecVideoRenderer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java index 473d61e3fa..dd0c5356ea 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java @@ -376,7 +376,7 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { } @Override - protected void onOutputFormatChanged(MediaCodec codec, android.media.MediaFormat outputFormat) { + protected void onOutputFormatChanged(MediaCodec codec, MediaFormat outputFormat) { boolean hasCrop = outputFormat.containsKey(KEY_CROP_RIGHT) && outputFormat.containsKey(KEY_CROP_LEFT) && outputFormat.containsKey(KEY_CROP_BOTTOM) && outputFormat.containsKey(KEY_CROP_TOP); From 2d2fcf1510be02a2315d41dd1a49e01aa0f71db4 Mon Sep 17 00:00:00 2001 From: andrewlewis Date: Sun, 7 May 2017 08:18:25 -0700 Subject: [PATCH 12/28] Use native byte order for SimpleOutputBuffers The default byte order for ByteBuffers is big endian, but platform decoder output buffers use native byte order. AudioProcessors handle native byte order input/output. When using a software audio decoding extension the Sonic audio processor would receive big endian input but was outputting to a native byte order buffer, which could be little endian. This mismatch caused audio output to be distorted. After this change both platform decoder and extension decoder output buffers should be in native byte order. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=155320973 --- .../google/android/exoplayer2/decoder/SimpleOutputBuffer.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/decoder/SimpleOutputBuffer.java b/library/core/src/main/java/com/google/android/exoplayer2/decoder/SimpleOutputBuffer.java index 309c7fd144..49c7dafbd6 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/decoder/SimpleOutputBuffer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/decoder/SimpleOutputBuffer.java @@ -16,6 +16,7 @@ package com.google.android.exoplayer2.decoder; import java.nio.ByteBuffer; +import java.nio.ByteOrder; /** * Buffer for {@link SimpleDecoder} output. @@ -40,7 +41,7 @@ public class SimpleOutputBuffer extends OutputBuffer { public ByteBuffer init(long timeUs, int size) { this.timeUs = timeUs; if (data == null || data.capacity() < size) { - data = ByteBuffer.allocateDirect(size); + data = ByteBuffer.allocateDirect(size).order(ByteOrder.nativeOrder()); } data.position(0); data.limit(size); From 963b7cbf72104e1fe1d61d8b218f620c4a39f5c1 Mon Sep 17 00:00:00 2001 From: andrewlewis Date: Mon, 8 May 2017 00:25:59 -0700 Subject: [PATCH 13/28] Fix interpolation for rate/pitch adjustment Based on https://github.com/waywardgeek/sonic/commit/7b441933. Issue: #2774 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=155349817 --- .../java/com/google/android/exoplayer2/audio/Sonic.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/Sonic.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/Sonic.java index 5d6f01b6e0..ef7877ae1e 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/audio/Sonic.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/Sonic.java @@ -374,8 +374,8 @@ import java.util.Arrays; } private short interpolate(short[] in, int inPos, int oldSampleRate, int newSampleRate) { - short left = in[inPos * numChannels]; - short right = in[inPos * numChannels + numChannels]; + short left = in[inPos]; + short right = in[inPos + numChannels]; int position = newRatePosition * oldSampleRate; int leftPosition = oldRatePosition * newSampleRate; int rightPosition = (oldRatePosition + 1) * newSampleRate; @@ -402,7 +402,7 @@ import java.util.Arrays; enlargeOutputBufferIfNeeded(1); for (int i = 0; i < numChannels; i++) { outputBuffer[numOutputSamples * numChannels + i] = - interpolate(pitchBuffer, position + i, oldSampleRate, newSampleRate); + interpolate(pitchBuffer, position * numChannels + i, oldSampleRate, newSampleRate); } newRatePosition++; numOutputSamples++; From 1d6816d94b3eb3238abfaa38c2edda1a1e4d9e0f Mon Sep 17 00:00:00 2001 From: olly Date: Sat, 7 Jan 2017 11:59:20 +0000 Subject: [PATCH 14/28] Avoid process death if OOM occurs on a loading thread This is most commonly caused by malformed media, where the media indicates that something we need to make an allocation for is *really huge*. Failing playback is appropriate for this case; killing the process is not. Issue: #2780 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=155408062 --- .../google/android/exoplayer2/upstream/Loader.java | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/upstream/Loader.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/Loader.java index bca90ddc5c..1bdebf7c17 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/upstream/Loader.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/upstream/Loader.java @@ -33,11 +33,11 @@ import java.util.concurrent.ExecutorService; public final class Loader implements LoaderErrorThrower { /** - * Thrown when an unexpected exception is encountered during loading. + * Thrown when an unexpected exception or error is encountered during loading. */ public static final class UnexpectedLoaderException extends IOException { - public UnexpectedLoaderException(Exception cause) { + public UnexpectedLoaderException(Throwable cause) { super("Unexpected " + cause.getClass().getSimpleName() + ": " + cause.getMessage(), cause); } @@ -316,6 +316,14 @@ public final class Loader implements LoaderErrorThrower { if (!released) { obtainMessage(MSG_IO_EXCEPTION, new UnexpectedLoaderException(e)).sendToTarget(); } + } catch (OutOfMemoryError e) { + // This can occur if a stream is malformed in a way that causes an extractor to think it + // needs to allocate a large amount of memory. We don't want the process to die in this + // case, but we do want the playback to fail. + Log.e(TAG, "OutOfMemory error loading stream", e); + if (!released) { + obtainMessage(MSG_IO_EXCEPTION, new UnexpectedLoaderException(e)).sendToTarget(); + } } catch (Error e) { // We'd hope that the platform would kill the process if an Error is thrown here, but the // executor may catch the error (b/20616433). Throw it here, but also pass and throw it from From 51132f58f9e08cd247a9d2f7149c860037cdb590 Mon Sep 17 00:00:00 2001 From: tasnimsunny Date: Mon, 8 May 2017 12:46:09 -0700 Subject: [PATCH 15/28] Make removal of non-existent cache span a no-op ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=155413733 --- .../google/android/exoplayer2/upstream/cache/SimpleCache.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/SimpleCache.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/SimpleCache.java index 14f006c850..bbff7dc4a2 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/SimpleCache.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/SimpleCache.java @@ -286,7 +286,9 @@ public final class SimpleCache implements Cache { private void removeSpan(CacheSpan span, boolean removeEmptyCachedContent) throws CacheException { CachedContent cachedContent = index.get(span.key); - Assertions.checkState(cachedContent.removeSpan(span)); + if (cachedContent == null || !cachedContent.removeSpan(span)) { + return; + } totalSpace -= span.length; if (removeEmptyCachedContent && cachedContent.isEmpty()) { index.removeEmpty(cachedContent.key); From 70b628526a742a433fdf5d1fccbb9e1995e4297d Mon Sep 17 00:00:00 2001 From: aquilescanta Date: Tue, 9 May 2017 03:14:58 -0700 Subject: [PATCH 16/28] Propagate playlist loading error if it prevents playback This imitates DashMediaSource's behavior. Issue:#2623 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=155485738 --- .../exoplayer2/source/hls/HlsChunkSource.java | 7 +++++++ .../exoplayer2/source/hls/HlsMediaSource.java | 2 +- .../hls/playlist/HlsPlaylistTracker.java | 19 +++++++++++++++---- 3 files changed, 23 insertions(+), 5 deletions(-) diff --git a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsChunkSource.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsChunkSource.java index 49c4d04abc..795e2f0eaa 100644 --- a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsChunkSource.java +++ b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsChunkSource.java @@ -92,6 +92,7 @@ import java.util.Locale; private boolean isTimestampMaster; private byte[] scratchSpace; private IOException fatalError; + private HlsUrl expectedPlaylistUrl; private Uri encryptionKeyUri; private byte[] encryptionKey; @@ -143,6 +144,9 @@ import java.util.Locale; if (fatalError != null) { throw fatalError; } + if (expectedPlaylistUrl != null) { + playlistTracker.maybeThrowPlaylistRefreshError(expectedPlaylistUrl); + } } /** @@ -195,6 +199,7 @@ import java.util.Locale; public void getNextChunk(HlsMediaChunk previous, long playbackPositionUs, HlsChunkHolder out) { int oldVariantIndex = previous == null ? C.INDEX_UNSET : trackGroup.indexOf(previous.trackFormat); + expectedPlaylistUrl = null; // Use start time of the previous chunk rather than its end time because switching format will // require downloading overlapping segments. long bufferedDurationUs = previous == null ? 0 @@ -208,6 +213,7 @@ import java.util.Locale; HlsUrl selectedUrl = variants[selectedVariantIndex]; if (!playlistTracker.isSnapshotValid(selectedUrl)) { out.playlist = selectedUrl; + expectedPlaylistUrl = selectedUrl; // Retry when playlist is refreshed. return; } @@ -247,6 +253,7 @@ import java.util.Locale; out.endOfStream = true; } else /* Live */ { out.playlist = selectedUrl; + expectedPlaylistUrl = selectedUrl; } return; } diff --git a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaSource.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaSource.java index 3cd9f19522..1bfb8371a0 100644 --- a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaSource.java +++ b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaSource.java @@ -84,7 +84,7 @@ public final class HlsMediaSource implements MediaSource, @Override public void maybeThrowSourceInfoRefreshError() throws IOException { - playlistTracker.maybeThrowPlaylistRefreshError(); + playlistTracker.maybeThrowPrimaryPlaylistRefreshError(); } @Override diff --git a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylistTracker.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylistTracker.java index 02a8e3f098..62b77a0575 100644 --- a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylistTracker.java +++ b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylistTracker.java @@ -200,18 +200,29 @@ public final class HlsPlaylistTracker implements Loader.Callback Date: Tue, 9 May 2017 03:20:40 -0700 Subject: [PATCH 17/28] Make MODE_SINGLE_PMT the default mode Even though this is not strictly spec compliant, this will make exoplayer behave like it used to before multiple program support. Developers who want to take advantage of the multiple program support are probably less than the ones who only want their stream to "just work". This is particularly useful for streams obtained after a filtering component, like a tv tuner. Issue:#2757 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=155486122 --- .../extractor/ts/TsExtractorTest.java | 4 ++-- .../extractor/DefaultExtractorsFactory.java | 21 ++++++++++++++++++- .../exoplayer2/extractor/ts/TsExtractor.java | 21 ++++++++++++++----- 3 files changed, 38 insertions(+), 8 deletions(-) diff --git a/library/core/src/androidTest/java/com/google/android/exoplayer2/extractor/ts/TsExtractorTest.java b/library/core/src/androidTest/java/com/google/android/exoplayer2/extractor/ts/TsExtractorTest.java index 7bf722cd8f..efd653b8d9 100644 --- a/library/core/src/androidTest/java/com/google/android/exoplayer2/extractor/ts/TsExtractorTest.java +++ b/library/core/src/androidTest/java/com/google/android/exoplayer2/extractor/ts/TsExtractorTest.java @@ -75,7 +75,7 @@ public final class TsExtractorTest extends InstrumentationTestCase { public void testCustomPesReader() throws Exception { CustomTsPayloadReaderFactory factory = new CustomTsPayloadReaderFactory(true, false); - TsExtractor tsExtractor = new TsExtractor(TsExtractor.MODE_NORMAL, new TimestampAdjuster(0), + TsExtractor tsExtractor = new TsExtractor(TsExtractor.MODE_MULTI_PMT, new TimestampAdjuster(0), factory); FakeExtractorInput input = new FakeExtractorInput.Builder() .setData(TestUtil.getByteArray(getInstrumentation(), "ts/sample.ts")) @@ -100,7 +100,7 @@ public final class TsExtractorTest extends InstrumentationTestCase { public void testCustomInitialSectionReader() throws Exception { CustomTsPayloadReaderFactory factory = new CustomTsPayloadReaderFactory(false, true); - TsExtractor tsExtractor = new TsExtractor(TsExtractor.MODE_NORMAL, new TimestampAdjuster(0), + TsExtractor tsExtractor = new TsExtractor(TsExtractor.MODE_MULTI_PMT, new TimestampAdjuster(0), factory); FakeExtractorInput input = new FakeExtractorInput.Builder() .setData(TestUtil.getByteArray(getInstrumentation(), "ts/sample_with_sdt.ts")) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/DefaultExtractorsFactory.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/DefaultExtractorsFactory.java index 022ca1277d..c47a91b176 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/DefaultExtractorsFactory.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/DefaultExtractorsFactory.java @@ -26,7 +26,9 @@ import com.google.android.exoplayer2.extractor.ts.AdtsExtractor; import com.google.android.exoplayer2.extractor.ts.DefaultTsPayloadReaderFactory; import com.google.android.exoplayer2.extractor.ts.PsExtractor; import com.google.android.exoplayer2.extractor.ts.TsExtractor; +import com.google.android.exoplayer2.extractor.ts.TsPayloadReader; import com.google.android.exoplayer2.extractor.wav.WavExtractor; +import com.google.android.exoplayer2.util.TimestampAdjuster; import java.lang.reflect.Constructor; /** @@ -67,8 +69,13 @@ public final class DefaultExtractorsFactory implements ExtractorsFactory { private @MatroskaExtractor.Flags int matroskaFlags; private @FragmentedMp4Extractor.Flags int fragmentedMp4Flags; private @Mp3Extractor.Flags int mp3Flags; + private @TsExtractor.Mode int tsMode; private @DefaultTsPayloadReaderFactory.Flags int tsFlags; + public DefaultExtractorsFactory() { + tsMode = TsExtractor.MODE_SINGLE_PMT; + } + /** * Sets flags for {@link MatroskaExtractor} instances created by the factory. * @@ -107,6 +114,18 @@ public final class DefaultExtractorsFactory implements ExtractorsFactory { return this; } + /** + * Sets the mode for {@link TsExtractor} instances created by the factory. + * + * @see TsExtractor#TsExtractor(int, TimestampAdjuster, TsPayloadReader.Factory). + * @param mode The mode to use. + * @return The factory, for convenience. + */ + public synchronized DefaultExtractorsFactory setTsExtractorMode(@TsExtractor.Mode int mode) { + tsMode = mode; + return this; + } + /** * Sets flags for {@link DefaultTsPayloadReaderFactory}s used by {@link TsExtractor} instances * created by the factory. @@ -130,7 +149,7 @@ public final class DefaultExtractorsFactory implements ExtractorsFactory { extractors[3] = new Mp3Extractor(mp3Flags); extractors[4] = new AdtsExtractor(); extractors[5] = new Ac3Extractor(); - extractors[6] = new TsExtractor(tsFlags); + extractors[6] = new TsExtractor(tsMode, tsFlags); extractors[7] = new FlvExtractor(); extractors[8] = new OggExtractor(); extractors[9] = new PsExtractor(); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/TsExtractor.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/TsExtractor.java index df6efb722c..71b8375bd8 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/TsExtractor.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/TsExtractor.java @@ -65,13 +65,13 @@ public final class TsExtractor implements Extractor { * Modes for the extractor. */ @Retention(RetentionPolicy.SOURCE) - @IntDef({MODE_NORMAL, MODE_SINGLE_PMT, MODE_HLS}) + @IntDef({MODE_MULTI_PMT, MODE_SINGLE_PMT, MODE_HLS}) public @interface Mode {} /** * Behave as defined in ISO/IEC 13818-1. */ - public static final int MODE_NORMAL = 0; + public static final int MODE_MULTI_PMT = 0; /** * Assume only one PMT will be contained in the stream, even if more are declared by the PAT. */ @@ -132,12 +132,23 @@ public final class TsExtractor implements Extractor { * {@code FLAG_*} values that control the behavior of the payload readers. */ public TsExtractor(@Flags int defaultTsPayloadReaderFlags) { - this(MODE_NORMAL, new TimestampAdjuster(0), - new DefaultTsPayloadReaderFactory(defaultTsPayloadReaderFlags)); + this(MODE_SINGLE_PMT, defaultTsPayloadReaderFlags); } /** - * @param mode Mode for the extractor. One of {@link #MODE_NORMAL}, {@link #MODE_SINGLE_PMT} + * @param mode Mode for the extractor. One of {@link #MODE_MULTI_PMT}, {@link #MODE_SINGLE_PMT} + * and {@link #MODE_HLS}. + * @param defaultTsPayloadReaderFlags A combination of {@link DefaultTsPayloadReaderFactory} + * {@code FLAG_*} values that control the behavior of the payload readers. + */ + public TsExtractor(@Mode int mode, @Flags int defaultTsPayloadReaderFlags) { + this(mode, new TimestampAdjuster(0), + new DefaultTsPayloadReaderFactory(defaultTsPayloadReaderFlags)); + } + + + /** + * @param mode Mode for the extractor. One of {@link #MODE_MULTI_PMT}, {@link #MODE_SINGLE_PMT} * and {@link #MODE_HLS}. * @param timestampAdjuster A timestamp adjuster for offsetting and scaling sample timestamps. * @param payloadReaderFactory Factory for injecting a custom set of payload readers. From 7eadfb1ff019f61f53cfb7d323cd77ba96c20cd6 Mon Sep 17 00:00:00 2001 From: andrewlewis Date: Wed, 10 May 2017 19:36:06 -0700 Subject: [PATCH 18/28] Fix wrap_content handling in DefaultTimeBar Issue: #2788 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=155705318 --- .../google/android/exoplayer2/ui/DefaultTimeBar.java | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/library/ui/src/main/java/com/google/android/exoplayer2/ui/DefaultTimeBar.java b/library/ui/src/main/java/com/google/android/exoplayer2/ui/DefaultTimeBar.java index 12f31f5da1..06ecb1aa69 100644 --- a/library/ui/src/main/java/com/google/android/exoplayer2/ui/DefaultTimeBar.java +++ b/library/ui/src/main/java/com/google/android/exoplayer2/ui/DefaultTimeBar.java @@ -343,16 +343,18 @@ public class DefaultTimeBar extends View implements TimeBar { @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - int measureWidth = MeasureSpec.getSize(widthMeasureSpec); - int measureHeight = MeasureSpec.getSize(heightMeasureSpec); - setMeasuredDimension(measureWidth, measureHeight); + int heightMode = MeasureSpec.getMode(heightMeasureSpec); + int heightSize = MeasureSpec.getSize(heightMeasureSpec); + int height = heightMode == MeasureSpec.UNSPECIFIED ? touchTargetHeight + : heightMode == MeasureSpec.EXACTLY ? heightSize : Math.min(touchTargetHeight, heightSize); + setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), height); } @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { int width = right - left; int height = bottom - top; - int barY = height - touchTargetHeight; + int barY = (height - touchTargetHeight) / 2; int seekLeft = getPaddingLeft(); int seekRight = width - getPaddingRight(); int progressY = barY + (touchTargetHeight - barHeight) / 2; From 9a2cac7f741101c58900da7c0070d66c4a578740 Mon Sep 17 00:00:00 2001 From: arakawa_yusuke Date: Thu, 11 May 2017 21:11:21 +0900 Subject: [PATCH 19/28] Refactor: Change the position of variable. --- .../java/com/google/android/exoplayer2/demo/PlayerActivity.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/demo/src/main/java/com/google/android/exoplayer2/demo/PlayerActivity.java b/demo/src/main/java/com/google/android/exoplayer2/demo/PlayerActivity.java index d01a64a051..34e0365933 100644 --- a/demo/src/main/java/com/google/android/exoplayer2/demo/PlayerActivity.java +++ b/demo/src/main/java/com/google/android/exoplayer2/demo/PlayerActivity.java @@ -234,7 +234,6 @@ public class PlayerActivity extends Activity implements OnClickListener, ExoPlay Intent intent = getIntent(); boolean needNewPlayer = player == null; if (needNewPlayer) { - boolean preferExtensionDecoders = intent.getBooleanExtra(PREFER_EXTENSION_DECODERS, false); UUID drmSchemeUuid = intent.hasExtra(DRM_SCHEME_UUID_EXTRA) ? UUID.fromString(intent.getStringExtra(DRM_SCHEME_UUID_EXTRA)) : null; DrmSessionManager drmSessionManager = null; @@ -253,6 +252,7 @@ public class PlayerActivity extends Activity implements OnClickListener, ExoPlay } } + boolean preferExtensionDecoders = intent.getBooleanExtra(PREFER_EXTENSION_DECODERS, false); @DefaultRenderersFactory.ExtensionRendererMode int extensionRendererMode = ((DemoApplication) getApplication()).useExtensionRenderers() ? (preferExtensionDecoders ? DefaultRenderersFactory.EXTENSION_RENDERER_MODE_PREFER From 1cda6e025e38608263572fb5a65b125c58b4334f Mon Sep 17 00:00:00 2001 From: sillywab8 Date: Fri, 12 May 2017 08:24:40 -0500 Subject: [PATCH 20/28] Add support for AVC Level 5.2 --- .../com/google/android/exoplayer2/mediacodec/MediaCodecUtil.java | 1 + 1 file changed, 1 insertion(+) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecUtil.java b/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecUtil.java index a09f6e26dd..2bb3603df9 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecUtil.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecUtil.java @@ -429,6 +429,7 @@ public final class MediaCodecUtil { case CodecProfileLevel.AVCLevel42: return 8704 * 16 * 16; case CodecProfileLevel.AVCLevel5: return 22080 * 16 * 16; case CodecProfileLevel.AVCLevel51: return 36864 * 16 * 16; + case CodecProfileLevel.AVCLevel52: return 36864 * 16 * 16; default: return -1; } } From e7ec4e6755e83162d1e2b3b44b6dce863348d01c Mon Sep 17 00:00:00 2001 From: bachinger Date: Sun, 14 May 2017 22:26:39 -0700 Subject: [PATCH 21/28] Enlarge size of data array of parsable packetArray if ogg packet size exceeds the current size. https://github.com/google/ExoPlayer/issues/2782 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=156018137 --- .../assets/ogg/bear_vorbis.ogg.0.dump | 2 +- .../assets/ogg/bear_vorbis.ogg.1.dump | 2 +- .../assets/ogg/bear_vorbis.ogg.2.dump | 2 +- .../assets/ogg/bear_vorbis.ogg.3.dump | 2 +- .../assets/ogg/bear_vorbis.ogg.unklen.dump | 2 +- .../exoplayer2/extractor/ogg/OggPacket.java | 19 +++++++++++++++++-- .../extractor/ogg/StreamReader.java | 5 ++--- .../extractor/ogg/VorbisReader.java | 2 +- 8 files changed, 25 insertions(+), 11 deletions(-) diff --git a/library/core/src/androidTest/assets/ogg/bear_vorbis.ogg.0.dump b/library/core/src/androidTest/assets/ogg/bear_vorbis.ogg.0.dump index 536f76adad..8e2c5125a3 100644 --- a/library/core/src/androidTest/assets/ogg/bear_vorbis.ogg.0.dump +++ b/library/core/src/androidTest/assets/ogg/bear_vorbis.ogg.0.dump @@ -9,7 +9,7 @@ track 0: id = null containerMimeType = null sampleMimeType = audio/vorbis - maxInputSize = 65025 + maxInputSize = -1 width = -1 height = -1 frameRate = -1.0 diff --git a/library/core/src/androidTest/assets/ogg/bear_vorbis.ogg.1.dump b/library/core/src/androidTest/assets/ogg/bear_vorbis.ogg.1.dump index 7490773bd5..aa25303ac3 100644 --- a/library/core/src/androidTest/assets/ogg/bear_vorbis.ogg.1.dump +++ b/library/core/src/androidTest/assets/ogg/bear_vorbis.ogg.1.dump @@ -9,7 +9,7 @@ track 0: id = null containerMimeType = null sampleMimeType = audio/vorbis - maxInputSize = 65025 + maxInputSize = -1 width = -1 height = -1 frameRate = -1.0 diff --git a/library/core/src/androidTest/assets/ogg/bear_vorbis.ogg.2.dump b/library/core/src/androidTest/assets/ogg/bear_vorbis.ogg.2.dump index 82ad16e701..58969058fa 100644 --- a/library/core/src/androidTest/assets/ogg/bear_vorbis.ogg.2.dump +++ b/library/core/src/androidTest/assets/ogg/bear_vorbis.ogg.2.dump @@ -9,7 +9,7 @@ track 0: id = null containerMimeType = null sampleMimeType = audio/vorbis - maxInputSize = 65025 + maxInputSize = -1 width = -1 height = -1 frameRate = -1.0 diff --git a/library/core/src/androidTest/assets/ogg/bear_vorbis.ogg.3.dump b/library/core/src/androidTest/assets/ogg/bear_vorbis.ogg.3.dump index 810b66901c..4c789a8431 100644 --- a/library/core/src/androidTest/assets/ogg/bear_vorbis.ogg.3.dump +++ b/library/core/src/androidTest/assets/ogg/bear_vorbis.ogg.3.dump @@ -9,7 +9,7 @@ track 0: id = null containerMimeType = null sampleMimeType = audio/vorbis - maxInputSize = 65025 + maxInputSize = -1 width = -1 height = -1 frameRate = -1.0 diff --git a/library/core/src/androidTest/assets/ogg/bear_vorbis.ogg.unklen.dump b/library/core/src/androidTest/assets/ogg/bear_vorbis.ogg.unklen.dump index 8e86ca340d..2f163572bf 100644 --- a/library/core/src/androidTest/assets/ogg/bear_vorbis.ogg.unklen.dump +++ b/library/core/src/androidTest/assets/ogg/bear_vorbis.ogg.unklen.dump @@ -9,7 +9,7 @@ track 0: id = null containerMimeType = null sampleMimeType = audio/vorbis - maxInputSize = 65025 + maxInputSize = -1 width = -1 height = -1 frameRate = -1.0 diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ogg/OggPacket.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ogg/OggPacket.java index 892f0a68af..c7f4e9489b 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ogg/OggPacket.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ogg/OggPacket.java @@ -20,6 +20,7 @@ import com.google.android.exoplayer2.extractor.ExtractorInput; import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.ParsableByteArray; import java.io.IOException; +import java.util.Arrays; /** * OGG packet class. @@ -27,8 +28,8 @@ import java.io.IOException; /* package */ final class OggPacket { private final OggPageHeader pageHeader = new OggPageHeader(); - private final ParsableByteArray packetArray = - new ParsableByteArray(new byte[OggPageHeader.MAX_PAGE_PAYLOAD], 0); + private final ParsableByteArray packetArray = new ParsableByteArray( + new byte[OggPageHeader.MAX_PAGE_PAYLOAD], 0); private int currentSegmentIndex = C.INDEX_UNSET; private int segmentCount; @@ -85,6 +86,9 @@ import java.io.IOException; int size = calculatePacketSize(currentSegmentIndex); int segmentIndex = currentSegmentIndex + segmentCount; if (size > 0) { + if (packetArray.capacity() < packetArray.limit() + size) { + packetArray.data = Arrays.copyOf(packetArray.data, packetArray.limit() + size); + } input.readFully(packetArray.data, packetArray.limit(), size); packetArray.setLimit(packetArray.limit() + size); populated = pageHeader.laces[segmentIndex - 1] != 255; @@ -118,6 +122,17 @@ import java.io.IOException; return packetArray; } + /** + * Trims the packet data array. + */ + public void trimPayload() { + if (packetArray.data.length == OggPageHeader.MAX_PAGE_PAYLOAD) { + return; + } + packetArray.data = Arrays.copyOf(packetArray.data, Math.max(OggPageHeader.MAX_PAGE_PAYLOAD, + packetArray.limit())); + } + /** * Calculates the size of the packet starting from {@code startSegmentIndex}. * diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ogg/StreamReader.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ogg/StreamReader.java index 6424155bd9..c203b0c6bd 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ogg/StreamReader.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ogg/StreamReader.java @@ -103,15 +103,12 @@ import java.io.IOException; switch (state) { case STATE_READ_HEADERS: return readHeaders(input); - case STATE_SKIP_HEADERS: input.skipFully((int) payloadStartPosition); state = STATE_READ_PAYLOAD; return Extractor.RESULT_CONTINUE; - case STATE_READ_PAYLOAD: return readPayload(input, seekPosition); - default: // Never happens. throw new IllegalStateException(); @@ -152,6 +149,8 @@ import java.io.IOException; setupData = null; state = STATE_READ_PAYLOAD; + // First payload packet. Trim the payload array of the ogg packet after headers have been read. + oggPacket.trimPayload(); return Extractor.RESULT_CONTINUE; } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ogg/VorbisReader.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ogg/VorbisReader.java index ae0a69ef7d..31ac6858be 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ogg/VorbisReader.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ogg/VorbisReader.java @@ -101,7 +101,7 @@ import java.util.ArrayList; codecInitialisationData.add(vorbisSetup.setupHeaderData); setupData.format = Format.createAudioSampleFormat(null, MimeTypes.AUDIO_VORBIS, null, - this.vorbisSetup.idHeader.bitrateNominal, OggPageHeader.MAX_PAGE_PAYLOAD, + this.vorbisSetup.idHeader.bitrateNominal, Format.NO_VALUE, this.vorbisSetup.idHeader.channels, (int) this.vorbisSetup.idHeader.sampleRate, codecInitialisationData, null, 0, null); return true; From ecc4184e6c6ac8242fc796962f7eba936990077d Mon Sep 17 00:00:00 2001 From: andrewlewis Date: Mon, 15 May 2017 11:14:04 -0700 Subject: [PATCH 22/28] Enable neon for libvpx on arm64-v8a ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=156077889 --- extensions/vp9/src/main/jni/generate_libvpx_android_configs.sh | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/extensions/vp9/src/main/jni/generate_libvpx_android_configs.sh b/extensions/vp9/src/main/jni/generate_libvpx_android_configs.sh index 15dbabdb1f..5f058d0551 100755 --- a/extensions/vp9/src/main/jni/generate_libvpx_android_configs.sh +++ b/extensions/vp9/src/main/jni/generate_libvpx_android_configs.sh @@ -51,8 +51,7 @@ config[3]+=" --disable-sse3 --disable-ssse3 --disable-sse4_1 --disable-avx" config[3]+=" --disable-avx2 --enable-pic" arch[4]="arm64-v8a" -config[4]="--force-target=armv8-android-gcc --sdk-path=$ndk --disable-neon" -config[4]+=" --disable-neon-asm" +config[4]="--force-target=armv8-android-gcc --sdk-path=$ndk --enable-neon" arch[5]="x86_64" config[5]="--force-target=x86_64-android-gcc --sdk-path=$ndk --disable-sse2" From a0f4bf0ee18a5f883530202a2aad0f53604b99cb Mon Sep 17 00:00:00 2001 From: andrewlewis Date: Mon, 15 May 2017 15:36:20 -0700 Subject: [PATCH 23/28] Constrain DefaultTimeBar maximum positions ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=156113616 --- .../google/android/exoplayer2/ui/DefaultTimeBar.java | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/library/ui/src/main/java/com/google/android/exoplayer2/ui/DefaultTimeBar.java b/library/ui/src/main/java/com/google/android/exoplayer2/ui/DefaultTimeBar.java index 06ecb1aa69..fd05fdd5d0 100644 --- a/library/ui/src/main/java/com/google/android/exoplayer2/ui/DefaultTimeBar.java +++ b/library/ui/src/main/java/com/google/android/exoplayer2/ui/DefaultTimeBar.java @@ -465,12 +465,10 @@ public class DefaultTimeBar extends View implements TimeBar { scrubberBar.set(progressBar); long newScrubberTime = scrubbing ? scrubPosition : position; if (duration > 0) { - int bufferedPixelWidth = - (int) ((progressBar.width() * bufferedPosition) / duration); - bufferedBar.right = progressBar.left + bufferedPixelWidth; - int scrubberPixelPosition = - (int) ((progressBar.width() * newScrubberTime) / duration); - scrubberBar.right = progressBar.left + scrubberPixelPosition; + int bufferedPixelWidth = (int) ((progressBar.width() * bufferedPosition) / duration); + bufferedBar.right = Math.min(progressBar.left + bufferedPixelWidth, progressBar.right); + int scrubberPixelPosition = (int) ((progressBar.width() * newScrubberTime) / duration); + scrubberBar.right = Math.min(progressBar.left + scrubberPixelPosition, progressBar.right); } else { bufferedBar.right = progressBar.left; scrubberBar.right = progressBar.left; From a4a31546047a6bea63e41775fb8f4c8ff88cf9be Mon Sep 17 00:00:00 2001 From: olly Date: Mon, 15 May 2017 18:06:20 -0700 Subject: [PATCH 24/28] Clear up BaseRenderer.disable - Call onDisabled last. onDisabled really shouldn't be doing anything with the stream, so pretty sure this is fine (and guarantees the stream is cleared properly even if onDisabled throws a RTE). - Remove super.onDisabled calls from Text/Metadata renderers. This is just for consistency; we don't make such calls in other direct descendants of BaseRenderer. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=156130640 --- .../main/java/com/google/android/exoplayer2/BaseRenderer.java | 4 +--- .../google/android/exoplayer2/metadata/MetadataRenderer.java | 1 - .../java/com/google/android/exoplayer2/text/TextRenderer.java | 1 - 3 files changed, 1 insertion(+), 5 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/BaseRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/BaseRenderer.java index 44fb6d68ae..396584a39e 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/BaseRenderer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/BaseRenderer.java @@ -142,9 +142,9 @@ public abstract class BaseRenderer implements Renderer, RendererCapabilities { public final void disable() { Assertions.checkState(state == STATE_ENABLED); state = STATE_DISABLED; - onDisabled(); stream = null; streamIsFinal = false; + onDisabled(); } // RendererCapabilities implementation. @@ -300,8 +300,6 @@ public abstract class BaseRenderer implements Renderer, RendererCapabilities { /** * Returns whether the upstream source is ready. - * - * @return Whether the source is ready. */ protected final boolean isSourceReady() { return readEndOfStream ? streamIsFinal : stream.isReady(); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/metadata/MetadataRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/metadata/MetadataRenderer.java index 814238970b..70b2d8aab9 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/metadata/MetadataRenderer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/metadata/MetadataRenderer.java @@ -153,7 +153,6 @@ public final class MetadataRenderer extends BaseRenderer implements Callback { protected void onDisabled() { flushPendingMetadata(); decoder = null; - super.onDisabled(); } @Override diff --git a/library/core/src/main/java/com/google/android/exoplayer2/text/TextRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/text/TextRenderer.java index 2f07fe5294..4950549b19 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/text/TextRenderer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/text/TextRenderer.java @@ -254,7 +254,6 @@ public final class TextRenderer extends BaseRenderer implements Callback { streamFormat = null; clearOutput(); releaseDecoder(); - super.onDisabled(); } @Override From fb3c19055691ed835c9594d9a92bdf76117b835f Mon Sep 17 00:00:00 2001 From: olly Date: Mon, 15 May 2017 18:11:45 -0700 Subject: [PATCH 25/28] Clear the correct buffer in MediaCodecRenderer ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=156131086 --- .../android/exoplayer2/mediacodec/MediaCodecRenderer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java index 8fb9bc9271..25a5aa4dd3 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java @@ -488,7 +488,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer { } if (format == null) { // We don't have a format yet, so try and read one. - buffer.clear(); + flagsOnlyBuffer.clear(); int result = readSource(formatHolder, flagsOnlyBuffer, true); if (result == C.RESULT_FORMAT_READ) { onInputFormatChanged(formatHolder.format); From bf71ad48838169326b7f5f95c6a4a3f5723c8870 Mon Sep 17 00:00:00 2001 From: olly Date: Mon, 22 May 2017 13:52:51 -0700 Subject: [PATCH 26/28] Fix TTML positioning Issue: #2824 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=156781252 --- .../exoplayer2/text/ttml/TtmlDecoderTest.java | 34 +++--- .../exoplayer2/text/ttml/TtmlDecoder.java | 100 +++++++++++++----- .../exoplayer2/text/ttml/TtmlNode.java | 7 +- .../exoplayer2/text/ttml/TtmlRegion.java | 14 ++- 4 files changed, 101 insertions(+), 54 deletions(-) diff --git a/library/core/src/androidTest/java/com/google/android/exoplayer2/text/ttml/TtmlDecoderTest.java b/library/core/src/androidTest/java/com/google/android/exoplayer2/text/ttml/TtmlDecoderTest.java index 381aaa34ae..496e3f87de 100644 --- a/library/core/src/androidTest/java/com/google/android/exoplayer2/text/ttml/TtmlDecoderTest.java +++ b/library/core/src/androidTest/java/com/google/android/exoplayer2/text/ttml/TtmlDecoderTest.java @@ -157,39 +157,39 @@ public final class TtmlDecoderTest extends InstrumentationTestCase { assertEquals(2, output.size()); Cue ttmlCue = output.get(0); assertEquals("lorem", ttmlCue.text.toString()); - assertEquals(10.f / 100.f, ttmlCue.position); - assertEquals(10.f / 100.f, ttmlCue.line); - assertEquals(20.f / 100.f, ttmlCue.size); + assertEquals(10f / 100f, ttmlCue.position); + assertEquals(10f / 100f, ttmlCue.line); + assertEquals(20f / 100f, ttmlCue.size); ttmlCue = output.get(1); assertEquals("amet", ttmlCue.text.toString()); - assertEquals(60.f / 100.f, ttmlCue.position); - assertEquals(10.f / 100.f, ttmlCue.line); - assertEquals(20.f / 100.f, ttmlCue.size); + assertEquals(60f / 100f, ttmlCue.position); + assertEquals(10f / 100f, ttmlCue.line); + assertEquals(20f / 100f, ttmlCue.size); output = subtitle.getCues(5000000); assertEquals(1, output.size()); ttmlCue = output.get(0); assertEquals("ipsum", ttmlCue.text.toString()); - assertEquals(40.f / 100.f, ttmlCue.position); - assertEquals(40.f / 100.f, ttmlCue.line); - assertEquals(20.f / 100.f, ttmlCue.size); + assertEquals(40f / 100f, ttmlCue.position); + assertEquals(40f / 100f, ttmlCue.line); + assertEquals(20f / 100f, ttmlCue.size); output = subtitle.getCues(9000000); assertEquals(1, output.size()); ttmlCue = output.get(0); assertEquals("dolor", ttmlCue.text.toString()); - assertEquals(10.f / 100.f, ttmlCue.position); - assertEquals(80.f / 100.f, ttmlCue.line); - assertEquals(Cue.DIMEN_UNSET, ttmlCue.size); + assertEquals(10f / 100f, ttmlCue.position); + assertEquals(80f / 100f, ttmlCue.line); + assertEquals(1f, ttmlCue.size); output = subtitle.getCues(21000000); assertEquals(1, output.size()); ttmlCue = output.get(0); assertEquals("She first said this", ttmlCue.text.toString()); - assertEquals(45.f / 100.f, ttmlCue.position); - assertEquals(45.f / 100.f, ttmlCue.line); - assertEquals(35.f / 100.f, ttmlCue.size); + assertEquals(45f / 100f, ttmlCue.position); + assertEquals(45f / 100f, ttmlCue.line); + assertEquals(35f / 100f, ttmlCue.size); output = subtitle.getCues(25000000); ttmlCue = output.get(0); assertEquals("She first said this\nThen this", ttmlCue.text.toString()); @@ -197,8 +197,8 @@ public final class TtmlDecoderTest extends InstrumentationTestCase { assertEquals(1, output.size()); ttmlCue = output.get(0); assertEquals("She first said this\nThen this\nFinally this", ttmlCue.text.toString()); - assertEquals(45.f / 100.f, ttmlCue.position); - assertEquals(45.f / 100.f, ttmlCue.line); + assertEquals(45f / 100f, ttmlCue.position); + assertEquals(45f / 100f, ttmlCue.line); } public void testEmptyStyleAttribute() throws IOException, SubtitleDecoderException { diff --git a/library/core/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlDecoder.java b/library/core/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlDecoder.java index 71ce17eeed..0012ce2c22 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlDecoder.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlDecoder.java @@ -17,7 +17,6 @@ package com.google.android.exoplayer2.text.ttml; import android.text.Layout; import android.util.Log; -import android.util.Pair; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.text.Cue; import com.google.android.exoplayer2.text.SimpleSubtitleDecoder; @@ -100,7 +99,7 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder { XmlPullParser xmlParser = xmlParserFactory.newPullParser(); Map globalStyles = new HashMap<>(); Map regionMap = new HashMap<>(); - regionMap.put(TtmlNode.ANONYMOUS_REGION_ID, new TtmlRegion()); + regionMap.put(TtmlNode.ANONYMOUS_REGION_ID, new TtmlRegion(null)); ByteArrayInputStream inputStream = new ByteArrayInputStream(bytes, 0, length); xmlParser.setInput(inputStream, null); TtmlSubtitle ttmlSubtitle = null; @@ -211,9 +210,9 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder { globalStyles.put(style.getId(), style); } } else if (XmlPullParserUtil.isStartTag(xmlParser, TtmlNode.TAG_REGION)) { - Pair ttmlRegionInfo = parseRegionAttributes(xmlParser); - if (ttmlRegionInfo != null) { - globalRegions.put(ttmlRegionInfo.first, ttmlRegionInfo.second); + TtmlRegion ttmlRegion = parseRegionAttributes(xmlParser); + if (ttmlRegion != null) { + globalRegions.put(ttmlRegion.id, ttmlRegion); } } } while (!XmlPullParserUtil.isEndTag(xmlParser, TtmlNode.TAG_HEAD)); @@ -221,41 +220,84 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder { } /** - * Parses a region declaration. Supports origin and extent definition but only when defined in - * terms of percentage of the viewport. Regions that do not correctly declare origin are ignored. + * Parses a region declaration. + *

    + * If the region defines an origin and/or extent, it is required that they're defined as + * percentages of the viewport. Region declarations that define origin and/or extent in other + * formats are unsupported, and null is returned. */ - private Pair parseRegionAttributes(XmlPullParser xmlParser) { + private TtmlRegion parseRegionAttributes(XmlPullParser xmlParser) { String regionId = XmlPullParserUtil.getAttributeValue(xmlParser, TtmlNode.ATTR_ID); - String regionOrigin = XmlPullParserUtil.getAttributeValue(xmlParser, TtmlNode.ATTR_TTS_ORIGIN); - String regionExtent = XmlPullParserUtil.getAttributeValue(xmlParser, TtmlNode.ATTR_TTS_EXTENT); - if (regionOrigin == null || regionId == null) { + if (regionId == null) { return null; } - float position = Cue.DIMEN_UNSET; - float line = Cue.DIMEN_UNSET; - Matcher originMatcher = PERCENTAGE_COORDINATES.matcher(regionOrigin); - if (originMatcher.matches()) { - try { - position = Float.parseFloat(originMatcher.group(1)) / 100.f; - line = Float.parseFloat(originMatcher.group(2)) / 100.f; - } catch (NumberFormatException e) { - Log.w(TAG, "Ignoring region with malformed origin: '" + regionOrigin + "'", e); - position = Cue.DIMEN_UNSET; + + float position; + float line; + String regionOrigin = XmlPullParserUtil.getAttributeValue(xmlParser, TtmlNode.ATTR_TTS_ORIGIN); + if (regionOrigin != null) { + Matcher originMatcher = PERCENTAGE_COORDINATES.matcher(regionOrigin); + if (originMatcher.matches()) { + try { + position = Float.parseFloat(originMatcher.group(1)) / 100f; + line = Float.parseFloat(originMatcher.group(2)) / 100f; + } catch (NumberFormatException e) { + Log.w(TAG, "Ignoring region with malformed origin: " + regionOrigin); + return null; + } + } else { + Log.w(TAG, "Ignoring region with unsupported origin: " + regionOrigin); + return null; } + } else { + // Origin is omitted. Default to top left. + position = 0; + line = 0; } - float width = Cue.DIMEN_UNSET; + + float width; + float height; + String regionExtent = XmlPullParserUtil.getAttributeValue(xmlParser, TtmlNode.ATTR_TTS_EXTENT); if (regionExtent != null) { Matcher extentMatcher = PERCENTAGE_COORDINATES.matcher(regionExtent); if (extentMatcher.matches()) { try { - width = Float.parseFloat(extentMatcher.group(1)) / 100.f; + width = Float.parseFloat(extentMatcher.group(1)) / 100f; + height = Float.parseFloat(extentMatcher.group(2)) / 100f; } catch (NumberFormatException e) { - Log.w(TAG, "Ignoring malformed region extent: '" + regionExtent + "'", e); + Log.w(TAG, "Ignoring region with malformed extent: " + regionOrigin); + return null; } + } else { + Log.w(TAG, "Ignoring region with unsupported extent: " + regionOrigin); + return null; + } + } else { + // Extent is omitted. Default to extent of parent. + width = 1; + height = 1; + } + + @Cue.AnchorType int lineAnchor = Cue.ANCHOR_TYPE_START; + String displayAlign = XmlPullParserUtil.getAttributeValue(xmlParser, + TtmlNode.ATTR_TTS_DISPLAY_ALIGN); + if (displayAlign != null) { + switch (displayAlign.toLowerCase()) { + case "center": + lineAnchor = Cue.ANCHOR_TYPE_MIDDLE; + line += height / 2; + break; + case "after": + lineAnchor = Cue.ANCHOR_TYPE_END; + line += height; + break; + default: + // Default "before" case. Do nothing. + break; } } - return position != Cue.DIMEN_UNSET ? new Pair<>(regionId, new TtmlRegion(position, line, - Cue.LINE_TYPE_FRACTION, width)) : null; + + return new TtmlRegion(regionId, position, line, Cue.LINE_TYPE_FRACTION, lineAnchor, width); } private String[] parseStyleIds(String parentStyleIds) { @@ -277,7 +319,7 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder { try { style.setBackgroundColor(ColorParser.parseTtmlColor(attributeValue)); } catch (IllegalArgumentException e) { - Log.w(TAG, "failed parsing background value: '" + attributeValue + "'"); + Log.w(TAG, "Failed parsing background value: " + attributeValue); } break; case TtmlNode.ATTR_TTS_COLOR: @@ -285,7 +327,7 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder { try { style.setFontColor(ColorParser.parseTtmlColor(attributeValue)); } catch (IllegalArgumentException e) { - Log.w(TAG, "failed parsing color value: '" + attributeValue + "'"); + Log.w(TAG, "Failed parsing color value: " + attributeValue); } break; case TtmlNode.ATTR_TTS_FONT_FAMILY: @@ -296,7 +338,7 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder { style = createIfNull(style); parseFontSize(attributeValue, style); } catch (SubtitleDecoderException e) { - Log.w(TAG, "failed parsing fontSize value: '" + attributeValue + "'"); + Log.w(TAG, "Failed parsing fontSize value: " + attributeValue); } break; case TtmlNode.ATTR_TTS_FONT_WEIGHT: diff --git a/library/core/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlNode.java b/library/core/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlNode.java index 18378df445..43fa7a1bd9 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlNode.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlNode.java @@ -50,14 +50,15 @@ import java.util.TreeSet; public static final String ANONYMOUS_REGION_ID = ""; public static final String ATTR_ID = "id"; - public static final String ATTR_TTS_BACKGROUND_COLOR = "backgroundColor"; + public static final String ATTR_TTS_ORIGIN = "origin"; public static final String ATTR_TTS_EXTENT = "extent"; + public static final String ATTR_TTS_DISPLAY_ALIGN = "displayAlign"; + public static final String ATTR_TTS_BACKGROUND_COLOR = "backgroundColor"; public static final String ATTR_TTS_FONT_STYLE = "fontStyle"; public static final String ATTR_TTS_FONT_SIZE = "fontSize"; public static final String ATTR_TTS_FONT_FAMILY = "fontFamily"; public static final String ATTR_TTS_FONT_WEIGHT = "fontWeight"; public static final String ATTR_TTS_COLOR = "color"; - public static final String ATTR_TTS_ORIGIN = "origin"; public static final String ATTR_TTS_TEXT_DECORATION = "textDecoration"; public static final String ATTR_TTS_TEXT_ALIGN = "textAlign"; @@ -179,7 +180,7 @@ import java.util.TreeSet; for (Entry entry : regionOutputs.entrySet()) { TtmlRegion region = regionMap.get(entry.getKey()); cues.add(new Cue(cleanUpText(entry.getValue()), null, region.line, region.lineType, - Cue.TYPE_UNSET, region.position, Cue.TYPE_UNSET, region.width)); + region.lineAnchor, region.position, Cue.TYPE_UNSET, region.width)); } return cues; } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlRegion.java b/library/core/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlRegion.java index 5f30834b4d..98823d7a84 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlRegion.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlRegion.java @@ -22,20 +22,24 @@ import com.google.android.exoplayer2.text.Cue; */ /* package */ final class TtmlRegion { + public final String id; public final float position; public final float line; - @Cue.LineType - public final int lineType; + @Cue.LineType public final int lineType; + @Cue.AnchorType public final int lineAnchor; public final float width; - public TtmlRegion() { - this(Cue.DIMEN_UNSET, Cue.DIMEN_UNSET, Cue.TYPE_UNSET, Cue.DIMEN_UNSET); + public TtmlRegion(String id) { + this(id, Cue.DIMEN_UNSET, Cue.DIMEN_UNSET, Cue.TYPE_UNSET, Cue.TYPE_UNSET, Cue.DIMEN_UNSET); } - public TtmlRegion(float position, float line, @Cue.LineType int lineType, float width) { + public TtmlRegion(String id, float position, float line, @Cue.LineType int lineType, + @Cue.AnchorType int lineAnchor, float width) { + this.id = id; this.position = position; this.line = line; this.lineType = lineType; + this.lineAnchor = lineAnchor; this.width = width; } From c14a14e51d5c9a6d29f101892fdf184d61f546f9 Mon Sep 17 00:00:00 2001 From: olly Date: Tue, 23 May 2017 07:45:53 -0700 Subject: [PATCH 27/28] Update gradle + bintray-release ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=156860658 --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index cbc34cecd6..da75def90b 100644 --- a/build.gradle +++ b/build.gradle @@ -16,7 +16,7 @@ buildscript { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:2.3.0' + classpath 'com.android.tools.build:gradle:2.3.1' classpath 'com.novoda:bintray-release:0.4.0' } // Workaround for the following test coverage issue. Remove when fixed: From fe50459449413ae3a9f3a9bb720812547fbd63a8 Mon Sep 17 00:00:00 2001 From: olly Date: Tue, 23 May 2017 09:41:37 -0700 Subject: [PATCH 28/28] Bump version and update release notes ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=156872383 --- RELEASENOTES.md | 23 +++++++++++++++++++ build.gradle | 2 +- demo/src/main/AndroidManifest.xml | 4 ++-- .../exoplayer2/ExoPlayerLibraryInfo.java | 6 ++--- 4 files changed, 29 insertions(+), 6 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index d216e767b0..6a1defa809 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -1,5 +1,28 @@ # Release notes # +### r2.4.1 ### + +* Stability: Avoid OutOfMemoryError in extractors when parsing malformed media + ([#2780](https://github.com/google/ExoPlayer/issues/2780)). +* Stability: Avoid native crash on Galaxy Nexus. Avoid unnecessarily large codec + input buffer allocations on all devices + ([#2607](https://github.com/google/ExoPlayer/issues/2607)). +* Variable speed playback: Fix interpolation for rate/pitch adjustment + ([#2774](https://github.com/google/ExoPlayer/issues/2774)). +* HLS: Include EXT-X-DATERANGE tags in HlsMediaPlaylist. +* HLS: Don't expose CEA-608 track if CLOSED-CAPTIONS=NONE + ([#2743](https://github.com/google/ExoPlayer/issues/2743)). +* HLS: Correctly propagate errors loading the media playlist + ([#2623](https://github.com/google/ExoPlayer/issues/2623)). +* UI: DefaultTimeBar enhancements and bug fixes + ([#2740](https://github.com/google/ExoPlayer/issues/2740)). +* Ogg: Fix failure to play some Ogg files + ([#2782](https://github.com/google/ExoPlayer/issues/2782)). +* Captions: Don't select text tack with no language by default. +* Captions: TTML positioning fixes + ([#2824](https://github.com/google/ExoPlayer/issues/2824)). +* Misc bugfixes. + ### r2.4.0 ### * New modular library structure. You can read more about depending on individual diff --git a/build.gradle b/build.gradle index da75def90b..258b11d2e6 100644 --- a/build.gradle +++ b/build.gradle @@ -48,7 +48,7 @@ allprojects { releaseRepoName = getBintrayRepo() releaseUserOrg = 'google' releaseGroupId = 'com.google.android.exoplayer' - releaseVersion = 'r2.4.0' + releaseVersion = 'r2.4.1' releaseWebsite = 'https://github.com/google/ExoPlayer' } if (it.hasProperty('externalBuildDir')) { diff --git a/demo/src/main/AndroidManifest.xml b/demo/src/main/AndroidManifest.xml index 6580e687cc..1bb859028d 100644 --- a/demo/src/main/AndroidManifest.xml +++ b/demo/src/main/AndroidManifest.xml @@ -16,8 +16,8 @@ + android:versionCode="2401" + android:versionName="2.4.1"> diff --git a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerLibraryInfo.java b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerLibraryInfo.java index 13cf35d449..23c2ddbde9 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerLibraryInfo.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerLibraryInfo.java @@ -24,13 +24,13 @@ public interface ExoPlayerLibraryInfo { * The version of the library expressed as a string, for example "1.2.3". */ // Intentionally hardcoded. Do not derive from other constants (e.g. VERSION_INT) or vice versa. - String VERSION = "2.4.0"; + String VERSION = "2.4.1"; /** * The version of the library expressed as {@code "ExoPlayerLib/" + VERSION}. */ // Intentionally hardcoded. Do not derive from other constants (e.g. VERSION) or vice versa. - String VERSION_SLASHY = "ExoPlayerLib/2.4.0"; + String VERSION_SLASHY = "ExoPlayerLib/2.4.1"; /** * The version of the library expressed as an integer, for example 1002003. @@ -40,7 +40,7 @@ public interface ExoPlayerLibraryInfo { * integer version 123045006 (123-045-006). */ // Intentionally hardcoded. Do not derive from other constants (e.g. VERSION) or vice versa. - int VERSION_INT = 2004000; + int VERSION_INT = 2004001; /** * Whether the library was compiled with {@link com.google.android.exoplayer2.util.Assertions}