Compare commits

...

5 commits

Author SHA1 Message Date
ivanbuper
6604afcb3a Add Kotlin dependencies to missing_aar_type_workaround.gradle
#cherrypick

PiperOrigin-RevId: 671736852
(cherry picked from commit a1357befff)
2024-09-06 15:07:08 +01:00
aquilescanta
afc72880e9 Populate DeviceInfo in CastPlayer using MediaRouter2 info
This enables linking the media session to a routing session.

Issue: androidx/media#1056
PiperOrigin-RevId: 671425490
(cherry picked from commit 4ea58a133e)
2024-09-05 19:04:22 +01:00
aquilescanta
4e8f17a7cb Do not clear the timeline after the Cast receiver disconnects
The goal is to enable the app to fetch the timeline after a
disconnection in order to prepare and resume local playback.

PiperOrigin-RevId: 671022044
(cherry picked from commit a00c446529)
2024-09-05 19:02:28 +01:00
ivanbuper
4e305729a0 Bump Media3 version to 1.5.0-alpha01
PiperOrigin-RevId: 670535221
(cherry picked from commit 9562c976a9)
2024-09-03 14:55:00 +01:00
ivanbuper
9c13301b0a Update release notes for Media3 1.5.0-alpha01 release
PiperOrigin-RevId: 670523759
(cherry picked from commit e16b4fff8d)
2024-09-03 14:31:59 +01:00
6 changed files with 167 additions and 31 deletions

View file

