Implemented showing volume level on NowPlayingFragment (#312)

Replaced up/down volume buttons with a seekbar that displays the current
volume level and can be used to change the volume level
This commit is contained in:
Martijn Brekhof 2016-11-30 13:20:21 +01:00 committed by Synced Synapse
parent d2b5449e98
commit c5848ce648
7 changed files with 368 additions and 108 deletions

View File

@ -21,8 +21,10 @@ import org.xbmc.kore.jsonrpc.ApiCallback;
import org.xbmc.kore.jsonrpc.HostConnection;
import org.xbmc.kore.jsonrpc.method.JSONRPC;
import org.xbmc.kore.jsonrpc.method.Player;
import org.xbmc.kore.jsonrpc.notification.Application;
import org.xbmc.kore.jsonrpc.notification.Input;
import org.xbmc.kore.jsonrpc.notification.System;
import org.xbmc.kore.jsonrpc.type.ApplicationType;
import org.xbmc.kore.jsonrpc.type.ListType;
import org.xbmc.kore.jsonrpc.type.PlayerType;
import org.xbmc.kore.utils.LogUtils;
@ -43,10 +45,20 @@ import java.util.List;
*/
public class HostConnectionObserver
implements HostConnection.PlayerNotificationsObserver,
HostConnection.SystemNotificationsObserver,
HostConnection.InputNotificationsObserver {
HostConnection.SystemNotificationsObserver,
HostConnection.InputNotificationsObserver,
HostConnection.ApplicationNotificationsObserver {
public static final String TAG = LogUtils.makeLogTag(HostConnectionObserver.class);
public interface ApplicationEventsObserver {
/**
* Notifies the observer that volume has changed
* @param volume
* @param muted
*/
public void applicationOnVolumeChanged(int volume, boolean muted);
}
/**
* Interface that an observer has to implement to receive player events
*/
@ -123,6 +135,7 @@ public class HostConnectionObserver
* The list of observers
*/
private List<PlayerEventsObserver> playerEventsObservers = new ArrayList<PlayerEventsObserver>();
private List<ApplicationEventsObserver> applicationEventsObservers = new ArrayList<>();
// /**
// * Handlers for which observer, on which to notify them
@ -130,7 +143,7 @@ public class HostConnectionObserver
// private Map<PlayerEventsObserver, Handler> observerHandlerMap = new HashMap<PlayerEventsObserver, Handler>();
private Handler checkerHandler = new Handler();
private Runnable httpCheckerRunnable = new Runnable() {
private Runnable httpPlayerCheckerRunnable = new Runnable() {
@Override
public void run() {
final int HTTP_NOTIFICATION_CHECK_INTERVAL = 3000;
@ -145,6 +158,20 @@ public class HostConnectionObserver
}
};
private Runnable httpApplicationCheckerRunnable = new Runnable() {
@Override
public void run() {
final int HTTP_NOTIFICATION_CHECK_INTERVAL = 3000;
// If no one is listening to this, just exit
if (applicationEventsObservers.isEmpty()) return;
getApplicationProperties();
// Keep checking
checkerHandler.postDelayed(this, HTTP_NOTIFICATION_CHECK_INTERVAL);
}
};
private Runnable tcpCheckerRunnable = new Runnable() {
@Override
public void run() {
@ -158,8 +185,8 @@ public class HostConnectionObserver
@Override
public void onSuccess(String result) {
// Ok, we've got a ping, if we were in a error or uninitialized state, update
if ((lastCallResult == PlayerEventsObserver.PLAYER_NO_RESULT) ||
(lastCallResult == PlayerEventsObserver.PLAYER_CONNECTION_ERROR)) {
if ((hostState.lastCallResult == PlayerEventsObserver.PLAYER_NO_RESULT) ||
(hostState.lastCallResult == PlayerEventsObserver.PLAYER_CONNECTION_ERROR)) {
checkWhatsPlaying();
}
checkerHandler.postDelayed(tcpCheckerRunnable, PING_AFTER_SUCCESS_CHECK_INTERVAL);
@ -181,14 +208,31 @@ public class HostConnectionObserver
}
};
private int lastCallResult = PlayerEventsObserver.PLAYER_NO_RESULT;
private PlayerType.GetActivePlayersReturnType lastGetActivePlayerResult = null;
private PlayerType.PropertyValue lastGetPropertiesResult = null;
private ListType.ItemsAll lastGetItemResult = null;
private int lastErrorCode;
private String lastErrorDescription;
public class HostState {
private int lastCallResult = PlayerEventsObserver.PLAYER_NO_RESULT;
private PlayerType.GetActivePlayersReturnType lastGetActivePlayerResult = null;
private PlayerType.PropertyValue lastGetPropertiesResult = null;
private ListType.ItemsAll lastGetItemResult = null;
private boolean volumeMuted = false;
private int volumeLevel = -1; // -1 indicates no volumeLevel known
private int lastErrorCode;
private String lastErrorDescription;
public int getVolumeLevel() {
return volumeLevel;
}
public boolean isVolumeMuted() {
return volumeMuted;
}
}
public HostState hostState;
private HostConnectionObserver() {}
public HostConnectionObserver(HostConnection connection) {
this.hostState = new HostState();
this.connection = connection;
}
@ -216,7 +260,7 @@ public class HostConnectionObserver
// Start the ping checker
checkerHandler.post(tcpCheckerRunnable);
} else {
checkerHandler.post(httpCheckerRunnable);
checkerHandler.post(httpPlayerCheckerRunnable);
}
}
}
@ -229,7 +273,8 @@ public class HostConnectionObserver
playerEventsObservers.remove(observer);
// observerHandlerMap.remove(observer);
LogUtils.LOGD(TAG, "Unregistering observer. Still got " + playerEventsObservers.size() +
LogUtils.LOGD(TAG, "Unregistering player observer " + observer.getClass().getSimpleName() +
". Still got " + playerEventsObservers.size() +
" observers.");
if (playerEventsObservers.isEmpty()) {
@ -241,9 +286,61 @@ public class HostConnectionObserver
connection.unregisterInputNotificationsObserver(this);
checkerHandler.removeCallbacks(tcpCheckerRunnable);
} else {
checkerHandler.removeCallbacks(httpCheckerRunnable);
checkerHandler.removeCallbacks(httpPlayerCheckerRunnable);
}
hostState.lastCallResult = PlayerEventsObserver.PLAYER_NO_RESULT;
}
}
/**
* Registers a new observer that will be notified about application events
* @param observer Observer
* @param replyImmediately
*/
public void registerApplicationObserver(ApplicationEventsObserver observer, boolean replyImmediately) {
if (this.connection == null)
return;
applicationEventsObservers.add(observer);
if (replyImmediately) {
if( hostState.volumeLevel == -1 ) {
getApplicationProperties();
} else {
observer.applicationOnVolumeChanged(hostState.volumeLevel, hostState.volumeMuted);
}
}
if (applicationEventsObservers.size() == 1) {
// If this is the first observer, start checking through HTTP or register us
// as a connection observer, which we will pass to the "real" observer
if (connection.getProtocol() == HostConnection.PROTOCOL_TCP) {
connection.registerApplicationNotificationsObserver(this, checkerHandler);
} else {
checkerHandler.post(httpApplicationCheckerRunnable);
}
}
}
/**
* Unregisters a previously registered observer
* @param observer Observer to unregister
*/
public void unregisterApplicationObserver(PlayerEventsObserver observer) {
applicationEventsObservers.remove(observer);
LogUtils.LOGD(TAG, "Unregistering application observer " + observer.getClass().getSimpleName() +
". Still got " + applicationEventsObservers.size() +
" observers.");
if (applicationEventsObservers.isEmpty()) {
// No more observers, so unregister us from the host connection, or stop
// the http checker thread
if (connection.getProtocol() == HostConnection.PROTOCOL_TCP) {
connection.unregisterApplicationotificationsObserver(this);
} else {
checkerHandler.removeCallbacks(httpApplicationCheckerRunnable);
}
lastCallResult = PlayerEventsObserver.PLAYER_NO_RESULT;
}
}
@ -262,9 +359,9 @@ public class HostConnectionObserver
connection.unregisterInputNotificationsObserver(this);
checkerHandler.removeCallbacks(tcpCheckerRunnable);
} else {
checkerHandler.removeCallbacks(httpCheckerRunnable);
checkerHandler.removeCallbacks(httpPlayerCheckerRunnable);
}
lastCallResult = PlayerEventsObserver.PLAYER_NO_RESULT;
hostState.lastCallResult = PlayerEventsObserver.PLAYER_NO_RESULT;
}
/**
@ -332,6 +429,39 @@ public class HostConnectionObserver
}
}
@Override
public void onVolumeChanged(Application.OnVolumeChanged notification) {
hostState.volumeMuted = notification.muted;
hostState.volumeLevel = notification.volume;
for (ApplicationEventsObserver observer : applicationEventsObservers) {
observer.applicationOnVolumeChanged(notification.volume, notification.muted);
}
}
private void getApplicationProperties() {
org.xbmc.kore.jsonrpc.method.Application.GetProperties getProperties =
new org.xbmc.kore.jsonrpc.method.Application.GetProperties(org.xbmc.kore.jsonrpc.method.Application.GetProperties.VOLUME,
org.xbmc.kore.jsonrpc.method.Application.GetProperties.MUTED);
getProperties.execute(connection, new ApiCallback<ApplicationType.PropertyValue>() {
@Override
public void onSuccess(ApplicationType.PropertyValue result) {
hostState.volumeMuted = result.muted;
hostState.volumeLevel = result.volume;
for (ApplicationEventsObserver observer : applicationEventsObservers) {
observer.applicationOnVolumeChanged(result.volume, result.muted);
}
}
@Override
public void onError(int errorCode, String description) {
LogUtils.LOGD(TAG, "Could not get application properties");
notifyConnectionError(errorCode, description, playerEventsObservers);
}
}, checkerHandler);
}
/**
* Checks whats playing and notifies observers
*/
@ -344,7 +474,7 @@ public class HostConnectionObserver
/**
* Calls Player.GetActivePlayers
* On success chains execution to chainCallGetProperties
* On success chains execution to chainCallGetPlayerProperties
*/
private void chainCallGetActivePlayers() {
Player.GetActivePlayers getActivePlayers = new Player.GetActivePlayers();
@ -356,7 +486,7 @@ public class HostConnectionObserver
notifyNothingIsPlaying(playerEventsObservers);
return;
}
chainCallGetProperties(result.get(0));
chainCallGetPlayerProperties(result.get(0));
}
@Override
@ -371,7 +501,7 @@ public class HostConnectionObserver
* Calls Player.GetProperties
* On success chains execution to chainCallGetItem
*/
private void chainCallGetProperties(final PlayerType.GetActivePlayersReturnType getActivePlayersResult) {
private void chainCallGetPlayerProperties(final PlayerType.GetActivePlayersReturnType getActivePlayersResult) {
String propertiesToGet[] = new String[] {
// Check is something more is needed
PlayerType.PropertyName.SPEED,
@ -465,7 +595,7 @@ public class HostConnectionObserver
}, checkerHandler);
}
// Whether to foorce a reply or if the results are equal to the last one, don't reply
// Whether to force a reply or if the results are equal to the last one, don't reply
private boolean forceReply = false;
/**
@ -478,11 +608,11 @@ public class HostConnectionObserver
private void notifyConnectionError(final int errorCode, final String description, List<PlayerEventsObserver> observers) {
// Reply if different from last result
if (forceReply ||
(lastCallResult != PlayerEventsObserver.PLAYER_CONNECTION_ERROR) ||
(lastErrorCode != errorCode)) {
lastCallResult = PlayerEventsObserver.PLAYER_CONNECTION_ERROR;
lastErrorCode = errorCode;
lastErrorDescription = description;
(hostState.lastCallResult != PlayerEventsObserver.PLAYER_CONNECTION_ERROR) ||
(hostState.lastErrorCode != errorCode)) {
hostState.lastCallResult = PlayerEventsObserver.PLAYER_CONNECTION_ERROR;
hostState.lastErrorCode = errorCode;
hostState.lastErrorDescription = description;
forceReply = false;
// Copy list to prevent ConcurrentModificationExceptions
List<PlayerEventsObserver> allObservers = new ArrayList<>(observers);
@ -519,8 +649,8 @@ public class HostConnectionObserver
private void notifyNothingIsPlaying(List<PlayerEventsObserver> observers) {
// Reply if forced or different from last result
if (forceReply ||
(lastCallResult != PlayerEventsObserver.PLAYER_IS_STOPPED)) {
lastCallResult = PlayerEventsObserver.PLAYER_IS_STOPPED;
(hostState.lastCallResult != PlayerEventsObserver.PLAYER_IS_STOPPED)) {
hostState.lastCallResult = PlayerEventsObserver.PLAYER_IS_STOPPED;
forceReply = false;
// Copy list to prevent ConcurrentModificationExceptions
List<PlayerEventsObserver> allObservers = new ArrayList<>(observers);
@ -554,16 +684,16 @@ public class HostConnectionObserver
int currentCallResult = (getPropertiesResult.speed == 0) ?
PlayerEventsObserver.PLAYER_IS_PAUSED : PlayerEventsObserver.PLAYER_IS_PLAYING;
if (forceReply ||
(lastCallResult != currentCallResult) ||
(lastGetPropertiesResult.speed != getPropertiesResult.speed) ||
(lastGetPropertiesResult.shuffled != getPropertiesResult.shuffled) ||
(!lastGetPropertiesResult.repeat.equals(getPropertiesResult.repeat)) ||
(lastGetItemResult.id != getItemResult.id) ||
(!lastGetItemResult.label.equals(getItemResult.label))) {
lastCallResult = currentCallResult;
lastGetActivePlayerResult = getActivePlayersResult;
lastGetPropertiesResult = getPropertiesResult;
lastGetItemResult = getItemResult;
(hostState.lastCallResult != currentCallResult) ||
(hostState.lastGetPropertiesResult.speed != getPropertiesResult.speed) ||
(hostState.lastGetPropertiesResult.shuffled != getPropertiesResult.shuffled) ||
(!hostState.lastGetPropertiesResult.repeat.equals(getPropertiesResult.repeat)) ||
(hostState.lastGetItemResult.id != getItemResult.id) ||
(!hostState.lastGetItemResult.label.equals(getItemResult.label))) {
hostState.lastCallResult = currentCallResult;
hostState.lastGetActivePlayerResult = getActivePlayersResult;
hostState.lastGetPropertiesResult = getPropertiesResult;
hostState.lastGetItemResult = getItemResult;
forceReply = false;
// Copy list to prevent ConcurrentModificationExceptions
List<PlayerEventsObserver> allObservers = new ArrayList<>(observers);
@ -619,16 +749,16 @@ public class HostConnectionObserver
* @param observer Obserser to call with last result
*/
public void replyWithLastResult(PlayerEventsObserver observer) {
switch (lastCallResult) {
switch (hostState.lastCallResult) {
case PlayerEventsObserver.PLAYER_CONNECTION_ERROR:
notifyConnectionError(lastErrorCode, lastErrorDescription, observer);
notifyConnectionError(hostState.lastErrorCode, hostState.lastErrorDescription, observer);
break;
case PlayerEventsObserver.PLAYER_IS_STOPPED:
notifyNothingIsPlaying(observer);
break;
case PlayerEventsObserver.PLAYER_IS_PAUSED:
case PlayerEventsObserver.PLAYER_IS_PLAYING:
notifySomethingIsPlaying(lastGetActivePlayerResult, lastGetPropertiesResult, lastGetItemResult, observer);
notifySomethingIsPlaying(hostState.lastGetActivePlayerResult, hostState.lastGetPropertiesResult, hostState.lastGetItemResult, observer);
break;
case PlayerEventsObserver.PLAYER_NO_RESULT:
observer.playerNoResultsYet();
@ -643,4 +773,8 @@ public class HostConnectionObserver
forceReply = true;
chainCallGetActivePlayers();
}
public HostState getHostState() {
return hostState;
}
}

View File

@ -32,6 +32,7 @@ import com.squareup.okhttp.RequestBody;
import com.squareup.okhttp.Response;
import org.xbmc.kore.host.HostInfo;
import org.xbmc.kore.jsonrpc.notification.Application;
import org.xbmc.kore.jsonrpc.notification.Input;
import org.xbmc.kore.jsonrpc.notification.Player;
import org.xbmc.kore.jsonrpc.notification.System;
@ -92,6 +93,10 @@ public class HostConnection {
public void onInputRequested(Input.OnInputRequested notification);
}
public interface ApplicationNotificationsObserver {
public void onVolumeChanged(Application.OnVolumeChanged notification);
}
/**
* Host to connect too
*/
@ -139,6 +144,12 @@ public class HostConnection {
private final HashMap<InputNotificationsObserver, Handler> inputNotificationsObservers =
new HashMap<InputNotificationsObserver, Handler>();
/**
* The observers that will be notified of application notifications
*/
private final HashMap<ApplicationNotificationsObserver, Handler> applicationNotificationsObservers =
new HashMap<>();
private ExecutorService executorService;
private final int connectTimeout;
@ -250,6 +261,23 @@ public class HostConnection {
inputNotificationsObservers.remove(observer);
}
/**
* Registers an observer for input notifications
* @param observer The {@link InputNotificationsObserver}
*/
public void registerApplicationNotificationsObserver(ApplicationNotificationsObserver observer,
Handler handler) {
applicationNotificationsObservers.put(observer, handler);
}
/**
* Unregisters and observer from the input notifications
* @param observer The {@link InputNotificationsObserver}
*/
public void unregisterApplicationotificationsObserver(ApplicationNotificationsObserver observer) {
applicationNotificationsObservers.remove(observer);
}
/**
* Calls the a method on the server
* This call is always asynchronous. The results will be posted, through the
@ -814,6 +842,19 @@ public class HostConnection {
}
});
}
} else if (notificationName.equals(Application.OnVolumeChanged.NOTIFICATION_NAME)) {
final Application.OnVolumeChanged apiNotification =
new Application.OnVolumeChanged(params);
for (final ApplicationNotificationsObserver observer :
applicationNotificationsObservers.keySet()) {
Handler handler = inputNotificationsObservers.get(observer);
handler.post(new Runnable() {
@Override
public void run() {
observer.onVolumeChanged(apiNotification);
}
});
}
}
LogUtils.LOGD(TAG, "Got a notification: " + jsonResponse.get("method").textValue());

View File

@ -57,7 +57,7 @@ public class Application {
public final static String METHOD_NAME = "Application.SetVolume";
/**
* Set the current volume
* Increment or decrement the volume
* @param volume String enum in {@link org.xbmc.kore.jsonrpc.type.GlobalType.IncrementDecrement}
*/
public SetVolume(String volume) {
@ -65,6 +65,15 @@ public class Application {
addParameterToRequest("volume", volume);
}
/**
* Set the volume
* @param volume volume between 0 and 100
*/
public SetVolume(int volume) {
super();
addParameterToRequest("volume", volume);
}
@Override
public String getMethodName() { return METHOD_NAME; }

View File

@ -0,0 +1,50 @@
/*
* Copyright 2015 Synced Synapse. 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.jsonrpc.notification;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.xbmc.kore.jsonrpc.ApiNotification;
import org.xbmc.kore.jsonrpc.type.GlobalType;
import org.xbmc.kore.utils.JsonUtils;
/**
* All Player.* notifications
*/
public class Application {
/**
* Player.OnSpeedChanged notification
* Speed of the playback of a media item has been changed. If there is no ID available extra information will be provided.
* be provided.
*/
public static class OnVolumeChanged extends ApiNotification {
public static final String NOTIFICATION_NAME = "Application.OnVolumeChanged";
public final int volume;
public final boolean muted;
public OnVolumeChanged(ObjectNode node) {
super(node);
ObjectNode dataNode = (ObjectNode)node.get("data");
volume = JsonUtils.intFromJsonNode(dataNode, "volume");
muted = JsonUtils.booleanFromJsonNode(dataNode, "muted");
}
public String getNotificationName() { return NOTIFICATION_NAME; }
}
}

View File

@ -50,7 +50,6 @@ import org.xbmc.kore.jsonrpc.method.Application;
import org.xbmc.kore.jsonrpc.method.GUI;
import org.xbmc.kore.jsonrpc.method.Input;
import org.xbmc.kore.jsonrpc.method.Player;
import org.xbmc.kore.jsonrpc.type.ApplicationType;
import org.xbmc.kore.jsonrpc.type.GlobalType;
import org.xbmc.kore.jsonrpc.type.ListType;
import org.xbmc.kore.jsonrpc.type.PlayerType;
@ -72,7 +71,8 @@ import butterknife.OnClick;
*/
public class NowPlayingFragment extends Fragment
implements HostConnectionObserver.PlayerEventsObserver,
GenericSelectDialog.GenericSelectDialogListener {
HostConnectionObserver.ApplicationEventsObserver,
GenericSelectDialog.GenericSelectDialogListener {
private static final String TAG = LogUtils.makeLogTag(NowPlayingFragment.class);
/**
@ -132,8 +132,6 @@ public class NowPlayingFragment extends Fragment
@InjectView(R.id.rewind) ImageButton rewindButton;
@InjectView(R.id.fast_forward) ImageButton fastForwardButton;
@InjectView(R.id.volume_down) ImageButton volumeDownButton;
@InjectView(R.id.volume_up) ImageButton volumeUpButton;
@InjectView(R.id.volume_mute) ImageButton volumeMuteButton;
@InjectView(R.id.shuffle) ImageButton shuffleButton;
@InjectView(R.id.repeat) ImageButton repeatButton;
@ -154,6 +152,9 @@ public class NowPlayingFragment extends Fragment
@InjectView(R.id.media_progress) TextView mediaProgress;
@InjectView(R.id.seek_bar) SeekBar mediaSeekbar;
@InjectView(R.id.volume_bar) SeekBar volumeSeekBar;
@InjectView(R.id.volume_text) TextView volumeTextView;
@InjectView(R.id.media_details) RelativeLayout mediaDetailsPanel;
@InjectView(R.id.rating) TextView mediaRating;
@InjectView(R.id.max_rating) TextView mediaMaxRating;
@ -187,11 +188,6 @@ public class NowPlayingFragment extends Fragment
ViewGroup root = (ViewGroup) inflater.inflate(R.layout.fragment_now_playing, container, false);
ButterKnife.inject(this, root);
setupVolumeRepeatButton(volumeDownButton,
new Application.SetVolume(GlobalType.IncrementDecrement.DECREMENT));
setupVolumeRepeatButton(volumeUpButton,
new Application.SetVolume(GlobalType.IncrementDecrement.INCREMENT));
// Setup dim the fanart when scroll changes
// Full dim on 4 * iconSize dp
Resources resources = getActivity().getResources();
@ -222,16 +218,7 @@ public class NowPlayingFragment extends Fragment
public void onResume() {
super.onResume();
hostConnectionObserver.registerPlayerObserver(this, true);
Application.GetProperties action = new Application.GetProperties(Application.GetProperties.MUTED);
action.execute(hostManager.getConnection(), new ApiCallback<ApplicationType.PropertyValue>() {
@Override
public void onSuccess(ApplicationType.PropertyValue result) {
setVolumeMuteButton(result.muted);
}
@Override
public void onError(int errorCode, String description) { }
}, callbackHandler);
hostConnectionObserver.registerApplicationObserver(this, true);
}
@Override
@ -239,6 +226,7 @@ public class NowPlayingFragment extends Fragment
super.onPause();
stopNowPlayingInfo();
hostConnectionObserver.unregisterPlayerObserver(this);
hostConnectionObserver.unregisterApplicationObserver(this);
}
/**
@ -262,16 +250,6 @@ public class NowPlayingFragment extends Fragment
public void onError(int errorCode, String description) { }
};
private void setupVolumeRepeatButton(View button, final ApiMethod<Integer> action) {
button.setOnTouchListener(new RepeatListener(UIUtils.initialButtonRepeatInterval, UIUtils.buttonRepeatInterval,
new View.OnClickListener() {
@Override
public void onClick(View v) {
action.execute(hostManager.getConnection(), defaultIntActionCallback, callbackHandler);
}
}));
}
/**
* Callbacks for bottom button bar
*/
@ -317,11 +295,17 @@ public class NowPlayingFragment extends Fragment
*/
@OnClick(R.id.volume_mute)
public void onVolumeMuteClicked(View v) {
// We boldly set the mute button to the desired state before actually setting
// the mute state on the host. We do this to make it clear to the user that the button
// was pressed.
HostConnectionObserver.HostState hostState = hostConnectionObserver.getHostState();
setVolumeState(!hostState.isVolumeMuted(), hostState.getVolumeLevel());
Application.SetMute action = new Application.SetMute();
action.execute(hostManager.getConnection(), new ApiCallback<Boolean>() {
@Override
public void onSuccess(Boolean result) {
setVolumeMuteButton(result);
//We depend on the listener to correct the mute button state
}
@Override
@ -386,7 +370,7 @@ public class NowPlayingFragment extends Fragment
case R.id.audiostreams:
// Setup audiostream select dialog
String[] audiostreams = new String[(availableAudioStreams != null) ?
availableAudioStreams.size() + ADDED_AUDIO_OPTIONS : ADDED_AUDIO_OPTIONS];
availableAudioStreams.size() + ADDED_AUDIO_OPTIONS : ADDED_AUDIO_OPTIONS];
audiostreams[0] = getString(R.string.audio_sync);
@ -394,21 +378,21 @@ public class NowPlayingFragment extends Fragment
for (int i = 0; i < availableAudioStreams.size(); i++) {
PlayerType.AudioStream current = availableAudioStreams.get(i);
audiostreams[i + ADDED_AUDIO_OPTIONS] = TextUtils.isEmpty(current.language) ?
current.name : current.language + " | " + current.name;
current.name : current.language + " | " + current.name;
if (current.index == currentAudiostreamIndex) {
selectedItem = i + ADDED_AUDIO_OPTIONS;
}
}
GenericSelectDialog dialog = GenericSelectDialog.newInstance(NowPlayingFragment.this,
SELECT_AUDIOSTREAM, getString(R.string.audiostreams), audiostreams, selectedItem);
SELECT_AUDIOSTREAM, getString(R.string.audiostreams), audiostreams, selectedItem);
dialog.show(NowPlayingFragment.this.getFragmentManager(), null);
}
return true;
case R.id.subtitles:
// Setup subtitles select dialog
String[] subtitles = new String[(availableSubtitles != null) ?
availableSubtitles.size() + ADDED_SUBTITLE_OPTIONS : ADDED_SUBTITLE_OPTIONS];
availableSubtitles.size() + ADDED_SUBTITLE_OPTIONS : ADDED_SUBTITLE_OPTIONS];
subtitles[0] = getString(R.string.download_subtitle);
subtitles[1] = getString(R.string.subtitle_sync);
@ -418,7 +402,7 @@ public class NowPlayingFragment extends Fragment
for (int i = 0; i < availableSubtitles.size(); i++) {
PlayerType.Subtitle current = availableSubtitles.get(i);
subtitles[i + ADDED_SUBTITLE_OPTIONS] = TextUtils.isEmpty(current.language) ?
current.name : current.language + " | " + current.name;
current.name : current.language + " | " + current.name;
if (current.index == currentSubtitleIndex) {
selectedItem = i + ADDED_SUBTITLE_OPTIONS;
}
@ -426,7 +410,7 @@ public class NowPlayingFragment extends Fragment
}
GenericSelectDialog dialog = GenericSelectDialog.newInstance(NowPlayingFragment.this,
SELECT_SUBTITLES, getString(R.string.subtitles), subtitles, selectedItem);
SELECT_SUBTITLES, getString(R.string.subtitles), subtitles, selectedItem);
dialog.show(NowPlayingFragment.this.getFragmentManager(), null);
return true;
}
@ -519,8 +503,8 @@ public class NowPlayingFragment extends Fragment
public void onError(int errorCode, String description) {
if (!isAdded()) return;
Toast.makeText(getActivity(),
String.format(getString(R.string.error_executing_subtitles), description),
Toast.LENGTH_SHORT).show();
String.format(getString(R.string.error_executing_subtitles), description),
Toast.LENGTH_SHORT).show();
}
}, callbackHandler);
}
@ -614,6 +598,11 @@ public class NowPlayingFragment extends Fragment
playerNoResultsYet();
}
@Override
public void applicationOnVolumeChanged(int volume, boolean muted) {
setVolumeState(muted, volume);
}
// Ignore this
public void inputOnInputRequested(String title, String type, String value) {}
public void observerOnStopObserving() {}
@ -679,7 +668,7 @@ public class NowPlayingFragment extends Fragment
title = getItemResult.title;
underTitle = Utils.listStringConcat(getItemResult.artist, ", ")
+ " | " + getItemResult.album;
+ " | " + getItemResult.album;
art = getItemResult.fanart;
poster = getItemResult.thumbnail;
@ -783,6 +772,8 @@ public class NowPlayingFragment extends Fragment
}
styledAttributes.recycle();
volumeSeekBar.setOnSeekBarChangeListener(volumeSeekbarChangeListener);
Resources resources = getActivity().getResources();
DisplayMetrics displayMetrics = new DisplayMetrics();
getActivity().getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
@ -796,7 +787,7 @@ public class NowPlayingFragment extends Fragment
// 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));
(getItemResult.type.equals(ListType.ItemsAll.TYPE_EPISODE));
if (!isVideo) {
ViewGroup.LayoutParams layoutParams = mediaPoster.getLayoutParams();
layoutParams.height = layoutParams.width;
@ -805,8 +796,8 @@ public class NowPlayingFragment extends Fragment
}
UIUtils.loadImageWithCharacterAvatar(getActivity(), hostManager,
poster, title,
mediaPoster, posterWidth, posterHeight);
poster, title,
mediaPoster, posterWidth, posterHeight);
UIUtils.loadImageIntoImageview(hostManager, art, mediaArt, displayMetrics.widthPixels, artHeight);
// Reset padding
@ -838,7 +829,7 @@ public class NowPlayingFragment extends Fragment
// TODO: change this check to the commeted out one when jsonrpc returns the correct type
// if (getPropertiesResult.type.equals(PlayerType.PropertyValue.TYPE_VIDEO)) {
if ((getPropertiesResult.audiostreams != null) &&
(getPropertiesResult.audiostreams.size() > 0)) {
(getPropertiesResult.audiostreams.size() > 0)) {
overflowButton.setVisibility(View.VISIBLE);
videoCastList.setVisibility(View.VISIBLE);
@ -936,14 +927,14 @@ public class NowPlayingFragment extends Fragment
*/
private void setDurationInfo(String type, GlobalType.Time time, GlobalType.Time totalTime, int speed) {
mediaTotalTime = totalTime.hours * 3600 +
totalTime.minutes * 60 +
totalTime.seconds;
totalTime.minutes * 60 +
totalTime.seconds;
mediaSeekbar.setMax(mediaTotalTime);
mediaDuration.setText(UIUtils.formatTime(totalTime));
mediaCurrentTime = time.hours * 3600 +
time.minutes * 60 +
time.seconds;
time.minutes * 60 +
time.seconds;
mediaSeekbar.setProgress(mediaCurrentTime);
mediaProgress.setText(UIUtils.formatTime(time));
@ -955,24 +946,53 @@ public class NowPlayingFragment extends Fragment
}
/**
* Sets the color of the mute volume button according to the player's status
* @param isMuted Whether the player is muted
* Sets UI volume state
* @param muted whether volume is muted
* @param volume
*/
private void setVolumeMuteButton(Boolean isMuted) {
private void setVolumeState(Boolean muted, int volume) {
if (!isAdded()) return;
if (isMuted) {
if (muted) {
volumeSeekBar.setProgress(0);
volumeTextView.setText(R.string.muted);
Resources.Theme theme = getActivity().getTheme();
TypedArray styledAttributes = theme.obtainStyledAttributes(new int[] {
R.attr.colorAccent});
volumeMuteButton.setColorFilter(
styledAttributes.getColor(0,
getActivity().getResources().getColor(R.color.accent_default)));
getActivity().getResources().getColor(R.color.accent_default)));
styledAttributes.recycle();
} else {
volumeSeekBar.setProgress(volume);
volumeTextView.setText(String.valueOf(volume));
volumeMuteButton.clearColorFilter();
}
}
private SeekBar.OnSeekBarChangeListener volumeSeekbarChangeListener = new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
if (fromUser) {
volumeTextView.setText(String.valueOf(progress));
}
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
new Application.SetVolume(seekBar.getProgress())
.execute(hostManager.getConnection(), defaultIntActionCallback, callbackHandler);
}
};
/**
* Seekbar change listener. Sends seek commands to XBMC based on the seekbar position
*/

