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:
parent
d2b5449e98
commit
c5848ce648
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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; }
|
||||
|
||||
|
|
|
@ -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; }
|
||||
}
|
||||
}
|
|
@ -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
|
||||
*/
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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>
|
||||
|
|
Loading…
Reference in New Issue