Tweak connection threads
This PR fixes some issues with connections and threading. Specifically, the change in #618 introduced threading in `HostConnection`, which had some issues. To fix them, the following changes were made: - A specific TCP listener thread is used and manually started, instead of using one of the threads in the pool. The TCP listener thread is a long lived thread that should always be running (as long as the connection is through TCP), blocked listening on the TCP socket, so it shouldn't be managed in a pool, where, theoretically, it can be paused and reused. - Changed the number of threads to 5. We shouldn't need more than this, otherwise we can overwhelm some Kodi hardware. - Had to sprinkle some `synchronized` to avoid race conditions. For instance, through a TCP connection, as soon as Kore is opened (on the remote screen) it will call at least `GetActivePlayers`, `GetProperties`, `Ping`. If the TCP socket isn't set up yet, each of these calls would create a socket (and a TCP listener thread), so we would open 3 or more sockets when we should just open 1. A `synchronized` in `executeThroughTcp` prevents this. The others prevent similar issues. Aditionally: - Tweaked the playlist fetching code, so that it happens exclusively in `HostConnectionObserver` and when PlaylistFragment is notified of changes, it already gets the playlist data. This somewhat simplifies the code, and makes it more consistent with the Player Observer code; - Change `EventServerConnection` to accept a Handler on which to post the result of the connection, so that the caller can control on which thread the result is called; - Calls to the various `RegisterObserver` loose the reply immediately parameter, as it was always true.
This commit is contained in:
parent
4ac37f3f02
commit
7dfd982643
|
@ -81,13 +81,17 @@ public class EventServerConnection {
|
|||
/**
|
||||
* Constructor. Starts the thread that keeps the connection alive. Make sure to call quit() when done.
|
||||
* @param hostInfo Host to connect to
|
||||
* @param callback Callback to call with the connection result
|
||||
* @param callbackHandler Handler on which to call the callback
|
||||
*/
|
||||
public EventServerConnection(final HostInfo hostInfo, final EventServerConnectionCallback callback) {
|
||||
public EventServerConnection(final HostInfo hostInfo,
|
||||
final EventServerConnectionCallback callback,
|
||||
final Handler callbackHandler) {
|
||||
this.hostInfo = hostInfo;
|
||||
|
||||
LogUtils.LOGD(TAG, "Starting EventServer Thread");
|
||||
// Handler thread that will keep pinging and send the requests to Kodi
|
||||
handlerThread = new HandlerThread("EventServerConnection", Process.THREAD_PRIORITY_DEFAULT);
|
||||
handlerThread = new HandlerThread("EventServerConnection", Process.THREAD_PRIORITY_BACKGROUND);
|
||||
handlerThread.start();
|
||||
|
||||
// Get the HandlerThread's Looper and use it for our Handler
|
||||
|
@ -103,12 +107,22 @@ public class EventServerConnection {
|
|||
LogUtils.LOGD(TAG, "Got an UnknownHostException, disabling EventServer");
|
||||
hostInetAddress = null;
|
||||
}
|
||||
// Call the callback on the caller's thread
|
||||
callbackHandler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
callback.OnConnectResult(hostInetAddress != null);
|
||||
}
|
||||
});
|
||||
|
||||
if (hostInetAddress != null) {
|
||||
// Start pinging
|
||||
commHandler.postDelayed(pingRunnable, PING_INTERVAL);
|
||||
} else {
|
||||
quitHandlerThread(handlerThread);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -57,17 +57,22 @@ public class HostConnectionObserver
|
|||
|
||||
public interface PlaylistEventsObserver {
|
||||
/**
|
||||
* Notifies that a playlist has been cleared
|
||||
* @param playlistId of playlist that has been cleared
|
||||
*/
|
||||
void playlistOnClear(int playlistId);
|
||||
|
||||
void playlistChanged(int playlistId);
|
||||
|
||||
/**
|
||||
* @param playlists the available playlists on the server
|
||||
* Notifies about the available playlists on Kodi
|
||||
* @param playlists Available playlists
|
||||
*/
|
||||
void playlistsAvailable(ArrayList<GetPlaylist.GetPlaylistResult> playlists);
|
||||
|
||||
/**
|
||||
* Notifies that an error occured when fetching playlists
|
||||
* @param errorCode Error code
|
||||
* @param description Error description
|
||||
*/
|
||||
void playlistOnError(int errorCode, String description);
|
||||
}
|
||||
|
||||
|
@ -164,8 +169,11 @@ public class HostConnectionObserver
|
|||
private List<ApplicationEventsObserver> applicationEventsObservers = new ArrayList<>();
|
||||
private List<PlaylistEventsObserver> playlistEventsObservers = new ArrayList<>();
|
||||
|
||||
// This controls the frequency with wich the playlist is checked.
|
||||
// It's checked everytime it reaches 0, being reset afterwards
|
||||
private int checkPlaylistFrequencyCounter = 0;
|
||||
|
||||
// Associate the Handler with the UI thread
|
||||
int checkPlaylistCounter = 0;
|
||||
private Handler checkerHandler = new Handler(Looper.getMainLooper());
|
||||
private Runnable httpCheckerRunnable = new Runnable() {
|
||||
@Override
|
||||
|
@ -183,11 +191,15 @@ public class HostConnectionObserver
|
|||
if (!applicationEventsObservers.isEmpty())
|
||||
getApplicationProperties();
|
||||
|
||||
if (!playlistEventsObservers.isEmpty() && checkPlaylistCounter > 1) {
|
||||
if (!playlistEventsObservers.isEmpty()) {
|
||||
if (checkPlaylistFrequencyCounter <= 0) {
|
||||
// Check playlist and reset the frequency counter
|
||||
checkPlaylist();
|
||||
checkPlaylistCounter = 0;
|
||||
checkPlaylistFrequencyCounter = 1;
|
||||
} else {
|
||||
checkPlaylistFrequencyCounter--;
|
||||
}
|
||||
}
|
||||
checkPlaylistCounter++;
|
||||
|
||||
checkerHandler.postDelayed(this, HTTP_NOTIFICATION_CHECK_INTERVAL);
|
||||
}
|
||||
|
@ -234,26 +246,18 @@ public class HostConnectionObserver
|
|||
}
|
||||
};
|
||||
|
||||
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;
|
||||
private static class HostState {
|
||||
int lastCallResult = PlayerEventsObserver.PLAYER_NO_RESULT;
|
||||
PlayerType.GetActivePlayersReturnType lastGetActivePlayerResult = null;
|
||||
PlayerType.PropertyValue lastGetPropertiesResult = null;
|
||||
ListType.ItemsAll lastGetItemResult = null;
|
||||
boolean volumeMuted = false;
|
||||
int volumeLevel = -1; // -1 indicates no volumeLevel known
|
||||
int lastErrorCode;
|
||||
String lastErrorDescription;
|
||||
ArrayList<GetPlaylist.GetPlaylistResult> lastGetPlaylistResults = null;
|
||||
}
|
||||
|
||||
public boolean isVolumeMuted() {
|
||||
return volumeMuted;
|
||||
}
|
||||
}
|
||||
|
||||
public HostState hostState;
|
||||
private HostState hostState;
|
||||
|
||||
public HostConnectionObserver(HostConnection connection) {
|
||||
this.hostState = new HostState();
|
||||
|
@ -264,14 +268,15 @@ public class HostConnectionObserver
|
|||
* Registers a new observer that will be notified about player events
|
||||
* @param observer Observer
|
||||
*/
|
||||
public void registerPlayerObserver(PlayerEventsObserver observer, boolean replyImmediately) {
|
||||
public void registerPlayerObserver(PlayerEventsObserver observer) {
|
||||
if (this.connection == null)
|
||||
return;
|
||||
|
||||
if (!playerEventsObservers.contains(observer))
|
||||
playerEventsObservers.add(observer);
|
||||
|
||||
if (replyImmediately) replyWithLastResult(observer);
|
||||
// Reply immediatelly
|
||||
replyWithLastResult(observer);
|
||||
|
||||
if (playerEventsObservers.size() == 1) {
|
||||
// If this is the first observer, start checking through HTTP or register us
|
||||
|
@ -297,8 +302,7 @@ public class HostConnectionObserver
|
|||
" observers.");
|
||||
|
||||
if (playerEventsObservers.isEmpty()) {
|
||||
// No more observers, so unregister us from the host connection, or stop
|
||||
// the http checker thread
|
||||
// No more observers. If through TCP unregister us from the host connection
|
||||
if (connection.getProtocol() == HostConnection.PROTOCOL_TCP) {
|
||||
connection.unregisterPlayerNotificationsObserver(this);
|
||||
connection.unregisterSystemNotificationsObserver(this);
|
||||
|
@ -311,22 +315,16 @@ public class HostConnectionObserver
|
|||
/**
|
||||
* Registers a new observer that will be notified about application events
|
||||
* @param observer Observer
|
||||
* @param replyImmediately Wether to immediatlely issue a reply with the current status
|
||||
*/
|
||||
public void registerApplicationObserver(ApplicationEventsObserver observer, boolean replyImmediately) {
|
||||
public void registerApplicationObserver(ApplicationEventsObserver observer) {
|
||||
if (this.connection == null)
|
||||
return;
|
||||
|
||||
if (!applicationEventsObservers.contains(observer))
|
||||
applicationEventsObservers.add(observer);
|
||||
|
||||
if (replyImmediately) {
|
||||
if( hostState.volumeLevel == -1 ) {
|
||||
getApplicationProperties();
|
||||
} else {
|
||||
observer.applicationOnVolumeChanged(hostState.volumeLevel, hostState.volumeMuted);
|
||||
}
|
||||
}
|
||||
// Reply immediatelly
|
||||
replyWithLastResult(observer);
|
||||
|
||||
if (applicationEventsObservers.size() == 1) {
|
||||
// If this is the first observer, start checking through HTTP or register us
|
||||
|
@ -350,8 +348,7 @@ public class HostConnectionObserver
|
|||
" observers.");
|
||||
|
||||
if (applicationEventsObservers.isEmpty()) {
|
||||
// No more observers, so unregister us from the host connection, or stop
|
||||
// the http checker thread
|
||||
// No more observers. If through TCP unregister us from the host connection
|
||||
if (connection.getProtocol() == HostConnection.PROTOCOL_TCP) {
|
||||
connection.unregisterApplicationNotificationsObserver(this);
|
||||
}
|
||||
|
@ -361,9 +358,8 @@ public class HostConnectionObserver
|
|||
/**
|
||||
* Registers a new observer that will be notified about playlist events
|
||||
* @param observer Observer
|
||||
* @param replyImmediately Whether to immediately issue a request if there are playlists available
|
||||
*/
|
||||
public void registerPlaylistObserver(PlaylistEventsObserver observer, boolean replyImmediately) {
|
||||
public void registerPlaylistObserver(PlaylistEventsObserver observer) {
|
||||
if (this.connection == null)
|
||||
return;
|
||||
|
||||
|
@ -371,19 +367,18 @@ public class HostConnectionObserver
|
|||
playlistEventsObservers.add(observer);
|
||||
}
|
||||
|
||||
// Reply immediatelly
|
||||
replyWithLastResult(observer);
|
||||
|
||||
if (playlistEventsObservers.size() == 1) {
|
||||
if (connection.getProtocol() == HostConnection.PROTOCOL_TCP) {
|
||||
connection.registerPlaylistNotificationsObserver(this, checkerHandler);
|
||||
}
|
||||
|
||||
startCheckerHandler();
|
||||
}
|
||||
|
||||
if (replyImmediately)
|
||||
checkPlaylist();
|
||||
}
|
||||
|
||||
public void unregisterPlaylistObserver(PlayerEventsObserver observer) {
|
||||
public void unregisterPlaylistObserver(PlaylistEventsObserver observer) {
|
||||
playlistEventsObservers.remove(observer);
|
||||
|
||||
LogUtils.LOGD(TAG, "Unregistering playlist observer " + observer.getClass().getSimpleName() +
|
||||
|
@ -391,11 +386,11 @@ public class HostConnectionObserver
|
|||
" observers.");
|
||||
|
||||
if (playlistEventsObservers.isEmpty()) {
|
||||
// No more observers, so unregister us from the host connection, or stop
|
||||
// the http checker thread
|
||||
// No more observers. If through TCP unregister us from the host connection
|
||||
if (connection.getProtocol() == HostConnection.PROTOCOL_TCP) {
|
||||
connection.unregisterPlaylistNotificationsObserver(this);
|
||||
}
|
||||
hostState.lastGetPlaylistResults = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -407,6 +402,8 @@ public class HostConnectionObserver
|
|||
observer.observerOnStopObserving();
|
||||
|
||||
playerEventsObservers.clear();
|
||||
playlistEventsObservers.clear();
|
||||
applicationEventsObservers.clear();
|
||||
|
||||
if (connection.getProtocol() == HostConnection.PROTOCOL_TCP) {
|
||||
connection.unregisterPlayerNotificationsObserver(this);
|
||||
|
@ -416,7 +413,7 @@ public class HostConnectionObserver
|
|||
connection.unregisterPlaylistNotificationsObserver(this);
|
||||
checkerHandler.removeCallbacks(tcpCheckerRunnable);
|
||||
}
|
||||
hostState.lastCallResult = PlayerEventsObserver.PLAYER_NO_RESULT;
|
||||
hostState = new HostState();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -522,6 +519,11 @@ public class HostConnectionObserver
|
|||
|
||||
@Override
|
||||
public void onPlaylistCleared(Playlist.OnClear notification) {
|
||||
if (hostState.lastGetPlaylistResults != null)
|
||||
hostState.lastGetPlaylistResults.clear();
|
||||
else
|
||||
hostState.lastGetPlaylistResults = new ArrayList<>();
|
||||
|
||||
for (PlaylistEventsObserver observer : playlistEventsObservers) {
|
||||
observer.playlistOnClear(notification.playlistId);
|
||||
}
|
||||
|
@ -529,16 +531,12 @@ public class HostConnectionObserver
|
|||
|
||||
@Override
|
||||
public void onPlaylistItemAdded(Playlist.OnAdd notification) {
|
||||
for (PlaylistEventsObserver observer : playlistEventsObservers) {
|
||||
observer.playlistChanged(notification.playlistId);
|
||||
}
|
||||
checkPlaylist();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPlaylistItemRemoved(Playlist.OnRemove notification) {
|
||||
for (PlaylistEventsObserver observer : playlistEventsObservers) {
|
||||
observer.playlistChanged(notification.playlistId);
|
||||
}
|
||||
checkPlaylist();
|
||||
}
|
||||
|
||||
private void startCheckerHandler() {
|
||||
|
@ -577,7 +575,6 @@ public class HostConnectionObserver
|
|||
}, checkerHandler);
|
||||
}
|
||||
|
||||
private ArrayList<GetPlaylist.GetPlaylistResult> prevGetPlaylistResults = new ArrayList<>();
|
||||
private boolean isCheckingPlaylist = false;
|
||||
private void checkPlaylist() {
|
||||
if (isCheckingPlaylist)
|
||||
|
@ -588,10 +585,12 @@ public class HostConnectionObserver
|
|||
connection.execute(new GetPlaylist(connection), new ApiCallback<ArrayList<GetPlaylist.GetPlaylistResult>>() {
|
||||
@Override
|
||||
public void onSuccess(ArrayList<GetPlaylist.GetPlaylistResult> result) {
|
||||
LogUtils.LOGD(TAG, "Checked playlist, got results: " + result.size());
|
||||
isCheckingPlaylist = false;
|
||||
|
||||
if (result.isEmpty()) {
|
||||
callPlaylistsOnClear(prevGetPlaylistResults);
|
||||
callPlaylistsOnClear(hostState.lastGetPlaylistResults);
|
||||
hostState.lastGetPlaylistResults = result;
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -599,19 +598,19 @@ public class HostConnectionObserver
|
|||
observer.playlistsAvailable(result);
|
||||
}
|
||||
|
||||
// Handle onClear for HTTP only connections
|
||||
// Handle cleared playlists
|
||||
if (hostState.lastGetPlaylistResults != null) {
|
||||
for (GetPlaylist.GetPlaylistResult getPlaylistResult : result) {
|
||||
for (int i = 0; i < prevGetPlaylistResults.size(); i++) {
|
||||
if (getPlaylistResult.id == prevGetPlaylistResults.get(i).id) {
|
||||
prevGetPlaylistResults.remove(i);
|
||||
for (int i = 0; i < hostState.lastGetPlaylistResults.size(); i++) {
|
||||
if (getPlaylistResult.id == hostState.lastGetPlaylistResults.get(i).id) {
|
||||
hostState.lastGetPlaylistResults.remove(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
callPlaylistsOnClear(prevGetPlaylistResults);
|
||||
|
||||
prevGetPlaylistResults = result;
|
||||
callPlaylistsOnClear(hostState.lastGetPlaylistResults);
|
||||
}
|
||||
hostState.lastGetPlaylistResults = result;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -622,10 +621,11 @@ public class HostConnectionObserver
|
|||
observer.playlistOnError(errorCode, description);
|
||||
}
|
||||
}
|
||||
}, new Handler());
|
||||
}, checkerHandler);
|
||||
}
|
||||
|
||||
private void callPlaylistsOnClear(ArrayList<GetPlaylist.GetPlaylistResult> clearedPlaylists) {
|
||||
if (clearedPlaylists == null) return;
|
||||
for (GetPlaylist.GetPlaylistResult getPlaylistResult : clearedPlaylists) {
|
||||
for (PlaylistEventsObserver observer : playlistEventsObservers) {
|
||||
observer.playlistOnClear(getPlaylistResult.id);
|
||||
|
@ -935,11 +935,11 @@ public class HostConnectionObserver
|
|||
}
|
||||
|
||||
/**
|
||||
* Replies to the observer with the last result we got.
|
||||
* Replies to the player observer with the last result we got.
|
||||
* If we have no result, nothing will be called on the observer interface.
|
||||
* @param observer Observer to call with last result
|
||||
* @param observer Player observer to call with last result
|
||||
*/
|
||||
public void replyWithLastResult(PlayerEventsObserver observer) {
|
||||
private void replyWithLastResult(PlayerEventsObserver observer) {
|
||||
switch (hostState.lastCallResult) {
|
||||
case PlayerEventsObserver.PLAYER_CONNECTION_ERROR:
|
||||
notifyConnectionError(hostState.lastErrorCode, hostState.lastErrorDescription, observer);
|
||||
|
@ -957,16 +957,41 @@ public class HostConnectionObserver
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Replies to the application observer with the last result we got.
|
||||
* If we have no result, nothing will be called on the observer interface.
|
||||
* @param observer Application observer to call with last result
|
||||
*/
|
||||
private void replyWithLastResult(ApplicationEventsObserver observer) {
|
||||
if (hostState.volumeLevel == -1) {
|
||||
getApplicationProperties();
|
||||
} else {
|
||||
observer.applicationOnVolumeChanged(hostState.volumeLevel, hostState.volumeMuted);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Replies to the playlist observer with the last result we got.
|
||||
* If we have no result, nothing will be called on the observer interface.
|
||||
* @param observer Playlist observer to call with last result
|
||||
*/
|
||||
private void replyWithLastResult(PlaylistEventsObserver observer) {
|
||||
if (hostState.lastGetPlaylistResults != null)
|
||||
observer.playlistsAvailable(hostState.lastGetPlaylistResults);
|
||||
else
|
||||
checkPlaylist();
|
||||
}
|
||||
|
||||
/**
|
||||
* Forces a refresh of the current cached results
|
||||
*/
|
||||
public void forceRefreshResults() {
|
||||
LogUtils.LOGD(TAG, "Forcing a refresh");
|
||||
public void refreshWhatsPlaying() {
|
||||
LogUtils.LOGD(TAG, "Forcing a refresh of what's playing");
|
||||
forceReply = true;
|
||||
checkWhatsPlaying();
|
||||
}
|
||||
|
||||
public HostState getHostState() {
|
||||
return hostState;
|
||||
public void refreshPlaylists() {
|
||||
LogUtils.LOGD(TAG, "Forcing a refresh of playlists");
|
||||
checkPlaylist();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -197,12 +197,16 @@ public class HostManager {
|
|||
* @return Active host connection
|
||||
*/
|
||||
public HostConnection getConnection() {
|
||||
if (currentHostConnection == null) {
|
||||
synchronized (this) {
|
||||
if (currentHostConnection == null) {
|
||||
currentHostInfo = getHostInfo();
|
||||
|
||||
if (currentHostInfo != null) {
|
||||
currentHostConnection = new HostConnection(currentHostInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return currentHostConnection;
|
||||
}
|
||||
|
|
|
@ -22,6 +22,7 @@ import org.xbmc.kore.jsonrpc.HostConnection;
|
|||
import org.xbmc.kore.jsonrpc.method.Playlist;
|
||||
import org.xbmc.kore.jsonrpc.type.ListType;
|
||||
import org.xbmc.kore.jsonrpc.type.PlaylistType;
|
||||
import org.xbmc.kore.utils.LogUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
|
@ -34,6 +35,7 @@ import java.util.concurrent.ExecutionException;
|
|||
* available.
|
||||
*/
|
||||
public class GetPlaylist implements Callable<ArrayList<GetPlaylist.GetPlaylistResult>> {
|
||||
private static final String TAG = LogUtils.makeLogTag(GetPlaylist.class);
|
||||
|
||||
private final static String[] propertiesToGet = new String[] {
|
||||
ListType.FieldsAll.ART,
|
||||
|
@ -62,7 +64,7 @@ public class GetPlaylist implements Callable<ArrayList<GetPlaylist.GetPlaylistRe
|
|||
|
||||
/**
|
||||
* Use this to get the first non-empty playlist
|
||||
* @param hostConnection
|
||||
* @param hostConnection {@link HostConnection} to use
|
||||
*/
|
||||
public GetPlaylist(HostConnection hostConnection) {
|
||||
this.hostConnection = hostConnection;
|
||||
|
@ -70,7 +72,7 @@ public class GetPlaylist implements Callable<ArrayList<GetPlaylist.GetPlaylistRe
|
|||
|
||||
/**
|
||||
* Use this to get a playlist for a specific playlist type
|
||||
* @param hostConnection
|
||||
* @param hostConnection {@link HostConnection} to use
|
||||
* @param playlistType should be one of the types from {@link org.xbmc.kore.jsonrpc.type.PlaylistType.GetPlaylistsReturnType}.
|
||||
* If null the first non-empty playlist is returned.
|
||||
*/
|
||||
|
@ -81,8 +83,8 @@ public class GetPlaylist implements Callable<ArrayList<GetPlaylist.GetPlaylistRe
|
|||
|
||||
/**
|
||||
* Use this to get a playlist for a specific playlist id
|
||||
* @param hostConnection
|
||||
* @param playlistId
|
||||
* @param hostConnection {@link HostConnection} to use
|
||||
* @param playlistId Kodi's playlist id
|
||||
*/
|
||||
public GetPlaylist(HostConnection hostConnection, int playlistId) {
|
||||
this.hostConnection = hostConnection;
|
||||
|
|
|
@ -131,7 +131,12 @@ public class HostConnection {
|
|||
private Socket socket = null;
|
||||
|
||||
/**
|
||||
* {@link java.util.HashMap} that will hold the {@link MethodCallInfo} with the information
|
||||
* Listener thread that will be listening on the TCP socket
|
||||
*/
|
||||
private Thread tcpListenerThread = null;
|
||||
|
||||
/**
|
||||
* {@link HashMap} that will hold the {@link MethodCallInfo} with the information
|
||||
* necessary to respond to clients (TCP only)
|
||||
*/
|
||||
private final HashMap<String, MethodCallInfo<?>> clientCallbacks = new HashMap<>();
|
||||
|
@ -209,7 +214,7 @@ public class HostConnection {
|
|||
// Start with the default host protocol
|
||||
this.protocol = hostInfo.getProtocol();
|
||||
// Create a single threaded executor
|
||||
this.executorService = Executors.newFixedThreadPool(10);
|
||||
this.executorService = Executors.newFixedThreadPool(5);
|
||||
// Set timeout
|
||||
this.connectTimeout = connectTimeout;
|
||||
}
|
||||
|
@ -226,7 +231,7 @@ public class HostConnection {
|
|||
* Overrides the protocol for this connection
|
||||
* @param protocol {@link #PROTOCOL_HTTP} or {@link #PROTOCOL_TCP}
|
||||
*/
|
||||
public void setProtocol(int protocol) {
|
||||
public synchronized void setProtocol(int protocol) {
|
||||
if (!isValidProtocol(protocol)) {
|
||||
throw new IllegalArgumentException("Invalid protocol specified.");
|
||||
}
|
||||
|
@ -393,8 +398,7 @@ public class HostConnection {
|
|||
* @param method The remote method to invoke
|
||||
* @param <T> The type of the return value of the method
|
||||
* @return the future result of the method call. API errors will be wrapped in
|
||||
* an {@link java.util.concurrent.ExecutionException ExecutionException} like
|
||||
* regular futures.
|
||||
* an {@link ExecutionException} like regular futures.
|
||||
*/
|
||||
public <T> Future<T> execute(ApiMethod<T> method) {
|
||||
final ApiFuture<T> future = new ApiFuture<>();
|
||||
|
@ -418,9 +422,9 @@ public class HostConnection {
|
|||
* @param callable executed using an {@link ExecutorService}
|
||||
* @param apiCallback used to return the result of the callable
|
||||
* @param handler used to execute the {@link ApiCallback} methods
|
||||
* @param <T>
|
||||
* @param <T> The callable return type
|
||||
*/
|
||||
public <T> void execute(Callable<T> callable, final ApiCallback<T> apiCallback, final Handler handler) {
|
||||
public <T> void execute(final Callable<T> callable, final ApiCallback<T> apiCallback, final Handler handler) {
|
||||
final Future<T> future = executorService.submit(callable);
|
||||
executorService.execute(new Runnable() {
|
||||
@Override
|
||||
|
@ -559,7 +563,7 @@ public class HostConnection {
|
|||
/**
|
||||
* Initializes this class OkHttpClient
|
||||
*/
|
||||
public OkHttpClient getOkHttpClient() {
|
||||
public synchronized OkHttpClient getOkHttpClient() {
|
||||
if (httpClient == null) {
|
||||
httpClient = new OkHttpClient();
|
||||
httpClient.setConnectTimeout(connectTimeout, TimeUnit.MILLISECONDS);
|
||||
|
@ -693,7 +697,7 @@ public class HostConnection {
|
|||
private <T> void executeThroughTcp(final ApiMethod<T> method) {
|
||||
String methodId = String.valueOf(method.getId());
|
||||
try {
|
||||
// TODO: Validate if this shouldn't be enclosed by a synchronized.
|
||||
synchronized (this) {
|
||||
if (socket == null) {
|
||||
// Open connection to the server and setup reader thread
|
||||
socket = openTcpConnection(hostInfo);
|
||||
|
@ -702,6 +706,7 @@ public class HostConnection {
|
|||
|
||||
// Write request
|
||||
sendTcpRequest(socket, method.toJsonString());
|
||||
}
|
||||
} catch (final ApiException e) {
|
||||
callErrorCallback(methodId, e);
|
||||
}
|
||||
|
@ -712,7 +717,7 @@ public class HostConnection {
|
|||
* This method calls connect() so that any errors are cathced
|
||||
* @param hostInfo Host info
|
||||
* @return Connection set up
|
||||
* @throws ApiException
|
||||
* @throws ApiException Exception if open is unsucessful
|
||||
*/
|
||||
private Socket openTcpConnection(HostInfo hostInfo) throws ApiException {
|
||||
try {
|
||||
|
@ -731,7 +736,6 @@ public class HostConnection {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Send a TCP request
|
||||
* @param socket Socket to write to
|
||||
|
@ -752,12 +756,12 @@ public class HostConnection {
|
|||
}
|
||||
|
||||
private void startListenerThread(final Socket socket) {
|
||||
executorService.execute(new Runnable() {
|
||||
tcpListenerThread = new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
|
||||
try {
|
||||
LogUtils.LOGD(TAG, "Starting socket listener thread...");
|
||||
LogUtils.LOGD(TAG, "Starting TCP socket listener thread from thread: " + Thread.currentThread().getName());
|
||||
// We're going to read from the socket. This will be a blocking call and
|
||||
// it will keep on going until disconnect() is called on this object.
|
||||
// Note: Mind the objects used here: we use createParser because it doesn't
|
||||
|
@ -774,11 +778,13 @@ public class HostConnection {
|
|||
callErrorCallback(null, new ApiException(ApiException.INVALID_JSON_RESPONSE_FROM_HOST, e));
|
||||
} catch (IOException e) {
|
||||
LogUtils.LOGW(TAG, "Error reading from socket.", e);
|
||||
disconnect();
|
||||
callErrorCallback(null, new ApiException(ApiException.IO_EXCEPTION_WHILE_READING_RESPONSE, e));
|
||||
} finally {
|
||||
disconnect();
|
||||
}
|
||||
}
|
||||
});
|
||||
tcpListenerThread.start();
|
||||
}
|
||||
|
||||
private boolean shouldIgnoreTcpResponse(ObjectNode jsonResponse) {
|
||||
|
@ -1113,7 +1119,7 @@ public class HostConnection {
|
|||
* Cleans up used resources.
|
||||
* This method should always be called if the protocol used is TCP, so we can shutdown gracefully
|
||||
*/
|
||||
public void disconnect() {
|
||||
public synchronized void disconnect() {
|
||||
if (protocol == PROTOCOL_HTTP)
|
||||
return;
|
||||
|
||||
|
|
|
@ -81,13 +81,13 @@ public class ConnectionObserversManagerService extends Service
|
|||
|
||||
if (hostConnectionObserver == null) {
|
||||
hostConnectionObserver = connectionObserver;
|
||||
hostConnectionObserver.registerPlayerObserver(this, true);
|
||||
hostConnectionObserver.registerPlayerObserver(this);
|
||||
} else if (hostConnectionObserver != connectionObserver) {
|
||||
// There has been a change in hosts.
|
||||
// Unregister the previous one and register the current one
|
||||
hostConnectionObserver.unregisterPlayerObserver(this);
|
||||
hostConnectionObserver = connectionObserver;
|
||||
hostConnectionObserver.registerPlayerObserver(this, true);
|
||||
hostConnectionObserver.registerPlayerObserver(this);
|
||||
}
|
||||
|
||||
// If we get killed after returning from here, restart
|
||||
|
|
|
@ -447,10 +447,10 @@ public abstract class BaseMediaActivity extends BaseActivity
|
|||
if (hostConnectionObserver == null)
|
||||
return;
|
||||
|
||||
hostConnectionObserver.registerApplicationObserver(this, true);
|
||||
hostConnectionObserver.registerPlayerObserver(this, true);
|
||||
hostConnectionObserver.registerApplicationObserver(this);
|
||||
hostConnectionObserver.registerPlayerObserver(this);
|
||||
|
||||
hostConnectionObserver.forceRefreshResults();
|
||||
hostConnectionObserver.refreshWhatsPlaying();
|
||||
}
|
||||
|
||||
private void updateNowPlayingPanel(PlayerType.PropertyValue getPropertiesResult,
|
||||
|
|
|
@ -138,8 +138,8 @@ public class VolumeControllerDialogFragmentListener extends AppCompatDialogFragm
|
|||
return;
|
||||
}
|
||||
|
||||
hostConnectionObserver.registerApplicationObserver(this, true);
|
||||
hostConnectionObserver.forceRefreshResults();
|
||||
hostConnectionObserver.registerApplicationObserver(this);
|
||||
hostConnectionObserver.refreshWhatsPlaying();
|
||||
}
|
||||
|
||||
private void setListeners() {
|
||||
|
|
|
@ -232,8 +232,8 @@ public class NowPlayingFragment extends Fragment
|
|||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
hostConnectionObserver.registerPlayerObserver(this, true);
|
||||
hostConnectionObserver.registerApplicationObserver(this, true);
|
||||
hostConnectionObserver.registerPlayerObserver(this);
|
||||
hostConnectionObserver.registerApplicationObserver(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -331,7 +331,7 @@ public class NowPlayingFragment extends Fragment
|
|||
public void onSuccess(String result) {
|
||||
if (!isAdded()) return;
|
||||
// Force a refresh
|
||||
hostConnectionObserver.forceRefreshResults();
|
||||
hostConnectionObserver.refreshWhatsPlaying();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -346,7 +346,7 @@ public class NowPlayingFragment extends Fragment
|
|||
@Override
|
||||
public void onSuccess(String result) {
|
||||
if (!isAdded()) return;
|
||||
hostConnectionObserver.forceRefreshResults();
|
||||
hostConnectionObserver.refreshWhatsPlaying();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -182,8 +182,8 @@ public class PlaylistFragment extends Fragment
|
|||
public void onResume() {
|
||||
super.onResume();
|
||||
|
||||
hostConnectionObserver.registerPlayerObserver(this, true);
|
||||
hostConnectionObserver.registerPlaylistObserver(this, true);
|
||||
hostConnectionObserver.registerPlayerObserver(this);
|
||||
hostConnectionObserver.registerPlaylistObserver(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -246,7 +246,7 @@ public class PlaylistFragment extends Fragment
|
|||
public void onError(int errorCode, String description) {
|
||||
refreshingPlaylist = false;
|
||||
|
||||
displayErrorGettingPlaylistMessage(description);
|
||||
playerOnConnectionError(errorCode, description);
|
||||
}
|
||||
}, callbackHandler);
|
||||
}
|
||||
|
@ -375,16 +375,13 @@ public class PlaylistFragment extends Fragment
|
|||
displayPlaylist();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void playlistChanged(int playlistId) {
|
||||
refreshPlaylist(new GetPlaylist(hostManager.getConnection(), playlistId));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void playlistsAvailable(ArrayList<GetPlaylist.GetPlaylistResult> playlists) {
|
||||
updatePlaylists(playlists);
|
||||
|
||||
if ( playerState == PLAYER_STATE.PLAYING ) // if item is currently playing displaying is already handled by playerOnPlay callback
|
||||
if ((playerState == PLAYER_STATE.PLAYING) &&
|
||||
(hostManager.getConnection().getProtocol() == HostConnection.PROTOCOL_TCP))
|
||||
// if item is currently playing displaying is already handled by playerOnPlay callback
|
||||
return;
|
||||
|
||||
// BUG: When playing movies playlist stops, audio tab gets selected when it contains a playlist.
|
||||
|
@ -398,7 +395,7 @@ public class PlaylistFragment extends Fragment
|
|||
|
||||
@Override
|
||||
public void playlistOnError(int errorCode, String description) {
|
||||
displayErrorGettingPlaylistMessage(description);
|
||||
playerOnConnectionError(errorCode, description);
|
||||
}
|
||||
|
||||
private void updatePlaylists(ArrayList<GetPlaylist.GetPlaylistResult> playlists) {
|
||||
|
@ -488,16 +485,6 @@ public class PlaylistFragment extends Fragment
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays an error on the info panel
|
||||
* @param details Details message
|
||||
*/
|
||||
private void displayErrorGettingPlaylistMessage(String details) {
|
||||
switchToPanel(R.id.info_panel);
|
||||
infoTitle.setText(R.string.error_getting_playlist);
|
||||
infoMessage.setText(String.format(getString(R.string.error_message), details));
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays empty playlist
|
||||
*/
|
||||
|
|
|
@ -176,9 +176,10 @@ public class RemoteActivity extends BaseActivity
|
|||
public void onResume() {
|
||||
super.onResume();
|
||||
hostConnectionObserver = hostManager.getHostConnectionObserver();
|
||||
hostConnectionObserver.registerPlayerObserver(this, true);
|
||||
// Force a refresh, mainly to update the time elapsed on the fragments
|
||||
hostConnectionObserver.forceRefreshResults();
|
||||
hostConnectionObserver.registerPlayerObserver(this);
|
||||
// Force a refresh, specifically to update the time elapsed on the fragments
|
||||
hostConnectionObserver.refreshWhatsPlaying();
|
||||
hostConnectionObserver.refreshPlaylists();
|
||||
|
||||
// Check whether we should keep the remote activity above the lockscreen
|
||||
boolean keepAboveLockscreen = PreferenceManager
|
||||
|
|
|
@ -224,7 +224,7 @@ public class RemoteFragment extends Fragment
|
|||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
hostConnectionObserver.registerPlayerObserver(this, true);
|
||||
hostConnectionObserver.registerPlayerObserver(this);
|
||||
if (eventServerConnection == null)
|
||||
eventServerConnection = createEventServerConnection();
|
||||
}
|
||||
|
@ -265,7 +265,7 @@ public class RemoteFragment extends Fragment
|
|||
eventServerConnection = null;
|
||||
}
|
||||
}
|
||||
});
|
||||
}, callbackHandler);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
Loading…
Reference in New Issue