implement custom mediaroute playback controller
This commit is contained in:
parent
926f3912cf
commit
6d0debfd4d
|
@ -0,0 +1,7 @@
|
|||
package de.danoeh.antennapod.config;
|
||||
|
||||
import de.danoeh.antennapod.core.CastCallbacks;
|
||||
|
||||
public class CastCallbackImpl implements CastCallbacks {
|
||||
|
||||
}
|
|
@ -16,5 +16,6 @@ public class ClientConfigurator {
|
|||
ClientConfig.playbackServiceCallbacks = new PlaybackServiceCallbacksImpl();
|
||||
ClientConfig.flattrCallbacks = new FlattrCallbacksImpl();
|
||||
ClientConfig.dbTasksCallbacks = new DBTasksCallbacksImpl();
|
||||
ClientConfig.castCallbacks = new CastCallbackImpl();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,226 @@
|
|||
package de.danoeh.antennapod.cast;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Bundle;
|
||||
import android.os.RemoteException;
|
||||
import android.support.v4.media.MediaDescriptionCompat;
|
||||
import android.support.v4.media.MediaMetadataCompat;
|
||||
import android.support.v4.media.session.MediaControllerCompat;
|
||||
import android.support.v4.media.session.MediaSessionCompat;
|
||||
import android.support.v4.media.session.PlaybackStateCompat;
|
||||
import android.support.v7.app.MediaRouteControllerDialog;
|
||||
import android.support.v7.media.MediaRouter;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import android.util.TypedValue;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import de.danoeh.antennapod.R;
|
||||
|
||||
public class CustomMRControllerDialog extends MediaRouteControllerDialog {
|
||||
public static final String TAG = "CustomMRContrDialog";
|
||||
|
||||
private MediaRouter mediaRouter;
|
||||
private MediaSessionCompat.Token token;
|
||||
|
||||
private ImageView artView;
|
||||
private TextView titleView;
|
||||
private TextView subtitleView;
|
||||
private ImageButton playPauseButton;
|
||||
private LinearLayout rootView;
|
||||
|
||||
private MediaControllerCompat mediaController;
|
||||
private MediaControllerCompat.Callback mediaControllerCallback;
|
||||
|
||||
public CustomMRControllerDialog(Context context) {
|
||||
this(context, 0);
|
||||
}
|
||||
|
||||
public CustomMRControllerDialog(Context context, int theme) {
|
||||
super(context, theme);
|
||||
mediaRouter = MediaRouter.getInstance(getContext());
|
||||
token = mediaRouter.getMediaSessionToken();
|
||||
try {
|
||||
if (token != null) {
|
||||
mediaController = new MediaControllerCompat(getContext(), token);
|
||||
}
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, "Error creating media controller", e);
|
||||
}
|
||||
|
||||
if (mediaController != null) {
|
||||
mediaControllerCallback = new MediaControllerCompat.Callback() {
|
||||
@Override
|
||||
public void onSessionDestroyed() {
|
||||
if (mediaController != null) {
|
||||
mediaController.unregisterCallback(mediaControllerCallback);
|
||||
mediaController = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMetadataChanged(MediaMetadataCompat metadata) {
|
||||
updateViews();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPlaybackStateChanged(PlaybackStateCompat state) {
|
||||
updateState();
|
||||
}
|
||||
};
|
||||
mediaController.registerCallback(mediaControllerCallback);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateMediaControlView(Bundle savedInstanceState) {
|
||||
rootView = new LinearLayout(getContext());
|
||||
rootView.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT));
|
||||
rootView.setOrientation(LinearLayout.VERTICAL);
|
||||
|
||||
artView = new ImageView(getContext()) {
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||
int desiredWidth = widthMeasureSpec;
|
||||
int desiredHeight = heightMeasureSpec;
|
||||
if (MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY) {
|
||||
Drawable drawable = getDrawable();
|
||||
if (drawable != null) {
|
||||
int originalWidth = MeasureSpec.getSize(widthMeasureSpec);
|
||||
int intrHeight = drawable.getIntrinsicHeight();
|
||||
int intrWidth = drawable.getIntrinsicWidth();
|
||||
float scale;
|
||||
if (intrHeight*16 > intrWidth*9) {
|
||||
// image is taller than 16:9
|
||||
scale = (float) originalWidth * 9 / 16 / intrHeight;
|
||||
} else {
|
||||
// image is more horizontal than 16:9
|
||||
scale = (float) originalWidth / intrWidth;
|
||||
}
|
||||
desiredHeight = MeasureSpec.makeMeasureSpec((int) (intrHeight * scale + 0.5f), MeasureSpec.EXACTLY);
|
||||
}
|
||||
}
|
||||
|
||||
super.onMeasure(desiredWidth, desiredHeight);
|
||||
}
|
||||
};
|
||||
artView.setLayoutParams(new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT));
|
||||
artView.setScaleType(ImageView.ScaleType.FIT_CENTER);
|
||||
|
||||
rootView.addView(artView);
|
||||
View playbackControlLayout = View.inflate(getContext(), R.layout.media_router_controller, rootView);
|
||||
|
||||
titleView = (TextView) playbackControlLayout.findViewById(R.id.mrc_control_title);
|
||||
subtitleView = (TextView) playbackControlLayout.findViewById(R.id.mrc_control_subtitle);
|
||||
playPauseButton = (ImageButton) playbackControlLayout.findViewById(R.id.mrc_control_play_pause);
|
||||
|
||||
updateViews();
|
||||
return rootView;
|
||||
}
|
||||
|
||||
private void updateViews() {
|
||||
if (token == null || artView == null || mediaController == null) {
|
||||
rootView.setVisibility(View.GONE);
|
||||
return;
|
||||
}
|
||||
MediaMetadataCompat metadata = mediaController.getMetadata();
|
||||
MediaDescriptionCompat description = metadata == null ? null : metadata.getDescription();
|
||||
if (description == null) {
|
||||
rootView.setVisibility(View.GONE);
|
||||
return;
|
||||
}
|
||||
|
||||
PlaybackStateCompat state = mediaController.getPlaybackState();
|
||||
MediaRouter.RouteInfo route = MediaRouter.getInstance(getContext()).getSelectedRoute();
|
||||
|
||||
CharSequence title = description.getTitle();
|
||||
boolean hasTitle = !TextUtils.isEmpty(title);
|
||||
CharSequence subtitle = description.getSubtitle();
|
||||
boolean hasSubtitle = !TextUtils.isEmpty(subtitle);
|
||||
|
||||
boolean showTitle = false;
|
||||
boolean showSubtitle = false;
|
||||
if (route.getPresentationDisplayId() != MediaRouter.RouteInfo.PRESENTATION_DISPLAY_ID_NONE) {
|
||||
// The user is currently casting screen.
|
||||
titleView.setText(android.support.v7.mediarouter.R.string.mr_controller_casting_screen);
|
||||
showTitle = true;
|
||||
} else if (state == null || state.getState() == PlaybackStateCompat.STATE_NONE) {
|
||||
// Show "No media selected" as we don't yet know the playback state.
|
||||
// (Only exception is bluetooth where we don't show anything.)
|
||||
if (!route.isDeviceTypeBluetooth()) {
|
||||
titleView.setText(android.support.v7.mediarouter.R.string.mr_controller_no_media_selected);
|
||||
showTitle = true;
|
||||
}
|
||||
} else if (!hasTitle && !hasSubtitle) {
|
||||
titleView.setText(android.support.v7.mediarouter.R.string.mr_controller_no_info_available);
|
||||
showTitle = true;
|
||||
} else {
|
||||
if (hasTitle) {
|
||||
titleView.setText(title);
|
||||
showTitle = true;
|
||||
}
|
||||
if (hasSubtitle) {
|
||||
subtitleView.setText(subtitle);
|
||||
showSubtitle = true;
|
||||
}
|
||||
}
|
||||
titleView.setVisibility(showTitle ? View.VISIBLE : View.GONE);
|
||||
subtitleView.setVisibility(showSubtitle ? View.VISIBLE : View.GONE);
|
||||
|
||||
updateState();
|
||||
|
||||
Bitmap art = metadata.getBitmap(MediaMetadataCompat.METADATA_KEY_ART);
|
||||
if (art == null) {
|
||||
artView.setVisibility(View.GONE);
|
||||
return;
|
||||
}
|
||||
artView.setImageBitmap(art);
|
||||
artView.setVisibility(View.VISIBLE);
|
||||
rootView.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
private void updateState() {
|
||||
if (mediaController == null) {
|
||||
return;
|
||||
}
|
||||
PlaybackStateCompat state = mediaController.getPlaybackState();
|
||||
if (state != null) {
|
||||
boolean isPlaying = state.getState() == PlaybackStateCompat.STATE_BUFFERING
|
||||
|| state.getState() == PlaybackStateCompat.STATE_PLAYING;
|
||||
boolean supportsPlay = (state.getActions() & (PlaybackStateCompat.ACTION_PLAY
|
||||
| PlaybackStateCompat.ACTION_PLAY_PAUSE)) != 0;
|
||||
boolean supportsPause = (state.getActions() & (PlaybackStateCompat.ACTION_PAUSE
|
||||
| PlaybackStateCompat.ACTION_PLAY_PAUSE)) != 0;
|
||||
if (isPlaying && supportsPause) {
|
||||
playPauseButton.setVisibility(View.VISIBLE);
|
||||
playPauseButton.setImageResource(getThemeResource(getContext(),
|
||||
android.support.v7.mediarouter.R.attr.mediaRoutePauseDrawable));
|
||||
playPauseButton.setContentDescription(getContext().getResources()
|
||||
.getText(android.support.v7.mediarouter.R.string.mr_controller_pause));
|
||||
} else if (!isPlaying && supportsPlay) {
|
||||
playPauseButton.setVisibility(View.VISIBLE);
|
||||
playPauseButton.setImageResource(getThemeResource(getContext(),
|
||||
android.support.v7.mediarouter.R.attr.mediaRoutePlayDrawable));
|
||||
playPauseButton.setContentDescription(getContext().getResources()
|
||||
.getText(android.support.v7.mediarouter.R.string.mr_controller_play));
|
||||
} else {
|
||||
playPauseButton.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static int getThemeResource(Context context, int attr) {
|
||||
TypedValue value = new TypedValue();
|
||||
return context.getTheme().resolveAttribute(attr, value, true) ? value.resourceId : 0;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
package de.danoeh.antennapod.config;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v7.app.MediaRouteControllerDialog;
|
||||
import android.support.v7.app.MediaRouteControllerDialogFragment;
|
||||
import android.support.v7.app.MediaRouteDialogFactory;
|
||||
|
||||
import de.danoeh.antennapod.cast.CustomMRControllerDialog;
|
||||
import de.danoeh.antennapod.core.CastCallbacks;
|
||||
|
||||
public class CastCallbackImpl implements CastCallbacks {
|
||||
@Override
|
||||
public MediaRouteDialogFactory getMediaRouterDialogFactory() {
|
||||
return new MediaRouteDialogFactory() {
|
||||
@NonNull
|
||||
@Override
|
||||
public MediaRouteControllerDialogFragment onCreateControllerDialogFragment() {
|
||||
return new MediaRouteControllerDialogFragment() {
|
||||
@Override
|
||||
public MediaRouteControllerDialog onCreateControllerDialog(Context context, Bundle savedInstanceState) {
|
||||
return new CustomMRControllerDialog(context);
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:orientation="vertical" android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<!--<ImageView android:id="@+id/mrc_art"-->
|
||||
<!--android:layout_width="match_parent"-->
|
||||
<!--android:layout_height="wrap_content"-->
|
||||
<!--android:adjustViewBounds="true"-->
|
||||
<!--android:scaleType="fitCenter"-->
|
||||
<!--android:background="?attr/colorPrimary"-->
|
||||
<!--android:layout_gravity="top"-->
|
||||
<!--android:visibility="gone"/>-->
|
||||
|
||||
<RelativeLayout android:id="@+id/mrc_playback_control"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:paddingTop="16dp"
|
||||
android:paddingBottom="16dp"
|
||||
android:paddingLeft="24dp"
|
||||
android:paddingStart="24dp"
|
||||
android:paddingRight="12dp"
|
||||
android:paddingEnd="12dp">
|
||||
<ImageButton android:id="@+id/mrc_control_play_pause"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="12dp"
|
||||
android:layout_marginStart="12dp"
|
||||
android:layout_alignParentRight="true"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:contentDescription="@string/mr_controller_play"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"/>
|
||||
|
||||
<LinearLayout android:id="@+id/mrc_control_title_container"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_toLeftOf="@id/mrc_control_play_pause"
|
||||
android:layout_toStartOf="@id/mrc_control_play_pause">
|
||||
<TextView android:id="@+id/mrc_control_title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAppearance="?attr/mediaRouteControllerPrimaryTextStyle"
|
||||
android:singleLine="true" />
|
||||
<TextView android:id="@+id/mrc_control_subtitle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAppearance="?attr/mediaRouteControllerSecondaryTextStyle"
|
||||
android:singleLine="true" />
|
||||
</LinearLayout>
|
||||
</RelativeLayout>
|
||||
|
||||
<View android:id="@+id/mrc_control_divider"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="8dp"/>
|
||||
</LinearLayout>
|
|
@ -0,0 +1,7 @@
|
|||
package de.danoeh.antennapod.core;
|
||||
|
||||
/**
|
||||
* Callbacks for Chromecast support on the core module
|
||||
*/
|
||||
public interface CastCallbacks {
|
||||
}
|
|
@ -30,6 +30,8 @@ public class ClientConfig {
|
|||
|
||||
public static DBTasksCallbacks dbTasksCallbacks;
|
||||
|
||||
public static CastCallbacks castCallbacks;
|
||||
|
||||
private static boolean initialized = false;
|
||||
|
||||
public static synchronized void initialize(Context context) {
|
||||
|
|
|
@ -28,11 +28,9 @@ import android.support.v7.app.NotificationCompat;
|
|||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import android.util.Pair;
|
||||
import android.view.Display;
|
||||
import android.view.InputDevice;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.SurfaceHolder;
|
||||
import android.view.WindowManager;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.bumptech.glide.Glide;
|
||||
|
@ -945,31 +943,32 @@ public class PlaybackService extends Service {
|
|||
builder.putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_TITLE, p.getEpisodeTitle());
|
||||
builder.putString(MediaMetadataCompat.METADATA_KEY_ALBUM, p.getFeedTitle());
|
||||
|
||||
if (p.getImageLocation() != null && UserPreferences.setLockscreenBackground()) {
|
||||
builder.putString(MediaMetadataCompat.METADATA_KEY_ART_URI, p.getImageLocation().toString());
|
||||
try {
|
||||
if (isCasting) {
|
||||
String imageLocation = p.getImageLocation();
|
||||
|
||||
if (!TextUtils.isEmpty(imageLocation)) {
|
||||
if (isCasting || UserPreferences.setLockscreenBackground()) {
|
||||
builder.putString(MediaMetadataCompat.METADATA_KEY_ART_URI, imageLocation);
|
||||
builder.putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_ICON_URI, imageLocation);
|
||||
try {
|
||||
Bitmap art = Glide.with(this)
|
||||
.load(p.getImageLocation())
|
||||
.load(imageLocation)
|
||||
.asBitmap()
|
||||
.diskCacheStrategy(ApGlideSettings.AP_DISK_CACHE_STRATEGY)
|
||||
.into(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL)
|
||||
.get();
|
||||
builder.putBitmap(MediaMetadataCompat.METADATA_KEY_ART, art);
|
||||
} else {
|
||||
WindowManager wm = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
|
||||
Display display = wm.getDefaultDisplay();
|
||||
Bitmap art = Glide.with(this)
|
||||
.load(p.getImageLocation())
|
||||
// Icon is useful for MediaDescription,
|
||||
Bitmap icon = Glide.with(this)
|
||||
.load(imageLocation)
|
||||
.asBitmap()
|
||||
.diskCacheStrategy(ApGlideSettings.AP_DISK_CACHE_STRATEGY)
|
||||
.centerCrop()
|
||||
.into(display.getWidth(), display.getHeight())
|
||||
.fitCenter()
|
||||
.into(128, 128)
|
||||
.get();
|
||||
builder.putBitmap(MediaMetadataCompat.METADATA_KEY_ART, art);
|
||||
builder.putBitmap(MediaMetadataCompat.METADATA_KEY_DISPLAY_ICON, icon);
|
||||
} catch (Throwable tr) {
|
||||
Log.e(TAG, Log.getStackTraceString(tr));
|
||||
}
|
||||
} catch (Throwable tr) {
|
||||
Log.e(TAG, Log.getStackTraceString(tr));
|
||||
}
|
||||
}
|
||||
if (!Thread.currentThread().isInterrupted() && started) {
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
package de.danoeh.antennapod.core;
|
||||
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v7.app.MediaRouteDialogFactory;
|
||||
|
||||
/**
|
||||
* Callbacks for Chromecast support on the core module
|
||||
*/
|
||||
public interface CastCallbacks {
|
||||
|
||||
@Nullable MediaRouteDialogFactory getMediaRouterDialogFactory();
|
||||
}
|
|
@ -31,6 +31,8 @@ public class ClientConfig {
|
|||
|
||||
public static DBTasksCallbacks dbTasksCallbacks;
|
||||
|
||||
public static CastCallbacks castCallbacks;
|
||||
|
||||
private static boolean initialized = false;
|
||||
|
||||
public static synchronized void initialize(Context context) {
|
||||
|
|
|
@ -62,6 +62,7 @@ import java.util.concurrent.CopyOnWriteArrayList;
|
|||
import java.util.concurrent.CopyOnWriteArraySet;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import de.danoeh.antennapod.core.ClientConfig;
|
||||
import de.danoeh.antennapod.core.R;
|
||||
|
||||
import static com.google.android.gms.cast.RemoteMediaPlayer.RESUME_STATE_PLAY;
|
||||
|
@ -130,12 +131,12 @@ public class CastManager extends BaseCastManager implements OnFailedListener {
|
|||
|
||||
public static synchronized CastManager init(Context context) {
|
||||
if (INSTANCE == null) {
|
||||
//TODO also setup dialog factory if necessary
|
||||
CastConfiguration castConfiguration = new CastConfiguration.Builder(CAST_APP_ID)
|
||||
.enableDebug()
|
||||
.enableAutoReconnect()
|
||||
.enableWifiReconnection()
|
||||
.setLaunchOptions(true, Locale.getDefault())
|
||||
.setMediaRouteDialogFactory(ClientConfig.castCallbacks.getMediaRouterDialogFactory())
|
||||
.build();
|
||||
Log.d(TAG, "New instance of CastManager is created");
|
||||
if (ConnectionResult.SUCCESS != GoogleApiAvailability.getInstance()
|
||||
|
|
Loading…
Reference in New Issue