Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for Android Media Session #3080

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
## Changelog

### Version 5.2.2
- Add basic support for Android Media Sessions on ExoPlayer [#3080](https://github.com/react-native-video/react-native-video/pull/3080)

### Version 5.2.0

- Fix for tvOS native audio menu language selector
Expand Down
20 changes: 20 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -435,6 +435,14 @@ To setup DRM please follow [this guide](./DRM.md)

Platforms: Android Exoplayer, iOS

#### enableMediaSession
Controls whether the ExoPlayer creates an Android Media Session. This enables external controls such as hardware buttons (eg. Play/Pause on a tv remote), or Google Assistant

* **false (default)** - No Media Session
* **true** - Create Media Session

Platforms: Android ExoPlayer

#### filter
Add video filter
* **FilterType.NONE (default)** - No Filter
Expand Down Expand Up @@ -545,6 +553,18 @@ maxBitRate={2000000} // 2 megabits

Platforms: Android ExoPlayer, iOS

#### mediaSessionMetadata
If `enableMediaSession` is true, this can be used to pass additional metadata to the session. This metadata can be displayed to the user by Google (eg. Asking Google Assistant "What's Playing?")

Supports `title`, `description`, `subtitle`, and `imageUri`
eg.

```js
mediaSessionMetadata={{ title: "Title", subtitle: "Subtitle", description: "Description", imageUri: "https://image.png" }}
```

Platforms: Android ExoPlayer

#### minLoadRetryCount
Sets the minimum number of times to retry loading data before failing and reporting an error to the application. Useful to recover from transient internet failures.

Expand Down
7 changes: 7 additions & 0 deletions Video.js
Original file line number Diff line number Diff line change
Expand Up @@ -472,6 +472,13 @@ Video.propTypes = {
reportBandwidth: PropTypes.bool,
disableFocus: PropTypes.bool,
controls: PropTypes.bool,
enableMediaSession: PropTypes.bool,
mediaSessionMetadata: PropTypes.shape({
title: PropTypes.string,
subtitle: PropTypes.string,
description: PropTypes.string,
imageUri: PropTypes.string,
}),
audioOnly: PropTypes.bool,
currentTime: PropTypes.number,
fullscreenAutorotate: PropTypes.bool,
Expand Down
1 change: 1 addition & 0 deletions android-exoplayer/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ dependencies {
implementation('com.google.android.exoplayer:exoplayer:2.13.3') {
exclude group: 'com.android.support'
}
implementation('com.google.android.exoplayer:extension-mediasession:2.13.3')

// All support libs must use the same version
implementation "androidx.annotation:annotation:1.1.0"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
import android.net.Uri;
import android.os.Handler;
import android.os.Message;
import android.support.v4.media.MediaDescriptionCompat;
import android.support.v4.media.session.MediaSessionCompat;
import android.text.TextUtils;
import android.util.Log;
import android.view.View;
Expand Down Expand Up @@ -43,6 +45,8 @@
import com.google.android.exoplayer2.drm.FrameworkMediaDrm;
import com.google.android.exoplayer2.drm.HttpMediaDrmCallback;
import com.google.android.exoplayer2.drm.UnsupportedDrmException;
import com.google.android.exoplayer2.ext.mediasession.MediaSessionConnector;
import com.google.android.exoplayer2.ext.mediasession.TimelineQueueNavigator;
import com.google.android.exoplayer2.mediacodec.MediaCodecRenderer;
import com.google.android.exoplayer2.mediacodec.MediaCodecUtil;
import com.google.android.exoplayer2.metadata.Metadata;
Expand Down Expand Up @@ -109,6 +113,9 @@ class ReactExoplayerView extends FrameLayout implements

private ExoPlayerView exoPlayerView;

private MediaSessionCompat mediaSession;
private MediaSessionConnector mediaSessionConnector;

private DataSource.Factory mediaDataSourceFactory;
private SimpleExoPlayer player;
private DefaultTrackSelector trackSelector;
Expand Down Expand Up @@ -157,6 +164,8 @@ class ReactExoplayerView extends FrameLayout implements
private String drmLicenseUrl = null;
private String[] drmLicenseHeader = null;
private boolean controls;
private boolean enableMediaSession = false;
private MediaDescriptionCompat.Builder mediaSessionMetadata = new MediaDescriptionCompat.Builder();
// \ End props

// React
Expand Down Expand Up @@ -425,6 +434,19 @@ public void run() {

PlaybackParameters params = new PlaybackParameters(rate, 1f);
player.setPlaybackParameters(params);

if (enableMediaSession) {
mediaSession = new MediaSessionCompat(getContext(), TAG);
mediaSessionConnector = new MediaSessionConnector(mediaSession);
mediaSessionConnector.setPlayer(player);
mediaSessionConnector.setQueueNavigator(new TimelineQueueNavigator(mediaSession) {
@Override
public MediaDescriptionCompat getMediaDescription(Player player, int windowIndex) {
return mediaSessionMetadata.build();
}
});
mediaSession.setActive(true);
}
}
if (playerNeedsSource && srcUri != null) {
exoPlayerView.invalidateAspectRatio();
Expand Down Expand Up @@ -570,6 +592,10 @@ private void releasePlayer() {
trackSelector = null;
player = null;
}
if (mediaSession != null) {
mediaSession.setActive(false);
mediaSession.release();
}
progressHandler.removeMessages(SHOW_PROGRESS);
themedReactContext.removeLifecycleEventListener(this);
audioBecomingNoisyReceiver.removeListener();
Expand Down Expand Up @@ -762,6 +788,10 @@ public void onPlayerStateChanged(boolean playWhenReady, int playbackState) {
playerControlView.show();
}
setKeepScreenOn(preventsDisplaySleepDuringVideoPlayback);

if (playWhenReady == isPaused) {
eventEmitter.playbackRateChange(playWhenReady ? 1 : 0);
}
break;
case Player.STATE_ENDED:
text += "ended";
Expand Down Expand Up @@ -1292,6 +1322,15 @@ public void setDisableFocus(boolean disableFocus) {
this.disableFocus = disableFocus;
}

public void setEnableMediaSession(boolean enableMediaSession) {
this.enableMediaSession = enableMediaSession;
}

public void setMediaSessionTitle(String title) { this.mediaSessionMetadata.setTitle((title)); }
public void setMediaSessionSubtitle(String subtitle) { this.mediaSessionMetadata.setSubtitle((subtitle)); }
public void setMediaSessionDescription(String description) { this.mediaSessionMetadata.setDescription((description)); }
public void setMediaSessionImage(String uri) { this.mediaSessionMetadata.setMediaUri((Uri.parse(uri))); }

public void setFullscreen(boolean fullscreen) {
if (fullscreen == isFullscreen) {
return; // Avoid generating events when nothing is changing
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,8 @@ public class ReactExoplayerViewManager extends ViewGroupManager<ReactExoplayerVi
private static final String PROP_SELECTED_VIDEO_TRACK_VALUE = "value";
private static final String PROP_HIDE_SHUTTER_VIEW = "hideShutterView";
private static final String PROP_CONTROLS = "controls";
private static final String PROP_ENABLE_MEDIA_SESSION = "enableMediaSession";
private static final String PROP_MEDIA_SESSION_METADATA = "mediaSessionMetadata";

private ReactExoplayerConfig config;

Expand Down Expand Up @@ -314,6 +316,26 @@ public void setControls(final ReactExoplayerView videoView, final boolean contro
videoView.setControls(controls);
}

@ReactProp(name = PROP_ENABLE_MEDIA_SESSION, defaultBoolean = false)
public void setEnabledMediaSession(final ReactExoplayerView videoView, final boolean enableMediaSession) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure, but wanted to double check just in case. Is there a spelling error here?

Suggested change
public void setEnabledMediaSession(final ReactExoplayerView videoView, final boolean enableMediaSession) {
public void setEnableMediaSession(final ReactExoplayerView videoView, final boolean enableMediaSession) {

videoView.setEnableMediaSession(enableMediaSession);
}

@ReactProp(name = PROP_MEDIA_SESSION_METADATA, defaultBoolean = false)
public void setMediaSessionMetadata(final ReactExoplayerView videoView, @Nullable ReadableMap mediaSessionMetadata) {
if (mediaSessionMetadata == null) return;

String title = mediaSessionMetadata.getString("title");
String subtitle = mediaSessionMetadata.getString("subtitle");
String description = mediaSessionMetadata.getString("description");
String imageUri = mediaSessionMetadata.getString("imageUri");

if (title != null) { videoView.setMediaSessionTitle(title); }
if (subtitle != null) { videoView.setMediaSessionSubtitle(subtitle); }
if (description != null) { videoView.setMediaSessionDescription(description); }
if (imageUri != null) { videoView.setMediaSessionImage(imageUri); }
}

@ReactProp(name = PROP_BUFFER_CONFIG)
public void setBufferConfig(final ReactExoplayerView videoView, @Nullable ReadableMap bufferConfig) {
int minBufferMs = DefaultLoadControl.DEFAULT_MIN_BUFFER_MS;
Expand Down