@ -1,6 +1,11 @@
# Release notes # Release notes
### Unreleased changes ## 1.5
### 1.5.0-alpha01 (2024-09-06)
This release includes the following changes since the
[1.4.1 release](#141-2024-08-23):
* Common Library: * Common Library:
* Add `ForwardingSimpleBasePlayer` that allows forwarding to another * Add `ForwardingSimpleBasePlayer` that allows forwarding to another
@ -46,7 +51,6 @@
Transformer via a `Surface`. Transformer via a `Surface`.
* `ImageAssetLoader` reports unsupported input via `AssetLoader.onError` * `ImageAssetLoader` reports unsupported input via `AssetLoader.onError`
instead of throwing an `IllegalStateException`. instead of throwing an `IllegalStateException`.
* Track Selection:
* Extractors: * Extractors:
* Allow `Mp4Extractor` and `FragmentedMp4Extractor` to identify H264 * Allow `Mp4Extractor` and `FragmentedMp4Extractor` to identify H264
samples that are not used as reference by subsequent samples. samples that are not used as reference by subsequent samples.
@ -79,7 +83,6 @@
* Add a custom `VoiceSpan` and populate it for * Add a custom `VoiceSpan` and populate it for
[WebVTT voice spans](https://www.w3.org/TR/webvtt1/#webvtt-cue-voice-span) [WebVTT voice spans](https://www.w3.org/TR/webvtt1/#webvtt-cue-voice-span)
([#1632](https://github.com/androidx/media/issues/1632)). ([#1632](https://github.com/androidx/media/issues/1632)).
* Metadata:
* Image: * Image:
* Add `ExternallyLoadedImageDecoder` for simplified integration with * Add `ExternallyLoadedImageDecoder` for simplified integration with
external image loading libraries like Glide or Coil. external image loading libraries like Glide or Coil.
@ -87,7 +90,6 @@
* Add `FileDescriptorDataSource`, a new `DataSource` that can be used to * Add `FileDescriptorDataSource`, a new `DataSource` that can be used to
read from a `FileDescriptor` read from a `FileDescriptor`
([#3757](https://github.com/google/ExoPlayer/issues/3757)). ([#3757](https://github.com/google/ExoPlayer/issues/3757)).
* DRM:
* Effect: * Effect:
* Add `DefaultVideoFrameProcessor` workaround for minor `SurfaceTexture` * Add `DefaultVideoFrameProcessor` workaround for minor `SurfaceTexture`
scaling. `SurfaceTexture` may include a small scaling that cuts off a scaling. `SurfaceTexture` may include a small scaling that cuts off a
@ -95,7 +97,6 @@
such that output is closer to expected. such that output is closer to expected.
* Speed up `DefaultVideoFrameProcessor.queueInputBitmap()`. As a result, * Speed up `DefaultVideoFrameProcessor.queueInputBitmap()`. As a result,
exporting images to videos with `Transformer` is faster. exporting images to videos with `Transformer` is faster.
* Muxers:
* IMA extension: * IMA extension:
* Fix bug where clearing the playlist may cause an * Fix bug where clearing the playlist may cause an
`ArrayIndexOutOfBoundsException` in `ArrayIndexOutOfBoundsException` in
@ -107,17 +108,9 @@
playback can't be suppressed without the system crashing the service playback can't be suppressed without the system crashing the service
with a `ForegroundServiceDidNotStartInTimeException` with a `ForegroundServiceDidNotStartInTimeException`
([#1528](https://github.com/google/ExoPlayer/issues/1528)). ([#1528](https://github.com/google/ExoPlayer/issues/1528)).
* UI:
* Downloads:
* OkHttp Extension:
* Cronet Extension:
* RTMP Extension:
* HLS Extension:
* DASH Extension: * DASH Extension:
* Add support for periods starting in the middle of a segment * Add support for periods starting in the middle of a segment
([#1440](https://github.com/androidx/media/issues/1440)). ([#1440](https://github.com/androidx/media/issues/1440)).
* Smooth Streaming Extension:
* RTSP Extension:
* Decoder Extensions (FFmpeg, VP9, AV1, etc.): * Decoder Extensions (FFmpeg, VP9, AV1, etc.):
* Add the IAMF decoder module, which provides support for playback of MP4 * Add the IAMF decoder module, which provides support for playback of MP4
files containing IAMF tracks using the libiamf native library to files containing IAMF tracks using the libiamf native library to
@ -125,14 +118,17 @@
* Playback is enabled with a stereo layout as well as 5.1 with * Playback is enabled with a stereo layout as well as 5.1 with
spatialization together with optional head tracking enabled, but spatialization together with optional head tracking enabled, but
binaural playback support is currently not available. binaural playback support is currently not available.
* MIDI extension:
* Leanback extension:
* Cast Extension: * Cast Extension:
* Stop clearning the timeline after the CastSession disconnects, which
enables the sender app to resume playback locally after a disconnection.
* Populate CastPlayer's `DeviceInfo` when a `Context` is provided. This
enables linking the `MediaSession` to a `RoutingSession`, which is
necessary for integrating Output Switcher
([#1056](https://github.com/androidx/media/issues/1056)).
* Test Utilities: * Test Utilities:
* `DataSourceContractTest` now includes tests to verify: * `DataSourceContractTest` now includes tests to verify:
* Input stream `read position` is updated. * Input stream `read position` is updated.
* Output buffer `offset` is applied correctly. * Output buffer `offset` is applied correctly.
* Demo app:
* Remove deprecated symbols: * Remove deprecated symbols:
* Remove deprecated `Player.hasPrevious`, `Player.hasPreviousWindow()`. * Remove deprecated `Player.hasPrevious`, `Player.hasPreviousWindow()`.
Use `Player.hasPreviousMediaItem()` instead. Use `Player.hasPreviousMediaItem()` instead.

View file

@ -12,8 +12,8 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
project.ext { project.ext {
releaseVersion = '1.4.1' releaseVersion = '1.5.0-alpha01'
releaseVersionCode = 1_004_001_3_00 releaseVersionCode = 1_005_000_0_01
minSdkVersion = 21 minSdkVersion = 21
// See https://developer.android.com/training/cars/media/automotive-os#automotive-module // See https://developer.android.com/training/cars/media/automotive-os#automotive-module
automotiveMinSdkVersion = 28 automotiveMinSdkVersion = 28

View file

@ -17,16 +17,26 @@ package androidx.media3.cast;
import static androidx.annotation.VisibleForTesting.PROTECTED; import static androidx.annotation.VisibleForTesting.PROTECTED;
import static androidx.media3.common.util.Assertions.checkArgument; import static androidx.media3.common.util.Assertions.checkArgument;
import static androidx.media3.common.util.Util.SDK_INT;
import static androidx.media3.common.util.Util.castNonNull; import static androidx.media3.common.util.Util.castNonNull;
import static java.lang.Math.min; import static java.lang.Math.min;
import android.content.Context;
import android.media.MediaRouter2;
import android.media.MediaRouter2.RouteCallback;
import android.media.MediaRouter2.RoutingController;
import android.media.MediaRouter2.TransferCallback;
import android.media.RouteDiscoveryPreference;
import android.os.Handler;
import android.os.Looper; import android.os.Looper;
import android.view.Surface; import android.view.Surface;
import android.view.SurfaceHolder; import android.view.SurfaceHolder;
import android.view.SurfaceView; import android.view.SurfaceView;
import android.view.TextureView; import android.view.TextureView;
import androidx.annotation.DoNotInline;
import androidx.annotation.IntRange; import androidx.annotation.IntRange;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.annotation.VisibleForTesting; import androidx.annotation.VisibleForTesting;
import androidx.media3.common.AudioAttributes; import androidx.media3.common.AudioAttributes;
import androidx.media3.common.BasePlayer; import androidx.media3.common.BasePlayer;
@ -83,8 +93,11 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
@UnstableApi @UnstableApi
public final class CastPlayer extends BasePlayer { public final class CastPlayer extends BasePlayer {
/** The {@link DeviceInfo} returned by {@link #getDeviceInfo() this player}. */ /**
public static final DeviceInfo DEVICE_INFO = * A {@link DeviceInfo#PLAYBACK_TYPE_REMOTE remote} {@link DeviceInfo} with a null {@link
* DeviceInfo#routingControllerId}.
*/
public static final DeviceInfo DEVICE_INFO_REMOTE_EMPTY =
new DeviceInfo.Builder(DeviceInfo.PLAYBACK_TYPE_REMOTE).build(); new DeviceInfo.Builder(DeviceInfo.PLAYBACK_TYPE_REMOTE).build();
static { static {
@ -128,6 +141,7 @@ public final class CastPlayer extends BasePlayer {
// TODO: Allow custom implementations of CastTimelineTracker. // TODO: Allow custom implementations of CastTimelineTracker.
private final CastTimelineTracker timelineTracker; private final CastTimelineTracker timelineTracker;
private final Timeline.Period period; private final Timeline.Period period;
@Nullable private final Api30Impl api30Impl;
// Result callbacks. // Result callbacks.
private final StatusListener statusListener; private final StatusListener statusListener;
@ -153,6 +167,7 @@ public final class CastPlayer extends BasePlayer {
private long pendingSeekPositionMs; private long pendingSeekPositionMs;
@Nullable private PositionInfo pendingMediaItemRemovalPosition; @Nullable private PositionInfo pendingMediaItemRemovalPosition;
private MediaMetadata mediaMetadata; private MediaMetadata mediaMetadata;
private DeviceInfo deviceInfo;
/** /**
* Creates a new cast player. * Creates a new cast player.
@ -202,6 +217,7 @@ public final class CastPlayer extends BasePlayer {
@IntRange(from = 1) long seekBackIncrementMs, @IntRange(from = 1) long seekBackIncrementMs,
@IntRange(from = 1) long seekForwardIncrementMs) { @IntRange(from = 1) long seekForwardIncrementMs) {
this( this(
/* context= */ null,
castContext, castContext,
mediaItemConverter, mediaItemConverter,
seekBackIncrementMs, seekBackIncrementMs,
@ -212,6 +228,8 @@ public final class CastPlayer extends BasePlayer {
/** /**
* Creates a new cast player. * Creates a new cast player.
* *
* @param context A {@link Context} used to populate {@link #getDeviceInfo()}. If null, {@link
* #getDeviceInfo()} will always return {@link #DEVICE_INFO_REMOTE_EMPTY}.
* @param castContext The context from which the cast session is obtained. * @param castContext The context from which the cast session is obtained.
* @param mediaItemConverter The {@link MediaItemConverter} to use. * @param mediaItemConverter The {@link MediaItemConverter} to use.
* @param seekBackIncrementMs The {@link #seekBack()} increment, in milliseconds. * @param seekBackIncrementMs The {@link #seekBack()} increment, in milliseconds.
@ -223,6 +241,7 @@ public final class CastPlayer extends BasePlayer {
* negative. * negative.
*/ */
public CastPlayer( public CastPlayer(
@Nullable Context context,
CastContext castContext, CastContext castContext,
MediaItemConverter mediaItemConverter, MediaItemConverter mediaItemConverter,
@IntRange(from = 1) long seekBackIncrementMs, @IntRange(from = 1) long seekBackIncrementMs,
@ -260,6 +279,14 @@ public final class CastPlayer extends BasePlayer {
CastSession session = sessionManager.getCurrentCastSession(); CastSession session = sessionManager.getCurrentCastSession();
setRemoteMediaClient(session != null ? session.getRemoteMediaClient() : null); setRemoteMediaClient(session != null ? session.getRemoteMediaClient() : null);
updateInternalStateAndNotifyIfChanged(); updateInternalStateAndNotifyIfChanged();
if (SDK_INT >= 30 && context != null) {
api30Impl = new Api30Impl(context);
api30Impl.initialize();
deviceInfo = api30Impl.fetchDeviceInfo();
} else {
api30Impl = null;
deviceInfo = DEVICE_INFO_REMOTE_EMPTY;
}
} }
/** /**
@ -530,6 +557,10 @@ public final class CastPlayer extends BasePlayer {
@Override @Override
public void release() { public void release() {
// The SDK_INT check is not necessary, but it prevents a lint error for the release call.
if (SDK_INT >= 30 && api30Impl != null) {
api30Impl.release();
}
SessionManager sessionManager = castContext.getSessionManager(); SessionManager sessionManager = castContext.getSessionManager();
sessionManager.removeSessionManagerListener(statusListener, CastSession.class); sessionManager.removeSessionManagerListener(statusListener, CastSession.class);
sessionManager.endCurrentSession(false); sessionManager.endCurrentSession(false);
@ -782,10 +813,14 @@ public final class CastPlayer extends BasePlayer {
return CueGroup.EMPTY_TIME_ZERO; return CueGroup.EMPTY_TIME_ZERO;
} }
/** This method always returns {@link CastPlayer#DEVICE_INFO}. */ /**
* Returns a {@link DeviceInfo} describing the receiver device. Returns {@link
* #DEVICE_INFO_REMOTE_EMPTY} if no {@link Context} was provided at construction, or if the Cast
* {@link RoutingController} could not be identified.
*/
@Override @Override
public DeviceInfo getDeviceInfo() { public DeviceInfo getDeviceInfo() {
return DEVICE_INFO; return deviceInfo;
} }
/** This method is not supported and always returns {@code 0}. */ /** This method is not supported and always returns {@code 0}. */
@ -1283,11 +1318,8 @@ public final class CastPlayer extends BasePlayer {
remoteMediaClient.registerCallback(statusListener); remoteMediaClient.registerCallback(statusListener);
remoteMediaClient.addProgressListener(statusListener, PROGRESS_REPORT_PERIOD_MS); remoteMediaClient.addProgressListener(statusListener, PROGRESS_REPORT_PERIOD_MS);
updateInternalStateAndNotifyIfChanged(); updateInternalStateAndNotifyIfChanged();
} else { } else if (sessionAvailabilityListener != null) {
updateTimelineAndNotifyIfChanged(); sessionAvailabilityListener.onCastSessionUnavailable();
if (sessionAvailabilityListener != null) {
sessionAvailabilityListener.onCastSessionUnavailable();
}
} }
} }
@ -1537,4 +1569,109 @@ public final class CastPlayer extends BasePlayer {
return pendingResultCallback == resultCallback; return pendingResultCallback == resultCallback;
} }
} }
@RequiresApi(30)
private final class Api30Impl {
private final MediaRouter2 mediaRouter2;
private final TransferCallback transferCallback;
private final RouteCallback emptyRouteCallback;
private final Handler handler;
public Api30Impl(Context context) {
mediaRouter2 = MediaRouter2.getInstance(context);
transferCallback = new MediaRouter2TransferCallbackImpl();
emptyRouteCallback = new MediaRouter2RouteCallbackImpl();
handler = new Handler(Looper.getMainLooper());
}
/** Acquires necessary resources and registers callbacks. */
@DoNotInline
public void initialize() {
mediaRouter2.registerTransferCallback(handler::post, transferCallback);
// We need at least one route callback registered in order to get transfer callback updates.
mediaRouter2.registerRouteCallback(
handler::post,
emptyRouteCallback,
new RouteDiscoveryPreference.Builder(ImmutableList.of(), /* activeScan= */ false)
.build());
}
/**
* Releases any resources acquired in {@link #initialize()} and unregisters any registered
* callbacks.
*/
@DoNotInline
public void release() {
mediaRouter2.unregisterTransferCallback(transferCallback);
mediaRouter2.unregisterRouteCallback(emptyRouteCallback);
handler.removeCallbacksAndMessages(/* token= */ null);
}
/** Updates the device info with an up-to-date value and notifies the listeners. */
@DoNotInline
private void updateDeviceInfo() {
DeviceInfo oldDeviceInfo = deviceInfo;
DeviceInfo newDeviceInfo = fetchDeviceInfo();
deviceInfo = newDeviceInfo;
if (!deviceInfo.equals(oldDeviceInfo)) {
listeners.sendEvent(
EVENT_DEVICE_INFO_CHANGED, listener -> listener.onDeviceInfoChanged(newDeviceInfo));
}
}
/**
* Returns a {@link DeviceInfo} with the {@link RoutingController#getId() id} that corresponds
* to the Cast session, or {@link #DEVICE_INFO_REMOTE_EMPTY} if not available.
*/
@DoNotInline
public DeviceInfo fetchDeviceInfo() {
// TODO: b/364833997 - Fetch this information from the AndroidX MediaRouter selected route
// once the selected route id matches the controller id.
List<RoutingController> controllers = mediaRouter2.getControllers();
// The controller at position zero is always the system controller (local playback). All other
// controllers are for remote playback, and could be the Cast one.
if (controllers.size() != 2) {
// There's either no remote routing controller, or there's more than one. In either case we
// don't populate the device info because either there's no Cast routing controller, or we
// cannot safely identify the Cast routing controller.
return DEVICE_INFO_REMOTE_EMPTY;
} else {
// There's only one remote routing controller. It's safe to assume it's the Cast routing
// controller.
RoutingController remoteController = controllers.get(1);
// TODO b/364580007 - Populate volume information, and implement Player volume-related
// methods.
return new DeviceInfo.Builder(DeviceInfo.PLAYBACK_TYPE_REMOTE)
.setRoutingControllerId(remoteController.getId())
.build();
}
}
/**
* Empty {@link RouteCallback} implementation necessary for registering the {@link MediaRouter2}
* instance with the system_server.
*
* <p>This callback must be registered so that the media router service notifies the {@link
* MediaRouter2TransferCallbackImpl} of transfer events.
*/
private final class MediaRouter2RouteCallbackImpl extends RouteCallback {}
/**
* {@link TransferCallback} implementation to listen for {@link RoutingController} creation and
* releases.
*/
private final class MediaRouter2TransferCallbackImpl extends TransferCallback {
@Override
public void onTransfer(RoutingController oldController, RoutingController newController) {
updateDeviceInfo();
}
@Override
public void onStop(RoutingController controller) {
updateDeviceInfo();
}
}
}
} }

View file

@ -1902,7 +1902,7 @@ public class CastPlayerTest {
public void getDeviceInfo_returnsCorrectDeviceInfoWithPlaybackTypeRemote() { public void getDeviceInfo_returnsCorrectDeviceInfoWithPlaybackTypeRemote() {
DeviceInfo deviceInfo = castPlayer.getDeviceInfo(); DeviceInfo deviceInfo = castPlayer.getDeviceInfo();
assertThat(deviceInfo).isEqualTo(CastPlayer.DEVICE_INFO); assertThat(deviceInfo).isEqualTo(CastPlayer.DEVICE_INFO_REMOTE_EMPTY);
assertThat(deviceInfo.playbackType).isEqualTo(DeviceInfo.PLAYBACK_TYPE_REMOTE); assertThat(deviceInfo.playbackType).isEqualTo(DeviceInfo.PLAYBACK_TYPE_REMOTE);
} }

View file

@ -29,11 +29,11 @@ public final class MediaLibraryInfo {
/** The version of the library expressed as a string, for example "1.2.3" or "1.2.0-beta01". */ /** The version of the library expressed as a string, for example "1.2.3" or "1.2.0-beta01". */
// Intentionally hardcoded. Do not derive from other constants (e.g. VERSION_INT) or vice versa. // Intentionally hardcoded. Do not derive from other constants (e.g. VERSION_INT) or vice versa.
public static final String VERSION = "1.4.1"; public static final String VERSION = "1.5.0-alpha01";
/** The version of the library expressed as {@code TAG + "/" + VERSION}. */ /** The version of the library expressed as {@code TAG + "/" + VERSION}. */
// Intentionally hardcoded. Do not derive from other constants (e.g. VERSION) or vice versa. // Intentionally hardcoded. Do not derive from other constants (e.g. VERSION) or vice versa.
public static final String VERSION_SLASHY = "AndroidXMedia3/1.4.1"; public static final String VERSION_SLASHY = "AndroidXMedia3/1.5.0-alpha01";
/** /**
* The version of the library expressed as an integer, for example 1002003300. * The version of the library expressed as an integer, for example 1002003300.
@ -47,7 +47,7 @@ public final class MediaLibraryInfo {
* (123-045-006-3-00). * (123-045-006-3-00).
*/ */
// Intentionally hardcoded. Do not derive from other constants (e.g. VERSION) or vice versa. // Intentionally hardcoded. Do not derive from other constants (e.g. VERSION) or vice versa.
public static final int VERSION_INT = 1_004_001_3_00; public static final int VERSION_INT = 1_005_000_0_01;
/** Whether the library was compiled with {@link Assertions} checks enabled. */ /** Whether the library was compiled with {@link Assertions} checks enabled. */
public static final boolean ASSERTIONS_ENABLED = true; public static final boolean ASSERTIONS_ENABLED = true;

View file

@ -30,6 +30,9 @@ def addMissingAarTypeToXml(xml) {
"org.mockito:mockito-core", "org.mockito:mockito-core",
"org.robolectric:robolectric", "org.robolectric:robolectric",
"com.github.philburk:jsyn", "com.github.philburk:jsyn",
"org.jetbrains.kotlin:kotlin-stdlib-jdk8",
"org.jetbrains.kotlinx:kotlinx-coroutines-core",
"org.jetbrains.kotlinx:kotlinx-coroutines-android",
] ]
// Dependencies that have AAR files. // Dependencies that have AAR files.
def aar_dependencies = [ def aar_dependencies = [