mirror of
https://github.com/samsonjs/media.git
synced 2026-03-25 09:25:53 +00:00
Compare commits
5 commits
release
...
1.5.0-alph
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6604afcb3a | ||
|
|
afc72880e9 | ||
|
|
4e8f17a7cb | ||
|
|
4e305729a0 | ||
|
|
9c13301b0a |
6 changed files with 167 additions and 31 deletions
|
|
@ -1,6 +1,11 @@
|
|||
# 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:
|
||||
* Add `ForwardingSimpleBasePlayer` that allows forwarding to another
|
||||
|
|
@ -46,7 +51,6 @@
|
|||
Transformer via a `Surface`.
|
||||
* `ImageAssetLoader` reports unsupported input via `AssetLoader.onError`
|
||||
instead of throwing an `IllegalStateException`.
|
||||
* Track Selection:
|
||||
* Extractors:
|
||||
* Allow `Mp4Extractor` and `FragmentedMp4Extractor` to identify H264
|
||||
samples that are not used as reference by subsequent samples.
|
||||
|
|
@ -79,7 +83,6 @@
|
|||
* Add a custom `VoiceSpan` and populate it for
|
||||
[WebVTT voice spans](https://www.w3.org/TR/webvtt1/#webvtt-cue-voice-span)
|
||||
([#1632](https://github.com/androidx/media/issues/1632)).
|
||||
* Metadata:
|
||||
* Image:
|
||||
* Add `ExternallyLoadedImageDecoder` for simplified integration with
|
||||
external image loading libraries like Glide or Coil.
|
||||
|
|
@ -87,7 +90,6 @@
|
|||
* Add `FileDescriptorDataSource`, a new `DataSource` that can be used to
|
||||
read from a `FileDescriptor`
|
||||
([#3757](https://github.com/google/ExoPlayer/issues/3757)).
|
||||
* DRM:
|
||||
* Effect:
|
||||
* Add `DefaultVideoFrameProcessor` workaround for minor `SurfaceTexture`
|
||||
scaling. `SurfaceTexture` may include a small scaling that cuts off a
|
||||
|
|
@ -95,7 +97,6 @@
|
|||
such that output is closer to expected.
|
||||
* Speed up `DefaultVideoFrameProcessor.queueInputBitmap()`. As a result,
|
||||
exporting images to videos with `Transformer` is faster.
|
||||
* Muxers:
|
||||
* IMA extension:
|
||||
* Fix bug where clearing the playlist may cause an
|
||||
`ArrayIndexOutOfBoundsException` in
|
||||
|
|
@ -107,17 +108,9 @@
|
|||
playback can't be suppressed without the system crashing the service
|
||||
with a `ForegroundServiceDidNotStartInTimeException`
|
||||
([#1528](https://github.com/google/ExoPlayer/issues/1528)).
|
||||
* UI:
|
||||
* Downloads:
|
||||
* OkHttp Extension:
|
||||
* Cronet Extension:
|
||||
* RTMP Extension:
|
||||
* HLS Extension:
|
||||
* DASH Extension:
|
||||
* Add support for periods starting in the middle of a segment
|
||||
([#1440](https://github.com/androidx/media/issues/1440)).
|
||||
* Smooth Streaming Extension:
|
||||
* RTSP Extension:
|
||||
* Decoder Extensions (FFmpeg, VP9, AV1, etc.):
|
||||
* Add the IAMF decoder module, which provides support for playback of MP4
|
||||
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
|
||||
spatialization together with optional head tracking enabled, but
|
||||
binaural playback support is currently not available.
|
||||
* MIDI extension:
|
||||
* Leanback 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:
|
||||
* `DataSourceContractTest` now includes tests to verify:
|
||||
* Input stream `read position` is updated.
|
||||
* Output buffer `offset` is applied correctly.
|
||||
* Demo app:
|
||||
* Remove deprecated symbols:
|
||||
* Remove deprecated `Player.hasPrevious`, `Player.hasPreviousWindow()`.
|
||||
Use `Player.hasPreviousMediaItem()` instead.
|
||||
|
|
|
|||
|
|
@ -12,8 +12,8 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
project.ext {
|
||||
releaseVersion = '1.4.1'
|
||||
releaseVersionCode = 1_004_001_3_00
|
||||
releaseVersion = '1.5.0-alpha01'
|
||||
releaseVersionCode = 1_005_000_0_01
|
||||
minSdkVersion = 21
|
||||
// See https://developer.android.com/training/cars/media/automotive-os#automotive-module
|
||||
automotiveMinSdkVersion = 28
|
||||
|
|
|
|||
|
|
@ -17,16 +17,26 @@ package androidx.media3.cast;
|
|||
|
||||
import static androidx.annotation.VisibleForTesting.PROTECTED;
|
||||
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 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.view.Surface;
|
||||
import android.view.SurfaceHolder;
|
||||
import android.view.SurfaceView;
|
||||
import android.view.TextureView;
|
||||
import androidx.annotation.DoNotInline;
|
||||
import androidx.annotation.IntRange;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.RequiresApi;
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
import androidx.media3.common.AudioAttributes;
|
||||
import androidx.media3.common.BasePlayer;
|
||||
|
|
@ -83,8 +93,11 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
|||
@UnstableApi
|
||||
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();
|
||||
|
||||
static {
|
||||
|
|
@ -128,6 +141,7 @@ public final class CastPlayer extends BasePlayer {
|
|||
// TODO: Allow custom implementations of CastTimelineTracker.
|
||||
private final CastTimelineTracker timelineTracker;
|
||||
private final Timeline.Period period;
|
||||
@Nullable private final Api30Impl api30Impl;
|
||||
|
||||
// Result callbacks.
|
||||
private final StatusListener statusListener;
|
||||
|
|
@ -153,6 +167,7 @@ public final class CastPlayer extends BasePlayer {
|
|||
private long pendingSeekPositionMs;
|
||||
@Nullable private PositionInfo pendingMediaItemRemovalPosition;
|
||||
private MediaMetadata mediaMetadata;
|
||||
private DeviceInfo deviceInfo;
|
||||
|
||||
/**
|
||||
* Creates a new cast player.
|
||||
|
|
@ -202,6 +217,7 @@ public final class CastPlayer extends BasePlayer {
|
|||
@IntRange(from = 1) long seekBackIncrementMs,
|
||||
@IntRange(from = 1) long seekForwardIncrementMs) {
|
||||
this(
|
||||
/* context= */ null,
|
||||
castContext,
|
||||
mediaItemConverter,
|
||||
seekBackIncrementMs,
|
||||
|
|
@ -212,6 +228,8 @@ public final class CastPlayer extends BasePlayer {
|
|||
/**
|
||||
* 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 mediaItemConverter The {@link MediaItemConverter} to use.
|
||||
* @param seekBackIncrementMs The {@link #seekBack()} increment, in milliseconds.
|
||||
|
|
@ -223,6 +241,7 @@ public final class CastPlayer extends BasePlayer {
|
|||
* negative.
|
||||
*/
|
||||
public CastPlayer(
|
||||
@Nullable Context context,
|
||||
CastContext castContext,
|
||||
MediaItemConverter mediaItemConverter,
|
||||
@IntRange(from = 1) long seekBackIncrementMs,
|
||||
|
|
@ -260,6 +279,14 @@ public final class CastPlayer extends BasePlayer {
|
|||
CastSession session = sessionManager.getCurrentCastSession();
|
||||
setRemoteMediaClient(session != null ? session.getRemoteMediaClient() : null);
|
||||
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
|
||||
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.removeSessionManagerListener(statusListener, CastSession.class);
|
||||
sessionManager.endCurrentSession(false);
|
||||
|
|
@ -782,10 +813,14 @@ public final class CastPlayer extends BasePlayer {
|
|||
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
|
||||
public DeviceInfo getDeviceInfo() {
|
||||
return DEVICE_INFO;
|
||||
return deviceInfo;
|
||||
}
|
||||
|
||||
/** This method is not supported and always returns {@code 0}. */
|
||||
|
|
@ -1283,11 +1318,8 @@ public final class CastPlayer extends BasePlayer {
|
|||
remoteMediaClient.registerCallback(statusListener);
|
||||
remoteMediaClient.addProgressListener(statusListener, PROGRESS_REPORT_PERIOD_MS);
|
||||
updateInternalStateAndNotifyIfChanged();
|
||||
} else {
|
||||
updateTimelineAndNotifyIfChanged();
|
||||
if (sessionAvailabilityListener != null) {
|
||||
sessionAvailabilityListener.onCastSessionUnavailable();
|
||||
}
|
||||
} else if (sessionAvailabilityListener != null) {
|
||||
sessionAvailabilityListener.onCastSessionUnavailable();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1537,4 +1569,109 @@ public final class CastPlayer extends BasePlayer {
|
|||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1902,7 +1902,7 @@ public class CastPlayerTest {
|
|||
public void getDeviceInfo_returnsCorrectDeviceInfoWithPlaybackTypeRemote() {
|
||||
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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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". */
|
||||
// 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}. */
|
||||
// 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.
|
||||
|
|
@ -47,7 +47,7 @@ public final class MediaLibraryInfo {
|
|||
* (123-045-006-3-00).
|
||||
*/
|
||||
// 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. */
|
||||
public static final boolean ASSERTIONS_ENABLED = true;
|
||||
|
|
|
|||
|
|
@ -30,6 +30,9 @@ def addMissingAarTypeToXml(xml) {
|
|||
"org.mockito:mockito-core",
|
||||
"org.robolectric:robolectric",
|
||||
"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.
|
||||
def aar_dependencies = [
|
||||
|
|
|
|||
Loading…
Reference in a new issue