View File

@ -179,14 +179,7 @@
android:orientation="horizontal"
style="@style/ButtonBar"
android:background="?attr/contentBackgroundColor">
<ImageButton
android:id="@+id/volume_down"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="match_parent"
style="@style/Widget.Button.Borderless"
android:src="?attr/iconVolumeDown"
android:contentDescription="@string/volume_down"/>
<ImageButton
android:id="@+id/volume_mute"
android:layout_width="0dp"
@ -195,14 +188,26 @@
style="@style/Widget.Button.Borderless"
android:src="?attr/iconVolumeMute"
android:contentDescription="@string/volume_mute"/>
<ImageButton
android:id="@+id/volume_up"
<LinearLayout
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="match_parent"
style="@style/Widget.Button.Borderless"
android:src="?attr/iconVolumeUp"
android:contentDescription="@string/volume_up"/>
android:layout_weight="2"
android:orientation="vertical">
<TextView
android:id="@+id/volume_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal|top"
style="@style/TextAppearance.Media.SmallDetails"
android:textSize="10sp"/>
<SeekBar
android:id="@+id/volume_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="@style/Widget.Button.Borderless"
android:layout_gravity="bottom"/>
</LinearLayout>
<ImageButton
android:id="@+id/repeat"
android:layout_width="0dp"

View File

@ -393,5 +393,6 @@
<string name="by_album">By album</string>
<string name="by_artist">By artist</string>
<string name="by_artist_and_year">By artist and year</string>
<string name="muted">muted</string>
</resources>