mirror of
https://github.com/servo/servo.git
synced 2025-08-05 05:30:08 +01:00
MediaSession show media controls on Android
This commit is contained in:
parent
761f21fc8b
commit
dd63ba425f
11 changed files with 183 additions and 26 deletions
|
@ -224,9 +224,9 @@ pub struct MediaMetadata {
|
||||||
pub enum MediaSessionPlaybackState {
|
pub enum MediaSessionPlaybackState {
|
||||||
/// The browsing context does not specify whether it’s playing or paused.
|
/// The browsing context does not specify whether it’s playing or paused.
|
||||||
None_ = 1,
|
None_ = 1,
|
||||||
/// The browsing context has paused media and it can be resumed.
|
|
||||||
Playing,
|
|
||||||
/// The browsing context is currently playing media and it can be paused.
|
/// The browsing context is currently playing media and it can be paused.
|
||||||
|
Playing,
|
||||||
|
/// The browsing context has paused media and it can be resumed.
|
||||||
Paused,
|
Paused,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1804,9 +1804,15 @@ impl HTMLMediaElement {
|
||||||
PlaybackState::Playing => {
|
PlaybackState::Playing => {
|
||||||
media_session_playback_state = MediaSessionPlaybackState::Playing;
|
media_session_playback_state = MediaSessionPlaybackState::Playing;
|
||||||
},
|
},
|
||||||
|
PlaybackState::Buffering => {
|
||||||
|
// Do not send the media session playback state change event
|
||||||
|
// in this case as a None_ state is expected to clean up the
|
||||||
|
// session.
|
||||||
|
return;
|
||||||
|
},
|
||||||
_ => {},
|
_ => {},
|
||||||
};
|
};
|
||||||
println!(
|
debug!(
|
||||||
"Sending media session event playback state changed to {:?}",
|
"Sending media session event playback state changed to {:?}",
|
||||||
media_session_playback_state
|
media_session_playback_state
|
||||||
);
|
);
|
||||||
|
|
|
@ -131,12 +131,7 @@ pub trait HostTrait {
|
||||||
/// Sets system clipboard contents.
|
/// Sets system clipboard contents.
|
||||||
fn set_clipboard_contents(&self, contents: String);
|
fn set_clipboard_contents(&self, contents: String);
|
||||||
/// Called when we get the media session metadata/
|
/// Called when we get the media session metadata/
|
||||||
fn on_media_session_metadata(
|
fn on_media_session_metadata(&self, title: String, artist: String, album: String);
|
||||||
&self,
|
|
||||||
title: String,
|
|
||||||
artist: Option<String>,
|
|
||||||
album: Option<String>,
|
|
||||||
);
|
|
||||||
/// Called when the media sessoin playback state changes.
|
/// Called when the media sessoin playback state changes.
|
||||||
fn on_media_session_playback_state_change(&self, state: i32);
|
fn on_media_session_playback_state_change(&self, state: i32);
|
||||||
}
|
}
|
||||||
|
@ -595,8 +590,8 @@ impl ServoGlue {
|
||||||
MediaSessionEvent::SetMetadata(metadata) => {
|
MediaSessionEvent::SetMetadata(metadata) => {
|
||||||
self.callbacks.host_callbacks.on_media_session_metadata(
|
self.callbacks.host_callbacks.on_media_session_metadata(
|
||||||
metadata.title,
|
metadata.title,
|
||||||
metadata.artist,
|
metadata.artist.unwrap_or("".to_owned()),
|
||||||
metadata.album,
|
metadata.album.unwrap_or("".to_owned()),
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
MediaSessionEvent::PlaybackStateChange(state) => self
|
MediaSessionEvent::PlaybackStateChange(state) => self
|
||||||
|
|
|
@ -509,15 +509,47 @@ impl HostTrait for HostCallbacks {
|
||||||
|
|
||||||
fn set_clipboard_contents(&self, _contents: String) {}
|
fn set_clipboard_contents(&self, _contents: String) {}
|
||||||
|
|
||||||
fn on_media_session_metadata(
|
fn on_media_session_metadata(&self, title: String, artist: String, album: String) {
|
||||||
&self,
|
info!("on_media_session_metadata");
|
||||||
_title: String,
|
let env = self.jvm.get_env().unwrap();
|
||||||
_artist: Option<String>,
|
let title = match new_string(&env, &title) {
|
||||||
_album: Option<String>,
|
Ok(s) => s,
|
||||||
) {
|
Err(_) => return,
|
||||||
|
};
|
||||||
|
let title = JValue::Object(JObject::from(title));
|
||||||
|
|
||||||
|
let artist = match new_string(&env, &artist) {
|
||||||
|
Ok(s) => s,
|
||||||
|
Err(_) => return,
|
||||||
|
};
|
||||||
|
let artist = JValue::Object(JObject::from(artist));
|
||||||
|
|
||||||
|
let album = match new_string(&env, &album) {
|
||||||
|
Ok(s) => s,
|
||||||
|
Err(_) => return,
|
||||||
|
};
|
||||||
|
let album = JValue::Object(JObject::from(album));
|
||||||
|
env.call_method(
|
||||||
|
self.callbacks.as_obj(),
|
||||||
|
"onMediaSessionMetadata",
|
||||||
|
"(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V",
|
||||||
|
&[title, artist, album],
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_media_session_playback_state_change(&self, state: i32) {}
|
fn on_media_session_playback_state_change(&self, state: i32) {
|
||||||
|
info!("on_media_session_playback_state_change {:?}", state);
|
||||||
|
let env = self.jvm.get_env().unwrap();
|
||||||
|
let state = JValue::Int(state as jint);
|
||||||
|
env.call_method(
|
||||||
|
self.callbacks.as_obj(),
|
||||||
|
"onMediaSessionPlaybackStateChange",
|
||||||
|
"(I)V",
|
||||||
|
&[state],
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn initialize_android_glue(env: &JNIEnv, activity: JObject) {
|
fn initialize_android_glue(env: &JNIEnv, activity: JObject) {
|
||||||
|
|
|
@ -7,9 +7,16 @@ package org.mozilla.servo;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.app.AlertDialog;
|
import android.app.AlertDialog;
|
||||||
|
import android.app.PendingIntent;
|
||||||
|
import android.app.Notification;
|
||||||
|
import android.app.NotificationChannel;
|
||||||
|
import android.app.NotificationManager;
|
||||||
|
import android.content.BroadcastReceiver;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
import android.content.IntentFilter;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.system.ErrnoException;
|
import android.system.ErrnoException;
|
||||||
import android.system.Os;
|
import android.system.Os;
|
||||||
|
@ -23,8 +30,6 @@ import android.widget.ProgressBar;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
//import org.mozilla.servo.MediaSessionCallback;
|
|
||||||
|
|
||||||
import org.mozilla.servoview.ServoView;
|
import org.mozilla.servoview.ServoView;
|
||||||
import org.mozilla.servoview.Servo;
|
import org.mozilla.servoview.Servo;
|
||||||
|
|
||||||
|
@ -32,7 +37,24 @@ import java.io.File;
|
||||||
|
|
||||||
public class MainActivity extends Activity implements Servo.Client {
|
public class MainActivity extends Activity implements Servo.Client {
|
||||||
|
|
||||||
|
private class NotificationID {
|
||||||
|
private int lastID = 0;
|
||||||
|
public int getNext() {
|
||||||
|
lastID++;
|
||||||
|
return lastID;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int get() {
|
||||||
|
return lastID;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static final String LOGTAG = "MainActivity";
|
private static final String LOGTAG = "MainActivity";
|
||||||
|
private static final String MEDIA_CHANNEL_ID = "MediaNotificationChannel";
|
||||||
|
private static final String KEY_MEDIA_PAUSE = "org.mozilla.servoview.MainActivity.pause";
|
||||||
|
private static final String KEY_MEDIA_PREV = "org.mozilla.servoview.MainActivity.prev";
|
||||||
|
private static final String KEY_MEDIA_NEXT = "org.mozilla.servoview.MainActivity.next";
|
||||||
|
private static final String KEY_MEDIA_STOP = "org.mozilla.servoview.MainActivity.stop";
|
||||||
|
|
||||||
ServoView mServoView;
|
ServoView mServoView;
|
||||||
Button mBackButton;
|
Button mBackButton;
|
||||||
|
@ -43,7 +65,8 @@ public class MainActivity extends Activity implements Servo.Client {
|
||||||
ProgressBar mProgressBar;
|
ProgressBar mProgressBar;
|
||||||
TextView mIdleText;
|
TextView mIdleText;
|
||||||
boolean mCanGoBack;
|
boolean mCanGoBack;
|
||||||
// MediaSession mSession;
|
NotificationID mNotificationID;
|
||||||
|
BroadcastReceiver mMediaSessionActionReceiver;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
@ -86,6 +109,16 @@ public class MainActivity extends Activity implements Servo.Client {
|
||||||
mServoView.loadUri(intent.getData());
|
mServoView.loadUri(intent.getData());
|
||||||
}
|
}
|
||||||
setupUrlField();
|
setupUrlField();
|
||||||
|
|
||||||
|
mNotificationID = new NotificationID();
|
||||||
|
createMediaNotificationChannel();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onDestroy() {
|
||||||
|
Log.d("SERVOMEDIA", "onDestroy");
|
||||||
|
super.onDestroy();
|
||||||
|
hideMediaSessionControls();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setupUrlField() {
|
private void setupUrlField() {
|
||||||
|
@ -206,6 +239,7 @@ public class MainActivity extends Activity implements Servo.Client {
|
||||||
mServoView.onPause();
|
mServoView.onPause();
|
||||||
super.onPause();
|
super.onPause();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onResume() {
|
public void onResume() {
|
||||||
mServoView.onResume();
|
mServoView.onResume();
|
||||||
|
@ -228,10 +262,98 @@ public class MainActivity extends Activity implements Servo.Client {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onMediaSessionPlaybackStateChange(int state) {
|
public void onMediaSessionPlaybackStateChange(int state) {
|
||||||
Log.d("SERVOMEDIA", "PLAYBACK STATE CHANGED");
|
Log.d("SERVOMEDIA", "PLAYBACK STATE CHANGED " + state);
|
||||||
|
if (state == 1 /* none */) {
|
||||||
|
hideMediaSessionControls();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (state == 2 /* playing */) {
|
||||||
|
showMediaSessionControls();
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* private void createMediaSession() {
|
private void createMediaNotificationChannel() {
|
||||||
mSession = new MediaSession(this, "ServoMediaSession");
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
}*/
|
CharSequence name = getString(R.string.media_channel_name);
|
||||||
|
String description = getString(R.string.media_channel_description);
|
||||||
|
int importance = NotificationManager.IMPORTANCE_DEFAULT;
|
||||||
|
NotificationChannel channel = new NotificationChannel(MEDIA_CHANNEL_ID, name, importance);
|
||||||
|
channel.setDescription(description);
|
||||||
|
NotificationManager notificationManager = getSystemService(NotificationManager.class);
|
||||||
|
notificationManager.createNotificationChannel(channel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showMediaSessionControls() {
|
||||||
|
Context context = getApplicationContext();
|
||||||
|
|
||||||
|
IntentFilter filter = new IntentFilter();
|
||||||
|
filter.addAction(KEY_MEDIA_PAUSE);
|
||||||
|
filter.addAction(KEY_MEDIA_STOP);
|
||||||
|
filter.addAction(KEY_MEDIA_PREV);
|
||||||
|
filter.addAction(KEY_MEDIA_NEXT);
|
||||||
|
|
||||||
|
mMediaSessionActionReceiver = new BroadcastReceiver() {
|
||||||
|
@Override
|
||||||
|
public void onReceive(Context context, Intent intent) {
|
||||||
|
if (intent.getAction().equals(KEY_MEDIA_PAUSE)) {
|
||||||
|
Log.d("SERVOMEDIA", "PAUSE");
|
||||||
|
} else if (intent.getAction().equals(KEY_MEDIA_STOP)) {
|
||||||
|
Log.d("SERVOMEDIA", "STOP");
|
||||||
|
} else if (intent.getAction().equals(KEY_MEDIA_NEXT)) {
|
||||||
|
Log.d("SERVOMEDIA", "NEXT");
|
||||||
|
} else if (intent.getAction().equals(KEY_MEDIA_PREV)) {
|
||||||
|
Log.d("SERVOMEDIA", "PREV");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
registerReceiver(mMediaSessionActionReceiver, filter);
|
||||||
|
|
||||||
|
Intent pauseIntent = new Intent(KEY_MEDIA_PAUSE);
|
||||||
|
Notification.Action pauseAction =
|
||||||
|
new Notification.Action(R.drawable.media_session_pause, "Pause",
|
||||||
|
PendingIntent.getBroadcast(context, 0, pauseIntent, 0));
|
||||||
|
|
||||||
|
Intent nextIntent = new Intent(KEY_MEDIA_NEXT);
|
||||||
|
Notification.Action nextAction =
|
||||||
|
new Notification.Action(R.drawable.media_session_next, "Next",
|
||||||
|
PendingIntent.getBroadcast(context, 0, nextIntent, 0));
|
||||||
|
|
||||||
|
Intent prevIntent = new Intent(KEY_MEDIA_PREV);
|
||||||
|
Notification.Action prevAction =
|
||||||
|
new Notification.Action(R.drawable.media_session_prev, "Previous",
|
||||||
|
PendingIntent.getBroadcast(context, 0, prevIntent, 0));
|
||||||
|
|
||||||
|
Intent stopIntent = new Intent(KEY_MEDIA_STOP);
|
||||||
|
Notification.Action stopAction =
|
||||||
|
new Notification.Action(R.drawable.media_session_stop, "Stop",
|
||||||
|
PendingIntent.getBroadcast(context, 0, stopIntent, 0));
|
||||||
|
|
||||||
|
Notification.Builder builder = new Notification.Builder(context, this.MEDIA_CHANNEL_ID);
|
||||||
|
builder
|
||||||
|
.setSmallIcon(R.drawable.media_session_icon)
|
||||||
|
.setContentTitle("This is the notification title")
|
||||||
|
.setVisibility(Notification.VISIBILITY_PUBLIC)
|
||||||
|
.addAction(prevAction)
|
||||||
|
.addAction(pauseAction)
|
||||||
|
.addAction(nextAction)
|
||||||
|
.addAction(stopAction)
|
||||||
|
.setStyle(new Notification.MediaStyle()
|
||||||
|
.setShowActionsInCompactView(1 /* pause button */ )
|
||||||
|
.setShowActionsInCompactView(3 /* stop button */));
|
||||||
|
|
||||||
|
NotificationManager notificationManager = getSystemService(NotificationManager.class);
|
||||||
|
notificationManager.notify(mNotificationID.getNext(), builder.build());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void hideMediaSessionControls() {
|
||||||
|
Log.d("SERVOMEDIA", "hideMediaSessionControls");
|
||||||
|
NotificationManager notificationManager = getSystemService(NotificationManager.class);
|
||||||
|
notificationManager.cancel(mNotificationID.get());
|
||||||
|
unregisterReceiver(mMediaSessionActionReceiver);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
BIN
support/android/apk/servoapp/src/main/res/drawable/media_session_icon.png
Executable file
BIN
support/android/apk/servoapp/src/main/res/drawable/media_session_icon.png
Executable file
Binary file not shown.
After Width: | Height: | Size: 213 B |
BIN
support/android/apk/servoapp/src/main/res/drawable/media_session_next.png
Executable file
BIN
support/android/apk/servoapp/src/main/res/drawable/media_session_next.png
Executable file
Binary file not shown.
After Width: | Height: | Size: 203 B |
BIN
support/android/apk/servoapp/src/main/res/drawable/media_session_pause.png
Executable file
BIN
support/android/apk/servoapp/src/main/res/drawable/media_session_pause.png
Executable file
Binary file not shown.
After Width: | Height: | Size: 114 B |
BIN
support/android/apk/servoapp/src/main/res/drawable/media_session_prev.png
Executable file
BIN
support/android/apk/servoapp/src/main/res/drawable/media_session_prev.png
Executable file
Binary file not shown.
After Width: | Height: | Size: 205 B |
BIN
support/android/apk/servoapp/src/main/res/drawable/media_session_stop.png
Executable file
BIN
support/android/apk/servoapp/src/main/res/drawable/media_session_stop.png
Executable file
Binary file not shown.
After Width: | Height: | Size: 106 B |
|
@ -1,3 +1,5 @@
|
||||||
<resources>
|
<resources>
|
||||||
<string name="app_name">Servo</string>
|
<string name="app_name">Servo</string>
|
||||||
|
<string name="media_channel_name">ServoMedia</string>
|
||||||
|
<string name="media_channel_description">Notication channel for multimedia activity</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue