blob: a409923027fa4fd46ff9bfd13dee9d0f441293d7 [file] [log] [blame]
/*
* Copyright (C) 2013 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.example.android.supportv7.media;
import com.example.android.supportv7.R;
import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.media.AudioManager;
import android.media.AudioManager.OnAudioFocusChangeListener;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.SystemClock;
import android.support.v4.app.FragmentManager;
import android.support.v4.media.session.MediaSessionCompat;
import android.support.v4.view.MenuItemCompat;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.app.MediaRouteActionProvider;
import android.support.v7.app.MediaRouteControllerDialog;
import android.support.v7.app.MediaRouteControllerDialogFragment;
import android.support.v7.app.MediaRouteDialogFactory;
import android.support.v7.app.MediaRouteDiscoveryFragment;
import android.support.v7.media.MediaControlIntent;
import android.support.v7.media.MediaItemStatus;
import android.support.v7.media.MediaRouteSelector;
import android.support.v7.media.MediaRouter;
import android.support.v7.media.MediaRouter.Callback;
import android.support.v7.media.MediaRouter.ProviderInfo;
import android.support.v7.media.MediaRouter.RouteInfo;
import android.util.Log;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.ArrayAdapter;
import android.widget.CheckBox;
import android.widget.ImageButton;
import android.widget.ListView;
import android.widget.SeekBar;
import android.widget.SeekBar.OnSeekBarChangeListener;
import android.widget.TabHost;
import android.widget.TabHost.OnTabChangeListener;
import android.widget.TabHost.TabSpec;
import android.widget.TextView;
import java.io.File;
/**
* <h3>Media Router Support Activity</h3>
*
* <p>
* This demonstrates how to use the {@link MediaRouter} API to build an
* application that allows the user to send content to various rendering
* targets.
* </p>
*/
public class SampleMediaRouterActivity extends AppCompatActivity {
private static final String TAG = "SampleMediaRouterActivity";
private static final String DISCOVERY_FRAGMENT_TAG = "DiscoveryFragment";
private static final boolean ENABLE_DEFAULT_CONTROL_CHECK_BOX = false;
private MediaRouter mMediaRouter;
private MediaRouteSelector mSelector;
private LibraryAdapter mLibraryItems;
private PlaylistAdapter mPlayListItems;
private TextView mInfoTextView;
private ListView mLibraryView;
private ListView mPlayListView;
private CheckBox mUseDefaultControlCheckBox;
private ImageButton mPauseResumeButton;
private ImageButton mStopButton;
private SeekBar mSeekBar;
private boolean mNeedResume;
private boolean mSeeking;
private final Handler mHandler = new Handler();
private final Runnable mUpdateSeekRunnable = new Runnable() {
@Override
public void run() {
updateProgress();
// update Ui every 1 second
mHandler.postDelayed(this, 1000);
}
};
private final SessionManager mSessionManager = new SessionManager("app");
private Player mPlayer;
private final MediaRouter.Callback mMediaRouterCB = new MediaRouter.Callback() {
// Return a custom callback that will simply log all of the route events
// for demonstration purposes.
@Override
public void onRouteAdded(MediaRouter router, RouteInfo route) {
Log.d(TAG, "onRouteAdded: route=" + route);
}
@Override
public void onRouteChanged(MediaRouter router, RouteInfo route) {
Log.d(TAG, "onRouteChanged: route=" + route);
}
@Override
public void onRouteRemoved(MediaRouter router, RouteInfo route) {
Log.d(TAG, "onRouteRemoved: route=" + route);
}
@Override
public void onRouteSelected(MediaRouter router, RouteInfo route) {
Log.d(TAG, "onRouteSelected: route=" + route);
mPlayer = Player.create(SampleMediaRouterActivity.this, route, mMediaSession);
mPlayer.updatePresentation();
mSessionManager.setPlayer(mPlayer);
mSessionManager.unsuspend();
updateUi();
}
@Override
public void onRouteUnselected(MediaRouter router, RouteInfo route) {
Log.d(TAG, "onRouteUnselected: route=" + route);
mMediaSession.setActive(false);
PlaylistItem item = getCheckedPlaylistItem();
if (item != null) {
long pos = item.getPosition() + (mSessionManager.isPaused() ?
0 : (SystemClock.elapsedRealtime() - item.getTimestamp()));
mSessionManager.suspend(pos);
}
mPlayer.updatePresentation();
mPlayer.release();
}
@Override
public void onRouteVolumeChanged(MediaRouter router, RouteInfo route) {
Log.d(TAG, "onRouteVolumeChanged: route=" + route);
}
@Override
public void onRoutePresentationDisplayChanged(
MediaRouter router, RouteInfo route) {
Log.d(TAG, "onRoutePresentationDisplayChanged: route=" + route);
mPlayer.updatePresentation();
}
@Override
public void onProviderAdded(MediaRouter router, ProviderInfo provider) {
Log.d(TAG, "onRouteProviderAdded: provider=" + provider);
}
@Override
public void onProviderRemoved(MediaRouter router, ProviderInfo provider) {
Log.d(TAG, "onRouteProviderRemoved: provider=" + provider);
}
@Override
public void onProviderChanged(MediaRouter router, ProviderInfo provider) {
Log.d(TAG, "onRouteProviderChanged: provider=" + provider);
}
};
private MediaSessionCompat mMediaSession;
private ComponentName mEventReceiver;
private AudioManager mAudioManager;
private PendingIntent mMediaPendingIntent;
private final OnAudioFocusChangeListener mAfChangeListener =
new OnAudioFocusChangeListener() {
@Override
public void onAudioFocusChange(int focusChange) {
if (focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT) {
Log.d(TAG, "onAudioFocusChange: LOSS_TRANSIENT");
} else if (focusChange == AudioManager.AUDIOFOCUS_GAIN) {
Log.d(TAG, "onAudioFocusChange: AUDIOFOCUS_GAIN");
} else if (focusChange == AudioManager.AUDIOFOCUS_LOSS) {
Log.d(TAG, "onAudioFocusChange: AUDIOFOCUS_LOSS");
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
// Be sure to call the super class.
super.onCreate(savedInstanceState);
// Get the media router service.
mMediaRouter = MediaRouter.getInstance(this);
// Create a route selector for the type of routes that we care about.
mSelector = new MediaRouteSelector.Builder()
.addControlCategory(MediaControlIntent.CATEGORY_LIVE_AUDIO)
.addControlCategory(MediaControlIntent.CATEGORY_LIVE_VIDEO)
.addControlCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK)
.addControlCategory(SampleMediaRouteProvider.CATEGORY_SAMPLE_ROUTE)
.build();
// Add a fragment to take care of media route discovery.
// This fragment automatically adds or removes a callback whenever the activity
// is started or stopped.
FragmentManager fm = getSupportFragmentManager();
DiscoveryFragment fragment = (DiscoveryFragment)fm.findFragmentByTag(
DISCOVERY_FRAGMENT_TAG);
if (fragment == null) {
fragment = new DiscoveryFragment(mMediaRouterCB);
fragment.setRouteSelector(mSelector);
fm.beginTransaction()
.add(fragment, DISCOVERY_FRAGMENT_TAG)
.commit();
} else {
fragment.setCallback(mMediaRouterCB);
fragment.setRouteSelector(mSelector);
}
// Populate an array adapter with streaming media items.
String[] mediaNames = getResources().getStringArray(R.array.media_names);
String[] mediaUris = getResources().getStringArray(R.array.media_uris);
mLibraryItems = new LibraryAdapter();
for (int i = 0; i < mediaNames.length; i++) {
mLibraryItems.add(new MediaItem(
"[streaming] "+mediaNames[i], Uri.parse(mediaUris[i]), "video/mp4"));
}
// Scan local external storage directory for media files.
File externalDir = Environment.getExternalStorageDirectory();
if (externalDir != null) {
File list[] = externalDir.listFiles();
if (list != null) {
for (int i = 0; i < list.length; i++) {
String filename = list[i].getName();
if (filename.matches(".*\\.(m4v|mp4)")) {
mLibraryItems.add(new MediaItem("[local] " + filename,
Uri.fromFile(list[i]), "video/mp4"));
}
}
}
}
mPlayListItems = new PlaylistAdapter();
// Initialize the layout.
setContentView(R.layout.sample_media_router);
TabHost tabHost=(TabHost)findViewById(R.id.tabHost);
tabHost.setup();
String tabName = getResources().getString(R.string.library_tab_text);
TabSpec spec1=tabHost.newTabSpec(tabName);
spec1.setContent(R.id.tab1);
spec1.setIndicator(tabName);
tabName = getResources().getString(R.string.playlist_tab_text);
TabSpec spec2=tabHost.newTabSpec(tabName);
spec2.setIndicator(tabName);
spec2.setContent(R.id.tab2);
tabName = getResources().getString(R.string.info_tab_text);
TabSpec spec3=tabHost.newTabSpec(tabName);
spec3.setIndicator(tabName);
spec3.setContent(R.id.tab3);
tabHost.addTab(spec1);
tabHost.addTab(spec2);
tabHost.addTab(spec3);
tabHost.setOnTabChangedListener(new OnTabChangeListener() {
@Override
public void onTabChanged(String arg0) {
updateUi();
}
});
mLibraryView = (ListView) findViewById(R.id.media);
mLibraryView.setAdapter(mLibraryItems);
mLibraryView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
mLibraryView.setOnItemClickListener(new OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
updateButtons();
}
});
mPlayListView = (ListView) findViewById(R.id.playlist);
mPlayListView.setAdapter(mPlayListItems);
mPlayListView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
mPlayListView.setOnItemClickListener(new OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
updateButtons();
}
});
mInfoTextView = (TextView) findViewById(R.id.info);
mUseDefaultControlCheckBox = (CheckBox) findViewById(R.id.custom_control_view_checkbox);
if (ENABLE_DEFAULT_CONTROL_CHECK_BOX) {
mUseDefaultControlCheckBox.setVisibility(View.VISIBLE);
}
mPauseResumeButton = (ImageButton)findViewById(R.id.pause_resume_button);
mPauseResumeButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if (mSessionManager.isPaused()) {
mSessionManager.resume();
} else {
mSessionManager.pause();
}
}
});
mStopButton = (ImageButton)findViewById(R.id.stop_button);
mStopButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
mSessionManager.stop();
}
});
mSeekBar = (SeekBar) findViewById(R.id.seekbar);
mSeekBar.setOnSeekBarChangeListener(new OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
PlaylistItem item = getCheckedPlaylistItem();
if (fromUser && item != null && item.getDuration() > 0) {
long pos = progress * item.getDuration() / 100;
mSessionManager.seek(item.getItemId(), pos);
item.setPosition(pos);
item.setTimestamp(SystemClock.elapsedRealtime());
}
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
mSeeking = true;
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
mSeeking = false;
updateUi();
}
});
// Schedule Ui update
mHandler.postDelayed(mUpdateSeekRunnable, 1000);
// Build the PendingIntent for the remote control client
mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
mEventReceiver = new ComponentName(getPackageName(),
SampleMediaButtonReceiver.class.getName());
Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON);
mediaButtonIntent.setComponent(mEventReceiver);
mMediaPendingIntent = PendingIntent.getBroadcast(this, 0, mediaButtonIntent, 0);
// Create and register the remote control client
createMediaSession();
mMediaRouter.setMediaSessionCompat(mMediaSession);
// Set up playback manager and player
mPlayer = Player.create(SampleMediaRouterActivity.this,
mMediaRouter.getSelectedRoute(), mMediaSession);
mSessionManager.setPlayer(mPlayer);
mSessionManager.setCallback(new SessionManager.Callback() {
@Override
public void onStatusChanged() {
updateUi();
}
@Override
public void onItemChanged(PlaylistItem item) {
}
});
updateUi();
}
private void createMediaSession() {
// Create the MediaSession
mMediaSession = new MediaSessionCompat(this, "SampleMediaRouter", mEventReceiver,
mMediaPendingIntent);
mMediaSession.setFlags(MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS
| MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS);
mMediaSession.setCallback(new MediaSessionCompat.Callback() {
@Override
public boolean onMediaButtonEvent(Intent mediaButtonEvent) {
if (mediaButtonEvent != null) {
return handleMediaKey(
(KeyEvent) mediaButtonEvent.getParcelableExtra(Intent.EXTRA_KEY_EVENT));
}
return super.onMediaButtonEvent(mediaButtonEvent);
}
@Override
public void onPlay() {
mSessionManager.resume();
}
@Override
public void onPause() {
mSessionManager.pause();
}
});
SampleMediaButtonReceiver.setActivity(SampleMediaRouterActivity.this);
}
public boolean handleMediaKey(KeyEvent event) {
if (event != null && event.getAction() == KeyEvent.ACTION_DOWN
&& event.getRepeatCount() == 0) {
switch (event.getKeyCode()) {
case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
case KeyEvent.KEYCODE_HEADSETHOOK:
{
Log.d(TAG, "Received Play/Pause event from RemoteControlClient");
if (mSessionManager.isPaused()) {
mSessionManager.resume();
} else {
mSessionManager.pause();
}
return true;
}
case KeyEvent.KEYCODE_MEDIA_PLAY:
{
Log.d(TAG, "Received Play event from RemoteControlClient");
if (mSessionManager.isPaused()) {
mSessionManager.resume();
}
return true;
}
case KeyEvent.KEYCODE_MEDIA_PAUSE:
{
Log.d(TAG, "Received Pause event from RemoteControlClient");
if (!mSessionManager.isPaused()) {
mSessionManager.pause();
}
return true;
}
case KeyEvent.KEYCODE_MEDIA_STOP:
{
Log.d(TAG, "Received Stop event from RemoteControlClient");
mSessionManager.stop();
return true;
}
default:
break;
}
}
return false;
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
return handleMediaKey(event) || super.onKeyDown(keyCode, event);
}
@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
return handleMediaKey(event) || super.onKeyUp(keyCode, event);
}
@Override
public void onStart() {
// Be sure to call the super class.
super.onStart();
}
@Override
public void onPause() {
// pause media player for local playback case only
if (!mPlayer.isRemotePlayback() && !mSessionManager.isPaused()) {
mNeedResume = true;
mSessionManager.pause();
}
super.onPause();
}
@Override
public void onResume() {
// resume media player for local playback case only
if (!mPlayer.isRemotePlayback() && mNeedResume) {
mSessionManager.resume();
mNeedResume = false;
}
super.onResume();
}
@Override
public void onDestroy() {
mSessionManager.stop();
mPlayer.release();
mMediaSession.release();
super.onDestroy();
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Be sure to call the super class.
super.onCreateOptionsMenu(menu);
// Inflate the menu and configure the media router action provider.
getMenuInflater().inflate(R.menu.sample_media_router_menu, menu);
MenuItem mediaRouteMenuItem = menu.findItem(R.id.media_route_menu_item);
MediaRouteActionProvider mediaRouteActionProvider =
(MediaRouteActionProvider)MenuItemCompat.getActionProvider(mediaRouteMenuItem);
mediaRouteActionProvider.setRouteSelector(mSelector);
mediaRouteActionProvider.setDialogFactory(new MediaRouteDialogFactory() {
@Override
public MediaRouteControllerDialogFragment onCreateControllerDialogFragment() {
return new ControllerDialogFragment(mPlayer, mUseDefaultControlCheckBox);
}
});
// Return true to show the menu.
return true;
}
private void updateProgress() {
// Estimate content position from last status time and elapsed time.
// (Note this might be slightly out of sync with remote side, however
// it avoids frequent polling the MRP.)
int progress = 0;
PlaylistItem item = getCheckedPlaylistItem();
if (item != null) {
int state = item.getState();
long duration = item.getDuration();
if (duration <= 0) {
if (state == MediaItemStatus.PLAYBACK_STATE_PLAYING
|| state == MediaItemStatus.PLAYBACK_STATE_PAUSED) {
mSessionManager.updateStatus();
}
} else {
long position = item.getPosition();
long timeDelta = mSessionManager.isPaused() ? 0 :
(SystemClock.elapsedRealtime() - item.getTimestamp());
progress = (int)(100.0 * (position + timeDelta) / duration);
}
}
mSeekBar.setProgress(progress);
}
private void updateUi() {
updatePlaylist();
updateRouteDescription();
updateButtons();
if (mPlayer != null) {
mPlayer.updateMetadata();
}
}
private void updatePlaylist() {
mPlayListItems.clear();
for (PlaylistItem item : mSessionManager.getPlaylist()) {
mPlayListItems.add(item);
}
mPlayListView.invalidate();
}
private void updateRouteDescription() {
RouteInfo route = mMediaRouter.getSelectedRoute();
mInfoTextView.setText("Currently selected route:"
+ "\nName: " + route.getName()
+ "\nProvider: " + route.getProvider().getPackageName()
+ "\nDescription: " + route.getDescription());
}
private void updateButtons() {
MediaRouter.RouteInfo route = mMediaRouter.getSelectedRoute();
// show pause or resume icon depending on current state
mPauseResumeButton.setImageResource(mSessionManager.isPaused() ?
R.drawable.ic_media_play : R.drawable.ic_media_pause);
// only enable seek bar when duration is known
PlaylistItem item = getCheckedPlaylistItem();
mSeekBar.setEnabled(item != null && item.getDuration() > 0);
}
private PlaylistItem getCheckedPlaylistItem() {
int count = mPlayListView.getCount();
int index = mPlayListView.getCheckedItemPosition();
if (count > 0) {
if (index < 0 || index >= count) {
index = 0;
mPlayListView.setItemChecked(0, true);
}
return mPlayListItems.getItem(index);
}
return null;
}
public static final class DiscoveryFragment extends MediaRouteDiscoveryFragment {
private static final String TAG = "DiscoveryFragment";
private Callback mCallback;
public DiscoveryFragment() {
mCallback = null;
}
public DiscoveryFragment(Callback cb) {
mCallback = cb;
}
public void setCallback(Callback cb) {
mCallback = cb;
}
@Override
public Callback onCreateCallback() {
return mCallback;
}
@Override
public int onPrepareCallbackFlags() {
// Add the CALLBACK_FLAG_UNFILTERED_EVENTS flag to ensure that we will
// observe and log all route events including those that are for routes
// that do not match our selector. This is only for demonstration purposes
// and should not be needed by most applications.
return super.onPrepareCallbackFlags()
| MediaRouter.CALLBACK_FLAG_UNFILTERED_EVENTS;
}
}
private static final class MediaItem {
public final String mName;
public final Uri mUri;
public final String mMime;
public MediaItem(String name, Uri uri, String mime) {
mName = name;
mUri = uri;
mMime = mime;
}
@Override
public String toString() {
return mName;
}
}
private final class LibraryAdapter extends ArrayAdapter<MediaItem> {
public LibraryAdapter() {
super(SampleMediaRouterActivity.this, R.layout.media_item);
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
final View v;
if (convertView == null) {
v = getLayoutInflater().inflate(R.layout.media_item, null);
} else {
v = convertView;
}
final MediaItem item = getItem(position);
TextView tv = (TextView)v.findViewById(R.id.item_text);
tv.setText(item.mName);
ImageButton b = (ImageButton)v.findViewById(R.id.item_action);
b.setImageResource(R.drawable.ic_menu_add);
b.setTag(item);
b.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if (item != null) {
mSessionManager.add(item.mUri, item.mMime);
}
}
});
return v;
}
}
private final class PlaylistAdapter extends ArrayAdapter<PlaylistItem> {
public PlaylistAdapter() {
super(SampleMediaRouterActivity.this, R.layout.media_item);
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
final View v;
if (convertView == null) {
v = getLayoutInflater().inflate(R.layout.media_item, null);
} else {
v = convertView;
}
final PlaylistItem item = getItem(position);
TextView tv = (TextView)v.findViewById(R.id.item_text);
tv.setText(item.toString());
ImageButton b = (ImageButton)v.findViewById(R.id.item_action);
b.setImageResource(R.drawable.ic_menu_delete);
b.setTag(item);
b.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if (item != null) {
mSessionManager.remove(item.getItemId());
}
}
});
return v;
}
}
/**
* Trivial subclass of this activity used to provide another copy of the
* same activity using a light theme instead of the dark theme.
*/
public static class Light extends SampleMediaRouterActivity {
}
/**
* Trivial subclass of this activity used to provide another copy of the
* same activity using a light theme with dark action bar instead of the dark theme.
*/
public static class LightWithDarkActionBar extends SampleMediaRouterActivity {
}
public static class ControllerDialogFragment extends MediaRouteControllerDialogFragment {
private MediaRouteControllerDialog mControllerDialog;
private Player mPlayer;
private CheckBox mUseDefaultControlCheckBox;
public ControllerDialogFragment() {
super();
}
public ControllerDialogFragment(Player player, CheckBox customControlViewCheckBox) {
mPlayer = player;
this.mUseDefaultControlCheckBox = customControlViewCheckBox;
}
@Override
public MediaRouteControllerDialog onCreateControllerDialog(
Context context, Bundle savedInstanceState) {
mControllerDialog = this.mUseDefaultControlCheckBox.isChecked()
? super.onCreateControllerDialog(context, savedInstanceState)
: new MyMediaRouteControllerDialog(context);
mControllerDialog.setOnDismissListener(new DialogInterface.OnDismissListener() {
@Override
public void onDismiss(DialogInterface dialog) {
mControllerDialog = null;
}
});
return mControllerDialog;
}
public void setPlayer(Player player) {
mPlayer = player;
}
}
}