2015-01-14 12:12:47 +01:00
|
|
|
/*
|
|
|
|
* 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.
|
|
|
|
*/
|
2015-03-09 22:35:18 +01:00
|
|
|
package org.xbmc.kore.host;
|
2015-01-14 12:12:47 +01:00
|
|
|
|
|
|
|
import android.os.Handler;
|
2018-07-03 20:54:26 +02:00
|
|
|
import android.os.Looper;
|
2015-01-14 12:12:47 +01:00
|
|
|
|
2019-03-30 12:08:58 +01:00
|
|
|
import org.xbmc.kore.host.actions.GetPlaylist;
|
2015-03-09 22:35:18 +01:00
|
|
|
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;
|
2016-11-30 13:20:21 +01:00
|
|
|
import org.xbmc.kore.jsonrpc.notification.Application;
|
2015-03-09 22:35:18 +01:00
|
|
|
import org.xbmc.kore.jsonrpc.notification.Input;
|
2019-02-07 17:39:56 +01:00
|
|
|
import org.xbmc.kore.jsonrpc.notification.Player.NotificationsData;
|
2019-03-30 12:08:58 +01:00
|
|
|
import org.xbmc.kore.jsonrpc.notification.Playlist;
|
2015-03-09 22:35:18 +01:00
|
|
|
import org.xbmc.kore.jsonrpc.notification.System;
|
2016-11-30 13:20:21 +01:00
|
|
|
import org.xbmc.kore.jsonrpc.type.ApplicationType;
|
2015-03-09 22:35:18 +01:00
|
|
|
import org.xbmc.kore.jsonrpc.type.ListType;
|
|
|
|
import org.xbmc.kore.jsonrpc.type.PlayerType;
|
|
|
|
import org.xbmc.kore.utils.LogUtils;
|
2015-01-14 12:12:47 +01:00
|
|
|
|
|
|
|
import java.util.ArrayList;
|
|
|
|
import java.util.List;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Object that listens to a connection and notifies observers about changes in its state
|
|
|
|
* This class serves as an adpater to the {@link HostConnection.PlayerNotificationsObserver},
|
|
|
|
* to enable to get notifications not only through TCP but also through HTTP.
|
|
|
|
* Depending on the connection protocol this class registers itself as an observer for
|
|
|
|
* {@link HostConnection.PlayerNotificationsObserver} and forwards the notifications it gets,
|
|
|
|
* or, if through HTTP, starts a periodic polling of XBMC, and tries to discern when a change in
|
|
|
|
* the player has occurred, notifying the listeners
|
|
|
|
*
|
|
|
|
* NOTE: An object of this class should always be called from the same thread.
|
|
|
|
*/
|
|
|
|
public class HostConnectionObserver
|
|
|
|
implements HostConnection.PlayerNotificationsObserver,
|
2016-11-30 13:20:21 +01:00
|
|
|
HostConnection.SystemNotificationsObserver,
|
|
|
|
HostConnection.InputNotificationsObserver,
|
2019-03-30 12:08:58 +01:00
|
|
|
HostConnection.ApplicationNotificationsObserver,
|
|
|
|
HostConnection.PlaylistNotificationsObserver {
|
2015-01-14 12:12:47 +01:00
|
|
|
public static final String TAG = LogUtils.makeLogTag(HostConnectionObserver.class);
|
|
|
|
|
2019-03-30 12:08:58 +01:00
|
|
|
public interface PlaylistEventsObserver {
|
|
|
|
/**
|
|
|
|
* @param playlistId of playlist that has been cleared
|
|
|
|
*/
|
|
|
|
void playlistOnClear(int playlistId);
|
|
|
|
|
|
|
|
void playlistChanged(int playlistId);
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param playlists the available playlists on the server
|
|
|
|
*/
|
|
|
|
void playlistsAvailable(ArrayList<GetPlaylist.GetPlaylistResult> playlists);
|
|
|
|
|
|
|
|
void playlistOnError(int errorCode, String description);
|
|
|
|
}
|
|
|
|
|
2019-02-07 17:39:56 +01:00
|
|
|
/**
|
|
|
|
* Interface that an observer has to implement to receive playlist events
|
|
|
|
*/
|
2016-11-30 13:20:21 +01:00
|
|
|
public interface ApplicationEventsObserver {
|
|
|
|
/**
|
|
|
|
* Notifies the observer that volume has changed
|
2019-02-08 17:42:33 +01:00
|
|
|
* @param volume Volume level
|
|
|
|
* @param muted Is muted
|
2016-11-30 13:20:21 +01:00
|
|
|
*/
|
2019-02-08 17:42:33 +01:00
|
|
|
void applicationOnVolumeChanged(int volume, boolean muted);
|
2016-11-30 13:20:21 +01:00
|
|
|
}
|
|
|
|
|
2015-01-14 12:12:47 +01:00
|
|
|
/**
|
|
|
|
* Interface that an observer has to implement to receive player events
|
|
|
|
*/
|
|
|
|
public interface PlayerEventsObserver {
|
|
|
|
/**
|
|
|
|
* Constants for possible events. Useful to save the last event and compare with the
|
|
|
|
* current one to check for differences
|
|
|
|
*/
|
2019-02-08 17:42:33 +01:00
|
|
|
int PLAYER_NO_RESULT = 0,
|
2015-01-14 12:12:47 +01:00
|
|
|
PLAYER_CONNECTION_ERROR = 1,
|
|
|
|
PLAYER_IS_PLAYING = 2,
|
|
|
|
PLAYER_IS_PAUSED = 3,
|
|
|
|
PLAYER_IS_STOPPED = 4;
|
|
|
|
|
2019-02-08 17:42:33 +01:00
|
|
|
void playerOnPropertyChanged(NotificationsData notificationsData);
|
2017-07-13 20:10:49 +02:00
|
|
|
|
2015-01-14 12:12:47 +01:00
|
|
|
/**
|
|
|
|
* Notifies that something is playing
|
2015-03-09 22:35:18 +01:00
|
|
|
* @param getActivePlayerResult Active player obtained by a call to {@link org.xbmc.kore.jsonrpc.method.Player.GetActivePlayers}
|
|
|
|
* @param getPropertiesResult Properties obtained by a call to {@link org.xbmc.kore.jsonrpc.method.Player.GetProperties}
|
|
|
|
* @param getItemResult Currently playing item, obtained by a call to {@link org.xbmc.kore.jsonrpc.method.Player.GetItem}
|
2015-01-14 12:12:47 +01:00
|
|
|
*/
|
2019-02-08 17:42:33 +01:00
|
|
|
void playerOnPlay(PlayerType.GetActivePlayersReturnType getActivePlayerResult,
|
2019-03-30 12:08:58 +01:00
|
|
|
PlayerType.PropertyValue getPropertiesResult,
|
|
|
|
ListType.ItemsAll getItemResult);
|
2015-01-14 12:12:47 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Notifies that something is paused
|
2015-03-09 22:35:18 +01:00
|
|
|
* @param getActivePlayerResult Active player obtained by a call to {@link org.xbmc.kore.jsonrpc.method.Player.GetActivePlayers}
|
|
|
|
* @param getPropertiesResult Properties obtained by a call to {@link org.xbmc.kore.jsonrpc.method.Player.GetProperties}
|
|
|
|
* @param getItemResult Currently paused item, obtained by a call to {@link org.xbmc.kore.jsonrpc.method.Player.GetItem}
|
2015-01-14 12:12:47 +01:00
|
|
|
*/
|
2019-02-08 17:42:33 +01:00
|
|
|
void playerOnPause(PlayerType.GetActivePlayersReturnType getActivePlayerResult,
|
2019-03-30 12:08:58 +01:00
|
|
|
PlayerType.PropertyValue getPropertiesResult,
|
|
|
|
ListType.ItemsAll getItemResult);
|
2015-01-14 12:12:47 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Notifies that media is stopped/nothing is playing
|
|
|
|
*/
|
2019-02-08 17:42:33 +01:00
|
|
|
void playerOnStop();
|
2015-01-14 12:12:47 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Called when we get a connection error
|
2019-02-08 17:42:33 +01:00
|
|
|
* @param errorCode Code
|
|
|
|
* @param description Description
|
2015-01-14 12:12:47 +01:00
|
|
|
*/
|
2019-02-08 17:42:33 +01:00
|
|
|
void playerOnConnectionError(int errorCode, String description);
|
2015-01-14 12:12:47 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Notifies that we don't have a result yet
|
|
|
|
*/
|
2019-02-08 17:42:33 +01:00
|
|
|
void playerNoResultsYet();
|
2015-01-14 12:12:47 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Notifies that XBMC has quit/shutdown/sleep
|
|
|
|
*/
|
2019-02-08 17:42:33 +01:00
|
|
|
void systemOnQuit();
|
2015-01-14 12:12:47 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Notifies that XBMC has requested input
|
|
|
|
*/
|
2019-02-08 17:42:33 +01:00
|
|
|
void inputOnInputRequested(String title, String type, String value);
|
2015-02-15 20:11:32 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Notifies the observer that it this is stopping
|
|
|
|
*/
|
2019-02-08 17:42:33 +01:00
|
|
|
void observerOnStopObserving();
|
2015-01-14 12:12:47 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The connection on which to listen
|
|
|
|
*/
|
|
|
|
private HostConnection connection;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The list of observers
|
|
|
|
*/
|
2019-02-08 17:42:33 +01:00
|
|
|
private List<PlayerEventsObserver> playerEventsObservers = new ArrayList<>();
|
2016-11-30 13:20:21 +01:00
|
|
|
private List<ApplicationEventsObserver> applicationEventsObservers = new ArrayList<>();
|
2019-03-30 12:08:58 +01:00
|
|
|
private List<PlaylistEventsObserver> playlistEventsObservers = new ArrayList<>();
|
2015-01-14 12:12:47 +01:00
|
|
|
|
2018-07-03 20:54:26 +02:00
|
|
|
// Associate the Handler with the UI thread
|
2019-03-30 12:08:58 +01:00
|
|
|
int checkPlaylistCounter = 0;
|
2018-07-03 20:54:26 +02:00
|
|
|
private Handler checkerHandler = new Handler(Looper.getMainLooper());
|
2019-02-07 17:39:56 +01:00
|
|
|
private Runnable httpCheckerRunnable = new Runnable() {
|
2015-01-14 12:12:47 +01:00
|
|
|
@Override
|
|
|
|
public void run() {
|
2019-02-08 17:42:33 +01:00
|
|
|
final int HTTP_NOTIFICATION_CHECK_INTERVAL = 2000;
|
|
|
|
// If no one is listening to this, just exit
|
2019-03-30 12:08:58 +01:00
|
|
|
if (playerEventsObservers.isEmpty()
|
|
|
|
&& applicationEventsObservers.isEmpty()
|
|
|
|
&& playlistEventsObservers.isEmpty())
|
2019-02-08 17:42:33 +01:00
|
|
|
return;
|
|
|
|
|
|
|
|
if (!playerEventsObservers.isEmpty())
|
2019-02-07 17:39:56 +01:00
|
|
|
checkWhatsPlaying();
|
2015-01-14 12:12:47 +01:00
|
|
|
|
2019-02-08 17:42:33 +01:00
|
|
|
if (!applicationEventsObservers.isEmpty())
|
2019-02-07 17:39:56 +01:00
|
|
|
getApplicationProperties();
|
2015-01-14 12:12:47 +01:00
|
|
|
|
2019-03-30 12:08:58 +01:00
|
|
|
if (!playlistEventsObservers.isEmpty() && checkPlaylistCounter > 1) {
|
|
|
|
checkPlaylist();
|
|
|
|
checkPlaylistCounter = 0;
|
|
|
|
}
|
|
|
|
checkPlaylistCounter++;
|
|
|
|
|
2019-02-08 17:42:33 +01:00
|
|
|
checkerHandler.postDelayed(this, HTTP_NOTIFICATION_CHECK_INTERVAL);
|
2015-01-14 12:12:47 +01:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2019-02-07 17:39:56 +01:00
|
|
|
private Runnable tcpCheckerRunnable = new Runnable() {
|
2016-11-30 13:20:21 +01:00
|
|
|
@Override
|
|
|
|
public void run() {
|
|
|
|
// If no one is listening to this, just exit
|
2019-03-30 12:08:58 +01:00
|
|
|
if (playerEventsObservers.isEmpty() && applicationEventsObservers.isEmpty() &&
|
|
|
|
playlistEventsObservers.isEmpty())
|
2019-02-07 17:39:56 +01:00
|
|
|
return;
|
2016-11-30 13:20:21 +01:00
|
|
|
|
2015-01-14 12:12:47 +01:00
|
|
|
final int PING_AFTER_ERROR_CHECK_INTERVAL = 2000,
|
|
|
|
PING_AFTER_SUCCESS_CHECK_INTERVAL = 10000;
|
|
|
|
JSONRPC.Ping ping = new JSONRPC.Ping();
|
|
|
|
ping.execute(connection, new ApiCallback<String>() {
|
|
|
|
@Override
|
2015-03-16 19:33:40 +01:00
|
|
|
public void onSuccess(String result) {
|
2017-07-13 20:10:49 +02:00
|
|
|
// Ok, we've got a ping, if there are playerEventsObservers and
|
|
|
|
// we were in a error or uninitialized state, update
|
2019-02-08 17:42:33 +01:00
|
|
|
if ((!playerEventsObservers.isEmpty()) &&
|
2017-07-13 20:10:49 +02:00
|
|
|
((hostState.lastCallResult == PlayerEventsObserver.PLAYER_NO_RESULT) ||
|
2019-03-30 12:08:58 +01:00
|
|
|
(hostState.lastCallResult == PlayerEventsObserver.PLAYER_CONNECTION_ERROR))) {
|
2019-02-08 17:42:33 +01:00
|
|
|
LogUtils.LOGD(TAG, "Checking what's playing because we don't have info about it");
|
2015-01-14 12:12:47 +01:00
|
|
|
checkWhatsPlaying();
|
2019-02-07 17:39:56 +01:00
|
|
|
}
|
|
|
|
|
2019-03-30 12:08:58 +01:00
|
|
|
if ((!playlistEventsObservers.isEmpty()) &&
|
|
|
|
(hostState.lastCallResult == PlayerEventsObserver.PLAYER_CONNECTION_ERROR)) {
|
|
|
|
checkPlaylist();
|
|
|
|
}
|
|
|
|
|
2019-02-08 17:42:33 +01:00
|
|
|
checkerHandler.postDelayed(tcpCheckerRunnable, PING_AFTER_SUCCESS_CHECK_INTERVAL);
|
2015-01-14 12:12:47 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void onError(int errorCode, String description) {
|
|
|
|
// Notify a connection error
|
|
|
|
notifyConnectionError(errorCode, description, playerEventsObservers);
|
|
|
|
checkerHandler.postDelayed(tcpCheckerRunnable, PING_AFTER_ERROR_CHECK_INTERVAL);
|
|
|
|
}
|
|
|
|
}, checkerHandler);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2016-11-30 13:20:21 +01:00
|
|
|
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;
|
|
|
|
|
2015-01-14 12:12:47 +01:00
|
|
|
public HostConnectionObserver(HostConnection connection) {
|
2016-11-30 13:20:21 +01:00
|
|
|
this.hostState = new HostState();
|
2015-01-14 12:12:47 +01:00
|
|
|
this.connection = connection;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Registers a new observer that will be notified about player events
|
|
|
|
* @param observer Observer
|
|
|
|
*/
|
2015-02-15 20:11:32 +01:00
|
|
|
public void registerPlayerObserver(PlayerEventsObserver observer, boolean replyImmediately) {
|
2015-01-14 12:12:47 +01:00
|
|
|
if (this.connection == null)
|
|
|
|
return;
|
|
|
|
|
2019-02-08 17:42:33 +01:00
|
|
|
if (!playerEventsObservers.contains(observer))
|
2019-02-07 17:39:56 +01:00
|
|
|
playerEventsObservers.add(observer);
|
2015-01-14 12:12:47 +01:00
|
|
|
|
2015-02-15 20:11:32 +01:00
|
|
|
if (replyImmediately) replyWithLastResult(observer);
|
|
|
|
|
2015-01-14 12:12:47 +01:00
|
|
|
if (playerEventsObservers.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.registerPlayerNotificationsObserver(this, checkerHandler);
|
|
|
|
connection.registerSystemNotificationsObserver(this, checkerHandler);
|
|
|
|
connection.registerInputNotificationsObserver(this, checkerHandler);
|
|
|
|
}
|
2019-02-08 17:42:33 +01:00
|
|
|
startCheckerHandler();
|
2015-01-14 12:12:47 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Unregisters a previously registered observer
|
|
|
|
* @param observer Observer to unregister
|
|
|
|
*/
|
2015-02-15 20:11:32 +01:00
|
|
|
public void unregisterPlayerObserver(PlayerEventsObserver observer) {
|
2015-01-14 12:12:47 +01:00
|
|
|
playerEventsObservers.remove(observer);
|
|
|
|
|
2016-11-30 13:20:21 +01:00
|
|
|
LogUtils.LOGD(TAG, "Unregistering player observer " + observer.getClass().getSimpleName() +
|
|
|
|
". Still got " + playerEventsObservers.size() +
|
2015-01-14 12:12:47 +01:00
|
|
|
" observers.");
|
|
|
|
|
2016-06-29 12:04:24 +02:00
|
|
|
if (playerEventsObservers.isEmpty()) {
|
2015-01-14 12:12:47 +01:00
|
|
|
// No more observers, so unregister us from the host connection, or stop
|
|
|
|
// the http checker thread
|
|
|
|
if (connection.getProtocol() == HostConnection.PROTOCOL_TCP) {
|
|
|
|
connection.unregisterPlayerNotificationsObserver(this);
|
|
|
|
connection.unregisterSystemNotificationsObserver(this);
|
|
|
|
connection.unregisterInputNotificationsObserver(this);
|
2016-11-30 13:20:21 +01:00
|
|
|
}
|
|
|
|
hostState.lastCallResult = PlayerEventsObserver.PLAYER_NO_RESULT;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Registers a new observer that will be notified about application events
|
|
|
|
* @param observer Observer
|
2019-02-08 17:42:33 +01:00
|
|
|
* @param replyImmediately Wether to immediatlely issue a reply with the current status
|
2016-11-30 13:20:21 +01:00
|
|
|
*/
|
|
|
|
public void registerApplicationObserver(ApplicationEventsObserver observer, boolean replyImmediately) {
|
|
|
|
if (this.connection == null)
|
|
|
|
return;
|
|
|
|
|
2019-02-08 17:42:33 +01:00
|
|
|
if (!applicationEventsObservers.contains(observer))
|
2019-02-07 17:39:56 +01:00
|
|
|
applicationEventsObservers.add(observer);
|
2016-11-30 13:20:21 +01:00
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
2019-02-08 17:42:33 +01:00
|
|
|
startCheckerHandler();
|
2016-11-30 13:20:21 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Unregisters a previously registered observer
|
|
|
|
* @param observer Observer to unregister
|
|
|
|
*/
|
2016-12-02 12:04:17 +01:00
|
|
|
public void unregisterApplicationObserver(ApplicationEventsObserver observer) {
|
2016-11-30 13:20:21 +01:00
|
|
|
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) {
|
2017-07-13 20:10:49 +02:00
|
|
|
connection.unregisterApplicationNotificationsObserver(this);
|
2015-01-14 12:12:47 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-03-30 12:08:58 +01:00
|
|
|
/**
|
|
|
|
* 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) {
|
|
|
|
if (this.connection == null)
|
2019-02-08 17:42:33 +01:00
|
|
|
return;
|
|
|
|
|
2019-03-30 12:08:58 +01:00
|
|
|
if ( ! playlistEventsObservers.contains(observer) ) {
|
|
|
|
playlistEventsObservers.add(observer);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (playlistEventsObservers.size() == 1) {
|
|
|
|
if (connection.getProtocol() == HostConnection.PROTOCOL_TCP) {
|
|
|
|
connection.registerPlaylistNotificationsObserver(this, checkerHandler);
|
|
|
|
}
|
|
|
|
|
|
|
|
startCheckerHandler();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (replyImmediately)
|
|
|
|
checkPlaylist();
|
|
|
|
}
|
|
|
|
|
|
|
|
public void unregisterPlaylistObserver(PlayerEventsObserver observer) {
|
|
|
|
playlistEventsObservers.remove(observer);
|
|
|
|
|
|
|
|
LogUtils.LOGD(TAG, "Unregistering playlist observer " + observer.getClass().getSimpleName() +
|
|
|
|
". Still got " + playlistEventsObservers.size() +
|
|
|
|
" observers.");
|
|
|
|
|
|
|
|
if (playlistEventsObservers.isEmpty()) {
|
|
|
|
// No more observers, so unregister us from the host connection, or stop
|
|
|
|
// the http checker thread
|
|
|
|
if (connection.getProtocol() == HostConnection.PROTOCOL_TCP) {
|
|
|
|
connection.unregisterPlaylistNotificationsObserver(this);
|
|
|
|
}
|
2019-02-08 17:42:33 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-01-14 12:12:47 +01:00
|
|
|
/**
|
|
|
|
* Unregisters all observers
|
|
|
|
*/
|
2015-02-15 20:11:32 +01:00
|
|
|
public void stopObserving() {
|
|
|
|
for (final PlayerEventsObserver observer : playerEventsObservers)
|
|
|
|
observer.observerOnStopObserving();
|
|
|
|
|
2015-01-14 12:12:47 +01:00
|
|
|
playerEventsObservers.clear();
|
|
|
|
|
|
|
|
if (connection.getProtocol() == HostConnection.PROTOCOL_TCP) {
|
|
|
|
connection.unregisterPlayerNotificationsObserver(this);
|
|
|
|
connection.unregisterSystemNotificationsObserver(this);
|
|
|
|
connection.unregisterInputNotificationsObserver(this);
|
2019-02-07 17:39:56 +01:00
|
|
|
connection.unregisterApplicationNotificationsObserver(this);
|
2019-03-30 12:08:58 +01:00
|
|
|
connection.unregisterPlaylistNotificationsObserver(this);
|
2015-01-14 12:12:47 +01:00
|
|
|
checkerHandler.removeCallbacks(tcpCheckerRunnable);
|
|
|
|
}
|
2016-11-30 13:20:21 +01:00
|
|
|
hostState.lastCallResult = PlayerEventsObserver.PLAYER_NO_RESULT;
|
2015-01-14 12:12:47 +01:00
|
|
|
}
|
|
|
|
|
2017-07-13 20:10:49 +02:00
|
|
|
@Override
|
|
|
|
public void onPropertyChanged(org.xbmc.kore.jsonrpc.notification.Player.OnPropertyChanged notification) {
|
|
|
|
List<PlayerEventsObserver> allObservers = new ArrayList<>(playerEventsObservers);
|
|
|
|
for (final PlayerEventsObserver observer : allObservers) {
|
|
|
|
observer.playerOnPropertyChanged(notification.data);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-01-14 12:12:47 +01:00
|
|
|
/**
|
|
|
|
* The {@link HostConnection.PlayerNotificationsObserver} interface methods
|
2019-02-08 17:42:33 +01:00
|
|
|
* Start the chain calls to get whats playing
|
2015-01-14 12:12:47 +01:00
|
|
|
*/
|
2015-03-09 22:35:18 +01:00
|
|
|
public void onPlay(org.xbmc.kore.jsonrpc.notification.Player.OnPlay notification) {
|
2019-02-08 17:42:33 +01:00
|
|
|
// Ignore this if Kodi is Leia or higher, as we'll be properly notified via OnAVStart
|
|
|
|
// See https://github.com/xbmc/Kore/issues/602 and https://github.com/xbmc/xbmc/pull/13726
|
2019-03-30 12:08:58 +01:00
|
|
|
// Note: OnPlay is still required for picture items.
|
|
|
|
if (connection.getHostInfo().isLeiaOrLater() &&
|
|
|
|
! notification.data.item.type.contentEquals("picture") ) {
|
2019-02-08 17:42:33 +01:00
|
|
|
LogUtils.LOGD(TAG, "OnPlay notification ignored. Will wait for OnAVStart.");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
checkWhatsPlaying();
|
2015-01-14 12:12:47 +01:00
|
|
|
}
|
|
|
|
|
2018-05-03 21:09:34 +02:00
|
|
|
public void onResume(org.xbmc.kore.jsonrpc.notification.Player.OnResume notification) {
|
2019-02-08 17:42:33 +01:00
|
|
|
checkWhatsPlaying();
|
2015-01-14 12:12:47 +01:00
|
|
|
}
|
|
|
|
|
2018-05-03 21:09:34 +02:00
|
|
|
public void onPause(org.xbmc.kore.jsonrpc.notification.Player.OnPause notification) {
|
2019-02-08 17:42:33 +01:00
|
|
|
checkWhatsPlaying();
|
2018-05-03 21:09:34 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
public void onSpeedChanged(org.xbmc.kore.jsonrpc.notification.Player.OnSpeedChanged notification) {
|
2019-02-08 17:42:33 +01:00
|
|
|
checkWhatsPlaying();
|
2015-01-14 12:12:47 +01:00
|
|
|
}
|
|
|
|
|
2015-03-09 22:35:18 +01:00
|
|
|
public void onSeek(org.xbmc.kore.jsonrpc.notification.Player.OnSeek notification) {
|
2019-02-08 17:42:33 +01:00
|
|
|
checkWhatsPlaying();
|
2015-01-14 12:12:47 +01:00
|
|
|
}
|
|
|
|
|
2015-03-09 22:35:18 +01:00
|
|
|
public void onStop(org.xbmc.kore.jsonrpc.notification.Player.OnStop notification) {
|
2019-02-08 17:42:33 +01:00
|
|
|
// We could directly notify that nothing is playing here, but in Kodi Leia everytime
|
|
|
|
// there's a playlist change, onStop is triggered, which caused the UI to display
|
|
|
|
// that nothing was being played. Checking what's playing prevents this.
|
|
|
|
checkWhatsPlaying();
|
|
|
|
}
|
|
|
|
|
|
|
|
public void onAVStart(org.xbmc.kore.jsonrpc.notification.Player.OnAVStart notification) {
|
|
|
|
checkWhatsPlaying();
|
|
|
|
}
|
|
|
|
|
|
|
|
public void onAVChange(org.xbmc.kore.jsonrpc.notification.Player.OnAVChange notification) {
|
|
|
|
// Just ignore this, as it is fired by Kodi very often, and we're only
|
|
|
|
// interested in play/resume/stop changes
|
2015-01-14 12:12:47 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The {@link HostConnection.SystemNotificationsObserver} interface methods
|
|
|
|
*/
|
|
|
|
public void onQuit(System.OnQuit notification) {
|
2015-02-15 20:11:32 +01:00
|
|
|
// Copy list to prevent ConcurrentModificationExceptions
|
|
|
|
List<PlayerEventsObserver> allObservers = new ArrayList<>(playerEventsObservers);
|
|
|
|
for (final PlayerEventsObserver observer : allObservers) {
|
2015-01-14 12:12:47 +01:00
|
|
|
observer.systemOnQuit();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public void onRestart(System.OnRestart notification) {
|
2015-02-15 20:11:32 +01:00
|
|
|
// Copy list to prevent ConcurrentModificationExceptions
|
|
|
|
List<PlayerEventsObserver> allObservers = new ArrayList<>(playerEventsObservers);
|
|
|
|
for (final PlayerEventsObserver observer : allObservers) {
|
2015-01-14 12:12:47 +01:00
|
|
|
observer.systemOnQuit();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public void onSleep(System.OnSleep notification) {
|
2015-02-15 20:11:32 +01:00
|
|
|
// Copy list to prevent ConcurrentModificationExceptions
|
|
|
|
List<PlayerEventsObserver> allObservers = new ArrayList<>(playerEventsObservers);
|
|
|
|
for (final PlayerEventsObserver observer : allObservers) {
|
2015-01-14 12:12:47 +01:00
|
|
|
observer.systemOnQuit();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public void onInputRequested(Input.OnInputRequested notification) {
|
2015-02-15 20:11:32 +01:00
|
|
|
// Copy list to prevent ConcurrentModificationExceptions
|
|
|
|
List<PlayerEventsObserver> allObservers = new ArrayList<>(playerEventsObservers);
|
|
|
|
for (final PlayerEventsObserver observer : allObservers) {
|
2015-01-14 12:12:47 +01:00
|
|
|
observer.inputOnInputRequested(notification.title, notification.type, notification.value);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-11-30 13:20:21 +01:00
|
|
|
@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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-03-30 12:08:58 +01:00
|
|
|
@Override
|
|
|
|
public void onPlaylistCleared(Playlist.OnClear notification) {
|
|
|
|
for (PlaylistEventsObserver observer : playlistEventsObservers) {
|
|
|
|
observer.playlistOnClear(notification.playlistId);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void onPlaylistItemAdded(Playlist.OnAdd notification) {
|
|
|
|
for (PlaylistEventsObserver observer : playlistEventsObservers) {
|
|
|
|
observer.playlistChanged(notification.playlistId);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void onPlaylistItemRemoved(Playlist.OnRemove notification) {
|
|
|
|
for (PlaylistEventsObserver observer : playlistEventsObservers) {
|
|
|
|
observer.playlistChanged(notification.playlistId);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private void startCheckerHandler() {
|
|
|
|
// Check if checkerHandler is already running, to prevent multiple runnables to be posted
|
|
|
|
// when multiple observers are registered.
|
|
|
|
if (checkerHandler.hasMessages(0))
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (connection.getProtocol() == HostConnection.PROTOCOL_TCP) {
|
|
|
|
checkerHandler.post(tcpCheckerRunnable);
|
|
|
|
} else {
|
|
|
|
checkerHandler.post(httpCheckerRunnable);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-11-30 13:20:21 +01:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2019-03-30 12:08:58 +01:00
|
|
|
private ArrayList<GetPlaylist.GetPlaylistResult> prevGetPlaylistResults = new ArrayList<>();
|
|
|
|
private boolean isCheckingPlaylist = false;
|
|
|
|
private void checkPlaylist() {
|
|
|
|
if (isCheckingPlaylist)
|
|
|
|
return;
|
|
|
|
|
|
|
|
isCheckingPlaylist = true;
|
|
|
|
|
|
|
|
connection.execute(new GetPlaylist(connection), new ApiCallback<ArrayList<GetPlaylist.GetPlaylistResult>>() {
|
|
|
|
@Override
|
|
|
|
public void onSuccess(ArrayList<GetPlaylist.GetPlaylistResult> result) {
|
|
|
|
isCheckingPlaylist = false;
|
|
|
|
|
|
|
|
if (result.isEmpty()) {
|
|
|
|
callPlaylistsOnClear(prevGetPlaylistResults);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (PlaylistEventsObserver observer : playlistEventsObservers) {
|
|
|
|
observer.playlistsAvailable(result);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Handle onClear for HTTP only connections
|
|
|
|
for (GetPlaylist.GetPlaylistResult getPlaylistResult : result) {
|
|
|
|
for (int i = 0; i < prevGetPlaylistResults.size(); i++) {
|
|
|
|
if (getPlaylistResult.id == prevGetPlaylistResults.get(i).id) {
|
|
|
|
prevGetPlaylistResults.remove(i);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
callPlaylistsOnClear(prevGetPlaylistResults);
|
|
|
|
|
|
|
|
prevGetPlaylistResults = result;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void onError(int errorCode, String description) {
|
|
|
|
isCheckingPlaylist = false;
|
|
|
|
|
|
|
|
for (PlaylistEventsObserver observer : playlistEventsObservers) {
|
|
|
|
observer.playlistOnError(errorCode, description);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}, new Handler());
|
|
|
|
}
|
|
|
|
|
|
|
|
private void callPlaylistsOnClear(ArrayList<GetPlaylist.GetPlaylistResult> clearedPlaylists) {
|
|
|
|
for (GetPlaylist.GetPlaylistResult getPlaylistResult : clearedPlaylists) {
|
|
|
|
for (PlaylistEventsObserver observer : playlistEventsObservers) {
|
|
|
|
observer.playlistOnClear(getPlaylistResult.id);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-02-08 17:42:33 +01:00
|
|
|
/**
|
|
|
|
* Indicator set when we are calling Kodi to check what's playing, so that we don't call it
|
|
|
|
* while there are still pending calls
|
|
|
|
*/
|
|
|
|
private boolean checkingWhatsPlaying = false;
|
|
|
|
|
2015-01-14 12:12:47 +01:00
|
|
|
/**
|
|
|
|
* Checks whats playing and notifies observers
|
|
|
|
*/
|
|
|
|
private void checkWhatsPlaying() {
|
2019-02-08 17:42:33 +01:00
|
|
|
// We don't properly protect this against race conditions because it's
|
|
|
|
// not worth the trouble - we can safely call Kodi multiple times.
|
|
|
|
if (checkingWhatsPlaying) {
|
|
|
|
LogUtils.LOGD(TAG, "Already checking whats playing, returning");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
checkingWhatsPlaying = true;
|
2015-01-14 12:12:47 +01:00
|
|
|
LogUtils.LOGD(TAG, "Checking whats playing");
|
|
|
|
|
|
|
|
// Start the calls: Player.GetActivePlayers -> Player.GetProperties -> Player.GetItem
|
|
|
|
chainCallGetActivePlayers();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Calls Player.GetActivePlayers
|
2016-11-30 13:20:21 +01:00
|
|
|
* On success chains execution to chainCallGetPlayerProperties
|
2015-01-14 12:12:47 +01:00
|
|
|
*/
|
|
|
|
private void chainCallGetActivePlayers() {
|
|
|
|
Player.GetActivePlayers getActivePlayers = new Player.GetActivePlayers();
|
|
|
|
getActivePlayers.execute(connection, new ApiCallback<ArrayList<PlayerType.GetActivePlayersReturnType>>() {
|
|
|
|
@Override
|
2015-03-16 19:33:40 +01:00
|
|
|
public void onSuccess(ArrayList<PlayerType.GetActivePlayersReturnType> result) {
|
2015-01-14 12:12:47 +01:00
|
|
|
if (result.isEmpty()) {
|
|
|
|
LogUtils.LOGD(TAG, "Nothing is playing");
|
|
|
|
notifyNothingIsPlaying(playerEventsObservers);
|
|
|
|
return;
|
|
|
|
}
|
2016-11-30 13:20:21 +01:00
|
|
|
chainCallGetPlayerProperties(result.get(0));
|
2015-01-14 12:12:47 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void onError(int errorCode, String description) {
|
|
|
|
LogUtils.LOGD(TAG, "Notifying error");
|
|
|
|
notifyConnectionError(errorCode, description, playerEventsObservers);
|
|
|
|
}
|
|
|
|
}, checkerHandler);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Calls Player.GetProperties
|
|
|
|
* On success chains execution to chainCallGetItem
|
|
|
|
*/
|
2016-11-30 13:20:21 +01:00
|
|
|
private void chainCallGetPlayerProperties(final PlayerType.GetActivePlayersReturnType getActivePlayersResult) {
|
2015-01-14 12:12:47 +01:00
|
|
|
String propertiesToGet[] = new String[] {
|
|
|
|
// Check is something more is needed
|
|
|
|
PlayerType.PropertyName.SPEED,
|
|
|
|
PlayerType.PropertyName.PERCENTAGE,
|
|
|
|
PlayerType.PropertyName.POSITION,
|
|
|
|
PlayerType.PropertyName.TIME,
|
|
|
|
PlayerType.PropertyName.TOTALTIME,
|
|
|
|
PlayerType.PropertyName.REPEAT,
|
|
|
|
PlayerType.PropertyName.SHUFFLED,
|
|
|
|
PlayerType.PropertyName.CURRENTAUDIOSTREAM,
|
|
|
|
PlayerType.PropertyName.CURRENTSUBTITLE,
|
|
|
|
PlayerType.PropertyName.AUDIOSTREAMS,
|
|
|
|
PlayerType.PropertyName.SUBTITLES,
|
2015-03-17 19:45:21 +01:00
|
|
|
PlayerType.PropertyName.PLAYLISTID,
|
2019-03-30 12:08:58 +01:00
|
|
|
};
|
2015-01-14 12:12:47 +01:00
|
|
|
|
|
|
|
Player.GetProperties getProperties = new Player.GetProperties(getActivePlayersResult.playerid, propertiesToGet);
|
|
|
|
getProperties.execute(connection, new ApiCallback<PlayerType.PropertyValue>() {
|
|
|
|
@Override
|
2015-03-16 19:33:40 +01:00
|
|
|
public void onSuccess(PlayerType.PropertyValue result) {
|
2015-01-14 12:12:47 +01:00
|
|
|
chainCallGetItem(getActivePlayersResult, result);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void onError(int errorCode, String description) {
|
|
|
|
notifyConnectionError(errorCode, description, playerEventsObservers);
|
|
|
|
}
|
|
|
|
}, checkerHandler);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Calls Player.GetItem
|
|
|
|
* On success notifies observers
|
|
|
|
*/
|
|
|
|
private void chainCallGetItem(final PlayerType.GetActivePlayersReturnType getActivePlayersResult,
|
|
|
|
final PlayerType.PropertyValue getPropertiesResult) {
|
|
|
|
// COMMENT, LYRICS, MUSICBRAINZTRACKID, MUSICBRAINZARTISTID, MUSICBRAINZALBUMID,
|
|
|
|
// MUSICBRAINZALBUMARTISTID, TRAILER, ORIGINALTITLE, LASTPLAYED, MPAA, COUNTRY,
|
|
|
|
// PRODUCTIONCODE, SET, SHOWLINK, FILE,
|
|
|
|
// ARTISTID, ALBUMID, TVSHOW_ID, SETID, WATCHEDEPISODES, DISC, TAG, GENREID,
|
|
|
|
// ALBUMARTISTID, DESCRIPTION, THEME, MOOD, STYLE, ALBUMLABEL, SORTTITLE, UNIQUEID,
|
|
|
|
// DATEADDED, CHANNEL, CHANNELTYPE, HIDDEN, LOCKED, CHANNELNUMBER, STARTTIME, ENDTIME,
|
|
|
|
// EPISODEGUIDE, ORIGINALTITLE, PLAYCOUNT, PLOTOUTLINE, SET,
|
|
|
|
String[] propertiesToGet = new String[] {
|
|
|
|
ListType.FieldsAll.ART,
|
|
|
|
ListType.FieldsAll.ARTIST,
|
|
|
|
ListType.FieldsAll.ALBUMARTIST,
|
|
|
|
ListType.FieldsAll.ALBUM,
|
|
|
|
ListType.FieldsAll.CAST,
|
|
|
|
ListType.FieldsAll.DIRECTOR,
|
|
|
|
ListType.FieldsAll.DISPLAYARTIST,
|
|
|
|
ListType.FieldsAll.DURATION,
|
|
|
|
ListType.FieldsAll.EPISODE,
|
|
|
|
ListType.FieldsAll.FANART,
|
|
|
|
ListType.FieldsAll.FILE,
|
|
|
|
ListType.FieldsAll.FIRSTAIRED,
|
|
|
|
ListType.FieldsAll.GENRE,
|
|
|
|
ListType.FieldsAll.IMDBNUMBER,
|
|
|
|
ListType.FieldsAll.PLOT,
|
|
|
|
ListType.FieldsAll.PREMIERED,
|
|
|
|
ListType.FieldsAll.RATING,
|
|
|
|
ListType.FieldsAll.RESUME,
|
|
|
|
ListType.FieldsAll.RUNTIME,
|
|
|
|
ListType.FieldsAll.SEASON,
|
|
|
|
ListType.FieldsAll.SHOWTITLE,
|
|
|
|
ListType.FieldsAll.STREAMDETAILS,
|
|
|
|
ListType.FieldsAll.STUDIO,
|
|
|
|
ListType.FieldsAll.TAGLINE,
|
|
|
|
ListType.FieldsAll.THUMBNAIL,
|
|
|
|
ListType.FieldsAll.TITLE,
|
|
|
|
ListType.FieldsAll.TOP250,
|
|
|
|
ListType.FieldsAll.TRACK,
|
|
|
|
ListType.FieldsAll.VOTES,
|
|
|
|
ListType.FieldsAll.WRITER,
|
|
|
|
ListType.FieldsAll.YEAR,
|
|
|
|
ListType.FieldsAll.DESCRIPTION,
|
2019-03-30 12:08:58 +01:00
|
|
|
};
|
2015-01-14 12:12:47 +01:00
|
|
|
// propertiesToGet = ListType.FieldsAll.allValues;
|
|
|
|
Player.GetItem getItem = new Player.GetItem(getActivePlayersResult.playerid, propertiesToGet);
|
|
|
|
getItem.execute(connection, new ApiCallback<ListType.ItemsAll>() {
|
|
|
|
@Override
|
2015-03-16 19:33:40 +01:00
|
|
|
public void onSuccess(ListType.ItemsAll result) {
|
2015-01-14 12:12:47 +01:00
|
|
|
// Ok, now we got a result
|
|
|
|
notifySomethingIsPlaying(getActivePlayersResult, getPropertiesResult, result, playerEventsObservers);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void onError(int errorCode, String description) {
|
|
|
|
notifyConnectionError(errorCode, description, playerEventsObservers);
|
|
|
|
}
|
|
|
|
}, checkerHandler);
|
|
|
|
}
|
|
|
|
|
2016-11-30 13:20:21 +01:00
|
|
|
// Whether to force a reply or if the results are equal to the last one, don't reply
|
2015-01-14 12:12:47 +01:00
|
|
|
private boolean forceReply = false;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Notifies a list of observers of a connection error
|
|
|
|
* Only notifies them if the result is different from the last one
|
|
|
|
* @param errorCode Error code to report
|
|
|
|
* @param description Description to report
|
|
|
|
* @param observers List of observers
|
|
|
|
*/
|
|
|
|
private void notifyConnectionError(final int errorCode, final String description, List<PlayerEventsObserver> observers) {
|
2019-02-08 17:42:33 +01:00
|
|
|
checkingWhatsPlaying = false;
|
2015-01-14 12:12:47 +01:00
|
|
|
// Reply if different from last result
|
|
|
|
if (forceReply ||
|
2016-11-30 13:20:21 +01:00
|
|
|
(hostState.lastCallResult != PlayerEventsObserver.PLAYER_CONNECTION_ERROR) ||
|
|
|
|
(hostState.lastErrorCode != errorCode)) {
|
|
|
|
hostState.lastCallResult = PlayerEventsObserver.PLAYER_CONNECTION_ERROR;
|
|
|
|
hostState.lastErrorCode = errorCode;
|
|
|
|
hostState.lastErrorDescription = description;
|
2015-01-14 12:12:47 +01:00
|
|
|
forceReply = false;
|
2015-02-15 20:11:32 +01:00
|
|
|
// Copy list to prevent ConcurrentModificationExceptions
|
|
|
|
List<PlayerEventsObserver> allObservers = new ArrayList<>(observers);
|
|
|
|
for (final PlayerEventsObserver observer : allObservers) {
|
2015-01-14 12:12:47 +01:00
|
|
|
notifyConnectionError(errorCode, description, observer);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Notifies a specific observer of a connection error
|
|
|
|
* Always notifies the observer, and doesn't save results in last call
|
|
|
|
* @param errorCode Error code to report
|
|
|
|
* @param description Description to report
|
|
|
|
* @param observer Observers
|
|
|
|
*/
|
|
|
|
private void notifyConnectionError(final int errorCode, final String description, PlayerEventsObserver observer) {
|
|
|
|
observer.playerOnConnectionError(errorCode, description);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Nothing is playing, notify observers calling playerOnStop
|
|
|
|
* Only notifies them if the result is different from the last one
|
|
|
|
* @param observers List of observers
|
|
|
|
*/
|
|
|
|
private void notifyNothingIsPlaying(List<PlayerEventsObserver> observers) {
|
2019-02-08 17:42:33 +01:00
|
|
|
checkingWhatsPlaying = false;
|
2015-01-14 12:12:47 +01:00
|
|
|
// Reply if forced or different from last result
|
|
|
|
if (forceReply ||
|
2016-11-30 13:20:21 +01:00
|
|
|
(hostState.lastCallResult != PlayerEventsObserver.PLAYER_IS_STOPPED)) {
|
|
|
|
hostState.lastCallResult = PlayerEventsObserver.PLAYER_IS_STOPPED;
|
2015-01-14 12:12:47 +01:00
|
|
|
forceReply = false;
|
2015-02-15 20:11:32 +01:00
|
|
|
// Copy list to prevent ConcurrentModificationExceptions
|
|
|
|
List<PlayerEventsObserver> allObservers = new ArrayList<>(observers);
|
|
|
|
for (final PlayerEventsObserver observer : allObservers) {
|
2015-01-14 12:12:47 +01:00
|
|
|
notifyNothingIsPlaying(observer);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Notifies a specific observer
|
|
|
|
* Always notifies the observer, and doesn't save results in last call
|
|
|
|
* @param observer Observer
|
|
|
|
*/
|
|
|
|
private void notifyNothingIsPlaying(PlayerEventsObserver observer) {
|
|
|
|
observer.playerOnStop();
|
|
|
|
}
|
|
|
|
|
2018-06-09 19:46:05 +02:00
|
|
|
private boolean getPropertiesResultChanged(PlayerType.PropertyValue getPropertiesResult) {
|
|
|
|
return (hostState.lastGetPropertiesResult == null) ||
|
2019-03-30 12:08:58 +01:00
|
|
|
(hostState.lastGetPropertiesResult.speed != getPropertiesResult.speed) ||
|
|
|
|
(hostState.lastGetPropertiesResult.shuffled != getPropertiesResult.shuffled) ||
|
|
|
|
(!hostState.lastGetPropertiesResult.repeat.equals(getPropertiesResult.repeat));
|
2018-06-09 19:46:05 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
private boolean getItemResultChanged(ListType.ItemsAll getItemResult) {
|
|
|
|
return (hostState.lastGetItemResult == null) ||
|
2019-03-30 12:08:58 +01:00
|
|
|
(hostState.lastGetItemResult.id != getItemResult.id) ||
|
|
|
|
(!hostState.lastGetItemResult.label.equals(getItemResult.label));
|
2018-06-09 19:46:05 +02:00
|
|
|
}
|
|
|
|
|
2015-01-14 12:12:47 +01:00
|
|
|
/**
|
|
|
|
* Something is playing or paused, notify observers
|
|
|
|
* Only notifies them if the result is different from the last one
|
2019-02-08 17:42:33 +01:00
|
|
|
* @param getActivePlayersResult Previous call result
|
|
|
|
* @param getPropertiesResult Previous call result
|
|
|
|
* @param getItemResult Previous call result
|
2015-01-14 12:12:47 +01:00
|
|
|
* @param observers List of observers
|
|
|
|
*/
|
|
|
|
private void notifySomethingIsPlaying(final PlayerType.GetActivePlayersReturnType getActivePlayersResult,
|
|
|
|
final PlayerType.PropertyValue getPropertiesResult,
|
|
|
|
final ListType.ItemsAll getItemResult,
|
|
|
|
List<PlayerEventsObserver> observers) {
|
2019-02-08 17:42:33 +01:00
|
|
|
checkingWhatsPlaying = false;
|
2015-01-14 12:12:47 +01:00
|
|
|
int currentCallResult = (getPropertiesResult.speed == 0) ?
|
2019-03-30 12:08:58 +01:00
|
|
|
PlayerEventsObserver.PLAYER_IS_PAUSED : PlayerEventsObserver.PLAYER_IS_PLAYING;
|
2015-01-14 12:12:47 +01:00
|
|
|
if (forceReply ||
|
2019-03-30 12:08:58 +01:00
|
|
|
(hostState.lastCallResult != currentCallResult) ||
|
|
|
|
getPropertiesResultChanged(getPropertiesResult) ||
|
|
|
|
getItemResultChanged(getItemResult)) {
|
2016-11-30 13:20:21 +01:00
|
|
|
hostState.lastCallResult = currentCallResult;
|
|
|
|
hostState.lastGetActivePlayerResult = getActivePlayersResult;
|
|
|
|
hostState.lastGetPropertiesResult = getPropertiesResult;
|
|
|
|
hostState.lastGetItemResult = getItemResult;
|
2015-01-14 12:12:47 +01:00
|
|
|
forceReply = false;
|
2015-02-15 20:11:32 +01:00
|
|
|
// Copy list to prevent ConcurrentModificationExceptions
|
|
|
|
List<PlayerEventsObserver> allObservers = new ArrayList<>(observers);
|
|
|
|
for (final PlayerEventsObserver observer : allObservers) {
|
2015-01-14 12:12:47 +01:00
|
|
|
notifySomethingIsPlaying(getActivePlayersResult, getPropertiesResult, getItemResult, observer);
|
|
|
|
}
|
|
|
|
}
|
2015-05-25 23:37:32 +02:00
|
|
|
|
|
|
|
// Workaround for when playing has started but time info isn't updated yet.
|
|
|
|
// See https://github.com/xbmc/Kore/issues/78#issuecomment-104148064
|
|
|
|
// If the playing time returned is 0sec, we'll schedule another check
|
|
|
|
// to give Kodi some time to report the correct playing time
|
|
|
|
if ((currentCallResult == PlayerEventsObserver.PLAYER_IS_PLAYING) &&
|
|
|
|
(connection.getProtocol() == HostConnection.PROTOCOL_TCP) &&
|
|
|
|
(getPropertiesResult.time.ToSeconds() == 0)) {
|
2019-02-08 17:42:33 +01:00
|
|
|
LogUtils.LOGD(TAG, "Scheduling new call to check what's playing because time is 0.");
|
2015-05-25 23:37:32 +02:00
|
|
|
final int RECHECK_INTERVAL = 3000;
|
|
|
|
checkerHandler.postDelayed(new Runnable() {
|
|
|
|
@Override
|
|
|
|
public void run() {
|
|
|
|
forceReply = true;
|
|
|
|
checkWhatsPlaying();
|
|
|
|
}
|
|
|
|
}, RECHECK_INTERVAL);
|
|
|
|
}
|
2015-01-14 12:12:47 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Something is playing or paused, notify a specific observer
|
|
|
|
* Always notifies the observer, and doesn't save results in last call
|
2019-02-08 17:42:33 +01:00
|
|
|
* @param getActivePlayersResult Previous call result
|
|
|
|
* @param getPropertiesResult Previous call result
|
|
|
|
* @param getItemResult Previous call result
|
2015-01-14 12:12:47 +01:00
|
|
|
* @param observer Specific observer
|
|
|
|
*/
|
|
|
|
private void notifySomethingIsPlaying(final PlayerType.GetActivePlayersReturnType getActivePlayersResult,
|
|
|
|
final PlayerType.PropertyValue getPropertiesResult,
|
|
|
|
final ListType.ItemsAll getItemResult,
|
|
|
|
PlayerEventsObserver observer) {
|
|
|
|
if (getPropertiesResult.speed == 0) {
|
|
|
|
// Paused
|
|
|
|
observer.playerOnPause(getActivePlayersResult, getPropertiesResult, getItemResult);
|
|
|
|
} else {
|
|
|
|
// Playing
|
|
|
|
observer.playerOnPlay(getActivePlayersResult, getPropertiesResult, getItemResult);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Replies to the observer with the last result we got.
|
|
|
|
* If we have no result, nothing will be called on the observer interface.
|
2017-07-13 20:10:49 +02:00
|
|
|
* @param observer Observer to call with last result
|
2015-01-14 12:12:47 +01:00
|
|
|
*/
|
|
|
|
public void replyWithLastResult(PlayerEventsObserver observer) {
|
2016-11-30 13:20:21 +01:00
|
|
|
switch (hostState.lastCallResult) {
|
2015-01-14 12:12:47 +01:00
|
|
|
case PlayerEventsObserver.PLAYER_CONNECTION_ERROR:
|
2016-11-30 13:20:21 +01:00
|
|
|
notifyConnectionError(hostState.lastErrorCode, hostState.lastErrorDescription, observer);
|
2015-01-14 12:12:47 +01:00
|
|
|
break;
|
|
|
|
case PlayerEventsObserver.PLAYER_IS_STOPPED:
|
|
|
|
notifyNothingIsPlaying(observer);
|
|
|
|
break;
|
|
|
|
case PlayerEventsObserver.PLAYER_IS_PAUSED:
|
|
|
|
case PlayerEventsObserver.PLAYER_IS_PLAYING:
|
2016-11-30 13:20:21 +01:00
|
|
|
notifySomethingIsPlaying(hostState.lastGetActivePlayerResult, hostState.lastGetPropertiesResult, hostState.lastGetItemResult, observer);
|
2015-01-14 12:12:47 +01:00
|
|
|
break;
|
|
|
|
case PlayerEventsObserver.PLAYER_NO_RESULT:
|
|
|
|
observer.playerNoResultsYet();
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Forces a refresh of the current cached results
|
|
|
|
*/
|
|
|
|
public void forceRefreshResults() {
|
2019-02-08 17:42:33 +01:00
|
|
|
LogUtils.LOGD(TAG, "Forcing a refresh");
|
2015-01-14 12:12:47 +01:00
|
|
|
forceReply = true;
|
2019-02-08 17:42:33 +01:00
|
|
|
checkWhatsPlaying();
|
2015-01-14 12:12:47 +01:00
|
|
|
}
|
2016-11-30 13:20:21 +01:00
|
|
|
|
|
|
|
public HostState getHostState() {
|
|
|
|
return hostState;
|
|
|
|
}
|
2015-01-14 12:12:47 +01:00
|
|
|
}
|