Commit c2cbb637 by Michael Katz Committed by GitHub

Merge pull request #210 from androidx/release-1.0.0-beta03

1.0.0-beta03
parents 50475814 6661b757
Showing with 2118 additions and 300 deletions
...@@ -17,6 +17,7 @@ body: ...@@ -17,6 +17,7 @@ body:
label: Media3 Version label: Media3 Version
description: What version of Media3 are you using? description: What version of Media3 are you using?
options: options:
- 1.0.0-beta03
- 1.0.0-beta02 - 1.0.0-beta02
- 1.0.0-beta01 - 1.0.0-beta01
- 1.0.0-alpha03 - 1.0.0-alpha03
......
...@@ -76,3 +76,6 @@ extensions/cronet/jniLibs/* ...@@ -76,3 +76,6 @@ extensions/cronet/jniLibs/*
!extensions/cronet/jniLibs/README.md !extensions/cronet/jniLibs/README.md
extensions/cronet/libs/* extensions/cronet/libs/*
!extensions/cronet/libs/README.md !extensions/cronet/libs/README.md
# MIDI extension
extensions/midi/lib
...@@ -21,7 +21,7 @@ all of the information requested in the issue template. ...@@ -21,7 +21,7 @@ all of the information requested in the issue template.
## Pull requests ## Pull requests
We will also consider high quality pull requests. These should normally merge We will also consider high quality pull requests. These should merge
into the `main` branch. Before a pull request can be accepted you must submit into the `main` branch. Before a pull request can be accepted you must submit
a Contributor License Agreement, as described below. a Contributor License Agreement, as described below.
......
Release notes Release notes
### 1.0.0-beta03 (2022-11-22)
This release corresponds to the
[ExoPlayer 2.18.2 release](https://github.com/google/ExoPlayer/releases/tag/r2.18.2).
* Core library:
* Add `ExoPlayer.isTunnelingEnabled` to check if tunneling is enabled for
the currently selected tracks
([#2518](https://github.com/google/ExoPlayer/issues/2518)).
* Add `WrappingMediaSource` to simplify wrapping a single `MediaSource`
([#7279](https://github.com/google/ExoPlayer/issues/7279)).
* Discard back buffer before playback gets stuck due to insufficient
available memory.
* Close the Tracing "doSomeWork" block when offload is enabled.
* Fix session tracking problem with fast seeks in `PlaybackStatsListener`
([#180](https://github.com/androidx/media/issues/180)).
* Send missing `onMediaItemTransition` callback when calling `seekToNext`
or `seekToPrevious` in a single-item playlist
([#10667](https://github.com/google/ExoPlayer/issues/10667)).
* Add `Player.getSurfaceSize` that returns the size of the surface on
which the video is rendered.
* Fix bug where removing listeners during the player release can cause an
`IllegalStateException`
([#10758](https://github.com/google/ExoPlayer/issues/10758)).
* Build:
* Enforce minimum `compileSdkVersion` to avoid compilation errors
([#10684](https://github.com/google/ExoPlayer/issues/10684)).
* Avoid publishing block when included in another gradle build.
* Track selection:
* Prefer other tracks to Dolby Vision if display does not support it.
([#8944](https://github.com/google/ExoPlayer/issues/8944)).
* Downloads:
* Fix potential infinite loop in `ProgressiveDownloader` caused by
simultaneous download and playback with the same `PriorityTaskManager`
([#10570](https://github.com/google/ExoPlayer/pull/10570)).
* Make download notification appear immediately
([#183](https://github.com/androidx/media/pull/183)).
* Limit parallel download removals to 1 to avoid excessive thread creation
([#10458](https://github.com/google/ExoPlayer/issues/10458)).
* Video:
* Try alternative decoder for Dolby Vision if display does not support it.
([#9794](https://github.com/google/ExoPlayer/issues/9794)).
* Audio:
* Use `SingleThreadExecutor` for releasing `AudioTrack` instances to avoid
OutOfMemory errors when releasing multiple players at the same time
([#10057](https://github.com/google/ExoPlayer/issues/10057)).
* Adds `AudioOffloadListener.onExperimentalOffloadedPlayback` for the
AudioTrack offload state.
([#134](https://github.com/androidx/media/issues/134)).
* Make `AudioTrackBufferSizeProvider` a public interface.
* Add `ExoPlayer.setPreferredAudioDevice` to set the preferred audio
output device ([#135](https://github.com/androidx/media/issues/135)).
* Rename `androidx.media3.exoplayer.audio.AudioProcessor` to
`androidx.media3.common.audio.AudioProcessor`.
* Map 8-channel and 12-channel audio to the 7.1 and 7.1.4 channel masks
respectively on all Android versions
([#10701](https://github.com/google/ExoPlayer/issues/10701)).
* Metadata:
* `MetadataRenderer` can now be configured to render metadata as soon as
they are available. Create an instance with
`MetadataRenderer(MetadataOutput, Looper, MetadataDecoderFactory,
boolean)` to specify whether the renderer will output metadata early or
in sync with the player position.
* DRM:
* Work around a bug in the Android 13 ClearKey implementation that returns
a non-empty but invalid license URL.
* Fix `setMediaDrmSession failed: session not opened` error when switching
between DRM schemes in a playlist (e.g. Widevine to ClearKey).
* Text:
* CEA-608: Ensure service switch commands on field 2 are handled correctly
([#10666](https://github.com/google/ExoPlayer/issues/10666)).
* DASH:
* Parse `EventStream.presentationTimeOffset` from manifests
([#10460](https://github.com/google/ExoPlayer/issues/10460)).
* UI:
* Use current overrides of the player as preset in
`TrackSelectionDialogBuilder`
([#10429](https://github.com/google/ExoPlayer/issues/10429)).
* Session:
* Ensure commands are always executed in the correct order even if some
require asynchronous resolution
([#85](https://github.com/androidx/media/issues/85)).
* Add `DefaultMediaNotificationProvider.Builder` to build
`DefaultMediaNotificationProvider` instances. The builder can configure
the notification ID, the notification channel ID and the notification
channel name used by the provider. Also, add method
`DefaultMediaNotificationProvider.setSmallIcon(int)` to set the
notifications small icon.
([#104](https://github.com/androidx/media/issues/104)).
* Ensure commands sent before `MediaController.release()` are not dropped
([#99](https://github.com/androidx/media/issues/99)).
* `SimpleBitmapLoader` can load bitmap from `file://` URIs
([#108](https://github.com/androidx/media/issues/108)).
* Fix assertion that prevents `MediaController` to seek over an ad in a
period ([#122](https://github.com/androidx/media/issues/122)).
* When playback ends, the `MediaSessionService` is stopped from the
foreground and a notification is shown to restart playback of the last
played media item
([#112](https://github.com/androidx/media/issues/112)).
* Don't start a foreground service with a pending intent for pause
([#167](https://github.com/androidx/media/issues/167)).
* Manually hide the 'badge' associated with the notification created by
`DefaultNotificationProvider` on API 26 and API 27 (the badge is
automatically hidden on API 28+)
([#131](https://github.com/androidx/media/issues/131)).
* Fix bug where a second binder connection from a legacy MediaSession to a
Media3 MediaController causes IllegalStateExceptions
([#49](https://github.com/androidx/media/issues/49)).
* RTSP:
* Add H263 fragmented packet handling
([#119](https://github.com/androidx/media/pull/119)).
* Add support for MP4A-LATM
([#162](https://github.com/androidx/media/pull/162)).
* IMA:
* Add timeout for loading ad information to handle cases where the IMA SDK
gets stuck loading an ad
([#10510](https://github.com/google/ExoPlayer/issues/10510)).
* Prevent skipping mid-roll ads when seeking to the end of the content
([#10685](https://github.com/google/ExoPlayer/issues/10685)).
* Correctly calculate window duration for live streams with server-side
inserted ads, for example IMA DAI
([#10764](https://github.com/google/ExoPlayer/issues/10764)).
* FFmpeg extension:
* Add newly required flags to link FFmpeg libraries with NDK 23.1.7779620
and above ([#9933](https://github.com/google/ExoPlayer/issues/9933)).
* AV1 extension:
* Update CMake version to avoid incompatibilities with the latest Android
Studio releases
([#9933](https://github.com/google/ExoPlayer/issues/9933)).
* Cast extension:
* Implement `getDeviceInfo()` to be able to identify `CastPlayer` when
controlling playback with a `MediaController`
([#142](https://github.com/androidx/media/issues/142)).
* Transformer:
* Add muxer watchdog timer to detect when generating an output sample is
too slow.
* Remove deprecated symbols:
* Remove `Transformer.Builder.setOutputMimeType(String)`. This feature has
been removed. The MIME type will always be MP4 when the default muxer is
used.
### 1.0.0-beta02 (2022-07-21) ### 1.0.0-beta02 (2022-07-21)
...@@ -32,6 +173,8 @@ This release corresponds to the ...@@ -32,6 +173,8 @@ This release corresponds to the
* RTSP: * RTSP:
* Add VP8 fragmented packet handling * Add VP8 fragmented packet handling
([#110](https://github.com/androidx/media/pull/110)). ([#110](https://github.com/androidx/media/pull/110)).
* Support frames/fragments in VP9
([#115](https://github.com/androidx/media/pull/115)).
* Leanback extension: * Leanback extension:
* Listen to `playWhenReady` changes in `LeanbackAdapter` * Listen to `playWhenReady` changes in `LeanbackAdapter`
([10420](https://github.com/google/ExoPlayer/issues/10420)). ([10420](https://github.com/google/ExoPlayer/issues/10420)).
...@@ -266,6 +409,8 @@ This release corresponds to the ...@@ -266,6 +409,8 @@ This release corresponds to the
`DEFAULT_TRACK_SELECTOR_PARAMETERS_WITHOUT_CONTEXT` otherwise. `DEFAULT_TRACK_SELECTOR_PARAMETERS_WITHOUT_CONTEXT` otherwise.
* Remove constructor `DefaultTrackSelector(ExoTrackSelection.Factory)`. * Remove constructor `DefaultTrackSelector(ExoTrackSelection.Factory)`.
Use `DefaultTrackSelector(Context, ExoTrackSelection.Factory)` instead. Use `DefaultTrackSelector(Context, ExoTrackSelection.Factory)` instead.
* Remove `Transformer.Builder.setContext`. The `Context` should be passed
to the `Transformer.Builder` constructor instead.
### 1.0.0-alpha03 (2022-03-14) ### 1.0.0-alpha03 (2022-03-14)
......
...@@ -22,6 +22,9 @@ android { ...@@ -22,6 +22,9 @@ android {
targetSdkVersion project.ext.targetSdkVersion targetSdkVersion project.ext.targetSdkVersion
consumerProguardFiles 'proguard-rules.txt' consumerProguardFiles 'proguard-rules.txt'
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner' testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
aarMetadata {
minCompileSdk = project.ext.compileSdkVersion
}
} }
compileOptions { compileOptions {
......
...@@ -12,15 +12,18 @@ ...@@ -12,15 +12,18 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
project.ext { project.ext {
releaseVersion = '1.0.0-beta02' releaseVersion = '1.0.0-beta03'
releaseVersionCode = 1_000_000_1_02 releaseVersionCode = 1_000_000_1_03
minSdkVersion = 16 minSdkVersion = 16
appTargetSdkVersion = 29 appTargetSdkVersion = 33
// API version before restricting local file access.
// https://developer.android.com/training/data-storage/app-specific
mainDemoAppTargetSdkVersion = 29
// Upgrading this requires [Internal ref: b/193254928] to be fixed, or some // Upgrading this requires [Internal ref: b/193254928] to be fixed, or some
// additional robolectric config. // additional robolectric config.
targetSdkVersion = 30 targetSdkVersion = 30
compileSdkVersion = 32 compileSdkVersion = 33
dexmakerVersion = '2.28.1' dexmakerVersion = '2.28.3'
junitVersion = '4.13.2' junitVersion = '4.13.2'
// Use the same Guava version as the Android repo: // Use the same Guava version as the Android repo:
// https://cs.android.com/android/platform/superproject/+/master:external/guava/METADATA // https://cs.android.com/android/platform/superproject/+/master:external/guava/METADATA
...@@ -40,7 +43,7 @@ project.ext { ...@@ -40,7 +43,7 @@ project.ext {
androidxConstraintLayoutVersion = '2.0.4' androidxConstraintLayoutVersion = '2.0.4'
androidxCoreVersion = '1.7.0' androidxCoreVersion = '1.7.0'
androidxFuturesVersion = '1.1.0' androidxFuturesVersion = '1.1.0'
androidxMediaVersion = '1.4.3' androidxMediaVersion = '1.6.0'
androidxMedia2Version = '1.2.0' androidxMedia2Version = '1.2.0'
androidxMultidexVersion = '2.0.1' androidxMultidexVersion = '2.0.1'
androidxRecyclerViewVersion = '1.2.1' androidxRecyclerViewVersion = '1.2.1'
......
...@@ -78,6 +78,9 @@ project(modulePrefix + 'lib-extractor').projectDir = new File(rootDir, 'librarie ...@@ -78,6 +78,9 @@ project(modulePrefix + 'lib-extractor').projectDir = new File(rootDir, 'librarie
include modulePrefix + 'lib-cast' include modulePrefix + 'lib-cast'
project(modulePrefix + 'lib-cast').projectDir = new File(rootDir, 'libraries/cast') project(modulePrefix + 'lib-cast').projectDir = new File(rootDir, 'libraries/cast')
include modulePrefix + 'lib-effect'
project(modulePrefix + 'lib-effect').projectDir = new File(rootDir, 'libraries/effect')
include modulePrefix + 'lib-transformer' include modulePrefix + 'lib-transformer'
project(modulePrefix + 'lib-transformer').projectDir = new File(rootDir, 'libraries/transformer') project(modulePrefix + 'lib-transformer').projectDir = new File(rootDir, 'libraries/transformer')
......
...@@ -22,8 +22,13 @@ ...@@ -22,8 +22,13 @@
<uses-sdk/> <uses-sdk/>
<application android:label="@string/application_name" android:icon="@mipmap/ic_launcher" <application
android:largeHeap="true" android:allowBackup="false"> android:name="androidx.multidex.MultiDexApplication"
android:label="@string/application_name"
android:icon="@mipmap/ic_launcher"
android:largeHeap="true"
android:allowBackup="false"
android:taskAffinity="">
<meta-data android:name="com.google.android.gms.cast.framework.OPTIONS_PROVIDER_CLASS_NAME" <meta-data android:name="com.google.android.gms.cast.framework.OPTIONS_PROVIDER_CLASS_NAME"
android:value="androidx.media3.cast.DefaultCastOptionsProvider"/> android:value="androidx.media3.cast.DefaultCastOptionsProvider"/>
......
...@@ -52,6 +52,7 @@ dependencies { ...@@ -52,6 +52,7 @@ dependencies {
implementation project(modulePrefix + 'lib-exoplayer-smoothstreaming') implementation project(modulePrefix + 'lib-exoplayer-smoothstreaming')
implementation project(modulePrefix + 'lib-ui') implementation project(modulePrefix + 'lib-ui')
implementation 'androidx.annotation:annotation:' + androidxAnnotationVersion implementation 'androidx.annotation:annotation:' + androidxAnnotationVersion
implementation 'androidx.multidex:multidex:' + androidxMultidexVersion
compileOnly 'org.checkerframework:checker-qual:' + checkerframeworkVersion compileOnly 'org.checkerframework:checker-qual:' + checkerframeworkVersion
compileOnly 'org.checkerframework:checker-compat-qual:' + checkerframeworkCompatVersion compileOnly 'org.checkerframework:checker-compat-qual:' + checkerframeworkCompatVersion
} }
...@@ -22,6 +22,7 @@ ...@@ -22,6 +22,7 @@
<uses-sdk/> <uses-sdk/>
<application <application
android:name="androidx.multidex.MultiDexApplication"
android:allowBackup="false" android:allowBackup="false"
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"
android:label="@string/application_name"> android:label="@string/application_name">
......
...@@ -29,6 +29,7 @@ import android.opengl.GLUtils; ...@@ -29,6 +29,7 @@ import android.opengl.GLUtils;
import androidx.media3.common.C; import androidx.media3.common.C;
import androidx.media3.common.util.GlProgram; import androidx.media3.common.util.GlProgram;
import androidx.media3.common.util.GlUtil; import androidx.media3.common.util.GlUtil;
import androidx.media3.common.util.Log;
import java.io.IOException; import java.io.IOException;
import java.util.Locale; import java.util.Locale;
import javax.microedition.khronos.opengles.GL10; import javax.microedition.khronos.opengles.GL10;
...@@ -41,6 +42,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; ...@@ -41,6 +42,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
/* package */ final class BitmapOverlayVideoProcessor /* package */ final class BitmapOverlayVideoProcessor
implements VideoProcessingGLSurfaceView.VideoProcessor { implements VideoProcessingGLSurfaceView.VideoProcessor {
private static final String TAG = "BitmapOverlayVP";
private static final int OVERLAY_WIDTH = 512; private static final int OVERLAY_WIDTH = 512;
private static final int OVERLAY_HEIGHT = 256; private static final int OVERLAY_HEIGHT = 256;
...@@ -85,6 +87,9 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; ...@@ -85,6 +87,9 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
/* fragmentShaderFilePath= */ "bitmap_overlay_video_processor_fragment.glsl"); /* fragmentShaderFilePath= */ "bitmap_overlay_video_processor_fragment.glsl");
} catch (IOException e) { } catch (IOException e) {
throw new IllegalStateException(e); throw new IllegalStateException(e);
} catch (GlUtil.GlException e) {
Log.e(TAG, "Failed to initialize the shader program", e);
return;
} }
program.setBufferAttribute( program.setBufferAttribute(
"aFramePosition", "aFramePosition",
...@@ -119,7 +124,11 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; ...@@ -119,7 +124,11 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
GLES20.glBindTexture(GL10.GL_TEXTURE_2D, textures[0]); GLES20.glBindTexture(GL10.GL_TEXTURE_2D, textures[0]);
GLUtils.texSubImage2D( GLUtils.texSubImage2D(
GL10.GL_TEXTURE_2D, /* level= */ 0, /* xoffset= */ 0, /* yoffset= */ 0, overlayBitmap); GL10.GL_TEXTURE_2D, /* level= */ 0, /* xoffset= */ 0, /* yoffset= */ 0, overlayBitmap);
GlUtil.checkGlError(); try {
GlUtil.checkGlError();
} catch (GlUtil.GlException e) {
Log.e(TAG, "Failed to populate the texture", e);
}
// Run the shader program. // Run the shader program.
GlProgram program = checkNotNull(this.program); GlProgram program = checkNotNull(this.program);
...@@ -128,16 +137,28 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; ...@@ -128,16 +137,28 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
program.setFloatUniform("uScaleX", bitmapScaleX); program.setFloatUniform("uScaleX", bitmapScaleX);
program.setFloatUniform("uScaleY", bitmapScaleY); program.setFloatUniform("uScaleY", bitmapScaleY);
program.setFloatsUniform("uTexTransform", transformMatrix); program.setFloatsUniform("uTexTransform", transformMatrix);
program.bindAttributesAndUniforms(); try {
program.bindAttributesAndUniforms();
} catch (GlUtil.GlException e) {
Log.e(TAG, "Failed to update the shader program", e);
}
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT); GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, /* first= */ 0, /* count= */ 4); GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, /* first= */ 0, /* count= */ 4);
GlUtil.checkGlError(); try {
GlUtil.checkGlError();
} catch (GlUtil.GlException e) {
Log.e(TAG, "Failed to draw a frame", e);
}
} }
@Override @Override
public void release() { public void release() {
if (program != null) { if (program != null) {
program.delete(); try {
program.delete();
} catch (GlUtil.GlException e) {
Log.e(TAG, "Failed to delete the shader program", e);
}
} }
} }
} }
...@@ -15,6 +15,8 @@ ...@@ -15,6 +15,8 @@
*/ */
package androidx.media3.demo.gl; package androidx.media3.demo.gl;
import static androidx.media3.common.util.Assertions.checkNotNull;
import android.app.Activity; import android.app.Activity;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
...@@ -83,7 +85,8 @@ public final class MainActivity extends Activity { ...@@ -83,7 +85,8 @@ public final class MainActivity extends Activity {
VideoProcessingGLSurfaceView videoProcessingGLSurfaceView = VideoProcessingGLSurfaceView videoProcessingGLSurfaceView =
new VideoProcessingGLSurfaceView( new VideoProcessingGLSurfaceView(
context, requestSecureSurface, new BitmapOverlayVideoProcessor(context)); context, requestSecureSurface, new BitmapOverlayVideoProcessor(context));
FrameLayout contentFrame = findViewById(R.id.exo_content_frame); checkNotNull(playerView);
FrameLayout contentFrame = playerView.findViewById(R.id.exo_content_frame);
contentFrame.addView(videoProcessingGLSurfaceView); contentFrame.addView(videoProcessingGLSurfaceView);
this.videoProcessingGLSurfaceView = videoProcessingGLSurfaceView; this.videoProcessingGLSurfaceView = videoProcessingGLSurfaceView;
} }
......
...@@ -28,6 +28,7 @@ import androidx.media3.common.C; ...@@ -28,6 +28,7 @@ import androidx.media3.common.C;
import androidx.media3.common.Format; import androidx.media3.common.Format;
import androidx.media3.common.util.Assertions; import androidx.media3.common.util.Assertions;
import androidx.media3.common.util.GlUtil; import androidx.media3.common.util.GlUtil;
import androidx.media3.common.util.Log;
import androidx.media3.common.util.TimedValueQueue; import androidx.media3.common.util.TimedValueQueue;
import androidx.media3.exoplayer.ExoPlayer; import androidx.media3.exoplayer.ExoPlayer;
import androidx.media3.exoplayer.video.VideoFrameMetadataListener; import androidx.media3.exoplayer.video.VideoFrameMetadataListener;
...@@ -70,6 +71,7 @@ public final class VideoProcessingGLSurfaceView extends GLSurfaceView { ...@@ -70,6 +71,7 @@ public final class VideoProcessingGLSurfaceView extends GLSurfaceView {
} }
private static final int EGL_PROTECTED_CONTENT_EXT = 0x32C0; private static final int EGL_PROTECTED_CONTENT_EXT = 0x32C0;
private static final String TAG = "VPGlSurfaceView";
private final VideoRenderer renderer; private final VideoRenderer renderer;
private final Handler mainHandler; private final Handler mainHandler;
...@@ -239,7 +241,11 @@ public final class VideoProcessingGLSurfaceView extends GLSurfaceView { ...@@ -239,7 +241,11 @@ public final class VideoProcessingGLSurfaceView extends GLSurfaceView {
@Override @Override
public synchronized void onSurfaceCreated(GL10 gl, EGLConfig config) { public synchronized void onSurfaceCreated(GL10 gl, EGLConfig config) {
texture = GlUtil.createExternalTexture(); try {
texture = GlUtil.createExternalTexture();
} catch (GlUtil.GlException e) {
Log.e(TAG, "Failed to create an external texture", e);
}
surfaceTexture = new SurfaceTexture(texture); surfaceTexture = new SurfaceTexture(texture);
surfaceTexture.setOnFrameAvailableListener( surfaceTexture.setOnFrameAvailableListener(
surfaceTexture -> { surfaceTexture -> {
......
...@@ -11,6 +11,7 @@ ...@@ -11,6 +11,7 @@
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
apply from: '../../constants.gradle' apply from: '../../constants.gradle'
apply plugin: 'com.android.application' apply plugin: 'com.android.application'
...@@ -26,7 +27,9 @@ android { ...@@ -26,7 +27,9 @@ android {
versionName project.ext.releaseVersion versionName project.ext.releaseVersion
versionCode project.ext.releaseVersionCode versionCode project.ext.releaseVersionCode
minSdkVersion project.ext.minSdkVersion minSdkVersion project.ext.minSdkVersion
targetSdkVersion project.ext.appTargetSdkVersion // Not using appTargetSDKVersion to allow local file access on API 29
// and higher [Internal ref: b/191644662]
targetSdkVersion project.ext.mainDemoAppTargetSdkVersion
multiDexEnabled true multiDexEnabled true
} }
......
...@@ -399,7 +399,7 @@ ...@@ -399,7 +399,7 @@
"uri": "ssai://dai.google.com/?contentSourceId=2528370&videoId=tears-of-steel&format=2&adsId=1" "uri": "ssai://dai.google.com/?contentSourceId=2528370&videoId=tears-of-steel&format=2&adsId=1"
}, },
{ {
"name": "HLS Live: Big Buck Bunny (mid), 3 ads each [10 s]", "name": "HLS Live: Big Buck Bunny (mid), 3 ads [10/10/10s]",
"uri": "ssai://dai.google.com/?assetKey=sN_IYUG8STe1ZzhIIE_ksA&format=2&adsId=3" "uri": "ssai://dai.google.com/?assetKey=sN_IYUG8STe1ZzhIIE_ksA&format=2&adsId=3"
}, },
{ {
......
...@@ -14,6 +14,7 @@ ...@@ -14,6 +14,7 @@
limitations under the License. limitations under the License.
--> -->
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="androidx.media3.demo.session"> package="androidx.media3.demo.session">
<uses-sdk/> <uses-sdk/>
...@@ -21,10 +22,12 @@ ...@@ -21,10 +22,12 @@
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<application <application
android:name="androidx.multidex.MultiDexApplication"
android:allowBackup="false" android:allowBackup="false"
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"
android:label="@string/app_name" android:label="@string/app_name"
android:theme="@style/Theme.Media3Demo"> android:theme="@style/Theme.Media3Demo"
tools:replace="android:name">
<activity <activity
android:name=".MainActivity" android:name=".MainActivity"
......
...@@ -13,10 +13,25 @@ ...@@ -13,10 +13,25 @@
"id": "video_02", "id": "video_02",
"title": "TTML Netflix Japanese examples (IMSC1.1)", "title": "TTML Netflix Japanese examples (IMSC1.1)",
"source": "https://storage.googleapis.com/exoplayer-test-media-1/gen-3/screens/dash-vod-single-segment/video-avc-baseline-480.mp4", "source": "https://storage.googleapis.com/exoplayer-test-media-1/gen-3/screens/dash-vod-single-segment/video-avc-baseline-480.mp4",
"subtitle_uri": "https://storage.googleapis.com/exoplayer-test-media-1/ttml/netflix_japanese_ttml.xml",
"album": "Video with subtitle", "album": "Video with subtitle",
"artist": "Netflix", "artist": "Subtitles",
"genre": "Video", "genre": "Video",
"image": "https://cdn.pixabay.com/photo/2014/10/09/13/14/video-481821_960_720.png",
"subtitles": [
{
"subtitle_uri": "https://storage.googleapis.com/exoplayer-test-media-1/ttml/netflix_japanese_ttml.xml",
"subtitle_mime_type": "application/ttml+xml",
"subtitle_lang": "ja"
}
]
},
{
"id": "video_03",
"title": "MPEG-4 Timed Text",
"album": "Video with subtitle",
"artist": "Subtitles",
"genre": "Video",
"source": "https://storage.googleapis.com/exoplayer-test-media-1/mp4/dizzy-with-tx3g.mp4",
"image": "https://cdn.pixabay.com/photo/2014/10/09/13/14/video-481821_960_720.png" "image": "https://cdn.pixabay.com/photo/2014/10/09/13/14/video-481821_960_720.png"
}, },
{ {
......
...@@ -34,7 +34,6 @@ import androidx.media3.session.MediaBrowser ...@@ -34,7 +34,6 @@ import androidx.media3.session.MediaBrowser
import androidx.media3.session.SessionToken import androidx.media3.session.SessionToken
import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
import com.google.common.util.concurrent.ListenableFuture import com.google.common.util.concurrent.ListenableFuture
import com.google.common.util.concurrent.MoreExecutors
class MainActivity : AppCompatActivity() { class MainActivity : AppCompatActivity() {
private lateinit var browserFuture: ListenableFuture<MediaBrowser> private lateinit var browserFuture: ListenableFuture<MediaBrowser>
...@@ -105,7 +104,7 @@ class MainActivity : AppCompatActivity() { ...@@ -105,7 +104,7 @@ class MainActivity : AppCompatActivity() {
SessionToken(this, ComponentName(this, PlaybackService::class.java)) SessionToken(this, ComponentName(this, PlaybackService::class.java))
) )
.buildAsync() .buildAsync()
browserFuture.addListener({ pushRoot() }, MoreExecutors.directExecutor()) browserFuture.addListener({ pushRoot() }, ContextCompat.getMainExecutor(this))
} }
private fun releaseBrowser() { private fun releaseBrowser() {
...@@ -132,7 +131,7 @@ class MainActivity : AppCompatActivity() { ...@@ -132,7 +131,7 @@ class MainActivity : AppCompatActivity() {
subItemMediaList.addAll(children) subItemMediaList.addAll(children)
mediaListAdapter.notifyDataSetChanged() mediaListAdapter.notifyDataSetChanged()
}, },
MoreExecutors.directExecutor() ContextCompat.getMainExecutor(this)
) )
} }
......
...@@ -18,10 +18,13 @@ package androidx.media3.demo.session ...@@ -18,10 +18,13 @@ package androidx.media3.demo.session
import android.content.res.AssetManager import android.content.res.AssetManager
import android.net.Uri import android.net.Uri
import androidx.media3.common.MediaItem import androidx.media3.common.MediaItem
import androidx.media3.common.MediaItem.SubtitleConfiguration
import androidx.media3.common.MediaMetadata import androidx.media3.common.MediaMetadata
import androidx.media3.common.MediaMetadata.FOLDER_TYPE_ALBUMS
import androidx.media3.common.MediaMetadata.FOLDER_TYPE_ARTISTS
import androidx.media3.common.MediaMetadata.FOLDER_TYPE_GENRES
import androidx.media3.common.MediaMetadata.FOLDER_TYPE_MIXED import androidx.media3.common.MediaMetadata.FOLDER_TYPE_MIXED
import androidx.media3.common.MediaMetadata.FOLDER_TYPE_NONE import androidx.media3.common.MediaMetadata.FOLDER_TYPE_NONE
import androidx.media3.common.MediaMetadata.FOLDER_TYPE_PLAYLISTS
import androidx.media3.common.util.Util import androidx.media3.common.util.Util
import com.google.common.collect.ImmutableList import com.google.common.collect.ImmutableList
import org.json.JSONObject import org.json.JSONObject
...@@ -65,13 +68,13 @@ object MediaItemTree { ...@@ -65,13 +68,13 @@ object MediaItemTree {
mediaId: String, mediaId: String,
isPlayable: Boolean, isPlayable: Boolean,
@MediaMetadata.FolderType folderType: Int, @MediaMetadata.FolderType folderType: Int,
subtitleConfigurations: List<SubtitleConfiguration> = mutableListOf(),
album: String? = null, album: String? = null,
artist: String? = null, artist: String? = null,
genre: String? = null, genre: String? = null,
sourceUri: Uri? = null, sourceUri: Uri? = null,
imageUri: Uri? = null, imageUri: Uri? = null
): MediaItem { ): MediaItem {
// TODO(b/194280027): add artwork
val metadata = val metadata =
MediaMetadata.Builder() MediaMetadata.Builder()
.setAlbumTitle(album) .setAlbumTitle(album)
...@@ -82,8 +85,10 @@ object MediaItemTree { ...@@ -82,8 +85,10 @@ object MediaItemTree {
.setIsPlayable(isPlayable) .setIsPlayable(isPlayable)
.setArtworkUri(imageUri) .setArtworkUri(imageUri)
.build() .build()
return MediaItem.Builder() return MediaItem.Builder()
.setMediaId(mediaId) .setMediaId(mediaId)
.setSubtitleConfigurations(subtitleConfigurations)
.setMediaMetadata(metadata) .setMediaMetadata(metadata)
.setUri(sourceUri) .setUri(sourceUri)
.build() .build()
...@@ -156,6 +161,19 @@ object MediaItemTree { ...@@ -156,6 +161,19 @@ object MediaItemTree {
val title = mediaObject.getString("title") val title = mediaObject.getString("title")
val artist = mediaObject.getString("artist") val artist = mediaObject.getString("artist")
val genre = mediaObject.getString("genre") val genre = mediaObject.getString("genre")
val subtitleConfigurations: MutableList<SubtitleConfiguration> = mutableListOf()
if (mediaObject.has("subtitles")) {
val subtitlesJson = mediaObject.getJSONArray("subtitles")
for (i in 0 until subtitlesJson.length()) {
val subtitleObject = subtitlesJson.getJSONObject(i)
subtitleConfigurations.add(
SubtitleConfiguration.Builder(Uri.parse(subtitleObject.getString("subtitle_uri")))
.setMimeType(subtitleObject.getString("subtitle_mime_type"))
.setLanguage(subtitleObject.getString("subtitle_lang"))
.build()
)
}
}
val sourceUri = Uri.parse(mediaObject.getString("source")) val sourceUri = Uri.parse(mediaObject.getString("source"))
val imageUri = Uri.parse(mediaObject.getString("image")) val imageUri = Uri.parse(mediaObject.getString("image"))
// key of such items in tree // key of such items in tree
...@@ -170,12 +188,13 @@ object MediaItemTree { ...@@ -170,12 +188,13 @@ object MediaItemTree {
title = title, title = title,
mediaId = idInTree, mediaId = idInTree,
isPlayable = true, isPlayable = true,
folderType = FOLDER_TYPE_NONE,
subtitleConfigurations,
album = album, album = album,
artist = artist, artist = artist,
genre = genre, genre = genre,
sourceUri = sourceUri, sourceUri = sourceUri,
imageUri = imageUri, imageUri = imageUri
folderType = FOLDER_TYPE_NONE
) )
) )
...@@ -188,7 +207,8 @@ object MediaItemTree { ...@@ -188,7 +207,8 @@ object MediaItemTree {
title = album, title = album,
mediaId = albumFolderIdInTree, mediaId = albumFolderIdInTree,
isPlayable = true, isPlayable = true,
folderType = FOLDER_TYPE_PLAYLISTS folderType = FOLDER_TYPE_ALBUMS,
subtitleConfigurations
) )
) )
treeNodes[ALBUM_ID]!!.addChild(albumFolderIdInTree) treeNodes[ALBUM_ID]!!.addChild(albumFolderIdInTree)
...@@ -203,7 +223,8 @@ object MediaItemTree { ...@@ -203,7 +223,8 @@ object MediaItemTree {
title = artist, title = artist,
mediaId = artistFolderIdInTree, mediaId = artistFolderIdInTree,
isPlayable = true, isPlayable = true,
folderType = FOLDER_TYPE_PLAYLISTS folderType = FOLDER_TYPE_ARTISTS,
subtitleConfigurations
) )
) )
treeNodes[ARTIST_ID]!!.addChild(artistFolderIdInTree) treeNodes[ARTIST_ID]!!.addChild(artistFolderIdInTree)
...@@ -218,7 +239,8 @@ object MediaItemTree { ...@@ -218,7 +239,8 @@ object MediaItemTree {
title = genre, title = genre,
mediaId = genreFolderIdInTree, mediaId = genreFolderIdInTree,
isPlayable = true, isPlayable = true,
folderType = FOLDER_TYPE_PLAYLISTS folderType = FOLDER_TYPE_GENRES,
subtitleConfigurations
) )
) )
treeNodes[GENRE_ID]!!.addChild(genreFolderIdInTree) treeNodes[GENRE_ID]!!.addChild(genreFolderIdInTree)
......
...@@ -30,6 +30,7 @@ import android.widget.ListView ...@@ -30,6 +30,7 @@ import android.widget.ListView
import android.widget.TextView import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.media3.common.C
import androidx.media3.common.MediaItem import androidx.media3.common.MediaItem
import androidx.media3.common.Player import androidx.media3.common.Player
import androidx.media3.session.MediaBrowser import androidx.media3.session.MediaBrowser
...@@ -38,7 +39,6 @@ import com.google.android.material.floatingactionbutton.ExtendedFloatingActionBu ...@@ -38,7 +39,6 @@ import com.google.android.material.floatingactionbutton.ExtendedFloatingActionBu
import com.google.android.material.snackbar.BaseTransientBottomBar import com.google.android.material.snackbar.BaseTransientBottomBar
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import com.google.common.util.concurrent.ListenableFuture import com.google.common.util.concurrent.ListenableFuture
import com.google.common.util.concurrent.MoreExecutors
class PlayableFolderActivity : AppCompatActivity() { class PlayableFolderActivity : AppCompatActivity() {
private lateinit var browserFuture: ListenableFuture<MediaBrowser> private lateinit var browserFuture: ListenableFuture<MediaBrowser>
...@@ -69,10 +69,13 @@ class PlayableFolderActivity : AppCompatActivity() { ...@@ -69,10 +69,13 @@ class PlayableFolderActivity : AppCompatActivity() {
mediaList.setOnItemClickListener { _, _, position, _ -> mediaList.setOnItemClickListener { _, _, position, _ ->
run { run {
val browser = this.browser ?: return@run val browser = this.browser ?: return@run
browser.setMediaItems(subItemMediaList) browser.setMediaItems(
subItemMediaList,
/* startIndex= */ position,
/* startPositionMs= */ C.TIME_UNSET
)
browser.shuffleModeEnabled = false browser.shuffleModeEnabled = false
browser.prepare() browser.prepare()
browser.seekToDefaultPosition(/* windowIndex= */ position)
browser.play() browser.play()
val intent = Intent(this, PlayerActivity::class.java) val intent = Intent(this, PlayerActivity::class.java)
startActivity(intent) startActivity(intent)
...@@ -132,7 +135,7 @@ class PlayableFolderActivity : AppCompatActivity() { ...@@ -132,7 +135,7 @@ class PlayableFolderActivity : AppCompatActivity() {
SessionToken(this, ComponentName(this, PlaybackService::class.java)) SessionToken(this, ComponentName(this, PlaybackService::class.java))
) )
.buildAsync() .buildAsync()
browserFuture.addListener({ displayFolder() }, MoreExecutors.directExecutor()) browserFuture.addListener({ displayFolder() }, ContextCompat.getMainExecutor(this))
} }
private fun releaseBrowser() { private fun releaseBrowser() {
......
...@@ -29,9 +29,11 @@ import android.widget.ListView ...@@ -29,9 +29,11 @@ import android.widget.ListView
import android.widget.TextView import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.media3.common.C.TRACK_TYPE_TEXT
import androidx.media3.common.MediaItem import androidx.media3.common.MediaItem
import androidx.media3.common.MediaMetadata import androidx.media3.common.MediaMetadata
import androidx.media3.common.Player import androidx.media3.common.Player
import androidx.media3.common.Tracks
import androidx.media3.session.MediaController import androidx.media3.session.MediaController
import androidx.media3.session.SessionToken import androidx.media3.session.SessionToken
import androidx.media3.ui.PlayerView import androidx.media3.ui.PlayerView
...@@ -147,6 +149,10 @@ class PlayerActivity : AppCompatActivity() { ...@@ -147,6 +149,10 @@ class PlayerActivity : AppCompatActivity() {
override fun onRepeatModeChanged(repeatMode: Int) { override fun onRepeatModeChanged(repeatMode: Int) {
updateRepeatSwitchUI(repeatMode) updateRepeatSwitchUI(repeatMode)
} }
override fun onTracksChanged(tracks: Tracks) {
playerView.setShowSubtitleButton(tracks.isTypeSupported(TRACK_TYPE_TEXT))
}
} }
) )
} }
......
...@@ -20,6 +20,7 @@ android { ...@@ -20,6 +20,7 @@ android {
compileSdkVersion project.ext.compileSdkVersion compileSdkVersion project.ext.compileSdkVersion
compileOptions { compileOptions {
coreLibraryDesugaringEnabled true
sourceCompatibility JavaVersion.VERSION_1_8 sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8
} }
...@@ -76,11 +77,14 @@ dependencies { ...@@ -76,11 +77,14 @@ dependencies {
implementation 'androidx.constraintlayout:constraintlayout:' + androidxConstraintLayoutVersion implementation 'androidx.constraintlayout:constraintlayout:' + androidxConstraintLayoutVersion
implementation 'androidx.multidex:multidex:' + androidxMultidexVersion implementation 'androidx.multidex:multidex:' + androidxMultidexVersion
implementation 'com.google.android.material:material:' + androidxMaterialVersion implementation 'com.google.android.material:material:' + androidxMaterialVersion
implementation project(modulePrefix + 'lib-effect')
implementation project(modulePrefix + 'lib-exoplayer') implementation project(modulePrefix + 'lib-exoplayer')
implementation project(modulePrefix + 'lib-exoplayer-dash') implementation project(modulePrefix + 'lib-exoplayer-dash')
implementation project(modulePrefix + 'lib-transformer') implementation project(modulePrefix + 'lib-transformer')
implementation project(modulePrefix + 'lib-ui') implementation project(modulePrefix + 'lib-ui')
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5'
// For MediaPipe and its dependencies: // For MediaPipe and its dependencies:
withMediaPipeImplementation fileTree(dir: 'libs', include: ['*.aar']) withMediaPipeImplementation fileTree(dir: 'libs', include: ['*.aar'])
withMediaPipeImplementation 'com.google.flogger:flogger:latest.release' withMediaPipeImplementation 'com.google.flogger:flogger:latest.release'
......
...@@ -29,6 +29,7 @@ ...@@ -29,6 +29,7 @@
android:label="@string/app_name" android:label="@string/app_name"
android:theme="@style/Theme.AppCompat" android:theme="@style/Theme.AppCompat"
android:taskAffinity="" android:taskAffinity=""
android:requestLegacyExternalStorage="true"
tools:targetApi="29"> tools:targetApi="29">
<activity android:name=".ConfigurationActivity" <activity android:name=".ConfigurationActivity"
android:configChanges="keyboard|keyboardHidden|orientation|screenSize|screenLayout|smallestScreenSize|uiMode" android:configChanges="keyboard|keyboardHidden|orientation|screenSize|screenLayout|smallestScreenSize|uiMode"
......
...@@ -15,6 +15,7 @@ ...@@ -15,6 +15,7 @@
*/ */
package androidx.media3.demo.transformer; package androidx.media3.demo.transformer;
import static androidx.media3.common.util.Assertions.checkArgument;
import static androidx.media3.common.util.Assertions.checkStateNotNull; import static androidx.media3.common.util.Assertions.checkStateNotNull;
import android.content.Context; import android.content.Context;
...@@ -27,15 +28,14 @@ import android.graphics.Paint; ...@@ -27,15 +28,14 @@ import android.graphics.Paint;
import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.BitmapDrawable;
import android.opengl.GLES20; import android.opengl.GLES20;
import android.opengl.GLUtils; import android.opengl.GLUtils;
import android.util.Size; import android.util.Pair;
import androidx.media3.common.C; import androidx.media3.common.C;
import androidx.media3.common.FrameProcessingException;
import androidx.media3.common.util.GlProgram; import androidx.media3.common.util.GlProgram;
import androidx.media3.common.util.GlUtil; import androidx.media3.common.util.GlUtil;
import androidx.media3.transformer.FrameProcessingException; import androidx.media3.effect.SingleFrameGlTextureProcessor;
import androidx.media3.transformer.SingleFrameGlTextureProcessor;
import java.io.IOException; import java.io.IOException;
import java.util.Locale; import java.util.Locale;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
/** /**
* A {@link SingleFrameGlTextureProcessor} that overlays a bitmap with a logo and timer on each * A {@link SingleFrameGlTextureProcessor} that overlays a bitmap with a logo and timer on each
...@@ -45,10 +45,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; ...@@ -45,10 +45,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
*/ */
// TODO(b/227625365): Delete this class and use a texture processor from the Transformer library, // TODO(b/227625365): Delete this class and use a texture processor from the Transformer library,
// once overlaying a bitmap and text is supported in Transformer. // once overlaying a bitmap and text is supported in Transformer.
/* package */ final class BitmapOverlayProcessor implements SingleFrameGlTextureProcessor { /* package */ final class BitmapOverlayProcessor extends SingleFrameGlTextureProcessor {
static {
GlUtil.glAssertionsEnabled = true;
}
private static final String VERTEX_SHADER_PATH = "vertex_shader_copy_es2.glsl"; private static final String VERTEX_SHADER_PATH = "vertex_shader_copy_es2.glsl";
private static final String FRAGMENT_SHADER_PATH = "fragment_shader_bitmap_overlay_es2.glsl"; private static final String FRAGMENT_SHADER_PATH = "fragment_shader_bitmap_overlay_es2.glsl";
...@@ -57,16 +54,25 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; ...@@ -57,16 +54,25 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
private final Paint paint; private final Paint paint;
private final Bitmap overlayBitmap; private final Bitmap overlayBitmap;
private final Bitmap logoBitmap;
private final Canvas overlayCanvas; private final Canvas overlayCanvas;
private final GlProgram glProgram;
private float bitmapScaleX; private float bitmapScaleX;
private float bitmapScaleY; private float bitmapScaleY;
private int bitmapTexId; private int bitmapTexId;
private @MonotonicNonNull Size outputSize;
private @MonotonicNonNull Bitmap logoBitmap;
private @MonotonicNonNull GlProgram glProgram;
public BitmapOverlayProcessor() { /**
* Creates a new instance.
*
* @param context The {@link Context}.
* @param useHdr Whether input textures come from an HDR source. If {@code true}, colors will be
* in linear RGB BT.2020. If {@code false}, colors will be in linear RGB BT.709.
* @throws FrameProcessingException If a problem occurs while reading shader files.
*/
public BitmapOverlayProcessor(Context context, boolean useHdr) throws FrameProcessingException {
super(useHdr);
checkArgument(!useHdr, "BitmapOverlayProcessor does not support HDR colors.");
paint = new Paint(); paint = new Paint();
paint.setTextSize(64); paint.setTextSize(64);
paint.setAntiAlias(true); paint.setAntiAlias(true);
...@@ -75,19 +81,6 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; ...@@ -75,19 +81,6 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
overlayBitmap = overlayBitmap =
Bitmap.createBitmap(BITMAP_WIDTH_HEIGHT, BITMAP_WIDTH_HEIGHT, Bitmap.Config.ARGB_8888); Bitmap.createBitmap(BITMAP_WIDTH_HEIGHT, BITMAP_WIDTH_HEIGHT, Bitmap.Config.ARGB_8888);
overlayCanvas = new Canvas(overlayBitmap); overlayCanvas = new Canvas(overlayBitmap);
}
@Override
public void initialize(Context context, int inputTexId, int inputWidth, int inputHeight)
throws IOException {
if (inputWidth > inputHeight) {
bitmapScaleX = inputWidth / (float) inputHeight;
bitmapScaleY = 1f;
} else {
bitmapScaleX = 1f;
bitmapScaleY = inputHeight / (float) inputWidth;
}
outputSize = new Size(inputWidth, inputHeight);
try { try {
logoBitmap = logoBitmap =
...@@ -97,30 +90,46 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; ...@@ -97,30 +90,46 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
} catch (PackageManager.NameNotFoundException e) { } catch (PackageManager.NameNotFoundException e) {
throw new IllegalStateException(e); throw new IllegalStateException(e);
} }
bitmapTexId = GlUtil.createTexture(BITMAP_WIDTH_HEIGHT, BITMAP_WIDTH_HEIGHT); try {
GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, /* level= */ 0, overlayBitmap, /* border= */ 0); bitmapTexId =
GlUtil.createTexture(
glProgram = new GlProgram(context, VERTEX_SHADER_PATH, FRAGMENT_SHADER_PATH); BITMAP_WIDTH_HEIGHT,
BITMAP_WIDTH_HEIGHT,
/* useHighPrecisionColorComponents= */ false);
GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, /* level= */ 0, overlayBitmap, /* border= */ 0);
glProgram = new GlProgram(context, VERTEX_SHADER_PATH, FRAGMENT_SHADER_PATH);
} catch (GlUtil.GlException | IOException e) {
throw new FrameProcessingException(e);
}
// Draw the frame on the entire normalized device coordinate space, from -1 to 1, for x and y. // Draw the frame on the entire normalized device coordinate space, from -1 to 1, for x and y.
glProgram.setBufferAttribute( glProgram.setBufferAttribute(
"aFramePosition", "aFramePosition",
GlUtil.getNormalizedCoordinateBounds(), GlUtil.getNormalizedCoordinateBounds(),
GlUtil.HOMOGENEOUS_COORDINATE_VECTOR_SIZE); GlUtil.HOMOGENEOUS_COORDINATE_VECTOR_SIZE);
glProgram.setSamplerTexIdUniform("uTexSampler0", inputTexId, /* texUnitIndex= */ 0);
glProgram.setSamplerTexIdUniform("uTexSampler1", bitmapTexId, /* texUnitIndex= */ 1); glProgram.setSamplerTexIdUniform("uTexSampler1", bitmapTexId, /* texUnitIndex= */ 1);
glProgram.setFloatUniform("uScaleX", bitmapScaleX);
glProgram.setFloatUniform("uScaleY", bitmapScaleY);
} }
@Override @Override
public Size getOutputSize() { public Pair<Integer, Integer> configure(int inputWidth, int inputHeight) {
return checkStateNotNull(outputSize); if (inputWidth > inputHeight) {
bitmapScaleX = inputWidth / (float) inputHeight;
bitmapScaleY = 1f;
} else {
bitmapScaleX = 1f;
bitmapScaleY = inputHeight / (float) inputWidth;
}
glProgram.setFloatUniform("uScaleX", bitmapScaleX);
glProgram.setFloatUniform("uScaleY", bitmapScaleY);
return Pair.create(inputWidth, inputHeight);
} }
@Override @Override
public void drawFrame(long presentationTimeUs) throws FrameProcessingException { public void drawFrame(int inputTexId, long presentationTimeUs) throws FrameProcessingException {
try { try {
checkStateNotNull(glProgram).use(); glProgram.use();
// Draw to the canvas and store it in a texture. // Draw to the canvas and store it in a texture.
String text = String text =
...@@ -137,19 +146,23 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; ...@@ -137,19 +146,23 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
flipBitmapVertically(overlayBitmap)); flipBitmapVertically(overlayBitmap));
GlUtil.checkGlError(); GlUtil.checkGlError();
glProgram.setSamplerTexIdUniform("uTexSampler0", inputTexId, /* texUnitIndex= */ 0);
glProgram.bindAttributesAndUniforms(); glProgram.bindAttributesAndUniforms();
// The four-vertex triangle strip forms a quad. // The four-vertex triangle strip forms a quad.
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, /* first= */ 0, /* count= */ 4); GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, /* first= */ 0, /* count= */ 4);
GlUtil.checkGlError(); GlUtil.checkGlError();
} catch (GlUtil.GlException e) { } catch (GlUtil.GlException e) {
throw new FrameProcessingException(e); throw new FrameProcessingException(e, presentationTimeUs);
} }
} }
@Override @Override
public void release() { public void release() throws FrameProcessingException {
if (glProgram != null) { super.release();
try {
glProgram.delete(); glProgram.delete();
} catch (GlUtil.GlException e) {
throw new FrameProcessingException(e);
} }
} }
......
...@@ -18,8 +18,8 @@ package androidx.media3.demo.transformer; ...@@ -18,8 +18,8 @@ package androidx.media3.demo.transformer;
import android.graphics.Matrix; import android.graphics.Matrix;
import androidx.media3.common.C; import androidx.media3.common.C;
import androidx.media3.common.util.Util; import androidx.media3.common.util.Util;
import androidx.media3.transformer.GlMatrixTransformation; import androidx.media3.effect.GlMatrixTransformation;
import androidx.media3.transformer.MatrixTransformation; import androidx.media3.effect.MatrixTransformation;
/** /**
* Factory for {@link GlMatrixTransformation GlMatrixTransformations} and {@link * Factory for {@link GlMatrixTransformation GlMatrixTransformations} and {@link
......
...@@ -16,39 +16,29 @@ ...@@ -16,39 +16,29 @@
package androidx.media3.demo.transformer; package androidx.media3.demo.transformer;
import static androidx.media3.common.util.Assertions.checkArgument; import static androidx.media3.common.util.Assertions.checkArgument;
import static androidx.media3.common.util.Assertions.checkStateNotNull;
import android.content.Context; import android.content.Context;
import android.opengl.GLES20; import android.opengl.GLES20;
import android.util.Size; import android.util.Pair;
import androidx.media3.common.FrameProcessingException;
import androidx.media3.common.util.GlProgram; import androidx.media3.common.util.GlProgram;
import androidx.media3.common.util.GlUtil; import androidx.media3.common.util.GlUtil;
import androidx.media3.transformer.FrameProcessingException; import androidx.media3.effect.SingleFrameGlTextureProcessor;
import androidx.media3.transformer.SingleFrameGlTextureProcessor;
import java.io.IOException; import java.io.IOException;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
/** /**
* A {@link SingleFrameGlTextureProcessor} that periodically dims the frames such that pixels are * A {@link SingleFrameGlTextureProcessor} that periodically dims the frames such that pixels are
* darker the further they are away from the frame center. * darker the further they are away from the frame center.
*/ */
/* package */ final class PeriodicVignetteProcessor implements SingleFrameGlTextureProcessor { /* package */ final class PeriodicVignetteProcessor extends SingleFrameGlTextureProcessor {
static {
GlUtil.glAssertionsEnabled = true;
}
private static final String VERTEX_SHADER_PATH = "vertex_shader_copy_es2.glsl"; private static final String VERTEX_SHADER_PATH = "vertex_shader_copy_es2.glsl";
private static final String FRAGMENT_SHADER_PATH = "fragment_shader_vignette_es2.glsl"; private static final String FRAGMENT_SHADER_PATH = "fragment_shader_vignette_es2.glsl";
private static final float DIMMING_PERIOD_US = 5_600_000f; private static final float DIMMING_PERIOD_US = 5_600_000f;
private float centerX; private final GlProgram glProgram;
private float centerY; private final float minInnerRadius;
private float minInnerRadius; private final float deltaInnerRadius;
private float deltaInnerRadius;
private float outerRadius;
private @MonotonicNonNull Size outputSize;
private @MonotonicNonNull GlProgram glProgram;
/** /**
* Creates a new instance. * Creates a new instance.
...@@ -61,29 +51,35 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; ...@@ -61,29 +51,35 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
* *
* <p>The parameters are given in normalized texture coordinates from 0 to 1. * <p>The parameters are given in normalized texture coordinates from 0 to 1.
* *
* @param context The {@link Context}.
* @param useHdr Whether input textures come from an HDR source. If {@code true}, colors will be
* in linear RGB BT.2020. If {@code false}, colors will be in linear RGB BT.709.
* @param centerX The x-coordinate of the center of the effect. * @param centerX The x-coordinate of the center of the effect.
* @param centerY The y-coordinate of the center of the effect. * @param centerY The y-coordinate of the center of the effect.
* @param minInnerRadius The lower bound of the radius that is unaffected by the effect. * @param minInnerRadius The lower bound of the radius that is unaffected by the effect.
* @param maxInnerRadius The upper bound of the radius that is unaffected by the effect. * @param maxInnerRadius The upper bound of the radius that is unaffected by the effect.
* @param outerRadius The radius after which all pixels are black. * @param outerRadius The radius after which all pixels are black.
* @throws FrameProcessingException If a problem occurs while reading shader files.
*/ */
public PeriodicVignetteProcessor( public PeriodicVignetteProcessor(
float centerX, float centerY, float minInnerRadius, float maxInnerRadius, float outerRadius) { Context context,
boolean useHdr,
float centerX,
float centerY,
float minInnerRadius,
float maxInnerRadius,
float outerRadius)
throws FrameProcessingException {
super(useHdr);
checkArgument(minInnerRadius <= maxInnerRadius); checkArgument(minInnerRadius <= maxInnerRadius);
checkArgument(maxInnerRadius <= outerRadius); checkArgument(maxInnerRadius <= outerRadius);
this.centerX = centerX;
this.centerY = centerY;
this.minInnerRadius = minInnerRadius; this.minInnerRadius = minInnerRadius;
this.deltaInnerRadius = maxInnerRadius - minInnerRadius; this.deltaInnerRadius = maxInnerRadius - minInnerRadius;
this.outerRadius = outerRadius; try {
} glProgram = new GlProgram(context, VERTEX_SHADER_PATH, FRAGMENT_SHADER_PATH);
} catch (IOException | GlUtil.GlException e) {
@Override throw new FrameProcessingException(e);
public void initialize(Context context, int inputTexId, int inputWidth, int inputHeight) }
throws IOException {
outputSize = new Size(inputWidth, inputHeight);
glProgram = new GlProgram(context, VERTEX_SHADER_PATH, FRAGMENT_SHADER_PATH);
glProgram.setSamplerTexIdUniform("uTexSampler", inputTexId, /* texUnitIndex= */ 0);
glProgram.setFloatsUniform("uCenter", new float[] {centerX, centerY}); glProgram.setFloatsUniform("uCenter", new float[] {centerX, centerY});
glProgram.setFloatsUniform("uOuterRadius", new float[] {outerRadius}); glProgram.setFloatsUniform("uOuterRadius", new float[] {outerRadius});
// Draw the frame on the entire normalized device coordinate space, from -1 to 1, for x and y. // Draw the frame on the entire normalized device coordinate space, from -1 to 1, for x and y.
...@@ -94,14 +90,15 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; ...@@ -94,14 +90,15 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
} }
@Override @Override
public Size getOutputSize() { public Pair<Integer, Integer> configure(int inputWidth, int inputHeight) {
return checkStateNotNull(outputSize); return Pair.create(inputWidth, inputHeight);
} }
@Override @Override
public void drawFrame(long presentationTimeUs) throws FrameProcessingException { public void drawFrame(int inputTexId, long presentationTimeUs) throws FrameProcessingException {
try { try {
checkStateNotNull(glProgram).use(); glProgram.use();
glProgram.setSamplerTexIdUniform("uTexSampler", inputTexId, /* texUnitIndex= */ 0);
double theta = presentationTimeUs * 2 * Math.PI / DIMMING_PERIOD_US; double theta = presentationTimeUs * 2 * Math.PI / DIMMING_PERIOD_US;
float innerRadius = float innerRadius =
minInnerRadius + deltaInnerRadius * (0.5f - 0.5f * (float) Math.cos(theta)); minInnerRadius + deltaInnerRadius * (0.5f - 0.5f * (float) Math.cos(theta));
...@@ -110,14 +107,17 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; ...@@ -110,14 +107,17 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
// The four-vertex triangle strip forms a quad. // The four-vertex triangle strip forms a quad.
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, /* first= */ 0, /* count= */ 4); GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, /* first= */ 0, /* count= */ 4);
} catch (GlUtil.GlException e) { } catch (GlUtil.GlException e) {
throw new FrameProcessingException(e); throw new FrameProcessingException(e, presentationTimeUs);
} }
} }
@Override @Override
public void release() { public void release() throws FrameProcessingException {
if (glProgram != null) { super.release();
try {
glProgram.delete(); glProgram.delete();
} catch (GlUtil.GlException e) {
throw new FrameProcessingException(e);
} }
} }
} }
...@@ -34,16 +34,26 @@ ...@@ -34,16 +34,26 @@
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" /> app:layout_constraintTop_toTopOf="parent" />
<Button <Button
android:id="@+id/select_file_button" android:id="@+id/select_preset_file_button"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="32dp" android:layout_marginTop="32dp"
android:layout_marginStart="32dp" android:layout_marginStart="8dp"
android:layout_marginEnd="32dp" android:text="@string/select_preset_file_title"
android:text="@string/select_file_title" android:textSize="12sp"
app:layout_constraintTop_toBottomOf="@+id/configuration_text_view" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/configuration_text_view" />
<Button
android:id="@+id/select_local_file_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="32dp"
android:layout_marginEnd="8dp"
android:text="@string/select_local_file_title"
android:textSize="12sp"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" /> app:layout_constraintTop_toBottomOf="@+id/configuration_text_view" />
<TextView <TextView
android:id="@+id/selected_file_text_view" android:id="@+id/selected_file_text_view"
android:layout_width="0dp" android:layout_width="0dp"
...@@ -57,52 +67,50 @@ ...@@ -57,52 +67,50 @@
android:gravity="center" android:gravity="center"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/select_file_button" /> app:layout_constraintTop_toBottomOf="@+id/select_preset_file_button" />
<androidx.core.widget.NestedScrollView <androidx.core.widget.NestedScrollView
android:layout_width="fill_parent" android:layout_width="match_parent"
android:layout_height="0dp" android:layout_height="0dp"
android:padding="4dp"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/selected_file_text_view" app:layout_constraintTop_toBottomOf="@+id/selected_file_text_view"
app:layout_constraintBottom_toTopOf="@+id/select_demo_effects_button"> app:layout_constraintBottom_toTopOf="@+id/select_demo_effects_button">
<TableLayout <TableLayout
android:layout_width="fill_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:stretchColumns="1" android:stretchColumns="0"
android:layout_marginTop="32dp" android:layout_marginTop="32dp"
android:measureWithLargestChild="true" android:measureWithLargestChild="true"
android:paddingLeft="24dp"
android:paddingRight="12dp"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"> app:layout_constraintBottom_toBottomOf="parent">
<TableRow <TableRow
android:layout_weight="1" android:layout_weight="1">
android:gravity="center_vertical" >
<TextView <TextView
android:layout_gravity="center_vertical"
android:text="@string/remove_audio" /> android:text="@string/remove_audio" />
<CheckBox <CheckBox
android:id="@+id/remove_audio_checkbox" android:layout_gravity="end"
android:layout_gravity="right"/> android:id="@+id/remove_audio_checkbox"/>
</TableRow> </TableRow>
<TableRow <TableRow android:layout_weight="1">
android:layout_weight="1"
android:gravity="center_vertical" >
<TextView <TextView
android:layout_gravity="center_vertical"
android:text="@string/remove_video"/> android:text="@string/remove_video"/>
<CheckBox <CheckBox
android:id="@+id/remove_video_checkbox" android:id="@+id/remove_video_checkbox"
android:layout_gravity="right" /> android:layout_gravity="end" />
</TableRow> </TableRow>
<TableRow <TableRow
android:layout_weight="1" android:layout_weight="1">
android:gravity="center_vertical" >
<TextView <TextView
android:layout_gravity="center_vertical"
android:text="@string/flatten_for_slow_motion"/> android:text="@string/flatten_for_slow_motion"/>
<CheckBox <CheckBox
android:id="@+id/flatten_for_slow_motion_checkbox" android:id="@+id/flatten_for_slow_motion_checkbox"
android:layout_gravity="right" /> android:layout_gravity="end" />
</TableRow> </TableRow>
<TableRow <TableRow
android:layout_weight="1" android:layout_weight="1"
...@@ -160,44 +168,64 @@ ...@@ -160,44 +168,64 @@
android:gravity="right" /> android:gravity="right" />
</TableRow> </TableRow>
<TableRow <TableRow
android:layout_weight="1" android:layout_weight="1">
android:gravity="center_vertical" >
<TextView <TextView
android:layout_gravity="center_vertical"
android:id="@+id/trim" android:id="@+id/trim"
android:text="@string/trim" /> android:text="@string/trim" />
<CheckBox <CheckBox
android:id="@+id/trim_checkbox" android:id="@+id/trim_checkbox"
android:layout_gravity="right" /> android:layout_gravity="end" />
</TableRow> </TableRow>
<TableRow <TableRow
android:layout_weight="1" android:layout_weight="1">
android:gravity="center_vertical" >
<TextView <TextView
android:layout_gravity="center_vertical"
android:text="@string/enable_fallback" /> android:text="@string/enable_fallback" />
<CheckBox <CheckBox
android:id="@+id/enable_fallback_checkbox" android:id="@+id/enable_fallback_checkbox"
android:layout_gravity="right" android:layout_gravity="end"
android:checked="true"/> android:checked="true"/>
</TableRow> </TableRow>
<TableRow <TableRow
android:layout_weight="1" android:layout_weight="1">
android:gravity="center_vertical" >
<TextView <TextView
android:layout_gravity="center_vertical"
android:text="@string/enable_debug_preview" />
<CheckBox
android:id="@+id/enable_debug_preview_checkbox"
android:layout_gravity="end"
android:checked="true"/>
</TableRow>
<TableRow
android:layout_weight="1">
<TextView
android:layout_gravity="center_vertical"
android:id="@+id/request_sdr_tone_mapping" android:id="@+id/request_sdr_tone_mapping"
android:text="@string/request_sdr_tone_mapping" /> android:text="@string/request_sdr_tone_mapping" />
<CheckBox <CheckBox
android:id="@+id/request_sdr_tone_mapping_checkbox" android:id="@+id/request_sdr_tone_mapping_checkbox"
android:layout_gravity="right" /> android:layout_gravity="end" />
</TableRow> </TableRow>
<TableRow <TableRow
android:layout_weight="1" android:layout_weight="1">
android:gravity="center_vertical" >
<TextView <TextView
android:layout_gravity="center_vertical"
android:id="@+id/hdr_editing" android:id="@+id/hdr_editing"
android:text="@string/hdr_editing" /> android:text="@string/hdr_editing" />
<CheckBox <CheckBox
android:id="@+id/hdr_editing_checkbox" android:id="@+id/hdr_editing_checkbox"
android:layout_gravity="right" /> android:layout_gravity="end" />
</TableRow>
<TableRow
android:layout_weight="1">
<TextView
android:layout_gravity="center_vertical"
android:id="@+id/force_interpret_hdr_video_as_sdr"
android:text="@string/force_interpret_hdr_video_as_sdr" />
<CheckBox
android:id="@+id/force_interpret_hdr_video_as_sdr_checkbox"
android:layout_gravity="end" />
</TableRow> </TableRow>
</TableLayout> </TableLayout>
</androidx.core.widget.NestedScrollView> </androidx.core.widget.NestedScrollView>
......
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright 2022 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.
-->
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
tools:context=".ConfigurationActivity">
<TableLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:stretchColumns="1"
android:layout_marginTop="32dp"
android:measureWithLargestChild="true"
android:paddingLeft="24dp"
android:paddingRight="12dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent">
<TableRow
android:layout_weight="1"
android:gravity="center_vertical" >
<TextView
android:text="@string/contrast_value" />
<com.google.android.material.slider.Slider
android:id="@+id/contrast_slider"
android:valueFrom="-1.0"
android:value="0.0"
android:valueTo="1.0"
android:layout_gravity="right"/>
</TableRow>
</TableLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright 2022 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.
-->
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
tools:context=".ConfigurationActivity">
<TableLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:stretchColumns="1"
android:layout_marginTop="32dp"
android:measureWithLargestChild="true"
android:paddingLeft="24dp"
android:paddingRight="12dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent">
<TableRow
android:layout_weight="1"
android:gravity="center_vertical" >
<TextView
android:text="@string/hue_adjustment" />
<com.google.android.material.slider.Slider
android:id="@+id/hsl_adjustments_hue"
android:valueFrom="-360"
android:value="0"
android:valueTo="360"
android:layout_gravity="right"/>
</TableRow>
<TableRow
android:layout_weight="1"
android:gravity="center_vertical" >
<TextView
android:text="@string/saturation_adjustment" />
<com.google.android.material.slider.Slider
android:id="@+id/hsl_adjustments_saturation"
android:valueFrom="-100"
android:value="0"
android:valueTo="100"
android:layout_gravity="right"/>
</TableRow>
<TableRow
android:layout_weight="1"
android:gravity="center_vertical" >
<TextView
android:text="@string/lightness_adjustment" />
<com.google.android.material.slider.Slider
android:id="@+id/hsl_adjustment_lightness"
android:valueFrom="-100"
android:value="0"
android:valueTo="100"
android:layout_gravity="right"/>
</TableRow>
</TableLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright 2022 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.
-->
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
tools:context=".ConfigurationActivity">
<TableLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:stretchColumns="1"
android:layout_marginTop="32dp"
android:measureWithLargestChild="true"
android:paddingLeft="24dp"
android:paddingRight="12dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent">
<TableRow
android:layout_weight="1"
android:gravity="center_vertical" >
<TextView
android:text="@string/rgb_adjustment_scale_red" />
<com.google.android.material.slider.Slider
android:id="@+id/rgb_adjustment_red_scale"
android:valueFrom="0"
android:value="1"
android:valueTo="2"
android:layout_gravity="right"
app:labelBehavior="gone"/>
</TableRow>
<TableRow
android:layout_weight="1"
android:gravity="center_vertical" >
<TextView
android:text="@string/rgb_adjustment_scale_green" />
<com.google.android.material.slider.Slider
android:id="@+id/rgb_adjustment_green_scale"
android:valueFrom="0"
android:value="1"
android:valueTo="2"
android:layout_gravity="right"
app:labelBehavior="gone"/>
</TableRow>
<TableRow
android:layout_weight="1"
android:gravity="center_vertical" >
<TextView
android:text="@string/rgb_adjustment_scale_blue" />
<com.google.android.material.slider.Slider
android:id="@+id/rgb_adjustment_blue_scale"
android:valueFrom="0"
android:value="1"
android:valueTo="2"
android:layout_gravity="right"
app:labelBehavior="gone"/>
</TableRow>
</TableLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
...@@ -29,42 +29,113 @@ ...@@ -29,42 +29,113 @@
app:cardElevation="2dp" app:cardElevation="2dp"
android:gravity="center_vertical" > android:gravity="center_vertical" >
<TextView <LinearLayout
android:id="@+id/information_text_view" android:layout_width="match_parent"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:orientation="vertical" android:orientation="vertical"
android:padding="8dp" /> android:layout_height="wrap_content">
<TextView
android:id="@+id/information_text_view"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="8dp" />
<Button
android:id="@+id/display_input_button"
android:visibility="gone"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/hide_input_video"
android:layout_margin="8dp" />
</LinearLayout>
</com.google.android.material.card.MaterialCardView> </com.google.android.material.card.MaterialCardView>
<com.google.android.material.card.MaterialCardView <com.google.android.material.card.MaterialCardView
android:id="@+id/input_card_view"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="0dp" android:layout_height="0dp"
android:layout_weight="1" android:layout_weight="1"
android:layout_margin="16dp" android:layout_margin="16dp"
app:cardCornerRadius="4dp" app:cardCornerRadius="4dp"
app:cardElevation="2dp"> app:cardElevation="2dp">
<FrameLayout
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content" >
<TextView
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:padding="8dp"
android:text="@string/input_video" />
<androidx.media3.ui.PlayerView <FrameLayout
android:id="@+id/player_view" android:layout_width="match_parent"
android:layout_height="wrap_content" >
<androidx.media3.ui.PlayerView
android:id="@+id/input_player_view"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<androidx.media3.ui.AspectRatioFrameLayout
android:id="@+id/input_debug_aspect_ratio_frame_layout"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" /> android:layout_height="match_parent" />
</FrameLayout>
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.card.MaterialCardView
android:id="@+id/output_card_view"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:layout_margin="16dp"
app:cardCornerRadius="4dp"
app:cardElevation="2dp">
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content" >
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:text="@string/output_video" />
<TextView
android:id="@+id/debug_text_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:textSize="10sp"
tools:ignore="SmallSp"/>
<TextView <FrameLayout
android:id="@+id/debug_text_view" android:layout_width="match_parent"
android:layout_gravity="center"
android:layout_height="wrap_content">
<androidx.media3.ui.PlayerView
android:id="@+id/output_player_view"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content" />
android:textSize="10sp"
tools:ignore="SmallSp"/>
<LinearLayout <LinearLayout
android:id="@+id/progress_view_group" android:id="@+id/progress_view_group"
android:layout_height="match_parent" android:layout_height="wrap_content"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_gravity="bottom"
android:padding="8dp" android:padding="8dp"
android:orientation="vertical"> android:orientation="vertical">
...@@ -96,5 +167,9 @@ ...@@ -96,5 +167,9 @@
</LinearLayout> </LinearLayout>
</FrameLayout> </FrameLayout>
</LinearLayout>
</com.google.android.material.card.MaterialCardView> </com.google.android.material.card.MaterialCardView>
</LinearLayout> </LinearLayout>
...@@ -17,7 +17,8 @@ ...@@ -17,7 +17,8 @@
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" translatable="false">Transformer Demo</string> <string name="app_name" translatable="false">Transformer Demo</string>
<string name="configuration" translatable="false">Configuration</string> <string name="configuration" translatable="false">Configuration</string>
<string name="select_file_title" translatable="false">Choose file</string> <string name="select_preset_file_title" translatable="false">Choose preset file</string>
<string name="select_local_file_title">Choose local file</string>
<string name="remove_audio" translatable="false">Remove audio</string> <string name="remove_audio" translatable="false">Remove audio</string>
<string name="remove_video" translatable="false">Remove video</string> <string name="remove_video" translatable="false">Remove video</string>
<string name="flatten_for_slow_motion" translatable="false">Flatten for slow motion</string> <string name="flatten_for_slow_motion" translatable="false">Flatten for slow motion</string>
...@@ -27,9 +28,11 @@ ...@@ -27,9 +28,11 @@
<string name="scale" translatable="false">Scale video</string> <string name="scale" translatable="false">Scale video</string>
<string name="rotate" translatable="false">Rotate video (degrees)</string> <string name="rotate" translatable="false">Rotate video (degrees)</string>
<string name="enable_fallback" translatable="false">Enable fallback</string> <string name="enable_fallback" translatable="false">Enable fallback</string>
<string name="enable_debug_preview" translatable="false">Enable debug preview</string>
<string name="trim" translatable="false">Trim</string> <string name="trim" translatable="false">Trim</string>
<string name="request_sdr_tone_mapping" translatable="false">Request SDR tone-mapping (API 31+)</string> <string name="request_sdr_tone_mapping" translatable="false">Request SDR tone-mapping (API 31+)</string>
<string name="hdr_editing" translatable="false">[Experimental] HDR editing</string> <string name="force_interpret_hdr_video_as_sdr" translatable="false">[Experimental] Force interpret HDR video as SDR (API 29+)</string>
<string name="hdr_editing" translatable="false">[Experimental] HDR editing (API 31+)</string>
<string name="select_demo_effects" translatable="false">Add demo effects</string> <string name="select_demo_effects" translatable="false">Add demo effects</string>
<string name="periodic_vignette_options" translatable="false">Periodic vignette options</string> <string name="periodic_vignette_options" translatable="false">Periodic vignette options</string>
<string name="no_media_pipe_error" translatable="false">Failed to load MediaPipe processor. Check the README for instructions.</string> <string name="no_media_pipe_error" translatable="false">Failed to load MediaPipe processor. Check the README for instructions.</string>
...@@ -40,8 +43,27 @@ ...@@ -40,8 +43,27 @@
<string name="transformation_timer" translatable="false">Transformation started %d seconds ago.</string> <string name="transformation_timer" translatable="false">Transformation started %d seconds ago.</string>
<string name="transformation_completed" translatable="false">Transformation completed in %d seconds.</string> <string name="transformation_completed" translatable="false">Transformation completed in %d seconds.</string>
<string name="transformation_error" translatable="false">Transformation error</string> <string name="transformation_error" translatable="false">Transformation error</string>
<string name="trim_range">Bounds in seconds</string>
<string-array name="color_filter_options">
<item>Grayscale</item>
<item>Inverted</item>
<item>Sepia</item>
</string-array>
<string name="contrast_value" translatable="false">Contrast value</string>
<string name="rgb_adjustment_options" translatable="false">Scale RGB Channels individually</string>
<string name="rgb_adjustment_scale_red" translatable="false">Scale red</string>
<string name="rgb_adjustment_scale_green" translatable="false">Scale green</string>
<string name="rgb_adjustment_scale_blue" translatable="false">Scale blue</string>
<string name="center_x">Center X</string> <string name="center_x">Center X</string>
<string name="center_y">Center Y</string> <string name="center_y">Center Y</string>
<string name="radius_range">Radius range</string> <string name="radius_range">Radius range</string>
<string name="trim_range">Bounds in seconds</string> <string name="hsl_adjustment_options" translatable="false">HSL adjustment options</string>
<string name="hue_adjustment">Hue adjustment</string>
<string name="saturation_adjustment">Saturation adjustment</string>
<string name="lightness_adjustment">Lightness adjustment</string>
<string name="input_video">Input video:</string>
<string name="output_video">Output video:</string>
<string name="permission_denied">Permission Denied</string>
<string name="hide_input_video">Hide input video</string>
<string name="show_input_video">Show input video</string>
</resources> </resources>
...@@ -21,7 +21,7 @@ class CombinedJavadocPlugin implements Plugin<Project> { ...@@ -21,7 +21,7 @@ class CombinedJavadocPlugin implements Plugin<Project> {
// Dackka snapshots are listed at https://androidx.dev/dackka/builds. // Dackka snapshots are listed at https://androidx.dev/dackka/builds.
static final String DACKKA_JAR_URL = static final String DACKKA_JAR_URL =
"https://androidx.dev/dackka/builds/8003564/artifacts/dackka-0.0.14.jar" "https://androidx.dev/dackka/builds/9221390/artifacts/dackka-1.0.4-all.jar"
@Override @Override
void apply(Project project) { void apply(Project project) {
...@@ -58,6 +58,11 @@ class CombinedJavadocPlugin implements Plugin<Project> { ...@@ -58,6 +58,11 @@ class CombinedJavadocPlugin implements Plugin<Project> {
"media-" + project.ext.androidxMediaVersion + "-api.jar")) { "media-" + project.ext.androidxMediaVersion + "-api.jar")) {
return false; return false;
} }
if (file ==~ /.*\/core-.\..\..-api.jar$/
&& !file.path.endsWith(
"core-" + project.ext.androidxCoreVersion + "-api.jar")) {
return false;
}
return true; return true;
} }
classpath += classpath +=
...@@ -115,11 +120,16 @@ class CombinedJavadocPlugin implements Plugin<Project> { ...@@ -115,11 +120,16 @@ class CombinedJavadocPlugin implements Plugin<Project> {
def sourcesString = project.files(sources.flatten()) def sourcesString = project.files(sources.flatten())
.filter({ f -> project.file(f).exists() }).join(";") .filter({ f -> project.file(f).exists() }).join(";")
def dependenciesString = project.files(dependencies).asPath.replace(':', ';') def dependenciesString = project.files(dependencies).asPath.replace(':', ';')
def sourceSet = [
"-src", sourcesString,
"-classpath", dependenciesString,
"-documentedVisibilities", "PUBLIC;PROTECTED"
].join(" ")
args("-moduleName", "", args("-moduleName", "",
"-outputDir", "$dackkaOutputDir", "-outputDir", "$dackkaOutputDir",
"-globalLinks", "$globalLinksString", "-globalLinks", "$globalLinksString",
"-loggingLevel", "WARN", "-loggingLevel", "WARN",
"-sourceSet", "-src $sourcesString -classpath $dependenciesString", "-sourceSet", "$sourceSet",
"-offlineMode") "-offlineMode")
environment("DEVSITE_TENANT", "androidx/media3") environment("DEVSITE_TENANT", "androidx/media3")
} }
......
...@@ -47,6 +47,7 @@ import androidx.media3.common.util.Assertions; ...@@ -47,6 +47,7 @@ import androidx.media3.common.util.Assertions;
import androidx.media3.common.util.Clock; import androidx.media3.common.util.Clock;
import androidx.media3.common.util.ListenerSet; import androidx.media3.common.util.ListenerSet;
import androidx.media3.common.util.Log; import androidx.media3.common.util.Log;
import androidx.media3.common.util.Size;
import androidx.media3.common.util.UnstableApi; import androidx.media3.common.util.UnstableApi;
import androidx.media3.common.util.Util; import androidx.media3.common.util.Util;
import com.google.android.gms.cast.CastStatusCodes; import com.google.android.gms.cast.CastStatusCodes;
...@@ -82,6 +83,10 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; ...@@ -82,6 +83,10 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
@UnstableApi @UnstableApi
public final class CastPlayer extends BasePlayer { public final class CastPlayer extends BasePlayer {
/** The {@link DeviceInfo} returned by {@link #getDeviceInfo() this player}. */
public static final DeviceInfo DEVICE_INFO =
new DeviceInfo(DeviceInfo.PLAYBACK_TYPE_REMOTE, /* minVolume= */ 0, /* maxVolume= */ 0);
static { static {
MediaLibraryInfo.registerModule("media3.cast"); MediaLibraryInfo.registerModule("media3.cast");
} }
...@@ -723,16 +728,22 @@ public final class CastPlayer extends BasePlayer { ...@@ -723,16 +728,22 @@ public final class CastPlayer extends BasePlayer {
return VideoSize.UNKNOWN; return VideoSize.UNKNOWN;
} }
/** This method is not supported and returns {@link Size#UNKNOWN}. */
@Override
public Size getSurfaceSize() {
return Size.UNKNOWN;
}
/** This method is not supported and returns an empty {@link CueGroup}. */ /** This method is not supported and returns an empty {@link CueGroup}. */
@Override @Override
public CueGroup getCurrentCues() { public CueGroup getCurrentCues() {
return CueGroup.EMPTY; return CueGroup.EMPTY_TIME_ZERO;
} }
/** This method is not supported and always returns {@link DeviceInfo#UNKNOWN}. */ /** This method always returns {@link CastPlayer#DEVICE_INFO}. */
@Override @Override
public DeviceInfo getDeviceInfo() { public DeviceInfo getDeviceInfo() {
return DeviceInfo.UNKNOWN; return DEVICE_INFO;
} }
/** This method is not supported and always returns {@code 0}. */ /** This method is not supported and always returns {@code 0}. */
......
...@@ -62,6 +62,7 @@ import static org.mockito.MockitoAnnotations.initMocks; ...@@ -62,6 +62,7 @@ import static org.mockito.MockitoAnnotations.initMocks;
import android.net.Uri; import android.net.Uri;
import androidx.media3.common.C; import androidx.media3.common.C;
import androidx.media3.common.DeviceInfo;
import androidx.media3.common.MediaItem; import androidx.media3.common.MediaItem;
import androidx.media3.common.MediaMetadata; import androidx.media3.common.MediaMetadata;
import androidx.media3.common.MimeTypes; import androidx.media3.common.MimeTypes;
...@@ -1864,6 +1865,14 @@ public class CastPlayerTest { ...@@ -1864,6 +1865,14 @@ public class CastPlayerTest {
verify(mockListener, never()).onMediaMetadataChanged(any()); verify(mockListener, never()).onMediaMetadataChanged(any());
} }
@Test
public void getDeviceInfo_returnsCorrectDeviceInfoWithPlaybackTypeRemote() {
DeviceInfo deviceInfo = castPlayer.getDeviceInfo();
assertThat(deviceInfo).isEqualTo(CastPlayer.DEVICE_INFO);
assertThat(deviceInfo.playbackType).isEqualTo(DeviceInfo.PLAYBACK_TYPE_REMOTE);
}
private int[] createMediaQueueItemIds(int numberOfIds) { private int[] createMediaQueueItemIds(int numberOfIds) {
int[] mediaQueueItemIds = new int[numberOfIds]; int[] mediaQueueItemIds = new int[numberOfIds];
for (int i = 0; i < numberOfIds; i++) { for (int i = 0; i < numberOfIds; i++) {
......
...@@ -25,6 +25,7 @@ import android.view.View; ...@@ -25,6 +25,7 @@ import android.view.View;
import androidx.annotation.IntDef; import androidx.annotation.IntDef;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.media3.common.util.UnstableApi; import androidx.media3.common.util.UnstableApi;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import java.lang.annotation.Documented; import java.lang.annotation.Documented;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;
...@@ -77,6 +78,7 @@ public final class AdOverlayInfo { ...@@ -77,6 +78,7 @@ public final class AdOverlayInfo {
* *
* @return This builder, for convenience. * @return This builder, for convenience.
*/ */
@CanIgnoreReturnValue
public Builder setDetailedReason(@Nullable String detailedReason) { public Builder setDetailedReason(@Nullable String detailedReason) {
this.detailedReason = detailedReason; this.detailedReason = detailedReason;
return this; return this;
......
...@@ -24,6 +24,7 @@ import androidx.annotation.Nullable; ...@@ -24,6 +24,7 @@ import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi; import androidx.annotation.RequiresApi;
import androidx.media3.common.util.UnstableApi; import androidx.media3.common.util.UnstableApi;
import androidx.media3.common.util.Util; import androidx.media3.common.util.Util;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import java.lang.annotation.Documented; import java.lang.annotation.Documented;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;
...@@ -94,30 +95,35 @@ public final class AudioAttributes implements Bundleable { ...@@ -94,30 +95,35 @@ public final class AudioAttributes implements Bundleable {
} }
/** See {@link android.media.AudioAttributes.Builder#setContentType(int)} */ /** See {@link android.media.AudioAttributes.Builder#setContentType(int)} */
@CanIgnoreReturnValue
public Builder setContentType(@C.AudioContentType int contentType) { public Builder setContentType(@C.AudioContentType int contentType) {
this.contentType = contentType; this.contentType = contentType;
return this; return this;
} }
/** See {@link android.media.AudioAttributes.Builder#setFlags(int)} */ /** See {@link android.media.AudioAttributes.Builder#setFlags(int)} */
@CanIgnoreReturnValue
public Builder setFlags(@C.AudioFlags int flags) { public Builder setFlags(@C.AudioFlags int flags) {
this.flags = flags; this.flags = flags;
return this; return this;
} }
/** See {@link android.media.AudioAttributes.Builder#setUsage(int)} */ /** See {@link android.media.AudioAttributes.Builder#setUsage(int)} */
@CanIgnoreReturnValue
public Builder setUsage(@C.AudioUsage int usage) { public Builder setUsage(@C.AudioUsage int usage) {
this.usage = usage; this.usage = usage;
return this; return this;
} }
/** See {@link android.media.AudioAttributes.Builder#setAllowedCapturePolicy(int)}. */ /** See {@link android.media.AudioAttributes.Builder#setAllowedCapturePolicy(int)}. */
@CanIgnoreReturnValue
public Builder setAllowedCapturePolicy(@C.AudioAllowedCapturePolicy int allowedCapturePolicy) { public Builder setAllowedCapturePolicy(@C.AudioAllowedCapturePolicy int allowedCapturePolicy) {
this.allowedCapturePolicy = allowedCapturePolicy; this.allowedCapturePolicy = allowedCapturePolicy;
return this; return this;
} }
/** See {@link android.media.AudioAttributes.Builder#setSpatializationBehavior(int)}. */ /** See {@link android.media.AudioAttributes.Builder#setSpatializationBehavior(int)}. */
@CanIgnoreReturnValue
public Builder setSpatializationBehavior(@C.SpatializationBehavior int spatializationBehavior) { public Builder setSpatializationBehavior(@C.SpatializationBehavior int spatializationBehavior) {
this.spatializationBehavior = spatializationBehavior; this.spatializationBehavior = spatializationBehavior;
return this; return this;
......
...@@ -21,7 +21,8 @@ import static java.lang.Math.min; ...@@ -21,7 +21,8 @@ import static java.lang.Math.min;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.media3.common.util.UnstableApi; import androidx.media3.common.util.UnstableApi;
import androidx.media3.common.util.Util; import androidx.media3.common.util.Util;
import java.util.Collections; import com.google.common.collect.ImmutableList;
import com.google.errorprone.annotations.ForOverride;
import java.util.List; import java.util.List;
/** Abstract base {@link Player} which implements common implementation independent methods. */ /** Abstract base {@link Player} which implements common implementation independent methods. */
...@@ -36,17 +37,17 @@ public abstract class BasePlayer implements Player { ...@@ -36,17 +37,17 @@ public abstract class BasePlayer implements Player {
@Override @Override
public final void setMediaItem(MediaItem mediaItem) { public final void setMediaItem(MediaItem mediaItem) {
setMediaItems(Collections.singletonList(mediaItem)); setMediaItems(ImmutableList.of(mediaItem));
} }
@Override @Override
public final void setMediaItem(MediaItem mediaItem, long startPositionMs) { public final void setMediaItem(MediaItem mediaItem, long startPositionMs) {
setMediaItems(Collections.singletonList(mediaItem), /* startWindowIndex= */ 0, startPositionMs); setMediaItems(ImmutableList.of(mediaItem), /* startIndex= */ 0, startPositionMs);
} }
@Override @Override
public final void setMediaItem(MediaItem mediaItem, boolean resetPosition) { public final void setMediaItem(MediaItem mediaItem, boolean resetPosition) {
setMediaItems(Collections.singletonList(mediaItem), resetPosition); setMediaItems(ImmutableList.of(mediaItem), resetPosition);
} }
@Override @Override
...@@ -56,12 +57,12 @@ public abstract class BasePlayer implements Player { ...@@ -56,12 +57,12 @@ public abstract class BasePlayer implements Player {
@Override @Override
public final void addMediaItem(int index, MediaItem mediaItem) { public final void addMediaItem(int index, MediaItem mediaItem) {
addMediaItems(index, Collections.singletonList(mediaItem)); addMediaItems(index, ImmutableList.of(mediaItem));
} }
@Override @Override
public final void addMediaItem(MediaItem mediaItem) { public final void addMediaItem(MediaItem mediaItem) {
addMediaItems(Collections.singletonList(mediaItem)); addMediaItems(ImmutableList.of(mediaItem));
} }
@Override @Override
...@@ -187,7 +188,12 @@ public abstract class BasePlayer implements Player { ...@@ -187,7 +188,12 @@ public abstract class BasePlayer implements Player {
@Override @Override
public final void seekToPreviousMediaItem() { public final void seekToPreviousMediaItem() {
int previousMediaItemIndex = getPreviousMediaItemIndex(); int previousMediaItemIndex = getPreviousMediaItemIndex();
if (previousMediaItemIndex != C.INDEX_UNSET) { if (previousMediaItemIndex == C.INDEX_UNSET) {
return;
}
if (previousMediaItemIndex == getCurrentMediaItemIndex()) {
repeatCurrentMediaItem();
} else {
seekToDefaultPosition(previousMediaItemIndex); seekToDefaultPosition(previousMediaItemIndex);
} }
} }
...@@ -254,7 +260,12 @@ public abstract class BasePlayer implements Player { ...@@ -254,7 +260,12 @@ public abstract class BasePlayer implements Player {
@Override @Override
public final void seekToNextMediaItem() { public final void seekToNextMediaItem() {
int nextMediaItemIndex = getNextMediaItemIndex(); int nextMediaItemIndex = getNextMediaItemIndex();
if (nextMediaItemIndex != C.INDEX_UNSET) { if (nextMediaItemIndex == C.INDEX_UNSET) {
return;
}
if (nextMediaItemIndex == getCurrentMediaItemIndex()) {
repeatCurrentMediaItem();
} else {
seekToDefaultPosition(nextMediaItemIndex); seekToDefaultPosition(nextMediaItemIndex);
} }
} }
...@@ -426,6 +437,17 @@ public abstract class BasePlayer implements Player { ...@@ -426,6 +437,17 @@ public abstract class BasePlayer implements Player {
: timeline.getWindow(getCurrentMediaItemIndex(), window).getDurationMs(); : timeline.getWindow(getCurrentMediaItemIndex(), window).getDurationMs();
} }
/**
* Repeat the current media item.
*
* <p>The default implementation seeks to the default position in the current item, which can be
* overridden for additional handling.
*/
@ForOverride
protected void repeatCurrentMediaItem() {
seekToDefaultPosition();
}
private @RepeatMode int getRepeatModeForNavigation() { private @RepeatMode int getRepeatModeForNavigation() {
@RepeatMode int repeatMode = getRepeatMode(); @RepeatMode int repeatMode = getRepeatMode();
return repeatMode == REPEAT_MODE_ONE ? REPEAT_MODE_OFF : repeatMode; return repeatMode == REPEAT_MODE_ONE ? REPEAT_MODE_OFF : repeatMode;
......
...@@ -1044,29 +1044,31 @@ public final class C { ...@@ -1044,29 +1044,31 @@ public final class C {
*/ */
@UnstableApi public static final int STEREO_MODE_STEREO_MESH = 3; @UnstableApi public static final int STEREO_MODE_STEREO_MESH = 3;
// LINT.IfChange(color_space)
/** /**
* Video colorspaces. One of {@link Format#NO_VALUE}, {@link #COLOR_SPACE_BT709}, {@link * Video colorspaces. One of {@link Format#NO_VALUE}, {@link #COLOR_SPACE_BT601}, {@link
* #COLOR_SPACE_BT601} or {@link #COLOR_SPACE_BT2020}. * #COLOR_SPACE_BT709} or {@link #COLOR_SPACE_BT2020}.
*/ */
@UnstableApi @UnstableApi
@Documented @Documented
@Retention(RetentionPolicy.SOURCE) @Retention(RetentionPolicy.SOURCE)
@Target(TYPE_USE) @Target(TYPE_USE)
@IntDef({Format.NO_VALUE, COLOR_SPACE_BT709, COLOR_SPACE_BT601, COLOR_SPACE_BT2020}) @IntDef({Format.NO_VALUE, COLOR_SPACE_BT601, COLOR_SPACE_BT709, COLOR_SPACE_BT2020})
public @interface ColorSpace {} public @interface ColorSpace {}
/** /**
* @see MediaFormat#COLOR_STANDARD_BT709
*/
@UnstableApi public static final int COLOR_SPACE_BT709 = MediaFormat.COLOR_STANDARD_BT709;
/**
* @see MediaFormat#COLOR_STANDARD_BT601_PAL * @see MediaFormat#COLOR_STANDARD_BT601_PAL
*/ */
@UnstableApi public static final int COLOR_SPACE_BT601 = MediaFormat.COLOR_STANDARD_BT601_PAL; @UnstableApi public static final int COLOR_SPACE_BT601 = MediaFormat.COLOR_STANDARD_BT601_PAL;
/** /**
* @see MediaFormat#COLOR_STANDARD_BT709
*/
@UnstableApi public static final int COLOR_SPACE_BT709 = MediaFormat.COLOR_STANDARD_BT709;
/**
* @see MediaFormat#COLOR_STANDARD_BT2020 * @see MediaFormat#COLOR_STANDARD_BT2020
*/ */
@UnstableApi public static final int COLOR_SPACE_BT2020 = MediaFormat.COLOR_STANDARD_BT2020; @UnstableApi public static final int COLOR_SPACE_BT2020 = MediaFormat.COLOR_STANDARD_BT2020;
// LINT.IfChange(color_transfer)
/** /**
* Video color transfer characteristics. One of {@link Format#NO_VALUE}, {@link * Video color transfer characteristics. One of {@link Format#NO_VALUE}, {@link
* #COLOR_TRANSFER_SDR}, {@link #COLOR_TRANSFER_ST2084} or {@link #COLOR_TRANSFER_HLG}. * #COLOR_TRANSFER_SDR}, {@link #COLOR_TRANSFER_ST2084} or {@link #COLOR_TRANSFER_HLG}.
...@@ -1090,6 +1092,7 @@ public final class C { ...@@ -1090,6 +1092,7 @@ public final class C {
*/ */
@UnstableApi public static final int COLOR_TRANSFER_HLG = MediaFormat.COLOR_TRANSFER_HLG; @UnstableApi public static final int COLOR_TRANSFER_HLG = MediaFormat.COLOR_TRANSFER_HLG;
// LINT.IfChange(color_range)
/** /**
* Video color range. One of {@link Format#NO_VALUE}, {@link #COLOR_RANGE_LIMITED} or {@link * Video color range. One of {@link Format#NO_VALUE}, {@link #COLOR_RANGE_LIMITED} or {@link
* #COLOR_RANGE_FULL}. * #COLOR_RANGE_FULL}.
......
...@@ -28,10 +28,23 @@ import java.lang.annotation.Target; ...@@ -28,10 +28,23 @@ import java.lang.annotation.Target;
import java.util.Arrays; import java.util.Arrays;
import org.checkerframework.dataflow.qual.Pure; import org.checkerframework.dataflow.qual.Pure;
/** Stores color info. */ /**
* Stores color info.
*
* <p>When a {@code null} {@code ColorInfo} instance is used, this often represents a generic {@link
* #SDR_BT709_LIMITED} instance.
*/
@UnstableApi @UnstableApi
public final class ColorInfo implements Bundleable { public final class ColorInfo implements Bundleable {
/** Color info representing SDR BT.709 limited range, which is a common SDR video color format. */
public static final ColorInfo SDR_BT709_LIMITED =
new ColorInfo(
C.COLOR_SPACE_BT709,
C.COLOR_RANGE_LIMITED,
C.COLOR_TRANSFER_SDR,
/* hdrStaticInfo= */ null);
/** /**
* Returns the {@link C.ColorSpace} corresponding to the given ISO color primary code, as per * Returns the {@link C.ColorSpace} corresponding to the given ISO color primary code, as per
* table A.7.21.1 in Rec. ITU-T T.832 (03/2009), or {@link Format#NO_VALUE} if no mapping can be * table A.7.21.1 in Rec. ITU-T T.832 (03/2009), or {@link Format#NO_VALUE} if no mapping can be
...@@ -76,6 +89,13 @@ public final class ColorInfo implements Bundleable { ...@@ -76,6 +89,13 @@ public final class ColorInfo implements Bundleable {
} }
} }
/** Returns whether the {@code ColorInfo} uses an HDR {@link C.ColorTransfer}. */
public static boolean isTransferHdr(@Nullable ColorInfo colorInfo) {
return colorInfo != null
&& colorInfo.colorTransfer != Format.NO_VALUE
&& colorInfo.colorTransfer != C.COLOR_TRANSFER_SDR;
}
/** /**
* The color space of the video. Valid values are {@link C#COLOR_SPACE_BT601}, {@link * The color space of the video. Valid values are {@link C#COLOR_SPACE_BT601}, {@link
* C#COLOR_SPACE_BT709}, {@link C#COLOR_SPACE_BT2020} or {@link Format#NO_VALUE} if unknown. * C#COLOR_SPACE_BT709}, {@link C#COLOR_SPACE_BT2020} or {@link Format#NO_VALUE} if unknown.
......
/*
* Copyright 2022 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 androidx.media3.common;
import android.view.SurfaceView;
import androidx.annotation.Nullable;
import androidx.media3.common.util.UnstableApi;
/** Provider for views to show diagnostic information during a transformation, for debugging. */
@UnstableApi
public interface DebugViewProvider {
/** Debug view provider that doesn't show any debug info. */
DebugViewProvider NONE = (int width, int height) -> null;
/**
* Returns a new surface view to show a preview of transformer output with the given width/height
* in pixels, or {@code null} if no debug information should be shown.
*
* <p>This method may be called on an arbitrary thread.
*/
@Nullable
SurfaceView getDebugPreviewSurfaceView(int width, int height);
}
/*
* Copyright 2022 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 androidx.media3.common;
import androidx.media3.common.util.UnstableApi;
/** Marker interface for a video frame effect. */
@UnstableApi
public interface Effect {}
...@@ -22,6 +22,7 @@ import android.util.SparseBooleanArray; ...@@ -22,6 +22,7 @@ import android.util.SparseBooleanArray;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.media3.common.util.UnstableApi; import androidx.media3.common.util.UnstableApi;
import androidx.media3.common.util.Util; import androidx.media3.common.util.Util;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
/** /**
* A set of integer flags. * A set of integer flags.
...@@ -53,6 +54,7 @@ public final class FlagSet { ...@@ -53,6 +54,7 @@ public final class FlagSet {
* @return This builder. * @return This builder.
* @throws IllegalStateException If {@link #build()} has already been called. * @throws IllegalStateException If {@link #build()} has already been called.
*/ */
@CanIgnoreReturnValue
public Builder add(int flag) { public Builder add(int flag) {
checkState(!buildCalled); checkState(!buildCalled);
flags.append(flag, /* value= */ true); flags.append(flag, /* value= */ true);
...@@ -67,6 +69,7 @@ public final class FlagSet { ...@@ -67,6 +69,7 @@ public final class FlagSet {
* @return This builder. * @return This builder.
* @throws IllegalStateException If {@link #build()} has already been called. * @throws IllegalStateException If {@link #build()} has already been called.
*/ */
@CanIgnoreReturnValue
public Builder addIf(int flag, boolean condition) { public Builder addIf(int flag, boolean condition) {
if (condition) { if (condition) {
return add(flag); return add(flag);
...@@ -81,6 +84,7 @@ public final class FlagSet { ...@@ -81,6 +84,7 @@ public final class FlagSet {
* @return This builder. * @return This builder.
* @throws IllegalStateException If {@link #build()} has already been called. * @throws IllegalStateException If {@link #build()} has already been called.
*/ */
@CanIgnoreReturnValue
public Builder addAll(int... flags) { public Builder addAll(int... flags) {
for (int flag : flags) { for (int flag : flags) {
add(flag); add(flag);
...@@ -95,6 +99,7 @@ public final class FlagSet { ...@@ -95,6 +99,7 @@ public final class FlagSet {
* @return This builder. * @return This builder.
* @throws IllegalStateException If {@link #build()} has already been called. * @throws IllegalStateException If {@link #build()} has already been called.
*/ */
@CanIgnoreReturnValue
public Builder addAll(FlagSet flags) { public Builder addAll(FlagSet flags) {
for (int i = 0; i < flags.size(); i++) { for (int i = 0; i < flags.size(); i++) {
add(flags.get(i)); add(flags.get(i));
...@@ -109,6 +114,7 @@ public final class FlagSet { ...@@ -109,6 +114,7 @@ public final class FlagSet {
* @return This builder. * @return This builder.
* @throws IllegalStateException If {@link #build()} has already been called. * @throws IllegalStateException If {@link #build()} has already been called.
*/ */
@CanIgnoreReturnValue
public Builder remove(int flag) { public Builder remove(int flag) {
checkState(!buildCalled); checkState(!buildCalled);
flags.delete(flag); flags.delete(flag);
...@@ -123,6 +129,7 @@ public final class FlagSet { ...@@ -123,6 +129,7 @@ public final class FlagSet {
* @return This builder. * @return This builder.
* @throws IllegalStateException If {@link #build()} has already been called. * @throws IllegalStateException If {@link #build()} has already been called.
*/ */
@CanIgnoreReturnValue
public Builder removeIf(int flag, boolean condition) { public Builder removeIf(int flag, boolean condition) {
if (condition) { if (condition) {
return remove(flag); return remove(flag);
...@@ -137,6 +144,7 @@ public final class FlagSet { ...@@ -137,6 +144,7 @@ public final class FlagSet {
* @return This builder. * @return This builder.
* @throws IllegalStateException If {@link #build()} has already been called. * @throws IllegalStateException If {@link #build()} has already been called.
*/ */
@CanIgnoreReturnValue
public Builder removeAll(int... flags) { public Builder removeAll(int... flags) {
for (int flag : flags) { for (int flag : flags) {
remove(flag); remove(flag);
......
...@@ -23,6 +23,7 @@ import android.view.TextureView; ...@@ -23,6 +23,7 @@ import android.view.TextureView;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.media3.common.text.Cue; import androidx.media3.common.text.Cue;
import androidx.media3.common.text.CueGroup; import androidx.media3.common.text.CueGroup;
import androidx.media3.common.util.Size;
import androidx.media3.common.util.UnstableApi; import androidx.media3.common.util.UnstableApi;
import java.util.List; import java.util.List;
...@@ -759,6 +760,12 @@ public class ForwardingPlayer implements Player { ...@@ -759,6 +760,12 @@ public class ForwardingPlayer implements Player {
return player.getVideoSize(); return player.getVideoSize();
} }
/** Calls {@link Player#getSurfaceSize()} on the delegate and returns the result. */
@Override
public Size getSurfaceSize() {
return player.getSurfaceSize();
}
/** Calls {@link Player#clearVideoSurface()} on the delegate. */ /** Calls {@link Player#clearVideoSurface()} on the delegate. */
@Override @Override
public void clearVideoSurface() { public void clearVideoSurface() {
......
/*
* Copyright 2022 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 androidx.media3.common;
import static androidx.media3.common.util.Assertions.checkArgument;
import androidx.media3.common.util.UnstableApi;
/** Value class specifying information about a decoded video frame. */
@UnstableApi
public class FrameInfo {
/** The width of the frame, in pixels. */
public final int width;
/** The height of the frame, in pixels. */
public final int height;
/** The ratio of width over height for each pixel. */
public final float pixelWidthHeightRatio;
/**
* An offset in microseconds that is part of the input timestamps and should be ignored for
* processing but added back to the output timestamps.
*
* <p>The offset stays constant within a stream but changes in between streams to ensure that
* frame timestamps are always monotonically increasing.
*/
public final long streamOffsetUs;
// TODO(b/227624622): Add color space information for HDR.
/**
* Creates a new instance.
*
* @param width The width of the frame, in pixels.
* @param height The height of the frame, in pixels.
* @param pixelWidthHeightRatio The ratio of width over height for each pixel.
* @param streamOffsetUs An offset in microseconds that is part of the input timestamps and should
* be ignored for processing but added back to the output timestamps.
*/
public FrameInfo(int width, int height, float pixelWidthHeightRatio, long streamOffsetUs) {
checkArgument(width > 0, "width must be positive, but is: " + width);
checkArgument(height > 0, "height must be positive, but is: " + height);
this.width = width;
this.height = height;
this.pixelWidthHeightRatio = pixelWidthHeightRatio;
this.streamOffsetUs = streamOffsetUs;
}
}
...@@ -13,9 +13,8 @@ ...@@ -13,9 +13,8 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package androidx.media3.transformer; package androidx.media3.common;
import androidx.media3.common.C;
import androidx.media3.common.util.UnstableApi; import androidx.media3.common.util.UnstableApi;
/** Thrown when an exception occurs while applying effects to video frames. */ /** Thrown when an exception occurs while applying effects to video frames. */
...@@ -23,6 +22,26 @@ import androidx.media3.common.util.UnstableApi; ...@@ -23,6 +22,26 @@ import androidx.media3.common.util.UnstableApi;
public final class FrameProcessingException extends Exception { public final class FrameProcessingException extends Exception {
/** /**
* Wraps the given exception in a {@code FrameProcessingException} if it is not already a {@code
* FrameProcessingException} and returns the exception otherwise.
*/
public static FrameProcessingException from(Exception exception) {
return from(exception, /* presentationTimeUs= */ C.TIME_UNSET);
}
/**
* Wraps the given exception in a {@code FrameProcessingException} with the given timestamp if it
* is not already a {@code FrameProcessingException} and returns the exception otherwise.
*/
public static FrameProcessingException from(Exception exception, long presentationTimeUs) {
if (exception instanceof FrameProcessingException) {
return (FrameProcessingException) exception;
} else {
return new FrameProcessingException(exception, presentationTimeUs);
}
}
/**
* The microsecond timestamp of the frame being processed while the exception occurred or {@link * The microsecond timestamp of the frame being processed while the exception occurred or {@link
* C#TIME_UNSET} if unknown. * C#TIME_UNSET} if unknown.
*/ */
......
/*
* Copyright 2022 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 androidx.media3.common;
import android.content.Context;
import android.opengl.EGLExt;
import android.view.Surface;
import androidx.annotation.Nullable;
import androidx.media3.common.util.UnstableApi;
import java.util.List;
/**
* Interface for a frame processor that applies changes to individual video frames.
*
* <p>The changes are specified by {@link Effect} instances passed to {@link Factory#create}.
*
* <p>Manages its input {@link Surface}, which can be accessed via {@link #getInputSurface()}. The
* output {@link Surface} must be set by the caller using {@link
* #setOutputSurfaceInfo(SurfaceInfo)}.
*
* <p>The caller must {@linkplain #registerInputFrame() register} input frames before rendering them
* to the input {@link Surface}.
*/
@UnstableApi
public interface FrameProcessor {
// TODO(b/243036513): Allow effects to be replaced.
/** A factory for {@link FrameProcessor} instances. */
interface Factory {
/**
* Creates a new {@link FrameProcessor} instance.
*
* @param context A {@link Context}.
* @param listener A {@link Listener}.
* @param effects The {@link Effect} instances to apply to each frame.
* @param debugViewProvider A {@link DebugViewProvider}.
* @param colorInfo The {@link ColorInfo} for input and output frames.
* @param releaseFramesAutomatically If {@code true}, the {@link FrameProcessor} will render
* output frames to the {@linkplain #setOutputSurfaceInfo(SurfaceInfo) output surface}
* automatically as {@link FrameProcessor} is done processing them. If {@code false}, the
* {@link FrameProcessor} will block until {@link #releaseOutputFrame(long)} is called, to
* render or drop the frame.
* @return A new instance.
* @throws FrameProcessingException If a problem occurs while creating the {@link
* FrameProcessor}.
*/
FrameProcessor create(
Context context,
Listener listener,
List<Effect> effects,
DebugViewProvider debugViewProvider,
ColorInfo colorInfo,
boolean releaseFramesAutomatically)
throws FrameProcessingException;
}
/**
* Listener for asynchronous frame processing events.
*
* <p>All listener methods must be called from the same thread.
*/
interface Listener {
/**
* Called when the output size changes.
*
* <p>The output size is the frame size in pixels after applying all {@linkplain Effect
* effects}.
*
* <p>The output size may differ from the size specified using {@link
* #setOutputSurfaceInfo(SurfaceInfo)}.
*/
void onOutputSizeChanged(int width, int height);
/**
* Called when an output frame with the given {@code presentationTimeUs} becomes available.
*
* @param presentationTimeUs The presentation time of the frame, in microseconds.
*/
void onOutputFrameAvailable(long presentationTimeUs);
/**
* Called when an exception occurs during asynchronous frame processing.
*
* <p>If an error occurred, consuming and producing further frames will not work as expected and
* the {@link FrameProcessor} should be released.
*/
void onFrameProcessingError(FrameProcessingException exception);
/** Called after the {@link FrameProcessor} has produced its final output frame. */
void onFrameProcessingEnded();
}
/**
* Indicates the frame should be released immediately after {@link #releaseOutputFrame(long)} is
* invoked.
*/
long RELEASE_OUTPUT_FRAME_IMMEDIATELY = -1;
/** Indicates the frame should be dropped after {@link #releaseOutputFrame(long)} is invoked. */
long DROP_OUTPUT_FRAME = -2;
/** Returns the input {@link Surface}, where {@link FrameProcessor} consumes input frames from. */
Surface getInputSurface();
/**
* Sets information about the input frames.
*
* <p>The new input information is applied from the next frame {@linkplain #registerInputFrame()
* registered} onwards.
*
* <p>Pixels are expanded using the {@link FrameInfo#pixelWidthHeightRatio} so that the output
* frames' pixels have a ratio of 1.
*
* <p>The caller should update {@link FrameInfo#streamOffsetUs} when switching input streams to
* ensure that frame timestamps are always monotonically increasing.
*/
void setInputFrameInfo(FrameInfo inputFrameInfo);
/**
* Informs the {@code FrameProcessor} that a frame will be queued to its input surface.
*
* <p>Must be called before rendering a frame to the frame processor's input surface.
*
* @throws IllegalStateException If called after {@link #signalEndOfInput()} or before {@link
* #setInputFrameInfo(FrameInfo)}.
*/
void registerInputFrame();
/**
* Returns the number of input frames that have been {@linkplain #registerInputFrame() registered}
* but not processed off the {@linkplain #getInputSurface() input surface} yet.
*/
int getPendingInputFrameCount();
/**
* Sets the output surface and supporting information. When output frames are released and not
* dropped, they will be rendered to this output {@link SurfaceInfo}.
*
* <p>The new output {@link SurfaceInfo} is applied from the next output frame rendered onwards.
* If the output {@link SurfaceInfo} is {@code null}, the {@code FrameProcessor} will stop
* rendering pending frames and resume rendering once a non-null {@link SurfaceInfo} is set.
*
* <p>If the dimensions given in {@link SurfaceInfo} do not match the {@linkplain
* Listener#onOutputSizeChanged(int,int) output size after applying the final effect} the frames
* are resized before rendering to the surface and letter/pillar-boxing is applied.
*
* <p>The caller is responsible for tracking the lifecycle of the {@link SurfaceInfo#surface}
* including calling this method with a new surface if it is destroyed. When this method returns,
* the previous output surface is no longer being used and can safely be released by the caller.
*/
void setOutputSurfaceInfo(@Nullable SurfaceInfo outputSurfaceInfo);
/**
* Releases the oldest unreleased output frame that has become {@linkplain
* Listener#onOutputFrameAvailable(long) available} at the given {@code releaseTimeNs}.
*
* <p>This will either render the output frame to the {@linkplain #setOutputSurfaceInfo output
* surface}, or drop the frame, per {@code releaseTimeNs}.
*
* <p>This method must only be called if {@code releaseFramesAutomatically} was set to {@code
* false} using the {@link Factory} and should be called exactly once for each frame that becomes
* {@linkplain Listener#onOutputFrameAvailable(long) available}.
*
* <p>The {@code releaseTimeNs} may be passed to {@link EGLExt#eglPresentationTimeANDROID}
* depending on the implementation.
*
* @param releaseTimeNs The release time to use for the frame, in nanoseconds. The release time
* can be before of after the current system time. Use {@link #DROP_OUTPUT_FRAME} to drop the
* frame, or {@link #RELEASE_OUTPUT_FRAME_IMMEDIATELY} to release the frame immediately.
*/
void releaseOutputFrame(long releaseTimeNs);
/**
* Informs the {@code FrameProcessor} that no further input frames should be accepted.
*
* @throws IllegalStateException If called more than once.
*/
void signalEndOfInput();
/**
* Releases all resources.
*
* <p>If the frame processor is released before it has {@linkplain
* Listener#onFrameProcessingEnded() ended}, it will attempt to cancel processing any input frames
* that have already become available. Input frames that become available after release are
* ignored.
*
* <p>This method blocks until all resources are released or releasing times out.
*/
void release();
}
/*
* Copyright 2022 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 androidx.media3.common;
import android.media.MediaPlayer;
import android.os.Looper;
import androidx.media3.common.util.UnstableApi;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
/** A {@link Player} wrapper for the legacy Android platform {@link MediaPlayer}. */
@UnstableApi
public final class LegacyMediaPlayerWrapper extends SimpleBasePlayer {
private final MediaPlayer player;
private boolean playWhenReady;
/**
* Creates the {@link MediaPlayer} wrapper.
*
* @param looper The {@link Looper} used to call all methods on.
*/
public LegacyMediaPlayerWrapper(Looper looper) {
super(looper);
this.player = new MediaPlayer();
}
@Override
protected State getState() {
return new State.Builder()
.setAvailableCommands(new Commands.Builder().addAll(Player.COMMAND_PLAY_PAUSE).build())
.setPlayWhenReady(playWhenReady, Player.PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST)
.build();
}
@Override
protected ListenableFuture<?> handleSetPlayWhenReady(boolean playWhenReady) {
this.playWhenReady = playWhenReady;
// TODO: Only call these methods if the player is in Started or Paused state.
if (playWhenReady) {
player.start();
} else {
player.pause();
}
return Futures.immediateVoidFuture();
}
}
...@@ -29,11 +29,11 @@ public final class MediaLibraryInfo { ...@@ -29,11 +29,11 @@ public final class MediaLibraryInfo {
/** The version of the library expressed as a string, for example "1.2.3" or "1.2.3-beta01". */ /** The version of the library expressed as a string, for example "1.2.3" or "1.2.3-beta01". */
// Intentionally hardcoded. Do not derive from other constants (e.g. VERSION_INT) or vice versa. // Intentionally hardcoded. Do not derive from other constants (e.g. VERSION_INT) or vice versa.
public static final String VERSION = "1.0.0-beta02"; public static final String VERSION = "1.0.0-beta03";
/** The version of the library expressed as {@code TAG + "/" + VERSION}. */ /** The version of the library expressed as {@code TAG + "/" + VERSION}. */
// Intentionally hardcoded. Do not derive from other constants (e.g. VERSION) or vice versa. // Intentionally hardcoded. Do not derive from other constants (e.g. VERSION) or vice versa.
public static final String VERSION_SLASHY = "AndroidXMedia3/1.0.0-beta02"; public static final String VERSION_SLASHY = "AndroidXMedia3/1.0.0-beta03";
/** /**
* The version of the library expressed as an integer, for example 1002003300. * The version of the library expressed as an integer, for example 1002003300.
...@@ -47,7 +47,7 @@ public final class MediaLibraryInfo { ...@@ -47,7 +47,7 @@ public final class MediaLibraryInfo {
* (123-045-006-3-00). * (123-045-006-3-00).
*/ */
// Intentionally hardcoded. Do not derive from other constants (e.g. VERSION) or vice versa. // Intentionally hardcoded. Do not derive from other constants (e.g. VERSION) or vice versa.
public static final int VERSION_INT = 1_000_000_1_02; public static final int VERSION_INT = 1_000_000_1_03;
/** Whether the library was compiled with {@link Assertions} checks enabled. */ /** Whether the library was compiled with {@link Assertions} checks enabled. */
public static final boolean ASSERTIONS_ENABLED = true; public static final boolean ASSERTIONS_ENABLED = true;
......
...@@ -29,6 +29,7 @@ import androidx.annotation.Nullable; ...@@ -29,6 +29,7 @@ import androidx.annotation.Nullable;
import androidx.media3.common.util.UnstableApi; import androidx.media3.common.util.UnstableApi;
import androidx.media3.common.util.Util; import androidx.media3.common.util.Util;
import com.google.common.base.Objects; import com.google.common.base.Objects;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import java.lang.annotation.Documented; import java.lang.annotation.Documented;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;
...@@ -114,30 +115,35 @@ public final class MediaMetadata implements Bundleable { ...@@ -114,30 +115,35 @@ public final class MediaMetadata implements Bundleable {
} }
/** Sets the title. */ /** Sets the title. */
@CanIgnoreReturnValue
public Builder setTitle(@Nullable CharSequence title) { public Builder setTitle(@Nullable CharSequence title) {
this.title = title; this.title = title;
return this; return this;
} }
/** Sets the artist. */ /** Sets the artist. */
@CanIgnoreReturnValue
public Builder setArtist(@Nullable CharSequence artist) { public Builder setArtist(@Nullable CharSequence artist) {
this.artist = artist; this.artist = artist;
return this; return this;
} }
/** Sets the album title. */ /** Sets the album title. */
@CanIgnoreReturnValue
public Builder setAlbumTitle(@Nullable CharSequence albumTitle) { public Builder setAlbumTitle(@Nullable CharSequence albumTitle) {
this.albumTitle = albumTitle; this.albumTitle = albumTitle;
return this; return this;
} }
/** Sets the album artist. */ /** Sets the album artist. */
@CanIgnoreReturnValue
public Builder setAlbumArtist(@Nullable CharSequence albumArtist) { public Builder setAlbumArtist(@Nullable CharSequence albumArtist) {
this.albumArtist = albumArtist; this.albumArtist = albumArtist;
return this; return this;
} }
/** Sets the display title. */ /** Sets the display title. */
@CanIgnoreReturnValue
public Builder setDisplayTitle(@Nullable CharSequence displayTitle) { public Builder setDisplayTitle(@Nullable CharSequence displayTitle) {
this.displayTitle = displayTitle; this.displayTitle = displayTitle;
return this; return this;
...@@ -148,24 +154,28 @@ public final class MediaMetadata implements Bundleable { ...@@ -148,24 +154,28 @@ public final class MediaMetadata implements Bundleable {
* *
* <p>This is the secondary title of the media, unrelated to closed captions. * <p>This is the secondary title of the media, unrelated to closed captions.
*/ */
@CanIgnoreReturnValue
public Builder setSubtitle(@Nullable CharSequence subtitle) { public Builder setSubtitle(@Nullable CharSequence subtitle) {
this.subtitle = subtitle; this.subtitle = subtitle;
return this; return this;
} }
/** Sets the description. */ /** Sets the description. */
@CanIgnoreReturnValue
public Builder setDescription(@Nullable CharSequence description) { public Builder setDescription(@Nullable CharSequence description) {
this.description = description; this.description = description;
return this; return this;
} }
/** Sets the user {@link Rating}. */ /** Sets the user {@link Rating}. */
@CanIgnoreReturnValue
public Builder setUserRating(@Nullable Rating userRating) { public Builder setUserRating(@Nullable Rating userRating) {
this.userRating = userRating; this.userRating = userRating;
return this; return this;
} }
/** Sets the overall {@link Rating}. */ /** Sets the overall {@link Rating}. */
@CanIgnoreReturnValue
public Builder setOverallRating(@Nullable Rating overallRating) { public Builder setOverallRating(@Nullable Rating overallRating) {
this.overallRating = overallRating; this.overallRating = overallRating;
return this; return this;
...@@ -175,6 +185,7 @@ public final class MediaMetadata implements Bundleable { ...@@ -175,6 +185,7 @@ public final class MediaMetadata implements Bundleable {
* @deprecated Use {@link #setArtworkData(byte[] data, Integer pictureType)} or {@link * @deprecated Use {@link #setArtworkData(byte[] data, Integer pictureType)} or {@link
* #maybeSetArtworkData(byte[] data, int pictureType)}, providing a {@link PictureType}. * #maybeSetArtworkData(byte[] data, int pictureType)}, providing a {@link PictureType}.
*/ */
@CanIgnoreReturnValue
@UnstableApi @UnstableApi
@Deprecated @Deprecated
public Builder setArtworkData(@Nullable byte[] artworkData) { public Builder setArtworkData(@Nullable byte[] artworkData) {
...@@ -185,6 +196,7 @@ public final class MediaMetadata implements Bundleable { ...@@ -185,6 +196,7 @@ public final class MediaMetadata implements Bundleable {
* Sets the artwork data as a compressed byte array with an associated {@link PictureType * Sets the artwork data as a compressed byte array with an associated {@link PictureType
* artworkDataType}. * artworkDataType}.
*/ */
@CanIgnoreReturnValue
public Builder setArtworkData( public Builder setArtworkData(
@Nullable byte[] artworkData, @Nullable @PictureType Integer artworkDataType) { @Nullable byte[] artworkData, @Nullable @PictureType Integer artworkDataType) {
this.artworkData = artworkData == null ? null : artworkData.clone(); this.artworkData = artworkData == null ? null : artworkData.clone();
...@@ -200,6 +212,7 @@ public final class MediaMetadata implements Bundleable { ...@@ -200,6 +212,7 @@ public final class MediaMetadata implements Bundleable {
* <p>Use {@link #setArtworkData(byte[], Integer)} to set the artwork data without checking the * <p>Use {@link #setArtworkData(byte[], Integer)} to set the artwork data without checking the
* {@link PictureType}. * {@link PictureType}.
*/ */
@CanIgnoreReturnValue
public Builder maybeSetArtworkData(byte[] artworkData, @PictureType int artworkDataType) { public Builder maybeSetArtworkData(byte[] artworkData, @PictureType int artworkDataType) {
if (this.artworkData == null if (this.artworkData == null
|| Util.areEqual(artworkDataType, PICTURE_TYPE_FRONT_COVER) || Util.areEqual(artworkDataType, PICTURE_TYPE_FRONT_COVER)
...@@ -211,30 +224,35 @@ public final class MediaMetadata implements Bundleable { ...@@ -211,30 +224,35 @@ public final class MediaMetadata implements Bundleable {
} }
/** Sets the artwork {@link Uri}. */ /** Sets the artwork {@link Uri}. */
@CanIgnoreReturnValue
public Builder setArtworkUri(@Nullable Uri artworkUri) { public Builder setArtworkUri(@Nullable Uri artworkUri) {
this.artworkUri = artworkUri; this.artworkUri = artworkUri;
return this; return this;
} }
/** Sets the track number. */ /** Sets the track number. */
@CanIgnoreReturnValue
public Builder setTrackNumber(@Nullable Integer trackNumber) { public Builder setTrackNumber(@Nullable Integer trackNumber) {
this.trackNumber = trackNumber; this.trackNumber = trackNumber;
return this; return this;
} }
/** Sets the total number of tracks. */ /** Sets the total number of tracks. */
@CanIgnoreReturnValue
public Builder setTotalTrackCount(@Nullable Integer totalTrackCount) { public Builder setTotalTrackCount(@Nullable Integer totalTrackCount) {
this.totalTrackCount = totalTrackCount; this.totalTrackCount = totalTrackCount;
return this; return this;
} }
/** Sets the {@link FolderType}. */ /** Sets the {@link FolderType}. */
@CanIgnoreReturnValue
public Builder setFolderType(@Nullable @FolderType Integer folderType) { public Builder setFolderType(@Nullable @FolderType Integer folderType) {
this.folderType = folderType; this.folderType = folderType;
return this; return this;
} }
/** Sets whether the media is playable. */ /** Sets whether the media is playable. */
@CanIgnoreReturnValue
public Builder setIsPlayable(@Nullable Boolean isPlayable) { public Builder setIsPlayable(@Nullable Boolean isPlayable) {
this.isPlayable = isPlayable; this.isPlayable = isPlayable;
return this; return this;
...@@ -243,6 +261,7 @@ public final class MediaMetadata implements Bundleable { ...@@ -243,6 +261,7 @@ public final class MediaMetadata implements Bundleable {
/** /**
* @deprecated Use {@link #setRecordingYear(Integer)} instead. * @deprecated Use {@link #setRecordingYear(Integer)} instead.
*/ */
@CanIgnoreReturnValue
@UnstableApi @UnstableApi
@Deprecated @Deprecated
public Builder setYear(@Nullable Integer year) { public Builder setYear(@Nullable Integer year) {
...@@ -250,6 +269,7 @@ public final class MediaMetadata implements Bundleable { ...@@ -250,6 +269,7 @@ public final class MediaMetadata implements Bundleable {
} }
/** Sets the year of the recording date. */ /** Sets the year of the recording date. */
@CanIgnoreReturnValue
public Builder setRecordingYear(@Nullable Integer recordingYear) { public Builder setRecordingYear(@Nullable Integer recordingYear) {
this.recordingYear = recordingYear; this.recordingYear = recordingYear;
return this; return this;
...@@ -260,6 +280,7 @@ public final class MediaMetadata implements Bundleable { ...@@ -260,6 +280,7 @@ public final class MediaMetadata implements Bundleable {
* *
* <p>Value should be between 1 and 12. * <p>Value should be between 1 and 12.
*/ */
@CanIgnoreReturnValue
public Builder setRecordingMonth( public Builder setRecordingMonth(
@Nullable @IntRange(from = 1, to = 12) Integer recordingMonth) { @Nullable @IntRange(from = 1, to = 12) Integer recordingMonth) {
this.recordingMonth = recordingMonth; this.recordingMonth = recordingMonth;
...@@ -271,12 +292,14 @@ public final class MediaMetadata implements Bundleable { ...@@ -271,12 +292,14 @@ public final class MediaMetadata implements Bundleable {
* *
* <p>Value should be between 1 and 31. * <p>Value should be between 1 and 31.
*/ */
@CanIgnoreReturnValue
public Builder setRecordingDay(@Nullable @IntRange(from = 1, to = 31) Integer recordingDay) { public Builder setRecordingDay(@Nullable @IntRange(from = 1, to = 31) Integer recordingDay) {
this.recordingDay = recordingDay; this.recordingDay = recordingDay;
return this; return this;
} }
/** Sets the year of the release date. */ /** Sets the year of the release date. */
@CanIgnoreReturnValue
public Builder setReleaseYear(@Nullable Integer releaseYear) { public Builder setReleaseYear(@Nullable Integer releaseYear) {
this.releaseYear = releaseYear; this.releaseYear = releaseYear;
return this; return this;
...@@ -287,6 +310,7 @@ public final class MediaMetadata implements Bundleable { ...@@ -287,6 +310,7 @@ public final class MediaMetadata implements Bundleable {
* *
* <p>Value should be between 1 and 12. * <p>Value should be between 1 and 12.
*/ */
@CanIgnoreReturnValue
public Builder setReleaseMonth(@Nullable @IntRange(from = 1, to = 12) Integer releaseMonth) { public Builder setReleaseMonth(@Nullable @IntRange(from = 1, to = 12) Integer releaseMonth) {
this.releaseMonth = releaseMonth; this.releaseMonth = releaseMonth;
return this; return this;
...@@ -297,60 +321,70 @@ public final class MediaMetadata implements Bundleable { ...@@ -297,60 +321,70 @@ public final class MediaMetadata implements Bundleable {
* *
* <p>Value should be between 1 and 31. * <p>Value should be between 1 and 31.
*/ */
@CanIgnoreReturnValue
public Builder setReleaseDay(@Nullable @IntRange(from = 1, to = 31) Integer releaseDay) { public Builder setReleaseDay(@Nullable @IntRange(from = 1, to = 31) Integer releaseDay) {
this.releaseDay = releaseDay; this.releaseDay = releaseDay;
return this; return this;
} }
/** Sets the writer. */ /** Sets the writer. */
@CanIgnoreReturnValue
public Builder setWriter(@Nullable CharSequence writer) { public Builder setWriter(@Nullable CharSequence writer) {
this.writer = writer; this.writer = writer;
return this; return this;
} }
/** Sets the composer. */ /** Sets the composer. */
@CanIgnoreReturnValue
public Builder setComposer(@Nullable CharSequence composer) { public Builder setComposer(@Nullable CharSequence composer) {
this.composer = composer; this.composer = composer;
return this; return this;
} }
/** Sets the conductor. */ /** Sets the conductor. */
@CanIgnoreReturnValue
public Builder setConductor(@Nullable CharSequence conductor) { public Builder setConductor(@Nullable CharSequence conductor) {
this.conductor = conductor; this.conductor = conductor;
return this; return this;
} }
/** Sets the disc number. */ /** Sets the disc number. */
@CanIgnoreReturnValue
public Builder setDiscNumber(@Nullable Integer discNumber) { public Builder setDiscNumber(@Nullable Integer discNumber) {
this.discNumber = discNumber; this.discNumber = discNumber;
return this; return this;
} }
/** Sets the total number of discs. */ /** Sets the total number of discs. */
@CanIgnoreReturnValue
public Builder setTotalDiscCount(@Nullable Integer totalDiscCount) { public Builder setTotalDiscCount(@Nullable Integer totalDiscCount) {
this.totalDiscCount = totalDiscCount; this.totalDiscCount = totalDiscCount;
return this; return this;
} }
/** Sets the genre. */ /** Sets the genre. */
@CanIgnoreReturnValue
public Builder setGenre(@Nullable CharSequence genre) { public Builder setGenre(@Nullable CharSequence genre) {
this.genre = genre; this.genre = genre;
return this; return this;
} }
/** Sets the compilation. */ /** Sets the compilation. */
@CanIgnoreReturnValue
public Builder setCompilation(@Nullable CharSequence compilation) { public Builder setCompilation(@Nullable CharSequence compilation) {
this.compilation = compilation; this.compilation = compilation;
return this; return this;
} }
/** Sets the name of the station streaming the media. */ /** Sets the name of the station streaming the media. */
@CanIgnoreReturnValue
public Builder setStation(@Nullable CharSequence station) { public Builder setStation(@Nullable CharSequence station) {
this.station = station; this.station = station;
return this; return this;
} }
/** Sets the extras {@link Bundle}. */ /** Sets the extras {@link Bundle}. */
@CanIgnoreReturnValue
public Builder setExtras(@Nullable Bundle extras) { public Builder setExtras(@Nullable Bundle extras) {
this.extras = extras; this.extras = extras;
return this; return this;
...@@ -365,6 +399,7 @@ public final class MediaMetadata implements Bundleable { ...@@ -365,6 +399,7 @@ public final class MediaMetadata implements Bundleable {
* <p>In the event that multiple {@link Metadata.Entry} objects within the {@link Metadata} * <p>In the event that multiple {@link Metadata.Entry} objects within the {@link Metadata}
* relate to the same {@link MediaMetadata} field, then the last one will be used. * relate to the same {@link MediaMetadata} field, then the last one will be used.
*/ */
@CanIgnoreReturnValue
@UnstableApi @UnstableApi
public Builder populateFromMetadata(Metadata metadata) { public Builder populateFromMetadata(Metadata metadata) {
for (int i = 0; i < metadata.length(); i++) { for (int i = 0; i < metadata.length(); i++) {
...@@ -384,6 +419,7 @@ public final class MediaMetadata implements Bundleable { ...@@ -384,6 +419,7 @@ public final class MediaMetadata implements Bundleable {
* <p>In the event that multiple {@link Metadata.Entry} objects within any of the {@link * <p>In the event that multiple {@link Metadata.Entry} objects within any of the {@link
* Metadata} relate to the same {@link MediaMetadata} field, then the last one will be used. * Metadata} relate to the same {@link MediaMetadata} field, then the last one will be used.
*/ */
@CanIgnoreReturnValue
@UnstableApi @UnstableApi
public Builder populateFromMetadata(List<Metadata> metadataList) { public Builder populateFromMetadata(List<Metadata> metadataList) {
for (int i = 0; i < metadataList.size(); i++) { for (int i = 0; i < metadataList.size(); i++) {
...@@ -397,6 +433,7 @@ public final class MediaMetadata implements Bundleable { ...@@ -397,6 +433,7 @@ public final class MediaMetadata implements Bundleable {
} }
/** Populates all the fields from {@code mediaMetadata}, provided they are non-null. */ /** Populates all the fields from {@code mediaMetadata}, provided they are non-null. */
@CanIgnoreReturnValue
@UnstableApi @UnstableApi
public Builder populate(@Nullable MediaMetadata mediaMetadata) { public Builder populate(@Nullable MediaMetadata mediaMetadata) {
if (mediaMetadata == null) { if (mediaMetadata == null) {
......
...@@ -20,6 +20,7 @@ import android.os.Parcelable; ...@@ -20,6 +20,7 @@ import android.os.Parcelable;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.media3.common.util.UnstableApi; import androidx.media3.common.util.UnstableApi;
import androidx.media3.common.util.Util; import androidx.media3.common.util.Util;
import com.google.common.primitives.Longs;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
...@@ -61,11 +62,28 @@ public final class Metadata implements Parcelable { ...@@ -61,11 +62,28 @@ public final class Metadata implements Parcelable {
} }
private final Entry[] entries; private final Entry[] entries;
/**
* The presentation time of the metadata, in microseconds.
*
* <p>This time is an offset from the start of the current {@link Timeline.Period}.
*
* <p>This time is {@link C#TIME_UNSET} when not known or undefined.
*/
public final long presentationTimeUs;
/** /**
* @param entries The metadata entries. * @param entries The metadata entries.
*/ */
public Metadata(Entry... entries) { public Metadata(Entry... entries) {
this(/* presentationTimeUs= */ C.TIME_UNSET, entries);
}
/**
* @param presentationTimeUs The presentation time for the metadata entries.
* @param entries The metadata entries.
*/
public Metadata(long presentationTimeUs, Entry... entries) {
this.presentationTimeUs = presentationTimeUs;
this.entries = entries; this.entries = entries;
} }
...@@ -73,7 +91,15 @@ public final class Metadata implements Parcelable { ...@@ -73,7 +91,15 @@ public final class Metadata implements Parcelable {
* @param entries The metadata entries. * @param entries The metadata entries.
*/ */
public Metadata(List<? extends Entry> entries) { public Metadata(List<? extends Entry> entries) {
this.entries = entries.toArray(new Entry[0]); this(entries.toArray(new Entry[0]));
}
/**
* @param presentationTimeUs The presentation time for the metadata entries.
* @param entries The metadata entries.
*/
public Metadata(long presentationTimeUs, List<? extends Entry> entries) {
this(presentationTimeUs, entries.toArray(new Entry[0]));
} }
/* package */ Metadata(Parcel in) { /* package */ Metadata(Parcel in) {
...@@ -81,6 +107,7 @@ public final class Metadata implements Parcelable { ...@@ -81,6 +107,7 @@ public final class Metadata implements Parcelable {
for (int i = 0; i < entries.length; i++) { for (int i = 0; i < entries.length; i++) {
entries[i] = in.readParcelable(Entry.class.getClassLoader()); entries[i] = in.readParcelable(Entry.class.getClassLoader());
} }
presentationTimeUs = in.readLong();
} }
/** Returns the number of metadata entries. */ /** Returns the number of metadata entries. */
...@@ -123,7 +150,21 @@ public final class Metadata implements Parcelable { ...@@ -123,7 +150,21 @@ public final class Metadata implements Parcelable {
if (entriesToAppend.length == 0) { if (entriesToAppend.length == 0) {
return this; return this;
} }
return new Metadata(Util.nullSafeArrayConcatenation(entries, entriesToAppend)); return new Metadata(
presentationTimeUs, Util.nullSafeArrayConcatenation(entries, entriesToAppend));
}
/**
* Returns a copy of this metadata with the specified presentation time.
*
* @param presentationTimeUs The new presentation time, in microseconds.
* @return The metadata instance with the new presentation time.
*/
public Metadata copyWithPresentationTimeUs(long presentationTimeUs) {
if (this.presentationTimeUs == presentationTimeUs) {
return this;
}
return new Metadata(presentationTimeUs, entries);
} }
@Override @Override
...@@ -135,17 +176,21 @@ public final class Metadata implements Parcelable { ...@@ -135,17 +176,21 @@ public final class Metadata implements Parcelable {
return false; return false;
} }
Metadata other = (Metadata) obj; Metadata other = (Metadata) obj;
return Arrays.equals(entries, other.entries); return Arrays.equals(entries, other.entries) && presentationTimeUs == other.presentationTimeUs;
} }
@Override @Override
public int hashCode() { public int hashCode() {
return Arrays.hashCode(entries); int result = Arrays.hashCode(entries);
result = 31 * result + Longs.hashCode(presentationTimeUs);
return result;
} }
@Override @Override
public String toString() { public String toString() {
return "entries=" + Arrays.toString(entries); return "entries="
+ Arrays.toString(entries)
+ (presentationTimeUs == C.TIME_UNSET ? "" : ", presentationTimeUs=" + presentationTimeUs);
} }
// Parcelable implementation. // Parcelable implementation.
...@@ -161,6 +206,7 @@ public final class Metadata implements Parcelable { ...@@ -161,6 +206,7 @@ public final class Metadata implements Parcelable {
for (Entry entry : entries) { for (Entry entry : entries) {
dest.writeParcelable(entry, 0); dest.writeParcelable(entry, 0);
} }
dest.writeLong(presentationTimeUs);
} }
public static final Parcelable.Creator<Metadata> CREATOR = public static final Parcelable.Creator<Metadata> CREATOR =
......
...@@ -91,11 +91,15 @@ public final class MimeTypes { ...@@ -91,11 +91,15 @@ public final class MimeTypes {
public static final String AUDIO_AMR_NB = BASE_TYPE_AUDIO + "/3gpp"; public static final String AUDIO_AMR_NB = BASE_TYPE_AUDIO + "/3gpp";
public static final String AUDIO_AMR_WB = BASE_TYPE_AUDIO + "/amr-wb"; public static final String AUDIO_AMR_WB = BASE_TYPE_AUDIO + "/amr-wb";
public static final String AUDIO_FLAC = BASE_TYPE_AUDIO + "/flac"; public static final String AUDIO_FLAC = BASE_TYPE_AUDIO + "/flac";
public static final String AUDIO_MIDI = BASE_TYPE_AUDIO + "/midi";
public static final String AUDIO_ALAC = BASE_TYPE_AUDIO + "/alac"; public static final String AUDIO_ALAC = BASE_TYPE_AUDIO + "/alac";
public static final String AUDIO_MSGSM = BASE_TYPE_AUDIO + "/gsm"; public static final String AUDIO_MSGSM = BASE_TYPE_AUDIO + "/gsm";
public static final String AUDIO_OGG = BASE_TYPE_AUDIO + "/ogg"; public static final String AUDIO_OGG = BASE_TYPE_AUDIO + "/ogg";
public static final String AUDIO_WAV = BASE_TYPE_AUDIO + "/wav"; public static final String AUDIO_WAV = BASE_TYPE_AUDIO + "/wav";
public static final String AUDIO_MIDI = BASE_TYPE_AUDIO + "/midi";
@UnstableApi
public static final String AUDIO_EXOPLAYER_MIDI = BASE_TYPE_AUDIO + "/x-exoplayer-midi";
@UnstableApi public static final String AUDIO_UNKNOWN = BASE_TYPE_AUDIO + "/x-unknown"; @UnstableApi public static final String AUDIO_UNKNOWN = BASE_TYPE_AUDIO + "/x-unknown";
// text/ MIME types // text/ MIME types
......
...@@ -33,9 +33,11 @@ import androidx.annotation.IntRange; ...@@ -33,9 +33,11 @@ import androidx.annotation.IntRange;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.media3.common.text.Cue; import androidx.media3.common.text.Cue;
import androidx.media3.common.text.CueGroup; import androidx.media3.common.text.CueGroup;
import androidx.media3.common.util.Size;
import androidx.media3.common.util.UnstableApi; import androidx.media3.common.util.UnstableApi;
import androidx.media3.common.util.Util; import androidx.media3.common.util.Util;
import com.google.common.base.Objects; import com.google.common.base.Objects;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import java.lang.annotation.Documented; import java.lang.annotation.Documented;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;
...@@ -406,6 +408,7 @@ public interface Player { ...@@ -406,6 +408,7 @@ public interface Player {
* @return This builder. * @return This builder.
* @throws IllegalStateException If {@link #build()} has already been called. * @throws IllegalStateException If {@link #build()} has already been called.
*/ */
@CanIgnoreReturnValue
public Builder add(@Command int command) { public Builder add(@Command int command) {
flagsBuilder.add(command); flagsBuilder.add(command);
return this; return this;
...@@ -419,6 +422,7 @@ public interface Player { ...@@ -419,6 +422,7 @@ public interface Player {
* @return This builder. * @return This builder.
* @throws IllegalStateException If {@link #build()} has already been called. * @throws IllegalStateException If {@link #build()} has already been called.
*/ */
@CanIgnoreReturnValue
public Builder addIf(@Command int command, boolean condition) { public Builder addIf(@Command int command, boolean condition) {
flagsBuilder.addIf(command, condition); flagsBuilder.addIf(command, condition);
return this; return this;
...@@ -431,6 +435,7 @@ public interface Player { ...@@ -431,6 +435,7 @@ public interface Player {
* @return This builder. * @return This builder.
* @throws IllegalStateException If {@link #build()} has already been called. * @throws IllegalStateException If {@link #build()} has already been called.
*/ */
@CanIgnoreReturnValue
public Builder addAll(@Command int... commands) { public Builder addAll(@Command int... commands) {
flagsBuilder.addAll(commands); flagsBuilder.addAll(commands);
return this; return this;
...@@ -443,6 +448,7 @@ public interface Player { ...@@ -443,6 +448,7 @@ public interface Player {
* @return This builder. * @return This builder.
* @throws IllegalStateException If {@link #build()} has already been called. * @throws IllegalStateException If {@link #build()} has already been called.
*/ */
@CanIgnoreReturnValue
public Builder addAll(Commands commands) { public Builder addAll(Commands commands) {
flagsBuilder.addAll(commands.flags); flagsBuilder.addAll(commands.flags);
return this; return this;
...@@ -454,6 +460,7 @@ public interface Player { ...@@ -454,6 +460,7 @@ public interface Player {
* @return This builder. * @return This builder.
* @throws IllegalStateException If {@link #build()} has already been called. * @throws IllegalStateException If {@link #build()} has already been called.
*/ */
@CanIgnoreReturnValue
public Builder addAllCommands() { public Builder addAllCommands() {
flagsBuilder.addAll(SUPPORTED_COMMANDS); flagsBuilder.addAll(SUPPORTED_COMMANDS);
return this; return this;
...@@ -466,6 +473,7 @@ public interface Player { ...@@ -466,6 +473,7 @@ public interface Player {
* @return This builder. * @return This builder.
* @throws IllegalStateException If {@link #build()} has already been called. * @throws IllegalStateException If {@link #build()} has already been called.
*/ */
@CanIgnoreReturnValue
public Builder remove(@Command int command) { public Builder remove(@Command int command) {
flagsBuilder.remove(command); flagsBuilder.remove(command);
return this; return this;
...@@ -479,6 +487,7 @@ public interface Player { ...@@ -479,6 +487,7 @@ public interface Player {
* @return This builder. * @return This builder.
* @throws IllegalStateException If {@link #build()} has already been called. * @throws IllegalStateException If {@link #build()} has already been called.
*/ */
@CanIgnoreReturnValue
public Builder removeIf(@Command int command, boolean condition) { public Builder removeIf(@Command int command, boolean condition) {
flagsBuilder.removeIf(command, condition); flagsBuilder.removeIf(command, condition);
return this; return this;
...@@ -491,6 +500,7 @@ public interface Player { ...@@ -491,6 +500,7 @@ public interface Player {
* @return This builder. * @return This builder.
* @throws IllegalStateException If {@link #build()} has already been called. * @throws IllegalStateException If {@link #build()} has already been called.
*/ */
@CanIgnoreReturnValue
public Builder removeAll(@Command int... commands) { public Builder removeAll(@Command int... commands) {
flagsBuilder.removeAll(commands); flagsBuilder.removeAll(commands);
return this; return this;
...@@ -2489,6 +2499,14 @@ public interface Player { ...@@ -2489,6 +2499,14 @@ public interface Player {
*/ */
VideoSize getVideoSize(); VideoSize getVideoSize();
/**
* Gets the size of the surface on which the video is rendered.
*
* @see Listener#onSurfaceSizeChanged(int, int)
*/
@UnstableApi
Size getSurfaceSize();
/** Returns the current {@link CueGroup}. */ /** Returns the current {@link CueGroup}. */
CueGroup getCurrentCues(); CueGroup getCurrentCues();
......
/*
* Copyright 2022 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 androidx.media3.common;
import static androidx.media3.common.util.Assertions.checkArgument;
import android.view.Surface;
import androidx.annotation.Nullable;
import androidx.media3.common.util.UnstableApi;
/** Immutable value class for a {@link Surface} and supporting information. */
@UnstableApi
public final class SurfaceInfo {
/** The {@link Surface}. */
public final Surface surface;
/** The width of frames rendered to the {@link #surface}, in pixels. */
public final int width;
/** The height of frames rendered to the {@link #surface}, in pixels. */
public final int height;
/**
* A counter-clockwise rotation to apply to frames before rendering them to the {@link #surface}.
*
* <p>Must be 0, 90, 180, or 270 degrees. Default is 0.
*/
public final int orientationDegrees;
/** Creates a new instance. */
public SurfaceInfo(Surface surface, int width, int height) {
this(surface, width, height, /* orientationDegrees= */ 0);
}
/** Creates a new instance. */
public SurfaceInfo(Surface surface, int width, int height, int orientationDegrees) {
checkArgument(
orientationDegrees == 0
|| orientationDegrees == 90
|| orientationDegrees == 180
|| orientationDegrees == 270,
"orientationDegrees must be 0, 90, 180, or 270");
this.surface = surface;
this.width = width;
this.height = height;
this.orientationDegrees = orientationDegrees;
}
@Override
public boolean equals(@Nullable Object o) {
if (this == o) {
return true;
}
if (!(o instanceof SurfaceInfo)) {
return false;
}
SurfaceInfo that = (SurfaceInfo) o;
return width == that.width
&& height == that.height
&& orientationDegrees == that.orientationDegrees
&& surface.equals(that.surface);
}
@Override
public int hashCode() {
int result = surface.hashCode();
result = 31 * result + width;
result = 31 * result + height;
result = 31 * result + orientationDegrees;
return result;
}
}
...@@ -34,6 +34,7 @@ import androidx.media3.common.util.BundleUtil; ...@@ -34,6 +34,7 @@ import androidx.media3.common.util.BundleUtil;
import androidx.media3.common.util.UnstableApi; import androidx.media3.common.util.UnstableApi;
import androidx.media3.common.util.Util; import androidx.media3.common.util.Util;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import com.google.errorprone.annotations.InlineMe; import com.google.errorprone.annotations.InlineMe;
import java.lang.annotation.Documented; import java.lang.annotation.Documented;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;
...@@ -261,6 +262,7 @@ public abstract class Timeline implements Bundleable { ...@@ -261,6 +262,7 @@ public abstract class Timeline implements Bundleable {
} }
/** Sets the data held by this window. */ /** Sets the data held by this window. */
@CanIgnoreReturnValue
@UnstableApi @UnstableApi
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation")
public Window set( public Window set(
...@@ -626,6 +628,7 @@ public abstract class Timeline implements Bundleable { ...@@ -626,6 +628,7 @@ public abstract class Timeline implements Bundleable {
* period is not within the window. * period is not within the window.
* @return This period, for convenience. * @return This period, for convenience.
*/ */
@CanIgnoreReturnValue
@UnstableApi @UnstableApi
public Period set( public Period set(
@Nullable Object id, @Nullable Object id,
...@@ -662,6 +665,7 @@ public abstract class Timeline implements Bundleable { ...@@ -662,6 +665,7 @@ public abstract class Timeline implements Bundleable {
* information has yet to be loaded. * information has yet to be loaded.
* @return This period, for convenience. * @return This period, for convenience.
*/ */
@CanIgnoreReturnValue
@UnstableApi @UnstableApi
public Period set( public Period set(
@Nullable Object id, @Nullable Object id,
......
...@@ -26,11 +26,11 @@ import androidx.media3.common.util.BundleableUtil; ...@@ -26,11 +26,11 @@ import androidx.media3.common.util.BundleableUtil;
import androidx.media3.common.util.Log; import androidx.media3.common.util.Log;
import androidx.media3.common.util.UnstableApi; import androidx.media3.common.util.UnstableApi;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import java.lang.annotation.Documented; import java.lang.annotation.Documented;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; import java.lang.annotation.Target;
import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
...@@ -179,8 +179,11 @@ public final class TrackGroup implements Bundleable { ...@@ -179,8 +179,11 @@ public final class TrackGroup implements Bundleable {
@Override @Override
public Bundle toBundle() { public Bundle toBundle() {
Bundle bundle = new Bundle(); Bundle bundle = new Bundle();
bundle.putParcelableArrayList( ArrayList<Bundle> arrayList = new ArrayList<>(formats.length);
keyForField(FIELD_FORMATS), BundleableUtil.toBundleArrayList(Lists.newArrayList(formats))); for (Format format : formats) {
arrayList.add(format.toBundle(/* excludeMetadata= */ true));
}
bundle.putParcelableArrayList(keyForField(FIELD_FORMATS), arrayList);
bundle.putString(keyForField(FIELD_ID), id); bundle.putString(keyForField(FIELD_ID), id);
return bundle; return bundle;
} }
......
...@@ -13,12 +13,15 @@ ...@@ -13,12 +13,15 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package androidx.media3.exoplayer.audio; package androidx.media3.common.audio;
import androidx.annotation.Nullable;
import androidx.media3.common.C; import androidx.media3.common.C;
import androidx.media3.common.Format; import androidx.media3.common.Format;
import androidx.media3.common.util.UnstableApi; import androidx.media3.common.util.UnstableApi;
import androidx.media3.common.util.Util; import androidx.media3.common.util.Util;
import com.google.common.base.Objects;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.ByteOrder; import java.nio.ByteOrder;
...@@ -70,6 +73,25 @@ public interface AudioProcessor { ...@@ -70,6 +73,25 @@ public interface AudioProcessor {
+ encoding + encoding
+ ']'; + ']';
} }
@Override
public boolean equals(@Nullable Object o) {
if (this == o) {
return true;
}
if (!(o instanceof AudioFormat)) {
return false;
}
AudioFormat that = (AudioFormat) o;
return sampleRate == that.sampleRate
&& channelCount == that.channelCount
&& encoding == that.encoding;
}
@Override
public int hashCode() {
return Objects.hashCode(sampleRate, channelCount, encoding);
}
} }
/** Exception thrown when a processor can't be configured for a given input audio format. */ /** Exception thrown when a processor can't be configured for a given input audio format. */
...@@ -98,6 +120,7 @@ public interface AudioProcessor { ...@@ -98,6 +120,7 @@ public interface AudioProcessor {
* @return The configured output audio format if this instance is {@link #isActive() active}. * @return The configured output audio format if this instance is {@link #isActive() active}.
* @throws UnhandledAudioFormatException Thrown if the specified format can't be handled as input. * @throws UnhandledAudioFormatException Thrown if the specified format can't be handled as input.
*/ */
@CanIgnoreReturnValue
AudioFormat configure(AudioFormat inputAudioFormat) throws UnhandledAudioFormatException; AudioFormat configure(AudioFormat inputAudioFormat) throws UnhandledAudioFormatException;
/** Returns whether the processor is configured and will process input buffers. */ /** Returns whether the processor is configured and will process input buffers. */
...@@ -134,8 +157,8 @@ public interface AudioProcessor { ...@@ -134,8 +157,8 @@ public interface AudioProcessor {
ByteBuffer getOutput(); ByteBuffer getOutput();
/** /**
* Returns whether this processor will return no more output from {@link #getOutput()} until it * Returns whether this processor will return no more output from {@link #getOutput()} until
* has been {@link #flush()}ed and more input has been queued. * {@link #flush()} has been called and more input has been queued.
*/ */
boolean isEnded(); boolean isEnded();
......
/*
* Copyright 2022 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 androidx.media3.common.audio;
import androidx.media3.common.PlaybackParameters;
import androidx.media3.common.util.UnstableApi;
/**
* Provides a chain of audio processors, which are used for any user-defined processing and applying
* playback parameters (if supported). Because applying playback parameters can skip and
* stretch/compress audio, the sink will query the chain for information on how to transform its
* output position to map it onto a media position, via {@link #getMediaDuration(long)} and {@link
* #getSkippedOutputFrameCount()}.
*/
@UnstableApi
public interface AudioProcessorChain {
/**
* Returns the fixed chain of audio processors that will process audio. This method is called once
* during initialization, but audio processors may change state to become active/inactive during
* playback.
*/
AudioProcessor[] getAudioProcessors();
/**
* Configures audio processors to apply the specified playback parameters immediately, returning
* the new playback parameters, which may differ from those passed in. Only called when processors
* have no input pending.
*
* @param playbackParameters The playback parameters to try to apply.
* @return The playback parameters that were actually applied.
*/
PlaybackParameters applyPlaybackParameters(PlaybackParameters playbackParameters);
/**
* Configures audio processors to apply whether to skip silences immediately, returning the new
* value. Only called when processors have no input pending.
*
* @param skipSilenceEnabled Whether silences should be skipped in the audio stream.
* @return The new value.
*/
boolean applySkipSilenceEnabled(boolean skipSilenceEnabled);
/**
* Returns the media duration corresponding to the specified playout duration, taking speed
* adjustment due to audio processing into account.
*
* <p>The scaling performed by this method will use the actual playback speed achieved by the
* audio processor chain, on average, since it was last flushed. This may differ very slightly
* from the target playback speed.
*
* @param playoutDuration The playout duration to scale.
* @return The corresponding media duration, in the same units as {@code duration}.
*/
long getMediaDuration(long playoutDuration);
/**
* Returns the number of output audio frames skipped since the audio processors were last flushed.
*/
long getSkippedOutputFrameCount();
}
/*
* Copyright 2022 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.
*/
@NonNullApi
package androidx.media3.common.audio;
import androidx.media3.common.util.NonNullApi;
...@@ -36,6 +36,7 @@ import androidx.media3.common.Bundleable; ...@@ -36,6 +36,7 @@ import androidx.media3.common.Bundleable;
import androidx.media3.common.util.Assertions; import androidx.media3.common.util.Assertions;
import androidx.media3.common.util.UnstableApi; import androidx.media3.common.util.UnstableApi;
import com.google.common.base.Objects; import com.google.common.base.Objects;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import java.lang.annotation.Documented; import java.lang.annotation.Documented;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;
...@@ -628,6 +629,7 @@ public final class Cue implements Bundleable { ...@@ -628,6 +629,7 @@ public final class Cue implements Bundleable {
* *
* @see Cue#text * @see Cue#text
*/ */
@CanIgnoreReturnValue
public Builder setText(CharSequence text) { public Builder setText(CharSequence text) {
this.text = text; this.text = text;
return this; return this;
...@@ -649,6 +651,7 @@ public final class Cue implements Bundleable { ...@@ -649,6 +651,7 @@ public final class Cue implements Bundleable {
* *
* @see Cue#bitmap * @see Cue#bitmap
*/ */
@CanIgnoreReturnValue
public Builder setBitmap(Bitmap bitmap) { public Builder setBitmap(Bitmap bitmap) {
this.bitmap = bitmap; this.bitmap = bitmap;
return this; return this;
...@@ -672,6 +675,7 @@ public final class Cue implements Bundleable { ...@@ -672,6 +675,7 @@ public final class Cue implements Bundleable {
* *
* @see Cue#textAlignment * @see Cue#textAlignment
*/ */
@CanIgnoreReturnValue
public Builder setTextAlignment(@Nullable Layout.Alignment textAlignment) { public Builder setTextAlignment(@Nullable Layout.Alignment textAlignment) {
this.textAlignment = textAlignment; this.textAlignment = textAlignment;
return this; return this;
...@@ -695,6 +699,7 @@ public final class Cue implements Bundleable { ...@@ -695,6 +699,7 @@ public final class Cue implements Bundleable {
* *
* @see Cue#multiRowAlignment * @see Cue#multiRowAlignment
*/ */
@CanIgnoreReturnValue
public Builder setMultiRowAlignment(@Nullable Layout.Alignment multiRowAlignment) { public Builder setMultiRowAlignment(@Nullable Layout.Alignment multiRowAlignment) {
this.multiRowAlignment = multiRowAlignment; this.multiRowAlignment = multiRowAlignment;
return this; return this;
...@@ -707,6 +712,7 @@ public final class Cue implements Bundleable { ...@@ -707,6 +712,7 @@ public final class Cue implements Bundleable {
* @see Cue#line * @see Cue#line
* @see Cue#lineType * @see Cue#lineType
*/ */
@CanIgnoreReturnValue
public Builder setLine(float line, @LineType int lineType) { public Builder setLine(float line, @LineType int lineType) {
this.line = line; this.line = line;
this.lineType = lineType; this.lineType = lineType;
...@@ -739,6 +745,7 @@ public final class Cue implements Bundleable { ...@@ -739,6 +745,7 @@ public final class Cue implements Bundleable {
* *
* @see Cue#lineAnchor * @see Cue#lineAnchor
*/ */
@CanIgnoreReturnValue
public Builder setLineAnchor(@AnchorType int lineAnchor) { public Builder setLineAnchor(@AnchorType int lineAnchor) {
this.lineAnchor = lineAnchor; this.lineAnchor = lineAnchor;
return this; return this;
...@@ -760,6 +767,7 @@ public final class Cue implements Bundleable { ...@@ -760,6 +767,7 @@ public final class Cue implements Bundleable {
* *
* @see Cue#position * @see Cue#position
*/ */
@CanIgnoreReturnValue
public Builder setPosition(float position) { public Builder setPosition(float position) {
this.position = position; this.position = position;
return this; return this;
...@@ -781,6 +789,7 @@ public final class Cue implements Bundleable { ...@@ -781,6 +789,7 @@ public final class Cue implements Bundleable {
* *
* @see Cue#positionAnchor * @see Cue#positionAnchor
*/ */
@CanIgnoreReturnValue
public Builder setPositionAnchor(@AnchorType int positionAnchor) { public Builder setPositionAnchor(@AnchorType int positionAnchor) {
this.positionAnchor = positionAnchor; this.positionAnchor = positionAnchor;
return this; return this;
...@@ -802,6 +811,7 @@ public final class Cue implements Bundleable { ...@@ -802,6 +811,7 @@ public final class Cue implements Bundleable {
* @see Cue#textSize * @see Cue#textSize
* @see Cue#textSizeType * @see Cue#textSizeType
*/ */
@CanIgnoreReturnValue
public Builder setTextSize(float textSize, @TextSizeType int textSizeType) { public Builder setTextSize(float textSize, @TextSizeType int textSizeType) {
this.textSize = textSize; this.textSize = textSize;
this.textSizeType = textSizeType; this.textSizeType = textSizeType;
...@@ -834,6 +844,7 @@ public final class Cue implements Bundleable { ...@@ -834,6 +844,7 @@ public final class Cue implements Bundleable {
* *
* @see Cue#size * @see Cue#size
*/ */
@CanIgnoreReturnValue
public Builder setSize(float size) { public Builder setSize(float size) {
this.size = size; this.size = size;
return this; return this;
...@@ -855,6 +866,7 @@ public final class Cue implements Bundleable { ...@@ -855,6 +866,7 @@ public final class Cue implements Bundleable {
* *
* @see Cue#bitmapHeight * @see Cue#bitmapHeight
*/ */
@CanIgnoreReturnValue
public Builder setBitmapHeight(float bitmapHeight) { public Builder setBitmapHeight(float bitmapHeight) {
this.bitmapHeight = bitmapHeight; this.bitmapHeight = bitmapHeight;
return this; return this;
...@@ -878,6 +890,7 @@ public final class Cue implements Bundleable { ...@@ -878,6 +890,7 @@ public final class Cue implements Bundleable {
* @see Cue#windowColor * @see Cue#windowColor
* @see Cue#windowColorSet * @see Cue#windowColorSet
*/ */
@CanIgnoreReturnValue
public Builder setWindowColor(@ColorInt int windowColor) { public Builder setWindowColor(@ColorInt int windowColor) {
this.windowColor = windowColor; this.windowColor = windowColor;
this.windowColorSet = true; this.windowColorSet = true;
...@@ -885,6 +898,7 @@ public final class Cue implements Bundleable { ...@@ -885,6 +898,7 @@ public final class Cue implements Bundleable {
} }
/** Sets {@link Cue#windowColorSet} to false. */ /** Sets {@link Cue#windowColorSet} to false. */
@CanIgnoreReturnValue
public Builder clearWindowColor() { public Builder clearWindowColor() {
this.windowColorSet = false; this.windowColorSet = false;
return this; return this;
...@@ -915,12 +929,14 @@ public final class Cue implements Bundleable { ...@@ -915,12 +929,14 @@ public final class Cue implements Bundleable {
* *
* @see Cue#verticalType * @see Cue#verticalType
*/ */
@CanIgnoreReturnValue
public Builder setVerticalType(@VerticalType int verticalType) { public Builder setVerticalType(@VerticalType int verticalType) {
this.verticalType = verticalType; this.verticalType = verticalType;
return this; return this;
} }
/** Sets the shear angle for this Cue. */ /** Sets the shear angle for this Cue. */
@CanIgnoreReturnValue
public Builder setShearDegrees(float shearDegrees) { public Builder setShearDegrees(float shearDegrees) {
this.shearDegrees = shearDegrees; this.shearDegrees = shearDegrees;
return this; return this;
......
...@@ -22,6 +22,7 @@ import android.os.Bundle; ...@@ -22,6 +22,7 @@ import android.os.Bundle;
import androidx.annotation.IntDef; import androidx.annotation.IntDef;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.media3.common.Bundleable; import androidx.media3.common.Bundleable;
import androidx.media3.common.Timeline;
import androidx.media3.common.util.BundleableUtil; import androidx.media3.common.util.BundleableUtil;
import androidx.media3.common.util.UnstableApi; import androidx.media3.common.util.UnstableApi;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
...@@ -35,8 +36,10 @@ import java.util.List; ...@@ -35,8 +36,10 @@ import java.util.List;
/** Class to represent the state of active {@link Cue Cues} at a particular time. */ /** Class to represent the state of active {@link Cue Cues} at a particular time. */
public final class CueGroup implements Bundleable { public final class CueGroup implements Bundleable {
/** Empty {@link CueGroup}. */ /** An empty group with no {@link Cue Cues} and presentation time of zero. */
@UnstableApi public static final CueGroup EMPTY = new CueGroup(ImmutableList.of()); @UnstableApi
public static final CueGroup EMPTY_TIME_ZERO =
new CueGroup(ImmutableList.of(), /* presentationTimeUs= */ 0);
/** /**
* The cues in this group. * The cues in this group.
...@@ -47,11 +50,18 @@ public final class CueGroup implements Bundleable { ...@@ -47,11 +50,18 @@ public final class CueGroup implements Bundleable {
* <p>This list may be empty if the group represents a state with no cues. * <p>This list may be empty if the group represents a state with no cues.
*/ */
public final ImmutableList<Cue> cues; public final ImmutableList<Cue> cues;
/**
* The presentation time of the {@link #cues}, in microseconds.
*
* <p>This time is an offset from the start of the current {@link Timeline.Period}.
*/
@UnstableApi public final long presentationTimeUs;
/** Creates a CueGroup. */ /** Creates a CueGroup. */
@UnstableApi @UnstableApi
public CueGroup(List<Cue> cues) { public CueGroup(List<Cue> cues, long presentationTimeUs) {
this.cues = ImmutableList.copyOf(cues); this.cues = ImmutableList.copyOf(cues);
this.presentationTimeUs = presentationTimeUs;
} }
// Bundleable implementation. // Bundleable implementation.
...@@ -59,10 +69,11 @@ public final class CueGroup implements Bundleable { ...@@ -59,10 +69,11 @@ public final class CueGroup implements Bundleable {
@Documented @Documented
@Retention(RetentionPolicy.SOURCE) @Retention(RetentionPolicy.SOURCE)
@Target(TYPE_USE) @Target(TYPE_USE)
@IntDef({FIELD_CUES}) @IntDef({FIELD_CUES, FIELD_PRESENTATION_TIME_US})
private @interface FieldNumber {} private @interface FieldNumber {}
private static final int FIELD_CUES = 0; private static final int FIELD_CUES = 0;
private static final int FIELD_PRESENTATION_TIME_US = 1;
@UnstableApi @UnstableApi
@Override @Override
...@@ -70,6 +81,7 @@ public final class CueGroup implements Bundleable { ...@@ -70,6 +81,7 @@ public final class CueGroup implements Bundleable {
Bundle bundle = new Bundle(); Bundle bundle = new Bundle();
bundle.putParcelableArrayList( bundle.putParcelableArrayList(
keyForField(FIELD_CUES), BundleableUtil.toBundleArrayList(filterOutBitmapCues(cues))); keyForField(FIELD_CUES), BundleableUtil.toBundleArrayList(filterOutBitmapCues(cues)));
bundle.putLong(keyForField(FIELD_PRESENTATION_TIME_US), presentationTimeUs);
return bundle; return bundle;
} }
...@@ -81,7 +93,8 @@ public final class CueGroup implements Bundleable { ...@@ -81,7 +93,8 @@ public final class CueGroup implements Bundleable {
cueBundles == null cueBundles == null
? ImmutableList.of() ? ImmutableList.of()
: BundleableUtil.fromBundleList(Cue.CREATOR, cueBundles); : BundleableUtil.fromBundleList(Cue.CREATOR, cueBundles);
return new CueGroup(cues); long presentationTimeUs = bundle.getLong(keyForField(FIELD_PRESENTATION_TIME_US));
return new CueGroup(cues, presentationTimeUs);
} }
private static String keyForField(@FieldNumber int field) { private static String keyForField(@FieldNumber int field) {
......
...@@ -79,13 +79,6 @@ public final class EGLSurfaceTexture implements SurfaceTexture.OnFrameAvailableL ...@@ -79,13 +79,6 @@ public final class EGLSurfaceTexture implements SurfaceTexture.OnFrameAvailableL
private static final int EGL_PROTECTED_CONTENT_EXT = 0x32C0; private static final int EGL_PROTECTED_CONTENT_EXT = 0x32C0;
/** A runtime exception to be thrown if some EGL operations failed. */
public static final class GlException extends RuntimeException {
private GlException(String msg) {
super(msg);
}
}
private final Handler handler; private final Handler handler;
private final int[] textureIdHolder; private final int[] textureIdHolder;
@Nullable private final TextureImageListener callback; @Nullable private final TextureImageListener callback;
...@@ -125,7 +118,7 @@ public final class EGLSurfaceTexture implements SurfaceTexture.OnFrameAvailableL ...@@ -125,7 +118,7 @@ public final class EGLSurfaceTexture implements SurfaceTexture.OnFrameAvailableL
* *
* @param secureMode The {@link SecureMode} to be used for EGL surface. * @param secureMode The {@link SecureMode} to be used for EGL surface.
*/ */
public void init(@SecureMode int secureMode) { public void init(@SecureMode int secureMode) throws GlUtil.GlException {
display = getDefaultDisplay(); display = getDefaultDisplay();
EGLConfig config = chooseEGLConfig(display); EGLConfig config = chooseEGLConfig(display);
context = createEGLContext(display, config, secureMode); context = createEGLContext(display, config, secureMode);
...@@ -206,22 +199,18 @@ public final class EGLSurfaceTexture implements SurfaceTexture.OnFrameAvailableL ...@@ -206,22 +199,18 @@ public final class EGLSurfaceTexture implements SurfaceTexture.OnFrameAvailableL
} }
} }
private static EGLDisplay getDefaultDisplay() { private static EGLDisplay getDefaultDisplay() throws GlUtil.GlException {
EGLDisplay display = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY); EGLDisplay display = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY);
if (display == null) { GlUtil.checkGlException(display != null, "eglGetDisplay failed");
throw new GlException("eglGetDisplay failed");
}
int[] version = new int[2]; int[] version = new int[2];
boolean eglInitialized = boolean eglInitialized =
EGL14.eglInitialize(display, version, /* majorOffset= */ 0, version, /* minorOffset= */ 1); EGL14.eglInitialize(display, version, /* majorOffset= */ 0, version, /* minorOffset= */ 1);
if (!eglInitialized) { GlUtil.checkGlException(eglInitialized, "eglInitialize failed");
throw new GlException("eglInitialize failed");
}
return display; return display;
} }
private static EGLConfig chooseEGLConfig(EGLDisplay display) { private static EGLConfig chooseEGLConfig(EGLDisplay display) throws GlUtil.GlException {
EGLConfig[] configs = new EGLConfig[1]; EGLConfig[] configs = new EGLConfig[1];
int[] numConfigs = new int[1]; int[] numConfigs = new int[1];
boolean success = boolean success =
...@@ -234,18 +223,17 @@ public final class EGLSurfaceTexture implements SurfaceTexture.OnFrameAvailableL ...@@ -234,18 +223,17 @@ public final class EGLSurfaceTexture implements SurfaceTexture.OnFrameAvailableL
/* config_size= */ 1, /* config_size= */ 1,
numConfigs, numConfigs,
/* num_configOffset= */ 0); /* num_configOffset= */ 0);
if (!success || numConfigs[0] <= 0 || configs[0] == null) { GlUtil.checkGlException(
throw new GlException( success && numConfigs[0] > 0 && configs[0] != null,
Util.formatInvariant( Util.formatInvariant(
/* format= */ "eglChooseConfig failed: success=%b, numConfigs[0]=%d, configs[0]=%s", /* format= */ "eglChooseConfig failed: success=%b, numConfigs[0]=%d, configs[0]=%s",
success, numConfigs[0], configs[0])); success, numConfigs[0], configs[0]));
}
return configs[0]; return configs[0];
} }
private static EGLContext createEGLContext( private static EGLContext createEGLContext(
EGLDisplay display, EGLConfig config, @SecureMode int secureMode) { EGLDisplay display, EGLConfig config, @SecureMode int secureMode) throws GlUtil.GlException {
int[] glAttributes; int[] glAttributes;
if (secureMode == SECURE_MODE_NONE) { if (secureMode == SECURE_MODE_NONE) {
glAttributes = new int[] {EGL14.EGL_CONTEXT_CLIENT_VERSION, 2, EGL14.EGL_NONE}; glAttributes = new int[] {EGL14.EGL_CONTEXT_CLIENT_VERSION, 2, EGL14.EGL_NONE};
...@@ -262,14 +250,13 @@ public final class EGLSurfaceTexture implements SurfaceTexture.OnFrameAvailableL ...@@ -262,14 +250,13 @@ public final class EGLSurfaceTexture implements SurfaceTexture.OnFrameAvailableL
EGLContext context = EGLContext context =
EGL14.eglCreateContext( EGL14.eglCreateContext(
display, config, android.opengl.EGL14.EGL_NO_CONTEXT, glAttributes, 0); display, config, android.opengl.EGL14.EGL_NO_CONTEXT, glAttributes, 0);
if (context == null) { GlUtil.checkGlException(context != null, "eglCreateContext failed");
throw new GlException("eglCreateContext failed");
}
return context; return context;
} }
private static EGLSurface createEGLSurface( private static EGLSurface createEGLSurface(
EGLDisplay display, EGLConfig config, EGLContext context, @SecureMode int secureMode) { EGLDisplay display, EGLConfig config, EGLContext context, @SecureMode int secureMode)
throws GlUtil.GlException {
EGLSurface surface; EGLSurface surface;
if (secureMode == SECURE_MODE_SURFACELESS_CONTEXT) { if (secureMode == SECURE_MODE_SURFACELESS_CONTEXT) {
surface = EGL14.EGL_NO_SURFACE; surface = EGL14.EGL_NO_SURFACE;
...@@ -297,20 +284,16 @@ public final class EGLSurfaceTexture implements SurfaceTexture.OnFrameAvailableL ...@@ -297,20 +284,16 @@ public final class EGLSurfaceTexture implements SurfaceTexture.OnFrameAvailableL
}; };
} }
surface = EGL14.eglCreatePbufferSurface(display, config, pbufferAttributes, /* offset= */ 0); surface = EGL14.eglCreatePbufferSurface(display, config, pbufferAttributes, /* offset= */ 0);
if (surface == null) { GlUtil.checkGlException(surface != null, "eglCreatePbufferSurface failed");
throw new GlException("eglCreatePbufferSurface failed");
}
} }
boolean eglMadeCurrent = boolean eglMadeCurrent =
EGL14.eglMakeCurrent(display, /* draw= */ surface, /* read= */ surface, context); EGL14.eglMakeCurrent(display, /* draw= */ surface, /* read= */ surface, context);
if (!eglMadeCurrent) { GlUtil.checkGlException(eglMadeCurrent, "eglMakeCurrent failed");
throw new GlException("eglMakeCurrent failed");
}
return surface; return surface;
} }
private static void generateTextureIds(int[] textureIdHolder) { private static void generateTextureIds(int[] textureIdHolder) throws GlUtil.GlException {
GLES20.glGenTextures(/* n= */ 1, textureIdHolder, /* offset= */ 0); GLES20.glGenTextures(/* n= */ 1, textureIdHolder, /* offset= */ 0);
GlUtil.checkGlError(); GlUtil.checkGlError();
} }
......
...@@ -22,6 +22,7 @@ import android.opengl.GLES11Ext; ...@@ -22,6 +22,7 @@ import android.opengl.GLES11Ext;
import android.opengl.GLES20; import android.opengl.GLES20;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.nio.Buffer; import java.nio.Buffer;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
...@@ -54,10 +55,26 @@ public final class GlProgram { ...@@ -54,10 +55,26 @@ public final class GlProgram {
* @throws IOException When failing to read shader files. * @throws IOException When failing to read shader files.
*/ */
public GlProgram(Context context, String vertexShaderFilePath, String fragmentShaderFilePath) public GlProgram(Context context, String vertexShaderFilePath, String fragmentShaderFilePath)
throws IOException { throws IOException, GlUtil.GlException {
this( this(loadAsset(context, vertexShaderFilePath), loadAsset(context, fragmentShaderFilePath));
GlUtil.loadAsset(context, vertexShaderFilePath), }
GlUtil.loadAsset(context, fragmentShaderFilePath));
/**
* Loads a file from the assets folder.
*
* @param context The {@link Context}.
* @param assetPath The path to the file to load, from the assets folder.
* @return The content of the file to load.
* @throws IOException If the file couldn't be read.
*/
public static String loadAsset(Context context, String assetPath) throws IOException {
@Nullable InputStream inputStream = null;
try {
inputStream = context.getAssets().open(assetPath);
return Util.fromUtf8Bytes(Util.toByteArray(inputStream));
} finally {
Util.closeQuietly(inputStream);
}
} }
/** /**
...@@ -69,7 +86,7 @@ public final class GlProgram { ...@@ -69,7 +86,7 @@ public final class GlProgram {
* @param vertexShaderGlsl The vertex shader program. * @param vertexShaderGlsl The vertex shader program.
* @param fragmentShaderGlsl The fragment shader program. * @param fragmentShaderGlsl The fragment shader program.
*/ */
public GlProgram(String vertexShaderGlsl, String fragmentShaderGlsl) { public GlProgram(String vertexShaderGlsl, String fragmentShaderGlsl) throws GlUtil.GlException {
programId = GLES20.glCreateProgram(); programId = GLES20.glCreateProgram();
GlUtil.checkGlError(); GlUtil.checkGlError();
...@@ -81,10 +98,9 @@ public final class GlProgram { ...@@ -81,10 +98,9 @@ public final class GlProgram {
GLES20.glLinkProgram(programId); GLES20.glLinkProgram(programId);
int[] linkStatus = new int[] {GLES20.GL_FALSE}; int[] linkStatus = new int[] {GLES20.GL_FALSE};
GLES20.glGetProgramiv(programId, GLES20.GL_LINK_STATUS, linkStatus, /* offset= */ 0); GLES20.glGetProgramiv(programId, GLES20.GL_LINK_STATUS, linkStatus, /* offset= */ 0);
if (linkStatus[0] != GLES20.GL_TRUE) { GlUtil.checkGlException(
GlUtil.throwGlException( linkStatus[0] == GLES20.GL_TRUE,
"Unable to link shader program: \n" + GLES20.glGetProgramInfoLog(programId)); "Unable to link shader program: \n" + GLES20.glGetProgramInfoLog(programId));
}
GLES20.glUseProgram(programId); GLES20.glUseProgram(programId);
attributeByName = new HashMap<>(); attributeByName = new HashMap<>();
int[] attributeCount = new int[1]; int[] attributeCount = new int[1];
...@@ -107,16 +123,15 @@ public final class GlProgram { ...@@ -107,16 +123,15 @@ public final class GlProgram {
GlUtil.checkGlError(); GlUtil.checkGlError();
} }
private static void addShader(int programId, int type, String glsl) { private static void addShader(int programId, int type, String glsl) throws GlUtil.GlException {
int shader = GLES20.glCreateShader(type); int shader = GLES20.glCreateShader(type);
GLES20.glShaderSource(shader, glsl); GLES20.glShaderSource(shader, glsl);
GLES20.glCompileShader(shader); GLES20.glCompileShader(shader);
int[] result = new int[] {GLES20.GL_FALSE}; int[] result = new int[] {GLES20.GL_FALSE};
GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, result, /* offset= */ 0); GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, result, /* offset= */ 0);
if (result[0] != GLES20.GL_TRUE) { GlUtil.checkGlException(
GlUtil.throwGlException(GLES20.glGetShaderInfoLog(shader) + ", source: " + glsl); result[0] == GLES20.GL_TRUE, GLES20.glGetShaderInfoLog(shader) + ", source: " + glsl);
}
GLES20.glAttachShader(programId, shader); GLES20.glAttachShader(programId, shader);
GLES20.glDeleteShader(shader); GLES20.glDeleteShader(shader);
...@@ -146,13 +161,13 @@ public final class GlProgram { ...@@ -146,13 +161,13 @@ public final class GlProgram {
* *
* <p>Call this in the rendering loop to switch between different programs. * <p>Call this in the rendering loop to switch between different programs.
*/ */
public void use() { public void use() throws GlUtil.GlException {
GLES20.glUseProgram(programId); GLES20.glUseProgram(programId);
GlUtil.checkGlError(); GlUtil.checkGlError();
} }
/** Deletes the program. Deleted programs cannot be used again. */ /** Deletes the program. Deleted programs cannot be used again. */
public void delete() { public void delete() throws GlUtil.GlException {
GLES20.glDeleteProgram(programId); GLES20.glDeleteProgram(programId);
GlUtil.checkGlError(); GlUtil.checkGlError();
} }
...@@ -161,7 +176,7 @@ public final class GlProgram { ...@@ -161,7 +176,7 @@ public final class GlProgram {
* Returns the location of an {@link Attribute}, which has been enabled as a vertex attribute * Returns the location of an {@link Attribute}, which has been enabled as a vertex attribute
* array. * array.
*/ */
public int getAttributeArrayLocationAndEnable(String attributeName) { public int getAttributeArrayLocationAndEnable(String attributeName) throws GlUtil.GlException {
int location = getAttributeLocation(attributeName); int location = getAttributeLocation(attributeName);
GLES20.glEnableVertexAttribArray(location); GLES20.glEnableVertexAttribArray(location);
GlUtil.checkGlError(); GlUtil.checkGlError();
...@@ -185,18 +200,23 @@ public final class GlProgram { ...@@ -185,18 +200,23 @@ public final class GlProgram {
checkNotNull(uniformByName.get(name)).setSamplerTexId(texId, texUnitIndex); checkNotNull(uniformByName.get(name)).setSamplerTexId(texId, texUnitIndex);
} }
/** Sets a float type uniform. */ /** Sets an {@code int} type uniform. */
public void setIntUniform(String name, int value) {
checkNotNull(uniformByName.get(name)).setInt(value);
}
/** Sets a {@code float} type uniform. */
public void setFloatUniform(String name, float value) { public void setFloatUniform(String name, float value) {
checkNotNull(uniformByName.get(name)).setFloat(value); checkNotNull(uniformByName.get(name)).setFloat(value);
} }
/** Sets a float array type uniform. */ /** Sets a {@code float[]} type uniform. */
public void setFloatsUniform(String name, float[] value) { public void setFloatsUniform(String name, float[] value) {
checkNotNull(uniformByName.get(name)).setFloats(value); checkNotNull(uniformByName.get(name)).setFloats(value);
} }
/** Binds all attributes and uniforms in the program. */ /** Binds all attributes and uniforms in the program. */
public void bindAttributesAndUniforms() { public void bindAttributesAndUniforms() throws GlUtil.GlException {
for (Attribute attribute : attributes) { for (Attribute attribute : attributes) {
attribute.bind(); attribute.bind();
} }
...@@ -277,7 +297,7 @@ public final class GlProgram { ...@@ -277,7 +297,7 @@ public final class GlProgram {
* *
* <p>Should be called before each drawing call. * <p>Should be called before each drawing call.
*/ */
public void bind() { public void bind() throws GlUtil.GlException {
Buffer buffer = checkNotNull(this.buffer, "call setBuffer before bind"); Buffer buffer = checkNotNull(this.buffer, "call setBuffer before bind");
GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, /* buffer= */ 0); GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, /* buffer= */ 0);
GLES20.glVertexAttribPointer( GLES20.glVertexAttribPointer(
...@@ -324,16 +344,17 @@ public final class GlProgram { ...@@ -324,16 +344,17 @@ public final class GlProgram {
private final int location; private final int location;
private final int type; private final int type;
private final float[] value; private final float[] floatValue;
private int texId; private int intValue;
private int texIdValue;
private int texUnitIndex; private int texUnitIndex;
private Uniform(String name, int location, int type) { private Uniform(String name, int location, int type) {
this.name = name; this.name = name;
this.location = location; this.location = location;
this.type = type; this.type = type;
this.value = new float[16]; this.floatValue = new float[16];
} }
/** /**
...@@ -343,18 +364,22 @@ public final class GlProgram { ...@@ -343,18 +364,22 @@ public final class GlProgram {
* @param texUnitIndex The GL texture unit index. * @param texUnitIndex The GL texture unit index.
*/ */
public void setSamplerTexId(int texId, int texUnitIndex) { public void setSamplerTexId(int texId, int texUnitIndex) {
this.texId = texId; this.texIdValue = texId;
this.texUnitIndex = texUnitIndex; this.texUnitIndex = texUnitIndex;
} }
/** Configures {@link #bind()} to use the specified {@code int} {@code value}. */
public void setInt(int value) {
this.intValue = value;
}
/** Configures {@link #bind()} to use the specified float {@code value} for this uniform. */ /** Configures {@link #bind()} to use the specified {@code float} {@code value}. */
public void setFloat(float value) { public void setFloat(float value) {
this.value[0] = value; this.floatValue[0] = value;
} }
/** Configures {@link #bind()} to use the specified float[] {@code value} for this uniform. */ /** Configures {@link #bind()} to use the specified {@code float[]} {@code value}. */
public void setFloats(float[] value) { public void setFloats(float[] value) {
System.arraycopy(value, /* srcPos= */ 0, this.value, /* destPos= */ 0, value.length); System.arraycopy(value, /* srcPos= */ 0, this.floatValue, /* destPos= */ 0, value.length);
} }
/** /**
...@@ -363,34 +388,37 @@ public final class GlProgram { ...@@ -363,34 +388,37 @@ public final class GlProgram {
* *
* <p>Should be called before each drawing call. * <p>Should be called before each drawing call.
*/ */
public void bind() { public void bind() throws GlUtil.GlException {
switch (type) { switch (type) {
case GLES20.GL_INT:
GLES20.glUniform1i(location, intValue);
break;
case GLES20.GL_FLOAT: case GLES20.GL_FLOAT:
GLES20.glUniform1fv(location, /* count= */ 1, value, /* offset= */ 0); GLES20.glUniform1fv(location, /* count= */ 1, floatValue, /* offset= */ 0);
GlUtil.checkGlError(); GlUtil.checkGlError();
break; break;
case GLES20.GL_FLOAT_VEC2: case GLES20.GL_FLOAT_VEC2:
GLES20.glUniform2fv(location, /* count= */ 1, value, /* offset= */ 0); GLES20.glUniform2fv(location, /* count= */ 1, floatValue, /* offset= */ 0);
GlUtil.checkGlError(); GlUtil.checkGlError();
break; break;
case GLES20.GL_FLOAT_VEC3: case GLES20.GL_FLOAT_VEC3:
GLES20.glUniform3fv(location, /* count= */ 1, value, /* offset= */ 0); GLES20.glUniform3fv(location, /* count= */ 1, floatValue, /* offset= */ 0);
GlUtil.checkGlError(); GlUtil.checkGlError();
break; break;
case GLES20.GL_FLOAT_MAT3: case GLES20.GL_FLOAT_MAT3:
GLES20.glUniformMatrix3fv( GLES20.glUniformMatrix3fv(
location, /* count= */ 1, /* transpose= */ false, value, /* offset= */ 0); location, /* count= */ 1, /* transpose= */ false, floatValue, /* offset= */ 0);
GlUtil.checkGlError(); GlUtil.checkGlError();
break; break;
case GLES20.GL_FLOAT_MAT4: case GLES20.GL_FLOAT_MAT4:
GLES20.glUniformMatrix4fv( GLES20.glUniformMatrix4fv(
location, /* count= */ 1, /* transpose= */ false, value, /* offset= */ 0); location, /* count= */ 1, /* transpose= */ false, floatValue, /* offset= */ 0);
GlUtil.checkGlError(); GlUtil.checkGlError();
break; break;
case GLES20.GL_SAMPLER_2D: case GLES20.GL_SAMPLER_2D:
case GLES11Ext.GL_SAMPLER_EXTERNAL_OES: case GLES11Ext.GL_SAMPLER_EXTERNAL_OES:
case GL_SAMPLER_EXTERNAL_2D_Y2Y_EXT: case GL_SAMPLER_EXTERNAL_2D_Y2Y_EXT:
if (texId == 0) { if (texIdValue == 0) {
throw new IllegalStateException("No call to setSamplerTexId() before bind."); throw new IllegalStateException("No call to setSamplerTexId() before bind.");
} }
GLES20.glActiveTexture(GLES20.GL_TEXTURE0 + texUnitIndex); GLES20.glActiveTexture(GLES20.GL_TEXTURE0 + texUnitIndex);
...@@ -399,7 +427,7 @@ public final class GlProgram { ...@@ -399,7 +427,7 @@ public final class GlProgram {
type == GLES20.GL_SAMPLER_2D type == GLES20.GL_SAMPLER_2D
? GLES20.GL_TEXTURE_2D ? GLES20.GL_TEXTURE_2D
: GLES11Ext.GL_TEXTURE_EXTERNAL_OES, : GLES11Ext.GL_TEXTURE_EXTERNAL_OES,
texId); texIdValue);
GLES20.glUniform1i(location, texUnitIndex); GLES20.glUniform1i(location, texUnitIndex);
GlUtil.checkGlError(); GlUtil.checkGlError();
break; break;
......
...@@ -270,6 +270,7 @@ public final class ListenerSet<T extends @NonNull Object> { ...@@ -270,6 +270,7 @@ public final class ListenerSet<T extends @NonNull Object> {
public void release(IterationFinishedEvent<T> event) { public void release(IterationFinishedEvent<T> event) {
released = true; released = true;
if (needsIterationFinishedEvent) { if (needsIterationFinishedEvent) {
needsIterationFinishedEvent = false;
event.invoke(listener, flagsBuilder.build()); event.invoke(listener, flagsBuilder.build());
} }
} }
......
...@@ -15,6 +15,8 @@ ...@@ -15,6 +15,8 @@
*/ */
package androidx.media3.common.util; package androidx.media3.common.util;
import static androidx.media3.common.util.Util.SDK_INT;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.media.AudioFormat; import android.media.AudioFormat;
import android.media.MediaFormat; import android.media.MediaFormat;
...@@ -191,6 +193,53 @@ public final class MediaFormatUtil { ...@@ -191,6 +193,53 @@ public final class MediaFormatUtil {
} }
} }
/**
* Creates and returns a {@code ColorInfo}, if a valid instance is described in the {@link
* MediaFormat}.
*/
@Nullable
public static ColorInfo getColorInfo(MediaFormat mediaFormat) {
if (SDK_INT < 29) {
return null;
}
int colorSpace =
mediaFormat.getInteger(MediaFormat.KEY_COLOR_STANDARD, /* defaultValue= */ Format.NO_VALUE);
int colorRange =
mediaFormat.getInteger(MediaFormat.KEY_COLOR_RANGE, /* defaultValue= */ Format.NO_VALUE);
int colorTransfer =
mediaFormat.getInteger(MediaFormat.KEY_COLOR_TRANSFER, /* defaultValue= */ Format.NO_VALUE);
@Nullable
ByteBuffer hdrStaticInfoByteBuffer = mediaFormat.getByteBuffer(MediaFormat.KEY_HDR_STATIC_INFO);
@Nullable
byte[] hdrStaticInfo =
hdrStaticInfoByteBuffer != null ? getArray(hdrStaticInfoByteBuffer) : null;
// Some devices may produce invalid values from MediaFormat#getInteger.
// See b/239435670 for more information.
if (!isValidColorSpace(colorSpace)) {
colorSpace = Format.NO_VALUE;
}
if (!isValidColorRange(colorRange)) {
colorRange = Format.NO_VALUE;
}
if (!isValidColorTransfer(colorTransfer)) {
colorTransfer = Format.NO_VALUE;
}
if (colorSpace != Format.NO_VALUE
|| colorRange != Format.NO_VALUE
|| colorTransfer != Format.NO_VALUE
|| hdrStaticInfo != null) {
return new ColorInfo(colorSpace, colorRange, colorTransfer, hdrStaticInfo);
}
return null;
}
public static byte[] getArray(ByteBuffer byteBuffer) {
byte[] array = new byte[byteBuffer.remaining()];
byteBuffer.get(array);
return array;
}
// Internal methods. // Internal methods.
private static void setBooleanAsInt(MediaFormat format, String key, int value) { private static void setBooleanAsInt(MediaFormat format, String key, int value) {
...@@ -253,5 +302,31 @@ public final class MediaFormatUtil { ...@@ -253,5 +302,31 @@ public final class MediaFormatUtil {
mediaFormat.setInteger(MediaFormat.KEY_PCM_ENCODING, mediaFormatPcmEncoding); mediaFormat.setInteger(MediaFormat.KEY_PCM_ENCODING, mediaFormatPcmEncoding);
} }
/** Whether this is a valid {@link C.ColorSpace} instance. */
private static boolean isValidColorSpace(int colorSpace) {
// LINT.IfChange(color_space)
return colorSpace == C.COLOR_SPACE_BT601
|| colorSpace == C.COLOR_SPACE_BT709
|| colorSpace == C.COLOR_SPACE_BT2020
|| colorSpace == Format.NO_VALUE;
}
/** Whether this is a valid {@link C.ColorRange} instance. */
private static boolean isValidColorRange(int colorRange) {
// LINT.IfChange(color_range)
return colorRange == C.COLOR_RANGE_LIMITED
|| colorRange == C.COLOR_RANGE_FULL
|| colorRange == Format.NO_VALUE;
}
/** Whether this is a valid {@link C.ColorTransfer} instance. */
private static boolean isValidColorTransfer(int colorTransfer) {
// LINT.IfChange(color_transfer)
return colorTransfer == C.COLOR_TRANSFER_SDR
|| colorTransfer == C.COLOR_TRANSFER_ST2084
|| colorTransfer == C.COLOR_TRANSFER_HLG
|| colorTransfer == Format.NO_VALUE;
}
private MediaFormatUtil() {} private MediaFormatUtil() {}
} }
...@@ -94,7 +94,7 @@ public final class NetworkTypeObserver { ...@@ -94,7 +94,7 @@ public final class NetworkTypeObserver {
networkType = C.NETWORK_TYPE_UNKNOWN; networkType = C.NETWORK_TYPE_UNKNOWN;
IntentFilter filter = new IntentFilter(); IntentFilter filter = new IntentFilter();
filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION); filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
context.registerReceiver(/* receiver= */ new Receiver(), filter); Util.registerReceiverNotExported(context, new Receiver(), filter);
} }
/** /**
......
/*
* Copyright (C) 2022 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 androidx.media3.common.util;
import static androidx.media3.common.util.Assertions.checkArgument;
import androidx.annotation.Nullable;
import androidx.media3.common.C;
/** Immutable class for describing width and height dimensions in pixels. */
@UnstableApi
public final class Size {
/** A static instance to represent an unknown size value. */
public static final Size UNKNOWN =
new Size(/* width= */ C.LENGTH_UNSET, /* height= */ C.LENGTH_UNSET);
private final int width;
private final int height;
/**
* Creates a new immutable Size instance.
*
* @param width The width of the size, in pixels, or {@link C#LENGTH_UNSET} if unknown.
* @param height The height of the size, in pixels, or {@link C#LENGTH_UNSET} if unknown.
* @throws IllegalArgumentException if an invalid {@code width} or {@code height} is specified.
*/
public Size(int width, int height) {
checkArgument(
(width == C.LENGTH_UNSET || width >= 0) && (height == C.LENGTH_UNSET || height >= 0));
this.width = width;
this.height = height;
}
/** Returns the width of the size (in pixels), or {@link C#LENGTH_UNSET} if unknown. */
public int getWidth() {
return width;
}
/** Returns the height of the size (in pixels), or {@link C#LENGTH_UNSET} if unknown. */
public int getHeight() {
return height;
}
@Override
public boolean equals(@Nullable Object obj) {
if (obj == null) {
return false;
}
if (this == obj) {
return true;
}
if (obj instanceof Size) {
Size other = (Size) obj;
return width == other.width && height == other.height;
}
return false;
}
@Override
public String toString() {
return width + "x" + height;
}
@Override
public int hashCode() {
// assuming most sizes are <2^16, doing a rotate will give us perfect hashing
return height ^ ((width << (Integer.SIZE / 2)) | (width >>> (Integer.SIZE / 2)));
}
}
...@@ -21,6 +21,7 @@ import android.os.Handler; ...@@ -21,6 +21,7 @@ import android.os.Handler;
import android.os.Looper; import android.os.Looper;
import androidx.annotation.GuardedBy; import androidx.annotation.GuardedBy;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
...@@ -136,6 +137,7 @@ import java.util.List; ...@@ -136,6 +137,7 @@ import java.util.List;
@Nullable private android.os.Message message; @Nullable private android.os.Message message;
@Nullable private SystemHandlerWrapper handler; @Nullable private SystemHandlerWrapper handler;
@CanIgnoreReturnValue
public SystemMessage setMessage(android.os.Message message, SystemHandlerWrapper handler) { public SystemMessage setMessage(android.os.Message message, SystemHandlerWrapper handler) {
this.message = message; this.message = message;
this.handler = handler; this.handler = handler;
......
...@@ -66,6 +66,8 @@ public final class TimestampAdjuster { ...@@ -66,6 +66,8 @@ public final class TimestampAdjuster {
* Next sample timestamps for calling threads in shared mode when {@link #timestampOffsetUs} has * Next sample timestamps for calling threads in shared mode when {@link #timestampOffsetUs} has
* not yet been set. * not yet been set.
*/ */
// incompatible type argument for type parameter T of ThreadLocal.
@SuppressWarnings("nullness:type.argument.type.incompatible")
private final ThreadLocal<Long> nextSampleTimestampUs; private final ThreadLocal<Long> nextSampleTimestampUs;
/** /**
...@@ -73,6 +75,8 @@ public final class TimestampAdjuster { ...@@ -73,6 +75,8 @@ public final class TimestampAdjuster {
* microseconds, or {@link #MODE_NO_OFFSET} if timestamps should not be offset, or {@link * microseconds, or {@link #MODE_NO_OFFSET} if timestamps should not be offset, or {@link
* #MODE_SHARED} if the adjuster will be used in shared mode. * #MODE_SHARED} if the adjuster will be used in shared mode.
*/ */
// incompatible types in assignment.
@SuppressWarnings("nullness:assignment.type.incompatible")
public TimestampAdjuster(long firstSampleTimestampUs) { public TimestampAdjuster(long firstSampleTimestampUs) {
nextSampleTimestampUs = new ThreadLocal<>(); nextSampleTimestampUs = new ThreadLocal<>();
reset(firstSampleTimestampUs); reset(firstSampleTimestampUs);
......
...@@ -34,9 +34,11 @@ import android.Manifest.permission; ...@@ -34,9 +34,11 @@ import android.Manifest.permission;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.app.Activity; import android.app.Activity;
import android.app.UiModeManager; import android.app.UiModeManager;
import android.content.BroadcastReceiver;
import android.content.ComponentName; import android.content.ComponentName;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageInfo; import android.content.pm.PackageInfo;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.PackageManager.NameNotFoundException;
...@@ -78,6 +80,11 @@ import androidx.media3.common.Player; ...@@ -78,6 +80,11 @@ import androidx.media3.common.Player;
import androidx.media3.common.Player.Commands; import androidx.media3.common.Player.Commands;
import com.google.common.base.Ascii; import com.google.common.base.Ascii;
import com.google.common.base.Charsets; import com.google.common.base.Charsets;
import com.google.common.util.concurrent.AsyncFunction;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
import com.google.common.util.concurrent.SettableFuture;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.Closeable; import java.io.Closeable;
import java.io.File; import java.io.File;
...@@ -100,6 +107,8 @@ import java.util.MissingResourceException; ...@@ -100,6 +107,8 @@ import java.util.MissingResourceException;
import java.util.NoSuchElementException; import java.util.NoSuchElementException;
import java.util.TimeZone; import java.util.TimeZone;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.regex.Matcher; import java.util.regex.Matcher;
...@@ -116,8 +125,8 @@ import org.checkerframework.checker.nullness.qual.PolyNull; ...@@ -116,8 +125,8 @@ import org.checkerframework.checker.nullness.qual.PolyNull;
public final class Util { public final class Util {
/** /**
* Like {@link android.os.Build.VERSION#SDK_INT}, but in a place where it can be conveniently * Like {@link Build.VERSION#SDK_INT}, but in a place where it can be conveniently overridden for
* overridden for local testing. * local testing.
*/ */
@UnstableApi public static final int SDK_INT = Build.VERSION.SDK_INT; @UnstableApi public static final int SDK_INT = Build.VERSION.SDK_INT;
...@@ -190,6 +199,54 @@ public final class Util { ...@@ -190,6 +199,54 @@ public final class Util {
} }
/** /**
* Registers a {@link BroadcastReceiver} that's not intended to receive broadcasts from other
* apps. This will be enforced by specifying {@link Context#RECEIVER_NOT_EXPORTED} if {@link
* #SDK_INT} is 33 or above.
*
* @param context The context on which {@link Context#registerReceiver} will be called.
* @param receiver The {@link BroadcastReceiver} to register. This value may be null.
* @param filter Selects the Intent broadcasts to be received.
* @return The first sticky intent found that matches {@code filter}, or null if there are none.
*/
@UnstableApi
@Nullable
public static Intent registerReceiverNotExported(
Context context, @Nullable BroadcastReceiver receiver, IntentFilter filter) {
if (SDK_INT < 33) {
return context.registerReceiver(receiver, filter);
} else {
return context.registerReceiver(receiver, filter, Context.RECEIVER_NOT_EXPORTED);
}
}
/**
* Registers a {@link BroadcastReceiver} that's not intended to receive broadcasts from other
* apps. This will be enforced by specifying {@link Context#RECEIVER_NOT_EXPORTED} if {@link
* #SDK_INT} is 33 or above.
*
* @param context The context on which {@link Context#registerReceiver} will be called.
* @param receiver The {@link BroadcastReceiver} to register. This value may be null.
* @param filter Selects the Intent broadcasts to be received.
* @param handler Handler identifying the thread that will receive the Intent.
* @return The first sticky intent found that matches {@code filter}, or null if there are none.
*/
@UnstableApi
@Nullable
public static Intent registerReceiverNotExported(
Context context, BroadcastReceiver receiver, IntentFilter filter, Handler handler) {
if (SDK_INT < 33) {
return context.registerReceiver(receiver, filter, /* broadcastPermission= */ null, handler);
} else {
return context.registerReceiver(
receiver,
filter,
/* broadcastPermission= */ null,
handler,
Context.RECEIVER_NOT_EXPORTED);
}
}
/**
* Calls {@link Context#startForegroundService(Intent)} if {@link #SDK_INT} is 26 or higher, or * Calls {@link Context#startForegroundService(Intent)} if {@link #SDK_INT} is 26 or higher, or
* {@link Context#startService(Intent)} otherwise. * {@link Context#startService(Intent)} otherwise.
* *
...@@ -575,6 +632,94 @@ public final class Util { ...@@ -575,6 +632,94 @@ public final class Util {
} }
/** /**
* Posts the {@link Runnable} if the calling thread differs with the {@link Looper} of the {@link
* Handler}. Otherwise, runs the {@link Runnable} directly. Also returns a {@link
* ListenableFuture} for when the {@link Runnable} has run.
*
* @param handler The handler to which the {@link Runnable} will be posted.
* @param runnable The runnable to either post or run.
* @param successValue The value to set in the {@link ListenableFuture} once the runnable
* completes.
* @param <T> The type of {@code successValue}.
* @return A {@link ListenableFuture} for when the {@link Runnable} has run.
*/
@UnstableApi
public static <T> ListenableFuture<T> postOrRunWithCompletion(
Handler handler, Runnable runnable, T successValue) {
SettableFuture<T> outputFuture = SettableFuture.create();
postOrRun(
handler,
() -> {
try {
if (outputFuture.isCancelled()) {
return;
}
runnable.run();
outputFuture.set(successValue);
} catch (Throwable e) {
outputFuture.setException(e);
}
});
return outputFuture;
}
/**
* Asynchronously transforms the result of a {@link ListenableFuture}.
*
* <p>The transformation function is called using a {@linkplain MoreExecutors#directExecutor()
* direct executor}.
*
* <p>The returned Future attempts to keep its cancellation state in sync with that of the input
* future and that of the future returned by the transform function. That is, if the returned
* Future is cancelled, it will attempt to cancel the other two, and if either of the other two is
* cancelled, the returned Future will also be cancelled. All forwarded cancellations will not
* attempt to interrupt.
*
* @param future The input {@link ListenableFuture}.
* @param transformFunction The function transforming the result of the input future.
* @param <T> The result type of the input future.
* @param <U> The result type of the transformation function.
* @return A {@link ListenableFuture} for the transformed result.
*/
@UnstableApi
public static <T, U> ListenableFuture<T> transformFutureAsync(
ListenableFuture<U> future, AsyncFunction<U, T> transformFunction) {
// This is a simplified copy of Guava's Futures.transformAsync.
SettableFuture<T> outputFuture = SettableFuture.create();
outputFuture.addListener(
() -> {
if (outputFuture.isCancelled()) {
future.cancel(/* mayInterruptIfRunning= */ false);
}
},
MoreExecutors.directExecutor());
future.addListener(
() -> {
U inputFutureResult;
try {
inputFutureResult = Futures.getDone(future);
} catch (CancellationException cancellationException) {
outputFuture.cancel(/* mayInterruptIfRunning= */ false);
return;
} catch (ExecutionException exception) {
@Nullable Throwable cause = exception.getCause();
outputFuture.setException(cause == null ? exception : cause);
return;
} catch (RuntimeException | Error error) {
outputFuture.setException(error);
return;
}
try {
outputFuture.setFuture(transformFunction.apply(inputFutureResult));
} catch (Throwable exception) {
outputFuture.setException(exception);
}
},
MoreExecutors.directExecutor());
return outputFuture;
}
/**
* Returns the {@link Looper} associated with the current thread, or the {@link Looper} of the * Returns the {@link Looper} associated with the current thread, or the {@link Looper} of the
* application's main thread if the current thread doesn't have a {@link Looper}. * application's main thread if the current thread doesn't have a {@link Looper}.
*/ */
...@@ -1716,6 +1861,7 @@ public final class Util { ...@@ -1716,6 +1861,7 @@ public final class Util {
* @return The channel configuration or {@link AudioFormat#CHANNEL_INVALID} if output is not * @return The channel configuration or {@link AudioFormat#CHANNEL_INVALID} if output is not
* possible. * possible.
*/ */
@SuppressLint("InlinedApi") // Inlined AudioFormat constants.
@UnstableApi @UnstableApi
public static int getAudioTrackChannelConfig(int channelCount) { public static int getAudioTrackChannelConfig(int channelCount) {
switch (channelCount) { switch (channelCount) {
...@@ -1734,21 +1880,9 @@ public final class Util { ...@@ -1734,21 +1880,9 @@ public final class Util {
case 7: case 7:
return AudioFormat.CHANNEL_OUT_5POINT1 | AudioFormat.CHANNEL_OUT_BACK_CENTER; return AudioFormat.CHANNEL_OUT_5POINT1 | AudioFormat.CHANNEL_OUT_BACK_CENTER;
case 8: case 8:
if (SDK_INT >= 23) { return AudioFormat.CHANNEL_OUT_7POINT1_SURROUND;
return AudioFormat.CHANNEL_OUT_7POINT1_SURROUND;
} else if (SDK_INT >= 21) {
// Equal to AudioFormat.CHANNEL_OUT_7POINT1_SURROUND, which is hidden before Android M.
return AudioFormat.CHANNEL_OUT_5POINT1
| AudioFormat.CHANNEL_OUT_SIDE_LEFT
| AudioFormat.CHANNEL_OUT_SIDE_RIGHT;
} else {
// 8 ch output is not supported before Android L.
return AudioFormat.CHANNEL_INVALID;
}
case 12: case 12:
return Util.SDK_INT >= 32 return AudioFormat.CHANNEL_OUT_7POINT1POINT4;
? AudioFormat.CHANNEL_OUT_7POINT1POINT4
: AudioFormat.CHANNEL_INVALID;
default: default:
return AudioFormat.CHANNEL_INVALID; return AudioFormat.CHANNEL_INVALID;
} }
...@@ -2604,6 +2738,7 @@ public final class Util { ...@@ -2604,6 +2738,7 @@ public final class Util {
* @param newFromIndex The new from index. * @param newFromIndex The new from index.
*/ */
@UnstableApi @UnstableApi
@SuppressWarnings("ExtendsObject") // See go/lsc-extends-object
public static <T extends Object> void moveItems( public static <T extends Object> void moveItems(
List<T> items, int fromIndex, int toIndex, int newFromIndex) { List<T> items, int fromIndex, int toIndex, int newFromIndex) {
ArrayDeque<T> removedItems = new ArrayDeque<>(); ArrayDeque<T> removedItems = new ArrayDeque<>();
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment