public class

MediaItem

extends CustomVersionedParcelable

 java.lang.Object

androidx.versionedparcelable.CustomVersionedParcelable

↳androidx.media2.common.MediaItem

Subclasses:

CallbackMediaItem, UriMediaItem, FileMediaItem

Gradle dependencies

compile group: 'androidx.media2', name: 'media2-common', version: '1.3.0'

  • groupId: androidx.media2
  • artifactId: media2-common
  • version: 1.3.0

Artifact androidx.media2:media2-common:1.3.0 it located at Google repository (https://maven.google.com/)

Overview

A class with information on a single media item with the metadata information. Here are use cases.

  • Specify media items to SessionPlayer for playback.
  • Share media items across the processes.

Subclasses of the session player may only accept certain subclasses of the media items. Check the player documentation that you're interested in.

When it's shared across the processes, we cannot guarantee that they contain the right values because media items are application dependent especially for the metadata.

When an object of the MediaItem's subclass is sent across the process between MediaSession/MediaController or MediaLibraryService.MediaLibrarySession/ MediaBrowser, the object will sent as if it's MediaItem. The recipient cannot get the object with the subclasses' type. This will sanitize process specific information (e.g. java.io.FileDescriptor, , etc).

This object is thread safe.

Summary

Fields
public static final longPOSITION_UNKNOWN

Used when a position is unknown.

Methods
public voidaddOnMetadataChangedListener(java.util.concurrent.Executor executor, MediaItem.OnMetadataChangedListener listener)

public longgetEndPosition()

Return the position in milliseconds at which the playback will end.

public java.lang.StringgetMediaId()

Gets the media id for this item.

public MediaMetadatagetMetadata()

Gets the metadata of the media.

public longgetStartPosition()

Return the position in milliseconds at which the playback will start.

public voidonPreParceling(boolean isStream)

Called immediately before this object is going to be serialized, can be used to handle any custom fields that cannot be easily annotated.

public voidremoveOnMetadataChangedListener(MediaItem.OnMetadataChangedListener listener)

public voidsetMetadata(MediaMetadata metadata)

Sets metadata.

public java.lang.StringtoString()

from CustomVersionedParcelableonPostParceling
from java.lang.Objectclone, equals, finalize, getClass, hashCode, notify, notifyAll, wait, wait, wait

Fields

public static final long POSITION_UNKNOWN

Used when a position is unknown.

See also: MediaItem.getEndPosition()

Methods

public java.lang.String toString()

public void setMetadata(MediaMetadata metadata)

Sets metadata. If the metadata is not null, its id should be matched with this instance's media id.

Parameters:

metadata: metadata to update

See also: MediaMetadata.METADATA_KEY_MEDIA_ID

public MediaMetadata getMetadata()

Gets the metadata of the media.

Returns:

metadata from the session

public long getStartPosition()

Return the position in milliseconds at which the playback will start.

Returns:

the position in milliseconds at which the playback will start

public long getEndPosition()

Return the position in milliseconds at which the playback will end. MediaItem.POSITION_UNKNOWN means ending at the end of source content.

Returns:

the position in milliseconds at which the playback will end

public java.lang.String getMediaId()

Gets the media id for this item. If it's not null, it's a persistent unique key for the underlying media content.

Returns:

media Id from the session

public void addOnMetadataChangedListener(java.util.concurrent.Executor executor, MediaItem.OnMetadataChangedListener listener)

public void removeOnMetadataChangedListener(MediaItem.OnMetadataChangedListener listener)

public void onPreParceling(boolean isStream)

Called immediately before this object is going to be serialized, can be used to handle any custom fields that cannot be easily annotated.

Source

/*
 * Copyright 2019 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.media2.common;

import static androidx.annotation.RestrictTo.Scope.LIBRARY;
import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP;

import android.text.TextUtils;
import android.util.Log;

import androidx.annotation.GuardedBy;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RestrictTo;
import androidx.core.util.Pair;
import androidx.versionedparcelable.CustomVersionedParcelable;
import androidx.versionedparcelable.NonParcelField;
import androidx.versionedparcelable.ParcelField;
import androidx.versionedparcelable.VersionedParcelize;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executor;

/**
 * A class with information on a single media item with the metadata information. Here are use
 * cases.
 *
 * <ul>
 *   <li>Specify media items to {@link SessionPlayer} for playback.
 *   <li>Share media items across the processes.
 * </ul>
 *
 * <p>Subclasses of the session player may only accept certain subclasses of the media items. Check
 * the player documentation that you're interested in.
 *
 * <p>When it's shared across the processes, we cannot guarantee that they contain the right values
 * because media items are application dependent especially for the metadata.
 *
 * <p>When an object of the {@link MediaItem}'s subclass is sent across the process between {@link
 * androidx.media2.session.MediaSession}/{@link androidx.media2.session.MediaController} or {@link
 * androidx.media2.session.MediaLibraryService.MediaLibrarySession}/ {@link
 * androidx.media2.session.MediaBrowser}, the object will sent as if it's {@link MediaItem}. The
 * recipient cannot get the object with the subclasses' type. This will sanitize process specific
 * information (e.g. {@link java.io.FileDescriptor}, {@link android.content.Context}, etc).
 *
 * <p>This object is thread safe.
 *
 * @deprecated androidx.media2 is deprecated. Please migrate to <a
 *     href="https://developer.android.com/guide/topics/media/media3">androidx.media3</a>.
 */
@Deprecated
@VersionedParcelize(isCustom = true)
public class MediaItem extends CustomVersionedParcelable {
    private static final String TAG = "MediaItem";

    // intentionally less than long.MAX_VALUE.
    // Declare this first to avoid 'illegal forward reference'.
    static final long LONG_MAX = 0x7ffffffffffffffL;

    /**
     * Used when a position is unknown.
     *
     * @see #getEndPosition()
     */
    public static final long POSITION_UNKNOWN = LONG_MAX;

    @NonParcelField
    private final Object mLock = new Object();

    @GuardedBy("mLock")
    @ParcelField(1)
    MediaMetadata mMetadata;
    @ParcelField(2)
    long mStartPositionMs = 0;
    @ParcelField(3)
    long mEndPositionMs = POSITION_UNKNOWN;

    // WARNING: Adding a new ParcelField may break old library users (b/152830728)

    @GuardedBy("mLock")
    @NonParcelField
    private final List<Pair<OnMetadataChangedListener, Executor>> mListeners = new ArrayList<>();

    /**
     * Used for VersionedParcelable
     */
    MediaItem() {
    }

    /**
     * Used by {@link MediaItem.Builder} and its subclasses
     */
    // Note: Needs to be protected when we want to allow 3rd party player to define customized
    //       MediaItem.
    @SuppressWarnings("WeakerAccess") /* synthetic access */
    MediaItem(Builder builder) {
        this(builder.mMetadata, builder.mStartPositionMs, builder.mEndPositionMs);
    }

    MediaItem(MediaItem item) {
        this(item.mMetadata, item.mStartPositionMs, item.mEndPositionMs);
    }

    @SuppressWarnings("WeakerAccess") /* synthetic access */
    MediaItem(@Nullable MediaMetadata metadata, long startPositionMs, long endPositionMs) {
        if (startPositionMs > endPositionMs) {
            throw new IllegalStateException("Illegal start/end position: "
                    + startPositionMs + " : " + endPositionMs);
        }
        if (metadata != null && metadata.containsKey(MediaMetadata.METADATA_KEY_DURATION)) {
            long durationMs = metadata.getLong(MediaMetadata.METADATA_KEY_DURATION);
            if (durationMs != SessionPlayer.UNKNOWN_TIME && endPositionMs != POSITION_UNKNOWN
                    && endPositionMs > durationMs) {
                throw new IllegalStateException("endPositionMs shouldn't be greater than"
                        + " duration in the metdata, endPositionMs=" + endPositionMs
                        + ", durationMs=" + durationMs);
            }
        }
        mMetadata = metadata;
        mStartPositionMs = startPositionMs;
        mEndPositionMs = endPositionMs;
    }

    @Override
    public String toString() {
        final StringBuilder sb = new StringBuilder(getClass().getSimpleName());
        synchronized (mLock) {
            sb.append("{Media Id=").append(getMediaId());
            sb.append(", mMetadata=").append(mMetadata);
            sb.append(", mStartPositionMs=").append(mStartPositionMs);
            sb.append(", mEndPositionMs=").append(mEndPositionMs);
            sb.append('}');
        }
        return sb.toString();
    }

    /**
     * Sets metadata. If the metadata is not {@code null}, its id should be matched with this
     * instance's media id.
     *
     * @param metadata metadata to update
     * @see MediaMetadata#METADATA_KEY_MEDIA_ID
     */
    public void setMetadata(@Nullable MediaMetadata metadata) {
        List<Pair<OnMetadataChangedListener, Executor>> listeners = new ArrayList<>();
        synchronized (mLock) {
            if (mMetadata == metadata) {
                return;
            }
            if (mMetadata != null && metadata != null
                    && !TextUtils.equals(getMediaId(), metadata.getMediaId())) {
                Log.w(TAG, "MediaItem's media ID shouldn't be changed");
                return;
            }
            mMetadata = metadata;
            listeners.addAll(mListeners);
        }

        for (Pair<OnMetadataChangedListener, Executor> pair : listeners) {
            final OnMetadataChangedListener listener = pair.first;
            pair.second.execute(new Runnable() {
                @Override
                public void run() {
                    listener.onMetadataChanged(MediaItem.this, metadata);
                }
            });
        }
    }

    /**
     * Gets the metadata of the media.
     *
     * @return metadata from the session
     */
    @Nullable
    public MediaMetadata getMetadata() {
        synchronized (mLock) {
            return mMetadata;
        }
    }

    /**
     * Return the position in milliseconds at which the playback will start.
     * @return the position in milliseconds at which the playback will start
     */
    public long getStartPosition() {
        return mStartPositionMs;
    }

    /**
     * Return the position in milliseconds at which the playback will end.
     * {@link #POSITION_UNKNOWN} means ending at the end of source content.
     * @return the position in milliseconds at which the playback will end
     */
    public long getEndPosition() {
        return mEndPositionMs;
    }

    /**
     * Gets the media id for this item. If it's not {@code null}, it's a persistent unique key
     * for the underlying media content.
     *
     * @return media Id from the session
     */
    @RestrictTo(LIBRARY_GROUP)
    @Nullable
    public String getMediaId() {
        synchronized (mLock) {
            return mMetadata != null
                    ? mMetadata.getString(MediaMetadata.METADATA_KEY_MEDIA_ID) : null;
        }
    }

    /**
     */
    @RestrictTo(LIBRARY_GROUP)
    public void addOnMetadataChangedListener(
            Executor executor, OnMetadataChangedListener listener) {
        synchronized (mLock) {
            for (Pair<OnMetadataChangedListener, Executor> pair : mListeners) {
                if (pair.first == listener) {
                    return;
                }
            }
            mListeners.add(new Pair<>(listener, executor));
        }
    }

    /**
     */
    @RestrictTo(LIBRARY_GROUP)
    public void removeOnMetadataChangedListener(OnMetadataChangedListener listener) {
        synchronized (mLock) {
            for (int i = mListeners.size() - 1; i >= 0; i--) {
                if (mListeners.get(i).first == listener) {
                    mListeners.remove(i);
                    return;
                }
            }
        }
    }

    /**
     * Builder for {@link MediaItem}.
     *
     * @deprecated androidx.media2 is deprecated. Please migrate to <a
     *     href="https://developer.android.com/guide/topics/media/media3">androidx.media3</a>.
     */
    @Deprecated
    public static class Builder {
        @SuppressWarnings("WeakerAccess") /* synthetic access */
                MediaMetadata mMetadata;
        @SuppressWarnings("WeakerAccess") /* synthetic access */
        long mStartPositionMs = 0;
        @SuppressWarnings("WeakerAccess") /* synthetic access */
        long mEndPositionMs = POSITION_UNKNOWN;

        /**
         * Default constructor
         */
        public Builder() {
        }

        /**
         * Set the metadata of this instance. {@code null} for unset.
         *
         * @param metadata metadata
         * @return this instance for chaining
         */
        @NonNull
        public Builder setMetadata(@Nullable MediaMetadata metadata) {
            mMetadata = metadata;
            return this;
        }

        /**
         * Sets the start position in milliseconds at which the playback will start.
         * Any negative number is treated as 0.
         *
         * @param position the start position in milliseconds at which the playback will start
         * @return this instance for chaining
         */
        @NonNull
        public Builder setStartPosition(long position) {
            if (position < 0) {
                position = 0;
            }
            mStartPositionMs = position;
            return this;
        }

        /**
         * Sets the end position in milliseconds at which the playback will end.
         * Any negative number is treated as maximum length of the media item.
         *
         * @param position the end position in milliseconds at which the playback will end
         * @return this instance for chaining
         */
        @NonNull
        public Builder setEndPosition(long position) {
            if (position < 0) {
                position = POSITION_UNKNOWN;
            }
            mEndPositionMs = position;
            return this;
        }

        /**
         * Build {@link MediaItem}.
         *
         * @return a new {@link MediaItem}.
         */
        @NonNull
        public MediaItem build() {
            return new MediaItem(this);
        }
    }

    /**
     */
    @RestrictTo(LIBRARY_GROUP)
    public interface OnMetadataChangedListener {
        /**
         * Called when a media item's metadata is changed.
         */
        void onMetadataChanged(@NonNull MediaItem item,
                @Nullable MediaMetadata metadata);
    }

    /**
     */
    @RestrictTo(LIBRARY)
    @Override
    public void onPreParceling(boolean isStream) {
        if (getClass() != MediaItem.class) {
            throw new RuntimeException("MediaItem's subclasses shouldn't be parcelized.");
        }
        super.onPreParceling(isStream);
    }
}