Kore/app/src/main/java/org/xbmc/kore/ui/BaseMediaActivity.java

550 lines
22 KiB
Java

/*
* Copyright 2017 Martijn Brekhof. All rights reserved.
*
* 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 org.xbmc.kore.ui;
import android.annotation.TargetApi;
import android.content.Intent;
import android.content.res.Resources;
import android.os.Bundle;
import android.os.Handler;
import android.preference.PreferenceManager;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentTransaction;
import android.support.v4.widget.DrawerLayout;
import android.support.v7.app.ActionBar;
import android.support.v7.widget.Toolbar;
import android.text.TextUtils;
import android.transition.TransitionInflater;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.Window;
import android.widget.ImageView;
import com.sothree.slidinguppanel.SlidingUpPanelLayout;
import org.xbmc.kore.R;
import org.xbmc.kore.Settings;
import org.xbmc.kore.host.HostConnectionObserver;
import org.xbmc.kore.host.HostManager;
import org.xbmc.kore.jsonrpc.ApiCallback;
import org.xbmc.kore.jsonrpc.ApiMethod;
import org.xbmc.kore.jsonrpc.method.Application;
import org.xbmc.kore.jsonrpc.method.Player;
import org.xbmc.kore.jsonrpc.type.ListType;
import org.xbmc.kore.jsonrpc.type.PlayerType;
import org.xbmc.kore.ui.generic.NavigationDrawerFragment;
import org.xbmc.kore.ui.sections.remote.RemoteActivity;
import org.xbmc.kore.ui.generic.VolumeControllerDialogFragmentListener;
import org.xbmc.kore.ui.widgets.MediaProgressIndicator;
import org.xbmc.kore.ui.widgets.NowPlayingPanel;
import org.xbmc.kore.ui.widgets.VolumeLevelIndicator;
import org.xbmc.kore.utils.LogUtils;
import org.xbmc.kore.utils.SharedElementTransition;
import org.xbmc.kore.utils.UIUtils;
import org.xbmc.kore.utils.Utils;
import butterknife.ButterKnife;
import butterknife.BindView;
public abstract class BaseMediaActivity extends BaseActivity
implements HostConnectionObserver.ApplicationEventsObserver,
HostConnectionObserver.PlayerEventsObserver,
NowPlayingPanel.OnPanelButtonsClickListener,
MediaProgressIndicator.OnProgressChangeListener {
private static final String TAG = LogUtils.makeLogTag(BaseMediaActivity.class);
private static final String NAVICON_ISARROW = "navstate";
private static final String ACTIONBAR_TITLE = "actionbartitle";
@BindView(R.id.now_playing_panel) NowPlayingPanel nowPlayingPanel;
private NavigationDrawerFragment navigationDrawerFragment;
private SharedElementTransition sharedElementTransition = new SharedElementTransition();
private boolean drawerIndicatorIsArrow;
private int currentActivePlayerId = -1;
private HostManager hostManager;
private HostConnectionObserver hostConnectionObserver;
private boolean showNowPlayingPanel;
protected abstract String getActionBarTitle();
protected abstract Fragment createFragment();
/**
* Default callback for methods that don't return anything
*/
private ApiCallback<String> defaultStringActionCallback = ApiMethod.getDefaultActionCallback();
private Handler callbackHandler = new Handler();
private ApiCallback<Integer> defaultIntActionCallback = ApiMethod.getDefaultActionCallback();
private Runnable hidePanelRunnable = new Runnable() {
@Override
public void run() {
nowPlayingPanel.setPanelState(SlidingUpPanelLayout.PanelState.HIDDEN);
}
};
@Override
@TargetApi(21)
protected void onCreate(Bundle savedInstanceState) {
// Request transitions on lollipop
if (Utils.isLollipopOrLater()) {
getWindow().requestFeature(Window.FEATURE_CONTENT_TRANSITIONS);
}
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_generic_media);
ButterKnife.bind(this);
// Set up the drawer.
navigationDrawerFragment = (NavigationDrawerFragment)getSupportFragmentManager()
.findFragmentById(R.id.navigation_drawer);
navigationDrawerFragment.setUp(R.id.navigation_drawer, (DrawerLayout) findViewById(R.id.drawer_layout));
Toolbar toolbar = findViewById(R.id.default_toolbar);
setSupportActionBar(toolbar);
ActionBar actionBar = getSupportActionBar();
if (actionBar != null) {
String actionBarTitle;
boolean naviconIsArrow = false;
if (savedInstanceState != null) {
actionBarTitle = savedInstanceState.getString(ACTIONBAR_TITLE);
naviconIsArrow = savedInstanceState.getBoolean(NAVICON_ISARROW);
} else {
actionBarTitle = getActionBarTitle();
}
actionBar.setDisplayHomeAsUpEnabled(true);
updateActionBar(actionBarTitle, naviconIsArrow);
}
String fragmentTitle = getActionBarTitle();
Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.fragment_container);
if (fragment == null) {
fragment = createFragment();
if (Utils.isLollipopAndPreOreo()) {
fragment.setExitTransition(null);
fragment.setReenterTransition(TransitionInflater
.from(this)
.inflateTransition(android.R.transition.fade));
}
getSupportFragmentManager()
.beginTransaction()
.add(R.id.fragment_container, fragment, fragmentTitle)
.commit();
}
if (Utils.isLollipopAndPreOreo()) {
sharedElementTransition.setupExitTransition(this, fragment);
}
hostManager = HostManager.getInstance(this);
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putBoolean(NAVICON_ISARROW, drawerIndicatorIsArrow);
ActionBar actionBar = getSupportActionBar();
if (actionBar != null) {
CharSequence title = actionBar.getTitle();
if (title != null) {
outState.putString(ACTIONBAR_TITLE, title.toString());
}
}
}
@Override
protected void onResume() {
super.onResume();
showNowPlayingPanel = PreferenceManager.getDefaultSharedPreferences(this)
.getBoolean(Settings.KEY_PREF_SHOW_NOW_PLAYING_PANEL,
Settings.DEFAULT_PREF_SHOW_NOW_PLAYING_PANEL);
if(showNowPlayingPanel) {
setupNowPlayingPanel();
} else {
//Hide it in case we were displaying the panel and user disabled showing
//the panel in Settings
nowPlayingPanel.setPanelState(SlidingUpPanelLayout.PanelState.HIDDEN);
}
}
@Override
public void onPause() {
super.onPause();
if(!showNowPlayingPanel)
return;
hostConnectionObserver = hostManager.getHostConnectionObserver();
if (hostConnectionObserver == null)
return;
hostConnectionObserver.unregisterApplicationObserver(this);
hostConnectionObserver.unregisterPlayerObserver(this);
}
/**
* Override hardware volume keys and send to Kodi
*/
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
boolean handled = VolumeControllerDialogFragmentListener.handleVolumeKeyEvent(this, event);
if (handled) {
new VolumeControllerDialogFragmentListener()
.show(getSupportFragmentManager(), VolumeControllerDialogFragmentListener.class.getName());
}
return handled || super.dispatchKeyEvent(event);
}
public boolean getDrawerIndicatorIsArrow() {
return drawerIndicatorIsArrow;
}
/**
* Sets the title and drawer indicator of the toolbar
* @param title toolbar title
* @param showArrowIndicator true if the toolbar should show the back arrow indicator,
* false if it should show the drawer icon
*/
protected void updateActionBar(String title, boolean showArrowIndicator) {
if (showArrowIndicator != drawerIndicatorIsArrow) {
navigationDrawerFragment.animateDrawerToggle(showArrowIndicator);
drawerIndicatorIsArrow = showArrowIndicator;
}
ActionBar actionBar = getSupportActionBar();
if (actionBar != null)
actionBar.setTitle(title);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.media_info, menu);
return super.onCreateOptionsMenu(menu);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.action_show_remote:
Intent launchIntent = new Intent(this, RemoteActivity.class)
.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
startActivity(launchIntent);
return true;
default:
break;
}
return super.onOptionsItemSelected(item);
}
@TargetApi(21)
protected void showFragment(AbstractFragment fragment, ImageView sharedImageView, AbstractFragment.DataHolder dataHolder) {
FragmentTransaction fragTrans = getSupportFragmentManager().beginTransaction();
// Set up transitions
if (Utils.isLollipopAndPreOreo()) {
dataHolder.setPosterTransitionName(sharedImageView.getTransitionName());
sharedElementTransition.setupEnterTransition(this, fragTrans, fragment, sharedImageView);
} else {
fragTrans.setCustomAnimations(R.anim.fragment_details_enter, 0,
R.anim.fragment_list_popenter, 0);
}
fragTrans.replace(R.id.fragment_container, fragment, getActionBarTitle())
.addToBackStack(null)
.commit();
}
@Override
public void applicationOnVolumeChanged(int volume, boolean muted) {
nowPlayingPanel.setVolume(volume, muted);
}
@Override
public void playerOnPropertyChanged(org.xbmc.kore.jsonrpc.notification.Player.NotificationsData notificationsData) {
if (notificationsData.property.shuffled != null)
nowPlayingPanel.setShuffled(notificationsData.property.shuffled);
if (notificationsData.property.repeatMode != null )
nowPlayingPanel.setRepeatMode(notificationsData.property.repeatMode);
}
@Override
public void playerOnPlay(PlayerType.GetActivePlayersReturnType getActivePlayerResult,
PlayerType.PropertyValue getPropertiesResult,
ListType.ItemsAll getItemResult) {
currentActivePlayerId = getActivePlayerResult.playerid;
updateNowPlayingPanel(getPropertiesResult, getItemResult);
}
@Override
public void playerOnPause(PlayerType.GetActivePlayersReturnType getActivePlayerResult, PlayerType.PropertyValue getPropertiesResult, ListType.ItemsAll getItemResult) {
currentActivePlayerId = getActivePlayerResult.playerid;
updateNowPlayingPanel(getPropertiesResult, getItemResult);
}
@Override
public void playerOnStop() {
currentActivePlayerId = -1;
//We delay hiding the panel to prevent hiding the panel when playing
// the next item in a playlist
callbackHandler.removeCallbacks(hidePanelRunnable);
callbackHandler.postDelayed(hidePanelRunnable, 1000);
}
@Override
public void playerOnConnectionError(int errorCode, String description) {
}
@Override
public void playerNoResultsYet() {
}
@Override
public void observerOnStopObserving() {
nowPlayingPanel.setPanelState(SlidingUpPanelLayout.PanelState.HIDDEN);
}
@Override
public void systemOnQuit() {
nowPlayingPanel.setPanelState(SlidingUpPanelLayout.PanelState.HIDDEN);
}
@Override
public void inputOnInputRequested(String title, String type, String value) {
}
@Override
public void onProgressChanged(int progress) {
PlayerType.PositionTime positionTime = new PlayerType.PositionTime(progress);
Player.Seek seekAction = new Player.Seek(currentActivePlayerId, positionTime);
seekAction.execute(HostManager.getInstance(this).getConnection(), new ApiCallback<PlayerType.SeekReturnType>() {
@Override
public void onSuccess(PlayerType.SeekReturnType result) {
// Ignore
}
@Override
public void onError(int errorCode, String description) {
LogUtils.LOGE(TAG, "Got an error calling Player.Seek. Error code: " + errorCode + ", description: " + description);
}
}, new Handler());
}
@Override
public void onPlayClicked() {
Player.PlayPause action = new Player.PlayPause(currentActivePlayerId);
action.execute(hostManager.getConnection(), defaultIntActionCallback, callbackHandler);
}
@Override
public void onPreviousClicked() {
Player.GoTo action = new Player.GoTo(currentActivePlayerId, Player.GoTo.PREVIOUS);
action.execute(hostManager.getConnection(), defaultStringActionCallback, callbackHandler);
}
@Override
public void onNextClicked() {
Player.GoTo action = new Player.GoTo(currentActivePlayerId, Player.GoTo.NEXT);
action.execute(hostManager.getConnection(), defaultStringActionCallback, callbackHandler);
}
@Override
public void onVolumeMuteClicked() {
Application.SetMute action = new Application.SetMute();
action.execute(hostManager.getConnection(), new ApiCallback<Boolean>() {
@Override
public void onSuccess(Boolean result) {
//We depend on the listener to correct the mute button state
}
@Override
public void onError(int errorCode, String description) { }
}, new Handler());
}
@Override
public void onShuffleClicked() {
Player.SetShuffle action = new Player.SetShuffle(currentActivePlayerId);
action.execute(hostManager.getConnection(), new ApiCallback<String>() {
@Override
public void onSuccess(String result) {
//We depend on the listener to correct the mute button state
}
@Override
public void onError(int errorCode, String description) { }
}, callbackHandler);
}
@Override
public void onRepeatClicked() {
Player.SetRepeat action = new Player.SetRepeat(currentActivePlayerId, PlayerType.Repeat.CYCLE);
action.execute(hostManager.getConnection(), new ApiCallback<String>() {
@Override
public void onSuccess(String result) {
//We depend on the listener to correct the mute button state
}
@Override
public void onError(int errorCode, String description) { }
}, callbackHandler);
}
@Override
public void onVolumeMutedIndicatorClicked() {
Application.SetMute action = new Application.SetMute();
action.execute(hostManager.getConnection(), new ApiCallback<Boolean>() {
@Override
public void onSuccess(Boolean result) {
//We depend on the listener to correct the mute button state
}
@Override
public void onError(int errorCode, String description) { }
}, new Handler());
}
private void setupNowPlayingPanel() {
nowPlayingPanel.setOnVolumeChangeListener(new VolumeLevelIndicator.OnVolumeChangeListener() {
@Override
public void onVolumeChanged(int volume) {
new Application.SetVolume(volume)
.execute(hostManager.getConnection(), defaultIntActionCallback, new Handler());
}
});
nowPlayingPanel.setOnPanelButtonsClickListener(this);
nowPlayingPanel.setOnProgressChangeListener(this);
hostConnectionObserver = hostManager.getHostConnectionObserver();
if (hostConnectionObserver == null)
return;
hostConnectionObserver.registerApplicationObserver(this, true);
hostConnectionObserver.registerPlayerObserver(this, true);
hostConnectionObserver.forceRefreshResults();
}
private void updateNowPlayingPanel(PlayerType.PropertyValue getPropertiesResult,
ListType.ItemsAll getItemResult) {
String title;
String poster;
String details = null;
callbackHandler.removeCallbacks(hidePanelRunnable);
// Only set state to collapsed if panel is currently hidden. This prevents collapsing
// the panel when the user expanded the panel and started playing the item from a paused
// state
if (nowPlayingPanel.getPanelState() == SlidingUpPanelLayout.PanelState.HIDDEN) {
nowPlayingPanel.setPanelState(SlidingUpPanelLayout.PanelState.COLLAPSED);
}
nowPlayingPanel.setMediaProgress(getPropertiesResult.time, getPropertiesResult.totaltime);
nowPlayingPanel.setPlayButton(getPropertiesResult.speed > 0);
nowPlayingPanel.setShuffled(getPropertiesResult.shuffled);
nowPlayingPanel.setRepeatMode(getPropertiesResult.repeat);
nowPlayingPanel.setSpeed(getPropertiesResult.speed);
switch (getItemResult.type) {
case ListType.ItemsAll.TYPE_MOVIE:
title = getItemResult.title;
details = getItemResult.tagline;
poster = TextUtils.isEmpty(getItemResult.thumbnail) ? getItemResult.fanart
: getItemResult.thumbnail;
break;
case ListType.ItemsAll.TYPE_EPISODE:
title = getItemResult.title;
String seasonEpisode = String.format(getString(R.string.season_episode_abbrev),
getItemResult.season, getItemResult.episode);
details = String.format("%s | %s", getItemResult.showtitle, seasonEpisode);
poster = TextUtils.isEmpty(getItemResult.art.poster) ? getItemResult.art.fanart
: getItemResult.art.poster;
break;
case ListType.ItemsAll.TYPE_SONG:
title = getItemResult.title;
details = getItemResult.displayartist + " | " + getItemResult.album;
poster = TextUtils.isEmpty(getItemResult.thumbnail) ? getItemResult.fanart
: getItemResult.thumbnail;
break;
case ListType.ItemsAll.TYPE_MUSIC_VIDEO:
title = getItemResult.title;
details = Utils.listStringConcat(getItemResult.artist, ", ") + " | " + getItemResult.album;
poster = TextUtils.isEmpty(getItemResult.thumbnail) ? getItemResult.fanart
: getItemResult.thumbnail;
break;
case ListType.ItemsAll.TYPE_CHANNEL:
title = getItemResult.label;
details = getItemResult.title;
poster = TextUtils.isEmpty(getItemResult.thumbnail) ? getItemResult.fanart
: getItemResult.thumbnail;
break;
default:
title = getItemResult.label;
poster = TextUtils.isEmpty(getItemResult.thumbnail) ? getItemResult.fanart
: getItemResult.thumbnail;
break;
}
if (title.contentEquals(nowPlayingPanel.getTitle()))
return; // Still showing same item as previous call
nowPlayingPanel.setTitle(title);
if (details != null) {
nowPlayingPanel.setDetails(details);
}
if ((getItemResult.type.contentEquals(ListType.ItemsAll.TYPE_MUSIC_VIDEO)) ||
(getItemResult.type.contentEquals(ListType.ItemsAll.TYPE_SONG))) {
nowPlayingPanel.setNextPrevVisibility(View.VISIBLE);
} else {
nowPlayingPanel.setNextPrevVisibility(View.GONE);
}
Resources resources = getResources();
int posterWidth = resources.getDimensionPixelOffset(R.dimen.now_playing_panel_art_width);
int posterHeight = resources.getDimensionPixelOffset(R.dimen.now_playing_panel_height);
// If not video, change aspect ration of poster to a square
boolean isVideo = (getItemResult.type.equals(ListType.ItemsAll.TYPE_MOVIE)) ||
(getItemResult.type.equals(ListType.ItemsAll.TYPE_EPISODE));
nowPlayingPanel.setSquarePoster(!isVideo);
UIUtils.loadImageWithCharacterAvatar(this, hostManager, poster, title,
nowPlayingPanel.getPoster(),
(isVideo) ? posterWidth : posterHeight, posterHeight);
}
}