Merge branch 'main' into rtp-mpeg4
@ -37,6 +37,7 @@ project.ext {
|
||||
androidxAnnotationExperimentalVersion = '1.2.0'
|
||||
androidxAppCompatVersion = '1.3.1'
|
||||
androidxCollectionVersion = '1.1.0'
|
||||
androidxConstraintLayoutVersion = '2.0.4'
|
||||
androidxCoreVersion = '1.7.0'
|
||||
androidxFuturesVersion = '1.1.0'
|
||||
androidxMediaVersion = '1.4.3'
|
||||
|
@ -36,7 +36,7 @@ import androidx.media3.common.MediaItem;
|
||||
import androidx.media3.common.util.Assertions;
|
||||
import androidx.media3.common.util.Util;
|
||||
import androidx.media3.exoplayer.ExoPlayer;
|
||||
import androidx.media3.ui.StyledPlayerView;
|
||||
import androidx.media3.ui.PlayerView;
|
||||
import androidx.recyclerview.widget.ItemTouchHelper;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
@ -52,7 +52,7 @@ import com.google.android.gms.dynamite.DynamiteModule;
|
||||
public class MainActivity extends AppCompatActivity
|
||||
implements OnClickListener, PlayerManager.Listener {
|
||||
|
||||
private StyledPlayerView playerView;
|
||||
private PlayerView playerView;
|
||||
private PlayerManager playerManager;
|
||||
private RecyclerView mediaQueueList;
|
||||
private MediaQueueListAdapter mediaQueueListAdapter;
|
||||
|
@ -28,8 +28,8 @@ import androidx.media3.common.Player.TimelineChangeReason;
|
||||
import androidx.media3.common.Timeline;
|
||||
import androidx.media3.common.TracksInfo;
|
||||
import androidx.media3.exoplayer.ExoPlayer;
|
||||
import androidx.media3.ui.StyledPlayerControlView;
|
||||
import androidx.media3.ui.StyledPlayerView;
|
||||
import androidx.media3.ui.PlayerControlView;
|
||||
import androidx.media3.ui.PlayerView;
|
||||
import com.google.android.gms.cast.framework.CastContext;
|
||||
import java.util.ArrayList;
|
||||
|
||||
@ -51,7 +51,7 @@ import java.util.ArrayList;
|
||||
}
|
||||
|
||||
private final Context context;
|
||||
private final StyledPlayerView playerView;
|
||||
private final PlayerView playerView;
|
||||
private final Player localPlayer;
|
||||
private final CastPlayer castPlayer;
|
||||
private final ArrayList<MediaItem> mediaQueue;
|
||||
@ -66,11 +66,11 @@ import java.util.ArrayList;
|
||||
*
|
||||
* @param context A {@link Context}.
|
||||
* @param listener A {@link Listener} for queue position changes.
|
||||
* @param playerView The {@link StyledPlayerView} for playback.
|
||||
* @param playerView The {@link PlayerView} for playback.
|
||||
* @param castContext The {@link CastContext}.
|
||||
*/
|
||||
public PlayerManager(
|
||||
Context context, Listener listener, StyledPlayerView playerView, CastContext castContext) {
|
||||
Context context, Listener listener, PlayerView playerView, CastContext castContext) {
|
||||
this.context = context;
|
||||
this.listener = listener;
|
||||
this.playerView = playerView;
|
||||
@ -223,10 +223,12 @@ import java.util.ArrayList;
|
||||
if (currentPlayer != localPlayer || tracksInfo == lastSeenTrackGroupInfo) {
|
||||
return;
|
||||
}
|
||||
if (!tracksInfo.isTypeSupportedOrEmpty(C.TRACK_TYPE_VIDEO)) {
|
||||
if (tracksInfo.containsType(C.TRACK_TYPE_VIDEO)
|
||||
&& !tracksInfo.isTypeSupported(C.TRACK_TYPE_VIDEO, /* allowExceedsCapabilities= */ true)) {
|
||||
listener.onUnsupportedTrack(C.TRACK_TYPE_VIDEO);
|
||||
}
|
||||
if (!tracksInfo.isTypeSupportedOrEmpty(C.TRACK_TYPE_AUDIO)) {
|
||||
if (tracksInfo.containsType(C.TRACK_TYPE_AUDIO)
|
||||
&& !tracksInfo.isTypeSupported(C.TRACK_TYPE_AUDIO, /* allowExceedsCapabilities= */ true)) {
|
||||
listener.onUnsupportedTrack(C.TRACK_TYPE_AUDIO);
|
||||
}
|
||||
lastSeenTrackGroupInfo = tracksInfo;
|
||||
@ -270,7 +272,7 @@ import java.util.ArrayList;
|
||||
R.drawable.ic_baseline_cast_connected_400,
|
||||
/* theme= */ null));
|
||||
} else { // currentPlayer == localPlayer
|
||||
playerView.setControllerShowTimeoutMs(StyledPlayerControlView.DEFAULT_SHOW_TIMEOUT_MS);
|
||||
playerView.setControllerShowTimeoutMs(PlayerControlView.DEFAULT_SHOW_TIMEOUT_MS);
|
||||
playerView.setDefaultArtwork(null);
|
||||
}
|
||||
|
||||
|
@ -20,7 +20,7 @@
|
||||
android:layout_height="match_parent"
|
||||
android:keepScreenOn="true">
|
||||
|
||||
<androidx.media3.ui.StyledPlayerView android:id="@+id/player_view"
|
||||
<androidx.media3.ui.PlayerView android:id="@+id/player_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1"
|
||||
|
@ -15,19 +15,19 @@
|
||||
#extension GL_OES_EGL_image_external : require
|
||||
precision mediump float;
|
||||
// External texture containing video decoder output.
|
||||
uniform samplerExternalOES tex_sampler_0;
|
||||
uniform samplerExternalOES uTexSampler0;
|
||||
// Texture containing the overlap bitmap.
|
||||
uniform sampler2D tex_sampler_1;
|
||||
uniform sampler2D uTexSampler1;
|
||||
// Horizontal scaling factor for the overlap bitmap.
|
||||
uniform float scaleX;
|
||||
uniform float uScaleX;
|
||||
// Vertical scaling factory for the overlap bitmap.
|
||||
uniform float scaleY;
|
||||
varying vec2 v_texcoord;
|
||||
uniform float uScaleY;
|
||||
varying vec2 vTexCoords;
|
||||
void main() {
|
||||
vec4 videoColor = texture2D(tex_sampler_0, v_texcoord);
|
||||
vec4 overlayColor = texture2D(tex_sampler_1,
|
||||
vec2(v_texcoord.x * scaleX,
|
||||
v_texcoord.y * scaleY));
|
||||
vec4 videoColor = texture2D(uTexSampler0, vTexCoords);
|
||||
vec4 overlayColor = texture2D(uTexSampler1,
|
||||
vec2(vTexCoords.x * uScaleX,
|
||||
vTexCoords.y * uScaleY));
|
||||
// Blend the video decoder output and the overlay bitmap.
|
||||
gl_FragColor = videoColor * (1.0 - overlayColor.a)
|
||||
+ overlayColor * overlayColor.a;
|
||||
|
@ -11,11 +11,11 @@
|
||||
// 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.
|
||||
attribute vec4 a_position;
|
||||
attribute vec4 a_texcoord;
|
||||
uniform mat4 tex_transform;
|
||||
varying vec2 v_texcoord;
|
||||
attribute vec4 aFramePosition;
|
||||
attribute vec4 aTexCoords;
|
||||
uniform mat4 uTexTransform;
|
||||
varying vec2 vTexCoords;
|
||||
void main() {
|
||||
gl_Position = a_position;
|
||||
v_texcoord = (tex_transform * a_texcoord).xy;
|
||||
gl_Position = aFramePosition;
|
||||
vTexCoords = (uTexTransform * aTexCoords).xy;
|
||||
}
|
||||
|
@ -27,6 +27,7 @@ import android.graphics.drawable.BitmapDrawable;
|
||||
import android.opengl.GLES20;
|
||||
import android.opengl.GLUtils;
|
||||
import androidx.media3.common.C;
|
||||
import androidx.media3.common.util.GlProgram;
|
||||
import androidx.media3.common.util.GlUtil;
|
||||
import java.io.IOException;
|
||||
import java.util.Locale;
|
||||
@ -50,7 +51,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
private final Bitmap logoBitmap;
|
||||
private final Canvas overlayCanvas;
|
||||
|
||||
private GlUtil.@MonotonicNonNull Program program;
|
||||
private @MonotonicNonNull GlProgram program;
|
||||
|
||||
private float bitmapScaleX;
|
||||
private float bitmapScaleY;
|
||||
@ -78,7 +79,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
public void initialize() {
|
||||
try {
|
||||
program =
|
||||
new GlUtil.Program(
|
||||
new GlProgram(
|
||||
context,
|
||||
/* vertexShaderFilePath= */ "bitmap_overlay_video_processor_vertex.glsl",
|
||||
/* fragmentShaderFilePath= */ "bitmap_overlay_video_processor_fragment.glsl");
|
||||
@ -86,23 +87,9 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
program.setBufferAttribute(
|
||||
"a_position",
|
||||
new float[] {
|
||||
-1, -1, 0, 1,
|
||||
1, -1, 0, 1,
|
||||
-1, 1, 0, 1,
|
||||
1, 1, 0, 1
|
||||
},
|
||||
4);
|
||||
"aFramePosition", GlUtil.getNormalizedCoordinateBounds(), GlUtil.RECTANGLE_VERTICES_COUNT);
|
||||
program.setBufferAttribute(
|
||||
"a_texcoord",
|
||||
new float[] {
|
||||
0, 0, 0, 1,
|
||||
1, 0, 0, 1,
|
||||
0, 1, 0, 1,
|
||||
1, 1, 0, 1
|
||||
},
|
||||
4);
|
||||
"aTexCoords", GlUtil.getTextureCoordinateBounds(), GlUtil.RECTANGLE_VERTICES_COUNT);
|
||||
GLES20.glGenTextures(1, textures, 0);
|
||||
GLES20.glBindTexture(GL10.GL_TEXTURE_2D, textures[0]);
|
||||
GLES20.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_NEAREST);
|
||||
@ -131,12 +118,12 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
GlUtil.checkGlError();
|
||||
|
||||
// Run the shader program.
|
||||
GlUtil.Program program = checkNotNull(this.program);
|
||||
program.setSamplerTexIdUniform("tex_sampler_0", frameTexture, /* unit= */ 0);
|
||||
program.setSamplerTexIdUniform("tex_sampler_1", textures[0], /* unit= */ 1);
|
||||
program.setFloatUniform("scaleX", bitmapScaleX);
|
||||
program.setFloatUniform("scaleY", bitmapScaleY);
|
||||
program.setFloatsUniform("tex_transform", transformMatrix);
|
||||
GlProgram program = checkNotNull(this.program);
|
||||
program.setSamplerTexIdUniform("uTexSampler0", frameTexture, /* unit= */ 0);
|
||||
program.setSamplerTexIdUniform("uTexSampler1", textures[0], /* unit= */ 1);
|
||||
program.setFloatUniform("uScaleX", bitmapScaleX);
|
||||
program.setFloatUniform("uScaleY", bitmapScaleY);
|
||||
program.setFloatsUniform("uTexTransform", transformMatrix);
|
||||
program.bindAttributesAndUniforms();
|
||||
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
|
||||
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, /* first= */ 0, /* count= */ 4);
|
||||
|
@ -42,7 +42,7 @@ import androidx.media3.exoplayer.drm.HttpMediaDrmCallback;
|
||||
import androidx.media3.exoplayer.source.MediaSource;
|
||||
import androidx.media3.exoplayer.source.ProgressiveMediaSource;
|
||||
import androidx.media3.exoplayer.util.EventLogger;
|
||||
import androidx.media3.ui.StyledPlayerView;
|
||||
import androidx.media3.ui.PlayerView;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
@ -61,7 +61,7 @@ public final class MainActivity extends Activity {
|
||||
private static final String DRM_SCHEME_EXTRA = "drm_scheme";
|
||||
private static final String DRM_LICENSE_URL_EXTRA = "drm_license_url";
|
||||
|
||||
@Nullable private StyledPlayerView playerView;
|
||||
@Nullable private PlayerView playerView;
|
||||
@Nullable private VideoProcessingGLSurfaceView videoProcessingGLSurfaceView;
|
||||
|
||||
@Nullable private ExoPlayer player;
|
||||
@ -181,7 +181,7 @@ public final class MainActivity extends Activity {
|
||||
Assertions.checkNotNull(this.videoProcessingGLSurfaceView);
|
||||
videoProcessingGLSurfaceView.setPlayer(player);
|
||||
Assertions.checkNotNull(playerView).setPlayer(player);
|
||||
player.addAnalyticsListener(new EventLogger(/* trackSelector= */ null));
|
||||
player.addAnalyticsListener(new EventLogger());
|
||||
this.player = player;
|
||||
}
|
||||
|
||||
|
@ -20,7 +20,7 @@
|
||||
android:layout_height="match_parent"
|
||||
android:keepScreenOn="true">
|
||||
|
||||
<androidx.media3.ui.StyledPlayerView
|
||||
<androidx.media3.ui.PlayerView
|
||||
android:id="@+id/player_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
@ -76,6 +76,7 @@
|
||||
<data android:scheme="content"/>
|
||||
<data android:scheme="asset"/>
|
||||
<data android:scheme="file"/>
|
||||
<data android:scheme="ssai"/>
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="androidx.media3.demo.main.action.VIEW_LIST"/>
|
||||
|
@ -35,31 +35,31 @@
|
||||
"name": "HD (cenc)",
|
||||
"uri": "https://storage.googleapis.com/wvmedia/cenc/h264/tears/tears.mpd",
|
||||
"drm_scheme": "widevine",
|
||||
"drm_license_uri": "https://proxy.uat.widevine.com/proxy?provider=widevine_test"
|
||||
"drm_license_uri": "https://proxy.uat.widevine.com/proxy?video_id=2015_tears&provider=widevine_test"
|
||||
},
|
||||
{
|
||||
"name": "UHD (cenc)",
|
||||
"uri": "https://storage.googleapis.com/wvmedia/cenc/h264/tears/tears_uhd.mpd",
|
||||
"drm_scheme": "widevine",
|
||||
"drm_license_uri": "https://proxy.uat.widevine.com/proxy?provider=widevine_test"
|
||||
"drm_license_uri": "https://proxy.uat.widevine.com/proxy?video_id=2015_tears&provider=widevine_test"
|
||||
},
|
||||
{
|
||||
"name": "HD (cbcs)",
|
||||
"uri": "https://storage.googleapis.com/wvmedia/cbcs/h264/tears/tears_aes_cbcs.mpd",
|
||||
"drm_scheme": "widevine",
|
||||
"drm_license_uri": "https://proxy.uat.widevine.com/proxy?provider=widevine_test"
|
||||
"drm_license_uri": "https://proxy.uat.widevine.com/proxy?video_id=2015_tears&provider=widevine_test"
|
||||
},
|
||||
{
|
||||
"name": "UHD (cbcs)",
|
||||
"uri": "https://storage.googleapis.com/wvmedia/cbcs/h264/tears/tears_aes_cbcs_uhd.mpd",
|
||||
"drm_scheme": "widevine",
|
||||
"drm_license_uri": "https://proxy.uat.widevine.com/proxy?provider=widevine_test"
|
||||
"drm_license_uri": "https://proxy.uat.widevine.com/proxy?video_id=2015_tears&provider=widevine_test"
|
||||
},
|
||||
{
|
||||
"name": "Secure -> Clear -> Secure (cenc)",
|
||||
"uri": "https://storage.googleapis.com/exoplayer-test-media-1/widevine/tears_enc_clear_enc.mpd",
|
||||
"drm_scheme": "widevine",
|
||||
"drm_license_uri": "https://proxy.uat.widevine.com/proxy?provider=widevine_test",
|
||||
"drm_license_uri": "https://proxy.uat.widevine.com/proxy?video_id=2015_tears&provider=widevine_test",
|
||||
"drm_session_for_clear_content": true
|
||||
}
|
||||
]
|
||||
@ -71,25 +71,25 @@
|
||||
"name": "HD (cenc, full-sample)",
|
||||
"uri": "https://storage.googleapis.com/wvmedia/cenc/vp9/tears/tears.mpd",
|
||||
"drm_scheme": "widevine",
|
||||
"drm_license_uri": "https://proxy.uat.widevine.com/proxy?provider=widevine_test"
|
||||
"drm_license_uri": "https://proxy.uat.widevine.com/proxy?video_id=2015_tears&provider=widevine_test"
|
||||
},
|
||||
{
|
||||
"name": "UHD (cenc, full-sample)",
|
||||
"uri": "https://storage.googleapis.com/wvmedia/cenc/vp9/tears/tears_uhd.mpd",
|
||||
"drm_scheme": "widevine",
|
||||
"drm_license_uri": "https://proxy.uat.widevine.com/proxy?provider=widevine_test"
|
||||
"drm_license_uri": "https://proxy.uat.widevine.com/proxy?video_id=2015_tears&provider=widevine_test"
|
||||
},
|
||||
{
|
||||
"name": "HD (cenc, sub-sample)",
|
||||
"uri": "https://storage.googleapis.com/wvmedia/cenc/vp9/subsample/24fps/tears/tears.mpd",
|
||||
"drm_scheme": "widevine",
|
||||
"drm_license_uri": "https://proxy.uat.widevine.com/proxy?provider=widevine_test"
|
||||
"drm_license_uri": "https://proxy.uat.widevine.com/proxy?video_id=2015_tears&provider=widevine_test"
|
||||
},
|
||||
{
|
||||
"name": "UHD (cenc, sub-sample)",
|
||||
"uri": "https://storage.googleapis.com/wvmedia/cenc/vp9/subsample/24fps/tears/tears_uhd.mpd",
|
||||
"drm_scheme": "widevine",
|
||||
"drm_license_uri": "https://proxy.uat.widevine.com/proxy?provider=widevine_test"
|
||||
"drm_license_uri": "https://proxy.uat.widevine.com/proxy?video_id=2015_tears&provider=widevine_test"
|
||||
}
|
||||
]
|
||||
},
|
||||
@ -100,13 +100,13 @@
|
||||
"name": "HD (cenc)",
|
||||
"uri": "https://storage.googleapis.com/wvmedia/cenc/hevc/tears/tears.mpd",
|
||||
"drm_scheme": "widevine",
|
||||
"drm_license_uri": "https://proxy.uat.widevine.com/proxy?provider=widevine_test"
|
||||
"drm_license_uri": "https://proxy.uat.widevine.com/proxy?video_id=2015_tears&provider=widevine_test"
|
||||
},
|
||||
{
|
||||
"name": "UHD (cenc)",
|
||||
"uri": "https://storage.googleapis.com/wvmedia/cenc/hevc/tears/tears_uhd.mpd",
|
||||
"drm_scheme": "widevine",
|
||||
"drm_license_uri": "https://proxy.uat.widevine.com/proxy?provider=widevine_test"
|
||||
"drm_license_uri": "https://proxy.uat.widevine.com/proxy?video_id=2015_tears&provider=widevine_test"
|
||||
}
|
||||
]
|
||||
},
|
||||
@ -387,6 +387,98 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "IMA DAI streams",
|
||||
"samples": [
|
||||
{
|
||||
"name": "HLS VOD: Demo (skippable pre/post), single ads [30 s]",
|
||||
"uri": "ssai://dai.google.com/?contentSourceId=2483977&videoId=ima-vod-skippable-test&format=2&adsId=1"
|
||||
},
|
||||
{
|
||||
"name": "HLS VOD: Tears of Steel (pre/mid/mid/mid/post), single ads [10s]",
|
||||
"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]",
|
||||
"uri": "ssai://dai.google.com/?assetKey=sN_IYUG8STe1ZzhIIE_ksA&format=2&adsId=3"
|
||||
},
|
||||
{
|
||||
"name": "DASH VOD: Tears of Steel (11 periods, pre/mid/post), 2/5/2 ads [5/10s]",
|
||||
"uri": "ssai://dai.google.com/?contentSourceId=2559737&videoId=tos-dash&format=0&adsId=1"
|
||||
},
|
||||
{
|
||||
"name": "Playlist: No ads - HLS VOD: Demo (skippable pre/post) - No ads",
|
||||
"playlist": [
|
||||
{
|
||||
"uri": "https://html5demos.com/assets/dizzy.mp4"
|
||||
},
|
||||
{
|
||||
"uri": "ssai://dai.google.com/?contentSourceId=2483977&videoId=ima-vod-skippable-test&format=2&adsId=1"
|
||||
},
|
||||
{
|
||||
"uri": "https://html5demos.com/assets/dizzy.mp4"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Playlist: No ads - HLS VOD: Tears of steel (pre/mid/mid/mid/post) - No ads",
|
||||
"playlist": [
|
||||
{
|
||||
"uri": "https://html5demos.com/assets/dizzy.mp4"
|
||||
},
|
||||
{
|
||||
"uri": "ssai://dai.google.com/?contentSourceId=2528370&videoId=tears-of-steel&format=2&adsId=1"
|
||||
},
|
||||
{
|
||||
"uri": "https://html5demos.com/assets/dizzy.mp4"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Playlist: No ads - HLS Live: Big Buck Bunny (mid) - No ads",
|
||||
"playlist": [
|
||||
{
|
||||
"uri": "https://html5demos.com/assets/dizzy.mp4"
|
||||
},
|
||||
{
|
||||
"uri": "ssai://dai.google.com/?assetKey=sN_IYUG8STe1ZzhIIE_ksA&format=2&adsId=3"
|
||||
},
|
||||
{
|
||||
"uri": "https://html5demos.com/assets/dizzy.mp4"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Playlist: No ads - DASH VOD: Tears of Steel (11 periods, pre/mid/post) - No ads",
|
||||
"playlist": [
|
||||
{
|
||||
"uri": "https://html5demos.com/assets/dizzy.mp4"
|
||||
},
|
||||
{
|
||||
"uri": "ssai://dai.google.com/?contentSourceId=2559737&videoId=tos-dash&format=0&adsId=1"
|
||||
},
|
||||
{
|
||||
"uri": "https://html5demos.com/assets/dizzy.mp4"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Playlist: Client-side Ads - DASH VOD: Tears of Steel (11 periods, pre/mid/post) - No ads",
|
||||
"playlist": [
|
||||
{
|
||||
"uri": "https://storage.googleapis.com/exoplayer-test-media-1/mkv/android-screens-lavf-56.36.100-aac-avc-main-1280x720.mkv",
|
||||
"ad_tag_uri": "https://pubads.g.doubleclick.net/gampad/ads?sz=640x480&iu=/124319096/external/ad_rule_samples&ciu_szs=300x250&ad_rule=1&impl=s&gdfp_req=1&env=vp&output=vmap&unviewed_position_start=1&cust_params=deployment%3Ddevsite%26sample_ar%3Dpremidpost&cmsid=496&vid=short_onecue&correlator="
|
||||
},
|
||||
{
|
||||
"uri": "ssai://dai.google.com/?contentSourceId=2559737&videoId=tos-dash&format=0&adsId=1"
|
||||
},
|
||||
{
|
||||
"uri": "https://html5demos.com/assets/dizzy.mp4"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Playlists",
|
||||
"samples": [
|
||||
|
@ -16,7 +16,6 @@
|
||||
package androidx.media3.demo.main;
|
||||
|
||||
import android.content.Context;
|
||||
import androidx.media3.common.util.Log;
|
||||
import androidx.media3.database.DatabaseProvider;
|
||||
import androidx.media3.database.StandaloneDatabaseProvider;
|
||||
import androidx.media3.datasource.DataSource;
|
||||
@ -31,12 +30,9 @@ import androidx.media3.datasource.cronet.CronetDataSource;
|
||||
import androidx.media3.datasource.cronet.CronetUtil;
|
||||
import androidx.media3.exoplayer.DefaultRenderersFactory;
|
||||
import androidx.media3.exoplayer.RenderersFactory;
|
||||
import androidx.media3.exoplayer.offline.ActionFileUpgradeUtil;
|
||||
import androidx.media3.exoplayer.offline.DefaultDownloadIndex;
|
||||
import androidx.media3.exoplayer.offline.DownloadManager;
|
||||
import androidx.media3.exoplayer.offline.DownloadNotificationHelper;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.CookieHandler;
|
||||
import java.net.CookieManager;
|
||||
import java.net.CookiePolicy;
|
||||
@ -60,8 +56,6 @@ public final class DemoUtil {
|
||||
private static final boolean USE_CRONET_FOR_NETWORKING = true;
|
||||
|
||||
private static final String TAG = "DemoUtil";
|
||||
private static final String DOWNLOAD_ACTION_FILE = "actions";
|
||||
private static final String DOWNLOAD_TRACKER_ACTION_FILE = "tracked_actions";
|
||||
private static final String DOWNLOAD_CONTENT_DIRECTORY = "downloads";
|
||||
|
||||
private static DataSource.@MonotonicNonNull Factory dataSourceFactory;
|
||||
@ -155,14 +149,6 @@ public final class DemoUtil {
|
||||
|
||||
private static synchronized void ensureDownloadManagerInitialized(Context context) {
|
||||
if (downloadManager == null) {
|
||||
DefaultDownloadIndex downloadIndex = new DefaultDownloadIndex(getDatabaseProvider(context));
|
||||
upgradeActionFile(
|
||||
context, DOWNLOAD_ACTION_FILE, downloadIndex, /* addNewDownloadsAsCompleted= */ false);
|
||||
upgradeActionFile(
|
||||
context,
|
||||
DOWNLOAD_TRACKER_ACTION_FILE,
|
||||
downloadIndex,
|
||||
/* addNewDownloadsAsCompleted= */ true);
|
||||
downloadManager =
|
||||
new DownloadManager(
|
||||
context,
|
||||
@ -175,23 +161,6 @@ public final class DemoUtil {
|
||||
}
|
||||
}
|
||||
|
||||
private static synchronized void upgradeActionFile(
|
||||
Context context,
|
||||
String fileName,
|
||||
DefaultDownloadIndex downloadIndex,
|
||||
boolean addNewDownloadsAsCompleted) {
|
||||
try {
|
||||
ActionFileUpgradeUtil.upgradeAndDelete(
|
||||
new File(getDownloadDirectory(context), fileName),
|
||||
/* downloadIdProvider= */ null,
|
||||
downloadIndex,
|
||||
/* deleteOnFailure= */ true,
|
||||
addNewDownloadsAsCompleted);
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "Failed to upgrade action file: " + fileName, e);
|
||||
}
|
||||
}
|
||||
|
||||
private static synchronized DatabaseProvider getDatabaseProvider(Context context) {
|
||||
if (databaseProvider == null) {
|
||||
databaseProvider = new StandaloneDatabaseProvider(context);
|
||||
|
@ -15,8 +15,6 @@
|
||||
*/
|
||||
package androidx.media3.demo.main;
|
||||
|
||||
import static androidx.media3.common.util.Assertions.checkNotNull;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.Bundle;
|
||||
@ -43,6 +41,7 @@ import androidx.media3.exoplayer.ExoPlayer;
|
||||
import androidx.media3.exoplayer.RenderersFactory;
|
||||
import androidx.media3.exoplayer.drm.FrameworkMediaDrm;
|
||||
import androidx.media3.exoplayer.ima.ImaAdsLoader;
|
||||
import androidx.media3.exoplayer.ima.ImaServerSideAdInsertionMediaSource;
|
||||
import androidx.media3.exoplayer.mediacodec.MediaCodecRenderer.DecoderInitializationException;
|
||||
import androidx.media3.exoplayer.mediacodec.MediaCodecUtil.DecoderQueryException;
|
||||
import androidx.media3.exoplayer.offline.DownloadRequest;
|
||||
@ -52,24 +51,26 @@ import androidx.media3.exoplayer.source.ads.AdsLoader;
|
||||
import androidx.media3.exoplayer.trackselection.DefaultTrackSelector;
|
||||
import androidx.media3.exoplayer.util.DebugTextViewHelper;
|
||||
import androidx.media3.exoplayer.util.EventLogger;
|
||||
import androidx.media3.ui.StyledPlayerControlView;
|
||||
import androidx.media3.ui.StyledPlayerView;
|
||||
import androidx.media3.ui.PlayerControlView;
|
||||
import androidx.media3.ui.PlayerView;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
|
||||
/** An activity that plays media using {@link ExoPlayer}. */
|
||||
public class PlayerActivity extends AppCompatActivity
|
||||
implements OnClickListener, StyledPlayerControlView.VisibilityListener {
|
||||
implements OnClickListener, PlayerControlView.VisibilityListener {
|
||||
|
||||
// Saved instance state keys.
|
||||
|
||||
private static final String KEY_TRACK_SELECTION_PARAMETERS = "track_selection_parameters";
|
||||
private static final String KEY_SERVER_SIDE_ADS_LOADER_STATE = "server_side_ads_loader_state";
|
||||
private static final String KEY_ITEM_INDEX = "item_index";
|
||||
private static final String KEY_POSITION = "position";
|
||||
private static final String KEY_AUTO_PLAY = "auto_play";
|
||||
|
||||
protected StyledPlayerView playerView;
|
||||
protected PlayerView playerView;
|
||||
protected LinearLayout debugRootView;
|
||||
protected TextView debugTextView;
|
||||
protected @Nullable ExoPlayer player;
|
||||
@ -88,7 +89,10 @@ public class PlayerActivity extends AppCompatActivity
|
||||
|
||||
// For ad playback only.
|
||||
|
||||
private AdsLoader adsLoader;
|
||||
@Nullable private AdsLoader clientSideAdsLoader;
|
||||
@Nullable private ImaServerSideAdInsertionMediaSource.AdsLoader serverSideAdsLoader;
|
||||
private ImaServerSideAdInsertionMediaSource.AdsLoader.@MonotonicNonNull State
|
||||
serverSideAdsLoaderState;
|
||||
|
||||
// Activity lifecycle.
|
||||
|
||||
@ -116,6 +120,12 @@ public class PlayerActivity extends AppCompatActivity
|
||||
startAutoPlay = savedInstanceState.getBoolean(KEY_AUTO_PLAY);
|
||||
startItemIndex = savedInstanceState.getInt(KEY_ITEM_INDEX);
|
||||
startPosition = savedInstanceState.getLong(KEY_POSITION);
|
||||
Bundle adsLoaderStateBundle = savedInstanceState.getBundle(KEY_SERVER_SIDE_ADS_LOADER_STATE);
|
||||
if (adsLoaderStateBundle != null) {
|
||||
serverSideAdsLoaderState =
|
||||
ImaServerSideAdInsertionMediaSource.AdsLoader.State.CREATOR.fromBundle(
|
||||
adsLoaderStateBundle);
|
||||
}
|
||||
} else {
|
||||
trackSelectionParameters =
|
||||
new DefaultTrackSelector.ParametersBuilder(/* context= */ this).build();
|
||||
@ -127,7 +137,7 @@ public class PlayerActivity extends AppCompatActivity
|
||||
public void onNewIntent(Intent intent) {
|
||||
super.onNewIntent(intent);
|
||||
releasePlayer();
|
||||
releaseAdsLoader();
|
||||
releaseClientSideAdsLoader();
|
||||
clearStartPosition();
|
||||
setIntent(intent);
|
||||
}
|
||||
@ -179,7 +189,7 @@ public class PlayerActivity extends AppCompatActivity
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
releaseAdsLoader();
|
||||
releaseClientSideAdsLoader();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -208,6 +218,9 @@ public class PlayerActivity extends AppCompatActivity
|
||||
outState.putBoolean(KEY_AUTO_PLAY, startAutoPlay);
|
||||
outState.putInt(KEY_ITEM_INDEX, startItemIndex);
|
||||
outState.putLong(KEY_POSITION, startPosition);
|
||||
if (serverSideAdsLoaderState != null) {
|
||||
outState.putBundle(KEY_SERVER_SIDE_ADS_LOADER_STATE, serverSideAdsLoaderState.toBundle());
|
||||
}
|
||||
}
|
||||
|
||||
// Activity input
|
||||
@ -234,7 +247,7 @@ public class PlayerActivity extends AppCompatActivity
|
||||
}
|
||||
}
|
||||
|
||||
// StyledPlayerControlView.VisibilityListener implementation
|
||||
// PlayerControlView.VisibilityListener implementation
|
||||
|
||||
@Override
|
||||
public void onVisibilityChange(int visibility) {
|
||||
@ -247,7 +260,9 @@ public class PlayerActivity extends AppCompatActivity
|
||||
setContentView(R.layout.player_activity);
|
||||
}
|
||||
|
||||
/** @return Whether initialization was successful. */
|
||||
/**
|
||||
* @return Whether initialization was successful.
|
||||
*/
|
||||
protected boolean initializePlayer() {
|
||||
if (player == null) {
|
||||
Intent intent = getIntent();
|
||||
@ -261,25 +276,22 @@ public class PlayerActivity extends AppCompatActivity
|
||||
intent.getBooleanExtra(IntentUtil.PREFER_EXTENSION_DECODERS_EXTRA, false);
|
||||
RenderersFactory renderersFactory =
|
||||
DemoUtil.buildRenderersFactory(/* context= */ this, preferExtensionDecoders);
|
||||
MediaSource.Factory mediaSourceFactory =
|
||||
new DefaultMediaSourceFactory(dataSourceFactory)
|
||||
.setAdsLoaderProvider(this::getAdsLoader)
|
||||
.setAdViewProvider(playerView);
|
||||
|
||||
trackSelector = new DefaultTrackSelector(/* context= */ this);
|
||||
lastSeenTracksInfo = TracksInfo.EMPTY;
|
||||
player =
|
||||
new ExoPlayer.Builder(/* context= */ this)
|
||||
.setRenderersFactory(renderersFactory)
|
||||
.setMediaSourceFactory(mediaSourceFactory)
|
||||
.setMediaSourceFactory(createMediaSourceFactory())
|
||||
.setTrackSelector(trackSelector)
|
||||
.build();
|
||||
player.setTrackSelectionParameters(trackSelectionParameters);
|
||||
player.addListener(new PlayerEventListener());
|
||||
player.addAnalyticsListener(new EventLogger(trackSelector));
|
||||
player.addAnalyticsListener(new EventLogger());
|
||||
player.setAudioAttributes(AudioAttributes.DEFAULT, /* handleAudioFocus= */ true);
|
||||
player.setPlayWhenReady(startAutoPlay);
|
||||
playerView.setPlayer(player);
|
||||
serverSideAdsLoader.setPlayer(player);
|
||||
debugViewHelper = new DebugTextViewHelper(player, debugTextView);
|
||||
debugViewHelper.start();
|
||||
}
|
||||
@ -293,6 +305,22 @@ public class PlayerActivity extends AppCompatActivity
|
||||
return true;
|
||||
}
|
||||
|
||||
private MediaSource.Factory createMediaSourceFactory() {
|
||||
ImaServerSideAdInsertionMediaSource.AdsLoader.Builder serverSideAdLoaderBuilder =
|
||||
new ImaServerSideAdInsertionMediaSource.AdsLoader.Builder(/* context= */ this, playerView);
|
||||
if (serverSideAdsLoaderState != null) {
|
||||
serverSideAdLoaderBuilder.setAdsLoaderState(serverSideAdsLoaderState);
|
||||
}
|
||||
serverSideAdsLoader = serverSideAdLoaderBuilder.build();
|
||||
ImaServerSideAdInsertionMediaSource.Factory imaServerSideAdInsertionMediaSourceFactory =
|
||||
new ImaServerSideAdInsertionMediaSource.Factory(
|
||||
serverSideAdsLoader, new DefaultMediaSourceFactory(dataSourceFactory));
|
||||
return new DefaultMediaSourceFactory(dataSourceFactory)
|
||||
.setAdsLoaderProvider(this::getClientSideAdsLoader)
|
||||
.setAdViewProvider(playerView)
|
||||
.setServerSideAdInsertionMediaSourceFactory(imaServerSideAdInsertionMediaSourceFactory);
|
||||
}
|
||||
|
||||
private List<MediaItem> createMediaItems(Intent intent) {
|
||||
String action = intent.getAction();
|
||||
boolean actionIsListView = IntentUtil.ACTION_VIEW_LIST.equals(action);
|
||||
@ -304,7 +332,6 @@ public class PlayerActivity extends AppCompatActivity
|
||||
|
||||
List<MediaItem> mediaItems =
|
||||
createMediaItems(intent, DemoUtil.getDownloadTracker(/* context= */ this));
|
||||
boolean hasAds = false;
|
||||
for (int i = 0; i < mediaItems.size(); i++) {
|
||||
MediaItem mediaItem = mediaItems.get(i);
|
||||
|
||||
@ -318,8 +345,7 @@ public class PlayerActivity extends AppCompatActivity
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
MediaItem.DrmConfiguration drmConfiguration =
|
||||
checkNotNull(mediaItem.localConfiguration).drmConfiguration;
|
||||
MediaItem.DrmConfiguration drmConfiguration = mediaItem.localConfiguration.drmConfiguration;
|
||||
if (drmConfiguration != null) {
|
||||
if (Util.SDK_INT < 18) {
|
||||
showToast(R.string.error_drm_unsupported_before_api_18);
|
||||
@ -331,43 +357,44 @@ public class PlayerActivity extends AppCompatActivity
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
hasAds |= mediaItem.localConfiguration.adsConfiguration != null;
|
||||
}
|
||||
if (!hasAds) {
|
||||
releaseAdsLoader();
|
||||
}
|
||||
return mediaItems;
|
||||
}
|
||||
|
||||
private AdsLoader getAdsLoader(MediaItem.AdsConfiguration adsConfiguration) {
|
||||
private AdsLoader getClientSideAdsLoader(MediaItem.AdsConfiguration adsConfiguration) {
|
||||
// The ads loader is reused for multiple playbacks, so that ad playback can resume.
|
||||
if (adsLoader == null) {
|
||||
adsLoader = new ImaAdsLoader.Builder(/* context= */ this).build();
|
||||
if (clientSideAdsLoader == null) {
|
||||
clientSideAdsLoader = new ImaAdsLoader.Builder(/* context= */ this).build();
|
||||
}
|
||||
adsLoader.setPlayer(player);
|
||||
return adsLoader;
|
||||
clientSideAdsLoader.setPlayer(player);
|
||||
return clientSideAdsLoader;
|
||||
}
|
||||
|
||||
protected void releasePlayer() {
|
||||
if (player != null) {
|
||||
updateTrackSelectorParameters();
|
||||
updateStartPosition();
|
||||
serverSideAdsLoaderState = serverSideAdsLoader.release();
|
||||
serverSideAdsLoader = null;
|
||||
debugViewHelper.stop();
|
||||
debugViewHelper = null;
|
||||
player.release();
|
||||
player = null;
|
||||
playerView.setPlayer(/* player= */ null);
|
||||
mediaItems = Collections.emptyList();
|
||||
}
|
||||
if (adsLoader != null) {
|
||||
adsLoader.setPlayer(null);
|
||||
if (clientSideAdsLoader != null) {
|
||||
clientSideAdsLoader.setPlayer(null);
|
||||
} else {
|
||||
playerView.getAdViewGroup().removeAllViews();
|
||||
}
|
||||
}
|
||||
|
||||
private void releaseAdsLoader() {
|
||||
if (adsLoader != null) {
|
||||
adsLoader.release();
|
||||
adsLoader = null;
|
||||
playerView.getOverlayFrameLayout().removeAllViews();
|
||||
private void releaseClientSideAdsLoader() {
|
||||
if (clientSideAdsLoader != null) {
|
||||
clientSideAdsLoader.release();
|
||||
clientSideAdsLoader = null;
|
||||
playerView.getAdViewGroup().removeAllViews();
|
||||
}
|
||||
}
|
||||
|
||||
@ -441,10 +468,14 @@ public class PlayerActivity extends AppCompatActivity
|
||||
if (tracksInfo == lastSeenTracksInfo) {
|
||||
return;
|
||||
}
|
||||
if (!tracksInfo.isTypeSupportedOrEmpty(C.TRACK_TYPE_VIDEO)) {
|
||||
if (tracksInfo.containsType(C.TRACK_TYPE_VIDEO)
|
||||
&& !tracksInfo.isTypeSupported(
|
||||
C.TRACK_TYPE_VIDEO, /* allowExceedsCapabilities= */ true)) {
|
||||
showToast(R.string.error_unsupported_video);
|
||||
}
|
||||
if (!tracksInfo.isTypeSupportedOrEmpty(C.TRACK_TYPE_AUDIO)) {
|
||||
if (tracksInfo.containsType(C.TRACK_TYPE_AUDIO)
|
||||
&& !tracksInfo.isTypeSupported(
|
||||
C.TRACK_TYPE_AUDIO, /* allowExceedsCapabilities= */ true)) {
|
||||
showToast(R.string.error_unsupported_audio);
|
||||
}
|
||||
lastSeenTracksInfo = tracksInfo;
|
||||
@ -488,7 +519,7 @@ public class PlayerActivity extends AppCompatActivity
|
||||
for (MediaItem item : IntentUtil.createMediaItemsFromIntent(intent)) {
|
||||
@Nullable
|
||||
DownloadRequest downloadRequest =
|
||||
downloadTracker.getDownloadRequest(checkNotNull(item.localConfiguration).uri);
|
||||
downloadTracker.getDownloadRequest(item.localConfiguration.uri);
|
||||
if (downloadRequest != null) {
|
||||
MediaItem.Builder builder = item.buildUpon();
|
||||
builder
|
||||
|
@ -21,7 +21,7 @@
|
||||
android:layout_height="match_parent"
|
||||
android:keepScreenOn="true">
|
||||
|
||||
<androidx.media3.ui.StyledPlayerView android:id="@+id/player_view"
|
||||
<androidx.media3.ui.PlayerView android:id="@+id/player_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
app:show_shuffle_button="true"
|
||||
|
@ -61,9 +61,9 @@ dependencies {
|
||||
implementation 'androidx.appcompat:appcompat:' + androidxAppCompatVersion
|
||||
implementation 'androidx.multidex:multidex:' + androidxMultidexVersion
|
||||
implementation 'com.google.android.material:material:' + androidxMaterialVersion
|
||||
implementation project(modulePrefix + ':lib-exoplayer')
|
||||
implementation project(modulePrefix + ':lib-exoplayer-dash')
|
||||
implementation project(modulePrefix + ':lib-exoplayer-hls')
|
||||
implementation project(modulePrefix + ':lib-ui')
|
||||
implementation project(modulePrefix + ':lib-session')
|
||||
implementation project(modulePrefix + 'lib-exoplayer')
|
||||
implementation project(modulePrefix + 'lib-exoplayer-dash')
|
||||
implementation project(modulePrefix + 'lib-exoplayer-hls')
|
||||
implementation project(modulePrefix + 'lib-ui')
|
||||
implementation project(modulePrefix + 'lib-session')
|
||||
}
|
||||
|
@ -21,15 +21,14 @@
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
android:allowBackup="false"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/Theme.Media3Demo">
|
||||
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:exported="true">
|
||||
android:name=".MainActivity"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
@ -37,18 +36,16 @@
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".PlayerActivity"
|
||||
android:exported="true">
|
||||
</activity>
|
||||
android:name=".PlayerActivity"
|
||||
android:exported="true"/>
|
||||
|
||||
<activity
|
||||
android:name=".PlayableFolderActivity"
|
||||
android:exported="true">
|
||||
</activity>
|
||||
android:name=".PlayableFolderActivity"
|
||||
android:exported="true"/>
|
||||
|
||||
<service
|
||||
android:name=".PlaybackService"
|
||||
android:exported="true">
|
||||
android:name=".PlaybackService"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="androidx.media3.session.MediaSessionService"/>
|
||||
<action android:name="android.media.browse.MediaBrowserService"/>
|
||||
|
@ -2,7 +2,7 @@
|
||||
"media": [
|
||||
{
|
||||
"id": "video_01",
|
||||
"title": "Future Scenerio",
|
||||
"title": "Future Scenario",
|
||||
"album": "Mango Open Movie project",
|
||||
"artist": "Blender Foundation",
|
||||
"genre": "Video",
|
||||
|
@ -34,7 +34,7 @@ import androidx.media3.common.MediaMetadata
|
||||
import androidx.media3.common.Player
|
||||
import androidx.media3.session.MediaController
|
||||
import androidx.media3.session.SessionToken
|
||||
import androidx.media3.ui.StyledPlayerView
|
||||
import androidx.media3.ui.PlayerView
|
||||
import com.google.common.util.concurrent.ListenableFuture
|
||||
import com.google.common.util.concurrent.MoreExecutors
|
||||
|
||||
@ -43,7 +43,7 @@ class PlayerActivity : AppCompatActivity() {
|
||||
private val controller: MediaController?
|
||||
get() = if (controllerFuture.isDone) controllerFuture.get() else null
|
||||
|
||||
private lateinit var playerView: StyledPlayerView
|
||||
private lateinit var playerView: PlayerView
|
||||
private lateinit var mediaList: ListView
|
||||
private lateinit var mediaListAdapter: PlayingMediaItemArrayAdapter
|
||||
private val subItemMediaList: MutableList<MediaItem> = mutableListOf()
|
||||
|
After Width: | Height: | Size: 114 B |
After Width: | Height: | Size: 251 B |
After Width: | Height: | Size: 109 B |
After Width: | Height: | Size: 191 B |
After Width: | Height: | Size: 113 B |
After Width: | Height: | Size: 298 B |
After Width: | Height: | Size: 121 B |
After Width: | Height: | Size: 383 B |
After Width: | Height: | Size: 126 B |
After Width: | Height: | Size: 455 B |
@ -26,7 +26,7 @@
|
||||
android:layout_height="300dp"
|
||||
android:layout_width="match_parent"
|
||||
>
|
||||
<androidx.media3.ui.StyledPlayerView
|
||||
<androidx.media3.ui.PlayerView
|
||||
android:id="@+id/player_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
|
@ -34,7 +34,7 @@
|
||||
android:layout_width="50dp"
|
||||
android:layout_height="match_parent"
|
||||
android:id="@+id/add_button"
|
||||
android:background="@android:drawable/ic_input_add"
|
||||
android:background="@drawable/baseline_playlist_add_white_48"
|
||||
/>
|
||||
|
||||
</LinearLayout>
|
||||
|
@ -35,7 +35,7 @@
|
||||
android:layout_width="50dp"
|
||||
android:layout_height="match_parent"
|
||||
android:id="@+id/delete_button"
|
||||
android:background="@android:drawable/ic_menu_close_clear_cancel"
|
||||
android:background="@drawable/baseline_playlist_remove_white_48"
|
||||
/>
|
||||
|
||||
</LinearLayout>
|
||||
|
@ -22,12 +22,14 @@
|
||||
<uses-sdk/>
|
||||
|
||||
<application
|
||||
android:allowBackup="false"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/application_name"
|
||||
android:exported="true">
|
||||
android:allowBackup="false"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/application_name"
|
||||
android:exported="true">
|
||||
|
||||
<activity android:name=".MainActivity">
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN"/>
|
||||
<category android:name="android.intent.category.LAUNCHER"/>
|
||||
|
9
demos/transformer/README.md
Normal file
@ -0,0 +1,9 @@
|
||||
# Transformer demo
|
||||
|
||||
This app demonstrates how to use the [Transformer][] API to modify videos, for
|
||||
example by removing audio or video.
|
||||
|
||||
See the [demos README](../README.md) for instructions on how to build and run
|
||||
this demo.
|
||||
|
||||
[Transformer]: https://exoplayer.dev/transforming-media.html
|
61
demos/transformer/build.gradle
Normal file
@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Copyright 2021 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.
|
||||
*/
|
||||
apply from: '../../constants.gradle'
|
||||
apply plugin: 'com.android.application'
|
||||
|
||||
android {
|
||||
compileSdkVersion project.ext.compileSdkVersion
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
versionName project.ext.releaseVersion
|
||||
versionCode project.ext.releaseVersionCode
|
||||
minSdkVersion 21
|
||||
targetSdkVersion project.ext.appTargetSdkVersion
|
||||
multiDexEnabled true
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
shrinkResources true
|
||||
minifyEnabled true
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt')
|
||||
signingConfig signingConfigs.debug
|
||||
}
|
||||
}
|
||||
|
||||
lintOptions {
|
||||
// This demo app isn't indexed and doesn't have translations.
|
||||
disable 'GoogleAppIndexingWarning','MissingTranslation'
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'androidx.core:core:' + androidxCoreVersion
|
||||
compileOnly 'org.checkerframework:checker-qual:' + checkerframeworkVersion
|
||||
implementation 'androidx.annotation:annotation:' + androidxAnnotationVersion
|
||||
implementation 'androidx.appcompat:appcompat:' + androidxAppCompatVersion
|
||||
implementation 'androidx.constraintlayout:constraintlayout:' + androidxConstraintLayoutVersion
|
||||
implementation 'androidx.multidex:multidex:' + androidxMultidexVersion
|
||||
implementation 'com.google.android.material:material:' + androidxMaterialVersion
|
||||
implementation project(modulePrefix + 'lib-exoplayer')
|
||||
implementation project(modulePrefix + 'lib-transformer')
|
||||
implementation project(modulePrefix + 'lib-ui')
|
||||
}
|
60
demos/transformer/src/main/AndroidManifest.xml
Normal file
@ -0,0 +1,60 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Copyright 2021 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.
|
||||
-->
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
package="androidx.media3.demo.transformer">
|
||||
<uses-sdk />
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
||||
|
||||
<application
|
||||
android:allowBackup="false"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:theme="@style/Theme.AppCompat"
|
||||
android:taskAffinity=""
|
||||
tools:targetApi="29">
|
||||
<activity android:name=".ConfigurationActivity"
|
||||
android:configChanges="keyboard|keyboardHidden|orientation|screenSize|screenLayout|smallestScreenSize|uiMode"
|
||||
android:launchMode="singleTop"
|
||||
android:label="@string/app_name"
|
||||
android:exported="true"
|
||||
android:theme="@style/Theme.MaterialComponents.DayNight.NoActionBar">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN"/>
|
||||
<category android:name="android.intent.category.LAUNCHER"/>
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="androidx.media3.demo.transformer.action.VIEW"/>
|
||||
<category android:name="android.intent.category.DEFAULT"/>
|
||||
<data android:scheme="http"/>
|
||||
<data android:scheme="https"/>
|
||||
<data android:scheme="content"/>
|
||||
<data android:scheme="asset"/>
|
||||
<data android:scheme="file"/>
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity android:name=".TransformerActivity"
|
||||
android:configChanges="keyboard|keyboardHidden|orientation|screenSize|screenLayout|smallestScreenSize|uiMode"
|
||||
android:launchMode="singleTop"
|
||||
android:label="@string/app_name"
|
||||
android:exported="true"
|
||||
android:theme="@style/Theme.MaterialComponents.DayNight.NoActionBar"/>
|
||||
</application>
|
||||
</manifest>
|
@ -0,0 +1,320 @@
|
||||
/*
|
||||
* Copyright 2021 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.demo.transformer;
|
||||
|
||||
import static androidx.media3.common.util.Assertions.checkNotNull;
|
||||
import static androidx.media3.common.util.Assertions.checkState;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.Button;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.Spinner;
|
||||
import android.widget.TextView;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.media3.common.MimeTypes;
|
||||
import androidx.media3.common.util.Util;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
||||
|
||||
/**
|
||||
* An {@link Activity} that sets the configuration to use for transforming and playing media, using
|
||||
* {@link TransformerActivity}.
|
||||
*/
|
||||
public final class ConfigurationActivity extends AppCompatActivity {
|
||||
public static final String SHOULD_REMOVE_AUDIO = "should_remove_audio";
|
||||
public static final String SHOULD_REMOVE_VIDEO = "should_remove_video";
|
||||
public static final String SHOULD_FLATTEN_FOR_SLOW_MOTION = "should_flatten_for_slow_motion";
|
||||
public static final String AUDIO_MIME_TYPE = "audio_mime_type";
|
||||
public static final String VIDEO_MIME_TYPE = "video_mime_type";
|
||||
public static final String RESOLUTION_HEIGHT = "resolution_height";
|
||||
public static final String TRANSLATE_X = "translate_x";
|
||||
public static final String TRANSLATE_Y = "translate_y";
|
||||
public static final String SCALE_X = "scale_x";
|
||||
public static final String SCALE_Y = "scale_y";
|
||||
public static final String ROTATE_DEGREES = "rotate_degrees";
|
||||
public static final String ENABLE_FALLBACK = "enable_fallback";
|
||||
public static final String ENABLE_HDR_EDITING = "enable_hdr_editing";
|
||||
private static final String[] INPUT_URIS = {
|
||||
"https://html5demos.com/assets/dizzy.mp4",
|
||||
"https://storage.googleapis.com/exoplayer-test-media-0/android-block-1080-hevc.mp4",
|
||||
"https://storage.googleapis.com/exoplayer-test-media-0/BigBuckBunny_320x180.mp4",
|
||||
"https://html5demos.com/assets/dizzy.webm",
|
||||
"https://storage.googleapis.com/exoplayer-test-media-1/mp4/portrait_4k60.mp4",
|
||||
};
|
||||
private static final String[] URI_DESCRIPTIONS = { // same order as INPUT_URIS
|
||||
"MP4 with H264 video and AAC audio",
|
||||
"MP4 with H265 video and AAC audio",
|
||||
"Long MP4 with H264 video and AAC audio",
|
||||
"WebM with VP8 video and Vorbis audio",
|
||||
"4K 60fps MP4 with H264 video and AAC audio (portrait, timestamps always increase)",
|
||||
};
|
||||
private static final String SAME_AS_INPUT_OPTION = "same as input";
|
||||
|
||||
private @MonotonicNonNull Button chooseFileButton;
|
||||
private @MonotonicNonNull TextView chosenFileTextView;
|
||||
private @MonotonicNonNull CheckBox removeAudioCheckbox;
|
||||
private @MonotonicNonNull CheckBox removeVideoCheckbox;
|
||||
private @MonotonicNonNull CheckBox flattenForSlowMotionCheckbox;
|
||||
private @MonotonicNonNull Spinner audioMimeSpinner;
|
||||
private @MonotonicNonNull Spinner videoMimeSpinner;
|
||||
private @MonotonicNonNull Spinner resolutionHeightSpinner;
|
||||
private @MonotonicNonNull Spinner translateSpinner;
|
||||
private @MonotonicNonNull Spinner scaleSpinner;
|
||||
private @MonotonicNonNull Spinner rotateSpinner;
|
||||
private @MonotonicNonNull CheckBox enableFallbackCheckBox;
|
||||
private @MonotonicNonNull CheckBox enableHdrEditingCheckBox;
|
||||
private int inputUriPosition;
|
||||
|
||||
@Override
|
||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.configuration_activity);
|
||||
|
||||
findViewById(R.id.transform_button).setOnClickListener(this::startTransformation);
|
||||
|
||||
chooseFileButton = findViewById(R.id.choose_file_button);
|
||||
chooseFileButton.setOnClickListener(this::chooseFile);
|
||||
|
||||
chosenFileTextView = findViewById(R.id.chosen_file_text_view);
|
||||
chosenFileTextView.setText(URI_DESCRIPTIONS[inputUriPosition]);
|
||||
|
||||
removeAudioCheckbox = findViewById(R.id.remove_audio_checkbox);
|
||||
removeAudioCheckbox.setOnClickListener(this::onRemoveAudio);
|
||||
|
||||
removeVideoCheckbox = findViewById(R.id.remove_video_checkbox);
|
||||
removeVideoCheckbox.setOnClickListener(this::onRemoveVideo);
|
||||
|
||||
flattenForSlowMotionCheckbox = findViewById(R.id.flatten_for_slow_motion_checkbox);
|
||||
|
||||
ArrayAdapter<String> audioMimeAdapter =
|
||||
new ArrayAdapter<>(/* context= */ this, R.layout.spinner_item);
|
||||
audioMimeAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
|
||||
audioMimeSpinner = findViewById(R.id.audio_mime_spinner);
|
||||
audioMimeSpinner.setAdapter(audioMimeAdapter);
|
||||
audioMimeAdapter.addAll(
|
||||
SAME_AS_INPUT_OPTION, MimeTypes.AUDIO_AAC, MimeTypes.AUDIO_AMR_NB, MimeTypes.AUDIO_AMR_WB);
|
||||
|
||||
ArrayAdapter<String> videoMimeAdapter =
|
||||
new ArrayAdapter<>(/* context= */ this, R.layout.spinner_item);
|
||||
videoMimeAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
|
||||
videoMimeSpinner = findViewById(R.id.video_mime_spinner);
|
||||
videoMimeSpinner.setAdapter(videoMimeAdapter);
|
||||
videoMimeAdapter.addAll(
|
||||
SAME_AS_INPUT_OPTION, MimeTypes.VIDEO_H263, MimeTypes.VIDEO_H264, MimeTypes.VIDEO_MP4V);
|
||||
if (Util.SDK_INT >= 24) {
|
||||
videoMimeAdapter.add(MimeTypes.VIDEO_H265);
|
||||
}
|
||||
|
||||
ArrayAdapter<String> resolutionHeightAdapter =
|
||||
new ArrayAdapter<>(/* context= */ this, R.layout.spinner_item);
|
||||
resolutionHeightAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
|
||||
resolutionHeightSpinner = findViewById(R.id.resolution_height_spinner);
|
||||
resolutionHeightSpinner.setAdapter(resolutionHeightAdapter);
|
||||
resolutionHeightAdapter.addAll(
|
||||
SAME_AS_INPUT_OPTION, "144", "240", "360", "480", "720", "1080", "1440", "2160");
|
||||
|
||||
ArrayAdapter<String> translateAdapter =
|
||||
new ArrayAdapter<>(/* context= */ this, R.layout.spinner_item);
|
||||
translateAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
|
||||
translateSpinner = findViewById(R.id.translate_spinner);
|
||||
translateSpinner.setAdapter(translateAdapter);
|
||||
translateAdapter.addAll(
|
||||
SAME_AS_INPUT_OPTION, "-.1, -.1", "0, 0", ".5, 0", "0, .5", "1, 1", "1.9, 0", "0, 1.9");
|
||||
|
||||
ArrayAdapter<String> scaleAdapter =
|
||||
new ArrayAdapter<>(/* context= */ this, R.layout.spinner_item);
|
||||
scaleAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
|
||||
scaleSpinner = findViewById(R.id.scale_spinner);
|
||||
scaleSpinner.setAdapter(scaleAdapter);
|
||||
scaleAdapter.addAll(SAME_AS_INPUT_OPTION, "-1, -1", "-1, 1", "1, 1", ".5, 1", ".5, .5", "2, 2");
|
||||
|
||||
ArrayAdapter<String> rotateAdapter =
|
||||
new ArrayAdapter<>(/* context= */ this, R.layout.spinner_item);
|
||||
rotateAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
|
||||
rotateSpinner = findViewById(R.id.rotate_spinner);
|
||||
rotateSpinner.setAdapter(rotateAdapter);
|
||||
rotateAdapter.addAll(SAME_AS_INPUT_OPTION, "0", "10", "45", "60", "90", "180");
|
||||
|
||||
enableFallbackCheckBox = findViewById(R.id.enable_fallback_checkbox);
|
||||
enableHdrEditingCheckBox = findViewById(R.id.hdr_editing_checkbox);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
@Nullable Uri intentUri = getIntent().getData();
|
||||
if (intentUri != null) {
|
||||
checkNotNull(chooseFileButton).setEnabled(false);
|
||||
checkNotNull(chosenFileTextView).setText(intentUri.toString());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onNewIntent(Intent intent) {
|
||||
super.onNewIntent(intent);
|
||||
setIntent(intent);
|
||||
}
|
||||
|
||||
@RequiresNonNull({
|
||||
"removeAudioCheckbox",
|
||||
"removeVideoCheckbox",
|
||||
"flattenForSlowMotionCheckbox",
|
||||
"audioMimeSpinner",
|
||||
"videoMimeSpinner",
|
||||
"resolutionHeightSpinner",
|
||||
"translateSpinner",
|
||||
"scaleSpinner",
|
||||
"rotateSpinner",
|
||||
"enableFallbackCheckBox",
|
||||
"enableHdrEditingCheckBox"
|
||||
})
|
||||
private void startTransformation(View view) {
|
||||
Intent transformerIntent = new Intent(this, TransformerActivity.class);
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putBoolean(SHOULD_REMOVE_AUDIO, removeAudioCheckbox.isChecked());
|
||||
bundle.putBoolean(SHOULD_REMOVE_VIDEO, removeVideoCheckbox.isChecked());
|
||||
bundle.putBoolean(SHOULD_FLATTEN_FOR_SLOW_MOTION, flattenForSlowMotionCheckbox.isChecked());
|
||||
String selectedAudioMimeType = String.valueOf(audioMimeSpinner.getSelectedItem());
|
||||
if (!SAME_AS_INPUT_OPTION.equals(selectedAudioMimeType)) {
|
||||
bundle.putString(AUDIO_MIME_TYPE, selectedAudioMimeType);
|
||||
}
|
||||
String selectedVideoMimeType = String.valueOf(videoMimeSpinner.getSelectedItem());
|
||||
if (!SAME_AS_INPUT_OPTION.equals(selectedVideoMimeType)) {
|
||||
bundle.putString(VIDEO_MIME_TYPE, selectedVideoMimeType);
|
||||
}
|
||||
String selectedResolutionHeight = String.valueOf(resolutionHeightSpinner.getSelectedItem());
|
||||
if (!SAME_AS_INPUT_OPTION.equals(selectedResolutionHeight)) {
|
||||
bundle.putInt(RESOLUTION_HEIGHT, Integer.parseInt(selectedResolutionHeight));
|
||||
}
|
||||
String selectedTranslate = String.valueOf(translateSpinner.getSelectedItem());
|
||||
if (!SAME_AS_INPUT_OPTION.equals(selectedTranslate)) {
|
||||
List<String> translateXY = Arrays.asList(selectedTranslate.split(", "));
|
||||
checkState(translateXY.size() == 2);
|
||||
bundle.putFloat(TRANSLATE_X, Float.parseFloat(translateXY.get(0)));
|
||||
bundle.putFloat(TRANSLATE_Y, Float.parseFloat(translateXY.get(1)));
|
||||
}
|
||||
String selectedScale = String.valueOf(scaleSpinner.getSelectedItem());
|
||||
if (!SAME_AS_INPUT_OPTION.equals(selectedScale)) {
|
||||
List<String> scaleXY = Arrays.asList(selectedScale.split(", "));
|
||||
checkState(scaleXY.size() == 2);
|
||||
bundle.putFloat(SCALE_X, Float.parseFloat(scaleXY.get(0)));
|
||||
bundle.putFloat(SCALE_Y, Float.parseFloat(scaleXY.get(1)));
|
||||
}
|
||||
String selectedRotate = String.valueOf(rotateSpinner.getSelectedItem());
|
||||
if (!SAME_AS_INPUT_OPTION.equals(selectedRotate)) {
|
||||
bundle.putFloat(ROTATE_DEGREES, Float.parseFloat(selectedRotate));
|
||||
}
|
||||
bundle.putBoolean(ENABLE_FALLBACK, enableFallbackCheckBox.isChecked());
|
||||
bundle.putBoolean(ENABLE_HDR_EDITING, enableHdrEditingCheckBox.isChecked());
|
||||
transformerIntent.putExtras(bundle);
|
||||
|
||||
@Nullable Uri intentUri = getIntent().getData();
|
||||
transformerIntent.setData(
|
||||
intentUri != null ? intentUri : Uri.parse(INPUT_URIS[inputUriPosition]));
|
||||
|
||||
startActivity(transformerIntent);
|
||||
}
|
||||
|
||||
private void chooseFile(View view) {
|
||||
new AlertDialog.Builder(/* context= */ this)
|
||||
.setTitle(R.string.choose_file_title)
|
||||
.setSingleChoiceItems(URI_DESCRIPTIONS, inputUriPosition, this::selectFileInDialog)
|
||||
.setPositiveButton(android.R.string.ok, /* listener= */ null)
|
||||
.create()
|
||||
.show();
|
||||
}
|
||||
|
||||
@RequiresNonNull("chosenFileTextView")
|
||||
private void selectFileInDialog(DialogInterface dialog, int which) {
|
||||
inputUriPosition = which;
|
||||
chosenFileTextView.setText(URI_DESCRIPTIONS[inputUriPosition]);
|
||||
}
|
||||
|
||||
@RequiresNonNull({
|
||||
"removeVideoCheckbox",
|
||||
"audioMimeSpinner",
|
||||
"videoMimeSpinner",
|
||||
"resolutionHeightSpinner",
|
||||
"translateSpinner",
|
||||
"scaleSpinner",
|
||||
"rotateSpinner",
|
||||
"enableHdrEditingCheckBox"
|
||||
})
|
||||
private void onRemoveAudio(View view) {
|
||||
if (((CheckBox) view).isChecked()) {
|
||||
removeVideoCheckbox.setChecked(false);
|
||||
enableTrackSpecificOptions(/* isAudioEnabled= */ false, /* isVideoEnabled= */ true);
|
||||
} else {
|
||||
enableTrackSpecificOptions(/* isAudioEnabled= */ true, /* isVideoEnabled= */ true);
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresNonNull({
|
||||
"removeAudioCheckbox",
|
||||
"audioMimeSpinner",
|
||||
"videoMimeSpinner",
|
||||
"resolutionHeightSpinner",
|
||||
"translateSpinner",
|
||||
"scaleSpinner",
|
||||
"rotateSpinner",
|
||||
"enableHdrEditingCheckBox"
|
||||
})
|
||||
private void onRemoveVideo(View view) {
|
||||
if (((CheckBox) view).isChecked()) {
|
||||
removeAudioCheckbox.setChecked(false);
|
||||
enableTrackSpecificOptions(/* isAudioEnabled= */ true, /* isVideoEnabled= */ false);
|
||||
} else {
|
||||
enableTrackSpecificOptions(/* isAudioEnabled= */ true, /* isVideoEnabled= */ true);
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresNonNull({
|
||||
"audioMimeSpinner",
|
||||
"videoMimeSpinner",
|
||||
"resolutionHeightSpinner",
|
||||
"translateSpinner",
|
||||
"scaleSpinner",
|
||||
"rotateSpinner",
|
||||
"enableHdrEditingCheckBox"
|
||||
})
|
||||
private void enableTrackSpecificOptions(boolean isAudioEnabled, boolean isVideoEnabled) {
|
||||
audioMimeSpinner.setEnabled(isAudioEnabled);
|
||||
videoMimeSpinner.setEnabled(isVideoEnabled);
|
||||
resolutionHeightSpinner.setEnabled(isVideoEnabled);
|
||||
translateSpinner.setEnabled(isVideoEnabled);
|
||||
scaleSpinner.setEnabled(isVideoEnabled);
|
||||
rotateSpinner.setEnabled(isVideoEnabled);
|
||||
enableHdrEditingCheckBox.setEnabled(isVideoEnabled);
|
||||
|
||||
findViewById(R.id.audio_mime_text_view).setEnabled(isAudioEnabled);
|
||||
findViewById(R.id.video_mime_text_view).setEnabled(isVideoEnabled);
|
||||
findViewById(R.id.resolution_height_text_view).setEnabled(isVideoEnabled);
|
||||
findViewById(R.id.translate).setEnabled(isVideoEnabled);
|
||||
findViewById(R.id.scale).setEnabled(isVideoEnabled);
|
||||
findViewById(R.id.rotate).setEnabled(isVideoEnabled);
|
||||
findViewById(R.id.hdr_editing).setEnabled(isVideoEnabled);
|
||||
}
|
||||
}
|
@ -0,0 +1,391 @@
|
||||
/*
|
||||
* Copyright 2021 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.demo.transformer;
|
||||
|
||||
import static android.Manifest.permission.READ_EXTERNAL_STORAGE;
|
||||
import static androidx.media3.common.util.Assertions.checkNotNull;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.graphics.Matrix;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.view.SurfaceHolder;
|
||||
import android.view.SurfaceView;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.media3.common.C;
|
||||
import androidx.media3.common.MediaItem;
|
||||
import androidx.media3.common.util.Log;
|
||||
import androidx.media3.common.util.Util;
|
||||
import androidx.media3.exoplayer.ExoPlayer;
|
||||
import androidx.media3.exoplayer.util.DebugTextViewHelper;
|
||||
import androidx.media3.transformer.DefaultEncoderFactory;
|
||||
import androidx.media3.transformer.EncoderSelector;
|
||||
import androidx.media3.transformer.ProgressHolder;
|
||||
import androidx.media3.transformer.TransformationException;
|
||||
import androidx.media3.transformer.TransformationRequest;
|
||||
import androidx.media3.transformer.TransformationResult;
|
||||
import androidx.media3.transformer.Transformer;
|
||||
import androidx.media3.ui.AspectRatioFrameLayout;
|
||||
import androidx.media3.ui.PlayerView;
|
||||
import com.google.android.material.progressindicator.LinearProgressIndicator;
|
||||
import com.google.common.base.Stopwatch;
|
||||
import com.google.common.base.Ticker;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
||||
|
||||
/** An {@link Activity} that transforms and plays media using {@link Transformer}. */
|
||||
public final class TransformerActivity extends AppCompatActivity {
|
||||
private static final String TAG = "TransformerActivity";
|
||||
|
||||
private @MonotonicNonNull PlayerView playerView;
|
||||
private @MonotonicNonNull TextView debugTextView;
|
||||
private @MonotonicNonNull TextView informationTextView;
|
||||
private @MonotonicNonNull ViewGroup progressViewGroup;
|
||||
private @MonotonicNonNull LinearProgressIndicator progressIndicator;
|
||||
private @MonotonicNonNull Stopwatch transformationStopwatch;
|
||||
private @MonotonicNonNull AspectRatioFrameLayout debugFrame;
|
||||
|
||||
@Nullable private DebugTextViewHelper debugTextViewHelper;
|
||||
@Nullable private ExoPlayer player;
|
||||
@Nullable private Transformer transformer;
|
||||
@Nullable private File externalCacheFile;
|
||||
|
||||
@Override
|
||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.transformer_activity);
|
||||
|
||||
playerView = findViewById(R.id.player_view);
|
||||
debugTextView = findViewById(R.id.debug_text_view);
|
||||
informationTextView = findViewById(R.id.information_text_view);
|
||||
progressViewGroup = findViewById(R.id.progress_view_group);
|
||||
progressIndicator = findViewById(R.id.progress_indicator);
|
||||
debugFrame = findViewById(R.id.debug_aspect_ratio_frame_layout);
|
||||
|
||||
transformationStopwatch =
|
||||
Stopwatch.createUnstarted(
|
||||
new Ticker() {
|
||||
public long read() {
|
||||
return android.os.SystemClock.elapsedRealtimeNanos();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStart() {
|
||||
super.onStart();
|
||||
|
||||
checkNotNull(progressIndicator);
|
||||
checkNotNull(informationTextView);
|
||||
checkNotNull(transformationStopwatch);
|
||||
checkNotNull(playerView);
|
||||
checkNotNull(debugTextView);
|
||||
checkNotNull(progressViewGroup);
|
||||
checkNotNull(debugFrame);
|
||||
startTransformation();
|
||||
|
||||
playerView.onResume();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStop() {
|
||||
super.onStop();
|
||||
|
||||
checkNotNull(transformer).cancel();
|
||||
transformer = null;
|
||||
|
||||
// The stop watch is reset after cancelling the transformation, in case cancelling causes the
|
||||
// stop watch to be stopped in a transformer callback.
|
||||
checkNotNull(transformationStopwatch).reset();
|
||||
|
||||
checkNotNull(playerView).onPause();
|
||||
releasePlayer();
|
||||
|
||||
checkNotNull(externalCacheFile).delete();
|
||||
externalCacheFile = null;
|
||||
}
|
||||
|
||||
@RequiresNonNull({
|
||||
"playerView",
|
||||
"debugTextView",
|
||||
"informationTextView",
|
||||
"progressIndicator",
|
||||
"transformationStopwatch",
|
||||
"progressViewGroup",
|
||||
"debugFrame",
|
||||
})
|
||||
private void startTransformation() {
|
||||
requestTransformerPermission();
|
||||
|
||||
Intent intent = getIntent();
|
||||
Uri uri = checkNotNull(intent.getData());
|
||||
try {
|
||||
externalCacheFile = createExternalCacheFile("transformer-output.mp4");
|
||||
String filePath = externalCacheFile.getAbsolutePath();
|
||||
@Nullable Bundle bundle = intent.getExtras();
|
||||
Transformer transformer = createTransformer(bundle, filePath);
|
||||
transformationStopwatch.start();
|
||||
transformer.startTransformation(MediaItem.fromUri(uri), filePath);
|
||||
this.transformer = transformer;
|
||||
} catch (IOException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
informationTextView.setText(R.string.transformation_started);
|
||||
playerView.setVisibility(View.GONE);
|
||||
Handler mainHandler = new Handler(getMainLooper());
|
||||
ProgressHolder progressHolder = new ProgressHolder();
|
||||
mainHandler.post(
|
||||
new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (transformer != null
|
||||
&& transformer.getProgress(progressHolder)
|
||||
!= Transformer.PROGRESS_STATE_NO_TRANSFORMATION) {
|
||||
progressIndicator.setProgress(progressHolder.progress);
|
||||
informationTextView.setText(
|
||||
getString(
|
||||
R.string.transformation_timer,
|
||||
transformationStopwatch.elapsed(TimeUnit.SECONDS)));
|
||||
mainHandler.postDelayed(/* r= */ this, /* delayMillis= */ 500);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Create a cache file, resetting it if it already exists.
|
||||
private File createExternalCacheFile(String fileName) throws IOException {
|
||||
File file = new File(getExternalCacheDir(), fileName);
|
||||
if (file.exists() && !file.delete()) {
|
||||
throw new IllegalStateException("Could not delete the previous transformer output file");
|
||||
}
|
||||
if (!file.createNewFile()) {
|
||||
throw new IllegalStateException("Could not create the transformer output file");
|
||||
}
|
||||
return file;
|
||||
}
|
||||
|
||||
@RequiresNonNull({
|
||||
"playerView",
|
||||
"debugTextView",
|
||||
"informationTextView",
|
||||
"transformationStopwatch",
|
||||
"progressViewGroup",
|
||||
"debugFrame",
|
||||
})
|
||||
private Transformer createTransformer(@Nullable Bundle bundle, String filePath) {
|
||||
Transformer.Builder transformerBuilder = new Transformer.Builder(/* context= */ this);
|
||||
if (bundle != null) {
|
||||
TransformationRequest.Builder requestBuilder = new TransformationRequest.Builder();
|
||||
requestBuilder.setFlattenForSlowMotion(
|
||||
bundle.getBoolean(ConfigurationActivity.SHOULD_FLATTEN_FOR_SLOW_MOTION));
|
||||
@Nullable String audioMimeType = bundle.getString(ConfigurationActivity.AUDIO_MIME_TYPE);
|
||||
if (audioMimeType != null) {
|
||||
requestBuilder.setAudioMimeType(audioMimeType);
|
||||
}
|
||||
@Nullable String videoMimeType = bundle.getString(ConfigurationActivity.VIDEO_MIME_TYPE);
|
||||
if (videoMimeType != null) {
|
||||
requestBuilder.setVideoMimeType(videoMimeType);
|
||||
}
|
||||
int resolutionHeight =
|
||||
bundle.getInt(
|
||||
ConfigurationActivity.RESOLUTION_HEIGHT, /* defaultValue= */ C.LENGTH_UNSET);
|
||||
if (resolutionHeight != C.LENGTH_UNSET) {
|
||||
requestBuilder.setResolution(resolutionHeight);
|
||||
}
|
||||
Matrix transformationMatrix = getTransformationMatrix(bundle);
|
||||
if (!transformationMatrix.isIdentity()) {
|
||||
requestBuilder.setTransformationMatrix(transformationMatrix);
|
||||
}
|
||||
requestBuilder.experimental_setEnableHdrEditing(
|
||||
bundle.getBoolean(ConfigurationActivity.ENABLE_HDR_EDITING));
|
||||
transformerBuilder
|
||||
.setTransformationRequest(requestBuilder.build())
|
||||
.setRemoveAudio(bundle.getBoolean(ConfigurationActivity.SHOULD_REMOVE_AUDIO))
|
||||
.setRemoveVideo(bundle.getBoolean(ConfigurationActivity.SHOULD_REMOVE_VIDEO))
|
||||
.setEncoderFactory(
|
||||
new DefaultEncoderFactory(
|
||||
EncoderSelector.DEFAULT,
|
||||
/* enableFallback= */ bundle.getBoolean(ConfigurationActivity.ENABLE_FALLBACK)));
|
||||
}
|
||||
return transformerBuilder
|
||||
.addListener(
|
||||
new Transformer.Listener() {
|
||||
@Override
|
||||
public void onTransformationCompleted(
|
||||
MediaItem mediaItem, TransformationResult transformationResult) {
|
||||
TransformerActivity.this.onTransformationCompleted(filePath);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTransformationError(
|
||||
MediaItem mediaItem, TransformationException exception) {
|
||||
TransformerActivity.this.onTransformationError(exception);
|
||||
}
|
||||
})
|
||||
.setDebugViewProvider(new DemoDebugViewProvider())
|
||||
.build();
|
||||
}
|
||||
|
||||
private static Matrix getTransformationMatrix(Bundle bundle) {
|
||||
Matrix transformationMatrix = new Matrix();
|
||||
|
||||
float translateX = bundle.getFloat(ConfigurationActivity.TRANSLATE_X, /* defaultValue= */ 0);
|
||||
float translateY = bundle.getFloat(ConfigurationActivity.TRANSLATE_Y, /* defaultValue= */ 0);
|
||||
// TODO(b/201293185): Implement an AdvancedFrameEditor to handle translation, as the current
|
||||
// transformationMatrix is automatically adjusted to focus on the original pixels and
|
||||
// effectively undo translations.
|
||||
transformationMatrix.postTranslate(translateX, translateY);
|
||||
|
||||
float scaleX = bundle.getFloat(ConfigurationActivity.SCALE_X, /* defaultValue= */ 1);
|
||||
float scaleY = bundle.getFloat(ConfigurationActivity.SCALE_Y, /* defaultValue= */ 1);
|
||||
transformationMatrix.postScale(scaleX, scaleY);
|
||||
|
||||
float rotateDegrees =
|
||||
bundle.getFloat(ConfigurationActivity.ROTATE_DEGREES, /* defaultValue= */ 0);
|
||||
transformationMatrix.postRotate(rotateDegrees);
|
||||
|
||||
return transformationMatrix;
|
||||
}
|
||||
|
||||
@RequiresNonNull({
|
||||
"informationTextView",
|
||||
"progressViewGroup",
|
||||
"debugFrame",
|
||||
"transformationStopwatch",
|
||||
})
|
||||
private void onTransformationError(TransformationException exception) {
|
||||
transformationStopwatch.stop();
|
||||
informationTextView.setText(R.string.transformation_error);
|
||||
progressViewGroup.setVisibility(View.GONE);
|
||||
debugFrame.removeAllViews();
|
||||
Toast.makeText(
|
||||
TransformerActivity.this, "Transformation error: " + exception, Toast.LENGTH_LONG)
|
||||
.show();
|
||||
Log.e(TAG, "Transformation error", exception);
|
||||
}
|
||||
|
||||
@RequiresNonNull({
|
||||
"playerView",
|
||||
"debugTextView",
|
||||
"informationTextView",
|
||||
"progressViewGroup",
|
||||
"debugFrame",
|
||||
"transformationStopwatch",
|
||||
})
|
||||
private void onTransformationCompleted(String filePath) {
|
||||
transformationStopwatch.stop();
|
||||
informationTextView.setText(
|
||||
getString(
|
||||
R.string.transformation_completed, transformationStopwatch.elapsed(TimeUnit.SECONDS)));
|
||||
progressViewGroup.setVisibility(View.GONE);
|
||||
debugFrame.removeAllViews();
|
||||
playerView.setVisibility(View.VISIBLE);
|
||||
playMediaItem(MediaItem.fromUri("file://" + filePath));
|
||||
Log.d(TAG, "Output file path: file://" + filePath);
|
||||
}
|
||||
|
||||
@RequiresNonNull({"playerView", "debugTextView"})
|
||||
private void playMediaItem(MediaItem mediaItem) {
|
||||
playerView.setPlayer(null);
|
||||
releasePlayer();
|
||||
|
||||
ExoPlayer player = new ExoPlayer.Builder(/* context= */ this).build();
|
||||
playerView.setPlayer(player);
|
||||
player.setMediaItem(mediaItem);
|
||||
player.play();
|
||||
player.prepare();
|
||||
this.player = player;
|
||||
debugTextViewHelper = new DebugTextViewHelper(player, debugTextView);
|
||||
debugTextViewHelper.start();
|
||||
}
|
||||
|
||||
private void releasePlayer() {
|
||||
if (debugTextViewHelper != null) {
|
||||
debugTextViewHelper.stop();
|
||||
debugTextViewHelper = null;
|
||||
}
|
||||
if (player != null) {
|
||||
player.release();
|
||||
player = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void requestTransformerPermission() {
|
||||
if (Util.SDK_INT < 23) {
|
||||
return;
|
||||
}
|
||||
if (checkSelfPermission(READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
|
||||
requestPermissions(new String[] {READ_EXTERNAL_STORAGE}, /* requestCode= */ 0);
|
||||
}
|
||||
}
|
||||
|
||||
private final class DemoDebugViewProvider implements Transformer.DebugViewProvider {
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public SurfaceView getDebugPreviewSurfaceView(int width, int height) {
|
||||
// Update the UI on the main thread and wait for the output surface to be available.
|
||||
CountDownLatch surfaceCreatedCountDownLatch = new CountDownLatch(1);
|
||||
SurfaceView surfaceView = new SurfaceView(/* context= */ TransformerActivity.this);
|
||||
runOnUiThread(
|
||||
() -> {
|
||||
AspectRatioFrameLayout debugFrame = checkNotNull(TransformerActivity.this.debugFrame);
|
||||
debugFrame.addView(surfaceView);
|
||||
debugFrame.setAspectRatio((float) width / height);
|
||||
surfaceView
|
||||
.getHolder()
|
||||
.addCallback(
|
||||
new SurfaceHolder.Callback() {
|
||||
@Override
|
||||
public void surfaceCreated(SurfaceHolder surfaceHolder) {
|
||||
surfaceCreatedCountDownLatch.countDown();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void surfaceChanged(
|
||||
SurfaceHolder surfaceHolder, int format, int width, int height) {
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void surfaceDestroyed(SurfaceHolder surfaceHolder) {
|
||||
// Do nothing.
|
||||
}
|
||||
});
|
||||
});
|
||||
try {
|
||||
surfaceCreatedCountDownLatch.await();
|
||||
} catch (InterruptedException e) {
|
||||
Log.w(TAG, "Interrupted waiting for debug surface.");
|
||||
Thread.currentThread().interrupt();
|
||||
return null;
|
||||
}
|
||||
return surfaceView;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
/*
|
||||
* Copyright 2021 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.demo.transformer;
|
||||
|
||||
import androidx.media3.common.util.NonNullApi;
|
206
demos/transformer/src/main/res/layout/configuration_activity.xml
Normal file
@ -0,0 +1,206 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Copyright 2021 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="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context=".ConfigurationActivity">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/configuration_text_view"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="24dp"
|
||||
android:layout_marginStart="32dp"
|
||||
android:layout_marginEnd="32dp"
|
||||
android:text="@string/configuration"
|
||||
android:textSize="24sp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
<Button
|
||||
android:id="@+id/choose_file_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="32dp"
|
||||
android:layout_marginStart="32dp"
|
||||
android:layout_marginEnd="32dp"
|
||||
android:text="@string/choose_file_title"
|
||||
app:layout_constraintTop_toBottomOf="@+id/configuration_text_view"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent" />
|
||||
<TextView
|
||||
android:id="@+id/chosen_file_text_view"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:layout_marginStart="32dp"
|
||||
android:layout_marginEnd="32dp"
|
||||
android:paddingLeft="24dp"
|
||||
android:paddingRight="24dp"
|
||||
android:textSize="12sp"
|
||||
android:gravity="center"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/choose_file_button" />
|
||||
<androidx.core.widget.NestedScrollView
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="0dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/chosen_file_text_view"
|
||||
app:layout_constraintBottom_toTopOf="@+id/transform_button">
|
||||
<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/remove_audio" />
|
||||
<CheckBox
|
||||
android:id="@+id/remove_audio_checkbox"
|
||||
android:layout_gravity="right"/>
|
||||
</TableRow>
|
||||
<TableRow
|
||||
android:layout_weight="1"
|
||||
android:gravity="center_vertical" >
|
||||
<TextView
|
||||
android:text="@string/remove_video"/>
|
||||
<CheckBox
|
||||
android:id="@+id/remove_video_checkbox"
|
||||
android:layout_gravity="right" />
|
||||
</TableRow>
|
||||
<TableRow
|
||||
android:layout_weight="1"
|
||||
android:gravity="center_vertical" >
|
||||
<TextView
|
||||
android:text="@string/flatten_for_slow_motion"/>
|
||||
<CheckBox
|
||||
android:id="@+id/flatten_for_slow_motion_checkbox"
|
||||
android:layout_gravity="right" />
|
||||
</TableRow>
|
||||
<TableRow
|
||||
android:layout_weight="1"
|
||||
android:gravity="center_vertical" >
|
||||
<TextView
|
||||
android:id="@+id/audio_mime_text_view"
|
||||
android:text="@string/audio_mime"/>
|
||||
<Spinner
|
||||
android:id="@+id/audio_mime_spinner"
|
||||
android:layout_gravity="right|center_vertical"
|
||||
android:gravity="right" />
|
||||
</TableRow>
|
||||
<TableRow
|
||||
android:layout_weight="1"
|
||||
android:gravity="center_vertical" >
|
||||
<TextView
|
||||
android:id="@+id/video_mime_text_view"
|
||||
android:text="@string/video_mime"/>
|
||||
<Spinner
|
||||
android:id="@+id/video_mime_spinner"
|
||||
android:layout_gravity="right|center_vertical"
|
||||
android:gravity="right" />
|
||||
</TableRow>
|
||||
<TableRow
|
||||
android:layout_weight="1"
|
||||
android:gravity="center_vertical" >
|
||||
<TextView
|
||||
android:id="@+id/resolution_height_text_view"
|
||||
android:text="@string/resolution_height"/>
|
||||
<Spinner
|
||||
android:id="@+id/resolution_height_spinner"
|
||||
android:layout_gravity="right|center_vertical"
|
||||
android:gravity="right" />
|
||||
</TableRow>
|
||||
<TableRow
|
||||
android:layout_weight="1"
|
||||
android:gravity="center_vertical" >
|
||||
<TextView
|
||||
android:id="@+id/translate"
|
||||
android:text="@string/translate"/>
|
||||
<Spinner
|
||||
android:id="@+id/translate_spinner"
|
||||
android:layout_gravity="right|center_vertical"
|
||||
android:gravity="right" />
|
||||
</TableRow>
|
||||
<TableRow
|
||||
android:layout_weight="1"
|
||||
android:gravity="center_vertical" >
|
||||
<TextView
|
||||
android:id="@+id/scale"
|
||||
android:text="@string/scale"/>
|
||||
<Spinner
|
||||
android:id="@+id/scale_spinner"
|
||||
android:layout_gravity="right|center_vertical"
|
||||
android:gravity="right" />
|
||||
</TableRow>
|
||||
<TableRow
|
||||
android:layout_weight="1"
|
||||
android:gravity="center_vertical" >
|
||||
<TextView
|
||||
android:id="@+id/rotate"
|
||||
android:text="@string/rotate"/>
|
||||
<Spinner
|
||||
android:id="@+id/rotate_spinner"
|
||||
android:layout_gravity="right|center_vertical"
|
||||
android:gravity="right" />
|
||||
</TableRow>
|
||||
<TableRow
|
||||
android:layout_weight="1"
|
||||
android:gravity="center_vertical" >
|
||||
<TextView
|
||||
android:text="@string/enable_fallback" />
|
||||
<CheckBox
|
||||
android:id="@+id/enable_fallback_checkbox"
|
||||
android:layout_gravity="right"
|
||||
android:checked="true"/>
|
||||
</TableRow>
|
||||
<TableRow
|
||||
android:layout_weight="1"
|
||||
android:gravity="center_vertical" >
|
||||
<TextView
|
||||
android:id="@+id/hdr_editing"
|
||||
android:text="@string/hdr_editing" />
|
||||
<CheckBox
|
||||
android:id="@+id/hdr_editing_checkbox"
|
||||
android:layout_gravity="right" />
|
||||
</TableRow>
|
||||
</TableLayout>
|
||||
</androidx.core.widget.NestedScrollView>
|
||||
<Button
|
||||
android:id="@+id/transform_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="28dp"
|
||||
android:layout_marginStart="32dp"
|
||||
android:layout_marginEnd="32dp"
|
||||
android:text="@string/transform"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Copyright 2018 The Android Open Source Project
|
||||
~ Copyright 2021 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.
|
||||
@ -13,11 +13,14 @@
|
||||
~ 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.
|
||||
-->
|
||||
|
||||
<resources xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="default_notification_channel_name" msgid="7213672915724563695">"Now playing"</string>
|
||||
<string name="play_button_content_description" msgid="963503759453979404">"Play"</string>
|
||||
<string name="media3_controls_pause_description" msgid="3510124037191104584">"Pause"</string>
|
||||
</resources>
|
||||
-->
|
||||
<TextView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="32dp"
|
||||
android:gravity="left|center_vertical"
|
||||
android:paddingLeft="4dp"
|
||||
android:paddingRight="4dp"
|
||||
android:layout_marginLeft="4dp"
|
||||
android:layout_marginRight="4dp"
|
||||
android:textIsSelectable="false" />
|
100
demos/transformer/src/main/res/layout/transformer_activity.xml
Normal file
@ -0,0 +1,100 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
~ Copyright 2021 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.
|
||||
-->
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:keepScreenOn="true"
|
||||
android:orientation="vertical">
|
||||
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
android:layout_margin="8dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="match_parent"
|
||||
app:cardCornerRadius="4dp"
|
||||
app:cardElevation="2dp"
|
||||
android:gravity="center_vertical" >
|
||||
|
||||
<TextView
|
||||
android:id="@+id/information_text_view"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:padding="8dp" />
|
||||
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1"
|
||||
android:layout_margin="16dp"
|
||||
app:cardCornerRadius="4dp"
|
||||
app:cardElevation="2dp">
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<androidx.media3.ui.PlayerView
|
||||
android:id="@+id/player_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/debug_text_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="10sp"
|
||||
tools:ignore="SmallSp"/>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/progress_view_group"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_width="match_parent"
|
||||
android:padding="8dp"
|
||||
android:orientation="vertical">
|
||||
|
||||
<com.google.android.material.progressindicator.LinearProgressIndicator
|
||||
android:id="@+id/progress_indicator"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:layout_gravity="center" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:text="@string/debug_preview" />
|
||||
|
||||
<androidx.media3.ui.AspectRatioFrameLayout
|
||||
android:id="@+id/debug_aspect_ratio_frame_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/debug_preview_not_available" />
|
||||
|
||||
</androidx.media3.ui.AspectRatioFrameLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</FrameLayout>
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
</LinearLayout>
|
BIN
demos/transformer/src/main/res/mipmap-hdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 3.3 KiB |
BIN
demos/transformer/src/main/res/mipmap-mdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 2.1 KiB |
BIN
demos/transformer/src/main/res/mipmap-xhdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 4.8 KiB |
BIN
demos/transformer/src/main/res/mipmap-xxhdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 7.3 KiB |
BIN
demos/transformer/src/main/res/mipmap-xxxhdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 10 KiB |
39
demos/transformer/src/main/res/values/strings.xml
Normal file
@ -0,0 +1,39 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Copyright 2021 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.
|
||||
-->
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="app_name" translatable="false">Transformer Demo</string>
|
||||
<string name="configuration" translatable="false">Configuration</string>
|
||||
<string name="choose_file_title" translatable="false">Choose file</string>
|
||||
<string name="remove_audio" translatable="false">Remove audio</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="audio_mime" translatable="false">Output audio MIME type</string>
|
||||
<string name="video_mime" translatable="false">Output video MIME type</string>
|
||||
<string name="resolution_height" translatable="false">Output video resolution</string>
|
||||
<string name="translate" translatable="false">Translate video</string>
|
||||
<string name="scale" translatable="false">Scale video</string>
|
||||
<string name="rotate" translatable="false">Rotate video (degrees)</string>
|
||||
<string name="enable_fallback" translatable="false">Enable fallback</string>
|
||||
<string name="transform" translatable="false">Transform</string>
|
||||
<string name="hdr_editing" translatable="false">[Experimental] HDR editing</string>
|
||||
<string name="debug_preview" translatable="false">Debug preview:</string>
|
||||
<string name="debug_preview_not_available" translatable="false">No debug preview available.</string>
|
||||
<string name="transformation_started" translatable="false">Transformation started</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_error" translatable="false">Transformation error</string>
|
||||
</resources>
|
2
gradle/wrapper/gradle-wrapper.properties
vendored
@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https://services.gradle.org/distributions/gradle-7.0.2-all.zip
|
||||
distributionUrl=https://services.gradle.org/distributions/gradle-7.3.3-all.zip
|
||||
|
@ -14,7 +14,7 @@
|
||||
apply from: "$gradle.ext.androidxMediaSettingsDir/common_library_config.gradle"
|
||||
|
||||
dependencies {
|
||||
api 'com.google.android.gms:play-services-cast-framework:20.1.0'
|
||||
api 'com.google.android.gms:play-services-cast-framework:21.0.1'
|
||||
implementation 'androidx.annotation:annotation:' + androidxAnnotationVersion
|
||||
implementation project(modulePrefix + 'lib-common')
|
||||
compileOnly 'org.checkerframework:checker-qual:' + checkerframeworkVersion
|
||||
|
@ -137,7 +137,7 @@ public final class CastPlayer extends BasePlayer {
|
||||
private final SeekResultCallback seekResultCallback;
|
||||
|
||||
// Listeners and notification.
|
||||
private final ListenerSet<Player.EventListener> listeners;
|
||||
private final ListenerSet<Listener> listeners;
|
||||
@Nullable private SessionAvailabilityListener sessionAvailabilityListener;
|
||||
|
||||
// Internal state.
|
||||
@ -150,7 +150,7 @@ public final class CastPlayer extends BasePlayer {
|
||||
private TrackSelectionArray currentTrackSelection;
|
||||
private TracksInfo currentTracksInfo;
|
||||
private Commands availableCommands;
|
||||
@Player.State private int playbackState;
|
||||
private @Player.State int playbackState;
|
||||
private int currentWindowIndex;
|
||||
private long lastReportedPositionMs;
|
||||
private int pendingSeekCount;
|
||||
@ -280,41 +280,11 @@ public final class CastPlayer extends BasePlayer {
|
||||
|
||||
@Override
|
||||
public void addListener(Listener listener) {
|
||||
EventListener eventListener = listener;
|
||||
addListener(eventListener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a listener to receive events from the player.
|
||||
*
|
||||
* <p>The listener's methods will be called on the thread associated with {@link
|
||||
* #getApplicationLooper()}.
|
||||
*
|
||||
* @param listener The listener to register.
|
||||
* @deprecated Use {@link #addListener(Listener)} and {@link #removeListener(Listener)} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
@SuppressWarnings("deprecation")
|
||||
public void addListener(EventListener listener) {
|
||||
listeners.add(listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeListener(Listener listener) {
|
||||
EventListener eventListener = listener;
|
||||
removeListener(eventListener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregister a listener registered through {@link #addListener(EventListener)}. The listener will
|
||||
* no longer receive events from the player.
|
||||
*
|
||||
* @param listener The listener to unregister.
|
||||
* @deprecated Use {@link #addListener(Listener)} and {@link #removeListener(Listener)} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
@SuppressWarnings("deprecation")
|
||||
public void removeListener(EventListener listener) {
|
||||
listeners.remove(listener);
|
||||
}
|
||||
|
||||
@ -387,14 +357,12 @@ public final class CastPlayer extends BasePlayer {
|
||||
}
|
||||
|
||||
@Override
|
||||
@Player.State
|
||||
public int getPlaybackState() {
|
||||
public @Player.State int getPlaybackState() {
|
||||
return playbackState;
|
||||
}
|
||||
|
||||
@Override
|
||||
@PlaybackSuppressionReason
|
||||
public int getPlaybackSuppressionReason() {
|
||||
public @PlaybackSuppressionReason int getPlaybackSuppressionReason() {
|
||||
return Player.PLAYBACK_SUPPRESSION_REASON_NONE;
|
||||
}
|
||||
|
||||
@ -475,7 +443,7 @@ public final class CastPlayer extends BasePlayer {
|
||||
}
|
||||
updateAvailableCommandsAndNotifyIfChanged();
|
||||
} else if (pendingSeekCount == 0) {
|
||||
listeners.queueEvent(/* eventFlag= */ C.INDEX_UNSET, EventListener::onSeekProcessed);
|
||||
listeners.queueEvent(/* eventFlag= */ C.INDEX_UNSET, Listener::onSeekProcessed);
|
||||
}
|
||||
listeners.flushEvents();
|
||||
}
|
||||
@ -559,7 +527,7 @@ public final class CastPlayer extends BasePlayer {
|
||||
setRepeatModeAndNotifyIfChanged(repeatMode);
|
||||
listeners.flushEvents();
|
||||
PendingResult<MediaChannelResult> pendingResult =
|
||||
remoteMediaClient.queueSetRepeatMode(getCastRepeatMode(repeatMode), /* jsonObject= */ null);
|
||||
remoteMediaClient.queueSetRepeatMode(getCastRepeatMode(repeatMode), /* customData= */ null);
|
||||
this.repeatMode.pendingResultCallback =
|
||||
new ResultCallback<MediaChannelResult>() {
|
||||
@Override
|
||||
@ -574,8 +542,7 @@ public final class CastPlayer extends BasePlayer {
|
||||
}
|
||||
|
||||
@Override
|
||||
@RepeatMode
|
||||
public int getRepeatMode() {
|
||||
public @RepeatMode int getRepeatMode() {
|
||||
return repeatMode.value;
|
||||
}
|
||||
|
||||
@ -1070,7 +1037,8 @@ public final class CastPlayer extends BasePlayer {
|
||||
int[] trackSupport = new int[] {supported ? C.FORMAT_HANDLED : C.FORMAT_UNSUPPORTED_TYPE};
|
||||
final boolean[] trackSelected = new boolean[] {selected};
|
||||
trackGroupInfos[i] =
|
||||
new TracksInfo.TrackGroupInfo(trackGroups[i], trackSupport, trackType, trackSelected);
|
||||
new TracksInfo.TrackGroupInfo(
|
||||
trackGroups[i], /* adaptiveSupported= */ false, trackSupport, trackSelected);
|
||||
}
|
||||
TrackGroupArray newTrackGroups = new TrackGroupArray(trackGroups);
|
||||
TrackSelectionArray newTrackSelections = new TrackSelectionArray(trackSelections);
|
||||
@ -1292,8 +1260,7 @@ public final class CastPlayer extends BasePlayer {
|
||||
* Retrieves the repeat mode from {@code remoteMediaClient} and maps it into a {@link
|
||||
* Player.RepeatMode}.
|
||||
*/
|
||||
@RepeatMode
|
||||
private static int fetchRepeatMode(RemoteMediaClient remoteMediaClient) {
|
||||
private static @RepeatMode int fetchRepeatMode(RemoteMediaClient remoteMediaClient) {
|
||||
MediaStatus mediaStatus = remoteMediaClient.getMediaStatus();
|
||||
if (mediaStatus == null) {
|
||||
// No media session active, yet.
|
||||
@ -1481,7 +1448,7 @@ public final class CastPlayer extends BasePlayer {
|
||||
currentWindowIndex = pendingSeekWindowIndex;
|
||||
pendingSeekWindowIndex = C.INDEX_UNSET;
|
||||
pendingSeekPositionMs = C.TIME_UNSET;
|
||||
listeners.sendEvent(/* eventFlag= */ C.INDEX_UNSET, EventListener::onSeekProcessed);
|
||||
listeners.sendEvent(/* eventFlag= */ C.INDEX_UNSET, Listener::onSeekProcessed);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -31,7 +31,9 @@ import androidx.media3.common.util.Assertions;
|
||||
|
||||
private final TrackGroup trackGroup;
|
||||
|
||||
/** @param trackGroup The {@link TrackGroup} from which the first track will only be selected. */
|
||||
/**
|
||||
* @param trackGroup The {@link TrackGroup} from which the first track will only be selected.
|
||||
*/
|
||||
public CastTrackSelection(TrackGroup trackGroup) {
|
||||
this.trackGroup = trackGroup;
|
||||
}
|
||||
|
@ -19,11 +19,13 @@ import android.net.Uri;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.media3.common.C;
|
||||
import androidx.media3.common.MediaItem;
|
||||
import androidx.media3.common.MimeTypes;
|
||||
import androidx.media3.common.util.Assertions;
|
||||
import androidx.media3.common.util.UnstableApi;
|
||||
import com.google.android.gms.cast.MediaInfo;
|
||||
import com.google.android.gms.cast.MediaMetadata;
|
||||
import com.google.android.gms.cast.MediaQueueItem;
|
||||
import com.google.android.gms.common.images.WebImage;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.UUID;
|
||||
@ -47,10 +49,43 @@ public final class DefaultMediaItemConverter implements MediaItemConverter {
|
||||
|
||||
@Override
|
||||
public MediaItem toMediaItem(MediaQueueItem mediaQueueItem) {
|
||||
// `item` came from `toMediaQueueItem()` so the custom JSON data must be set.
|
||||
MediaInfo mediaInfo = mediaQueueItem.getMedia();
|
||||
@Nullable MediaInfo mediaInfo = mediaQueueItem.getMedia();
|
||||
Assertions.checkNotNull(mediaInfo);
|
||||
return getMediaItem(Assertions.checkNotNull(mediaInfo.getCustomData()));
|
||||
androidx.media3.common.MediaMetadata.Builder metadataBuilder =
|
||||
new androidx.media3.common.MediaMetadata.Builder();
|
||||
@Nullable MediaMetadata metadata = mediaInfo.getMetadata();
|
||||
if (metadata != null) {
|
||||
if (metadata.containsKey(MediaMetadata.KEY_TITLE)) {
|
||||
metadataBuilder.setTitle(metadata.getString(MediaMetadata.KEY_TITLE));
|
||||
}
|
||||
if (metadata.containsKey(MediaMetadata.KEY_SUBTITLE)) {
|
||||
metadataBuilder.setSubtitle(metadata.getString(MediaMetadata.KEY_SUBTITLE));
|
||||
}
|
||||
if (metadata.containsKey(MediaMetadata.KEY_ARTIST)) {
|
||||
metadataBuilder.setArtist(metadata.getString(MediaMetadata.KEY_ARTIST));
|
||||
}
|
||||
if (metadata.containsKey(MediaMetadata.KEY_ALBUM_ARTIST)) {
|
||||
metadataBuilder.setAlbumArtist(metadata.getString(MediaMetadata.KEY_ALBUM_ARTIST));
|
||||
}
|
||||
if (metadata.containsKey(MediaMetadata.KEY_ALBUM_TITLE)) {
|
||||
metadataBuilder.setArtist(metadata.getString(MediaMetadata.KEY_ALBUM_TITLE));
|
||||
}
|
||||
if (!metadata.getImages().isEmpty()) {
|
||||
metadataBuilder.setArtworkUri(metadata.getImages().get(0).getUrl());
|
||||
}
|
||||
if (metadata.containsKey(MediaMetadata.KEY_COMPOSER)) {
|
||||
metadataBuilder.setComposer(metadata.getString(MediaMetadata.KEY_COMPOSER));
|
||||
}
|
||||
if (metadata.containsKey(MediaMetadata.KEY_DISC_NUMBER)) {
|
||||
metadataBuilder.setDiscNumber(metadata.getInt(MediaMetadata.KEY_DISC_NUMBER));
|
||||
}
|
||||
if (metadata.containsKey(MediaMetadata.KEY_TRACK_NUMBER)) {
|
||||
metadataBuilder.setTrackNumber(metadata.getInt(MediaMetadata.KEY_TRACK_NUMBER));
|
||||
}
|
||||
}
|
||||
// `mediaQueueItem` came from `toMediaQueueItem()` so the custom JSON data must be set.
|
||||
return getMediaItem(
|
||||
Assertions.checkNotNull(mediaInfo.getCustomData()), metadataBuilder.build());
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -59,10 +94,41 @@ public final class DefaultMediaItemConverter implements MediaItemConverter {
|
||||
if (mediaItem.localConfiguration.mimeType == null) {
|
||||
throw new IllegalArgumentException("The item must specify its mimeType");
|
||||
}
|
||||
MediaMetadata metadata = new MediaMetadata(MediaMetadata.MEDIA_TYPE_MOVIE);
|
||||
MediaMetadata metadata =
|
||||
new MediaMetadata(
|
||||
MimeTypes.isAudio(mediaItem.localConfiguration.mimeType)
|
||||
? MediaMetadata.MEDIA_TYPE_MUSIC_TRACK
|
||||
: MediaMetadata.MEDIA_TYPE_MOVIE);
|
||||
if (mediaItem.mediaMetadata.title != null) {
|
||||
metadata.putString(MediaMetadata.KEY_TITLE, mediaItem.mediaMetadata.title.toString());
|
||||
}
|
||||
if (mediaItem.mediaMetadata.subtitle != null) {
|
||||
metadata.putString(MediaMetadata.KEY_SUBTITLE, mediaItem.mediaMetadata.subtitle.toString());
|
||||
}
|
||||
if (mediaItem.mediaMetadata.artist != null) {
|
||||
metadata.putString(MediaMetadata.KEY_ARTIST, mediaItem.mediaMetadata.artist.toString());
|
||||
}
|
||||
if (mediaItem.mediaMetadata.albumArtist != null) {
|
||||
metadata.putString(
|
||||
MediaMetadata.KEY_ALBUM_ARTIST, mediaItem.mediaMetadata.albumArtist.toString());
|
||||
}
|
||||
if (mediaItem.mediaMetadata.albumTitle != null) {
|
||||
metadata.putString(
|
||||
MediaMetadata.KEY_ALBUM_TITLE, mediaItem.mediaMetadata.albumTitle.toString());
|
||||
}
|
||||
if (mediaItem.mediaMetadata.artworkUri != null) {
|
||||
metadata.addImage(new WebImage(mediaItem.mediaMetadata.artworkUri));
|
||||
}
|
||||
if (mediaItem.mediaMetadata.composer != null) {
|
||||
metadata.putString(MediaMetadata.KEY_COMPOSER, mediaItem.mediaMetadata.composer.toString());
|
||||
}
|
||||
if (mediaItem.mediaMetadata.discNumber != null) {
|
||||
metadata.putInt(MediaMetadata.KEY_DISC_NUMBER, mediaItem.mediaMetadata.discNumber);
|
||||
}
|
||||
if (mediaItem.mediaMetadata.trackNumber != null) {
|
||||
metadata.putInt(MediaMetadata.KEY_TRACK_NUMBER, mediaItem.mediaMetadata.trackNumber);
|
||||
}
|
||||
|
||||
MediaInfo mediaInfo =
|
||||
new MediaInfo.Builder(mediaItem.localConfiguration.uri.toString())
|
||||
.setStreamType(MediaInfo.STREAM_TYPE_BUFFERED)
|
||||
@ -75,19 +141,15 @@ public final class DefaultMediaItemConverter implements MediaItemConverter {
|
||||
|
||||
// Deserialization.
|
||||
|
||||
private static MediaItem getMediaItem(JSONObject customData) {
|
||||
private static MediaItem getMediaItem(
|
||||
JSONObject customData, androidx.media3.common.MediaMetadata mediaMetadata) {
|
||||
try {
|
||||
JSONObject mediaItemJson = customData.getJSONObject(KEY_MEDIA_ITEM);
|
||||
MediaItem.Builder builder = new MediaItem.Builder();
|
||||
builder.setUri(Uri.parse(mediaItemJson.getString(KEY_URI)));
|
||||
builder.setMediaId(mediaItemJson.getString(KEY_MEDIA_ID));
|
||||
if (mediaItemJson.has(KEY_TITLE)) {
|
||||
androidx.media3.common.MediaMetadata mediaMetadata =
|
||||
new androidx.media3.common.MediaMetadata.Builder()
|
||||
.setTitle(mediaItemJson.getString(KEY_TITLE))
|
||||
.build();
|
||||
builder.setMediaMetadata(mediaMetadata);
|
||||
}
|
||||
MediaItem.Builder builder =
|
||||
new MediaItem.Builder()
|
||||
.setUri(Uri.parse(mediaItemJson.getString(KEY_URI)))
|
||||
.setMediaId(mediaItemJson.getString(KEY_MEDIA_ID))
|
||||
.setMediaMetadata(mediaMetadata);
|
||||
if (mediaItemJson.has(KEY_MIME_TYPE)) {
|
||||
builder.setMimeType(mediaItemJson.getString(KEY_MIME_TYPE));
|
||||
}
|
||||
|
@ -17,9 +17,10 @@ apply from: "$gradle.ext.androidxMediaSettingsDir/common_library_config.gradle"
|
||||
// the Gradle properties of each library are populated and we can automatically
|
||||
// check if a 'releaseArtifactId' exists.
|
||||
rootProject.allprojects.forEach {
|
||||
if ((it.name.contains('lib-') || it.name.contains('test-'))
|
||||
if ((it.name.startsWith(modulePrefix.replace(':', '') + 'lib-')
|
||||
|| it.name.startsWith(modulePrefix.replace(':', '') + 'test-'))
|
||||
&& !it.name.endsWith('-common')) {
|
||||
evaluationDependsOn(modulePrefix + it.name)
|
||||
evaluationDependsOn(':' + it.name)
|
||||
}
|
||||
}
|
||||
// copybara:media3-only
|
||||
@ -36,8 +37,9 @@ dependencies {
|
||||
// List all released targets as constraints. This ensures they are all
|
||||
// resolved to the same version.
|
||||
rootProject.allprojects.forEach {
|
||||
if (it.hasProperty('releaseArtifactId')) {
|
||||
implementation project(modulePrefix + it.name)
|
||||
if (it.hasProperty('releaseArtifactId')
|
||||
&& it.releaseArtifactId.startsWith('media3-')) {
|
||||
implementation project(':' + it.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -37,6 +37,8 @@ public final class AdOverlayInfo {
|
||||
* The purpose of the overlay. One of {@link #PURPOSE_CONTROLS}, {@link #PURPOSE_CLOSE_AD}, {@link
|
||||
* #PURPOSE_OTHER} or {@link #PURPOSE_NOT_VISIBLE}.
|
||||
*/
|
||||
// @Target list includes both 'default' targets and TYPE_USE, to ensure backwards compatibility
|
||||
// with Kotlin usages from before TYPE_USE was added.
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@Target({FIELD, METHOD, PARAMETER, LOCAL_VARIABLE, TYPE_USE})
|
||||
@ -95,14 +97,18 @@ public final class AdOverlayInfo {
|
||||
/** An optional, detailed reason that the overlay view is needed. */
|
||||
@Nullable public final String reasonDetail;
|
||||
|
||||
/** @deprecated Use {@link Builder} instead. */
|
||||
/**
|
||||
* @deprecated Use {@link Builder} instead.
|
||||
*/
|
||||
@UnstableApi
|
||||
@Deprecated
|
||||
public AdOverlayInfo(View view, @Purpose int purpose) {
|
||||
this(view, purpose, /* detailedReason= */ null);
|
||||
}
|
||||
|
||||
/** @deprecated Use {@link Builder} instead. */
|
||||
/**
|
||||
* @deprecated Use {@link Builder} instead.
|
||||
*/
|
||||
@UnstableApi
|
||||
@Deprecated
|
||||
public AdOverlayInfo(View view, @Purpose int purpose, @Nullable String detailedReason) {
|
||||
|
@ -18,6 +18,11 @@ package androidx.media3.common;
|
||||
import static androidx.media3.common.util.Assertions.checkArgument;
|
||||
import static androidx.media3.common.util.Assertions.checkState;
|
||||
import static java.lang.Math.max;
|
||||
import static java.lang.annotation.ElementType.FIELD;
|
||||
import static java.lang.annotation.ElementType.LOCAL_VARIABLE;
|
||||
import static java.lang.annotation.ElementType.METHOD;
|
||||
import static java.lang.annotation.ElementType.PARAMETER;
|
||||
import static java.lang.annotation.ElementType.TYPE_USE;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
@ -30,6 +35,7 @@ import androidx.media3.common.util.Util;
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import org.checkerframework.checker.nullness.compatqual.NullableType;
|
||||
@ -61,7 +67,7 @@ public final class AdPlaybackState implements Bundleable {
|
||||
/** The URI of each ad in the ad group. */
|
||||
public final @NullableType Uri[] uris;
|
||||
/** The state of each ad in the ad group. */
|
||||
@AdState public final int[] states;
|
||||
public final @AdState int[] states;
|
||||
/** The durations of each ad in the ad group, in microseconds. */
|
||||
public final long[] durationsUs;
|
||||
/**
|
||||
@ -343,6 +349,7 @@ public final class AdPlaybackState implements Bundleable {
|
||||
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@Target(TYPE_USE)
|
||||
@IntDef({
|
||||
FIELD_TIME_US,
|
||||
FIELD_COUNT,
|
||||
@ -414,8 +421,11 @@ public final class AdPlaybackState implements Bundleable {
|
||||
* #AD_STATE_AVAILABLE}, {@link #AD_STATE_SKIPPED}, {@link #AD_STATE_PLAYED} or {@link
|
||||
* #AD_STATE_ERROR}.
|
||||
*/
|
||||
// @Target list includes both 'default' targets and TYPE_USE, to ensure backwards compatibility
|
||||
// with Kotlin usages from before TYPE_USE was added.
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@Target({FIELD, METHOD, PARAMETER, LOCAL_VARIABLE, TYPE_USE})
|
||||
@IntDef({
|
||||
AD_STATE_UNAVAILABLE,
|
||||
AD_STATE_AVAILABLE,
|
||||
@ -913,6 +923,7 @@ public final class AdPlaybackState implements Bundleable {
|
||||
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@Target(TYPE_USE)
|
||||
@IntDef({
|
||||
FIELD_AD_GROUPS,
|
||||
FIELD_AD_RESUME_POSITION_US,
|
||||
|
@ -15,6 +15,8 @@
|
||||
*/
|
||||
package androidx.media3.common;
|
||||
|
||||
import static java.lang.annotation.ElementType.TYPE_USE;
|
||||
|
||||
import android.os.Bundle;
|
||||
import androidx.annotation.DoNotInline;
|
||||
import androidx.annotation.IntDef;
|
||||
@ -25,6 +27,7 @@ import androidx.media3.common.util.Util;
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
/**
|
||||
@ -40,6 +43,11 @@ import java.lang.reflect.Method;
|
||||
*/
|
||||
public final class AudioAttributes implements Bundleable {
|
||||
|
||||
/**
|
||||
* The default audio attributes, where the content type is {@link C#CONTENT_TYPE_UNKNOWN}, usage
|
||||
* is {@link C#USAGE_MEDIA}, capture policy is {@link C#ALLOW_CAPTURE_BY_ALL} and no flags are
|
||||
* set.
|
||||
*/
|
||||
public static final AudioAttributes DEFAULT = new Builder().build();
|
||||
|
||||
/** Builder for {@link AudioAttributes}. */
|
||||
@ -65,19 +73,19 @@ public final class AudioAttributes implements Bundleable {
|
||||
spatializationBehavior = C.SPATIALIZATION_BEHAVIOR_AUTO;
|
||||
}
|
||||
|
||||
/** @see android.media.AudioAttributes.Builder#setContentType(int) */
|
||||
/** See {@link android.media.AudioAttributes.Builder#setContentType(int)} */
|
||||
public Builder setContentType(@C.AudioContentType int contentType) {
|
||||
this.contentType = contentType;
|
||||
return this;
|
||||
}
|
||||
|
||||
/** @see android.media.AudioAttributes.Builder#setFlags(int) */
|
||||
/** See {@link android.media.AudioAttributes.Builder#setFlags(int)} */
|
||||
public Builder setFlags(@C.AudioFlags int flags) {
|
||||
this.flags = flags;
|
||||
return this;
|
||||
}
|
||||
|
||||
/** @see android.media.AudioAttributes.Builder#setUsage(int) */
|
||||
/** See {@link android.media.AudioAttributes.Builder#setUsage(int)} */
|
||||
public Builder setUsage(@C.AudioUsage int usage) {
|
||||
this.usage = usage;
|
||||
return this;
|
||||
@ -91,7 +99,7 @@ public final class AudioAttributes implements Bundleable {
|
||||
|
||||
// TODO[b/190759307] Update javadoc to link to AudioAttributes.Builder#setSpatializationBehavior
|
||||
// once compile SDK target is set to 32.
|
||||
/** See AudioAttributes.Builder#setSpatializationBehavior(int). */
|
||||
/** See {@code android.media.AudioAttributes.Builder.setSpatializationBehavior(int)}. */
|
||||
public Builder setSpatializationBehavior(@C.SpatializationBehavior int spatializationBehavior) {
|
||||
this.spatializationBehavior = spatializationBehavior;
|
||||
return this;
|
||||
@ -104,10 +112,15 @@ public final class AudioAttributes implements Bundleable {
|
||||
}
|
||||
}
|
||||
|
||||
/** The {@link C.AudioContentType}. */
|
||||
public final @C.AudioContentType int contentType;
|
||||
/** The {@link C.AudioFlags}. */
|
||||
public final @C.AudioFlags int flags;
|
||||
/** The {@link C.AudioUsage}. */
|
||||
public final @C.AudioUsage int usage;
|
||||
/** The {@link C.AudioAllowedCapturePolicy}. */
|
||||
public final @C.AudioAllowedCapturePolicy int allowedCapturePolicy;
|
||||
/** The {@link C.SpatializationBehavior}. */
|
||||
public final @C.SpatializationBehavior int spatializationBehavior;
|
||||
|
||||
@Nullable private android.media.AudioAttributes audioAttributesV21;
|
||||
@ -180,6 +193,7 @@ public final class AudioAttributes implements Bundleable {
|
||||
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@Target(TYPE_USE)
|
||||
@IntDef({
|
||||
FIELD_CONTENT_TYPE,
|
||||
FIELD_FLAGS,
|
||||
|
@ -384,8 +384,7 @@ public abstract class BasePlayer implements Player {
|
||||
: timeline.getWindow(getCurrentMediaItemIndex(), window).getDurationMs();
|
||||
}
|
||||
|
||||
@RepeatMode
|
||||
private int getRepeatModeForNavigation() {
|
||||
private @RepeatMode int getRepeatModeForNavigation() {
|
||||
@RepeatMode int repeatMode = getRepeatMode();
|
||||
return repeatMode == REPEAT_MODE_ONE ? REPEAT_MODE_OFF : repeatMode;
|
||||
}
|
||||
|
@ -29,6 +29,7 @@ import android.media.AudioManager;
|
||||
import android.media.MediaCodec;
|
||||
import android.media.MediaCrypto;
|
||||
import android.media.MediaFormat;
|
||||
import android.net.Uri;
|
||||
import android.view.Surface;
|
||||
import androidx.annotation.IntDef;
|
||||
import androidx.annotation.RequiresApi;
|
||||
@ -68,6 +69,9 @@ public final class C {
|
||||
/** Represents an unset or unknown rate. */
|
||||
public static final float RATE_UNSET = -Float.MAX_VALUE;
|
||||
|
||||
/** Represents an unset or unknown integer rate. */
|
||||
@UnstableApi public static final int RATE_UNSET_INT = Integer.MIN_VALUE + 1;
|
||||
|
||||
/** Represents an unset or unknown length. */
|
||||
public static final int LENGTH_UNSET = -1;
|
||||
|
||||
@ -125,6 +129,9 @@ public final class C {
|
||||
/** The name of the sans-serif font family. */
|
||||
@UnstableApi public static final String SANS_SERIF_NAME = "sans-serif";
|
||||
|
||||
/** The {@link Uri#getScheme() URI scheme} used for content with server side ad insertion. */
|
||||
@UnstableApi public static final String SSAI_SCHEME = "ssai";
|
||||
|
||||
/**
|
||||
* Types of crypto implementation. May be one of {@link #CRYPTO_TYPE_NONE}, {@link
|
||||
* #CRYPTO_TYPE_UNSUPPORTED} or {@link #CRYPTO_TYPE_FRAMEWORK}. May also be an app-defined value
|
||||
@ -159,14 +166,21 @@ public final class C {
|
||||
*/
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@Target(TYPE_USE)
|
||||
@IntDef({CRYPTO_MODE_UNENCRYPTED, CRYPTO_MODE_AES_CTR, CRYPTO_MODE_AES_CBC})
|
||||
@UnstableApi
|
||||
public @interface CryptoMode {}
|
||||
/** @see MediaCodec#CRYPTO_MODE_UNENCRYPTED */
|
||||
/**
|
||||
* @see MediaCodec#CRYPTO_MODE_UNENCRYPTED
|
||||
*/
|
||||
@UnstableApi public static final int CRYPTO_MODE_UNENCRYPTED = MediaCodec.CRYPTO_MODE_UNENCRYPTED;
|
||||
/** @see MediaCodec#CRYPTO_MODE_AES_CTR */
|
||||
/**
|
||||
* @see MediaCodec#CRYPTO_MODE_AES_CTR
|
||||
*/
|
||||
@UnstableApi public static final int CRYPTO_MODE_AES_CTR = MediaCodec.CRYPTO_MODE_AES_CTR;
|
||||
/** @see MediaCodec#CRYPTO_MODE_AES_CBC */
|
||||
/**
|
||||
* @see MediaCodec#CRYPTO_MODE_AES_CBC
|
||||
*/
|
||||
@UnstableApi public static final int CRYPTO_MODE_AES_CBC = MediaCodec.CRYPTO_MODE_AES_CBC;
|
||||
|
||||
/**
|
||||
@ -187,6 +201,7 @@ public final class C {
|
||||
@UnstableApi
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@Target(TYPE_USE)
|
||||
@IntDef({
|
||||
Format.NO_VALUE,
|
||||
ENCODING_INVALID,
|
||||
@ -222,6 +237,7 @@ public final class C {
|
||||
@UnstableApi
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@Target(TYPE_USE)
|
||||
@IntDef({
|
||||
Format.NO_VALUE,
|
||||
ENCODING_INVALID,
|
||||
@ -233,11 +249,17 @@ public final class C {
|
||||
ENCODING_PCM_FLOAT
|
||||
})
|
||||
public @interface PcmEncoding {}
|
||||
/** @see AudioFormat#ENCODING_INVALID */
|
||||
/**
|
||||
* @see AudioFormat#ENCODING_INVALID
|
||||
*/
|
||||
@UnstableApi public static final int ENCODING_INVALID = AudioFormat.ENCODING_INVALID;
|
||||
/** @see AudioFormat#ENCODING_PCM_8BIT */
|
||||
/**
|
||||
* @see AudioFormat#ENCODING_PCM_8BIT
|
||||
*/
|
||||
@UnstableApi public static final int ENCODING_PCM_8BIT = AudioFormat.ENCODING_PCM_8BIT;
|
||||
/** @see AudioFormat#ENCODING_PCM_16BIT */
|
||||
/**
|
||||
* @see AudioFormat#ENCODING_PCM_16BIT
|
||||
*/
|
||||
@UnstableApi public static final int ENCODING_PCM_16BIT = AudioFormat.ENCODING_PCM_16BIT;
|
||||
/** Like {@link #ENCODING_PCM_16BIT}, but with the bytes in big endian order. */
|
||||
@UnstableApi public static final int ENCODING_PCM_16BIT_BIG_ENDIAN = 0x10000000;
|
||||
@ -245,41 +267,69 @@ public final class C {
|
||||
@UnstableApi public static final int ENCODING_PCM_24BIT = 0x20000000;
|
||||
/** PCM encoding with 32 bits per sample. */
|
||||
@UnstableApi public static final int ENCODING_PCM_32BIT = 0x30000000;
|
||||
/** @see AudioFormat#ENCODING_PCM_FLOAT */
|
||||
/**
|
||||
* @see AudioFormat#ENCODING_PCM_FLOAT
|
||||
*/
|
||||
@UnstableApi public static final int ENCODING_PCM_FLOAT = AudioFormat.ENCODING_PCM_FLOAT;
|
||||
/** @see AudioFormat#ENCODING_MP3 */
|
||||
/**
|
||||
* @see AudioFormat#ENCODING_MP3
|
||||
*/
|
||||
@UnstableApi public static final int ENCODING_MP3 = AudioFormat.ENCODING_MP3;
|
||||
/** @see AudioFormat#ENCODING_AAC_LC */
|
||||
/**
|
||||
* @see AudioFormat#ENCODING_AAC_LC
|
||||
*/
|
||||
@UnstableApi public static final int ENCODING_AAC_LC = AudioFormat.ENCODING_AAC_LC;
|
||||
/** @see AudioFormat#ENCODING_AAC_HE_V1 */
|
||||
/**
|
||||
* @see AudioFormat#ENCODING_AAC_HE_V1
|
||||
*/
|
||||
@UnstableApi public static final int ENCODING_AAC_HE_V1 = AudioFormat.ENCODING_AAC_HE_V1;
|
||||
/** @see AudioFormat#ENCODING_AAC_HE_V2 */
|
||||
/**
|
||||
* @see AudioFormat#ENCODING_AAC_HE_V2
|
||||
*/
|
||||
@UnstableApi public static final int ENCODING_AAC_HE_V2 = AudioFormat.ENCODING_AAC_HE_V2;
|
||||
/** @see AudioFormat#ENCODING_AAC_XHE */
|
||||
/**
|
||||
* @see AudioFormat#ENCODING_AAC_XHE
|
||||
*/
|
||||
@UnstableApi public static final int ENCODING_AAC_XHE = AudioFormat.ENCODING_AAC_XHE;
|
||||
/** @see AudioFormat#ENCODING_AAC_ELD */
|
||||
/**
|
||||
* @see AudioFormat#ENCODING_AAC_ELD
|
||||
*/
|
||||
@UnstableApi public static final int ENCODING_AAC_ELD = AudioFormat.ENCODING_AAC_ELD;
|
||||
/** AAC Error Resilient Bit-Sliced Arithmetic Coding. */
|
||||
@UnstableApi public static final int ENCODING_AAC_ER_BSAC = 0x40000000;
|
||||
/** @see AudioFormat#ENCODING_AC3 */
|
||||
/**
|
||||
* @see AudioFormat#ENCODING_AC3
|
||||
*/
|
||||
@UnstableApi public static final int ENCODING_AC3 = AudioFormat.ENCODING_AC3;
|
||||
/** @see AudioFormat#ENCODING_E_AC3 */
|
||||
/**
|
||||
* @see AudioFormat#ENCODING_E_AC3
|
||||
*/
|
||||
@UnstableApi public static final int ENCODING_E_AC3 = AudioFormat.ENCODING_E_AC3;
|
||||
/** @see AudioFormat#ENCODING_E_AC3_JOC */
|
||||
/**
|
||||
* @see AudioFormat#ENCODING_E_AC3_JOC
|
||||
*/
|
||||
@UnstableApi public static final int ENCODING_E_AC3_JOC = AudioFormat.ENCODING_E_AC3_JOC;
|
||||
/** @see AudioFormat#ENCODING_AC4 */
|
||||
/**
|
||||
* @see AudioFormat#ENCODING_AC4
|
||||
*/
|
||||
@UnstableApi public static final int ENCODING_AC4 = AudioFormat.ENCODING_AC4;
|
||||
/** @see AudioFormat#ENCODING_DTS */
|
||||
/**
|
||||
* @see AudioFormat#ENCODING_DTS
|
||||
*/
|
||||
@UnstableApi public static final int ENCODING_DTS = AudioFormat.ENCODING_DTS;
|
||||
/** @see AudioFormat#ENCODING_DTS_HD */
|
||||
/**
|
||||
* @see AudioFormat#ENCODING_DTS_HD
|
||||
*/
|
||||
@UnstableApi public static final int ENCODING_DTS_HD = AudioFormat.ENCODING_DTS_HD;
|
||||
/** @see AudioFormat#ENCODING_DOLBY_TRUEHD */
|
||||
/**
|
||||
* @see AudioFormat#ENCODING_DOLBY_TRUEHD
|
||||
*/
|
||||
@UnstableApi public static final int ENCODING_DOLBY_TRUEHD = AudioFormat.ENCODING_DOLBY_TRUEHD;
|
||||
|
||||
/** Represents the behavior affecting whether spatialization will be used. */
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@Target({FIELD, METHOD, PARAMETER, LOCAL_VARIABLE, TYPE_USE})
|
||||
@Target(TYPE_USE)
|
||||
@IntDef({SPATIALIZATION_BEHAVIOR_AUTO, SPATIALIZATION_BEHAVIOR_NEVER})
|
||||
public @interface SpatializationBehavior {}
|
||||
|
||||
@ -296,10 +346,13 @@ public final class C {
|
||||
* #STREAM_TYPE_RING}, {@link #STREAM_TYPE_SYSTEM}, {@link #STREAM_TYPE_VOICE_CALL} or {@link
|
||||
* #STREAM_TYPE_DEFAULT}.
|
||||
*/
|
||||
// @Target list includes both 'default' targets and TYPE_USE, to ensure backwards compatibility
|
||||
// with Kotlin usages from before TYPE_USE was added.
|
||||
@SuppressLint("UniqueConstants") // Intentional duplication to set STREAM_TYPE_DEFAULT.
|
||||
@UnstableApi
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@Target({FIELD, METHOD, PARAMETER, LOCAL_VARIABLE, TYPE_USE})
|
||||
@IntDef({
|
||||
STREAM_TYPE_ALARM,
|
||||
STREAM_TYPE_DTMF,
|
||||
@ -311,19 +364,33 @@ public final class C {
|
||||
STREAM_TYPE_DEFAULT
|
||||
})
|
||||
public @interface StreamType {}
|
||||
/** @see AudioManager#STREAM_ALARM */
|
||||
/**
|
||||
* @see AudioManager#STREAM_ALARM
|
||||
*/
|
||||
@UnstableApi public static final int STREAM_TYPE_ALARM = AudioManager.STREAM_ALARM;
|
||||
/** @see AudioManager#STREAM_DTMF */
|
||||
/**
|
||||
* @see AudioManager#STREAM_DTMF
|
||||
*/
|
||||
@UnstableApi public static final int STREAM_TYPE_DTMF = AudioManager.STREAM_DTMF;
|
||||
/** @see AudioManager#STREAM_MUSIC */
|
||||
/**
|
||||
* @see AudioManager#STREAM_MUSIC
|
||||
*/
|
||||
@UnstableApi public static final int STREAM_TYPE_MUSIC = AudioManager.STREAM_MUSIC;
|
||||
/** @see AudioManager#STREAM_NOTIFICATION */
|
||||
/**
|
||||
* @see AudioManager#STREAM_NOTIFICATION
|
||||
*/
|
||||
@UnstableApi public static final int STREAM_TYPE_NOTIFICATION = AudioManager.STREAM_NOTIFICATION;
|
||||
/** @see AudioManager#STREAM_RING */
|
||||
/**
|
||||
* @see AudioManager#STREAM_RING
|
||||
*/
|
||||
@UnstableApi public static final int STREAM_TYPE_RING = AudioManager.STREAM_RING;
|
||||
/** @see AudioManager#STREAM_SYSTEM */
|
||||
/**
|
||||
* @see AudioManager#STREAM_SYSTEM
|
||||
*/
|
||||
@UnstableApi public static final int STREAM_TYPE_SYSTEM = AudioManager.STREAM_SYSTEM;
|
||||
/** @see AudioManager#STREAM_VOICE_CALL */
|
||||
/**
|
||||
* @see AudioManager#STREAM_VOICE_CALL
|
||||
*/
|
||||
@UnstableApi public static final int STREAM_TYPE_VOICE_CALL = AudioManager.STREAM_VOICE_CALL;
|
||||
/** The default stream type used by audio renderers. Equal to {@link #STREAM_TYPE_MUSIC}. */
|
||||
@UnstableApi public static final int STREAM_TYPE_DEFAULT = STREAM_TYPE_MUSIC;
|
||||
@ -333,6 +400,8 @@ public final class C {
|
||||
* #CONTENT_TYPE_MUSIC}, {@link #CONTENT_TYPE_SONIFICATION}, {@link #CONTENT_TYPE_SPEECH} or
|
||||
* {@link #CONTENT_TYPE_UNKNOWN}.
|
||||
*/
|
||||
// @Target list includes both 'default' targets and TYPE_USE, to ensure backwards compatibility
|
||||
// with Kotlin usages from before TYPE_USE was added.
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@Target({FIELD, METHOD, PARAMETER, LOCAL_VARIABLE, TYPE_USE})
|
||||
@ -344,16 +413,26 @@ public final class C {
|
||||
CONTENT_TYPE_UNKNOWN
|
||||
})
|
||||
public @interface AudioContentType {}
|
||||
/** @see android.media.AudioAttributes#CONTENT_TYPE_MOVIE */
|
||||
/**
|
||||
* @see android.media.AudioAttributes#CONTENT_TYPE_MOVIE
|
||||
*/
|
||||
public static final int CONTENT_TYPE_MOVIE = android.media.AudioAttributes.CONTENT_TYPE_MOVIE;
|
||||
/** @see android.media.AudioAttributes#CONTENT_TYPE_MUSIC */
|
||||
/**
|
||||
* @see android.media.AudioAttributes#CONTENT_TYPE_MUSIC
|
||||
*/
|
||||
public static final int CONTENT_TYPE_MUSIC = android.media.AudioAttributes.CONTENT_TYPE_MUSIC;
|
||||
/** @see android.media.AudioAttributes#CONTENT_TYPE_SONIFICATION */
|
||||
/**
|
||||
* @see android.media.AudioAttributes#CONTENT_TYPE_SONIFICATION
|
||||
*/
|
||||
public static final int CONTENT_TYPE_SONIFICATION =
|
||||
android.media.AudioAttributes.CONTENT_TYPE_SONIFICATION;
|
||||
/** @see android.media.AudioAttributes#CONTENT_TYPE_SPEECH */
|
||||
/**
|
||||
* @see android.media.AudioAttributes#CONTENT_TYPE_SPEECH
|
||||
*/
|
||||
public static final int CONTENT_TYPE_SPEECH = android.media.AudioAttributes.CONTENT_TYPE_SPEECH;
|
||||
/** @see android.media.AudioAttributes#CONTENT_TYPE_UNKNOWN */
|
||||
/**
|
||||
* @see android.media.AudioAttributes#CONTENT_TYPE_UNKNOWN
|
||||
*/
|
||||
public static final int CONTENT_TYPE_UNKNOWN = android.media.AudioAttributes.CONTENT_TYPE_UNKNOWN;
|
||||
|
||||
/**
|
||||
@ -362,6 +441,8 @@ public final class C {
|
||||
* <p>Note that {@code FLAG_HW_AV_SYNC} is not available because the player takes care of setting
|
||||
* the flag when tunneling is enabled via a track selector.
|
||||
*/
|
||||
// @Target list includes both 'default' targets and TYPE_USE, to ensure backwards compatibility
|
||||
// with Kotlin usages from before TYPE_USE was added.
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@Target({FIELD, METHOD, PARAMETER, LOCAL_VARIABLE, TYPE_USE})
|
||||
@ -369,7 +450,9 @@ public final class C {
|
||||
flag = true,
|
||||
value = {FLAG_AUDIBILITY_ENFORCED})
|
||||
public @interface AudioFlags {}
|
||||
/** @see android.media.AudioAttributes#FLAG_AUDIBILITY_ENFORCED */
|
||||
/**
|
||||
* @see android.media.AudioAttributes#FLAG_AUDIBILITY_ENFORCED
|
||||
*/
|
||||
public static final int FLAG_AUDIBILITY_ENFORCED =
|
||||
android.media.AudioAttributes.FLAG_AUDIBILITY_ENFORCED;
|
||||
|
||||
@ -383,6 +466,8 @@ public final class C {
|
||||
* #USAGE_NOTIFICATION_RINGTONE}, {@link #USAGE_UNKNOWN}, {@link #USAGE_VOICE_COMMUNICATION} or
|
||||
* {@link #USAGE_VOICE_COMMUNICATION_SIGNALLING}.
|
||||
*/
|
||||
// @Target list includes both 'default' targets and TYPE_USE, to ensure backwards compatibility
|
||||
// with Kotlin usages from before TYPE_USE was added.
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@Target({FIELD, METHOD, PARAMETER, LOCAL_VARIABLE, TYPE_USE})
|
||||
@ -405,46 +490,78 @@ public final class C {
|
||||
USAGE_VOICE_COMMUNICATION_SIGNALLING
|
||||
})
|
||||
public @interface AudioUsage {}
|
||||
/** @see android.media.AudioAttributes#USAGE_ALARM */
|
||||
/**
|
||||
* @see android.media.AudioAttributes#USAGE_ALARM
|
||||
*/
|
||||
public static final int USAGE_ALARM = android.media.AudioAttributes.USAGE_ALARM;
|
||||
/** @see android.media.AudioAttributes#USAGE_ASSISTANCE_ACCESSIBILITY */
|
||||
/**
|
||||
* @see android.media.AudioAttributes#USAGE_ASSISTANCE_ACCESSIBILITY
|
||||
*/
|
||||
public static final int USAGE_ASSISTANCE_ACCESSIBILITY =
|
||||
android.media.AudioAttributes.USAGE_ASSISTANCE_ACCESSIBILITY;
|
||||
/** @see android.media.AudioAttributes#USAGE_ASSISTANCE_NAVIGATION_GUIDANCE */
|
||||
/**
|
||||
* @see android.media.AudioAttributes#USAGE_ASSISTANCE_NAVIGATION_GUIDANCE
|
||||
*/
|
||||
public static final int USAGE_ASSISTANCE_NAVIGATION_GUIDANCE =
|
||||
android.media.AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE;
|
||||
/** @see android.media.AudioAttributes#USAGE_ASSISTANCE_SONIFICATION */
|
||||
/**
|
||||
* @see android.media.AudioAttributes#USAGE_ASSISTANCE_SONIFICATION
|
||||
*/
|
||||
public static final int USAGE_ASSISTANCE_SONIFICATION =
|
||||
android.media.AudioAttributes.USAGE_ASSISTANCE_SONIFICATION;
|
||||
/** @see android.media.AudioAttributes#USAGE_ASSISTANT */
|
||||
/**
|
||||
* @see android.media.AudioAttributes#USAGE_ASSISTANT
|
||||
*/
|
||||
public static final int USAGE_ASSISTANT = android.media.AudioAttributes.USAGE_ASSISTANT;
|
||||
/** @see android.media.AudioAttributes#USAGE_GAME */
|
||||
/**
|
||||
* @see android.media.AudioAttributes#USAGE_GAME
|
||||
*/
|
||||
public static final int USAGE_GAME = android.media.AudioAttributes.USAGE_GAME;
|
||||
/** @see android.media.AudioAttributes#USAGE_MEDIA */
|
||||
/**
|
||||
* @see android.media.AudioAttributes#USAGE_MEDIA
|
||||
*/
|
||||
public static final int USAGE_MEDIA = android.media.AudioAttributes.USAGE_MEDIA;
|
||||
/** @see android.media.AudioAttributes#USAGE_NOTIFICATION */
|
||||
/**
|
||||
* @see android.media.AudioAttributes#USAGE_NOTIFICATION
|
||||
*/
|
||||
public static final int USAGE_NOTIFICATION = android.media.AudioAttributes.USAGE_NOTIFICATION;
|
||||
/** @see android.media.AudioAttributes#USAGE_NOTIFICATION_COMMUNICATION_DELAYED */
|
||||
/**
|
||||
* @see android.media.AudioAttributes#USAGE_NOTIFICATION_COMMUNICATION_DELAYED
|
||||
*/
|
||||
public static final int USAGE_NOTIFICATION_COMMUNICATION_DELAYED =
|
||||
android.media.AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_DELAYED;
|
||||
/** @see android.media.AudioAttributes#USAGE_NOTIFICATION_COMMUNICATION_INSTANT */
|
||||
/**
|
||||
* @see android.media.AudioAttributes#USAGE_NOTIFICATION_COMMUNICATION_INSTANT
|
||||
*/
|
||||
public static final int USAGE_NOTIFICATION_COMMUNICATION_INSTANT =
|
||||
android.media.AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_INSTANT;
|
||||
/** @see android.media.AudioAttributes#USAGE_NOTIFICATION_COMMUNICATION_REQUEST */
|
||||
/**
|
||||
* @see android.media.AudioAttributes#USAGE_NOTIFICATION_COMMUNICATION_REQUEST
|
||||
*/
|
||||
public static final int USAGE_NOTIFICATION_COMMUNICATION_REQUEST =
|
||||
android.media.AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_REQUEST;
|
||||
/** @see android.media.AudioAttributes#USAGE_NOTIFICATION_EVENT */
|
||||
/**
|
||||
* @see android.media.AudioAttributes#USAGE_NOTIFICATION_EVENT
|
||||
*/
|
||||
public static final int USAGE_NOTIFICATION_EVENT =
|
||||
android.media.AudioAttributes.USAGE_NOTIFICATION_EVENT;
|
||||
/** @see android.media.AudioAttributes#USAGE_NOTIFICATION_RINGTONE */
|
||||
/**
|
||||
* @see android.media.AudioAttributes#USAGE_NOTIFICATION_RINGTONE
|
||||
*/
|
||||
public static final int USAGE_NOTIFICATION_RINGTONE =
|
||||
android.media.AudioAttributes.USAGE_NOTIFICATION_RINGTONE;
|
||||
/** @see android.media.AudioAttributes#USAGE_UNKNOWN */
|
||||
/**
|
||||
* @see android.media.AudioAttributes#USAGE_UNKNOWN
|
||||
*/
|
||||
public static final int USAGE_UNKNOWN = android.media.AudioAttributes.USAGE_UNKNOWN;
|
||||
/** @see android.media.AudioAttributes#USAGE_VOICE_COMMUNICATION */
|
||||
/**
|
||||
* @see android.media.AudioAttributes#USAGE_VOICE_COMMUNICATION
|
||||
*/
|
||||
public static final int USAGE_VOICE_COMMUNICATION =
|
||||
android.media.AudioAttributes.USAGE_VOICE_COMMUNICATION;
|
||||
/** @see android.media.AudioAttributes#USAGE_VOICE_COMMUNICATION_SIGNALLING */
|
||||
/**
|
||||
* @see android.media.AudioAttributes#USAGE_VOICE_COMMUNICATION_SIGNALLING
|
||||
*/
|
||||
public static final int USAGE_VOICE_COMMUNICATION_SIGNALLING =
|
||||
android.media.AudioAttributes.USAGE_VOICE_COMMUNICATION_SIGNALLING;
|
||||
|
||||
@ -452,6 +569,8 @@ public final class C {
|
||||
* Capture policies for audio attributes. One of {@link #ALLOW_CAPTURE_BY_ALL}, {@link
|
||||
* #ALLOW_CAPTURE_BY_NONE} or {@link #ALLOW_CAPTURE_BY_SYSTEM}.
|
||||
*/
|
||||
// @Target list includes both 'default' targets and TYPE_USE, to ensure backwards compatibility
|
||||
// with Kotlin usages from before TYPE_USE was added.
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@Target({FIELD, METHOD, PARAMETER, LOCAL_VARIABLE, TYPE_USE})
|
||||
@ -464,62 +583,6 @@ public final class C {
|
||||
/** See {@link android.media.AudioAttributes#ALLOW_CAPTURE_BY_SYSTEM}. */
|
||||
public static final int ALLOW_CAPTURE_BY_SYSTEM = AudioAttributes.ALLOW_CAPTURE_BY_SYSTEM;
|
||||
|
||||
/**
|
||||
* Audio focus types. One of {@link #AUDIOFOCUS_NONE}, {@link #AUDIOFOCUS_GAIN}, {@link
|
||||
* #AUDIOFOCUS_GAIN_TRANSIENT}, {@link #AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK} or {@link
|
||||
* #AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE}.
|
||||
*/
|
||||
@UnstableApi
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@IntDef({
|
||||
AUDIOFOCUS_NONE,
|
||||
AUDIOFOCUS_GAIN,
|
||||
AUDIOFOCUS_GAIN_TRANSIENT,
|
||||
AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK,
|
||||
AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE
|
||||
})
|
||||
public @interface AudioFocusGain {}
|
||||
/** @see AudioManager#AUDIOFOCUS_NONE */
|
||||
@UnstableApi public static final int AUDIOFOCUS_NONE = AudioManager.AUDIOFOCUS_NONE;
|
||||
/** @see AudioManager#AUDIOFOCUS_GAIN */
|
||||
@UnstableApi public static final int AUDIOFOCUS_GAIN = AudioManager.AUDIOFOCUS_GAIN;
|
||||
/** @see AudioManager#AUDIOFOCUS_GAIN_TRANSIENT */
|
||||
@UnstableApi
|
||||
public static final int AUDIOFOCUS_GAIN_TRANSIENT = AudioManager.AUDIOFOCUS_GAIN_TRANSIENT;
|
||||
/** @see AudioManager#AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK */
|
||||
@UnstableApi
|
||||
public static final int AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK =
|
||||
AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK;
|
||||
/** @see AudioManager#AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE */
|
||||
@UnstableApi
|
||||
public static final int AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE =
|
||||
AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE;
|
||||
|
||||
/**
|
||||
* Playback offload mode. One of {@link #PLAYBACK_OFFLOAD_NOT_SUPPORTED},{@link
|
||||
* #PLAYBACK_OFFLOAD_SUPPORTED} or {@link #PLAYBACK_OFFLOAD_GAPLESS_SUPPORTED}.
|
||||
*/
|
||||
@UnstableApi
|
||||
@IntDef({
|
||||
PLAYBACK_OFFLOAD_NOT_SUPPORTED,
|
||||
PLAYBACK_OFFLOAD_SUPPORTED,
|
||||
PLAYBACK_OFFLOAD_GAPLESS_SUPPORTED
|
||||
})
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
public @interface AudioManagerOffloadMode {}
|
||||
/** See AudioManager#PLAYBACK_OFFLOAD_NOT_SUPPORTED */
|
||||
@UnstableApi
|
||||
public static final int PLAYBACK_OFFLOAD_NOT_SUPPORTED =
|
||||
AudioManager.PLAYBACK_OFFLOAD_NOT_SUPPORTED;
|
||||
/** See AudioManager#PLAYBACK_OFFLOAD_SUPPORTED */
|
||||
@UnstableApi
|
||||
public static final int PLAYBACK_OFFLOAD_SUPPORTED = AudioManager.PLAYBACK_OFFLOAD_SUPPORTED;
|
||||
/** See AudioManager#PLAYBACK_OFFLOAD_GAPLESS_SUPPORTED */
|
||||
@UnstableApi
|
||||
public static final int PLAYBACK_OFFLOAD_GAPLESS_SUPPORTED =
|
||||
AudioManager.PLAYBACK_OFFLOAD_GAPLESS_SUPPORTED;
|
||||
|
||||
/**
|
||||
* Flags which can apply to a buffer containing a media sample. Possible flag values are {@link
|
||||
* #BUFFER_FLAG_KEY_FRAME}, {@link #BUFFER_FLAG_END_OF_STREAM}, {@link #BUFFER_FLAG_LAST_SAMPLE},
|
||||
@ -528,6 +591,7 @@ public final class C {
|
||||
@UnstableApi
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@Target(TYPE_USE)
|
||||
@IntDef(
|
||||
flag = true,
|
||||
value = {
|
||||
@ -560,6 +624,7 @@ public final class C {
|
||||
@UnstableApi
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@Target(TYPE_USE)
|
||||
@IntDef(value = {VIDEO_OUTPUT_MODE_NONE, VIDEO_OUTPUT_MODE_YUV, VIDEO_OUTPUT_MODE_SURFACE_YUV})
|
||||
public @interface VideoOutputMode {}
|
||||
/** Video decoder output mode is not set. */
|
||||
@ -574,10 +639,13 @@ public final class C {
|
||||
* #VIDEO_SCALING_MODE_SCALE_TO_FIT}, {@link #VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING} or
|
||||
* {@link #VIDEO_SCALING_MODE_DEFAULT}.
|
||||
*/
|
||||
// @Target list includes both 'default' targets and TYPE_USE, to ensure backwards compatibility
|
||||
// with Kotlin usages from before TYPE_USE was added.
|
||||
@SuppressLint("UniqueConstants") // Intentional duplication to set VIDEO_SCALING_MODE_DEFAULT.
|
||||
@UnstableApi
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@Target({FIELD, METHOD, PARAMETER, LOCAL_VARIABLE, TYPE_USE})
|
||||
@IntDef({
|
||||
VIDEO_SCALING_MODE_SCALE_TO_FIT,
|
||||
VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING,
|
||||
@ -596,9 +664,12 @@ public final class C {
|
||||
@UnstableApi public static final int VIDEO_SCALING_MODE_DEFAULT = VIDEO_SCALING_MODE_SCALE_TO_FIT;
|
||||
|
||||
/** Strategies for calling {@link Surface#setFrameRate}. */
|
||||
// @Target list includes both 'default' targets and TYPE_USE, to ensure backwards compatibility
|
||||
// with Kotlin usages from before TYPE_USE was added.
|
||||
@UnstableApi
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@Target({FIELD, METHOD, PARAMETER, LOCAL_VARIABLE, TYPE_USE})
|
||||
@IntDef({VIDEO_CHANGE_FRAME_RATE_STRATEGY_OFF, VIDEO_CHANGE_FRAME_RATE_STRATEGY_ONLY_IF_SEAMLESS})
|
||||
public @interface VideoChangeFrameRateStrategy {}
|
||||
/**
|
||||
@ -618,6 +689,8 @@ public final class C {
|
||||
* Track selection flags. Possible flag values are {@link #SELECTION_FLAG_DEFAULT}, {@link
|
||||
* #SELECTION_FLAG_FORCED} and {@link #SELECTION_FLAG_AUTOSELECT}.
|
||||
*/
|
||||
// @Target list includes both 'default' targets and TYPE_USE, to ensure backwards compatibility
|
||||
// with Kotlin usages from before TYPE_USE was added.
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@Target({FIELD, METHOD, PARAMETER, LOCAL_VARIABLE, TYPE_USE})
|
||||
@ -651,9 +724,12 @@ public final class C {
|
||||
* Represents a streaming or other media type. One of {@link #TYPE_DASH}, {@link #TYPE_SS}, {@link
|
||||
* #TYPE_HLS}, {@link #TYPE_RTSP} or {@link #TYPE_OTHER}.
|
||||
*/
|
||||
// @Target list includes both 'default' targets and TYPE_USE, to ensure backwards compatibility
|
||||
// with Kotlin usages from before TYPE_USE was added.
|
||||
@UnstableApi
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@Target({FIELD, METHOD, PARAMETER, LOCAL_VARIABLE, TYPE_USE})
|
||||
@IntDef({TYPE_DASH, TYPE_SS, TYPE_HLS, TYPE_RTSP, TYPE_OTHER})
|
||||
public @interface ContentType {}
|
||||
/** Value returned by {@link Util#inferContentType(String)} for DASH manifests. */
|
||||
@ -693,6 +769,7 @@ public final class C {
|
||||
@UnstableApi
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@Target(TYPE_USE)
|
||||
@IntDef(
|
||||
open = true,
|
||||
value = {
|
||||
@ -787,6 +864,7 @@ public final class C {
|
||||
@UnstableApi
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@Target(TYPE_USE)
|
||||
@IntDef(
|
||||
open = true,
|
||||
value = {
|
||||
@ -890,6 +968,7 @@ public final class C {
|
||||
@UnstableApi
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@Target(TYPE_USE)
|
||||
@IntDef({
|
||||
Format.NO_VALUE,
|
||||
STEREO_MODE_MONO,
|
||||
@ -917,13 +996,20 @@ public final class C {
|
||||
@UnstableApi
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@Target(TYPE_USE)
|
||||
@IntDef({Format.NO_VALUE, COLOR_SPACE_BT709, COLOR_SPACE_BT601, COLOR_SPACE_BT2020})
|
||||
public @interface ColorSpace {}
|
||||
/** @see MediaFormat#COLOR_STANDARD_BT709 */
|
||||
/**
|
||||
* @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;
|
||||
/** @see MediaFormat#COLOR_STANDARD_BT2020 */
|
||||
/**
|
||||
* @see MediaFormat#COLOR_STANDARD_BT2020
|
||||
*/
|
||||
@UnstableApi public static final int COLOR_SPACE_BT2020 = MediaFormat.COLOR_STANDARD_BT2020;
|
||||
|
||||
/**
|
||||
@ -933,13 +1019,20 @@ public final class C {
|
||||
@UnstableApi
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@Target(TYPE_USE)
|
||||
@IntDef({Format.NO_VALUE, COLOR_TRANSFER_SDR, COLOR_TRANSFER_ST2084, COLOR_TRANSFER_HLG})
|
||||
public @interface ColorTransfer {}
|
||||
/** @see MediaFormat#COLOR_TRANSFER_SDR_VIDEO */
|
||||
/**
|
||||
* @see MediaFormat#COLOR_TRANSFER_SDR_VIDEO
|
||||
*/
|
||||
@UnstableApi public static final int COLOR_TRANSFER_SDR = MediaFormat.COLOR_TRANSFER_SDR_VIDEO;
|
||||
/** @see MediaFormat#COLOR_TRANSFER_ST2084 */
|
||||
/**
|
||||
* @see MediaFormat#COLOR_TRANSFER_ST2084
|
||||
*/
|
||||
@UnstableApi public static final int COLOR_TRANSFER_ST2084 = MediaFormat.COLOR_TRANSFER_ST2084;
|
||||
/** @see MediaFormat#COLOR_TRANSFER_HLG */
|
||||
/**
|
||||
* @see MediaFormat#COLOR_TRANSFER_HLG
|
||||
*/
|
||||
@UnstableApi public static final int COLOR_TRANSFER_HLG = MediaFormat.COLOR_TRANSFER_HLG;
|
||||
|
||||
/**
|
||||
@ -949,17 +1042,23 @@ public final class C {
|
||||
@UnstableApi
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@Target(TYPE_USE)
|
||||
@IntDef({Format.NO_VALUE, COLOR_RANGE_LIMITED, COLOR_RANGE_FULL})
|
||||
public @interface ColorRange {}
|
||||
/** @see MediaFormat#COLOR_RANGE_LIMITED */
|
||||
/**
|
||||
* @see MediaFormat#COLOR_RANGE_LIMITED
|
||||
*/
|
||||
@UnstableApi public static final int COLOR_RANGE_LIMITED = MediaFormat.COLOR_RANGE_LIMITED;
|
||||
/** @see MediaFormat#COLOR_RANGE_FULL */
|
||||
/**
|
||||
* @see MediaFormat#COLOR_RANGE_FULL
|
||||
*/
|
||||
@UnstableApi public static final int COLOR_RANGE_FULL = MediaFormat.COLOR_RANGE_FULL;
|
||||
|
||||
/** Video projection types. */
|
||||
@UnstableApi
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@Target(TYPE_USE)
|
||||
@IntDef({
|
||||
Format.NO_VALUE,
|
||||
PROJECTION_RECTANGULAR,
|
||||
@ -997,9 +1096,12 @@ public final class C {
|
||||
* #NETWORK_TYPE_4G}, {@link #NETWORK_TYPE_5G_SA}, {@link #NETWORK_TYPE_5G_NSA}, {@link
|
||||
* #NETWORK_TYPE_CELLULAR_UNKNOWN}, {@link #NETWORK_TYPE_ETHERNET} or {@link #NETWORK_TYPE_OTHER}.
|
||||
*/
|
||||
// @Target list includes both 'default' targets and TYPE_USE, to ensure backwards compatibility
|
||||
// with Kotlin usages from before TYPE_USE was added.
|
||||
@UnstableApi
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@Target({FIELD, METHOD, PARAMETER, LOCAL_VARIABLE, TYPE_USE})
|
||||
@IntDef({
|
||||
NETWORK_TYPE_UNKNOWN,
|
||||
NETWORK_TYPE_OFFLINE,
|
||||
@ -1044,6 +1146,8 @@ public final class C {
|
||||
* Mode specifying whether the player should hold a WakeLock and a WifiLock. One of {@link
|
||||
* #WAKE_MODE_NONE}, {@link #WAKE_MODE_LOCAL} or {@link #WAKE_MODE_NETWORK}.
|
||||
*/
|
||||
// @Target list includes both 'default' targets and TYPE_USE, to ensure backwards compatibility
|
||||
// with Kotlin usages from before TYPE_USE was added.
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@Target({FIELD, METHOD, PARAMETER, LOCAL_VARIABLE, TYPE_USE})
|
||||
@ -1081,6 +1185,8 @@ public final class C {
|
||||
* {@link #ROLE_FLAG_TRANSCRIBES_DIALOG}, {@link #ROLE_FLAG_EASY_TO_READ} and {@link
|
||||
* #ROLE_FLAG_TRICK_PLAY}.
|
||||
*/
|
||||
// @Target list includes both 'default' targets and TYPE_USE, to ensure backwards compatibility
|
||||
// with Kotlin usages from before TYPE_USE was added.
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@Target({FIELD, METHOD, PARAMETER, LOCAL_VARIABLE, TYPE_USE})
|
||||
@ -1156,9 +1262,12 @@ public final class C {
|
||||
* #FORMAT_EXCEEDS_CAPABILITIES}, {@link #FORMAT_UNSUPPORTED_DRM}, {@link
|
||||
* #FORMAT_UNSUPPORTED_SUBTYPE} or {@link #FORMAT_UNSUPPORTED_TYPE}.
|
||||
*/
|
||||
// @Target list includes both 'default' targets and TYPE_USE, to ensure backwards compatibility
|
||||
// with Kotlin usages from before TYPE_USE was added.
|
||||
@UnstableApi
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@Target({FIELD, METHOD, PARAMETER, LOCAL_VARIABLE, TYPE_USE})
|
||||
@IntDef({
|
||||
FORMAT_HANDLED,
|
||||
FORMAT_EXCEEDS_CAPABILITIES,
|
||||
@ -1208,7 +1317,9 @@ public final class C {
|
||||
*/
|
||||
@UnstableApi public static final int FORMAT_UNSUPPORTED_TYPE = 0b000;
|
||||
|
||||
/** @deprecated Use {@link Util#usToMs(long)}. */
|
||||
/**
|
||||
* @deprecated Use {@link Util#usToMs(long)}.
|
||||
*/
|
||||
@UnstableApi
|
||||
@InlineMe(
|
||||
replacement = "Util.usToMs(timeUs)",
|
||||
@ -1218,7 +1329,9 @@ public final class C {
|
||||
return Util.usToMs(timeUs);
|
||||
}
|
||||
|
||||
/** @deprecated Use {@link Util#msToUs(long)}. */
|
||||
/**
|
||||
* @deprecated Use {@link Util#msToUs(long)}.
|
||||
*/
|
||||
@UnstableApi
|
||||
@InlineMe(
|
||||
replacement = "Util.msToUs(timeMs)",
|
||||
@ -1228,7 +1341,9 @@ public final class C {
|
||||
return Util.msToUs(timeMs);
|
||||
}
|
||||
|
||||
/** @deprecated Use {@link Util#generateAudioSessionIdV21(Context)}. */
|
||||
/**
|
||||
* @deprecated Use {@link Util#generateAudioSessionIdV21(Context)}.
|
||||
*/
|
||||
@UnstableApi
|
||||
@InlineMe(
|
||||
replacement = "Util.generateAudioSessionIdV21(context)",
|
||||
@ -1239,7 +1354,9 @@ public final class C {
|
||||
return Util.generateAudioSessionIdV21(context);
|
||||
}
|
||||
|
||||
/** @deprecated Use {@link Util#getFormatSupportString(int)}. */
|
||||
/**
|
||||
* @deprecated Use {@link Util#getFormatSupportString(int)}.
|
||||
*/
|
||||
@UnstableApi
|
||||
@InlineMe(
|
||||
replacement = "Util.getFormatSupportString(formatSupport)",
|
||||
@ -1249,14 +1366,16 @@ public final class C {
|
||||
return Util.getFormatSupportString(formatSupport);
|
||||
}
|
||||
|
||||
/** @deprecated Use {@link Util#getErrorCodeForMediaDrmErrorCode(int)}. */
|
||||
/**
|
||||
* @deprecated Use {@link Util#getErrorCodeForMediaDrmErrorCode(int)}.
|
||||
*/
|
||||
@UnstableApi
|
||||
@InlineMe(
|
||||
replacement = "Util.getErrorCodeForMediaDrmErrorCode(mediaDrmErrorCode)",
|
||||
imports = {"androidx.media3.common.util.Util"})
|
||||
@Deprecated
|
||||
@PlaybackException.ErrorCode
|
||||
public static int getErrorCodeForMediaDrmErrorCode(int mediaDrmErrorCode) {
|
||||
public static @PlaybackException.ErrorCode int getErrorCodeForMediaDrmErrorCode(
|
||||
int mediaDrmErrorCode) {
|
||||
return Util.getErrorCodeForMediaDrmErrorCode(mediaDrmErrorCode);
|
||||
}
|
||||
}
|
||||
|
@ -15,6 +15,8 @@
|
||||
*/
|
||||
package androidx.media3.common;
|
||||
|
||||
import static java.lang.annotation.ElementType.TYPE_USE;
|
||||
|
||||
import android.os.Bundle;
|
||||
import androidx.annotation.IntDef;
|
||||
import androidx.annotation.Nullable;
|
||||
@ -22,6 +24,7 @@ import androidx.media3.common.util.UnstableApi;
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
import java.util.Arrays;
|
||||
import org.checkerframework.dataflow.qual.Pure;
|
||||
|
||||
@ -35,8 +38,7 @@ public final class ColorInfo implements Bundleable {
|
||||
* made.
|
||||
*/
|
||||
@Pure
|
||||
@C.ColorSpace
|
||||
public static int isoColorPrimariesToColorSpace(int isoColorPrimaries) {
|
||||
public static @C.ColorSpace int isoColorPrimariesToColorSpace(int isoColorPrimaries) {
|
||||
switch (isoColorPrimaries) {
|
||||
case 1:
|
||||
return C.COLOR_SPACE_BT709;
|
||||
@ -58,8 +60,8 @@ public final class ColorInfo implements Bundleable {
|
||||
* mapping can be made.
|
||||
*/
|
||||
@Pure
|
||||
@C.ColorTransfer
|
||||
public static int isoTransferCharacteristicsToColorTransfer(int isoTransferCharacteristics) {
|
||||
public static @C.ColorTransfer int isoTransferCharacteristicsToColorTransfer(
|
||||
int isoTransferCharacteristics) {
|
||||
switch (isoTransferCharacteristics) {
|
||||
case 1: // BT.709.
|
||||
case 6: // SMPTE 170M.
|
||||
@ -78,20 +80,20 @@ public final class ColorInfo implements Bundleable {
|
||||
* 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.ColorSpace public final int colorSpace;
|
||||
public final @C.ColorSpace int colorSpace;
|
||||
|
||||
/**
|
||||
* The color range of the video. Valid values are {@link C#COLOR_RANGE_LIMITED}, {@link
|
||||
* C#COLOR_RANGE_FULL} or {@link Format#NO_VALUE} if unknown.
|
||||
*/
|
||||
@C.ColorRange public final int colorRange;
|
||||
public final @C.ColorRange int colorRange;
|
||||
|
||||
/**
|
||||
* The color transfer characteristics of the video. Valid values are {@link C#COLOR_TRANSFER_HLG},
|
||||
* {@link C#COLOR_TRANSFER_ST2084}, {@link C#COLOR_TRANSFER_SDR} or {@link Format#NO_VALUE} if
|
||||
* unknown.
|
||||
*/
|
||||
@C.ColorTransfer public final int colorTransfer;
|
||||
public final @C.ColorTransfer int colorTransfer;
|
||||
|
||||
/** HdrStaticInfo as defined in CTA-861.3, or null if none specified. */
|
||||
@Nullable public final byte[] hdrStaticInfo;
|
||||
@ -163,6 +165,7 @@ public final class ColorInfo implements Bundleable {
|
||||
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@Target(TYPE_USE)
|
||||
@IntDef({
|
||||
FIELD_COLOR_SPACE,
|
||||
FIELD_COLOR_RANGE,
|
||||
|
@ -15,12 +15,13 @@
|
||||
*/
|
||||
package androidx.media3.common;
|
||||
|
||||
import static java.lang.annotation.ElementType.TYPE_USE;
|
||||
|
||||
import android.os.Bundle;
|
||||
import androidx.annotation.IntDef;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.media3.common.util.UnstableApi;
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
@ -31,7 +32,7 @@ public final class DeviceInfo implements Bundleable {
|
||||
/** Types of playback. One of {@link #PLAYBACK_TYPE_LOCAL} or {@link #PLAYBACK_TYPE_REMOTE}. */
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@Target({ElementType.TYPE_USE})
|
||||
@Target(TYPE_USE)
|
||||
@IntDef({
|
||||
PLAYBACK_TYPE_LOCAL,
|
||||
PLAYBACK_TYPE_REMOTE,
|
||||
@ -88,6 +89,7 @@ public final class DeviceInfo implements Bundleable {
|
||||
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@Target(TYPE_USE)
|
||||
@IntDef({FIELD_PLAYBACK_TYPE, FIELD_MIN_VOLUME, FIELD_MAX_VOLUME})
|
||||
private @interface FieldNumber {}
|
||||
|
||||
|
@ -52,7 +52,8 @@ public final class DrmInitData implements Comparator<SchemeData>, Parcelable {
|
||||
* @param mediaData DRM session acquisition data obtained from the media.
|
||||
* @return A {@link DrmInitData} obtained from merging a media manifest and a media stream.
|
||||
*/
|
||||
public static @Nullable DrmInitData createSessionCreationData(
|
||||
@Nullable
|
||||
public static DrmInitData createSessionCreationData(
|
||||
@Nullable DrmInitData manifestData, @Nullable DrmInitData mediaData) {
|
||||
ArrayList<SchemeData> result = new ArrayList<>();
|
||||
String schemeType = null;
|
||||
@ -91,7 +92,9 @@ public final class DrmInitData implements Comparator<SchemeData>, Parcelable {
|
||||
/** Number of {@link SchemeData}s. */
|
||||
public final int schemeDataCount;
|
||||
|
||||
/** @param schemeDatas Scheme initialization data for possibly multiple DRM schemes. */
|
||||
/**
|
||||
* @param schemeDatas Scheme initialization data for possibly multiple DRM schemes.
|
||||
*/
|
||||
public DrmInitData(List<SchemeData> schemeDatas) {
|
||||
this(null, false, schemeDatas.toArray(new SchemeData[0]));
|
||||
}
|
||||
@ -104,7 +107,9 @@ public final class DrmInitData implements Comparator<SchemeData>, Parcelable {
|
||||
this(schemeType, false, schemeDatas.toArray(new SchemeData[0]));
|
||||
}
|
||||
|
||||
/** @param schemeDatas Scheme initialization data for possibly multiple DRM schemes. */
|
||||
/**
|
||||
* @param schemeDatas Scheme initialization data for possibly multiple DRM schemes.
|
||||
*/
|
||||
public DrmInitData(SchemeData... schemeDatas) {
|
||||
this(null, schemeDatas);
|
||||
}
|
||||
|
@ -16,6 +16,7 @@
|
||||
package androidx.media3.common;
|
||||
|
||||
import static androidx.media3.common.MimeTypes.normalizeMimeType;
|
||||
import static java.lang.annotation.ElementType.TYPE_USE;
|
||||
|
||||
import android.net.Uri;
|
||||
import androidx.annotation.IntDef;
|
||||
@ -25,6 +26,7 @@ import androidx.media3.common.util.UnstableApi;
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@ -39,6 +41,7 @@ public final class FileTypes {
|
||||
*/
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@Target(TYPE_USE)
|
||||
@IntDef({
|
||||
UNKNOWN, AC3, AC4, ADTS, AMR, FLAC, FLV, MATROSKA, MP3, MP4, OGG, PS, TS, WAV, WEBVTT, JPEG
|
||||
})
|
||||
@ -111,8 +114,8 @@ public final class FileTypes {
|
||||
private FileTypes() {}
|
||||
|
||||
/** Returns the {@link Type} corresponding to the response headers provided. */
|
||||
@FileTypes.Type
|
||||
public static int inferFileTypeFromResponseHeaders(Map<String, List<String>> responseHeaders) {
|
||||
public static @FileTypes.Type int inferFileTypeFromResponseHeaders(
|
||||
Map<String, List<String>> responseHeaders) {
|
||||
@Nullable List<String> contentTypes = responseHeaders.get(HEADER_CONTENT_TYPE);
|
||||
@Nullable
|
||||
String mimeType = contentTypes == null || contentTypes.isEmpty() ? null : contentTypes.get(0);
|
||||
@ -124,8 +127,7 @@ public final class FileTypes {
|
||||
*
|
||||
* <p>Returns {@link #UNKNOWN} if the mime type is {@code null}.
|
||||
*/
|
||||
@FileTypes.Type
|
||||
public static int inferFileTypeFromMimeType(@Nullable String mimeType) {
|
||||
public static @FileTypes.Type int inferFileTypeFromMimeType(@Nullable String mimeType) {
|
||||
if (mimeType == null) {
|
||||
return FileTypes.UNKNOWN;
|
||||
}
|
||||
@ -175,8 +177,7 @@ public final class FileTypes {
|
||||
}
|
||||
|
||||
/** Returns the {@link Type} corresponding to the {@link Uri} provided. */
|
||||
@FileTypes.Type
|
||||
public static int inferFileTypeFromUri(Uri uri) {
|
||||
public static @FileTypes.Type int inferFileTypeFromUri(Uri uri) {
|
||||
@Nullable String filename = uri.getLastPathSegment();
|
||||
if (filename == null) {
|
||||
return FileTypes.UNKNOWN;
|
||||
|
@ -15,6 +15,8 @@
|
||||
*/
|
||||
package androidx.media3.common;
|
||||
|
||||
import static java.lang.annotation.ElementType.TYPE_USE;
|
||||
|
||||
import android.os.Bundle;
|
||||
import androidx.annotation.IntDef;
|
||||
import androidx.annotation.Nullable;
|
||||
@ -25,6 +27,7 @@ import com.google.common.base.Joiner;
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
@ -153,14 +156,14 @@ public final class Format implements Bundleable {
|
||||
private int rotationDegrees;
|
||||
private float pixelWidthHeightRatio;
|
||||
@Nullable private byte[] projectionData;
|
||||
@C.StereoMode private int stereoMode;
|
||||
private @C.StereoMode int stereoMode;
|
||||
@Nullable private ColorInfo colorInfo;
|
||||
|
||||
// Audio specific.
|
||||
|
||||
private int channelCount;
|
||||
private int sampleRate;
|
||||
@C.PcmEncoding private int pcmEncoding;
|
||||
private @C.PcmEncoding int pcmEncoding;
|
||||
private int encoderDelay;
|
||||
private int encoderPadding;
|
||||
|
||||
@ -170,7 +173,7 @@ public final class Format implements Bundleable {
|
||||
|
||||
// Provided by the source.
|
||||
|
||||
@C.CryptoType private int cryptoType;
|
||||
private @C.CryptoType int cryptoType;
|
||||
|
||||
/** Creates a new instance with default values. */
|
||||
public Builder() {
|
||||
@ -724,7 +727,7 @@ public final class Format implements Bundleable {
|
||||
* modes are {@link C#STEREO_MODE_MONO}, {@link C#STEREO_MODE_TOP_BOTTOM}, {@link
|
||||
* C#STEREO_MODE_LEFT_RIGHT}, {@link C#STEREO_MODE_STEREO_MESH}.
|
||||
*/
|
||||
@UnstableApi @C.StereoMode public final int stereoMode;
|
||||
@UnstableApi public final @C.StereoMode int stereoMode;
|
||||
/** The color metadata associated with the video, or null if not applicable. */
|
||||
@UnstableApi @Nullable public final ColorInfo colorInfo;
|
||||
|
||||
@ -735,7 +738,7 @@ public final class Format implements Bundleable {
|
||||
/** The audio sampling rate in Hz, or {@link #NO_VALUE} if unknown or not applicable. */
|
||||
public final int sampleRate;
|
||||
/** The {@link C.PcmEncoding} for PCM audio. Set to {@link #NO_VALUE} for other media types. */
|
||||
@UnstableApi @C.PcmEncoding public final int pcmEncoding;
|
||||
@UnstableApi public final @C.PcmEncoding int pcmEncoding;
|
||||
/**
|
||||
* The number of frames to trim from the start of the decoded audio stream, or 0 if not
|
||||
* applicable.
|
||||
@ -759,14 +762,16 @@ public final class Format implements Bundleable {
|
||||
* {@link #drmInitData} is non-null, but may be {@link C#CRYPTO_TYPE_UNSUPPORTED} to indicate that
|
||||
* the samples are encrypted using an unsupported crypto type.
|
||||
*/
|
||||
@UnstableApi @C.CryptoType public final int cryptoType;
|
||||
@UnstableApi public final @C.CryptoType int cryptoType;
|
||||
|
||||
// Lazily initialized hashcode.
|
||||
private int hashCode;
|
||||
|
||||
// Video.
|
||||
|
||||
/** @deprecated Use {@link Format.Builder}. */
|
||||
/**
|
||||
* @deprecated Use {@link Format.Builder}.
|
||||
*/
|
||||
@UnstableApi
|
||||
@Deprecated
|
||||
public static Format createVideoSampleFormat(
|
||||
@ -795,7 +800,9 @@ public final class Format implements Bundleable {
|
||||
.build();
|
||||
}
|
||||
|
||||
/** @deprecated Use {@link Format.Builder}. */
|
||||
/**
|
||||
* @deprecated Use {@link Format.Builder}.
|
||||
*/
|
||||
@UnstableApi
|
||||
@Deprecated
|
||||
public static Format createVideoSampleFormat(
|
||||
@ -830,7 +837,9 @@ public final class Format implements Bundleable {
|
||||
|
||||
// Audio.
|
||||
|
||||
/** @deprecated Use {@link Format.Builder}. */
|
||||
/**
|
||||
* @deprecated Use {@link Format.Builder}.
|
||||
*/
|
||||
@UnstableApi
|
||||
@Deprecated
|
||||
public static Format createAudioSampleFormat(
|
||||
@ -861,7 +870,9 @@ public final class Format implements Bundleable {
|
||||
.build();
|
||||
}
|
||||
|
||||
/** @deprecated Use {@link Format.Builder}. */
|
||||
/**
|
||||
* @deprecated Use {@link Format.Builder}.
|
||||
*/
|
||||
@UnstableApi
|
||||
@Deprecated
|
||||
public static Format createAudioSampleFormat(
|
||||
@ -896,7 +907,9 @@ public final class Format implements Bundleable {
|
||||
|
||||
// Generic.
|
||||
|
||||
/** @deprecated Use {@link Format.Builder}. */
|
||||
/**
|
||||
* @deprecated Use {@link Format.Builder}.
|
||||
*/
|
||||
@UnstableApi
|
||||
@Deprecated
|
||||
public static Format createContainerFormat(
|
||||
@ -923,7 +936,9 @@ public final class Format implements Bundleable {
|
||||
.build();
|
||||
}
|
||||
|
||||
/** @deprecated Use {@link Format.Builder}. */
|
||||
/**
|
||||
* @deprecated Use {@link Format.Builder}.
|
||||
*/
|
||||
@UnstableApi
|
||||
@Deprecated
|
||||
public static Format createSampleFormat(@Nullable String id, @Nullable String sampleMimeType) {
|
||||
@ -983,28 +998,36 @@ public final class Format implements Bundleable {
|
||||
return new Builder(this);
|
||||
}
|
||||
|
||||
/** @deprecated Use {@link #buildUpon()} and {@link Builder#setMaxInputSize(int)}. */
|
||||
/**
|
||||
* @deprecated Use {@link #buildUpon()} and {@link Builder#setMaxInputSize(int)}.
|
||||
*/
|
||||
@UnstableApi
|
||||
@Deprecated
|
||||
public Format copyWithMaxInputSize(int maxInputSize) {
|
||||
return buildUpon().setMaxInputSize(maxInputSize).build();
|
||||
}
|
||||
|
||||
/** @deprecated Use {@link #buildUpon()} and {@link Builder#setSubsampleOffsetUs(long)}. */
|
||||
/**
|
||||
* @deprecated Use {@link #buildUpon()} and {@link Builder#setSubsampleOffsetUs(long)}.
|
||||
*/
|
||||
@UnstableApi
|
||||
@Deprecated
|
||||
public Format copyWithSubsampleOffsetUs(long subsampleOffsetUs) {
|
||||
return buildUpon().setSubsampleOffsetUs(subsampleOffsetUs).build();
|
||||
}
|
||||
|
||||
/** @deprecated Use {@link #buildUpon()} and {@link Builder#setLabel(String)} . */
|
||||
/**
|
||||
* @deprecated Use {@link #buildUpon()} and {@link Builder#setLabel(String)} .
|
||||
*/
|
||||
@UnstableApi
|
||||
@Deprecated
|
||||
public Format copyWithLabel(@Nullable String label) {
|
||||
return buildUpon().setLabel(label).build();
|
||||
}
|
||||
|
||||
/** @deprecated Use {@link #withManifestFormatInfo(Format)}. */
|
||||
/**
|
||||
* @deprecated Use {@link #withManifestFormatInfo(Format)}.
|
||||
*/
|
||||
@UnstableApi
|
||||
@Deprecated
|
||||
public Format copyWithManifestFormatInfo(Format manifestFormat) {
|
||||
@ -1089,21 +1112,27 @@ public final class Format implements Bundleable {
|
||||
return buildUpon().setEncoderDelay(encoderDelay).setEncoderPadding(encoderPadding).build();
|
||||
}
|
||||
|
||||
/** @deprecated Use {@link #buildUpon()} and {@link Builder#setFrameRate(float)}. */
|
||||
/**
|
||||
* @deprecated Use {@link #buildUpon()} and {@link Builder#setFrameRate(float)}.
|
||||
*/
|
||||
@UnstableApi
|
||||
@Deprecated
|
||||
public Format copyWithFrameRate(float frameRate) {
|
||||
return buildUpon().setFrameRate(frameRate).build();
|
||||
}
|
||||
|
||||
/** @deprecated Use {@link #buildUpon()} and {@link Builder#setDrmInitData(DrmInitData)}. */
|
||||
/**
|
||||
* @deprecated Use {@link #buildUpon()} and {@link Builder#setDrmInitData(DrmInitData)}.
|
||||
*/
|
||||
@UnstableApi
|
||||
@Deprecated
|
||||
public Format copyWithDrmInitData(@Nullable DrmInitData drmInitData) {
|
||||
return buildUpon().setDrmInitData(drmInitData).build();
|
||||
}
|
||||
|
||||
/** @deprecated Use {@link #buildUpon()} and {@link Builder#setMetadata(Metadata)}. */
|
||||
/**
|
||||
* @deprecated Use {@link #buildUpon()} and {@link Builder#setMetadata(Metadata)}.
|
||||
*/
|
||||
@UnstableApi
|
||||
@Deprecated
|
||||
public Format copyWithMetadata(@Nullable Metadata metadata) {
|
||||
@ -1417,6 +1446,7 @@ public final class Format implements Bundleable {
|
||||
// Bundleable implementation.
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@Target(TYPE_USE)
|
||||
@IntDef({
|
||||
FIELD_ID,
|
||||
FIELD_LABEL,
|
||||
|
@ -16,6 +16,7 @@
|
||||
package androidx.media3.common;
|
||||
|
||||
import static androidx.media3.common.util.Assertions.checkArgument;
|
||||
import static java.lang.annotation.ElementType.TYPE_USE;
|
||||
|
||||
import android.os.Bundle;
|
||||
import androidx.annotation.IntDef;
|
||||
@ -25,6 +26,7 @@ import com.google.common.base.Objects;
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* A rating expressed as "heart" or "no heart". It can be used to indicate whether the content is a
|
||||
@ -77,10 +79,11 @@ public final class HeartRating extends Rating {
|
||||
|
||||
// Bundleable implementation.
|
||||
|
||||
@RatingType private static final int TYPE = RATING_TYPE_HEART;
|
||||
private static final @RatingType int TYPE = RATING_TYPE_HEART;
|
||||
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@Target(TYPE_USE)
|
||||
@IntDef({FIELD_RATING_TYPE, FIELD_RATED, FIELD_IS_HEART})
|
||||
private @interface FieldNumber {}
|
||||
|
||||
@ -102,7 +105,7 @@ public final class HeartRating extends Rating {
|
||||
|
||||
private static HeartRating fromBundle(Bundle bundle) {
|
||||
checkArgument(
|
||||
bundle.getInt(keyForField(FIELD_RATING_TYPE), /* defaultValue= */ RATING_TYPE_DEFAULT)
|
||||
bundle.getInt(keyForField(FIELD_RATING_TYPE), /* defaultValue= */ RATING_TYPE_UNSET)
|
||||
== TYPE);
|
||||
boolean isRated = bundle.getBoolean(keyForField(FIELD_RATED), /* defaultValue= */ false);
|
||||
return isRated
|
||||
|
@ -17,6 +17,7 @@ package androidx.media3.common;
|
||||
|
||||
import static androidx.media3.common.util.Assertions.checkNotNull;
|
||||
import static androidx.media3.common.util.Assertions.checkState;
|
||||
import static java.lang.annotation.ElementType.TYPE_USE;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
@ -31,6 +32,7 @@ import com.google.common.collect.ImmutableMap;
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
@ -710,7 +712,9 @@ public final class MediaItem implements Bundleable {
|
||||
/** The UUID of the protection scheme. */
|
||||
public final UUID scheme;
|
||||
|
||||
/** @deprecated Use {@link #scheme} instead. */
|
||||
/**
|
||||
* @deprecated Use {@link #scheme} instead.
|
||||
*/
|
||||
@UnstableApi @Deprecated public final UUID uuid;
|
||||
|
||||
/**
|
||||
@ -719,7 +723,9 @@ public final class MediaItem implements Bundleable {
|
||||
*/
|
||||
@Nullable public final Uri licenseUri;
|
||||
|
||||
/** @deprecated Use {@link #licenseRequestHeaders} instead. */
|
||||
/**
|
||||
* @deprecated Use {@link #licenseRequestHeaders} instead.
|
||||
*/
|
||||
@UnstableApi @Deprecated public final ImmutableMap<String, String> requestHeaders;
|
||||
|
||||
/** The headers to attach to requests sent to the DRM license server. */
|
||||
@ -740,7 +746,9 @@ public final class MediaItem implements Bundleable {
|
||||
*/
|
||||
public final boolean forceDefaultLicenseUri;
|
||||
|
||||
/** @deprecated Use {@link #forcedSessionTrackTypes}. */
|
||||
/**
|
||||
* @deprecated Use {@link #forcedSessionTrackTypes}.
|
||||
*/
|
||||
@UnstableApi @Deprecated public final ImmutableList<@C.TrackType Integer> sessionForClearTypes;
|
||||
/**
|
||||
* The types of tracks for which to always use a DRM session even if the content is unencrypted.
|
||||
@ -927,7 +935,9 @@ public final class MediaItem implements Bundleable {
|
||||
|
||||
/** Optional subtitles to be sideloaded. */
|
||||
public final ImmutableList<SubtitleConfiguration> subtitleConfigurations;
|
||||
/** @deprecated Use {@link #subtitleConfigurations} instead. */
|
||||
/**
|
||||
* @deprecated Use {@link #subtitleConfigurations} instead.
|
||||
*/
|
||||
@UnstableApi @Deprecated public final List<Subtitle> subtitles;
|
||||
|
||||
/**
|
||||
@ -996,7 +1006,9 @@ public final class MediaItem implements Bundleable {
|
||||
}
|
||||
}
|
||||
|
||||
/** @deprecated Use {@link LocalConfiguration}. */
|
||||
/**
|
||||
* @deprecated Use {@link LocalConfiguration}.
|
||||
*/
|
||||
@UnstableApi
|
||||
@Deprecated
|
||||
public static final class PlaybackProperties extends LocalConfiguration {
|
||||
@ -1158,7 +1170,9 @@ public final class MediaItem implements Bundleable {
|
||||
builder.maxPlaybackSpeed);
|
||||
}
|
||||
|
||||
/** @deprecated Use {@link Builder} instead. */
|
||||
/**
|
||||
* @deprecated Use {@link Builder} instead.
|
||||
*/
|
||||
@UnstableApi
|
||||
@Deprecated
|
||||
public LiveConfiguration(
|
||||
@ -1210,6 +1224,7 @@ public final class MediaItem implements Bundleable {
|
||||
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@Target(TYPE_USE)
|
||||
@IntDef({
|
||||
FIELD_TARGET_OFFSET_MS,
|
||||
FIELD_MIN_OFFSET_MS,
|
||||
@ -1424,19 +1439,25 @@ public final class MediaItem implements Bundleable {
|
||||
}
|
||||
}
|
||||
|
||||
/** @deprecated Use {@link MediaItem.SubtitleConfiguration} instead */
|
||||
/**
|
||||
* @deprecated Use {@link MediaItem.SubtitleConfiguration} instead
|
||||
*/
|
||||
@UnstableApi
|
||||
@Deprecated
|
||||
public static final class Subtitle extends SubtitleConfiguration {
|
||||
|
||||
/** @deprecated Use {@link Builder} instead. */
|
||||
/**
|
||||
* @deprecated Use {@link Builder} instead.
|
||||
*/
|
||||
@UnstableApi
|
||||
@Deprecated
|
||||
public Subtitle(Uri uri, String mimeType, @Nullable String language) {
|
||||
this(uri, mimeType, language, /* selectionFlags= */ 0);
|
||||
}
|
||||
|
||||
/** @deprecated Use {@link Builder} instead. */
|
||||
/**
|
||||
* @deprecated Use {@link Builder} instead.
|
||||
*/
|
||||
@UnstableApi
|
||||
@Deprecated
|
||||
public Subtitle(
|
||||
@ -1444,7 +1465,9 @@ public final class MediaItem implements Bundleable {
|
||||
this(uri, mimeType, language, selectionFlags, /* roleFlags= */ 0, /* label= */ null);
|
||||
}
|
||||
|
||||
/** @deprecated Use {@link Builder} instead. */
|
||||
/**
|
||||
* @deprecated Use {@link Builder} instead.
|
||||
*/
|
||||
@UnstableApi
|
||||
@Deprecated
|
||||
public Subtitle(
|
||||
@ -1547,7 +1570,9 @@ public final class MediaItem implements Bundleable {
|
||||
return buildClippingProperties();
|
||||
}
|
||||
|
||||
/** @deprecated Use {@link #build()} instead. */
|
||||
/**
|
||||
* @deprecated Use {@link #build()} instead.
|
||||
*/
|
||||
@UnstableApi
|
||||
@Deprecated
|
||||
public ClippingProperties buildClippingProperties() {
|
||||
@ -1625,6 +1650,7 @@ public final class MediaItem implements Bundleable {
|
||||
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@Target(TYPE_USE)
|
||||
@IntDef({
|
||||
FIELD_START_POSITION_MS,
|
||||
FIELD_END_POSITION_MS,
|
||||
@ -1676,7 +1702,9 @@ public final class MediaItem implements Bundleable {
|
||||
}
|
||||
}
|
||||
|
||||
/** @deprecated Use {@link ClippingConfiguration} instead. */
|
||||
/**
|
||||
* @deprecated Use {@link ClippingConfiguration} instead.
|
||||
*/
|
||||
@UnstableApi
|
||||
@Deprecated
|
||||
public static final class ClippingProperties extends ClippingConfiguration {
|
||||
@ -1705,7 +1733,9 @@ public final class MediaItem implements Bundleable {
|
||||
* boundaries.
|
||||
*/
|
||||
@Nullable public final LocalConfiguration localConfiguration;
|
||||
/** @deprecated Use {@link #localConfiguration} instead. */
|
||||
/**
|
||||
* @deprecated Use {@link #localConfiguration} instead.
|
||||
*/
|
||||
@UnstableApi @Deprecated @Nullable public final PlaybackProperties playbackProperties;
|
||||
|
||||
/** The live playback configuration. */
|
||||
@ -1716,7 +1746,9 @@ public final class MediaItem implements Bundleable {
|
||||
|
||||
/** The clipping properties. */
|
||||
public final ClippingConfiguration clippingConfiguration;
|
||||
/** @deprecated Use {@link #clippingConfiguration} instead. */
|
||||
/**
|
||||
* @deprecated Use {@link #clippingConfiguration} instead.
|
||||
*/
|
||||
@UnstableApi @Deprecated public final ClippingProperties clippingProperties;
|
||||
|
||||
// Using PlaybackProperties and ClippingProperties until they're deleted.
|
||||
@ -1773,6 +1805,7 @@ public final class MediaItem implements Bundleable {
|
||||
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@Target(TYPE_USE)
|
||||
@IntDef({
|
||||
FIELD_MEDIA_ID,
|
||||
FIELD_LIVE_CONFIGURATION,
|
||||
@ -1839,7 +1872,7 @@ public final class MediaItem implements Bundleable {
|
||||
return new MediaItem(
|
||||
mediaId,
|
||||
clippingConfiguration,
|
||||
/* playbackProperties= */ null,
|
||||
/* localConfiguration= */ null,
|
||||
liveConfiguration,
|
||||
mediaMetadata);
|
||||
}
|
||||
|
@ -56,11 +56,11 @@ public final class MediaMetadata implements Bundleable {
|
||||
@Nullable private Rating userRating;
|
||||
@Nullable private Rating overallRating;
|
||||
@Nullable private byte[] artworkData;
|
||||
@Nullable @PictureType private Integer artworkDataType;
|
||||
@Nullable private @PictureType Integer artworkDataType;
|
||||
@Nullable private Uri artworkUri;
|
||||
@Nullable private Integer trackNumber;
|
||||
@Nullable private Integer totalTrackCount;
|
||||
@Nullable @FolderType private Integer folderType;
|
||||
@Nullable private @FolderType Integer folderType;
|
||||
@Nullable private Boolean isPlayable;
|
||||
@Nullable private Integer recordingYear;
|
||||
@Nullable private Integer recordingMonth;
|
||||
@ -248,7 +248,9 @@ public final class MediaMetadata implements Bundleable {
|
||||
return this;
|
||||
}
|
||||
|
||||
/** @deprecated Use {@link #setRecordingYear(Integer)} instead. */
|
||||
/**
|
||||
* @deprecated Use {@link #setRecordingYear(Integer)} instead.
|
||||
*/
|
||||
@UnstableApi
|
||||
@Deprecated
|
||||
public Builder setYear(@Nullable Integer year) {
|
||||
@ -521,6 +523,8 @@ public final class MediaMetadata implements Bundleable {
|
||||
* href="https://www.bluetooth.com/specifications/specs/a-v-remote-control-profile-1-6-2/">Bluetooth
|
||||
* AVRCP 1.6.2</a>).
|
||||
*/
|
||||
// @Target list includes both 'default' targets and TYPE_USE, to ensure backwards compatibility
|
||||
// with Kotlin usages from before TYPE_USE was added.
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@Target({FIELD, METHOD, PARAMETER, LOCAL_VARIABLE, TYPE_USE})
|
||||
@ -559,6 +563,8 @@ public final class MediaMetadata implements Bundleable {
|
||||
* <p>Values sourced from the ID3 v2.4 specification (See section 4.14 of
|
||||
* https://id3.org/id3v2.4.0-frames).
|
||||
*/
|
||||
// @Target list includes both 'default' targets and TYPE_USE, to ensure backwards compatibility
|
||||
// with Kotlin usages from before TYPE_USE was added.
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@Target({FIELD, METHOD, PARAMETER, LOCAL_VARIABLE, TYPE_USE})
|
||||
@ -650,7 +656,9 @@ public final class MediaMetadata implements Bundleable {
|
||||
@Nullable public final @FolderType Integer folderType;
|
||||
/** Optional boolean for media playability. */
|
||||
@Nullable public final Boolean isPlayable;
|
||||
/** @deprecated Use {@link #recordingYear} instead. */
|
||||
/**
|
||||
* @deprecated Use {@link #recordingYear} instead.
|
||||
*/
|
||||
@UnstableApi @Deprecated @Nullable public final Integer year;
|
||||
/** Optional year of the recording date. */
|
||||
@Nullable public final Integer recordingYear;
|
||||
@ -829,6 +837,7 @@ public final class MediaMetadata implements Bundleable {
|
||||
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@Target(TYPE_USE)
|
||||
@IntDef({
|
||||
FIELD_TITLE,
|
||||
FIELD_ARTIST,
|
||||
|
@ -62,12 +62,16 @@ public final class Metadata implements Parcelable {
|
||||
|
||||
private final Entry[] entries;
|
||||
|
||||
/** @param entries The metadata entries. */
|
||||
/**
|
||||
* @param entries The metadata entries.
|
||||
*/
|
||||
public Metadata(Entry... entries) {
|
||||
this.entries = entries;
|
||||
}
|
||||
|
||||
/** @param entries The metadata entries. */
|
||||
/**
|
||||
* @param entries The metadata entries.
|
||||
*/
|
||||
public Metadata(List<? extends Entry> entries) {
|
||||
this.entries = entries.toArray(new Entry[0]);
|
||||
}
|
||||
|
@ -552,8 +552,7 @@ public final class MimeTypes {
|
||||
* @return The corresponding {@link C.Encoding}, or {@link C#ENCODING_INVALID}.
|
||||
*/
|
||||
@UnstableApi
|
||||
@C.Encoding
|
||||
public static int getEncoding(String mimeType, @Nullable String codec) {
|
||||
public static @C.Encoding int getEncoding(String mimeType, @Nullable String codec) {
|
||||
switch (mimeType) {
|
||||
case MimeTypes.AUDIO_MPEG:
|
||||
return C.ENCODING_MP3;
|
||||
@ -728,8 +727,7 @@ public final class MimeTypes {
|
||||
}
|
||||
|
||||
/** Returns the encoding for {@link #audioObjectTypeIndication}. */
|
||||
@C.Encoding
|
||||
public int getEncoding() {
|
||||
public @C.Encoding int getEncoding() {
|
||||
// See AUDIO_OBJECT_TYPE_AAC_* constants in AacUtil.
|
||||
switch (audioObjectTypeIndication) {
|
||||
case 2:
|
||||
|
@ -16,6 +16,7 @@
|
||||
package androidx.media3.common;
|
||||
|
||||
import static androidx.media3.common.util.Assertions.checkArgument;
|
||||
import static java.lang.annotation.ElementType.TYPE_USE;
|
||||
|
||||
import android.os.Bundle;
|
||||
import androidx.annotation.FloatRange;
|
||||
@ -26,6 +27,7 @@ import com.google.common.base.Objects;
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/** A rating expressed as a percentage. */
|
||||
public final class PercentageRating extends Rating {
|
||||
@ -75,10 +77,11 @@ public final class PercentageRating extends Rating {
|
||||
|
||||
// Bundleable implementation.
|
||||
|
||||
@RatingType private static final int TYPE = RATING_TYPE_PERCENTAGE;
|
||||
private static final @RatingType int TYPE = RATING_TYPE_PERCENTAGE;
|
||||
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@Target(TYPE_USE)
|
||||
@IntDef({FIELD_RATING_TYPE, FIELD_PERCENT})
|
||||
private @interface FieldNumber {}
|
||||
|
||||
@ -98,7 +101,7 @@ public final class PercentageRating extends Rating {
|
||||
|
||||
private static PercentageRating fromBundle(Bundle bundle) {
|
||||
checkArgument(
|
||||
bundle.getInt(keyForField(FIELD_RATING_TYPE), /* defaultValue= */ RATING_TYPE_DEFAULT)
|
||||
bundle.getInt(keyForField(FIELD_RATING_TYPE), /* defaultValue= */ RATING_TYPE_UNSET)
|
||||
== TYPE);
|
||||
float percent = bundle.getFloat(keyForField(FIELD_PERCENT), /* defaultValue= */ RATING_UNSET);
|
||||
return percent == RATING_UNSET ? new PercentageRating() : new PercentageRating(percent);
|
||||
|
@ -46,6 +46,8 @@ public class PlaybackException extends Exception implements Bundleable {
|
||||
* <p>This list of errors may be extended in future versions, and {@link Player} implementations
|
||||
* may define custom error codes.
|
||||
*/
|
||||
// @Target list includes both 'default' targets and TYPE_USE, to ensure backwards compatibility
|
||||
// with Kotlin usages from before TYPE_USE was added.
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@Target({FIELD, METHOD, PARAMETER, LOCAL_VARIABLE, TYPE_USE})
|
||||
@ -408,6 +410,7 @@ public class PlaybackException extends Exception implements Bundleable {
|
||||
*/
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@Target(TYPE_USE)
|
||||
@IntDef(
|
||||
open = true,
|
||||
value = {
|
||||
|
@ -15,6 +15,8 @@
|
||||
*/
|
||||
package androidx.media3.common;
|
||||
|
||||
import static java.lang.annotation.ElementType.TYPE_USE;
|
||||
|
||||
import android.os.Bundle;
|
||||
import androidx.annotation.CheckResult;
|
||||
import androidx.annotation.FloatRange;
|
||||
@ -26,6 +28,7 @@ import androidx.media3.common.util.Util;
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/** Parameters that apply to playback, including speed setting. */
|
||||
public final class PlaybackParameters implements Bundleable {
|
||||
@ -121,6 +124,7 @@ public final class PlaybackParameters implements Bundleable {
|
||||
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@Target(TYPE_USE)
|
||||
@IntDef({FIELD_SPEED, FIELD_PITCH})
|
||||
private @interface FieldNumber {}
|
||||
|
||||
|
@ -15,12 +15,15 @@
|
||||
*/
|
||||
package androidx.media3.common;
|
||||
|
||||
import static java.lang.annotation.ElementType.TYPE_USE;
|
||||
|
||||
import android.os.Bundle;
|
||||
import androidx.annotation.IntDef;
|
||||
import androidx.media3.common.util.UnstableApi;
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* A rating for media content. The style of a rating can be one of {@link HeartRating}, {@link
|
||||
@ -41,8 +44,9 @@ public abstract class Rating implements Bundleable {
|
||||
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@Target(TYPE_USE)
|
||||
@IntDef({
|
||||
RATING_TYPE_DEFAULT,
|
||||
RATING_TYPE_UNSET,
|
||||
RATING_TYPE_HEART,
|
||||
RATING_TYPE_PERCENTAGE,
|
||||
RATING_TYPE_STAR,
|
||||
@ -50,7 +54,7 @@ public abstract class Rating implements Bundleable {
|
||||
})
|
||||
/* package */ @interface RatingType {}
|
||||
|
||||
/* package */ static final int RATING_TYPE_DEFAULT = -1;
|
||||
/* package */ static final int RATING_TYPE_UNSET = -1;
|
||||
/* package */ static final int RATING_TYPE_HEART = 0;
|
||||
/* package */ static final int RATING_TYPE_PERCENTAGE = 1;
|
||||
/* package */ static final int RATING_TYPE_STAR = 2;
|
||||
@ -58,6 +62,7 @@ public abstract class Rating implements Bundleable {
|
||||
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@Target(TYPE_USE)
|
||||
@IntDef({FIELD_RATING_TYPE})
|
||||
private @interface FieldNumber {}
|
||||
|
||||
@ -69,7 +74,7 @@ public abstract class Rating implements Bundleable {
|
||||
private static Rating fromBundle(Bundle bundle) {
|
||||
@RatingType
|
||||
int ratingType =
|
||||
bundle.getInt(keyForField(FIELD_RATING_TYPE), /* defaultValue= */ RATING_TYPE_DEFAULT);
|
||||
bundle.getInt(keyForField(FIELD_RATING_TYPE), /* defaultValue= */ RATING_TYPE_UNSET);
|
||||
switch (ratingType) {
|
||||
case RATING_TYPE_HEART:
|
||||
return HeartRating.CREATOR.fromBundle(bundle);
|
||||
@ -79,8 +84,9 @@ public abstract class Rating implements Bundleable {
|
||||
return StarRating.CREATOR.fromBundle(bundle);
|
||||
case RATING_TYPE_THUMB:
|
||||
return ThumbRating.CREATOR.fromBundle(bundle);
|
||||
case RATING_TYPE_UNSET:
|
||||
default:
|
||||
throw new IllegalArgumentException("Encountered unknown rating type: " + ratingType);
|
||||
throw new IllegalArgumentException("Unknown RatingType: " + ratingType);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -16,6 +16,7 @@
|
||||
package androidx.media3.common;
|
||||
|
||||
import static androidx.media3.common.util.Assertions.checkArgument;
|
||||
import static java.lang.annotation.ElementType.TYPE_USE;
|
||||
|
||||
import android.os.Bundle;
|
||||
import androidx.annotation.FloatRange;
|
||||
@ -27,6 +28,7 @@ import com.google.common.base.Objects;
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/** A rating expressed as a fractional number of stars. */
|
||||
public final class StarRating extends Rating {
|
||||
@ -101,11 +103,12 @@ public final class StarRating extends Rating {
|
||||
|
||||
// Bundleable implementation.
|
||||
|
||||
@RatingType private static final int TYPE = RATING_TYPE_STAR;
|
||||
private static final @RatingType int TYPE = RATING_TYPE_STAR;
|
||||
private static final int MAX_STARS_DEFAULT = 5;
|
||||
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@Target(TYPE_USE)
|
||||
@IntDef({FIELD_RATING_TYPE, FIELD_MAX_STARS, FIELD_STAR_RATING})
|
||||
private @interface FieldNumber {}
|
||||
|
||||
@ -127,7 +130,7 @@ public final class StarRating extends Rating {
|
||||
|
||||
private static StarRating fromBundle(Bundle bundle) {
|
||||
checkArgument(
|
||||
bundle.getInt(keyForField(FIELD_RATING_TYPE), /* defaultValue= */ RATING_TYPE_DEFAULT)
|
||||
bundle.getInt(keyForField(FIELD_RATING_TYPE), /* defaultValue= */ RATING_TYPE_UNSET)
|
||||
== TYPE);
|
||||
int maxStars =
|
||||
bundle.getInt(keyForField(FIELD_MAX_STARS), /* defaultValue= */ MAX_STARS_DEFAULT);
|
||||
|
@ -44,7 +44,9 @@ public final class StreamKey implements Comparable<StreamKey>, Parcelable {
|
||||
/** The stream index. */
|
||||
public final int streamIndex;
|
||||
|
||||
/** @deprecated Use {@link #streamIndex}. */
|
||||
/**
|
||||
* @deprecated Use {@link #streamIndex}.
|
||||
*/
|
||||
@Deprecated public final int trackIndex;
|
||||
|
||||
/**
|
||||
|
@ -16,6 +16,7 @@
|
||||
package androidx.media3.common;
|
||||
|
||||
import static androidx.media3.common.util.Assertions.checkArgument;
|
||||
import static java.lang.annotation.ElementType.TYPE_USE;
|
||||
|
||||
import android.os.Bundle;
|
||||
import androidx.annotation.IntDef;
|
||||
@ -25,6 +26,7 @@ import com.google.common.base.Objects;
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/** A rating expressed as "thumbs up" or "thumbs down". */
|
||||
public final class ThumbRating extends Rating {
|
||||
@ -74,10 +76,11 @@ public final class ThumbRating extends Rating {
|
||||
|
||||
// Bundleable implementation.
|
||||
|
||||
@RatingType private static final int TYPE = RATING_TYPE_THUMB;
|
||||
private static final @RatingType int TYPE = RATING_TYPE_THUMB;
|
||||
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@Target(TYPE_USE)
|
||||
@IntDef({FIELD_RATING_TYPE, FIELD_RATED, FIELD_IS_THUMBS_UP})
|
||||
private @interface FieldNumber {}
|
||||
|
||||
@ -99,7 +102,7 @@ public final class ThumbRating extends Rating {
|
||||
|
||||
private static ThumbRating fromBundle(Bundle bundle) {
|
||||
checkArgument(
|
||||
bundle.getInt(keyForField(FIELD_RATING_TYPE), /* defaultValue= */ RATING_TYPE_DEFAULT)
|
||||
bundle.getInt(keyForField(FIELD_RATING_TYPE), /* defaultValue= */ RATING_TYPE_UNSET)
|
||||
== TYPE);
|
||||
boolean rated = bundle.getBoolean(keyForField(FIELD_RATED), /* defaultValue= */ false);
|
||||
return rated
|
||||
|
@ -20,6 +20,7 @@ import static androidx.media3.common.util.Assertions.checkArgument;
|
||||
import static androidx.media3.common.util.Assertions.checkState;
|
||||
import static java.lang.Math.max;
|
||||
import static java.lang.Math.min;
|
||||
import static java.lang.annotation.ElementType.TYPE_USE;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
@ -37,6 +38,7 @@ import com.google.errorprone.annotations.InlineMe;
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@ -167,7 +169,9 @@ public abstract class Timeline implements Bundleable {
|
||||
*/
|
||||
public Object uid;
|
||||
|
||||
/** @deprecated Use {@link #mediaItem} instead. */
|
||||
/**
|
||||
* @deprecated Use {@link #mediaItem} instead.
|
||||
*/
|
||||
@UnstableApi @Deprecated @Nullable public Object tag;
|
||||
|
||||
/** The {@link MediaItem} associated to the window. Not necessarily unique. */
|
||||
@ -210,7 +214,9 @@ public abstract class Timeline implements Bundleable {
|
||||
/** Whether this window may change when the timeline is updated. */
|
||||
public boolean isDynamic;
|
||||
|
||||
/** @deprecated Use {@link #isLive()} instead. */
|
||||
/**
|
||||
* @deprecated Use {@link #isLive()} instead.
|
||||
*/
|
||||
@UnstableApi @Deprecated public boolean isLive;
|
||||
|
||||
/**
|
||||
@ -414,6 +420,7 @@ public abstract class Timeline implements Bundleable {
|
||||
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@Target(TYPE_USE)
|
||||
@IntDef({
|
||||
FIELD_MEDIA_ITEM,
|
||||
FIELD_PRESENTATION_START_TIME_MS,
|
||||
@ -903,6 +910,7 @@ public abstract class Timeline implements Bundleable {
|
||||
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@Target(TYPE_USE)
|
||||
@IntDef({
|
||||
FIELD_WINDOW_INDEX,
|
||||
FIELD_DURATION_US,
|
||||
@ -1174,7 +1182,9 @@ public abstract class Timeline implements Bundleable {
|
||||
== C.INDEX_UNSET;
|
||||
}
|
||||
|
||||
/** @deprecated Use {@link #getPeriodPositionUs(Window, Period, int, long)} instead. */
|
||||
/**
|
||||
* @deprecated Use {@link #getPeriodPositionUs(Window, Period, int, long)} instead.
|
||||
*/
|
||||
@UnstableApi
|
||||
@Deprecated
|
||||
@InlineMe(replacement = "this.getPeriodPositionUs(window, period, windowIndex, windowPositionUs)")
|
||||
@ -1182,7 +1192,9 @@ public abstract class Timeline implements Bundleable {
|
||||
Window window, Period period, int windowIndex, long windowPositionUs) {
|
||||
return getPeriodPositionUs(window, period, windowIndex, windowPositionUs);
|
||||
}
|
||||
/** @deprecated Use {@link #getPeriodPositionUs(Window, Period, int, long, long)} instead. */
|
||||
/**
|
||||
* @deprecated Use {@link #getPeriodPositionUs(Window, Period, int, long, long)} instead.
|
||||
*/
|
||||
@UnstableApi
|
||||
@Deprecated
|
||||
@Nullable
|
||||
@ -1362,6 +1374,7 @@ public abstract class Timeline implements Bundleable {
|
||||
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@Target(TYPE_USE)
|
||||
@IntDef({
|
||||
FIELD_WINDOWS,
|
||||
FIELD_PERIODS,
|
||||
|
@ -16,6 +16,7 @@
|
||||
package androidx.media3.common;
|
||||
|
||||
import static androidx.media3.common.util.Assertions.checkArgument;
|
||||
import static java.lang.annotation.ElementType.TYPE_USE;
|
||||
|
||||
import android.os.Bundle;
|
||||
import androidx.annotation.CheckResult;
|
||||
@ -29,6 +30,7 @@ import com.google.common.collect.Lists;
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
@ -41,6 +43,8 @@ public final class TrackGroup implements Bundleable {
|
||||
public final int length;
|
||||
/** An identifier for the track group. */
|
||||
public final String id;
|
||||
/** The type of tracks in the group. */
|
||||
public final @C.TrackType int type;
|
||||
|
||||
private final Format[] formats;
|
||||
|
||||
@ -69,6 +73,11 @@ public final class TrackGroup implements Bundleable {
|
||||
this.id = id;
|
||||
this.formats = formats;
|
||||
this.length = formats.length;
|
||||
@C.TrackType int type = MimeTypes.getTrackType(formats[0].sampleMimeType);
|
||||
if (type == C.TRACK_TYPE_UNKNOWN) {
|
||||
type = MimeTypes.getTrackType(formats[0].containerMimeType);
|
||||
}
|
||||
this.type = type;
|
||||
verifyCorrectness();
|
||||
}
|
||||
|
||||
@ -132,13 +141,14 @@ public final class TrackGroup implements Bundleable {
|
||||
return false;
|
||||
}
|
||||
TrackGroup other = (TrackGroup) obj;
|
||||
return length == other.length && id.equals(other.id) && Arrays.equals(formats, other.formats);
|
||||
return id.equals(other.id) && Arrays.equals(formats, other.formats);
|
||||
}
|
||||
|
||||
// Bundleable implementation.
|
||||
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@Target(TYPE_USE)
|
||||
@IntDef({FIELD_FORMATS, FIELD_ID})
|
||||
private @interface FieldNumber {}
|
||||
|
||||
@ -204,8 +214,7 @@ public final class TrackGroup implements Bundleable {
|
||||
return language == null || language.equals(C.LANGUAGE_UNDETERMINED) ? "" : language;
|
||||
}
|
||||
|
||||
@C.RoleFlags
|
||||
private static int normalizeRoleFlags(@C.RoleFlags int roleFlags) {
|
||||
private static @C.RoleFlags int normalizeRoleFlags(@C.RoleFlags int roleFlags) {
|
||||
// Treat trick-play and non-trick-play formats as compatible.
|
||||
return roleFlags | C.ROLE_FLAG_TRICK_PLAY;
|
||||
}
|
||||
|
@ -15,6 +15,8 @@
|
||||
*/
|
||||
package androidx.media3.common;
|
||||
|
||||
import static java.lang.annotation.ElementType.TYPE_USE;
|
||||
|
||||
import android.os.Bundle;
|
||||
import androidx.annotation.IntDef;
|
||||
import androidx.annotation.Nullable;
|
||||
@ -25,6 +27,7 @@ import com.google.common.collect.ImmutableList;
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
import java.util.List;
|
||||
|
||||
/** An immutable array of {@link TrackGroup}s. */
|
||||
@ -105,6 +108,7 @@ public final class TrackGroupArray implements Bundleable {
|
||||
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@Target(TYPE_USE)
|
||||
@IntDef({
|
||||
FIELD_TRACK_GROUPS,
|
||||
})
|
||||
|
@ -15,10 +15,11 @@
|
||||
*/
|
||||
package androidx.media3.common;
|
||||
|
||||
import static java.lang.annotation.ElementType.TYPE_USE;
|
||||
|
||||
import androidx.annotation.IntDef;
|
||||
import androidx.media3.common.util.UnstableApi;
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
@ -38,7 +39,7 @@ public interface TrackSelection {
|
||||
*/
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@Target({ElementType.TYPE_USE})
|
||||
@Target(TYPE_USE)
|
||||
@IntDef(
|
||||
open = true,
|
||||
value = {TYPE_UNSET})
|
||||
|
@ -32,7 +32,9 @@ public final class TrackSelectionArray {
|
||||
// Lazily initialized hashcode.
|
||||
private int hashCode;
|
||||
|
||||
/** @param trackSelections The selections. Must not be null, but may contain null elements. */
|
||||
/**
|
||||
* @param trackSelections The selections. Must not be null, but may contain null elements.
|
||||
*/
|
||||
public TrackSelectionArray(@NullableType TrackSelection... trackSelections) {
|
||||
this.trackSelections = trackSelections;
|
||||
this.length = trackSelections.length;
|
||||
|
@ -0,0 +1,139 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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.checkNotNull;
|
||||
import static java.util.Collections.max;
|
||||
import static java.util.Collections.min;
|
||||
|
||||
import android.os.Bundle;
|
||||
import androidx.annotation.IntDef;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.media3.common.util.UnstableApi;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.primitives.Ints;
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Forces the selection of {@link #trackIndices} for a {@link TrackGroup}.
|
||||
*
|
||||
* <p>If multiple tracks in {@link #trackGroup} are overridden, as many as possible will be selected
|
||||
* depending on the player capabilities.
|
||||
*
|
||||
* <p>If {@link #trackIndices} is empty, no tracks from {@link #trackGroup} will be played. This is
|
||||
* similar to {@link TrackSelectionParameters#disabledTrackTypes}, except it will only affect the
|
||||
* playback of the associated {@link TrackGroup}. For example, if the only {@link
|
||||
* C#TRACK_TYPE_VIDEO} {@link TrackGroup} is associated with no tracks, no video will play until the
|
||||
* next video starts.
|
||||
*/
|
||||
public final class TrackSelectionOverride implements Bundleable {
|
||||
|
||||
/** The {@link TrackGroup} whose {@link #trackIndices} are forced to be selected. */
|
||||
public final TrackGroup trackGroup;
|
||||
/** The indices of tracks in a {@link TrackGroup} to be selected. */
|
||||
public final ImmutableList<Integer> trackIndices;
|
||||
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@IntDef({
|
||||
FIELD_TRACK_GROUP,
|
||||
FIELD_TRACKS,
|
||||
})
|
||||
private @interface FieldNumber {}
|
||||
|
||||
private static final int FIELD_TRACK_GROUP = 0;
|
||||
private static final int FIELD_TRACKS = 1;
|
||||
|
||||
/** Constructs an instance to force all tracks in {@code trackGroup} to be selected. */
|
||||
public TrackSelectionOverride(TrackGroup trackGroup) {
|
||||
this.trackGroup = trackGroup;
|
||||
ImmutableList.Builder<Integer> builder = new ImmutableList.Builder<>();
|
||||
for (int i = 0; i < trackGroup.length; i++) {
|
||||
builder.add(i);
|
||||
}
|
||||
this.trackIndices = builder.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs an instance to force {@code trackIndices} in {@code trackGroup} to be selected.
|
||||
*
|
||||
* @param trackGroup The {@link TrackGroup} for which to override the track selection.
|
||||
* @param trackIndices The indices of the tracks in the {@link TrackGroup} to select.
|
||||
*/
|
||||
public TrackSelectionOverride(TrackGroup trackGroup, List<Integer> trackIndices) {
|
||||
if (!trackIndices.isEmpty()) {
|
||||
if (min(trackIndices) < 0 || max(trackIndices) >= trackGroup.length) {
|
||||
throw new IndexOutOfBoundsException();
|
||||
}
|
||||
}
|
||||
this.trackGroup = trackGroup;
|
||||
this.trackIndices = ImmutableList.copyOf(trackIndices);
|
||||
}
|
||||
|
||||
/** Returns the {@link C.TrackType} of the overridden track group. */
|
||||
public @C.TrackType int getTrackType() {
|
||||
return trackGroup.type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(@Nullable Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null || getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
TrackSelectionOverride that = (TrackSelectionOverride) obj;
|
||||
return trackGroup.equals(that.trackGroup) && trackIndices.equals(that.trackIndices);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return trackGroup.hashCode() + 31 * trackIndices.hashCode();
|
||||
}
|
||||
|
||||
// Bundleable implementation
|
||||
|
||||
@UnstableApi
|
||||
@Override
|
||||
public Bundle toBundle() {
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putBundle(keyForField(FIELD_TRACK_GROUP), trackGroup.toBundle());
|
||||
bundle.putIntArray(keyForField(FIELD_TRACKS), Ints.toArray(trackIndices));
|
||||
return bundle;
|
||||
}
|
||||
|
||||
/** Object that can restore {@code TrackSelectionOverride} from a {@link Bundle}. */
|
||||
@UnstableApi
|
||||
public static final Creator<TrackSelectionOverride> CREATOR =
|
||||
bundle -> {
|
||||
@Nullable Bundle trackGroupBundle = bundle.getBundle(keyForField(FIELD_TRACK_GROUP));
|
||||
checkNotNull(trackGroupBundle); // Mandatory as there are no reasonable defaults.
|
||||
TrackGroup trackGroup = TrackGroup.CREATOR.fromBundle(trackGroupBundle);
|
||||
@Nullable int[] tracks = bundle.getIntArray(keyForField(FIELD_TRACKS));
|
||||
if (tracks == null) {
|
||||
return new TrackSelectionOverride(trackGroup);
|
||||
}
|
||||
return new TrackSelectionOverride(trackGroup, Ints.asList(tracks));
|
||||
};
|
||||
|
||||
private static String keyForField(@FieldNumber int field) {
|
||||
return Integer.toString(field, Character.MAX_RADIX);
|
||||
}
|
||||
}
|
@ -1,309 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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.checkNotNull;
|
||||
import static androidx.media3.common.util.BundleableUtil.fromBundleNullableList;
|
||||
import static androidx.media3.common.util.BundleableUtil.toBundleArrayList;
|
||||
import static java.util.Collections.max;
|
||||
import static java.util.Collections.min;
|
||||
|
||||
import android.os.Bundle;
|
||||
import androidx.annotation.IntDef;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.media3.common.util.UnstableApi;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.primitives.Ints;
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Forces the selection of the specified tracks in {@link TrackGroup TrackGroups}.
|
||||
*
|
||||
* <p>Each {@link TrackSelectionOverride override} only affects the selection of tracks of that
|
||||
* {@link C.TrackType type}. For example overriding the selection of an {@link C#TRACK_TYPE_AUDIO
|
||||
* audio} {@link TrackGroup} will not affect the selection of {@link C#TRACK_TYPE_VIDEO video} or
|
||||
* {@link C#TRACK_TYPE_TEXT text} tracks.
|
||||
*
|
||||
* <p>If multiple {@link TrackGroup TrackGroups} of the same {@link C.TrackType} are overridden,
|
||||
* which tracks will be selected depend on the player capabilities. For example, by default {@code
|
||||
* ExoPlayer} doesn't support selecting more than one {@link TrackGroup} per {@link C.TrackType}.
|
||||
*
|
||||
* <p>Overrides of {@link TrackGroup} that are not currently available are ignored. For example,
|
||||
* when the player transitions to the next {@link MediaItem} in a playlist, any overrides of the
|
||||
* previous {@link MediaItem} are ignored.
|
||||
*
|
||||
* @see TrackSelectionParameters#trackSelectionOverrides
|
||||
*/
|
||||
public final class TrackSelectionOverrides implements Bundleable {
|
||||
|
||||
/** Builder for {@link TrackSelectionOverrides}. */
|
||||
public static final class Builder {
|
||||
// Cannot use ImmutableMap.Builder as it doesn't support removing entries.
|
||||
private final HashMap<TrackGroup, TrackSelectionOverride> overrides;
|
||||
|
||||
/** Creates an builder with no {@link TrackSelectionOverride}. */
|
||||
public Builder() {
|
||||
overrides = new HashMap<>();
|
||||
}
|
||||
|
||||
private Builder(Map<TrackGroup, TrackSelectionOverride> overrides) {
|
||||
this.overrides = new HashMap<>(overrides);
|
||||
}
|
||||
|
||||
/** Adds an override for the provided {@link TrackGroup}. */
|
||||
@UnstableApi
|
||||
public Builder addOverride(TrackSelectionOverride override) {
|
||||
overrides.put(override.trackGroup, override);
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Removes the override associated with the provided {@link TrackGroup} if present. */
|
||||
@UnstableApi
|
||||
public Builder clearOverride(TrackGroup trackGroup) {
|
||||
overrides.remove(trackGroup);
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Set the override for the type of the provided {@link TrackGroup}. */
|
||||
public Builder setOverrideForType(TrackSelectionOverride override) {
|
||||
clearOverridesOfType(override.getTrackType());
|
||||
overrides.put(override.trackGroup, override);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove any override associated with {@link TrackGroup TrackGroups} of type {@code trackType}.
|
||||
*/
|
||||
public Builder clearOverridesOfType(@C.TrackType int trackType) {
|
||||
for (Iterator<TrackSelectionOverride> it = overrides.values().iterator(); it.hasNext(); ) {
|
||||
TrackSelectionOverride trackSelectionOverride = it.next();
|
||||
if (trackSelectionOverride.getTrackType() == trackType) {
|
||||
it.remove();
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Returns a new {@link TrackSelectionOverrides} instance with the current builder values. */
|
||||
public TrackSelectionOverrides build() {
|
||||
return new TrackSelectionOverrides(overrides);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Forces the selection of {@link #trackIndices} for a {@link TrackGroup}.
|
||||
*
|
||||
* <p>If multiple tracks in {@link #trackGroup} are overridden, as many as possible will be
|
||||
* selected depending on the player capabilities.
|
||||
*
|
||||
* <p>If {@link #trackIndices} is empty, no tracks from {@link #trackGroup} will be played. This
|
||||
* is similar to {@link TrackSelectionParameters#disabledTrackTypes}, except it will only affect
|
||||
* the playback of the associated {@link TrackGroup}. For example, if the only {@link
|
||||
* C#TRACK_TYPE_VIDEO} {@link TrackGroup} is associated with no tracks, no video will play until
|
||||
* the next video starts.
|
||||
*/
|
||||
public static final class TrackSelectionOverride implements Bundleable {
|
||||
|
||||
/** The {@link TrackGroup} whose {@link #trackIndices} are forced to be selected. */
|
||||
public final TrackGroup trackGroup;
|
||||
/** The indices of tracks in a {@link TrackGroup} to be selected. */
|
||||
public final ImmutableList<Integer> trackIndices;
|
||||
|
||||
/** Constructs an instance to force all tracks in {@code trackGroup} to be selected. */
|
||||
public TrackSelectionOverride(TrackGroup trackGroup) {
|
||||
this.trackGroup = trackGroup;
|
||||
ImmutableList.Builder<Integer> builder = new ImmutableList.Builder<>();
|
||||
for (int i = 0; i < trackGroup.length; i++) {
|
||||
builder.add(i);
|
||||
}
|
||||
this.trackIndices = builder.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs an instance to force {@code trackIndices} in {@code trackGroup} to be selected.
|
||||
*
|
||||
* @param trackGroup The {@link TrackGroup} for which to override the track selection.
|
||||
* @param trackIndices The indices of the tracks in the {@link TrackGroup} to select.
|
||||
*/
|
||||
public TrackSelectionOverride(TrackGroup trackGroup, List<Integer> trackIndices) {
|
||||
if (!trackIndices.isEmpty()) {
|
||||
if (min(trackIndices) < 0 || max(trackIndices) >= trackGroup.length) {
|
||||
throw new IndexOutOfBoundsException();
|
||||
}
|
||||
}
|
||||
this.trackGroup = trackGroup;
|
||||
this.trackIndices = ImmutableList.copyOf(trackIndices);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(@Nullable Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null || getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
TrackSelectionOverride that = (TrackSelectionOverride) obj;
|
||||
return trackGroup.equals(that.trackGroup) && trackIndices.equals(that.trackIndices);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return trackGroup.hashCode() + 31 * trackIndices.hashCode();
|
||||
}
|
||||
|
||||
/** Returns the {@link C.TrackType} of the overriden track group. */
|
||||
public @C.TrackType int getTrackType() {
|
||||
return MimeTypes.getTrackType(trackGroup.getFormat(0).sampleMimeType);
|
||||
}
|
||||
|
||||
// Bundleable implementation
|
||||
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@IntDef({
|
||||
FIELD_TRACK_GROUP,
|
||||
FIELD_TRACKS,
|
||||
})
|
||||
private @interface FieldNumber {}
|
||||
|
||||
private static final int FIELD_TRACK_GROUP = 0;
|
||||
private static final int FIELD_TRACKS = 1;
|
||||
|
||||
@UnstableApi
|
||||
@Override
|
||||
public Bundle toBundle() {
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putBundle(keyForField(FIELD_TRACK_GROUP), trackGroup.toBundle());
|
||||
bundle.putIntArray(keyForField(FIELD_TRACKS), Ints.toArray(trackIndices));
|
||||
return bundle;
|
||||
}
|
||||
|
||||
/** Object that can restore {@code TrackSelectionOverride} from a {@link Bundle}. */
|
||||
@UnstableApi
|
||||
public static final Creator<TrackSelectionOverride> CREATOR =
|
||||
bundle -> {
|
||||
@Nullable Bundle trackGroupBundle = bundle.getBundle(keyForField(FIELD_TRACK_GROUP));
|
||||
checkNotNull(trackGroupBundle); // Mandatory as there are no reasonable defaults.
|
||||
TrackGroup trackGroup = TrackGroup.CREATOR.fromBundle(trackGroupBundle);
|
||||
@Nullable int[] tracks = bundle.getIntArray(keyForField(FIELD_TRACKS));
|
||||
if (tracks == null) {
|
||||
return new TrackSelectionOverride(trackGroup);
|
||||
}
|
||||
return new TrackSelectionOverride(trackGroup, Ints.asList(tracks));
|
||||
};
|
||||
|
||||
private static String keyForField(@FieldNumber int field) {
|
||||
return Integer.toString(field, Character.MAX_RADIX);
|
||||
}
|
||||
}
|
||||
|
||||
/** Empty {@code TrackSelectionOverrides}, where no track selection is overridden. */
|
||||
public static final TrackSelectionOverrides EMPTY =
|
||||
new TrackSelectionOverrides(ImmutableMap.of());
|
||||
|
||||
private final ImmutableMap<TrackGroup, TrackSelectionOverride> overrides;
|
||||
|
||||
private TrackSelectionOverrides(Map<TrackGroup, TrackSelectionOverride> overrides) {
|
||||
this.overrides = ImmutableMap.copyOf(overrides);
|
||||
}
|
||||
|
||||
/** Returns a {@link Builder} initialized with the values of this instance. */
|
||||
public Builder buildUpon() {
|
||||
return new Builder(overrides);
|
||||
}
|
||||
|
||||
/** Returns a list of the {@link TrackSelectionOverride overrides}. */
|
||||
@UnstableApi
|
||||
public ImmutableList<TrackSelectionOverride> asList() {
|
||||
return ImmutableList.copyOf(overrides.values());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link TrackSelectionOverride} of the provided {@link TrackGroup} or {@code null}
|
||||
* if there is none.
|
||||
*/
|
||||
@Nullable
|
||||
public TrackSelectionOverride getOverride(TrackGroup trackGroup) {
|
||||
return overrides.get(trackGroup);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(@Nullable Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null || getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
TrackSelectionOverrides that = (TrackSelectionOverrides) obj;
|
||||
return overrides.equals(that.overrides);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return overrides.hashCode();
|
||||
}
|
||||
|
||||
// Bundleable implementation
|
||||
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@IntDef({
|
||||
FIELD_OVERRIDES,
|
||||
})
|
||||
private @interface FieldNumber {}
|
||||
|
||||
private static final int FIELD_OVERRIDES = 0;
|
||||
|
||||
@UnstableApi
|
||||
@Override
|
||||
public Bundle toBundle() {
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putParcelableArrayList(
|
||||
keyForField(FIELD_OVERRIDES), toBundleArrayList(overrides.values()));
|
||||
return bundle;
|
||||
}
|
||||
|
||||
/** Object that can restore {@code TrackSelectionOverrides} from a {@link Bundle}. */
|
||||
@UnstableApi
|
||||
public static final Creator<TrackSelectionOverrides> CREATOR =
|
||||
bundle -> {
|
||||
List<TrackSelectionOverride> trackSelectionOverrides =
|
||||
fromBundleNullableList(
|
||||
TrackSelectionOverride.CREATOR,
|
||||
bundle.getParcelableArrayList(keyForField(FIELD_OVERRIDES)),
|
||||
ImmutableList.of());
|
||||
ImmutableMap.Builder<TrackGroup, TrackSelectionOverride> builder =
|
||||
new ImmutableMap.Builder<>();
|
||||
for (int i = 0; i < trackSelectionOverrides.size(); i++) {
|
||||
TrackSelectionOverride trackSelectionOverride = trackSelectionOverrides.get(i);
|
||||
builder.put(trackSelectionOverride.trackGroup, trackSelectionOverride);
|
||||
}
|
||||
return new TrackSelectionOverrides(builder.build());
|
||||
};
|
||||
|
||||
private static String keyForField(@FieldNumber int field) {
|
||||
return Integer.toString(field, Character.MAX_RADIX);
|
||||
}
|
||||
}
|
@ -16,7 +16,8 @@
|
||||
package androidx.media3.common;
|
||||
|
||||
import static androidx.media3.common.util.Assertions.checkNotNull;
|
||||
import static androidx.media3.common.util.BundleableUtil.fromNullableBundle;
|
||||
import static androidx.media3.common.util.BundleableUtil.fromBundleNullableList;
|
||||
import static androidx.media3.common.util.BundleableUtil.toBundleArrayList;
|
||||
import static com.google.common.base.MoreObjects.firstNonNull;
|
||||
|
||||
import android.content.Context;
|
||||
@ -30,11 +31,15 @@ import androidx.annotation.RequiresApi;
|
||||
import androidx.media3.common.util.UnstableApi;
|
||||
import androidx.media3.common.util.Util;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.primitives.Ints;
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
import org.checkerframework.checker.initialization.qual.UnknownInitialization;
|
||||
@ -93,7 +98,7 @@ public class TrackSelectionParameters implements Bundleable {
|
||||
// General
|
||||
private boolean forceLowestBitrate;
|
||||
private boolean forceHighestSupportedBitrate;
|
||||
private TrackSelectionOverrides trackSelectionOverrides;
|
||||
private HashMap<TrackGroup, TrackSelectionOverride> overrides;
|
||||
private ImmutableSet<@C.TrackType Integer> disabledTrackTypes;
|
||||
|
||||
/**
|
||||
@ -126,7 +131,7 @@ public class TrackSelectionParameters implements Bundleable {
|
||||
// General
|
||||
forceLowestBitrate = false;
|
||||
forceHighestSupportedBitrate = false;
|
||||
trackSelectionOverrides = TrackSelectionOverrides.EMPTY;
|
||||
overrides = new HashMap<>();
|
||||
disabledTrackTypes = ImmutableSet.of();
|
||||
}
|
||||
|
||||
@ -234,11 +239,16 @@ public class TrackSelectionParameters implements Bundleable {
|
||||
bundle.getBoolean(
|
||||
keyForField(FIELD_FORCE_HIGHEST_SUPPORTED_BITRATE),
|
||||
DEFAULT_WITHOUT_CONTEXT.forceHighestSupportedBitrate);
|
||||
trackSelectionOverrides =
|
||||
fromNullableBundle(
|
||||
TrackSelectionOverrides.CREATOR,
|
||||
bundle.getBundle(keyForField(FIELD_SELECTION_OVERRIDE_KEYS)),
|
||||
TrackSelectionOverrides.EMPTY);
|
||||
overrides = new HashMap<>();
|
||||
List<TrackSelectionOverride> overrideList =
|
||||
fromBundleNullableList(
|
||||
TrackSelectionOverride.CREATOR,
|
||||
bundle.getParcelableArrayList(keyForField(FIELD_SELECTION_OVERRIDES)),
|
||||
ImmutableList.of());
|
||||
for (int i = 0; i < overrideList.size(); i++) {
|
||||
TrackSelectionOverride override = overrideList.get(i);
|
||||
overrides.put(override.trackGroup, override);
|
||||
}
|
||||
disabledTrackTypes =
|
||||
ImmutableSet.copyOf(
|
||||
Ints.asList(
|
||||
@ -252,7 +262,7 @@ public class TrackSelectionParameters implements Bundleable {
|
||||
"preferredAudioLanguages",
|
||||
"preferredAudioMimeTypes",
|
||||
"preferredTextLanguages",
|
||||
"trackSelectionOverrides",
|
||||
"overrides",
|
||||
"disabledTrackTypes",
|
||||
})
|
||||
private void init(@UnknownInitialization Builder this, TrackSelectionParameters parameters) {
|
||||
@ -283,8 +293,8 @@ public class TrackSelectionParameters implements Bundleable {
|
||||
// General
|
||||
forceLowestBitrate = parameters.forceLowestBitrate;
|
||||
forceHighestSupportedBitrate = parameters.forceHighestSupportedBitrate;
|
||||
trackSelectionOverrides = parameters.trackSelectionOverrides;
|
||||
disabledTrackTypes = parameters.disabledTrackTypes;
|
||||
overrides = new HashMap<>(parameters.overrides);
|
||||
}
|
||||
|
||||
/** Overrides the value of the builder with the value of {@link TrackSelectionParameters}. */
|
||||
@ -644,14 +654,42 @@ public class TrackSelectionParameters implements Bundleable {
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Adds an override for the provided {@link TrackGroup}. */
|
||||
public Builder addOverride(TrackSelectionOverride override) {
|
||||
overrides.put(override.trackGroup, override);
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Removes the override associated with the provided {@link TrackGroup} if present. */
|
||||
public Builder clearOverride(TrackGroup trackGroup) {
|
||||
overrides.remove(trackGroup);
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Set the override for the type of the provided {@link TrackGroup}. */
|
||||
public Builder setOverrideForType(TrackSelectionOverride override) {
|
||||
clearOverridesOfType(override.getTrackType());
|
||||
overrides.put(override.trackGroup, override);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the selection overrides.
|
||||
*
|
||||
* @param trackSelectionOverrides The track selection overrides.
|
||||
* @return This builder.
|
||||
* Remove any override associated with {@link TrackGroup TrackGroups} of type {@code trackType}.
|
||||
*/
|
||||
public Builder setTrackSelectionOverrides(TrackSelectionOverrides trackSelectionOverrides) {
|
||||
this.trackSelectionOverrides = trackSelectionOverrides;
|
||||
public Builder clearOverridesOfType(@C.TrackType int trackType) {
|
||||
Iterator<TrackSelectionOverride> it = overrides.values().iterator();
|
||||
while (it.hasNext()) {
|
||||
TrackSelectionOverride override = it.next();
|
||||
if (override.getTrackType() == trackType) {
|
||||
it.remove();
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Removes all track overrides. */
|
||||
public Builder clearOverrides() {
|
||||
overrides.clear();
|
||||
return this;
|
||||
}
|
||||
|
||||
@ -858,8 +896,9 @@ public class TrackSelectionParameters implements Bundleable {
|
||||
*/
|
||||
public final boolean forceHighestSupportedBitrate;
|
||||
|
||||
/** Overrides to force tracks to be selected. */
|
||||
public final TrackSelectionOverrides trackSelectionOverrides;
|
||||
/** Overrides to force selection of specific tracks. */
|
||||
public final ImmutableMap<TrackGroup, TrackSelectionOverride> overrides;
|
||||
|
||||
/**
|
||||
* The track types that are disabled. No track of a disabled type will be selected, thus no track
|
||||
* type contained in the set will be played. The default value is that no track type is disabled
|
||||
@ -896,7 +935,7 @@ public class TrackSelectionParameters implements Bundleable {
|
||||
// General
|
||||
this.forceLowestBitrate = builder.forceLowestBitrate;
|
||||
this.forceHighestSupportedBitrate = builder.forceHighestSupportedBitrate;
|
||||
this.trackSelectionOverrides = builder.trackSelectionOverrides;
|
||||
this.overrides = ImmutableMap.copyOf(builder.overrides);
|
||||
this.disabledTrackTypes = builder.disabledTrackTypes;
|
||||
}
|
||||
|
||||
@ -941,7 +980,7 @@ public class TrackSelectionParameters implements Bundleable {
|
||||
// General
|
||||
&& forceLowestBitrate == other.forceLowestBitrate
|
||||
&& forceHighestSupportedBitrate == other.forceHighestSupportedBitrate
|
||||
&& trackSelectionOverrides.equals(other.trackSelectionOverrides)
|
||||
&& overrides.equals(other.overrides)
|
||||
&& disabledTrackTypes.equals(other.disabledTrackTypes);
|
||||
}
|
||||
|
||||
@ -975,7 +1014,7 @@ public class TrackSelectionParameters implements Bundleable {
|
||||
// General
|
||||
result = 31 * result + (forceLowestBitrate ? 1 : 0);
|
||||
result = 31 * result + (forceHighestSupportedBitrate ? 1 : 0);
|
||||
result = 31 * result + trackSelectionOverrides.hashCode();
|
||||
result = 31 * result + overrides.hashCode();
|
||||
result = 31 * result + disabledTrackTypes.hashCode();
|
||||
return result;
|
||||
}
|
||||
@ -1007,8 +1046,7 @@ public class TrackSelectionParameters implements Bundleable {
|
||||
FIELD_PREFERRED_AUDIO_MIME_TYPES,
|
||||
FIELD_FORCE_LOWEST_BITRATE,
|
||||
FIELD_FORCE_HIGHEST_SUPPORTED_BITRATE,
|
||||
FIELD_SELECTION_OVERRIDE_KEYS,
|
||||
FIELD_SELECTION_OVERRIDE_VALUES,
|
||||
FIELD_SELECTION_OVERRIDES,
|
||||
FIELD_DISABLED_TRACK_TYPE,
|
||||
FIELD_PREFERRED_VIDEO_ROLE_FLAGS
|
||||
})
|
||||
@ -1036,10 +1074,9 @@ public class TrackSelectionParameters implements Bundleable {
|
||||
private static final int FIELD_PREFERRED_AUDIO_MIME_TYPES = 20;
|
||||
private static final int FIELD_FORCE_LOWEST_BITRATE = 21;
|
||||
private static final int FIELD_FORCE_HIGHEST_SUPPORTED_BITRATE = 22;
|
||||
private static final int FIELD_SELECTION_OVERRIDE_KEYS = 23;
|
||||
private static final int FIELD_SELECTION_OVERRIDE_VALUES = 24;
|
||||
private static final int FIELD_DISABLED_TRACK_TYPE = 25;
|
||||
private static final int FIELD_PREFERRED_VIDEO_ROLE_FLAGS = 26;
|
||||
private static final int FIELD_SELECTION_OVERRIDES = 23;
|
||||
private static final int FIELD_DISABLED_TRACK_TYPE = 24;
|
||||
private static final int FIELD_PREFERRED_VIDEO_ROLE_FLAGS = 25;
|
||||
|
||||
@UnstableApi
|
||||
@Override
|
||||
@ -1083,8 +1120,8 @@ public class TrackSelectionParameters implements Bundleable {
|
||||
bundle.putBoolean(keyForField(FIELD_FORCE_LOWEST_BITRATE), forceLowestBitrate);
|
||||
bundle.putBoolean(
|
||||
keyForField(FIELD_FORCE_HIGHEST_SUPPORTED_BITRATE), forceHighestSupportedBitrate);
|
||||
bundle.putBundle(
|
||||
keyForField(FIELD_SELECTION_OVERRIDE_KEYS), trackSelectionOverrides.toBundle());
|
||||
bundle.putParcelableArrayList(
|
||||
keyForField(FIELD_SELECTION_OVERRIDES), toBundleArrayList(overrides.values()));
|
||||
bundle.putIntArray(keyForField(FIELD_DISABLED_TRACK_TYPE), Ints.toArray(disabledTrackTypes));
|
||||
|
||||
return bundle;
|
||||
|
@ -20,6 +20,7 @@ import static androidx.media3.common.util.Assertions.checkNotNull;
|
||||
import static androidx.media3.common.util.BundleableUtil.fromBundleNullableList;
|
||||
import static androidx.media3.common.util.BundleableUtil.fromNullableBundle;
|
||||
import static androidx.media3.common.util.BundleableUtil.toBundleArrayList;
|
||||
import static java.lang.annotation.ElementType.TYPE_USE;
|
||||
|
||||
import android.os.Bundle;
|
||||
import androidx.annotation.IntDef;
|
||||
@ -31,79 +32,135 @@ import com.google.common.primitives.Booleans;
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/** Immutable information ({@link TrackGroupInfo}) about tracks. */
|
||||
/** Information about groups of tracks. */
|
||||
public final class TracksInfo implements Bundleable {
|
||||
|
||||
/**
|
||||
* Information about tracks in a {@link TrackGroup}: their {@link C.TrackType}, if their format is
|
||||
* supported by the player and if they are selected for playback.
|
||||
* Information about a single group of tracks, including the underlying {@link TrackGroup}, the
|
||||
* {@link C.TrackType type} of tracks it contains, and the level to which each track is supported
|
||||
* by the player.
|
||||
*/
|
||||
public static final class TrackGroupInfo implements Bundleable {
|
||||
|
||||
/** The number of tracks in the group. */
|
||||
public final int length;
|
||||
|
||||
private final TrackGroup trackGroup;
|
||||
@C.FormatSupport private final int[] trackSupport;
|
||||
private final @C.TrackType int trackType;
|
||||
private final boolean adaptiveSupported;
|
||||
private final @C.FormatSupport int[] trackSupport;
|
||||
private final boolean[] trackSelected;
|
||||
|
||||
/**
|
||||
* Constructs a TrackGroupInfo.
|
||||
*
|
||||
* @param trackGroup The {@link TrackGroup} described.
|
||||
* @param adaptiveSupported Whether adaptive selections containing more than one track in the
|
||||
* {@code trackGroup} are supported.
|
||||
* @param trackSupport The {@link C.FormatSupport} of each track in the {@code trackGroup}.
|
||||
* @param trackType The {@link C.TrackType} of the tracks in the {@code trackGroup}.
|
||||
* @param tracksSelected Whether a track is selected for each track in {@code trackGroup}.
|
||||
* @param tracksSelected Whether each track in the {@code trackGroup} is selected.
|
||||
*/
|
||||
@UnstableApi
|
||||
public TrackGroupInfo(
|
||||
TrackGroup trackGroup,
|
||||
boolean adaptiveSupported,
|
||||
@C.FormatSupport int[] trackSupport,
|
||||
@C.TrackType int trackType,
|
||||
boolean[] tracksSelected) {
|
||||
int length = trackGroup.length;
|
||||
length = trackGroup.length;
|
||||
checkArgument(length == trackSupport.length && length == tracksSelected.length);
|
||||
this.trackGroup = trackGroup;
|
||||
this.adaptiveSupported = adaptiveSupported && length > 1;
|
||||
this.trackSupport = trackSupport.clone();
|
||||
this.trackType = trackType;
|
||||
this.trackSelected = tracksSelected.clone();
|
||||
}
|
||||
|
||||
/** Returns the {@link TrackGroup} described by this {@code TrackGroupInfo}. */
|
||||
/** Returns the underlying {@link TrackGroup}. */
|
||||
public TrackGroup getTrackGroup() {
|
||||
return trackGroup;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the level of support for a track in a {@link TrackGroup}.
|
||||
* Returns the {@link Format} for a specified track.
|
||||
*
|
||||
* @param trackIndex The index of the track in the {@link TrackGroup}.
|
||||
* @return The {@link Format} of the track.
|
||||
*/
|
||||
public Format getTrackFormat(int trackIndex) {
|
||||
return trackGroup.getFormat(trackIndex);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the level of support for a specified track.
|
||||
*
|
||||
* @param trackIndex The index of the track in the {@link TrackGroup}.
|
||||
* @return The {@link C.FormatSupport} of the track.
|
||||
*/
|
||||
@UnstableApi
|
||||
@C.FormatSupport
|
||||
public int getTrackSupport(int trackIndex) {
|
||||
public @C.FormatSupport int getTrackSupport(int trackIndex) {
|
||||
return trackSupport[trackIndex];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns if a track in a {@link TrackGroup} is supported for playback.
|
||||
* Returns whether a specified track is supported for playback, without exceeding the advertised
|
||||
* capabilities of the device. Equivalent to {@code isTrackSupported(trackIndex, false)}.
|
||||
*
|
||||
* @param trackIndex The index of the track in the {@link TrackGroup}.
|
||||
* @return True if the track's format can be played, false otherwise.
|
||||
*/
|
||||
public boolean isTrackSupported(int trackIndex) {
|
||||
return trackSupport[trackIndex] == C.FORMAT_HANDLED;
|
||||
return isTrackSupported(trackIndex, /* allowExceedsCapabilities= */ false);
|
||||
}
|
||||
|
||||
/** Returns if at least one track in a {@link TrackGroup} is selected for playback. */
|
||||
/**
|
||||
* Returns whether a specified track is supported for playback.
|
||||
*
|
||||
* @param trackIndex The index of the track in the {@link TrackGroup}.
|
||||
* @param allowExceedsCapabilities Whether to consider the track as supported if it has a
|
||||
* supported {@link Format#sampleMimeType MIME type}, but otherwise exceeds the advertised
|
||||
* capabilities of the device. For example, a video track for which there's a corresponding
|
||||
* decoder whose maximum advertised resolution is exceeded by the resolution of the track.
|
||||
* Such tracks may be playable in some cases.
|
||||
* @return True if the track's format can be played, false otherwise.
|
||||
*/
|
||||
public boolean isTrackSupported(int trackIndex, boolean allowExceedsCapabilities) {
|
||||
return trackSupport[trackIndex] == C.FORMAT_HANDLED
|
||||
|| (allowExceedsCapabilities
|
||||
&& trackSupport[trackIndex] == C.FORMAT_EXCEEDS_CAPABILITIES);
|
||||
}
|
||||
|
||||
/** Returns whether at least one track in the group is selected for playback. */
|
||||
public boolean isSelected() {
|
||||
return Booleans.contains(trackSelected, true);
|
||||
}
|
||||
|
||||
/** Returns if at least one track in a {@link TrackGroup} is supported. */
|
||||
/** Returns whether adaptive selections containing more than one track are supported. */
|
||||
public boolean isAdaptiveSupported() {
|
||||
return adaptiveSupported;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether at least one track in the group is supported for playback, without exceeding
|
||||
* the advertised capabilities of the device. Equivalent to {@code isSupported(false)}.
|
||||
*/
|
||||
public boolean isSupported() {
|
||||
return isSupported(/* allowExceedsCapabilities= */ false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether at least one track in the group is supported for playback.
|
||||
*
|
||||
* @param allowExceedsCapabilities Whether to consider a track as supported if it has a
|
||||
* supported {@link Format#sampleMimeType MIME type}, but otherwise exceeds the advertised
|
||||
* capabilities of the device. For example, a video track for which there's a corresponding
|
||||
* decoder whose maximum advertised resolution is exceeded by the resolution of the track.
|
||||
* Such tracks may be playable in some cases.
|
||||
*/
|
||||
public boolean isSupported(boolean allowExceedsCapabilities) {
|
||||
for (int i = 0; i < trackSupport.length; i++) {
|
||||
if (isTrackSupported(i)) {
|
||||
if (isTrackSupported(i, allowExceedsCapabilities)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@ -111,29 +168,26 @@ public final class TracksInfo implements Bundleable {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns if a track in a {@link TrackGroup} is selected for playback.
|
||||
* Returns whether a specified track is selected for playback.
|
||||
*
|
||||
* <p>Multiple tracks of a track group may be selected. This is common in adaptive streaming,
|
||||
* where multiple tracks of different quality are selected and the player switches between them
|
||||
* depending on the network and the {@link TrackSelectionParameters}.
|
||||
* <p>Note that multiple tracks in the group may be selected. This is common in adaptive
|
||||
* streaming, where tracks of different qualities are selected and the player switches between
|
||||
* them during playback (e.g., based on the available network bandwidth).
|
||||
*
|
||||
* <p>While this class doesn't provide which selected track is currently playing, some player
|
||||
* implementations have ways of getting such information. For example ExoPlayer provides this
|
||||
* information in {@code ExoTrackSelection.getSelectedFormat}.
|
||||
* <p>This class doesn't provide a way to determine which of the selected tracks is currently
|
||||
* playing, however some player implementations have ways of getting such information. For
|
||||
* example, ExoPlayer provides this information via {@code ExoTrackSelection.getSelectedFormat}.
|
||||
*
|
||||
* @param trackIndex The index of the track in the {@link TrackGroup}.
|
||||
* @return true if the track is selected, false otherwise.
|
||||
* @return True if the track is selected, false otherwise.
|
||||
*/
|
||||
public boolean isTrackSelected(int trackIndex) {
|
||||
return trackSelected[trackIndex];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link C.TrackType} of the tracks in the {@link TrackGroup}. Tracks in a group
|
||||
* are all of the same type.
|
||||
*/
|
||||
/** Returns the {@link C.TrackType} of the group. */
|
||||
public @C.TrackType int getTrackType() {
|
||||
return trackType;
|
||||
return trackGroup.type;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -145,7 +199,7 @@ public final class TracksInfo implements Bundleable {
|
||||
return false;
|
||||
}
|
||||
TrackGroupInfo that = (TrackGroupInfo) other;
|
||||
return trackType == that.trackType
|
||||
return adaptiveSupported == that.adaptiveSupported
|
||||
&& trackGroup.equals(that.trackGroup)
|
||||
&& Arrays.equals(trackSupport, that.trackSupport)
|
||||
&& Arrays.equals(trackSelected, that.trackSelected);
|
||||
@ -154,8 +208,8 @@ public final class TracksInfo implements Bundleable {
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = trackGroup.hashCode();
|
||||
result = 31 * result + (adaptiveSupported ? 1 : 0);
|
||||
result = 31 * result + Arrays.hashCode(trackSupport);
|
||||
result = 31 * result + trackType;
|
||||
result = 31 * result + Arrays.hashCode(trackSelected);
|
||||
return result;
|
||||
}
|
||||
@ -163,26 +217,27 @@ public final class TracksInfo implements Bundleable {
|
||||
// Bundleable implementation.
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@Target(TYPE_USE)
|
||||
@IntDef({
|
||||
FIELD_TRACK_GROUP,
|
||||
FIELD_TRACK_SUPPORT,
|
||||
FIELD_TRACK_TYPE,
|
||||
FIELD_TRACK_SELECTED,
|
||||
FIELD_ADAPTIVE_SUPPORTED,
|
||||
})
|
||||
private @interface FieldNumber {}
|
||||
|
||||
private static final int FIELD_TRACK_GROUP = 0;
|
||||
private static final int FIELD_TRACK_SUPPORT = 1;
|
||||
private static final int FIELD_TRACK_TYPE = 2;
|
||||
private static final int FIELD_TRACK_SELECTED = 3;
|
||||
private static final int FIELD_ADAPTIVE_SUPPORTED = 4;
|
||||
|
||||
@Override
|
||||
public Bundle toBundle() {
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putBundle(keyForField(FIELD_TRACK_GROUP), trackGroup.toBundle());
|
||||
bundle.putIntArray(keyForField(FIELD_TRACK_SUPPORT), trackSupport);
|
||||
bundle.putInt(keyForField(FIELD_TRACK_TYPE), trackType);
|
||||
bundle.putBooleanArray(keyForField(FIELD_TRACK_SELECTED), trackSelected);
|
||||
bundle.putBoolean(keyForField(FIELD_ADAPTIVE_SUPPORTED), adaptiveSupported);
|
||||
return bundle;
|
||||
}
|
||||
|
||||
@ -194,17 +249,16 @@ public final class TracksInfo implements Bundleable {
|
||||
fromNullableBundle(
|
||||
TrackGroup.CREATOR, bundle.getBundle(keyForField(FIELD_TRACK_GROUP)));
|
||||
checkNotNull(trackGroup); // Can't create a trackGroup info without a trackGroup
|
||||
@C.FormatSupport
|
||||
final int[] trackSupport =
|
||||
final @C.FormatSupport int[] trackSupport =
|
||||
MoreObjects.firstNonNull(
|
||||
bundle.getIntArray(keyForField(FIELD_TRACK_SUPPORT)), new int[trackGroup.length]);
|
||||
@C.TrackType
|
||||
int trackType = bundle.getInt(keyForField(FIELD_TRACK_TYPE), C.TRACK_TYPE_UNKNOWN);
|
||||
boolean[] selected =
|
||||
MoreObjects.firstNonNull(
|
||||
bundle.getBooleanArray(keyForField(FIELD_TRACK_SELECTED)),
|
||||
new boolean[trackGroup.length]);
|
||||
return new TrackGroupInfo(trackGroup, trackSupport, trackType, selected);
|
||||
boolean adaptiveSupported =
|
||||
bundle.getBoolean(keyForField(FIELD_ADAPTIVE_SUPPORTED), false);
|
||||
return new TrackGroupInfo(trackGroup, adaptiveSupported, trackSupport, selected);
|
||||
};
|
||||
|
||||
private static String keyForField(@FieldNumber int field) {
|
||||
@ -214,36 +268,85 @@ public final class TracksInfo implements Bundleable {
|
||||
|
||||
private final ImmutableList<TrackGroupInfo> trackGroupInfos;
|
||||
|
||||
/** An empty {@code TrackInfo} containing no {@link TrackGroupInfo}. */
|
||||
/** An {@code TrackInfo} that contains no tracks. */
|
||||
@UnstableApi public static final TracksInfo EMPTY = new TracksInfo(ImmutableList.of());
|
||||
|
||||
/** Constructs {@code TracksInfo} from the provided {@link TrackGroupInfo}. */
|
||||
/**
|
||||
* Constructs an instance.
|
||||
*
|
||||
* @param trackGroupInfos The {@link TrackGroupInfo TrackGroupInfos} describing the groups of
|
||||
* tracks.
|
||||
*/
|
||||
@UnstableApi
|
||||
public TracksInfo(List<TrackGroupInfo> trackGroupInfos) {
|
||||
this.trackGroupInfos = ImmutableList.copyOf(trackGroupInfos);
|
||||
}
|
||||
|
||||
/** Returns the {@link TrackGroupInfo TrackGroupInfos}, describing each {@link TrackGroup}. */
|
||||
/** Returns the {@link TrackGroupInfo TrackGroupInfos} describing the groups of tracks. */
|
||||
public ImmutableList<TrackGroupInfo> getTrackGroupInfos() {
|
||||
return trackGroupInfos;
|
||||
}
|
||||
|
||||
/** Returns if there is at least one track of type {@code trackType} but none are supported. */
|
||||
public boolean isTypeSupportedOrEmpty(@C.TrackType int trackType) {
|
||||
boolean supported = true;
|
||||
/** Returns true if there are tracks of type {@code trackType}, and false otherwise. */
|
||||
public boolean containsType(@C.TrackType int trackType) {
|
||||
for (int i = 0; i < trackGroupInfos.size(); i++) {
|
||||
if (trackGroupInfos.get(i).trackType == trackType) {
|
||||
if (trackGroupInfos.get(i).isSupported()) {
|
||||
if (trackGroupInfos.get(i).getTrackType() == trackType) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if at least one track of type {@code trackType} is {@link
|
||||
* TrackGroupInfo#isTrackSupported(int) supported}.
|
||||
*/
|
||||
public boolean isTypeSupported(@C.TrackType int trackType) {
|
||||
return isTypeSupported(trackType, /* allowExceedsCapabilities= */ false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if at least one track of type {@code trackType} is {@link
|
||||
* TrackGroupInfo#isTrackSupported(int, boolean) supported}.
|
||||
*
|
||||
* @param allowExceedsCapabilities Whether to consider the track as supported if it has a
|
||||
* supported {@link Format#sampleMimeType MIME type}, but otherwise exceeds the advertised
|
||||
* capabilities of the device. For example, a video track for which there's a corresponding
|
||||
* decoder whose maximum advertised resolution is exceeded by the resolution of the track.
|
||||
* Such tracks may be playable in some cases.
|
||||
*/
|
||||
public boolean isTypeSupported(@C.TrackType int trackType, boolean allowExceedsCapabilities) {
|
||||
for (int i = 0; i < trackGroupInfos.size(); i++) {
|
||||
if (trackGroupInfos.get(i).getTrackType() == trackType) {
|
||||
if (trackGroupInfos.get(i).isSupported(allowExceedsCapabilities)) {
|
||||
return true;
|
||||
} else {
|
||||
supported = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return supported;
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Returns if at least one track of the type {@code trackType} is selected for playback. */
|
||||
/**
|
||||
* @deprecated Use {@link #containsType(int)} and {@link #isTypeSupported(int)}.
|
||||
*/
|
||||
@Deprecated
|
||||
@UnstableApi
|
||||
@SuppressWarnings("deprecation")
|
||||
public boolean isTypeSupportedOrEmpty(@C.TrackType int trackType) {
|
||||
return isTypeSupportedOrEmpty(trackType, /* allowExceedsCapabilities= */ false);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link #containsType(int)} and {@link #isTypeSupported(int, boolean)}.
|
||||
*/
|
||||
@Deprecated
|
||||
@UnstableApi
|
||||
public boolean isTypeSupportedOrEmpty(
|
||||
@C.TrackType int trackType, boolean allowExceedsCapabilities) {
|
||||
return !containsType(trackType) || isTypeSupported(trackType, allowExceedsCapabilities);
|
||||
}
|
||||
|
||||
/** Returns true if at least one track of the type {@code trackType} is selected for playback. */
|
||||
public boolean isTypeSelected(@C.TrackType int trackType) {
|
||||
for (int i = 0; i < trackGroupInfos.size(); i++) {
|
||||
TrackGroupInfo trackGroupInfo = trackGroupInfos.get(i);
|
||||
@ -274,6 +377,7 @@ public final class TracksInfo implements Bundleable {
|
||||
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@Target(TYPE_USE)
|
||||
@IntDef({
|
||||
FIELD_TRACK_GROUP_INFOS,
|
||||
})
|
||||
|
@ -15,6 +15,8 @@
|
||||
*/
|
||||
package androidx.media3.common;
|
||||
|
||||
import static java.lang.annotation.ElementType.TYPE_USE;
|
||||
|
||||
import android.os.Bundle;
|
||||
import androidx.annotation.FloatRange;
|
||||
import androidx.annotation.IntDef;
|
||||
@ -24,6 +26,7 @@ import androidx.media3.common.util.UnstableApi;
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/** Represents the video size. */
|
||||
public final class VideoSize implements Bundleable {
|
||||
@ -131,6 +134,7 @@ public final class VideoSize implements Bundleable {
|
||||
// Bundleable implementation.
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@Target(TYPE_USE)
|
||||
@IntDef({
|
||||
FIELD_WIDTH,
|
||||
FIELD_HEIGHT,
|
||||
|
@ -59,6 +59,8 @@ public final class Cue implements Bundleable {
|
||||
* The type of anchor, which may be unset. One of {@link #TYPE_UNSET}, {@link #ANCHOR_TYPE_START},
|
||||
* {@link #ANCHOR_TYPE_MIDDLE} or {@link #ANCHOR_TYPE_END}.
|
||||
*/
|
||||
// @Target list includes both 'default' targets and TYPE_USE, to ensure backwards compatibility
|
||||
// with Kotlin usages from before TYPE_USE was added.
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@Target({FIELD, METHOD, PARAMETER, LOCAL_VARIABLE, TYPE_USE})
|
||||
@ -87,6 +89,8 @@ public final class Cue implements Bundleable {
|
||||
* The type of line, which may be unset. One of {@link #TYPE_UNSET}, {@link #LINE_TYPE_FRACTION}
|
||||
* or {@link #LINE_TYPE_NUMBER}.
|
||||
*/
|
||||
// @Target list includes both 'default' targets and TYPE_USE, to ensure backwards compatibility
|
||||
// with Kotlin usages from before TYPE_USE was added.
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@Target({FIELD, METHOD, PARAMETER, LOCAL_VARIABLE, TYPE_USE})
|
||||
@ -104,6 +108,8 @@ public final class Cue implements Bundleable {
|
||||
* {@link #TEXT_SIZE_TYPE_FRACTIONAL}, {@link #TEXT_SIZE_TYPE_FRACTIONAL_IGNORE_PADDING} or {@link
|
||||
* #TEXT_SIZE_TYPE_ABSOLUTE}.
|
||||
*/
|
||||
// @Target list includes both 'default' targets and TYPE_USE, to ensure backwards compatibility
|
||||
// with Kotlin usages from before TYPE_USE was added.
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@Target({FIELD, METHOD, PARAMETER, LOCAL_VARIABLE, TYPE_USE})
|
||||
@ -128,6 +134,8 @@ public final class Cue implements Bundleable {
|
||||
* The type of vertical layout for this cue, which may be unset (i.e. horizontal). One of {@link
|
||||
* #TYPE_UNSET}, {@link #VERTICAL_TYPE_RL} or {@link #VERTICAL_TYPE_LR}.
|
||||
*/
|
||||
// @Target list includes both 'default' targets and TYPE_USE, to ensure backwards compatibility
|
||||
// with Kotlin usages from before TYPE_USE was added.
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@Target({FIELD, METHOD, PARAMETER, LOCAL_VARIABLE, TYPE_USE})
|
||||
@ -561,7 +569,7 @@ public final class Cue implements Bundleable {
|
||||
@Nullable private Alignment textAlignment;
|
||||
@Nullable private Alignment multiRowAlignment;
|
||||
private float line;
|
||||
@LineType private int lineType;
|
||||
private @LineType int lineType;
|
||||
private @AnchorType int lineAnchor;
|
||||
private float position;
|
||||
private @AnchorType int positionAnchor;
|
||||
@ -722,8 +730,7 @@ public final class Cue implements Bundleable {
|
||||
* @see Cue#lineType
|
||||
*/
|
||||
@Pure
|
||||
@LineType
|
||||
public int getLineType() {
|
||||
public @LineType int getLineType() {
|
||||
return lineType;
|
||||
}
|
||||
|
||||
@ -743,8 +750,7 @@ public final class Cue implements Bundleable {
|
||||
* @see Cue#lineAnchor
|
||||
*/
|
||||
@Pure
|
||||
@AnchorType
|
||||
public int getLineAnchor() {
|
||||
public @AnchorType int getLineAnchor() {
|
||||
return lineAnchor;
|
||||
}
|
||||
|
||||
@ -786,8 +792,7 @@ public final class Cue implements Bundleable {
|
||||
* @see Cue#positionAnchor
|
||||
*/
|
||||
@Pure
|
||||
@AnchorType
|
||||
public int getPositionAnchor() {
|
||||
public @AnchorType int getPositionAnchor() {
|
||||
return positionAnchor;
|
||||
}
|
||||
|
||||
@ -809,8 +814,7 @@ public final class Cue implements Bundleable {
|
||||
* @see Cue#textSizeType
|
||||
*/
|
||||
@Pure
|
||||
@TextSizeType
|
||||
public int getTextSizeType() {
|
||||
public @TextSizeType int getTextSizeType() {
|
||||
return textSizeType;
|
||||
}
|
||||
|
||||
@ -928,8 +932,7 @@ public final class Cue implements Bundleable {
|
||||
* @see Cue#verticalType
|
||||
*/
|
||||
@Pure
|
||||
@VerticalType
|
||||
public int getVerticalType() {
|
||||
public @VerticalType int getVerticalType() {
|
||||
return verticalType;
|
||||
}
|
||||
|
||||
@ -960,6 +963,7 @@ public final class Cue implements Bundleable {
|
||||
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@Target(TYPE_USE)
|
||||
@IntDef({
|
||||
FIELD_TEXT,
|
||||
FIELD_TEXT_ALIGNMENT,
|
||||
|
@ -39,7 +39,7 @@ public final class RubySpan implements LanguageFeatureSpan {
|
||||
public final String rubyText;
|
||||
|
||||
/** The position of the ruby text relative to the base text. */
|
||||
@TextAnnotation.Position public final int position;
|
||||
public final @TextAnnotation.Position int position;
|
||||
|
||||
public RubySpan(String rubyText, @TextAnnotation.Position int position) {
|
||||
this.rubyText = rubyText;
|
||||
|
@ -15,12 +15,14 @@
|
||||
*/
|
||||
package androidx.media3.common.text;
|
||||
|
||||
import static java.lang.annotation.ElementType.TYPE_USE;
|
||||
import static java.lang.annotation.RetentionPolicy.SOURCE;
|
||||
|
||||
import androidx.annotation.IntDef;
|
||||
import androidx.media3.common.util.UnstableApi;
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/** Properties of a text annotation (i.e. ruby, text emphasis marks). */
|
||||
@UnstableApi
|
||||
@ -57,6 +59,7 @@ public final class TextAnnotation {
|
||||
*/
|
||||
@Documented
|
||||
@Retention(SOURCE)
|
||||
@Target(TYPE_USE)
|
||||
@IntDef({POSITION_UNKNOWN, POSITION_BEFORE, POSITION_AFTER})
|
||||
public @interface Position {}
|
||||
|
||||
|
@ -15,12 +15,14 @@
|
||||
*/
|
||||
package androidx.media3.common.text;
|
||||
|
||||
import static java.lang.annotation.ElementType.TYPE_USE;
|
||||
import static java.lang.annotation.RetentionPolicy.SOURCE;
|
||||
|
||||
import androidx.annotation.IntDef;
|
||||
import androidx.media3.common.util.UnstableApi;
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* A styling span for text emphasis marks.
|
||||
@ -50,6 +52,7 @@ public final class TextEmphasisSpan implements LanguageFeatureSpan {
|
||||
*/
|
||||
@Documented
|
||||
@Retention(SOURCE)
|
||||
@Target(TYPE_USE)
|
||||
@IntDef({MARK_SHAPE_NONE, MARK_SHAPE_CIRCLE, MARK_SHAPE_DOT, MARK_SHAPE_SESAME})
|
||||
public @interface MarkShape {}
|
||||
|
||||
@ -71,6 +74,7 @@ public final class TextEmphasisSpan implements LanguageFeatureSpan {
|
||||
*/
|
||||
@Documented
|
||||
@Retention(SOURCE)
|
||||
@Target(TYPE_USE)
|
||||
@IntDef({MARK_FILL_UNKNOWN, MARK_FILL_FILLED, MARK_FILL_OPEN})
|
||||
public @interface MarkFill {}
|
||||
|
||||
@ -79,13 +83,13 @@ public final class TextEmphasisSpan implements LanguageFeatureSpan {
|
||||
public static final int MARK_FILL_OPEN = 2;
|
||||
|
||||
/** The mark shape used for text emphasis. */
|
||||
@MarkShape public int markShape;
|
||||
public @MarkShape int markShape;
|
||||
|
||||
/** The mark fill for the text emphasis mark. */
|
||||
@MarkShape public int markFill;
|
||||
public @MarkShape int markFill;
|
||||
|
||||
/** The position of the text emphasis relative to the base text. */
|
||||
@TextAnnotation.Position public final int position;
|
||||
public final @TextAnnotation.Position int position;
|
||||
|
||||
public TextEmphasisSpan(
|
||||
@MarkShape int shape, @MarkFill int fill, @TextAnnotation.Position int position) {
|
||||
|
@ -36,10 +36,14 @@ public interface Clock {
|
||||
*/
|
||||
long currentTimeMillis();
|
||||
|
||||
/** @see android.os.SystemClock#elapsedRealtime() */
|
||||
/**
|
||||
* @see android.os.SystemClock#elapsedRealtime()
|
||||
*/
|
||||
long elapsedRealtime();
|
||||
|
||||
/** @see android.os.SystemClock#uptimeMillis() */
|
||||
/**
|
||||
* @see android.os.SystemClock#uptimeMillis()
|
||||
*/
|
||||
long uptimeMillis();
|
||||
|
||||
/**
|
||||
|
@ -15,6 +15,8 @@
|
||||
*/
|
||||
package androidx.media3.common.util;
|
||||
|
||||
import static java.lang.annotation.ElementType.TYPE_USE;
|
||||
|
||||
import android.graphics.SurfaceTexture;
|
||||
import android.opengl.EGL14;
|
||||
import android.opengl.EGLConfig;
|
||||
@ -29,6 +31,7 @@ import androidx.annotation.RequiresApi;
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/** Generates a {@link SurfaceTexture} using EGL/GLES functions. */
|
||||
@RequiresApi(17)
|
||||
@ -47,6 +50,7 @@ public final class EGLSurfaceTexture implements SurfaceTexture.OnFrameAvailableL
|
||||
*/
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@Target(TYPE_USE)
|
||||
@IntDef({SECURE_MODE_NONE, SECURE_MODE_SURFACELESS_CONTEXT, SECURE_MODE_PROTECTED_PBUFFER})
|
||||
public @interface SecureMode {}
|
||||
|
||||
|
@ -0,0 +1,403 @@
|
||||
/*
|
||||
* 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.checkNotNull;
|
||||
|
||||
import android.content.Context;
|
||||
import android.opengl.GLES11Ext;
|
||||
import android.opengl.GLES20;
|
||||
import androidx.annotation.Nullable;
|
||||
import java.io.IOException;
|
||||
import java.nio.Buffer;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Represents a GLSL shader program.
|
||||
*
|
||||
* <p>After constructing a program, keep a reference for its lifetime and call {@link #delete()} (or
|
||||
* release the current GL context) when it's no longer needed.
|
||||
*/
|
||||
@UnstableApi
|
||||
public final class GlProgram {
|
||||
|
||||
// https://www.khronos.org/registry/OpenGL/extensions/EXT/EXT_YUV_target.txt
|
||||
private static final int GL_SAMPLER_EXTERNAL_2D_Y2Y_EXT = 0x8BE7;
|
||||
/** The identifier of a compiled and linked GLSL shader program. */
|
||||
private final int programId;
|
||||
|
||||
private final Attribute[] attributes;
|
||||
private final Uniform[] uniforms;
|
||||
private final Map<String, Attribute> attributeByName;
|
||||
private final Map<String, Uniform> uniformByName;
|
||||
|
||||
/**
|
||||
* Compiles a GL shader program from vertex and fragment shader GLSL GLES20 code.
|
||||
*
|
||||
* @param context The {@link Context}.
|
||||
* @param vertexShaderFilePath The path to a vertex shader program.
|
||||
* @param fragmentShaderFilePath The path to a fragment shader program.
|
||||
* @throws IOException When failing to read shader files.
|
||||
*/
|
||||
public GlProgram(Context context, String vertexShaderFilePath, String fragmentShaderFilePath)
|
||||
throws IOException {
|
||||
this(
|
||||
GlUtil.loadAsset(context, vertexShaderFilePath),
|
||||
GlUtil.loadAsset(context, fragmentShaderFilePath));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a GL shader program from vertex and fragment shader GLSL GLES20 code.
|
||||
*
|
||||
* <p>This involves slow steps, like compiling, linking, and switching the GL program, so do not
|
||||
* call this in fast rendering loops.
|
||||
*
|
||||
* @param vertexShaderGlsl The vertex shader program.
|
||||
* @param fragmentShaderGlsl The fragment shader program.
|
||||
*/
|
||||
public GlProgram(String vertexShaderGlsl, String fragmentShaderGlsl) {
|
||||
programId = GLES20.glCreateProgram();
|
||||
GlUtil.checkGlError();
|
||||
|
||||
// Add the vertex and fragment shaders.
|
||||
addShader(programId, GLES20.GL_VERTEX_SHADER, vertexShaderGlsl);
|
||||
addShader(programId, GLES20.GL_FRAGMENT_SHADER, fragmentShaderGlsl);
|
||||
|
||||
// Link and use the program, and enumerate attributes/uniforms.
|
||||
GLES20.glLinkProgram(programId);
|
||||
int[] linkStatus = new int[] {GLES20.GL_FALSE};
|
||||
GLES20.glGetProgramiv(programId, GLES20.GL_LINK_STATUS, linkStatus, /* offset= */ 0);
|
||||
if (linkStatus[0] != GLES20.GL_TRUE) {
|
||||
GlUtil.throwGlException(
|
||||
"Unable to link shader program: \n" + GLES20.glGetProgramInfoLog(programId));
|
||||
}
|
||||
GLES20.glUseProgram(programId);
|
||||
attributeByName = new HashMap<>();
|
||||
int[] attributeCount = new int[1];
|
||||
GLES20.glGetProgramiv(programId, GLES20.GL_ACTIVE_ATTRIBUTES, attributeCount, /* offset= */ 0);
|
||||
attributes = new Attribute[attributeCount[0]];
|
||||
for (int i = 0; i < attributeCount[0]; i++) {
|
||||
Attribute attribute = Attribute.create(programId, i);
|
||||
attributes[i] = attribute;
|
||||
attributeByName.put(attribute.name, attribute);
|
||||
}
|
||||
uniformByName = new HashMap<>();
|
||||
int[] uniformCount = new int[1];
|
||||
GLES20.glGetProgramiv(programId, GLES20.GL_ACTIVE_UNIFORMS, uniformCount, /* offset= */ 0);
|
||||
uniforms = new Uniform[uniformCount[0]];
|
||||
for (int i = 0; i < uniformCount[0]; i++) {
|
||||
Uniform uniform = Uniform.create(programId, i);
|
||||
uniforms[i] = uniform;
|
||||
uniformByName.put(uniform.name, uniform);
|
||||
}
|
||||
GlUtil.checkGlError();
|
||||
}
|
||||
|
||||
private static void addShader(int programId, int type, String glsl) {
|
||||
int shader = GLES20.glCreateShader(type);
|
||||
GLES20.glShaderSource(shader, glsl);
|
||||
GLES20.glCompileShader(shader);
|
||||
|
||||
int[] result = new int[] {GLES20.GL_FALSE};
|
||||
GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, result, /* offset= */ 0);
|
||||
if (result[0] != GLES20.GL_TRUE) {
|
||||
GlUtil.throwGlException(GLES20.glGetShaderInfoLog(shader) + ", source: " + glsl);
|
||||
}
|
||||
|
||||
GLES20.glAttachShader(programId, shader);
|
||||
GLES20.glDeleteShader(shader);
|
||||
GlUtil.checkGlError();
|
||||
}
|
||||
|
||||
private static int getAttributeLocation(int programId, String attributeName) {
|
||||
return GLES20.glGetAttribLocation(programId, attributeName);
|
||||
}
|
||||
|
||||
/** Returns the location of an {@link Attribute}. */
|
||||
private int getAttributeLocation(String attributeName) {
|
||||
return getAttributeLocation(programId, attributeName);
|
||||
}
|
||||
|
||||
private static int getUniformLocation(int programId, String uniformName) {
|
||||
return GLES20.glGetUniformLocation(programId, uniformName);
|
||||
}
|
||||
|
||||
/** Returns the location of a {@link Uniform}. */
|
||||
public int getUniformLocation(String uniformName) {
|
||||
return getUniformLocation(programId, uniformName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Uses the program.
|
||||
*
|
||||
* <p>Call this in the rendering loop to switch between different programs.
|
||||
*/
|
||||
public void use() {
|
||||
// TODO(b/214975934): When multiple GL programs are supported by Transformer, make sure
|
||||
// to call use() to switch between programs.
|
||||
GLES20.glUseProgram(programId);
|
||||
GlUtil.checkGlError();
|
||||
}
|
||||
|
||||
/** Deletes the program. Deleted programs cannot be used again. */
|
||||
public void delete() {
|
||||
GLES20.glDeleteProgram(programId);
|
||||
GlUtil.checkGlError();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the location of an {@link Attribute}, which has been enabled as a vertex attribute
|
||||
* array.
|
||||
*/
|
||||
public int getAttributeArrayLocationAndEnable(String attributeName) {
|
||||
int location = getAttributeLocation(attributeName);
|
||||
GLES20.glEnableVertexAttribArray(location);
|
||||
GlUtil.checkGlError();
|
||||
return location;
|
||||
}
|
||||
|
||||
/** Sets a float buffer type attribute. */
|
||||
public void setBufferAttribute(String name, float[] values, int size) {
|
||||
checkNotNull(attributeByName.get(name)).setBuffer(values, size);
|
||||
}
|
||||
|
||||
/** Sets a texture sampler type uniform. */
|
||||
public void setSamplerTexIdUniform(String name, int texId, int unit) {
|
||||
checkNotNull(uniformByName.get(name)).setSamplerTexId(texId, unit);
|
||||
}
|
||||
|
||||
/** Sets a float type uniform. */
|
||||
public void setFloatUniform(String name, float value) {
|
||||
checkNotNull(uniformByName.get(name)).setFloat(value);
|
||||
}
|
||||
|
||||
/** Sets a float array type uniform. */
|
||||
public void setFloatsUniform(String name, float[] value) {
|
||||
checkNotNull(uniformByName.get(name)).setFloats(value);
|
||||
}
|
||||
|
||||
/** Binds all attributes and uniforms in the program. */
|
||||
public void bindAttributesAndUniforms() {
|
||||
for (Attribute attribute : attributes) {
|
||||
attribute.bind();
|
||||
}
|
||||
for (Uniform uniform : uniforms) {
|
||||
uniform.bind();
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns the length of the null-terminated C string in {@code cString}. */
|
||||
private static int getCStringLength(byte[] cString) {
|
||||
for (int i = 0; i < cString.length; ++i) {
|
||||
if (cString[i] == '\0') {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return cString.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* GL attribute, which can be attached to a buffer with {@link Attribute#setBuffer(float[], int)}.
|
||||
*/
|
||||
private static final class Attribute {
|
||||
|
||||
/* Returns the attribute at the given index in the program. */
|
||||
public static Attribute create(int programId, int index) {
|
||||
int[] length = new int[1];
|
||||
GLES20.glGetProgramiv(
|
||||
programId, GLES20.GL_ACTIVE_ATTRIBUTE_MAX_LENGTH, length, /* offset= */ 0);
|
||||
byte[] nameBytes = new byte[length[0]];
|
||||
|
||||
GLES20.glGetActiveAttrib(
|
||||
programId,
|
||||
index,
|
||||
length[0],
|
||||
/* unusedLength */ new int[1],
|
||||
/* lengthOffset= */ 0,
|
||||
/* unusedSize */ new int[1],
|
||||
/* sizeOffset= */ 0,
|
||||
/* unusedType */ new int[1],
|
||||
/* typeOffset= */ 0,
|
||||
nameBytes,
|
||||
/* nameOffset= */ 0);
|
||||
String name = new String(nameBytes, /* offset= */ 0, getCStringLength(nameBytes));
|
||||
int location = getAttributeLocation(programId, name);
|
||||
|
||||
return new Attribute(name, index, location);
|
||||
}
|
||||
|
||||
/** The name of the attribute in the GLSL sources. */
|
||||
public final String name;
|
||||
|
||||
private final int index;
|
||||
private final int location;
|
||||
|
||||
@Nullable private Buffer buffer;
|
||||
private int size;
|
||||
|
||||
private Attribute(String name, int index, int location) {
|
||||
this.name = name;
|
||||
this.index = index;
|
||||
this.location = location;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures {@link #bind()} to attach vertices in {@code buffer} (each of size {@code size}
|
||||
* elements) to this {@link Attribute}.
|
||||
*
|
||||
* @param buffer Buffer to bind to this attribute.
|
||||
* @param size Number of elements per vertex.
|
||||
*/
|
||||
public void setBuffer(float[] buffer, int size) {
|
||||
this.buffer = GlUtil.createBuffer(buffer);
|
||||
this.size = size;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the vertex attribute to whatever was attached via {@link #setBuffer(float[], int)}.
|
||||
*
|
||||
* <p>Should be called before each drawing call.
|
||||
*/
|
||||
public void bind() {
|
||||
Buffer buffer = checkNotNull(this.buffer, "call setBuffer before bind");
|
||||
GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, /* buffer= */ 0);
|
||||
GLES20.glVertexAttribPointer(
|
||||
location, size, GLES20.GL_FLOAT, /* normalized= */ false, /* stride= */ 0, buffer);
|
||||
GLES20.glEnableVertexAttribArray(index);
|
||||
GlUtil.checkGlError();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* GL uniform, which can be attached to a sampler using {@link Uniform#setSamplerTexId(int, int)}.
|
||||
*/
|
||||
private static final class Uniform {
|
||||
|
||||
/** Returns the uniform at the given index in the program. */
|
||||
public static Uniform create(int programId, int index) {
|
||||
int[] length = new int[1];
|
||||
GLES20.glGetProgramiv(
|
||||
programId, GLES20.GL_ACTIVE_UNIFORM_MAX_LENGTH, length, /* offset= */ 0);
|
||||
|
||||
int[] type = new int[1];
|
||||
byte[] nameBytes = new byte[length[0]];
|
||||
|
||||
GLES20.glGetActiveUniform(
|
||||
programId,
|
||||
index,
|
||||
length[0],
|
||||
/* unusedLength */ new int[1],
|
||||
/* lengthOffset= */ 0,
|
||||
/* unusedSize */ new int[1],
|
||||
/*sizeOffset= */ 0,
|
||||
type,
|
||||
/* typeOffset= */ 0,
|
||||
nameBytes,
|
||||
/* nameOffset= */ 0);
|
||||
String name = new String(nameBytes, /* offset= */ 0, getCStringLength(nameBytes));
|
||||
int location = getUniformLocation(programId, name);
|
||||
|
||||
return new Uniform(name, location, type[0]);
|
||||
}
|
||||
|
||||
/** The name of the uniform in the GLSL sources. */
|
||||
public final String name;
|
||||
|
||||
private final int location;
|
||||
private final int type;
|
||||
private final float[] value;
|
||||
|
||||
private int texId;
|
||||
private int unit;
|
||||
|
||||
private Uniform(String name, int location, int type) {
|
||||
this.name = name;
|
||||
this.location = location;
|
||||
this.type = type;
|
||||
this.value = new float[16];
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures {@link #bind()} to use the specified {@code texId} for this sampler uniform.
|
||||
*
|
||||
* @param texId The GL texture identifier from which to sample.
|
||||
* @param unit The GL texture unit index.
|
||||
*/
|
||||
public void setSamplerTexId(int texId, int unit) {
|
||||
this.texId = texId;
|
||||
this.unit = unit;
|
||||
}
|
||||
|
||||
/** Configures {@link #bind()} to use the specified float {@code value} for this uniform. */
|
||||
public void setFloat(float value) {
|
||||
this.value[0] = value;
|
||||
}
|
||||
|
||||
/** Configures {@link #bind()} to use the specified float[] {@code value} for this uniform. */
|
||||
public void setFloats(float[] value) {
|
||||
System.arraycopy(value, /* srcPos= */ 0, this.value, /* destPos= */ 0, value.length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the uniform to whatever value was passed via {@link #setSamplerTexId(int, int)}, {@link
|
||||
* #setFloat(float)} or {@link #setFloats(float[])}.
|
||||
*
|
||||
* <p>Should be called before each drawing call.
|
||||
*/
|
||||
public void bind() {
|
||||
if (type == GLES20.GL_FLOAT) {
|
||||
GLES20.glUniform1fv(location, /* count= */ 1, value, /* offset= */ 0);
|
||||
GlUtil.checkGlError();
|
||||
return;
|
||||
}
|
||||
|
||||
if (type == GLES20.GL_FLOAT_MAT3) {
|
||||
GLES20.glUniformMatrix3fv(
|
||||
location, /* count= */ 1, /* transpose= */ false, value, /* offset= */ 0);
|
||||
GlUtil.checkGlError();
|
||||
return;
|
||||
}
|
||||
|
||||
if (type == GLES20.GL_FLOAT_MAT4) {
|
||||
GLES20.glUniformMatrix4fv(
|
||||
location, /* count= */ 1, /* transpose= */ false, value, /* offset= */ 0);
|
||||
GlUtil.checkGlError();
|
||||
return;
|
||||
}
|
||||
|
||||
if (texId == 0) {
|
||||
throw new IllegalStateException("No call to setSamplerTexId() before bind.");
|
||||
}
|
||||
GLES20.glActiveTexture(GLES20.GL_TEXTURE0 + unit);
|
||||
if (type == GLES11Ext.GL_SAMPLER_EXTERNAL_OES || type == GL_SAMPLER_EXTERNAL_2D_Y2Y_EXT) {
|
||||
GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, texId);
|
||||
} else if (type == GLES20.GL_SAMPLER_2D) {
|
||||
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, texId);
|
||||
} else {
|
||||
throw new IllegalStateException("Unexpected uniform type: " + type);
|
||||
}
|
||||
GLES20.glUniform1i(location, unit);
|
||||
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);
|
||||
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR);
|
||||
GLES20.glTexParameteri(
|
||||
GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE);
|
||||
GLES20.glTexParameteri(
|
||||
GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE);
|
||||
GlUtil.checkGlError();
|
||||
}
|
||||
}
|
||||
}
|
@ -16,7 +16,6 @@
|
||||
package androidx.media3.common.util;
|
||||
|
||||
import static android.opengl.GLU.gluErrorString;
|
||||
import static androidx.media3.common.util.Assertions.checkNotNull;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageManager;
|
||||
@ -33,16 +32,13 @@ import androidx.annotation.RequiresApi;
|
||||
import androidx.media3.common.C;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.Buffer;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.nio.FloatBuffer;
|
||||
import java.nio.IntBuffer;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import javax.microedition.khronos.egl.EGL10;
|
||||
|
||||
/** OpenGL ES 2.0 utilities. */
|
||||
/** OpenGL ES utilities. */
|
||||
@SuppressWarnings("InlinedApi") // GLES constants are used safely based on the API version.
|
||||
@UnstableApi
|
||||
public final class GlUtil {
|
||||
|
||||
@ -54,163 +50,73 @@ public final class GlUtil {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a GLSL shader program.
|
||||
*
|
||||
* <p>After constructing a program, keep a reference for its lifetime and call {@link #delete()}
|
||||
* (or release the current GL context) when it's no longer needed.
|
||||
*/
|
||||
public static final class Program {
|
||||
/** The identifier of a compiled and linked GLSL shader program. */
|
||||
private final int programId;
|
||||
|
||||
private final Attribute[] attributes;
|
||||
private final Uniform[] uniforms;
|
||||
private final Map<String, Attribute> attributeByName;
|
||||
private final Map<String, Uniform> uniformByName;
|
||||
|
||||
/**
|
||||
* Compiles a GL shader program from vertex and fragment shader GLSL GLES20 code.
|
||||
*
|
||||
* @param context The {@link Context}.
|
||||
* @param vertexShaderFilePath The path to a vertex shader program.
|
||||
* @param fragmentShaderFilePath The path to a fragment shader program.
|
||||
* @throws IOException When failing to read shader files.
|
||||
*/
|
||||
public Program(Context context, String vertexShaderFilePath, String fragmentShaderFilePath)
|
||||
throws IOException {
|
||||
this(loadAsset(context, vertexShaderFilePath), loadAsset(context, fragmentShaderFilePath));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a GL shader program from vertex and fragment shader GLSL GLES20 code.
|
||||
*
|
||||
* <p>This involves slow steps, like compiling, linking, and switching the GL program, so do not
|
||||
* call this in fast rendering loops.
|
||||
*
|
||||
* @param vertexShaderGlsl The vertex shader program.
|
||||
* @param fragmentShaderGlsl The fragment shader program.
|
||||
*/
|
||||
public Program(String vertexShaderGlsl, String fragmentShaderGlsl) {
|
||||
programId = GLES20.glCreateProgram();
|
||||
checkGlError();
|
||||
|
||||
// Add the vertex and fragment shaders.
|
||||
addShader(programId, GLES20.GL_VERTEX_SHADER, vertexShaderGlsl);
|
||||
addShader(programId, GLES20.GL_FRAGMENT_SHADER, fragmentShaderGlsl);
|
||||
|
||||
// Link and use the program, and enumerate attributes/uniforms.
|
||||
GLES20.glLinkProgram(programId);
|
||||
int[] linkStatus = new int[] {GLES20.GL_FALSE};
|
||||
GLES20.glGetProgramiv(programId, GLES20.GL_LINK_STATUS, linkStatus, /* offset= */ 0);
|
||||
if (linkStatus[0] != GLES20.GL_TRUE) {
|
||||
throwGlException(
|
||||
"Unable to link shader program: \n" + GLES20.glGetProgramInfoLog(programId));
|
||||
}
|
||||
GLES20.glUseProgram(programId);
|
||||
attributeByName = new HashMap<>();
|
||||
int[] attributeCount = new int[1];
|
||||
GLES20.glGetProgramiv(
|
||||
programId, GLES20.GL_ACTIVE_ATTRIBUTES, attributeCount, /* offset= */ 0);
|
||||
attributes = new Attribute[attributeCount[0]];
|
||||
for (int i = 0; i < attributeCount[0]; i++) {
|
||||
Attribute attribute = Attribute.create(programId, i);
|
||||
attributes[i] = attribute;
|
||||
attributeByName.put(attribute.name, attribute);
|
||||
}
|
||||
uniformByName = new HashMap<>();
|
||||
int[] uniformCount = new int[1];
|
||||
GLES20.glGetProgramiv(programId, GLES20.GL_ACTIVE_UNIFORMS, uniformCount, /* offset= */ 0);
|
||||
uniforms = new Uniform[uniformCount[0]];
|
||||
for (int i = 0; i < uniformCount[0]; i++) {
|
||||
Uniform uniform = Uniform.create(programId, i);
|
||||
uniforms[i] = uniform;
|
||||
uniformByName.put(uniform.name, uniform);
|
||||
}
|
||||
checkGlError();
|
||||
}
|
||||
|
||||
/**
|
||||
* Uses the program.
|
||||
*
|
||||
* <p>Call this in the rendering loop to switch between different programs.
|
||||
*/
|
||||
public void use() {
|
||||
// TODO(http://b/205002913): When multiple GL programs are supported by Transformer, make sure
|
||||
// to call use() to switch between programs.
|
||||
GLES20.glUseProgram(programId);
|
||||
checkGlError();
|
||||
}
|
||||
|
||||
/** Deletes the program. Deleted programs cannot be used again. */
|
||||
public void delete() {
|
||||
GLES20.glDeleteProgram(programId);
|
||||
checkGlError();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the location of an {@link Attribute}, which has been enabled as a vertex attribute
|
||||
* array.
|
||||
*/
|
||||
public int getAttributeArrayLocationAndEnable(String attributeName) {
|
||||
int location = getAttributeLocation(attributeName);
|
||||
GLES20.glEnableVertexAttribArray(location);
|
||||
checkGlError();
|
||||
return location;
|
||||
}
|
||||
|
||||
/** Returns the location of an {@link Attribute}. */
|
||||
private int getAttributeLocation(String attributeName) {
|
||||
return GlUtil.getAttributeLocation(programId, attributeName);
|
||||
}
|
||||
|
||||
/** Returns the location of a {@link Uniform}. */
|
||||
public int getUniformLocation(String uniformName) {
|
||||
return GlUtil.getUniformLocation(programId, uniformName);
|
||||
}
|
||||
|
||||
/** Sets a float buffer type attribute. */
|
||||
public void setBufferAttribute(String name, float[] values, int size) {
|
||||
checkNotNull(attributeByName.get(name)).setBuffer(values, size);
|
||||
}
|
||||
|
||||
/** Sets a texture sampler type uniform. */
|
||||
public void setSamplerTexIdUniform(String name, int texId, int unit) {
|
||||
checkNotNull(uniformByName.get(name)).setSamplerTexId(texId, unit);
|
||||
}
|
||||
|
||||
/** Sets a float type uniform. */
|
||||
public void setFloatUniform(String name, float value) {
|
||||
checkNotNull(uniformByName.get(name)).setFloat(value);
|
||||
}
|
||||
|
||||
/** Sets a float array type uniform. */
|
||||
public void setFloatsUniform(String name, float[] value) {
|
||||
checkNotNull(uniformByName.get(name)).setFloats(value);
|
||||
}
|
||||
|
||||
/** Binds all attributes and uniforms in the program. */
|
||||
public void bindAttributesAndUniforms() {
|
||||
for (Attribute attribute : attributes) {
|
||||
attribute.bind();
|
||||
}
|
||||
for (Uniform uniform : uniforms) {
|
||||
uniform.bind();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Whether to throw a {@link GlException} in case of an OpenGL error. */
|
||||
public static boolean glAssertionsEnabled = false;
|
||||
|
||||
/** Number of vertices in a rectangle. */
|
||||
public static final int RECTANGLE_VERTICES_COUNT = 4;
|
||||
|
||||
private static final String TAG = "GlUtil";
|
||||
|
||||
// https://www.khronos.org/registry/EGL/extensions/EXT/EGL_EXT_protected_content.txt
|
||||
private static final String EXTENSION_PROTECTED_CONTENT = "EGL_EXT_protected_content";
|
||||
// https://www.khronos.org/registry/EGL/extensions/KHR/EGL_KHR_surfaceless_context.txt
|
||||
private static final String EXTENSION_SURFACELESS_CONTEXT = "EGL_KHR_surfaceless_context";
|
||||
|
||||
// https://www.khronos.org/registry/EGL/extensions/KHR/EGL_KHR_gl_colorspace.txt
|
||||
private static final int EGL_GL_COLORSPACE_KHR = 0x309D;
|
||||
// https://www.khronos.org/registry/EGL/extensions/EXT/EGL_EXT_gl_colorspace_bt2020_linear.txt
|
||||
private static final int EGL_GL_COLORSPACE_BT2020_PQ_EXT = 0x3340;
|
||||
|
||||
private static final int[] EGL_WINDOW_SURFACE_ATTRIBUTES_NONE = new int[] {EGL14.EGL_NONE};
|
||||
private static final int[] EGL_WINDOW_SURFACE_ATTRIBUTES_BT2020_PQ =
|
||||
new int[] {EGL_GL_COLORSPACE_KHR, EGL_GL_COLORSPACE_BT2020_PQ_EXT, EGL14.EGL_NONE};
|
||||
private static final int[] EGL_CONFIG_ATTRIBUTES_RGBA_8888 =
|
||||
new int[] {
|
||||
EGL14.EGL_RENDERABLE_TYPE, EGL14.EGL_OPENGL_ES2_BIT,
|
||||
EGL14.EGL_RED_SIZE, /* redSize= */ 8,
|
||||
EGL14.EGL_GREEN_SIZE, /* greenSize= */ 8,
|
||||
EGL14.EGL_BLUE_SIZE, /* blueSize= */ 8,
|
||||
EGL14.EGL_ALPHA_SIZE, /* alphaSize= */ 8,
|
||||
EGL14.EGL_DEPTH_SIZE, /* depthSize= */ 0,
|
||||
EGL14.EGL_STENCIL_SIZE, /* stencilSize= */ 0,
|
||||
EGL14.EGL_NONE
|
||||
};
|
||||
private static final int[] EGL_CONFIG_ATTRIBUTES_RGBA_1010102 =
|
||||
new int[] {
|
||||
EGL14.EGL_RENDERABLE_TYPE, EGL14.EGL_OPENGL_ES2_BIT,
|
||||
EGL14.EGL_RED_SIZE, /* redSize= */ 10,
|
||||
EGL14.EGL_GREEN_SIZE, /* greenSize= */ 10,
|
||||
EGL14.EGL_BLUE_SIZE, /* blueSize= */ 10,
|
||||
EGL14.EGL_ALPHA_SIZE, /* alphaSize= */ 2,
|
||||
EGL14.EGL_DEPTH_SIZE, /* depthSize= */ 0,
|
||||
EGL14.EGL_STENCIL_SIZE, /* stencilSize= */ 0,
|
||||
EGL14.EGL_NONE
|
||||
};
|
||||
|
||||
/** Class only contains static methods. */
|
||||
private GlUtil() {}
|
||||
|
||||
/** Bounds of normalized device coordinates, commonly used for defining viewport boundaries. */
|
||||
public static float[] getNormalizedCoordinateBounds() {
|
||||
return new float[] {
|
||||
-1, -1, 0, 1,
|
||||
1, -1, 0, 1,
|
||||
-1, 1, 0, 1,
|
||||
1, 1, 0, 1
|
||||
};
|
||||
}
|
||||
|
||||
/** Typical bounds used for sampling from textures. */
|
||||
public static float[] getTextureCoordinateBounds() {
|
||||
return new float[] {
|
||||
0, 0, 0, 1,
|
||||
1, 0, 0, 1,
|
||||
0, 1, 0, 1,
|
||||
1, 1, 0, 1
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether creating a GL context with {@value #EXTENSION_PROTECTED_CONTENT} is possible.
|
||||
* If {@code true}, the device supports a protected output path for DRM content when using GL.
|
||||
@ -260,7 +166,16 @@ public final class GlUtil {
|
||||
/** Returns a new {@link EGLContext} for the specified {@link EGLDisplay}. */
|
||||
@RequiresApi(17)
|
||||
public static EGLContext createEglContext(EGLDisplay eglDisplay) {
|
||||
return Api17.createEglContext(eglDisplay);
|
||||
return Api17.createEglContext(eglDisplay, /* version= */ 2, EGL_CONFIG_ATTRIBUTES_RGBA_8888);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new {@link EGLContext} for the specified {@link EGLDisplay}, requesting ES 3 and an
|
||||
* RGBA 1010102 config.
|
||||
*/
|
||||
@RequiresApi(17)
|
||||
public static EGLContext createEglContextEs3Rgba1010102(EGLDisplay eglDisplay) {
|
||||
return Api17.createEglContext(eglDisplay, /* version= */ 3, EGL_CONFIG_ATTRIBUTES_RGBA_1010102);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -271,7 +186,24 @@ public final class GlUtil {
|
||||
*/
|
||||
@RequiresApi(17)
|
||||
public static EGLSurface getEglSurface(EGLDisplay eglDisplay, Object surface) {
|
||||
return Api17.getEglSurface(eglDisplay, surface);
|
||||
return Api17.getEglSurface(
|
||||
eglDisplay, surface, EGL_CONFIG_ATTRIBUTES_RGBA_8888, EGL_WINDOW_SURFACE_ATTRIBUTES_NONE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new {@link EGLSurface} wrapping the specified {@code surface}, for HDR rendering with
|
||||
* Rec. 2020 color primaries and using the PQ transfer function.
|
||||
*
|
||||
* @param eglDisplay The {@link EGLDisplay} to attach the surface to.
|
||||
* @param surface The surface to wrap; must be a surface, surface texture or surface holder.
|
||||
*/
|
||||
@RequiresApi(17)
|
||||
public static EGLSurface getEglSurfaceBt2020Pq(EGLDisplay eglDisplay, Object surface) {
|
||||
return Api17.getEglSurface(
|
||||
eglDisplay,
|
||||
surface,
|
||||
EGL_CONFIG_ATTRIBUTES_RGBA_1010102,
|
||||
EGL_WINDOW_SURFACE_ATTRIBUTES_BT2020_PQ);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -291,13 +223,29 @@ public final class GlUtil {
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes the specified {@code surface} the render target, using a viewport of {@code width} by
|
||||
* Makes the specified {@code eglSurface} the render target, using a viewport of {@code width} by
|
||||
* {@code height} pixels.
|
||||
*/
|
||||
@RequiresApi(17)
|
||||
public static void focusSurface(
|
||||
EGLDisplay eglDisplay, EGLContext eglContext, EGLSurface surface, int width, int height) {
|
||||
Api17.focusSurface(eglDisplay, eglContext, surface, width, height);
|
||||
public static void focusEglSurface(
|
||||
EGLDisplay eglDisplay, EGLContext eglContext, EGLSurface eglSurface, int width, int height) {
|
||||
Api17.focusRenderTarget(
|
||||
eglDisplay, eglContext, eglSurface, /* framebuffer= */ 0, width, height);
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes the specified {@code framebuffer} the render target, using a viewport of {@code width} by
|
||||
* {@code height} pixels.
|
||||
*/
|
||||
@RequiresApi(17)
|
||||
public static void focusFramebuffer(
|
||||
EGLDisplay eglDisplay,
|
||||
EGLContext eglContext,
|
||||
EGLSurface eglSurface,
|
||||
int framebuffer,
|
||||
int width,
|
||||
int height) {
|
||||
Api17.focusRenderTarget(eglDisplay, eglContext, eglSurface, framebuffer, width, height);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -362,46 +310,73 @@ public final class GlUtil {
|
||||
* GL_CLAMP_TO_EDGE wrapping.
|
||||
*/
|
||||
public static int createExternalTexture() {
|
||||
return generateAndBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the texture identifier for a newly-allocated texture with the specified dimensions.
|
||||
*
|
||||
* @param width of the new texture in pixels
|
||||
* @param height of the new texture in pixels
|
||||
*/
|
||||
public static int createTexture(int width, int height) {
|
||||
int texId = generateAndBindTexture(GLES20.GL_TEXTURE_2D);
|
||||
ByteBuffer byteBuffer = ByteBuffer.allocateDirect(width * height * 4);
|
||||
GLES20.glTexImage2D(
|
||||
GLES20.GL_TEXTURE_2D,
|
||||
/* level= */ 0,
|
||||
GLES20.GL_RGBA,
|
||||
width,
|
||||
height,
|
||||
/* border= */ 0,
|
||||
GLES20.GL_RGBA,
|
||||
GLES20.GL_UNSIGNED_BYTE,
|
||||
byteBuffer);
|
||||
checkGlError();
|
||||
return texId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a GL texture identifier of a newly generated and bound texture of the requested type
|
||||
* with default configuration of GL_LINEAR filtering and GL_CLAMP_TO_EDGE wrapping.
|
||||
*
|
||||
* @param textureTarget The target to which the texture is bound, e.g. {@link
|
||||
* GLES20#GL_TEXTURE_2D} for a two-dimensional texture or {@link
|
||||
* GLES11Ext#GL_TEXTURE_EXTERNAL_OES} for an external texture.
|
||||
*/
|
||||
private static int generateAndBindTexture(int textureTarget) {
|
||||
int[] texId = new int[1];
|
||||
GLES20.glGenTextures(/* n= */ 1, IntBuffer.wrap(texId));
|
||||
GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, texId[0]);
|
||||
GLES20.glTexParameteri(
|
||||
GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR);
|
||||
GLES20.glTexParameteri(
|
||||
GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);
|
||||
GLES20.glTexParameteri(
|
||||
GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE);
|
||||
GLES20.glTexParameteri(
|
||||
GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE);
|
||||
GLES20.glGenTextures(/* n= */ 1, texId, /* offset= */ 0);
|
||||
checkGlError();
|
||||
GLES20.glBindTexture(textureTarget, texId[0]);
|
||||
checkGlError();
|
||||
GLES20.glTexParameteri(textureTarget, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);
|
||||
checkGlError();
|
||||
GLES20.glTexParameteri(textureTarget, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE);
|
||||
checkGlError();
|
||||
GLES20.glTexParameteri(textureTarget, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE);
|
||||
checkGlError();
|
||||
return texId[0];
|
||||
}
|
||||
|
||||
private static void addShader(int programId, int type, String glsl) {
|
||||
int shader = GLES20.glCreateShader(type);
|
||||
GLES20.glShaderSource(shader, glsl);
|
||||
GLES20.glCompileShader(shader);
|
||||
|
||||
int[] result = new int[] {GLES20.GL_FALSE};
|
||||
GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, result, /* offset= */ 0);
|
||||
if (result[0] != GLES20.GL_TRUE) {
|
||||
throwGlException(GLES20.glGetShaderInfoLog(shader) + ", source: " + glsl);
|
||||
}
|
||||
|
||||
GLES20.glAttachShader(programId, shader);
|
||||
GLES20.glDeleteShader(shader);
|
||||
/**
|
||||
* Returns a new framebuffer for the texture.
|
||||
*
|
||||
* @param texId The identifier of the texture to attach to the framebuffer.
|
||||
*/
|
||||
public static int createFboForTexture(int texId) {
|
||||
int[] fboId = new int[1];
|
||||
GLES20.glGenFramebuffers(/* n= */ 1, fboId, /* offset= */ 0);
|
||||
checkGlError();
|
||||
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, fboId[0]);
|
||||
checkGlError();
|
||||
GLES20.glFramebufferTexture2D(
|
||||
GLES20.GL_FRAMEBUFFER, GLES20.GL_COLOR_ATTACHMENT0, GLES20.GL_TEXTURE_2D, texId, 0);
|
||||
checkGlError();
|
||||
return fboId[0];
|
||||
}
|
||||
|
||||
private static int getAttributeLocation(int programId, String attributeName) {
|
||||
return GLES20.glGetAttribLocation(programId, attributeName);
|
||||
}
|
||||
|
||||
private static int getUniformLocation(int programId, String uniformName) {
|
||||
return GLES20.glGetUniformLocation(programId, uniformName);
|
||||
}
|
||||
|
||||
private static void throwGlException(String errorMsg) {
|
||||
/* package */ static void throwGlException(String errorMsg) {
|
||||
Log.e(TAG, errorMsg);
|
||||
if (glAssertionsEnabled) {
|
||||
throw new GlException(errorMsg);
|
||||
@ -414,200 +389,6 @@ public final class GlUtil {
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns the length of the null-terminated string in {@code strVal}. */
|
||||
private static int strlen(byte[] strVal) {
|
||||
for (int i = 0; i < strVal.length; ++i) {
|
||||
if (strVal[i] == '\0') {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return strVal.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* GL attribute, which can be attached to a buffer with {@link Attribute#setBuffer(float[], int)}.
|
||||
*/
|
||||
private static final class Attribute {
|
||||
|
||||
/* Returns the attribute at the given index in the program. */
|
||||
public static Attribute create(int programId, int index) {
|
||||
int[] length = new int[1];
|
||||
GLES20.glGetProgramiv(
|
||||
programId, GLES20.GL_ACTIVE_ATTRIBUTE_MAX_LENGTH, length, /* offset= */ 0);
|
||||
byte[] nameBytes = new byte[length[0]];
|
||||
|
||||
GLES20.glGetActiveAttrib(
|
||||
programId,
|
||||
index,
|
||||
length[0],
|
||||
/* unusedLength */ new int[1],
|
||||
/* lengthOffset= */ 0,
|
||||
/* unusedSize */ new int[1],
|
||||
/* sizeOffset= */ 0,
|
||||
/* unusedType */ new int[1],
|
||||
/* typeOffset= */ 0,
|
||||
nameBytes,
|
||||
/* nameOffset= */ 0);
|
||||
String name = new String(nameBytes, /* offset= */ 0, strlen(nameBytes));
|
||||
int location = getAttributeLocation(programId, name);
|
||||
|
||||
return new Attribute(name, index, location);
|
||||
}
|
||||
|
||||
/** The name of the attribute in the GLSL sources. */
|
||||
public final String name;
|
||||
|
||||
private final int index;
|
||||
private final int location;
|
||||
|
||||
@Nullable private Buffer buffer;
|
||||
private int size;
|
||||
|
||||
private Attribute(String name, int index, int location) {
|
||||
this.name = name;
|
||||
this.index = index;
|
||||
this.location = location;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures {@link #bind()} to attach vertices in {@code buffer} (each of size {@code size}
|
||||
* elements) to this {@link Attribute}.
|
||||
*
|
||||
* @param buffer Buffer to bind to this attribute.
|
||||
* @param size Number of elements per vertex.
|
||||
*/
|
||||
public void setBuffer(float[] buffer, int size) {
|
||||
this.buffer = createBuffer(buffer);
|
||||
this.size = size;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the vertex attribute to whatever was attached via {@link #setBuffer(float[], int)}.
|
||||
*
|
||||
* <p>Should be called before each drawing call.
|
||||
*/
|
||||
public void bind() {
|
||||
Buffer buffer = checkNotNull(this.buffer, "call setBuffer before bind");
|
||||
GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, /* buffer= */ 0);
|
||||
GLES20.glVertexAttribPointer(
|
||||
location, size, GLES20.GL_FLOAT, /* normalized= */ false, /* stride= */ 0, buffer);
|
||||
GLES20.glEnableVertexAttribArray(index);
|
||||
checkGlError();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* GL uniform, which can be attached to a sampler using {@link Uniform#setSamplerTexId(int, int)}.
|
||||
*/
|
||||
private static final class Uniform {
|
||||
|
||||
/** Returns the uniform at the given index in the program. */
|
||||
public static Uniform create(int programId, int index) {
|
||||
int[] length = new int[1];
|
||||
GLES20.glGetProgramiv(
|
||||
programId, GLES20.GL_ACTIVE_UNIFORM_MAX_LENGTH, length, /* offset= */ 0);
|
||||
|
||||
int[] type = new int[1];
|
||||
byte[] nameBytes = new byte[length[0]];
|
||||
|
||||
GLES20.glGetActiveUniform(
|
||||
programId,
|
||||
index,
|
||||
length[0],
|
||||
/* unusedLength */ new int[1],
|
||||
/* lengthOffset= */ 0,
|
||||
/* unusedSize */ new int[1],
|
||||
/*sizeOffset= */ 0,
|
||||
type,
|
||||
/* typeOffset= */ 0,
|
||||
nameBytes,
|
||||
/* nameOffset= */ 0);
|
||||
String name = new String(nameBytes, /* offset= */ 0, strlen(nameBytes));
|
||||
int location = getUniformLocation(programId, name);
|
||||
|
||||
return new Uniform(name, location, type[0]);
|
||||
}
|
||||
|
||||
/** The name of the uniform in the GLSL sources. */
|
||||
public final String name;
|
||||
|
||||
private final int location;
|
||||
private final int type;
|
||||
private final float[] value;
|
||||
|
||||
private int texId;
|
||||
private int unit;
|
||||
|
||||
private Uniform(String name, int location, int type) {
|
||||
this.name = name;
|
||||
this.location = location;
|
||||
this.type = type;
|
||||
this.value = new float[16];
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures {@link #bind()} to use the specified {@code texId} for this sampler uniform.
|
||||
*
|
||||
* @param texId The GL texture identifier from which to sample.
|
||||
* @param unit The GL texture unit index.
|
||||
*/
|
||||
public void setSamplerTexId(int texId, int unit) {
|
||||
this.texId = texId;
|
||||
this.unit = unit;
|
||||
}
|
||||
|
||||
/** Configures {@link #bind()} to use the specified float {@code value} for this uniform. */
|
||||
public void setFloat(float value) {
|
||||
this.value[0] = value;
|
||||
}
|
||||
|
||||
/** Configures {@link #bind()} to use the specified float[] {@code value} for this uniform. */
|
||||
public void setFloats(float[] value) {
|
||||
System.arraycopy(value, /* srcPos= */ 0, this.value, /* destPos= */ 0, value.length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the uniform to whatever value was passed via {@link #setSamplerTexId(int, int)}, {@link
|
||||
* #setFloat(float)} or {@link #setFloats(float[])}.
|
||||
*
|
||||
* <p>Should be called before each drawing call.
|
||||
*/
|
||||
public void bind() {
|
||||
if (type == GLES20.GL_FLOAT) {
|
||||
GLES20.glUniform1fv(location, /* count= */ 1, value, /* offset= */ 0);
|
||||
checkGlError();
|
||||
return;
|
||||
}
|
||||
|
||||
if (type == GLES20.GL_FLOAT_MAT4) {
|
||||
GLES20.glUniformMatrix4fv(
|
||||
location, /* count= */ 1, /* transpose= */ false, value, /* offset= */ 0);
|
||||
checkGlError();
|
||||
return;
|
||||
}
|
||||
|
||||
if (texId == 0) {
|
||||
throw new IllegalStateException("No call to setSamplerTexId() before bind.");
|
||||
}
|
||||
GLES20.glActiveTexture(GLES20.GL_TEXTURE0 + unit);
|
||||
if (type == GLES11Ext.GL_SAMPLER_EXTERNAL_OES) {
|
||||
GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, texId);
|
||||
} else if (type == GLES20.GL_SAMPLER_2D) {
|
||||
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, texId);
|
||||
} else {
|
||||
throw new IllegalStateException("Unexpected uniform type: " + type);
|
||||
}
|
||||
GLES20.glUniform1i(location, unit);
|
||||
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);
|
||||
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR);
|
||||
GLES20.glTexParameteri(
|
||||
GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE);
|
||||
GLES20.glTexParameteri(
|
||||
GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE);
|
||||
checkGlError();
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresApi(17)
|
||||
private static final class Api17 {
|
||||
private Api17() {}
|
||||
@ -629,12 +410,13 @@ public final class GlUtil {
|
||||
}
|
||||
|
||||
@DoNotInline
|
||||
public static EGLContext createEglContext(EGLDisplay eglDisplay) {
|
||||
int[] contextAttributes = {EGL14.EGL_CONTEXT_CLIENT_VERSION, 2, EGL14.EGL_NONE};
|
||||
public static EGLContext createEglContext(
|
||||
EGLDisplay eglDisplay, int version, int[] configAttributes) {
|
||||
int[] contextAttributes = {EGL14.EGL_CONTEXT_CLIENT_VERSION, version, EGL14.EGL_NONE};
|
||||
EGLContext eglContext =
|
||||
EGL14.eglCreateContext(
|
||||
eglDisplay,
|
||||
getEglConfig(eglDisplay),
|
||||
getEglConfig(eglDisplay, configAttributes),
|
||||
EGL14.EGL_NO_CONTEXT,
|
||||
contextAttributes,
|
||||
/* offset= */ 0);
|
||||
@ -642,32 +424,41 @@ public final class GlUtil {
|
||||
EGL14.eglTerminate(eglDisplay);
|
||||
throwGlException(
|
||||
"eglCreateContext() failed to create a valid context. The device may not support EGL"
|
||||
+ " version 2");
|
||||
+ " version "
|
||||
+ version);
|
||||
}
|
||||
checkGlError();
|
||||
return eglContext;
|
||||
}
|
||||
|
||||
@DoNotInline
|
||||
public static EGLSurface getEglSurface(EGLDisplay eglDisplay, Object surface) {
|
||||
public static EGLSurface getEglSurface(
|
||||
EGLDisplay eglDisplay,
|
||||
Object surface,
|
||||
int[] configAttributes,
|
||||
int[] windowSurfaceAttributes) {
|
||||
return EGL14.eglCreateWindowSurface(
|
||||
eglDisplay,
|
||||
getEglConfig(eglDisplay),
|
||||
getEglConfig(eglDisplay, configAttributes),
|
||||
surface,
|
||||
new int[] {EGL14.EGL_NONE},
|
||||
windowSurfaceAttributes,
|
||||
/* offset= */ 0);
|
||||
}
|
||||
|
||||
@DoNotInline
|
||||
public static void focusSurface(
|
||||
EGLDisplay eglDisplay, EGLContext eglContext, EGLSurface surface, int width, int height) {
|
||||
int[] boundFrameBuffer = new int[1];
|
||||
GLES20.glGetIntegerv(GLES20.GL_FRAMEBUFFER_BINDING, boundFrameBuffer, /* offset= */ 0);
|
||||
int defaultFrameBuffer = 0;
|
||||
if (boundFrameBuffer[0] != defaultFrameBuffer) {
|
||||
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, defaultFrameBuffer);
|
||||
public static void focusRenderTarget(
|
||||
EGLDisplay eglDisplay,
|
||||
EGLContext eglContext,
|
||||
EGLSurface eglSurface,
|
||||
int framebuffer,
|
||||
int width,
|
||||
int height) {
|
||||
int[] boundFramebuffer = new int[1];
|
||||
GLES20.glGetIntegerv(GLES20.GL_FRAMEBUFFER_BINDING, boundFramebuffer, /* offset= */ 0);
|
||||
if (boundFramebuffer[0] != framebuffer) {
|
||||
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, framebuffer);
|
||||
}
|
||||
EGL14.eglMakeCurrent(eglDisplay, surface, surface, eglContext);
|
||||
EGL14.eglMakeCurrent(eglDisplay, eglSurface, eglSurface, eglContext);
|
||||
GLES20.glViewport(/* x= */ 0, /* y= */ 0, width, height);
|
||||
}
|
||||
|
||||
@ -695,22 +486,11 @@ public final class GlUtil {
|
||||
}
|
||||
|
||||
@DoNotInline
|
||||
private static EGLConfig getEglConfig(EGLDisplay eglDisplay) {
|
||||
int[] defaultConfiguration =
|
||||
new int[] {
|
||||
EGL14.EGL_RENDERABLE_TYPE, EGL14.EGL_OPENGL_ES2_BIT,
|
||||
EGL14.EGL_RED_SIZE, /* redSize= */ 8,
|
||||
EGL14.EGL_GREEN_SIZE, /* greenSize= */ 8,
|
||||
EGL14.EGL_BLUE_SIZE, /* blueSize= */ 8,
|
||||
EGL14.EGL_ALPHA_SIZE, /* alphaSize= */ 8,
|
||||
EGL14.EGL_DEPTH_SIZE, /* depthSize= */ 0,
|
||||
EGL14.EGL_STENCIL_SIZE, /* stencilSize= */ 0,
|
||||
EGL14.EGL_NONE
|
||||
};
|
||||
private static EGLConfig getEglConfig(EGLDisplay eglDisplay, int[] attributes) {
|
||||
EGLConfig[] eglConfigs = new EGLConfig[1];
|
||||
if (!EGL14.eglChooseConfig(
|
||||
eglDisplay,
|
||||
defaultConfiguration,
|
||||
attributes,
|
||||
/* attrib_listOffset= */ 0,
|
||||
eglConfigs,
|
||||
/* configsOffset= */ 0,
|
||||
|
@ -19,7 +19,7 @@ import java.util.Arrays;
|
||||
|
||||
/** Configurable loader for native libraries. */
|
||||
@UnstableApi
|
||||
public final class LibraryLoader {
|
||||
public abstract class LibraryLoader {
|
||||
|
||||
private static final String TAG = "LibraryLoader";
|
||||
|
||||
@ -27,7 +27,9 @@ public final class LibraryLoader {
|
||||
private boolean loadAttempted;
|
||||
private boolean isAvailable;
|
||||
|
||||
/** @param libraries The names of the libraries to load. */
|
||||
/**
|
||||
* @param libraries The names of the libraries to load.
|
||||
*/
|
||||
public LibraryLoader(String... libraries) {
|
||||
nativeLibraries = libraries;
|
||||
}
|
||||
@ -49,7 +51,7 @@ public final class LibraryLoader {
|
||||
loadAttempted = true;
|
||||
try {
|
||||
for (String lib : nativeLibraries) {
|
||||
System.loadLibrary(lib);
|
||||
loadLibrary(lib);
|
||||
}
|
||||
isAvailable = true;
|
||||
} catch (UnsatisfiedLinkError exception) {
|
||||
@ -59,4 +61,17 @@ public final class LibraryLoader {
|
||||
}
|
||||
return isAvailable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Should be implemented to call {@code System.loadLibrary(name)}.
|
||||
*
|
||||
* <p>It's necessary for each subclass to implement this method because {@link
|
||||
* System#loadLibrary(String)} uses reflection to obtain the calling class, which is then used to
|
||||
* obtain the class loader to use when loading the native library. If this class were to implement
|
||||
* the method directly, and if a subclass were to have a different class loader, then loading of
|
||||
* the native library would fail.
|
||||
*
|
||||
* @param name The name of the library to load.
|
||||
*/
|
||||
protected abstract void loadLibrary(String name);
|
||||
}
|
||||
|
@ -118,6 +118,21 @@ public final class ListenerSet<T extends @NonNull Object> {
|
||||
*/
|
||||
@CheckResult
|
||||
public ListenerSet<T> copy(Looper looper, IterationFinishedEvent<T> iterationFinishedEvent) {
|
||||
return copy(looper, clock, iterationFinishedEvent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies the listener set.
|
||||
*
|
||||
* @param looper The new {@link Looper} for the copied listener set.
|
||||
* @param clock The new {@link Clock} for the copied listener set.
|
||||
* @param iterationFinishedEvent The new {@link IterationFinishedEvent} sent when all other events
|
||||
* sent during one {@link Looper} message queue iteration were handled by the listeners.
|
||||
* @return The copied listener set.
|
||||
*/
|
||||
@CheckResult
|
||||
public ListenerSet<T> copy(
|
||||
Looper looper, Clock clock, IterationFinishedEvent<T> iterationFinishedEvent) {
|
||||
return new ListenerSet<>(listeners, looper, clock, iterationFinishedEvent);
|
||||
}
|
||||
|
||||
@ -152,6 +167,11 @@ public final class ListenerSet<T extends @NonNull Object> {
|
||||
}
|
||||
}
|
||||
|
||||
/** Removes all listeners from the set. */
|
||||
public void clear() {
|
||||
listeners.clear();
|
||||
}
|
||||
|
||||
/** Returns the number of added listeners. */
|
||||
public int size() {
|
||||
return listeners.size();
|
||||
|
@ -15,6 +15,8 @@
|
||||
*/
|
||||
package androidx.media3.common.util;
|
||||
|
||||
import static java.lang.annotation.ElementType.TYPE_USE;
|
||||
|
||||
import android.text.TextUtils;
|
||||
import androidx.annotation.IntDef;
|
||||
import androidx.annotation.Nullable;
|
||||
@ -22,6 +24,7 @@ import androidx.annotation.Size;
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
import java.net.UnknownHostException;
|
||||
import org.checkerframework.dataflow.qual.Pure;
|
||||
|
||||
@ -35,8 +38,9 @@ public final class Log {
|
||||
*/
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@Target(TYPE_USE)
|
||||
@IntDef({LOG_LEVEL_ALL, LOG_LEVEL_INFO, LOG_LEVEL_WARNING, LOG_LEVEL_ERROR, LOG_LEVEL_OFF})
|
||||
@interface LogLevel {}
|
||||
public @interface LogLevel {}
|
||||
/** Log level to log all messages. */
|
||||
public static final int LOG_LEVEL_ALL = 0;
|
||||
/** Log level to only log informative, warning and error messages. */
|
||||
@ -78,7 +82,9 @@ public final class Log {
|
||||
Log.logStackTraces = logStackTraces;
|
||||
}
|
||||
|
||||
/** @see android.util.Log#d(String, String) */
|
||||
/**
|
||||
* @see android.util.Log#d(String, String)
|
||||
*/
|
||||
@Pure
|
||||
public static void d(@Size(max = 23) String tag, String message) {
|
||||
if (logLevel == LOG_LEVEL_ALL) {
|
||||
@ -86,13 +92,17 @@ public final class Log {
|
||||
}
|
||||
}
|
||||
|
||||
/** @see android.util.Log#d(String, String, Throwable) */
|
||||
/**
|
||||
* @see android.util.Log#d(String, String, Throwable)
|
||||
*/
|
||||
@Pure
|
||||
public static void d(@Size(max = 23) String tag, String message, @Nullable Throwable throwable) {
|
||||
d(tag, appendThrowableString(message, throwable));
|
||||
}
|
||||
|
||||
/** @see android.util.Log#i(String, String) */
|
||||
/**
|
||||
* @see android.util.Log#i(String, String)
|
||||
*/
|
||||
@Pure
|
||||
public static void i(@Size(max = 23) String tag, String message) {
|
||||
if (logLevel <= LOG_LEVEL_INFO) {
|
||||
@ -100,13 +110,17 @@ public final class Log {
|
||||
}
|
||||
}
|
||||
|
||||
/** @see android.util.Log#i(String, String, Throwable) */
|
||||
/**
|
||||
* @see android.util.Log#i(String, String, Throwable)
|
||||
*/
|
||||
@Pure
|
||||
public static void i(@Size(max = 23) String tag, String message, @Nullable Throwable throwable) {
|
||||
i(tag, appendThrowableString(message, throwable));
|
||||
}
|
||||
|
||||
/** @see android.util.Log#w(String, String) */
|
||||
/**
|
||||
* @see android.util.Log#w(String, String)
|
||||
*/
|
||||
@Pure
|
||||
public static void w(@Size(max = 23) String tag, String message) {
|
||||
if (logLevel <= LOG_LEVEL_WARNING) {
|
||||
@ -114,13 +128,17 @@ public final class Log {
|
||||
}
|
||||
}
|
||||
|
||||
/** @see android.util.Log#w(String, String, Throwable) */
|
||||
/**
|
||||
* @see android.util.Log#w(String, String, Throwable)
|
||||
*/
|
||||
@Pure
|
||||
public static void w(@Size(max = 23) String tag, String message, @Nullable Throwable throwable) {
|
||||
w(tag, appendThrowableString(message, throwable));
|
||||
}
|
||||
|
||||
/** @see android.util.Log#e(String, String) */
|
||||
/**
|
||||
* @see android.util.Log#e(String, String)
|
||||
*/
|
||||
@Pure
|
||||
public static void e(@Size(max = 23) String tag, String message) {
|
||||
if (logLevel <= LOG_LEVEL_ERROR) {
|
||||
@ -128,7 +146,9 @@ public final class Log {
|
||||
}
|
||||
}
|
||||
|
||||
/** @see android.util.Log#e(String, String, Throwable) */
|
||||
/**
|
||||
* @see android.util.Log#e(String, String, Throwable)
|
||||
*/
|
||||
@Pure
|
||||
public static void e(@Size(max = 23) String tag, String message, @Nullable Throwable throwable) {
|
||||
e(tag, appendThrowableString(message, throwable));
|
||||
|
@ -30,7 +30,9 @@ public final class LongArray {
|
||||
this(DEFAULT_INITIAL_CAPACITY);
|
||||
}
|
||||
|
||||
/** @param initialCapacity The initial capacity of the array. */
|
||||
/**
|
||||
* @param initialCapacity The initial capacity of the array.
|
||||
*/
|
||||
public LongArray(int initialCapacity) {
|
||||
values = new long[initialCapacity];
|
||||
}
|
||||
|
@ -221,6 +221,17 @@ public final class MediaFormatUtil {
|
||||
case C.ENCODING_PCM_FLOAT:
|
||||
mediaFormatPcmEncoding = AudioFormat.ENCODING_PCM_FLOAT;
|
||||
break;
|
||||
case C.ENCODING_PCM_24BIT:
|
||||
mediaFormatPcmEncoding = AudioFormat.ENCODING_PCM_24BIT_PACKED;
|
||||
break;
|
||||
case C.ENCODING_PCM_32BIT:
|
||||
mediaFormatPcmEncoding = AudioFormat.ENCODING_PCM_32BIT;
|
||||
break;
|
||||
case C.ENCODING_INVALID:
|
||||
mediaFormatPcmEncoding = AudioFormat.ENCODING_INVALID;
|
||||
break;
|
||||
case Format.NO_VALUE:
|
||||
case C.ENCODING_PCM_16BIT_BIG_ENDIAN:
|
||||
default:
|
||||
// No matching value. Do nothing.
|
||||
return;
|
||||
|